From c8ae8fedb8414c8fe6abc710ea971fee31323bf3 Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Sun, 30 Jun 2024 11:10:42 +0200 Subject: [PATCH] feat: basic inverted rendering --- src/cursor.cpp | 12 +++- src/globals.hpp | 35 ++++++---- src/invert/shader.cpp | 42 ++++++++++++ src/invert/shader.hpp | 152 ++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 7 ++ src/renderer.cpp | 90 +++++++++++++++++++++++++ src/renderer.hpp | 1 + 7 files changed, 323 insertions(+), 16 deletions(-) create mode 100644 src/invert/shader.cpp create mode 100644 src/invert/shader.hpp diff --git a/src/cursor.cpp b/src/cursor.cpp index 60018df..741bd57 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -28,6 +28,7 @@ Is also largely identical to hyprlands impl, but uses our custom rendering to ro */ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMonitor, timespec* now, CRegion& damage, std::optional overridePos) { 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(); if (!pointers->hasCursor()) return; @@ -63,7 +64,10 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo box.rot = this->angle; // now pass the hotspot to rotate around - renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + 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); } /* @@ -96,6 +100,7 @@ It is largely copied from hyprland, but adjusted to allow the cursor to be rotat 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(); auto output = state->monitor->output; @@ -164,7 +169,10 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SPangle; // use our custom draw function - renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + 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); g_pHyprOpenGL->end(); glFlush(); diff --git a/src/globals.hpp b/src/globals.hpp index 7b94a4d..22350c5 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -2,21 +2,28 @@ #include -#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" -#define CONFIG_MODE "plugin:dynamic-cursors:mode" -#define CONFIG_SHAKE "plugin:dynamic-cursors:shake" -#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold" -#define CONFIG_SHAKE_NEAREST "plugin:dynamic-cursors:shake:nearest" -#define CONFIG_SHAKE_THRESHOLD "plugin:dynamic-cursors:shake:threshold" -#define CONFIG_SHAKE_FACTOR "plugin:dynamic-cursors:shake:factor" -#define CONFIG_SHAKE_EFFECTS "plugin:dynamic-cursors:shake:effects" -#define CONFIG_SHAKE_IPC "plugin:dynamic-cursors:shake:ipc" -#define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length" -#define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset" -#define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit" -#define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function" +#define CONFIG_ENABLED "plugin:dynamic-cursors:enabled" +#define CONFIG_MODE "plugin:dynamic-cursors:mode" +#define CONFIG_THRESHOLD "plugin:dynamic-cursors:threshold" +#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" -#define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" +#define CONFIG_LENGTH "plugin:dynamic-cursors:rotate:length" +#define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset" + +#define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit" +#define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function" + +#define CONFIG_SHAKE "plugin:dynamic-cursors:shake" +#define CONFIG_SHAKE_NEAREST "plugin:dynamic-cursors:shake:nearest" +#define CONFIG_SHAKE_THRESHOLD "plugin:dynamic-cursors:shake:threshold" +#define CONFIG_SHAKE_FACTOR "plugin:dynamic-cursors:shake:factor" +#define CONFIG_SHAKE_EFFECTS "plugin:dynamic-cursors:shake:effects" +#define CONFIG_SHAKE_IPC "plugin:dynamic-cursors:shake:ipc" + +#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" inline HANDLE PHANDLE = nullptr; 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 1761726..9e6dccf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "globals.hpp" #include "cursor.hpp" +#include "invert/shader.hpp" #include "src/managers/PointerManager.hpp" typedef void (*origRenderSofwareCursorsFor)(void*, SP, timespec*, CRegion&, std::optional); @@ -78,6 +79,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { } g_pDynamicCursors = std::make_unique(); + g_pShaders = std::make_unique(); static const auto RENDER_SOFTWARE_CURSORS_FOR_METHODS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderSoftwareCursorsFor"); g_pRenderSoftwareCursorsForHook = HyprlandAPI::createFunctionHook(PHANDLE, RENDER_SOFTWARE_CURSORS_FOR_METHODS[0].address, (void*) &hkRenderSoftwareCursorsFor); @@ -116,6 +118,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_THRESHOLD, Hyprlang::FLOAT{4}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_SHAKE_FACTOR, Hyprlang::FLOAT{1.5}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT, Hyprlang::INT{0}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_SHADER, Hyprlang::STRING{"normal"}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA, Hyprlang::INT{0}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_INVERT_CHROMA_COLOR, Hyprlang::INT{0xFF000000}); // opaque black + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MASS, Hyprlang::INT{5000}); diff --git a/src/renderer.cpp b/src/renderer.cpp index 7dbb304..f58ed0b 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -11,6 +11,7 @@ #include "renderer.hpp" #include "hyprland/math.hpp" +#include "invert/shader.hpp" /* This is the projectBox method from hyprland, but with support for rotation around a point, the hotspot. @@ -136,3 +137,92 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion glBindTexture(tex->m_iTarget, 0); } + +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) { + TRACY_GPU_ZONE("RenderDynamicCursor"); + + 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(wlr_output_transform_invert(!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); + + float glMatrix[9]; + wlr_matrix_multiply(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!"); + } + + // bind background and cursor texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(g_pHyprOpenGL->m_RenderData.pCurrentMonData->offloadFB.m_cTex->m_iTarget, g_pHyprOpenGL->m_RenderData.pCurrentMonData->offloadFB.m_cTex->m_iTexID); + + glActiveTexture(GL_TEXTURE1); + 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 + wlr_matrix_transpose(glMatrix, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); +#endif + glUniform1i(shader->backgroundTex, 0); + glUniform1i(shader->cursorTex, 1); + glUniform1f(shader->alpha, alpha); + + CBox transformedBox = newBox; + transformedBox.transform(wlTransformToHyprutils(wlr_output_transform_invert(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 05f26a4..62112e6 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -2,4 +2,5 @@ #include void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); +void renderCursorTextureInternalWithDamageInverted(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);