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
 

Reply via email to