diff --git a/README.md b/README.md
index 7ef2f7d..d6cc388 100644
--- a/README.md
+++ b/README.md
@@ -133,13 +133,19 @@ plugin:dynamic-cursors {
# controls how powerful the tilt is, the lower, the more power
# this value controls at which speed (px/s) the full tilt is reached
+ # the full tilt being 60° in both directions
limit = 5000
# relationship between speed and tilt, supports these values:
# linear - a linear function is used
# quadratic - a quadratic function is used (most realistic to actual air drag)
# negative_quadratic - negative version of the quadratic one, feels more aggressive
+ # see `activation` in `src/mode/utils.cpp` for how exactly the calculation is done
function = negative_quadratic
+
+ # time window (ms) over which the speed is calculated
+ # higher values will make slow motions smoother but more delayed
+ window = 100
}
# for mode = stretch
@@ -147,13 +153,19 @@ plugin:dynamic-cursors {
# controls how much the cursor is stretched
# this value controls at which speed (px/s) the full stretch is reached
+ # the full stretch being twice the original length
limit = 3000
# relationship between speed and stretch amount, supports these values:
# linear - a linear function is used
# quadratic - a quadratic function is used
# negative_quadratic - negative version of the quadratic one, feels more aggressive
+ # see `activation` in `src/mode/utils.cpp` for how exactly the calculation is done
function = quadratic
+
+ # time window (ms) over which the speed is calculated
+ # higher values will make slow motions smoother but more delayed
+ window = 100
}
# configure shake to find
@@ -223,7 +235,7 @@ plugin:dynamic-cursors {
### shape rules
Shape Rules can be used to override the mode or its behaviour on a per-shape basis. They can be defined with the keyword `shaperule` in the config file, preferably in the `plugin:dynamic-cursors` section.
-**Note:** Shape rules only apply to server side cursor shapes. Sadly, not everyone supports server side cursors yet, which means shape rules won't work with apps using toolkits like e.g. GTK.
+**Note:** Shape rules only apply to server side cursor shapes. Sadly, not everyone supports server side cursors yet, which means shape rules won't work in some applications.
A shape rule usually consists of three parts:
```
@@ -270,7 +282,7 @@ To use hyprcursor for magnified shapes, the following must be met:
- the hyprcursor theme should be **SVG-based**
As mentioned, there are some caveats to it. Here are the most common ones:
-- **Still pixelated on GTK apps and xwayland** - These apps are using clientside cursors, so the program itself is specifying the cursor shape, hence we cannot load a higher resolution for it. You can set a specific shape to show in these cases with the `fallback` option (see config).
+- **Still pixelated some apps and xwayland** - These apps probably are using clientside cursors, so the program itself is specifying the cursor shape, hence we cannot load a higher resolution for it. You can set a specific shape to show in these cases with the `fallback` option (see config).
- **Blurred at very large sizes** - The high resolution cursors are preloaded at a fixed size. If you magnify your cursor beyond this size, your cursors will look blurry. You can increase the preload size with the `resolution` option (see config), at the expense of some memory and higher loading times.
Loading a cursor theme at a high resolution is relatively resource intensive. This plugin thus loads the theme asynchronously on a seperate thread, meaning your session will stay interactive during this time. But this means that when loading the plugin or changing cursor theme, your CPU might spike momentarily and the high-resolution theme will only be available after a short time (usually just a couple of seconds).
@@ -282,7 +294,7 @@ This plugin has a couple of dispatchers to trigger certain effects with a keybin
- `size` (optional): overrides magnification factor
## performance
-> **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors.
+> **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors. If your are using an nvidia gpu, this plugin will fall back to software cursors, see [compatibility](#compatibility).
Depending on your Hyprland configuration, this plugin can have a different performance impact, mainly depending on whether you are using software or hardware cursors:
@@ -290,14 +302,14 @@ Depending on your Hyprland configuration, this plugin can have a different perfo
Transforming the cursor can be done in the same draw call that is used to draw the cursor anyway, so there is no additional performance impact. Note however that software cursors in of themselves are not really efficient.
**Hardware Cursors**: Medium performance impact.
-To transform the cursor smoothly, the cursor shape needs to be changed quite often. This is not exactly compatible with how hardware cursors are intended to work. Thus, performance can be compared to how an animated cursor shape would be rendered, every time the cursor is not stationary. It is however still more performant than software cursors.
Another limitation of hardware cursors are the size they are rendered at. This means that when the cursor is being magnified, software cursors will be used temporarily.
+To transform the cursor smoothly, the cursor shape needs to be changed quite often. This is not exactly compatible with how hardware cursors are intended to work. Thus, performance can be compared to how an animated cursor shape would be rendered, every time the cursor is not stationary. It is however still more performant than software cursors.
Another limitation of hardware cursors are the size they are rendered at. This means that when the cursor is being magnified, software cursors will be used temporarily.
If your are using an Nvidia GPU, the plugin will also fall back to software cursors because of driver limitations (see [compatibility](#compatibility)).
If you have any ideas to improve performance, let me know!
## compatibility
Compatibility with other plugins is not guaranteed. It probably should work with most plugins, unless they also change your cursor's behaviour. It will however work with any cursor theme.
-Also, this plugin won't work if your hardware cursors rely on `cursor:allow_dumb_copy = true`, which is probably the case if you are on nvidia. You'll probably have to wait until hardware cursors are correctly supported on Hyprland, and use software cursors in the meantime.
+The plugin does also not support _hardware cursors_ on Nvidia GPUs. If you are on nvidia, Hyprland will use CPU rendering to draw into your hardware cursor buffer, because of driver limitations. When using an effect with this plugin however, we potentially draw into the cursor buffer every frame (when the cursor is moving) which is really resource intensive if done on the CPU. Additionally the whole drawing logic would have to be implemented again to be able run on the CPU too. This is why on Nvidia GPUs, the plugin will automatically force the compositor to use software cursors, avoiding the above issues at a slight performance penalty.
## development
To work on this plugin, you can clone this repository and use the Makefile to build it. I suggest opening a nested Hyprland session, and loading the plugin there:
@@ -310,7 +322,7 @@ In some cases when working in a nest, nothing will happen with the plugin loaded
If you want to debug hardware cursors, this plugin also has an additional configuration option, `plugin:dynamic-cursors:hw_debug` which when true will show where the whole cursor buffer is, and also shows when it is updated.
-Also make sure you disable the plugin on your host session, otherwise your cursor will be rotated twice.
+Also make sure you disable the plugin on your host session if your are using hardware cursors, otherwise your cursor will be rotated twice.
## license
This plugin is licensed under the MIT License. Have a look at the `LICENSE.md` file for more information.
diff --git a/src/config/config.hpp b/src/config/config.hpp
index 572a070..90b95b5 100644
--- a/src/config/config.hpp
+++ b/src/config/config.hpp
@@ -29,9 +29,11 @@
#define CONFIG_TILT_LIMIT "tilt:limit"
#define CONFIG_TILT_FUNCTION "tilt:function"
+#define CONFIG_TILT_WINDOW "tilt:window"
#define CONFIG_STRETCH_LIMIT "stretch:limit"
#define CONFIG_STRETCH_FUNCTION "stretch:function"
+#define CONFIG_STRETCH_WINDOW "stretch:window"
#define CONFIG_HIGHRES_ENABLED "hyprcursor:enabled"
#define CONFIG_HIGHRES_NEAREST "hyprcursor:nearest"
diff --git a/src/main.cpp b/src/main.cpp
index ab26036..d467a54 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -135,9 +135,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
addShapeConfig(CONFIG_TILT_FUNCTION, "negative_quadratic");
addShapeConfig(CONFIG_TILT_LIMIT, 5000);
+ addShapeConfig(CONFIG_TILT_WINDOW, 100);
addShapeConfig(CONFIG_STRETCH_FUNCTION, "negative_quadratic");
addShapeConfig(CONFIG_STRETCH_LIMIT, 3000);
+ addShapeConfig(CONFIG_STRETCH_WINDOW, 100);
addShapeConfig(CONFIG_ROTATE_LENGTH, 20);
addShapeConfig(CONFIG_ROTATE_OFFSET, 0.0f);
diff --git a/src/mode/ModeStretch.cpp b/src/mode/ModeStretch.cpp
index 9eb1203..f6b0fa4 100644
--- a/src/mode/ModeStretch.cpp
+++ b/src/mode/ModeStretch.cpp
@@ -11,11 +11,13 @@ EModeUpdate CModeStretch::strategy() {
SModeResult CModeStretch::update(Vector2D pos) {
static auto const* PFUNCTION = (Hyprlang::STRING const*) getConfig(CONFIG_STRETCH_FUNCTION);
static auto* const* PLIMIT = (Hyprlang::INT* const*) getConfig(CONFIG_STRETCH_LIMIT);
+ static auto* const* PWINDOW = (Hyprlang::INT* const*) getConfig(CONFIG_STRETCH_WINDOW);
auto function = g_pShapeRuleHandler->getStringOr(CONFIG_STRETCH_FUNCTION, *PFUNCTION);
auto limit = g_pShapeRuleHandler->getIntOr(CONFIG_STRETCH_LIMIT, **PLIMIT);
+ auto window = g_pShapeRuleHandler->getIntOr(CONFIG_STRETCH_WINDOW, **PWINDOW);
// create samples array
- int max = std::max(1, (int)(g_pHyprRenderer->m_mostHzMonitor->m_refreshRate / 10)); // 100ms worth of history, avoiding divide by 0
+ int max = std::max(1, (int)(g_pHyprRenderer->m_mostHzMonitor->m_refreshRate / 1000 * window)); // [window]ms worth of history, avoiding divide by 0
samples.resize(max, pos);
samples_index = std::min(samples_index, max - 1);
@@ -26,7 +28,7 @@ SModeResult CModeStretch::update(Vector2D pos) {
int first = samples_index;
// calculate speed and tilt
- Vector2D speed = (samples[current] - samples[first]) / 0.1;
+ Vector2D speed = (samples[current] - samples[first]) / window * 1000;
double mag = speed.size();
double angle = -std::atan(speed.x / speed.y) + PI;
diff --git a/src/mode/ModeTilt.cpp b/src/mode/ModeTilt.cpp
index 07cc2ba..671e763 100644
--- a/src/mode/ModeTilt.cpp
+++ b/src/mode/ModeTilt.cpp
@@ -11,11 +11,13 @@ EModeUpdate CModeTilt::strategy() {
SModeResult CModeTilt::update(Vector2D pos) {
static auto const* PFUNCTION = (Hyprlang::STRING const*) getConfig(CONFIG_TILT_FUNCTION);
static auto* const* PLIMIT = (Hyprlang::INT* const*) getConfig(CONFIG_TILT_LIMIT);
+ static auto* const* PWINDOW = (Hyprlang::INT* const*) getConfig(CONFIG_TILT_WINDOW);
auto function = g_pShapeRuleHandler->getStringOr(CONFIG_TILT_FUNCTION, *PFUNCTION);
auto limit = g_pShapeRuleHandler->getIntOr(CONFIG_TILT_LIMIT, **PLIMIT);
+ auto window = g_pShapeRuleHandler->getIntOr(CONFIG_TILT_WINDOW, **PWINDOW);
// create samples array
- int max = std::max(1, (int)(g_pHyprRenderer->m_mostHzMonitor->m_refreshRate / 10)); // 100ms worth of history, avoiding divide by 0
+ int max = std::max(1, (int)(g_pHyprRenderer->m_mostHzMonitor->m_refreshRate / 1000 * window)); // [window]ms worth of history, avoiding divide by 0
samples.resize(max, pos);
samples_index = std::min(samples_index, max - 1);
@@ -26,7 +28,7 @@ SModeResult CModeTilt::update(Vector2D pos) {
int first = samples_index;
// calculate speed and tilt
- double speed = (samples[current].x - samples[first].x) / 0.1;
+ double speed = (samples[current].x - samples[first].x) / window * 1000;
auto result = SModeResult();
result.rotation = activation(function, limit, speed) * (PI / 3); // 120° in both directions
diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp
index 76b5050..31fe174 100644
--- a/src/render/renderer.cpp
+++ b/src/render/renderer.cpp
@@ -1,4 +1,6 @@
+#include "render/Shader.hpp"
#include
+#include
#include // don't unprivate stuff in here
#define private public
@@ -79,28 +81,19 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, const C
}
glActiveTexture(GL_TEXTURE0);
- glBindTexture(tex->m_target, tex->m_texID);
+ tex->bind();
- if (g_pHyprOpenGL->m_renderData.useNearestNeighbor || nearest) {
- glTexParameteri(tex->m_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(tex->m_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- } else {
- glTexParameteri(tex->m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(tex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- }
+ auto scaling = g_pHyprOpenGL->m_renderData.useNearestNeighbor || nearest ? GL_NEAREST : GL_LINEAR;
+ glTexParameteri(tex->m_target, GL_TEXTURE_MAG_FILTER, scaling);
+ glTexParameteri(tex->m_target, GL_TEXTURE_MIN_FILTER, scaling);
- glUseProgram(shader->program);
+ g_pHyprOpenGL->useProgram(shader->program);
-#ifndef GLES2
- glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
-#else
- glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.transpose().getMatrix().data());
-#endif
-
- glUniform1i(shader->tex, 0);
- glUniform1f(shader->alpha, alpha);
- glUniform1i(shader->discardOpaque, 0);
- glUniform1i(shader->discardAlpha, 0);
+ shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix());
+ shader->setUniformInt(SHADER_TEX, 0);
+ shader->setUniformFloat(SHADER_ALPHA, alpha);
+ shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0);
+ shader->setUniformInt(SHADER_DISCARD_ALPHA, 0);
CBox transformedBox = newBox;
transformedBox.transform(wlTransformToHyprutils(invertTransform(g_pHyprOpenGL->m_renderData.pMonitor->m_transform)), g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_transformedSize.y);
@@ -108,17 +101,15 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, const C
const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y);
const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);
- glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
- glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
- glUniform1f(shader->radius, 0);
+ shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y);
+ shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y);
+ shader->setUniformFloat(SHADER_RADIUS, 0);
- glUniform1i(shader->applyTint, 0);
+ shader->setUniformInt(SHADER_APPLY_TINT, 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);
+ glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]);
+ glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts);
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};
@@ -137,8 +128,7 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, const C
}
}
- glDisableVertexAttribArray(shader->posAttrib);
- glDisableVertexAttribArray(shader->texAttrib);
-
- glBindTexture(tex->m_target, 0);
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ tex->unbind();
}