feat: inefficient support for hardware cursors

This commit is contained in:
Virt 2024-06-22 15:01:47 +02:00
commit 581a094b7b
6 changed files with 339 additions and 2 deletions

View file

@ -1,18 +1,29 @@
#include "globals.hpp"
#include <chrono>
#include <cmath>
#include <cstdlib>
#define private public
#include <hyprland/src/managers/PointerManager.hpp>
#include <hyprland/src/render/OpenGL.hpp>
#include <hyprland/src/Compositor.hpp>
#undef private
#include <hyprland/src/Compositor.hpp>
#include <hyprland/src/config/ConfigValue.hpp>
#include "hyprland/cursor.hpp"
#include <hyprland/wlr/interfaces/wlr_output.h>
#include <hyprland/wlr/render/interface.h>
#include <hyprland/wlr/render/wlr_renderer.h>
#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<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos) {
if (!pointers->hasCursor())
@ -46,6 +57,10 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> 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<CPointerManager::SMonitorPointerState> state, SP<CTexture> 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<CPointerManager::SMonitorPointerState> 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();

View file

@ -1,15 +1,24 @@
#include "globals.hpp"
#define private public
#include <hyprland/src/managers/PointerManager.hpp>
#undef private
#include <hyprutils/math/Vector2D.hpp>
class CDynamicCursors;
class CDynamicCursors {
public:
/* hook on onCursorMoved */
void onCursorMoved(CPointerManager* pointers);
/* hook on renderSoftwareCursorsFor */
void renderSoftware(CPointerManager* pointers, SP<CMonitor> pMonitor, timespec* now, CRegion& damage, std::optional<Vector2D> overridePos);
/* hook on damageIfSoftware*/
void damageSoftware(CPointerManager* pointers);
/* hook on renderHWCursorBuffer */
wlr_buffer* renderHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture);
/* hook on setHWCursorBuffer */
bool setHardware(CPointerManager* pointers, SP<CPointerManager::SMonitorPointerState> state, wlr_buffer* buf);
private:
// calculates the current angle of the cursor

View file

@ -3,5 +3,6 @@
#include <hyprland/src/plugins/PluginAPI.hpp>
#define CONFIG_LENGTH "plugin:dynamic-cursors:length"
#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug"
inline HANDLE PHANDLE = nullptr;

137
src/hyprland/cursor.cpp Normal file
View file

@ -0,0 +1,137 @@
#include <hyprland/src/managers/PointerManager.hpp>
#include <hyprland/src/Compositor.hpp>
#include <hyprland/wlr/interfaces/wlr_output.h>
#include <hyprland/wlr/render/interface.h>
#include <hyprland/wlr/render/wlr_renderer.h>
/*
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;
}

13
src/hyprland/cursor.hpp Normal file
View file

@ -0,0 +1,13 @@
#include <hyprland/src/managers/PointerManager.hpp>
/*
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);

View file

@ -20,6 +20,24 @@ void hkDamageIfSoftware(void* thisptr) {
g_pDynamicCursors->damageSoftware((CPointerManager*) thisptr);
}
typedef wlr_buffer* (*origRenderHWCursorBuffer)(void*, SP<CPointerManager::SMonitorPointerState>, SP<CTexture>);
inline CFunctionHook* g_pRenderHWCursorBufferHook = nullptr;
wlr_buffer* hkRenderHWCursorBuffer(void* thisptr, SP<CPointerManager::SMonitorPointerState> state, SP<CTexture> texture) {
return g_pDynamicCursors->renderHardware((CPointerManager*) thisptr, state, texture);
}
typedef bool (*origSetHWCursorBuffer)(void*, SP<CPointerManager::SMonitorPointerState>, wlr_buffer*);
inline CFunctionHook* g_pSetHWCursorBufferHook = nullptr;
bool hkSetHWCursorBuffer(void* thisptr, SP<CPointerManager::SMonitorPointerState> 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"};
}