From 581a094b7b1d6460223754896953f9c74c07eeff Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:01:47 +0200 Subject: [PATCH] feat: inefficient support for hardware cursors --- src/cursor.cpp | 148 +++++++++++++++++++++++++++++++++++++++- src/cursor.hpp | 9 +++ src/globals.hpp | 3 +- src/hyprland/cursor.cpp | 137 +++++++++++++++++++++++++++++++++++++ src/hyprland/cursor.hpp | 13 ++++ src/main.cpp | 31 +++++++++ 6 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 src/hyprland/cursor.cpp create mode 100644 src/hyprland/cursor.hpp diff --git a/src/cursor.cpp b/src/cursor.cpp index db5acf0..f3874e3 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -1,18 +1,29 @@ #include "globals.hpp" +#include #include +#include #define private public #include #include +#include #undef private -#include #include +#include "hyprland/cursor.hpp" + +#include +#include +#include #include "cursor.hpp" #include "renderer.hpp" +/* +Reimplements rendering of the software cursor. +Is also largely identical to hyprlands impl, but uses our custom rendering to rotate the cursor. +*/ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos) { if (!pointers->hasCursor()) @@ -46,6 +57,10 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot); } +/* +This function implements damaging the screen such that the software cursor is drawn. +It is largely identical to hyprlands implementation, but expands the damage reagion, to accomodate various rotations. +*/ void CDynamicCursors::damageSoftware(CPointerManager* pointers) { // we damage a 3x3 area around the cursor, to accomodate for all possible hotspots and rotations @@ -65,6 +80,137 @@ void CDynamicCursors::damageSoftware(CPointerManager* pointers) { } } +/* +This function reimplements the hardware cursor buffer drawing. +It is largely copied from hyprland, but adjusted to allow the cursor to be rotated. +*/ +wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP state, SP texture) { + static auto* const* PHW_DEBUG= (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_HW_DEBUG)->getDataStaticPtr(); + + auto output = state->monitor->output; + + auto size = pointers->currentCursorImage.size; + // we try to allocate a buffer that is thrice as big, see software rendering + auto target = size * 3; + + if (output->impl->get_cursor_size) { + int w, h; + output->impl->get_cursor_size(output, &w, &h); + + if (w < target.x || h < target.y) { + Debug::log(TRACE, "hardware cursor too big! {} > {}x{}", pointers->currentCursorImage.size, w, h); + return nullptr; + } + + target.x = w; + target.y = h; + } + + if (target.x <= 0 || target.y <= 0) { + Debug::log(TRACE, "hw cursor for output {} failed the size checks ({}x{} is invalid)", state->monitor->szName, target.x, target.y); + return nullptr; + } + + if (!output->cursor_swapchain || target != Vector2D{output->cursor_swapchain->width, output->cursor_swapchain->height}) { + wlr_drm_format fmt = {0}; + if (!output_pick_cursor_format(output, &fmt)) { + Debug::log(TRACE, "Failed to pick cursor format"); + return nullptr; + } + + wlr_swapchain_destroy(output->cursor_swapchain); + output->cursor_swapchain = wlr_swapchain_create(output->allocator, target.x, target.y, &fmt); + wlr_drm_format_finish(&fmt); + + if (!output->cursor_swapchain) { + Debug::log(TRACE, "Failed to create cursor swapchain"); + return nullptr; + } + } + + wlr_buffer* buf = wlr_swapchain_acquire(output->cursor_swapchain, nullptr); + if (!buf) { + Debug::log(TRACE, "Failed to acquire a buffer from the cursor swapchain"); + return nullptr; + } + + CRegion damage = {0, 0, INT16_MAX, INT16_MAX}; + + g_pHyprRenderer->makeEGLCurrent(); + g_pHyprOpenGL->m_RenderData.pMonitor = state->monitor.get(); // has to be set cuz allocs + + const auto RBO = g_pHyprRenderer->getOrCreateRenderbuffer(buf, DRM_FORMAT_ARGB8888); + RBO->bind(); + + g_pHyprOpenGL->beginSimple(state->monitor.get(), damage, RBO); + + if (**PHW_DEBUG) + g_pHyprOpenGL->clear(CColor{rand() / float(RAND_MAX), rand() / float(RAND_MAX), rand() / float(RAND_MAX), 1.F}); + else + g_pHyprOpenGL->clear(CColor{0.F, 0.F, 0.F, 0.F}); + + // the box should start in the middle portion, rotate by our calculated amount + CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale}.round()}; + xbox.rot = this->calculate(&pointers->pointerPos); + + // use our custom draw function + renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot); + + g_pHyprOpenGL->end(); + glFlush(); + g_pHyprOpenGL->m_RenderData.pMonitor = nullptr; + + wlr_buffer_unlock(buf); + + return buf; +} + +/* +Implements the hardware cursor setting. +It is also mostly the same as stock hyprland, but with the hotspot translated more into the middle. +*/ +bool CDynamicCursors::setHardware(CPointerManager* pointers, SP state, wlr_buffer* buf) { + if (!state->monitor->output->impl->set_cursor) + return false; + + const auto HOTSPOT = pointers->transformedHotspot(state->monitor.lock()); + + Debug::log(TRACE, "[pointer] hw transformed hotspot for {}: {}", state->monitor->szName, HOTSPOT); + + if (!state->monitor->output->impl->set_cursor(state->monitor->output, buf, HOTSPOT.x + pointers->currentCursorImage.size.x, HOTSPOT.y + pointers->currentCursorImage.size.y)) + return false; + + wlr_buffer_unlock(state->cursorFrontBuffer); + state->cursorFrontBuffer = buf; + + g_pCompositor->scheduleFrameForMonitor(state->monitor.get()); + + if (buf) + wlr_buffer_lock(buf); + + return true; +} + +/* +Handles cursor move events. +*/ +void CDynamicCursors::onCursorMoved(CPointerManager* pointers) { + if (!pointers->hasCursor()) + return; + + for (auto& m : g_pCompositor->m_vMonitors) { + auto state = pointers->stateFor(m); + + state->box = pointers->getCursorBoxLogicalForMonitor(state->monitor.lock()); + + if (state->hardwareFailed || !state->entered) + continue; + + // we set a new hardware cursor on every move + pointers->attemptHardwareCursor(state); + } +} + double CDynamicCursors::calculate(Vector2D* pos) { static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr(); diff --git a/src/cursor.hpp b/src/cursor.hpp index df7529c..39eec13 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -1,15 +1,24 @@ #include "globals.hpp" +#define private public #include +#undef private #include class CDynamicCursors; class CDynamicCursors { public: + /* hook on onCursorMoved */ + void onCursorMoved(CPointerManager* pointers); + /* hook on renderSoftwareCursorsFor */ void renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos); /* hook on damageIfSoftware*/ void damageSoftware(CPointerManager* pointers); + /* hook on renderHWCursorBuffer */ + wlr_buffer* renderHardware(CPointerManager* pointers, SP state, SP texture); + /* hook on setHWCursorBuffer */ + bool setHardware(CPointerManager* pointers, SP state, wlr_buffer* buf); private: // calculates the current angle of the cursor diff --git a/src/globals.hpp b/src/globals.hpp index 2178e12..78066ae 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -2,6 +2,7 @@ #include -#define CONFIG_LENGTH "plugin:dynamic-cursors:length" +#define CONFIG_LENGTH "plugin:dynamic-cursors:length" +#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" inline HANDLE PHANDLE = nullptr; diff --git a/src/hyprland/cursor.cpp b/src/hyprland/cursor.cpp new file mode 100644 index 0000000..1d5bc4e --- /dev/null +++ b/src/hyprland/cursor.cpp @@ -0,0 +1,137 @@ + +#include +#include + +#include +#include +#include + +/* +The following functions are copied 1:1 from the hyprland codebase. +This is nessecary, as these are static interop functions which wlroots which are not accessible through headers. +Will probably be obsolete anyway after https://github.com/hyprwm/Hyprland/pull/6608 +*/ + +// TODO: make nicer +// this will come with the eventual rewrite of wlr_drm, etc... +bool wlr_drm_format_intersect(wlr_drm_format* dst, const wlr_drm_format* a, const wlr_drm_format* b) { + ASSERT(a->format == b->format); + + size_t capacity = a->len < b->len ? a->len : b->len; + uint64_t* modifiers = (uint64_t*)malloc(sizeof(*modifiers) * capacity); + if (!modifiers) + return false; + + struct wlr_drm_format fmt = { + .format = a->format, + .len = 0, + .capacity = capacity, + .modifiers = modifiers, + }; + + for (size_t i = 0; i < a->len; i++) { + for (size_t j = 0; j < b->len; j++) { + if (a->modifiers[i] == b->modifiers[j]) { + ASSERT(fmt.len < fmt.capacity); + fmt.modifiers[fmt.len++] = a->modifiers[i]; + break; + } + } + } + + wlr_drm_format_finish(dst); + *dst = fmt; + return true; +} + +bool wlr_drm_format_copy(wlr_drm_format* dst, const wlr_drm_format* src) { + ASSERT(src->len <= src->capacity); + + uint64_t* modifiers = (uint64_t*)malloc(sizeof(*modifiers) * src->len); + if (!modifiers) + return false; + + memcpy(modifiers, src->modifiers, sizeof(*modifiers) * src->len); + + wlr_drm_format_finish(dst); + dst->capacity = src->len; + dst->len = src->len; + dst->format = src->format; + dst->modifiers = modifiers; + return true; +} + +const wlr_drm_format_set* wlr_renderer_get_render_formats(wlr_renderer* r) { + if (!r->impl->get_render_formats) + return nullptr; + + return r->impl->get_render_formats(r); +} + +bool output_pick_format(wlr_output* output, const wlr_drm_format_set* display_formats, wlr_drm_format* format, uint32_t fmt) { + + const wlr_drm_format_set* render_formats = wlr_renderer_get_render_formats(g_pCompositor->m_sWLRRenderer); + if (render_formats == NULL) { + wlr_log(WLR_ERROR, "Failed to get render formats"); + return false; + } + + const wlr_drm_format* render_format = wlr_drm_format_set_get(render_formats, fmt); + if (render_format == NULL) { + wlr_log(WLR_DEBUG, "Renderer doesn't support format 0x%" PRIX32, fmt); + return false; + } + + if (display_formats != NULL) { + const wlr_drm_format* display_format = wlr_drm_format_set_get(display_formats, fmt); + if (display_format == NULL) { + wlr_log(WLR_DEBUG, "Output doesn't support format 0x%" PRIX32, fmt); + return false; + } + if (!wlr_drm_format_intersect(format, display_format, render_format)) { + wlr_log(WLR_DEBUG, + "Failed to intersect display and render " + "modifiers for format 0x%" PRIX32 " on output %s", + fmt, output->name); + return false; + } + } else { + // The output can display any format + if (!wlr_drm_format_copy(format, render_format)) + return false; + } + + if (format->len == 0) { + wlr_drm_format_finish(format); + wlr_log(WLR_DEBUG, "Failed to pick output format"); + return false; + } + + return true; +} + +bool output_pick_cursor_format(struct wlr_output* output, struct wlr_drm_format* format) { + struct wlr_allocator* allocator = output->allocator; + ASSERT(allocator != NULL); + + const struct wlr_drm_format_set* display_formats = NULL; + if (output->impl->get_cursor_formats) { + display_formats = output->impl->get_cursor_formats(output, allocator->buffer_caps); + if (display_formats == NULL) { + wlr_log(WLR_DEBUG, "Failed to get cursor display formats"); + return false; + } + } + + // Note: taken from https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4596/diffs#diff-content-e3ea164da86650995728d70bd118f6aa8c386797 + // If this fails to find a shared modifier try to use a linear + // modifier. This avoids a scenario where the hardware cannot render to + // linear textures but only linear textures are supported for cursors, + // as is the case with Nvidia and VmWare GPUs + if (!output_pick_format(output, display_formats, format, DRM_FORMAT_ARGB8888)) { + // Clear the format as output_pick_format doesn't zero it + memset(format, 0, sizeof(*format)); + return output_pick_format(output, NULL, format, DRM_FORMAT_ARGB8888); + } + return true; +} diff --git a/src/hyprland/cursor.hpp b/src/hyprland/cursor.hpp new file mode 100644 index 0000000..cf6f0ce --- /dev/null +++ b/src/hyprland/cursor.hpp @@ -0,0 +1,13 @@ +#include + +/* +The following functions are copied 1:1 from the hyprland codebase. +This is nessecary, as these are static interop functions which wlroots which are not accessible through headers. +Will probably be obsolete anyway after https://github.com/hyprwm/Hyprland/pull/6608 +*/ + +bool wlr_drm_format_intersect(wlr_drm_format* dst, const wlr_drm_format* a, const wlr_drm_format* b); +bool wlr_drm_format_copy(wlr_drm_format* dst, const wlr_drm_format* src); +const wlr_drm_format_set* wlr_renderer_get_render_formats(wlr_renderer* r); +bool output_pick_format(wlr_output* output, const wlr_drm_format_set* display_formats, wlr_drm_format* format, uint32_t fmt); +bool output_pick_cursor_format(struct wlr_output* output, struct wlr_drm_format* format); diff --git a/src/main.cpp b/src/main.cpp index 33bd6d1..76ea88d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,24 @@ void hkDamageIfSoftware(void* thisptr) { g_pDynamicCursors->damageSoftware((CPointerManager*) thisptr); } +typedef wlr_buffer* (*origRenderHWCursorBuffer)(void*, SP, SP); +inline CFunctionHook* g_pRenderHWCursorBufferHook = nullptr; +wlr_buffer* hkRenderHWCursorBuffer(void* thisptr, SP state, SP texture) { + return g_pDynamicCursors->renderHardware((CPointerManager*) thisptr, state, texture); +} + +typedef bool (*origSetHWCursorBuffer)(void*, SP, wlr_buffer*); +inline CFunctionHook* g_pSetHWCursorBufferHook = nullptr; +bool hkSetHWCursorBuffer(void* thisptr, SP state, wlr_buffer* buffer) { + return g_pDynamicCursors->setHardware((CPointerManager*) thisptr, state, buffer); +} + +typedef void (*origOnCursorMoved)(void*); +inline CFunctionHook* g_pOnCursorMovedHook = nullptr; +void hkOnCursorMoved(void* thisptr) { + return g_pDynamicCursors->onCursorMoved((CPointerManager*) thisptr); +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -41,7 +59,20 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { g_pDamageIfSoftwareHook = HyprlandAPI::createFunctionHook(PHANDLE, DAMAGE_IF_SOFTWARE_METHODS[0].address, (void*) &hkDamageIfSoftware); g_pDamageIfSoftwareHook->hook(); + static const auto RENDER_HW_CURSOR_BUFFER_METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderHWCursorBuffer"); + g_pRenderHWCursorBufferHook = HyprlandAPI::createFunctionHook(PHANDLE, RENDER_HW_CURSOR_BUFFER_METHODS[0].address, (void*) &hkRenderHWCursorBuffer); + g_pRenderHWCursorBufferHook->hook(); + + static const auto SET_HW_CURSOR_BUFFER_METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "setHWCursorBuffer"); + g_pSetHWCursorBufferHook = HyprlandAPI::createFunctionHook(PHANDLE, SET_HW_CURSOR_BUFFER_METHODS[0].address, (void*) &hkSetHWCursorBuffer); + g_pSetHWCursorBufferHook->hook(); + + static const auto ON_CURSOR_MOVED_METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "onCursorMoved"); + g_pOnCursorMovedHook = HyprlandAPI::createFunctionHook(PHANDLE, ON_CURSOR_MOVED_METHODS[0].address, (void*) &hkOnCursorMoved); + g_pOnCursorMovedHook->hook(); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_LENGTH, Hyprlang::INT{20}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_HW_DEBUG, Hyprlang::INT{0}); return {"dynamic-cursors", "The most stupid cursor plugin.", "Virt", "1.1"}; }