diff --git a/src/config/config.hpp b/src/config/config.hpp index d9acb94..a734aeb 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -10,6 +10,11 @@ #define CONFIG_THRESHOLD "threshold" #define CONFIG_HW_DEBUG "hw_debug" +#define CONFIG_INVERT "plugin:dynamic-cursors:invert" +#define CONFIG_INVERT_SHADER "plugin:dynamic-cursors:invert:shader" +#define CONFIG_INVERT_CHROMA "plugin:dynamic-cursors:invert:chroma" +#define CONFIG_INVERT_CHROMA_COLOR "plugin:dynamic-cursors:invert:chroma:color" + #define CONFIG_SHAKE "shake:enabled" #define CONFIG_SHAKE_NEAREST "shake:nearest" #define CONFIG_SHAKE_THRESHOLD "shake:threshold" diff --git a/src/cursor.cpp b/src/cursor.cpp index 5432f34..bd511b6 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -54,7 +54,12 @@ 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) { +<<<<<<< HEAD static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); +======= + static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); + static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); +>>>>>>> c8ae8fe (feat: basic inverted rendering) if (!pointers->hasCursor()) return; @@ -94,10 +99,17 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo box.rot = resultShown.rotation; // now pass the hotspot to rotate around +<<<<<<< HEAD renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, nullptr, 0, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST, resultShown.stretch.angle, resultShown.stretch.magnitude); if (pointers->currentCursorImage.surface) pointers->currentCursorImage.surface->resource()->frame(now); +======= + if (**PINVERT) + renderCursorTextureInternalWithDamageInverted(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + else + renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); +>>>>>>> c8ae8fe (feat: basic inverted rendering) } /* @@ -131,9 +143,16 @@ 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. */ +<<<<<<< HEAD SP CDynamicCursors::renderHardware(CPointerManager* pointers, SP state, SP texture) { static auto* const* PHW_DEBUG = (Hyprlang::INT* const*) getConfig(CONFIG_HW_DEBUG); static auto* const* PNEAREST = (Hyprlang::INT* const*) getConfig(CONFIG_SHAKE_NEAREST); +======= +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(); + static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); + static auto* const* PINVERT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_INVERT)->getDataStaticPtr(); +>>>>>>> c8ae8fe (feat: basic inverted rendering) auto output = state->monitor->output; @@ -243,7 +262,14 @@ SP CDynamicCursors::renderHardware(CPointerManager* pointer xbox.rot = resultShown.rotation; // use our custom draw function +<<<<<<< HEAD renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.waitTimeline, pointers->currentCursorImage.waitPoint, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST, resultShown.stretch.angle, resultShown.stretch.magnitude); +======= + if (**PINVERT) + renderCursorTextureInternalWithDamageInverted(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + else + renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); +>>>>>>> c8ae8fe (feat: basic inverted rendering) g_pHyprOpenGL->end(); glFlush(); diff --git a/src/invert/shader.cpp b/src/invert/shader.cpp new file mode 100644 index 0000000..471dc70 --- /dev/null +++ b/src/invert/shader.cpp @@ -0,0 +1,42 @@ +#include "src/debug/Log.hpp" + +#define private public +#include +#undef private + +#include +#include + +#include "shader.hpp" + +void CInversionShader::compile(std::string vertex, std::string fragment) { + + program = g_pHyprOpenGL->createProgram(vertex, fragment); + + texAttrib = glGetAttribLocation(program, "texcoord"); + posAttrib = glGetAttribLocation(program, "pos"); + + proj = glGetUniformLocation(program, "proj"); + backgroundTex = glGetUniformLocation(program, "backgroundTex"); + cursorTex = glGetUniformLocation(program, "cursorTex"); + alpha = glGetUniformLocation(program, "alpha"); + applyTint = glGetUniformLocation(program, "applyTint"); + tint = glGetUniformLocation(program, "tint"); +} + +CInversionShader::~CInversionShader() { + glDeleteProgram(program); + program = 0; +} + +CShaders::CShaders() { + RASSERT(eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)), + "Couldn't set current EGL!"); + + rgba.compile(VERTEX, RGBA); + ext.compile(VERTEX, EXT); + rgbx.compile(VERTEX, RGBX); + + RASSERT(eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)), + "Couldn't set current EGL!"); +} diff --git a/src/invert/shader.hpp b/src/invert/shader.hpp new file mode 100644 index 0000000..23ae14d --- /dev/null +++ b/src/invert/shader.hpp @@ -0,0 +1,152 @@ +#include +#include + +// we need our own shader class as we have two textures +class CInversionShader { + public: + GLuint program = 0; + GLint proj = -1; + GLint color = -1; + GLint alphaMatte = -1; + GLint cursorTex = -1; + GLint backgroundTex = -1; + GLint alpha = -1; + GLint posAttrib = -1; + GLint texAttrib = -1; + GLint matteTexAttrib = -1; + GLint discardOpaque = -1; + GLint discardAlpha = -1; + GLfloat discardAlphaValue = -1; + + GLint applyTint = -1; + GLint tint = -1; + + void compile(std::string vertex, std::string fragment); + ~CInversionShader(); +}; + +class CShaders { + public: + CInversionShader rgba; + CInversionShader rgbx; + CInversionShader ext; + + CShaders(); +}; + +inline std::unique_ptr g_pShaders; + +inline const std::string VERTEX = R"#( + uniform mat3 proj; + + attribute vec2 pos; + attribute vec2 texcoord; + + varying vec2 v_texcoord; + varying vec2 v_screencord; + + void main() { + gl_Position = vec4(proj * vec3(pos, 1.0), 1.0); + + v_screencord = gl_Position.xy / 2.0 + vec2(0.5, 0.5); // transform to texture coords + v_texcoord = texcoord; + } +)#"; + +inline const std::string RGBA = R"#( + precision highp float; + + varying vec2 v_texcoord; // is in 0-1 + varying vec2 v_screencord; // is in 0-1 + + uniform sampler2D cursorTex; + uniform sampler2D backgroundTex; + uniform float alpha; + + uniform int applyTint; + uniform vec3 tint; + + void main() { + + vec4 cursor = texture2D(cursorTex, v_texcoord); + + // load and invert background + vec4 background = texture2D(backgroundTex, v_screencord); + background.rgb = vec3(1.0, 1.0, 1.0) - background.rgb; + background *= cursor[3]; // premultiplied alpha + + // invert hue + //background.rgb = -background.rgb + dot(vec3(0.26312, 0.5283, 0.10488), background.rgb) * 2.0; + + vec4 pixColor = cursor; + //if (cursor[3] != 1.0) pixColor = cursor; + //pixColor = vec4(v_screencord + vec2(1.0, 1.0) / 2.0, 0.0, 1.0); + + vec4 chroma = vec4(0.0, 0.0, 0.0, 1.0); + float diff = (abs(chroma.x - cursor.x) + abs(chroma.y - cursor.y) + abs(chroma.z - cursor.z) + abs(chroma.w - cursor.w)) / 4.0; + pixColor = background * (1.0 - diff) + cursor * diff; + + if (applyTint == 1) { + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; + } + + gl_FragColor = pixColor * alpha; + } +)#"; + +inline const std::string RGBX = R"#( + precision highp float; + varying vec2 v_texcoord; + uniform sampler2D cursorTex; + uniform sampler2D backgroundTex; + uniform float alpha; + + uniform int applyTint; + uniform vec3 tint; + + void main() { + + + vec4 pixColor = vec4(texture2D(cursorTex, v_texcoord).rgb, 1.0); + + if (applyTint == 1) { + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; + } + + pixColor.r = 1.0; + + gl_FragColor = pixColor * alpha; + } +)#"; + +inline const std::string EXT = R"#( + #extension GL_OES_EGL_image_external : require + + precision highp float; + varying vec2 v_texcoord; + uniform samplerExternalOES texture0; + uniform samplerExternalOES texture1; + uniform float alpha; + + uniform int applyTint; + uniform vec3 tint; + + void main() { + + vec4 pixColor = texture2D(texture0, v_texcoord); + + if (applyTint == 1) { + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; + } + + pixColor.r = 1.0; + + gl_FragColor = pixColor * alpha; + } +)#"; diff --git a/src/main.cpp b/src/main.cpp index 3c52a1d..ad71250 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "cursor.hpp" #include "config/config.hpp" #include "src/debug/Log.hpp" +#include "invert/shader.hpp" #include "src/managers/PointerManager.hpp" #include "src/version.h" @@ -112,6 +113,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { addConfig(CONFIG_SHAKE_THRESHOLD, 4.0f); addConfig(CONFIG_SHAKE_FACTOR, 1.5f); + addConfig(CONFIG_INVERT, 0); + addConfig(CONFIG_INVERT_SHADER, "normal"); + addConfig(CONFIG_INVERT_CHROMA, 0); + addConfig(CONFIG_INVERT_CHROMA_COLOR, 0xFF000000); // opaque black + addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic"); addShapeConfig(CONFIG_TILT_LIMIT, 5000); @@ -128,6 +134,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { // init things g_pDynamicCursors = std::make_unique(); + g_pShaders = std::make_unique(); // try hooking try { @@ -145,7 +152,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { } return {"dynamic-cursors", "a plugin to make your hyprland cursor more realistic, also adds shake to find", "Virt", "0.1"}; -} + } APICALL EXPORT void PLUGIN_EXIT() { } diff --git a/src/renderer.cpp b/src/renderer.cpp index 301a48f..ce71d84 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -11,6 +11,7 @@ #include #include "renderer.hpp" +#include "invert/shader.hpp" /* This is the projectBox method from hyprland, but with support for rotation around a point, the hotspot. @@ -156,3 +157,101 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion glBindTexture(tex->m_iTarget, 0); } + +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, SP waitTimeline, uint64_t waitPoint, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch, int mode, bool chroma, CColor chromaColor, Vector2D screenOffset) { + TRACY_GPU_ZONE("RenderDynamicCursor"); + + if (waitTimeline != nullptr) { + if (!g_pHyprOpenGL->waitForTimelinePoint(waitTimeline, waitPoint)) { + Debug::log(ERR, "renderTextureInternalWithDamage: failed to wait for explicit sync point {}", waitPoint); + return; + } + } + + alpha = std::clamp(alpha, 0.f, 1.f); + + if (damage->empty()) + return; + + CBox newBox = *pBox; + g_pHyprOpenGL->m_RenderData.renderModif.applyToBox(newBox); + + // get transform + const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!g_pHyprOpenGL->m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : g_pHyprOpenGL->m_RenderData.pMonitor->transform)); + float matrix[9]; + projectCursorBox(matrix, newBox, TRANSFORM, newBox.rot, g_pHyprOpenGL->m_RenderData.monitorProjection.data(), hotspot, stretchAngle, stretch); + + float glMatrix[9]; + matrixMultiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix); + + CInversionShader* shader = nullptr; + + switch (tex->m_iType) { + case TEXTURE_RGBA: shader = &g_pShaders->rgba; break; + case TEXTURE_RGBX: shader = &g_pShaders->rgbx; break; + case TEXTURE_EXTERNAL: shader = &g_pShaders->ext; break; + default: RASSERT(false, "tex->m_iTarget unsupported!"); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(tex->m_iTarget, tex->m_iTexID); + + if (g_pHyprOpenGL->m_RenderData.useNearestNeighbor || nearest) { + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } else { + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + glUseProgram(shader->program); + +#ifndef GLES2 + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); +#else + matrixTranspose(glMatrix, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); +#endif + + glUniform2f(shader->screenOffset, screenOffset.x, screenOffset.y); + glUniform1i(shader->backgroundTex, 0); + glUniform1i(shader->cursorTex, 1); + glUniform1f(shader->alpha, alpha); + + glUniform1i(shader->mode, mode); + glUniform1i(shader->chroma, chroma); + glUniform4f(shader->chromaColor, chromaColor.r, chromaColor.g, chromaColor.b, chromaColor.a); + + CBox transformedBox = newBox; + transformedBox.transform(wlTransformToHyprutils(invertTransform(g_pHyprOpenGL->m_RenderData.pMonitor->transform)), g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.y); + + glUniform1i(shader->applyTint, 0); + + glVertexAttribPointer(shader->posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(shader->texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(shader->posAttrib); + glEnableVertexAttribArray(shader->texAttrib); + + if (g_pHyprOpenGL->m_RenderData.clipBox.width != 0 && g_pHyprOpenGL->m_RenderData.clipBox.height != 0) { + CRegion damageClip{g_pHyprOpenGL->m_RenderData.clipBox.x, g_pHyprOpenGL->m_RenderData.clipBox.y, g_pHyprOpenGL->m_RenderData.clipBox.width, g_pHyprOpenGL->m_RenderData.clipBox.height}; + damageClip.intersect(*damage); + + if (!damageClip.empty()) { + for (auto& RECT : damageClip.getRects()) { + g_pHyprOpenGL->scissor(&RECT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + } else { + for (auto& RECT : damage->getRects()) { + g_pHyprOpenGL->scissor(&RECT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + + glDisableVertexAttribArray(shader->posAttrib); + glDisableVertexAttribArray(shader->texAttrib); + + glBindTexture(tex->m_iTarget, 0); +} diff --git a/src/renderer.hpp b/src/renderer.hpp index 81dc1ff..c6a4e01 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -2,3 +2,4 @@ #include void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, SP waitTimeline, uint64_t waitPoint, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch); +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, SP waitTimeline, uint64_t waitPoint, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch, int mode, bool chroma, CColor chromaColor, Vector2D screenOffset);