Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package aquamarine for openSUSE:Factory checked in at 2026-11-03 18:00:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/aquamarine (Old) and /work/SRC/openSUSE:Factory/.aquamarine.new.1257 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "aquamarine" Tue Nov 3 18:00:32 2026 rev:14 rq:1394710 version:0.16.0 Changes: -------- --- /work/SRC/openSUSE:Factory/aquamarine/aquamarine.changes 2026-09-24 23:02:09.445015051 +0200 +++ /work/SRC/openSUSE:Factory/.aquamarine.new.1257/aquamarine.changes 2026-11-03 18:06:39.116267543 +0100 @@ -1,0 +2,15 @@ +Tue Nov 3 13:34:26 UTC 2026 - Florian "sp1rit" <[email protected]> + +- Update to aquamarine version 0.16.0: + + More wonderful fixes for yall! + + Changes: + * wayland: fix configure not applying sometimes + * build: link OpenGL::GLES3 instead of OpenGL::OpenGL on OpenBSD + * allocator/dumb: drop fd if exported in ~dtor + * headless: reuse parent Wayland buffer formats when nested + * atomic/output: don't change max_bpc based on scanout format + * drm: fix evdi outputs by adding CPU-copy fallback + * drm: close handles even when framebuffer creation fails + * link to OpenGL::GLES3 unconditionally + +------------------------------------------------------------------- Old: ---- aquamarine-0.15.1.tar.xz New: ---- aquamarine-0.16.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ aquamarine.spec ++++++ --- /var/tmp/diff_new_pack.BE0nj6/_old 2026-11-03 18:06:40.555327913 +0100 +++ /var/tmp/diff_new_pack.BE0nj6/_new 2026-11-03 18:06:40.557327997 +0100 @@ -16,13 +16,13 @@ # -%define sover 14 +%define sover 15 %define _description %{expand: Aquamarine is a very light linux rendering backend library. It provides basic abstractions for an application to render on a Wayland session (in a window) or a native DRM session.} Name: aquamarine -Version: 0.15.1 +Version: 0.16.0 Release: 0 Summary: Rendering backend library License: BSD-3-Clause ++++++ aquamarine-0.15.1.tar.xz -> aquamarine-0.16.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/CMakeLists.txt new/aquamarine-0.16.0/CMakeLists.txt --- old/aquamarine-0.15.1/CMakeLists.txt 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/CMakeLists.txt 2026-11-01 19:07:09.000000000 +0100 @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.27) file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) string(STRIP ${VER_RAW} AQUAMARINE_VERSION) @@ -70,9 +70,8 @@ PUBLIC "./include" PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") set_target_properties(aquamarine PROPERTIES VERSION ${AQUAMARINE_VERSION} - SOVERSION 14) -target_link_libraries(aquamarine OpenGL::EGL OpenGL::OpenGL PkgConfig::deps Threads::Threads) - + SOVERSION 15) +target_link_libraries(aquamarine OpenGL::EGL OpenGL::GLES3 PkgConfig::deps Threads::Threads) check_include_file("sys/timerfd.h" HAS_TIMERFD) pkg_check_modules(epoll IMPORTED_TARGET epoll-shim) if(NOT HAS_TIMERFD AND epoll_FOUND) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/VERSION new/aquamarine-0.16.0/VERSION --- old/aquamarine-0.15.1/VERSION 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/VERSION 2026-11-01 19:07:09.000000000 +0100 @@ -1 +1 @@ -0.15.1 +0.16.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/include/aquamarine/backend/DRM.hpp new/aquamarine-0.16.0/include/aquamarine/backend/DRM.hpp --- old/aquamarine-0.15.1/include/aquamarine/backend/DRM.hpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/include/aquamarine/backend/DRM.hpp 2026-11-01 19:07:09.000000000 +0100 @@ -548,6 +548,7 @@ std::chrono::microseconds commitLeadTime = std::chrono::microseconds{2000}; Hyprutils::Memory::CSharedPointer<CDRMDumbAllocator> dumbAllocator; + bool cpuCopyFallback = false; bool atomic = false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/include/aquamarine/backend/Wayland.hpp new/aquamarine-0.16.0/include/aquamarine/backend/Wayland.hpp --- old/aquamarine-0.15.1/include/aquamarine/backend/Wayland.hpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/include/aquamarine/backend/Wayland.hpp 2026-11-01 19:07:09.000000000 +0100 @@ -64,6 +64,7 @@ Hyprutils::Memory::CSharedPointer<CWaylandBuffer> wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer); void onFrameDone(); + void applyConfigure(); void onEnter(Hyprutils::Memory::CSharedPointer<CCWlPointer> pointer, uint32_t serial); // frame loop — unified scheduler shared with DRM. See CFrameScheduler. @@ -90,6 +91,8 @@ Hyprutils::Memory::CSharedPointer<CCXdgToplevel> xdgToplevel; Hyprutils::Memory::CSharedPointer<CCWlCallback> frameCallback; Hyprutils::Math::Vector2D surfaceSize; + Hyprutils::Math::Vector2D pendingSize; + bool announced = false; } waylandState; friend class CWaylandBackend; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/include/aquamarine/output/Output.hpp new/aquamarine-0.16.0/include/aquamarine/output/Output.hpp --- old/aquamarine-0.15.1/include/aquamarine/output/Output.hpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/include/aquamarine/output/Output.hpp 2026-11-01 19:07:09.000000000 +0100 @@ -67,6 +67,7 @@ AQ_OUTPUT_STATE_WCG = (1 << 13), AQ_OUTPUT_STATE_CURSOR_SHAPE = (1 << 14), AQ_OUTPUT_STATE_CURSOR_POS = (1 << 15), + AQ_OUTPUT_STATE_MAX_BPC = (1 << 16), }; struct SInternalState { @@ -89,6 +90,7 @@ hdr_output_metadata hdrMetadata = {}; uint16_t contentType = DRM_MODE_CONTENT_TYPE_GRAPHICS; eOutputColorRange colorRange = AQ_OUTPUT_COLOR_RANGE_AUTO; + uint8_t maxBpc = 0; // max link bits per component. 0 = unset (use the highest the connector allows) }; class CSnapshot { @@ -100,11 +102,11 @@ int error() const; private: - CSnapshot(const COutputState* owner, const SInternalState& state, const std::array<uint64_t, 16>& generations); + CSnapshot(const COutputState* owner, const SInternalState& state, const std::array<uint64_t, 17>& generations); const COutputState* m_owner = nullptr; SInternalState m_state; - std::array<uint64_t, 16> m_generations = {}; + std::array<uint64_t, 17> m_generations = {}; Hyprutils::OS::CFileDescriptor m_explicitInFence; int m_error = 0; @@ -137,10 +139,11 @@ void setHDRMetadata(const hdr_output_metadata& metadata); void setContentType(const uint16_t drmContentType); void setColorRange(eOutputColorRange range); + void setMaxBpc(uint8_t maxBpc); private: SInternalState internalState; - std::array<uint64_t, 16> propertyGenerations = {}; + std::array<uint64_t, 17> propertyGenerations = {}; uint64_t nextGeneration = 0; void markCommitted(uint32_t properties); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/allocator/DRMDumb.cpp new/aquamarine-0.16.0/src/allocator/DRMDumb.cpp --- old/aquamarine-0.15.1/src/allocator/DRMDumb.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/allocator/DRMDumb.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -65,6 +65,9 @@ TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("DRM Dumb: dropping buffer {}", primeFD))); + if (primeFD >= 0) + close(primeFD); + if (handle == 0) return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/Headless.cpp new/aquamarine-0.16.0/src/backend/Headless.cpp --- old/aquamarine-0.15.1/src/backend/Headless.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/Headless.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -161,6 +161,16 @@ return impl->getRenderableFormats(); } + // A nested compositor allocates on its parent Wayland device. Reuse + // the advertised modifiers instead of assuming implicit/linear support. + for (const auto& impl : backend->getImplementations()) { + if (impl->type() != AQ_BACKEND_WAYLAND) + continue; + auto formats = impl->getRenderFormats(); + if (!formats.empty()) + return formats; + } + // formats probably supported by EGL return {SDRMFormat{.drmFormat = DRM_FORMAT_XRGB8888, .modifiers = {DRM_FORMAT_INVALID}}, SDRMFormat{.drmFormat = DRM_FORMAT_XBGR8888, .modifiers = {DRM_FORMAT_INVALID}}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/Wayland.cpp new/aquamarine-0.16.0/src/backend/Wayland.cpp --- old/aquamarine-0.15.1/src/backend/Wayland.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/Wayland.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -182,7 +182,8 @@ o->self = o; if (backend->ready) o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); - idleCallbacks.emplace_back([this, o]() { backend->events.newOutput.emit(SP<IOutput>(o)); }); + // Start the configure handshake even when there is no other host traffic. + wl_display_flush(waylandState.display); return true; } @@ -229,12 +230,17 @@ } void Aquamarine::CWaylandBackend::onReady() { - for (auto const& o : outputs) { + const auto OUTPUTS = outputs; + for (auto const& o : OUTPUTS) { + if (std::ranges::find(outputs, o) == outputs.end()) + continue; + o->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock()); if (!o->swapchain) { backend->log(AQ_LOG_ERROR, std::format("Output {} failed: swapchain creation failed", o->name)); continue; } + o->applyConfigure(); } } @@ -526,6 +532,12 @@ waylandState.xdgSurface->setConfigure([this](CCXdgSurface* r, uint32_t serial) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure surface with {}", name, serial)); r->sendAckConfigure(serial); + + // Retain the acknowledged size for consumers that attach their listeners later. + if (modes.empty()) + modes.emplace_back(makeShared<SOutputMode>(SOutputMode{.refreshRate = 60000, .preferred = true})); + modes.front()->pixelSize = waylandState.pendingSize; + applyConfigure(); }); waylandState.xdgToplevel = makeShared<CCXdgToplevel>(waylandState.xdgSurface->sendGetToplevel()); @@ -545,12 +557,8 @@ w = 1280; h = 720; } - events.state.emit(SStateEvent{.size = {w, h}}); - // Kick off the first frame synchronously: the consumer expects events.frame in - // the same dispatch cycle as the toplevel configure. Deferring via scheduleFrame's - // idle races the first commit and can leave the output blank until the next event. - needsFrame = true; - sched.frameReady.emit(); + // The following xdg_surface.configure completes and acknowledges this size. + waylandState.pendingSize = {w, h}; }); waylandState.xdgToplevel->setClose([this](CCXdgToplevel* r) { destroy(); }); @@ -570,6 +578,32 @@ backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: initialized", name)); } +void Aquamarine::CWaylandOutput::applyConfigure() { + if (!backend->backend->ready || !swapchain || modes.empty()) + return; + + // Signal handlers may destroy the output. Keep it alive and stop notifying it + // if it is removed from the backend during an announcement or state change. + const auto OUTPUT = self.lock(); + const auto ACTIVE = [&]() { return std::ranges::find(backend->outputs, OUTPUT) != backend->outputs.end(); }; + if (!ACTIVE()) + return; + + if (!waylandState.announced) { + waylandState.announced = true; + backend->backend->events.newOutput.emit(SP<IOutput>(OUTPUT)); + if (!ACTIVE()) + return; + } + + events.state.emit(SStateEvent{.size = modes.front()->pixelSize}); + if (!ACTIVE()) + return; + + needsFrame = true; + sched.frameReady.emit(); +} + Aquamarine::CWaylandOutput::~CWaylandOutput() { events.destroy.emit(); // frameIdle captures a raw this and may still be queued; pull it before we die. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/drm/DRM.cpp new/aquamarine-0.16.0/src/backend/drm/DRM.cpp --- old/aquamarine-0.15.1/src/backend/drm/DRM.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/drm/DRM.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -1,3 +1,4 @@ + #include "aquamarine/output/Output.hpp" #include <algorithm> #include <aquamarine/backend/DRM.hpp> @@ -497,7 +498,7 @@ void Aquamarine::CDRMBackend::flushAsyncCommitEvents() { for (const auto& connector : connectors) { - if (connector->output && connector->output->asyncCommitEventPending) + if (connector && connector->output && connector->output->asyncCommitEventPending) emitAsyncCommitEvent(connector->output); } } @@ -586,7 +587,8 @@ } void Aquamarine::CDRMBackend::log(eBackendLogLevel l, const std::string& s) { - backend->log(l, s); + if (backend) + backend->log(l, s); } bool Aquamarine::CDRMBackend::sessionActive() { @@ -854,7 +856,7 @@ } bool Aquamarine::CDRMBackend::shouldBlit() { - return !!primary; + return primary && rendererRequired; } bool Aquamarine::CDRMBackend::initMgpu() { @@ -890,7 +892,7 @@ } bool Aquamarine::CDRMBackend::updateSecondaryRendererState(DRMFBList* deferred) { - if (!backend->ready) + if (!backend->ready || !rendererRequired) return true; if (!primary) { @@ -1120,8 +1122,7 @@ auto drmVerName = drmVer && drmVer->name ? drmVer->name : "unknown"; driver = driverFromName(drmVerName); if (driver == AQ_BACKEND_GPU_DRIVER_EVDI) { - // DisplayLink/evdi exposes KMS without a usable EGL renderer. - primary = {}; + // DisplayLink/evdi exposes KMS without a usable EGL renderer. Keep primary so its output swapchain stays LINEAR. rendererRequired = false; } @@ -2107,6 +2108,9 @@ recheckCRTCProps(); + // invalidate any stale state + invalidateFrame(); + if (!backend->backend->ready) return; @@ -2465,6 +2469,11 @@ } else drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); + if (!drmFB && backend->driver == AQ_BACKEND_GPU_DRIVER_EVDI) { + requiresSync = true; + return false; + } + if (!drmFB || drmFB->dead) { backend->log(AQ_LOG_ERROR, "drm: Asynchronous commit buffer failed to import to KMS"); return false; @@ -2571,7 +2580,8 @@ return false; } - if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && STATE.buffer->attachments.has<CDRMBufferUnimportable>()) { + if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER) && STATE.buffer->attachments.has<CDRMBufferUnimportable>() && + backend->driver != AQ_BACKEND_GPU_DRIVER_EVDI) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Cannot commit a KMS-unimportable buffer.")); return false; } @@ -2635,6 +2645,33 @@ blittedFB = LAST; } + auto nextMgpuBuffer = [&](SP<IAllocator> allocator) -> SP<IBuffer> { + if (!mgpu.swapchain) { + TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No swapchain for blit, creating")); + mgpu.swapchain = CSwapchain::create(allocator, backend.lock()); + } + + auto OPTIONS = swapchain->currentOptions(); + auto bufDma = STATE.buffer->dmabuf(); + OPTIONS.size = STATE.buffer->size; + if (OPTIONS.format == DRM_FORMAT_INVALID) + OPTIONS.format = bufDma.format; + OPTIONS.multigpu = false; // this is not a shared swapchain, and additionally, don't make it linear, nvidia would be mad + OPTIONS.cursor = false; + OPTIONS.scanout = true; + if (OPTIONS.length == 0) // releaseMgpuResources() cleared the swapchain and we committed without updating it. + OPTIONS.length = 2; + if (!mgpu.swapchain->reconfigure(OPTIONS)) { + backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain failed reconfiguring"); + return nullptr; + } + + auto buf = mgpu.swapchain->next(nullptr); + if (!buf) + backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain has no buffer"); + return buf; + }; + if (STATE.buffer && STATE.enabled) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state")); @@ -2653,31 +2690,9 @@ TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires blit, blitting")); - if (!mgpu.swapchain) { - TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: No swapchain for blit, creating")); - mgpu.swapchain = CSwapchain::create(backend->rendererState.allocator, backend.lock()); - } - - auto OPTIONS = swapchain->currentOptions(); - auto bufDma = STATE.buffer->dmabuf(); - OPTIONS.size = STATE.buffer->size; - if (OPTIONS.format == DRM_FORMAT_INVALID) - OPTIONS.format = bufDma.format; - OPTIONS.multigpu = false; // this is not a shared swapchain, and additionally, don't make it linear, nvidia would be mad - OPTIONS.cursor = false; - OPTIONS.scanout = true; - if (OPTIONS.length == 0) // releaseMgpuResources() cleared the swapchain and we committed without updating it. - OPTIONS.length = 2; - if (!mgpu.swapchain->reconfigure(OPTIONS)) { - backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain failed reconfiguring"); - return false; - } - - auto NEWAQBUF = mgpu.swapchain->next(nullptr); - if (!NEWAQBUF) { - backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain has no buffer"); + auto NEWAQBUF = nextMgpuBuffer(backend->rendererState.allocator); + if (!NEWAQBUF) return false; - } SP<Aquamarine::CDRMRenderer> primaryRenderer; if (backend->primary) @@ -2698,8 +2713,44 @@ data.outputState.explicitInFence = -1; drmFB = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present - } else - drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); // will return attachment if present + } else { + const bool EVDI = backend->driver == AQ_BACKEND_GPU_DRIVER_EVDI; + + if (!(EVDI && backend->cpuCopyFallback)) + drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); // will return attachment if present + + if (!drmFB && EVDI && backend->dumbAllocator) { + if (!backend->cpuCopyFallback) { + backend->backend->log(AQ_LOG_DEBUG, + std::format("drm: direct KMS import of primary buffer failed on {}, switching to dumb-buffer CPU copy", backend->gpuName)); + backend->cpuCopyFallback = true; + } + + SP<CDRMRenderer> primaryRenderer = backend->primary ? backend->primary->rendererState.renderer : nullptr; + if (!primaryRenderer) { + backend->backend->log(AQ_LOG_ERROR, "drm: evdi CPU copy requires a primary renderer"); + return false; + } + + auto NEWAQBUF = nextMgpuBuffer(backend->dumbAllocator); + if (!NEWAQBUF) + return false; + + auto blitResult = primaryRenderer->blit(STATE.buffer, NEWAQBUF, nullptr, + (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) ? STATE.explicitInFence : -1); + if (!blitResult.success) { + backend->backend->log(AQ_LOG_ERROR, "drm: evdi: primary blit into dumb buffer failed"); + return false; + } + + if (blitResult.syncFD.has_value() && (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE)) + data.outputState.explicitInFence = blitResult.syncFD.value(); + else + data.outputState.explicitInFence = -1; + + drmFB = CDRMFB::create(NEWAQBUF, backend, nullptr); + } + } if (!drmFB) { backend->backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); @@ -2870,6 +2921,12 @@ SP<CDRMFB> fb; if (backend->primary) { + if (!backend->rendererRequired) { + TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: backend has no renderer, no cursor plane")); + setCursorVisible(false); + return false; + } + TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires cursor blit, blitting")); if (!backend->rendererState.renderer || !backend->rendererState.allocator) { @@ -3051,6 +3108,10 @@ if (!backend || !backend->commitThread || !backend->atomic) return 0; + // queued commits only do direct imports, the evdi CPU copy goes through commitState + if (backend->cpuCopyFallback) + return 0; + uint32_t capabilities = AQ_OUTPUT_COMMIT_CAPABILITY_QUEUED | AQ_OUTPUT_COMMIT_CAPABILITY_TIMED; if (connector->canDoVrr) capabilities |= AQ_OUTPUT_COMMIT_CAPABILITY_VRR; @@ -3087,7 +3148,7 @@ if (!STATE.buffer || !STATE.enabled || STATE.drmFormat == DRM_FORMAT_INVALID) return {.error = EINVAL}; if (STATE.buffer->attachments.has<CDRMBufferUnimportable>()) - return {.error = EINVAL}; + return {.error = backend->driver == AQ_BACKEND_GPU_DRIVER_EVDI ? ENOTSUP : EINVAL}; if (STATE.adaptiveSync && !(CAPABILITIES & AQ_OUTPUT_COMMIT_CAPABILITY_VRR)) return {.error = ENOTSUP}; if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && !(CAPABILITIES & AQ_OUTPUT_COMMIT_CAPABILITY_TEARING)) @@ -3382,11 +3443,11 @@ dropped = true; + closeHandles(); + if (!id) return; - closeHandles(); - TRACE(backend->backend->log(AQ_LOG_TRACE, std::format("drm: dropping buffer {}", id))); int ret = drmModeCloseFB(backend->gpu->fd, id); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/drm/Renderer.cpp new/aquamarine-0.16.0/src/backend/drm/Renderer.cpp --- old/aquamarine-0.15.1/src/backend/drm/Renderer.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/drm/Renderer.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -777,7 +777,7 @@ constexpr GLenum PIXEL_BUFFER_FORMAT = GL_RGBA; -void CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buf, std::span<uint8_t> out) { +bool CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buf, std::span<uint8_t> out, GLenum format, uint32_t stride) { CEglContextGuard eglContext(*this); auto att = buf->attachments.get<CDRMRendererBufferOutputAttachment>(); if (!att || att->renderer != self) { @@ -790,7 +790,7 @@ att->eglImage = createEGLImage(dma); if (att->eglImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): createEGLImage failed: {}", eglGetError())); - return; + return false; } GLCALL(glGenRenderbuffers(1, &att->rbo)); @@ -804,14 +804,21 @@ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): glCheckFramebufferStatus failed: {}", glGetError())); - return; + GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); + buf->attachments.remove(att); + return false; } } GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); - GLCALL(proc.glReadnPixelsEXT(0, 0, dma.size.x, dma.size.y, GL_RGBA, GL_UNSIGNED_BYTE, out.size(), out.data())); + if (stride) + GLCALL(glPixelStorei(GL_PACK_ROW_LENGTH, stride / 4)); + GLCALL(proc.glReadnPixelsEXT(0, 0, dma.size.x, dma.size.y, format, GL_UNSIGNED_BYTE, out.size(), out.data())); + if (stride) + GLCALL(glPixelStorei(GL_PACK_ROW_LENGTH, 0)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); + return true; } void CDRMRenderer::waitOnSync(int fd) { @@ -1002,12 +1009,10 @@ GLuint rboID = 0, fboID = 0; const auto& toDma = to->dmabuf(); - if (!verifyDestinationDMABUF(toDma)) { - backend->log(AQ_LOG_ERROR, "EGL (blit): failed to blit: destination dmabuf unsupported"); - return {}; - } + const auto cpuOnly = to->attachments.get<CDRMRendererBufferCPUOnlyAttachment>(); + bool destImportable = !(cpuOnly && cpuOnly->renderer == self) && verifyDestinationDMABUF(toDma); - { + if (destImportable) { auto attachment = to->attachments.get<CDRMRendererBufferOutputAttachment>(); if (attachment && attachment->renderer == self) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): To attachment found")); @@ -1021,26 +1026,92 @@ rboImage = createEGLImage(toDma); if (rboImage == EGL_NO_IMAGE_KHR) { - backend->log(AQ_LOG_ERROR, std::format("EGL (blit): createEGLImage failed: {}", eglGetError())); - return {}; + backend->log(AQ_LOG_DEBUG, std::format("EGL (blit): createEGLImage failed: {}, will try CPU fallback", eglGetError())); + destImportable = false; + } else { + GLCALL(glGenRenderbuffers(1, &rboID)); + GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); + GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); + GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); + + GLCALL(glGenFramebuffers(1, &fboID)); + GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); + GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + backend->log(AQ_LOG_DEBUG, std::format("EGL (blit): glCheckFramebufferStatus failed: {}, will try CPU fallback", glGetError())); + glDeleteFramebuffers(1, &fboID); + glDeleteRenderbuffers(1, &rboID); + proc.eglDestroyImageKHR(egl.display, rboImage); + rboImage = nullptr; + rboID = 0; + fboID = 0; + destImportable = false; + } else + to->attachments.add(makeShared<CDRMRendererBufferOutputAttachment>(self, rboImage, fboID, rboID)); } + } + } + + if (!destImportable) { + if (!(to->caps() & BUFFER_CAPABILITY_DATAPTR)) { + backend->log(AQ_LOG_ERROR, "EGL (blit): destination cannot be imported and has no CPU mapping"); + return {}; + } + + if (!cpuOnly || cpuOnly->renderer != self) { + backend->log(AQ_LOG_DEBUG, "EGL (blit): destination not renderable, using CPU copy"); + to->attachments.add(makeShared<CDRMRendererBufferCPUOnlyAttachment>(self)); + } - GLCALL(glGenRenderbuffers(1, &rboID)); - GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); - GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage)); - GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); - - GLCALL(glGenFramebuffers(1, &fboID)); - GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); - GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID)); + auto [dst, dstFmt, dstLen] = to->beginDataPtr(0); + if (!dst) { + backend->log(AQ_LOG_ERROR, "EGL (blit): destination beginDataPtr returned null"); + return {}; + } - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - backend->log(AQ_LOG_ERROR, std::format("EGL (blit): glCheckFramebufferStatus failed: {}", glGetError())); + const bool RGB = dstFmt == DRM_FORMAT_XRGB8888 || dstFmt == DRM_FORMAT_ARGB8888; + const bool BGR = dstFmt == DRM_FORMAT_XBGR8888 || dstFmt == DRM_FORMAT_ABGR8888; + if (!RGB && !BGR) { + backend->log(AQ_LOG_ERROR, std::format("EGL (blit): destination format {} not supported for CPU fallback", fourccToName(dstFmt))); + to->endDataPtr(); + return {}; + } + + const size_t W = fromDma.size.x; + const size_t H = fromDma.size.y; + const size_t STRIDE = toDma.strides.at(0); + if (W == 0 || H == 0 || STRIDE % 4 || STRIDE < W * 4 || dstLen < STRIDE * (H - 1) + W * 4 || (!intermediateBuf.empty() && intermediateBuf.size() < W * H * 4)) { + backend->log(AQ_LOG_ERROR, "EGL (blit): buffer sizes invalid for CPU fallback"); + to->endDataPtr(); + return {}; + } + + GLenum readFormat = GL_RGBA; + if (!intermediateBuf.empty()) { + for (size_t y = 0; y < H; ++y) + memcpy(dst + y * STRIDE, intermediateBuf.data() + y * W * 4, W * 4); + } else { + if (RGB && exts.EXT_read_format_bgra) + readFormat = GL_BGRA_EXT; + + if (!readBuffer(from, {dst, dstLen}, readFormat, STRIDE)) { + backend->log(AQ_LOG_ERROR, "EGL (blit): source readback failed for CPU fallback"); + to->endDataPtr(); return {}; } + } - to->attachments.add(makeShared<CDRMRendererBufferOutputAttachment>(self, rboImage, fboID, rboID)); + if (RGB && readFormat == GL_RGBA) { + for (size_t y = 0; y < H; ++y) { + auto row = (uint32_t*)(dst + y * STRIDE); + for (size_t x = 0; x < W; ++x) + row[x] = (row[x] & 0xFF00FF00) | ((row[x] >> 16) & 0xFF) | ((row[x] & 0xFF) << 16); + } } + + to->endDataPtr(); + return {.success = true}; } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): rboImage 0x{:x}", (uintptr_t)rboImage))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/drm/Renderer.hpp new/aquamarine-0.16.0/src/backend/drm/Renderer.hpp --- old/aquamarine-0.15.1/src/backend/drm/Renderer.hpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/drm/Renderer.hpp 2026-11-01 19:07:09.000000000 +0100 @@ -63,6 +63,14 @@ Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer; }; + // marks a destination this renderer can't draw into, blit() goes straight to the CPU copy + class CDRMRendererBufferCPUOnlyAttachment : public IAttachment { + public: + CDRMRendererBufferCPUOnlyAttachment(Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer_) : renderer(renderer_) {} + + Hyprutils::Memory::CWeakPointer<CDRMRenderer> renderer; + }; + // CEglContextGuard is a RAII abstraction for the EGL context. // On initialization, it sets the EGL context to the renderer's display, // and on destruction, it restores the previous EGL context. @@ -154,7 +162,7 @@ } egl; CGLTex glTex(Hyprutils::Memory::CSharedPointer<IBuffer> buf); - void readBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buf, std::span<uint8_t> out); + bool readBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buf, std::span<uint8_t> out, GLenum format = GL_RGBA, uint32_t stride = 0); Hyprutils::Memory::CWeakPointer<CDRMRenderer> self; std::vector<SGLFormat> formats; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/backend/drm/impl/Atomic.cpp new/aquamarine-0.16.0/src/backend/drm/impl/Atomic.cpp --- old/aquamarine-0.15.1/src/backend/drm/impl/Atomic.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/backend/drm/impl/Atomic.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -16,42 +16,6 @@ using namespace Hyprutils::Math; #define SP CSharedPointer -// HW capabilites aren't checked. Should be handled by the drivers (and highly unlikely to get a format outside of bpc range) -// https://drmdb.emersion.fr/properties/3233857728/max%20bpc -static uint8_t getMaxBPC(uint64_t min, uint64_t max, uint32_t drmFormat) { - uint8_t formatBPC = 8; - - switch (drmFormat) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_RGBX8888: - case DRM_FORMAT_BGRX8888: - case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_ABGR8888: - case DRM_FORMAT_RGBA8888: - case DRM_FORMAT_BGRA8888: formatBPC = 8; break; - - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_XBGR2101010: - case DRM_FORMAT_RGBX1010102: - case DRM_FORMAT_BGRX1010102: - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_ABGR2101010: - case DRM_FORMAT_RGBA1010102: - case DRM_FORMAT_BGRA1010102: formatBPC = 10; break; - - case DRM_FORMAT_XRGB16161616: - case DRM_FORMAT_XBGR16161616: - case DRM_FORMAT_ARGB16161616: - case DRM_FORMAT_ABGR16161616: formatBPC = 16; break; - - // FIXME? handle non-rgb formats and some weird stuff like DRM_FORMAT_AXBXGXRX106106106106 - default: formatBPC = 8; break; - } - - return std::clamp<uint64_t>(formatBPC, min, max); -} - Aquamarine::CDRMAtomicRequest::CDRMAtomicRequest(Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_) : backend(backend_), req(drmModeAtomicAlloc()) { if (!req) failed = true; @@ -267,7 +231,10 @@ // Setup HDR if (connector->props.values.max_bpc && connector->maxBpcBounds.at(0) && connector->maxBpcBounds.at(1) && !connector->maxBpcFailed) { - newMaxBpc = getMaxBPC(connector->maxBpcBounds.at(0), connector->maxBpcBounds.at(1), data.mainFB->buffer->dmabuf().format); + // max_bpc should not follow the scanout buffer format, and should instead always follow what we set in monitor specific configs. + // even when in DS, we should not change the max_bpc to 8, as this will cause a modeset / flicker when going in / out of DS. + const uint64_t WANTED = STATE.maxBpc ? STATE.maxBpc : connector->maxBpcBounds.at(1); + newMaxBpc = std::clamp<uint64_t>(WANTED, connector->maxBpcBounds.at(0), connector->maxBpcBounds.at(1)); if (forceConnProps || connector->atomic.maxBpc != newMaxBpc) { add(connector->id, connector->props.values.max_bpc, newMaxBpc); maxBpcEmitted = true; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/include/Shared.hpp new/aquamarine-0.16.0/src/include/Shared.hpp --- old/aquamarine-0.15.1/src/include/Shared.hpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/include/Shared.hpp 2026-11-01 19:07:09.000000000 +0100 @@ -13,7 +13,7 @@ #define RASSERT(expr, reason, ...) \ if (!(expr)) { \ std::cout << std::format("\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \ - std::format(reason, ##__VA_ARGS__), __LINE__, \ + std::format(reason __VA_OPT__(,) __VA_ARGS__), __LINE__, \ ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })()); \ std::cout << "[Aquamarine] Assertion failed!"; \ raise(SIGABRT); \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aquamarine-0.15.1/src/output/Output.cpp new/aquamarine-0.16.0/src/output/Output.cpp --- old/aquamarine-0.15.1/src/output/Output.cpp 2026-09-17 09:32:02.000000000 +0200 +++ new/aquamarine-0.16.0/src/output/Output.cpp 2026-11-01 19:07:09.000000000 +0100 @@ -70,7 +70,7 @@ return false; } -Aquamarine::COutputState::CSnapshot::CSnapshot(const COutputState* owner, const SInternalState& state, const std::array<uint64_t, 16>& generations) : +Aquamarine::COutputState::CSnapshot::CSnapshot(const COutputState* owner, const SInternalState& state, const std::array<uint64_t, 17>& generations) : m_owner(owner), m_state(state), m_generations(generations) { if (!(m_state.committed & AQ_OUTPUT_STATE_EXPLICIT_IN_FENCE) || m_state.explicitInFence < 0) return; @@ -91,7 +91,7 @@ } bool Aquamarine::COutputState::CSnapshot::needsReconfig() const { - return m_state.committed & (AQ_OUTPUT_STATE_ENABLED | AQ_OUTPUT_STATE_FORMAT | AQ_OUTPUT_STATE_MODE | AQ_OUTPUT_STATE_HDR | AQ_OUTPUT_STATE_WCG); + return m_state.committed & (AQ_OUTPUT_STATE_ENABLED | AQ_OUTPUT_STATE_FORMAT | AQ_OUTPUT_STATE_MODE | AQ_OUTPUT_STATE_HDR | AQ_OUTPUT_STATE_WCG | AQ_OUTPUT_STATE_MAX_BPC); } int Aquamarine::COutputState::CSnapshot::error() const { @@ -152,7 +152,7 @@ } bool Aquamarine::COutputState::needsReconfig() const { - return internalState.committed & (AQ_OUTPUT_STATE_ENABLED | AQ_OUTPUT_STATE_FORMAT | AQ_OUTPUT_STATE_MODE | AQ_OUTPUT_STATE_HDR | AQ_OUTPUT_STATE_WCG); + return internalState.committed & (AQ_OUTPUT_STATE_ENABLED | AQ_OUTPUT_STATE_FORMAT | AQ_OUTPUT_STATE_MODE | AQ_OUTPUT_STATE_HDR | AQ_OUTPUT_STATE_WCG | AQ_OUTPUT_STATE_MAX_BPC); } void Aquamarine::COutputState::addDamage(const Hyprutils::Math::CRegion& region) { @@ -250,6 +250,11 @@ internalState.colorRange = range; } +void Aquamarine::COutputState::setMaxBpc(uint8_t maxBpc) { + internalState.maxBpc = maxBpc; + markCommitted(AQ_OUTPUT_STATE_MAX_BPC); +} + void Aquamarine::COutputState::markCommitted(uint32_t properties) { internalState.committed |= properties; ++++++ aquamarine.obsinfo ++++++ --- /var/tmp/diff_new_pack.BE0nj6/_old 2026-11-03 18:06:40.772337016 +0100 +++ /var/tmp/diff_new_pack.BE0nj6/_new 2026-11-03 18:06:40.779337310 +0100 @@ -1,5 +1,5 @@ name: aquamarine -version: 0.15.1 -mtime: 1789630322 -commit: f31c47a1b9d300847d8fc3108ad959448103dfc1 +version: 0.16.0 +mtime: 1793556429 +commit: b77d67648336223a6c8fb88844ae316e7b72b574
