diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 5a8f2bd26fc..4d15a5babc9 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -542,6 +542,7 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("cursor:hide_on_key_press", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:hide_on_touch", Hyprlang::INT{1}); m_pConfig->addConfigValue("cursor:allow_dumb_copy", Hyprlang::INT{0}); + m_pConfig->addConfigValue("cursor:allow_slow_copy", Hyprlang::INT{0}); m_pConfig->addConfigValue("autogenerated", Hyprlang::INT{0}); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 635427d14a2..4b835621e9d 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -396,6 +396,34 @@ int CMonitor::findAvailableDefaultWS() { return INT32_MAX; // shouldn't be reachable } +SP CMonitor::resizeSwapchain(SP swapchain, Vector2D size, bool isCursor, bool isDumb) { + if (!swapchain || size != swapchain->currentOptions().size) { + + if (!swapchain) { + auto allocator = isDumb && output->getBackend()->fallbackAllocator() ? output->getBackend()->fallbackAllocator() : output->getBackend()->preferredAllocator(); + swapchain = Aquamarine::CSwapchain::create(allocator, output->getBackend()); + } + + auto options = swapchain->currentOptions(); + options.size = size; + options.length = 2; + options.scanout = true; + options.cursor = isCursor; + options.multigpu = !isDumb && output->getBackend()->preferredAllocator()->drmFD() != g_pCompositor->m_iDRMFD; + if (!isCursor && cursorSwapchain) + options.format = cursorSwapchain->currentOptions().format; + // We do not set the format. If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us, + // but if it's set, we don't wanna change it. + + if (!swapchain->reconfigure(options)) { + Debug::log(TRACE, "Failed to reconfigure{} {} swapchain", isCursor ? "cursor" : "cursor fallback", isDumb ? " dumb" : ""); + return nullptr; + } + } + + return swapchain; +} + void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) { // Workspace std::string newDefaultWorkspaceName = ""; @@ -835,6 +863,30 @@ bool CMonitor::attemptDirectScanout() { return true; } +bool CMonitor::resizeCursorSwapchain(Vector2D size) { + auto swapchain = resizeSwapchain(cursorSwapchain, size, true, useDumbCursorBuffer); + if (!cursorSwapchain) + cursorSwapchain = swapchain; + + return !!swapchain; +} + +bool CMonitor::resizeCursorSwapchain(Vector2D size, bool useDumb) { + if (useDumbCursorBuffer != useDumb && cursorSwapchain) { + useDumbCursorBuffer = useDumb; + cursorSwapchain = nullptr; // recreate swapchain with a different allocator + } + return resizeCursorSwapchain(size); +} + +bool CMonitor::resizeCursorFallbackSwapchain(Vector2D size) { + auto swapchain = resizeSwapchain(cursorFallbackSwapchain, size, false); + if (!cursorFallbackSwapchain) + cursorFallbackSwapchain = swapchain; + + return !!swapchain; +} + CMonitorState::CMonitorState(CMonitor* owner) { m_pOwner = owner; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 32fc768a16f..0d891a654cc 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -100,6 +100,7 @@ class CMonitor { std::optional forceSize; SP currentMode; SP cursorSwapchain; + SP cursorFallbackSwapchain; bool dpmsStatus = true; bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. @@ -178,6 +179,9 @@ class CMonitor { CBox logicalBox(); void scheduleDone(); bool attemptDirectScanout(); + bool resizeCursorSwapchain(Vector2D size); + bool resizeCursorSwapchain(Vector2D size, bool useDumb); + bool resizeCursorFallbackSwapchain(Vector2D size); bool m_bEnabled = false; bool m_bRenderingInitPassed = false; @@ -189,10 +193,12 @@ class CMonitor { } private: - void setupDefaultWS(const SMonitorRule&); - int findAvailableDefaultWS(); + void setupDefaultWS(const SMonitorRule&); + int findAvailableDefaultWS(); + SP resizeSwapchain(SP swapchain, Vector2D size, bool isCursor, bool isDumb = false); - wl_event_source* doneSource = nullptr; + wl_event_source* doneSource = nullptr; + bool useDumbCursorBuffer = false; struct { CHyprSignalListener frame; diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index 3b4256880f4..d52c0237769 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -6,6 +6,11 @@ #include "../protocols/core/Compositor.hpp" #include "eventLoop/EventLoopManager.hpp" #include "SeatManager.hpp" +#include "render/OpenGL.hpp" +#include "render/Texture.hpp" +#include +#include +#include #include #include @@ -374,25 +379,8 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->cursorSwapchain || maxSize != state->monitor->cursorSwapchain->currentOptions().size) { - - if (!state->monitor->cursorSwapchain) - state->monitor->cursorSwapchain = Aquamarine::CSwapchain::create(state->monitor->output->getBackend()->preferredAllocator(), state->monitor->output->getBackend()); - - auto options = state->monitor->cursorSwapchain->currentOptions(); - options.size = maxSize; - options.length = 2; - options.scanout = true; - options.cursor = true; - options.multigpu = state->monitor->output->getBackend()->preferredAllocator()->drmFD() != g_pCompositor->m_iDRMFD; - // We do not set the format. If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us, - // but if it's set, we don't wanna change it. - - if (!state->monitor->cursorSwapchain->reconfigure(options)) { - Debug::log(TRACE, "Failed to reconfigure cursor swapchain"); - return nullptr; - } - } + if (!state->monitor->resizeCursorSwapchain(maxSize)) + return nullptr; // if we already rendered the cursor, revert the swapchain to avoid rendering the cursor over // the current front buffer @@ -413,34 +401,82 @@ SP CPointerManager::renderHWCursorBuffer(SPmakeEGLCurrent(); g_pHyprOpenGL->m_RenderData.pMonitor = state->monitor.get(); - auto RBO = g_pHyprRenderer->getOrCreateRenderbuffer(buf, state->monitor->cursorSwapchain->currentOptions().format); + bool needsFallback = false; + SP fallback; + + auto RBO = + buf->type() == Aquamarine::BUFFER_TYPE_DMABUF_DUMB ? nullptr : g_pHyprRenderer->getOrCreateRenderbuffer(buf, state->monitor->cursorSwapchain->currentOptions().format); if (!RBO) { Debug::log(TRACE, "Failed to create cursor RB with format {}, mod {}", buf->dmabuf().format, buf->dmabuf().modifier); static auto PDUMB = CConfigValue("cursor:allow_dumb_copy"); - if (!*PDUMB) + static auto PSLOW = CConfigValue("cursor:allow_slow_copy"); + + auto isDumb = buf->type() == Aquamarine::BUFFER_TYPE_DMABUF_DUMB; + if (isDumb != *PDUMB) { + Debug::log(TRACE, "[pointer] switching swapchain to {}", *PDUMB ? "dumb" : "gbm"); + if (!state->monitor->resizeCursorSwapchain(maxSize, *PDUMB)) + return nullptr; + + buf = state->monitor->cursorSwapchain->next(nullptr); + if (!buf) { + Debug::log(TRACE, "Failed to acquire a buffer from the cursor swapchain"); + return nullptr; + } + isDumb = buf->type() == Aquamarine::BUFFER_TYPE_DMABUF_DUMB; + } + + if (!*PDUMB && !*PSLOW) return nullptr; - auto bufData = buf->beginDataPtr(0); - auto bufPtr = std::get<0>(bufData); + // slow copy will fail with dumb buffer + if (*PSLOW && !*PDUMB) { + Debug::log(TRACE, "[pointer] performing slow copy"); + needsFallback = true; - // clear buffer - memset(bufPtr, 0, std::get<2>(bufData)); + if (!state->monitor->resizeCursorFallbackSwapchain(maxSize)) + return nullptr; - auto texBuffer = currentCursorImage.pBuffer ? currentCursorImage.pBuffer : currentCursorImage.surface->resource()->current.buffer; + fallback = state->monitor->cursorFallbackSwapchain->next(nullptr); + if (!fallback) { + Debug::log(TRACE, "Failed to acquire a buffer from the cursor fallback swapchain"); + return nullptr; + } - if (texBuffer) { - auto textAttrs = texBuffer->shm(); - auto texData = texBuffer->beginDataPtr(GBM_BO_TRANSFER_WRITE); - auto texPtr = std::get<0>(texData); - Debug::log(TRACE, "cursor texture {}x{} {} {} {}", textAttrs.size.x, textAttrs.size.y, (void*)texPtr, textAttrs.format, textAttrs.stride); - // copy cursor texture - for (int i = 0; i < texBuffer->shm().size.y; i++) - memcpy(bufPtr + i * buf->dmabuf().strides[0], texPtr + i * textAttrs.stride, textAttrs.stride); - } + RBO = g_pHyprRenderer->getOrCreateRenderbuffer(fallback, state->monitor->cursorFallbackSwapchain->currentOptions().format); + if (!RBO) { + Debug::log(TRACE, "Failed to create cursor RB with format {}, mod {}", fallback->dmabuf().format, fallback->dmabuf().modifier); + return nullptr; + } + } else { + Debug::log(TRACE, "[pointer] performing dumb copy"); + auto bufData = buf->beginDataPtr(0); + auto bufPtr = std::get<0>(bufData); + + auto bufSize = isDumb ? buf->shm().size : buf->dmabuf().size; + auto bufStride = isDumb ? buf->shm().stride : buf->dmabuf().strides[0]; + + // clear buffer + memset(bufPtr, 0, bufStride * bufSize.y); + + auto texBuffer = currentCursorImage.pBuffer ? currentCursorImage.pBuffer : currentCursorImage.surface->resource()->current.buffer; + if (texBuffer) { + auto textAttrs = texBuffer->shm(); + auto texData = texBuffer->beginDataPtr(GBM_BO_TRANSFER_WRITE); + auto texPtr = std::get<0>(texData); + Debug::log(TRACE, "cursor texture {}x{} {} {} {}", textAttrs.size.x, textAttrs.size.y, (void*)texPtr, textAttrs.format, textAttrs.stride); + + // copy cursor texture + if (bufStride == textAttrs.stride && bufSize == textAttrs.size) + memcpy(bufPtr, texPtr, textAttrs.stride * textAttrs.size.y); + else + for (int i = 0; i < texBuffer->shm().size.y; i++) + memcpy(bufPtr + i * bufStride, texPtr + i * textAttrs.stride, textAttrs.stride); + } - buf->endDataPtr(); + buf->endDataPtr(); - return buf; + return buf; + } } RBO->bind(); @@ -455,9 +491,44 @@ SP CPointerManager::renderHWCursorBuffer(SPrenderTexture(texture, &xbox, 1.F); g_pHyprOpenGL->end(); - glFlush(); - g_pHyprOpenGL->m_RenderData.pMonitor = nullptr; + if (!needsFallback) + glFlush(); + else { + glFinish(); + + // wlroots tries to blit here but it'll fail the same way we've got here in the first place + auto bufAttrs = buf->dmabuf(); + auto texAttrs = fallback->dmabuf(); + + auto bufData = buf->beginDataPtr(GBM_BO_TRANSFER_WRITE); + auto bufPtr = std::get<0>(bufData); + + Debug::log(TRACE, "cursor buffer {}x{} {} format={} stride={}", bufAttrs.size.x, bufAttrs.size.y, (void*)bufPtr, bufAttrs.format, bufAttrs.strides[0]); + Debug::log(TRACE, "fallback buffer {}x{} format={} stride={}", texAttrs.size.x, texAttrs.size.y, texAttrs.format, texAttrs.strides[0]); + + g_pHyprRenderer->makeEGLCurrent(); + auto fallbackTexture = new CTexture(fallback); + GLuint id; + glGenFramebuffers(1, &id); + glBindFramebuffer(GL_FRAMEBUFFER, id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, fallbackTexture->m_iTarget, fallbackTexture->m_iTexID, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + Debug::log(ERR, "cursor slow copy: glCheckFramebufferStatus failed"); + else { + const auto PFORMAT = FormatUtils::getPixelFormatFromDRM(bufAttrs.format); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + glReadPixels(0, 0, bufAttrs.size.x, bufAttrs.size.y, GL_RGBA, PFORMAT->glType, bufPtr); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + buf->endDataPtr(); + } + + g_pHyprOpenGL->m_RenderData.pMonitor = nullptr; g_pHyprRenderer->onRenderbufferDestroy(RBO.get()); return buf;