diff --git a/.vscode/launch.json b/.vscode/launch.json index 5cca8aa..0454504 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,6 +19,25 @@ "ignoreFailures": true } ] + }, + { + "name": "Release live-wallpaper", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/release/live-wallpaper", + "args": [ + "${workspaceFolder}/samples/test.lua" + ], + "cwd": "${workspaceFolder}", + "environment": [], + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e78dfef..b2a70e3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -25,6 +25,28 @@ }, "problemMatcher": "$gcc", "dependsOn": "CMake: configure debug" + }, + { + "label": "CMake: configure release", + "type": "shell", + "command": "cmake", + "args": ["--preset", "release"], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "CMake: build release", + "type": "shell", + "command": "cmake", + "args": ["--build", "--preset", "release"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": "build", + "problemMatcher": "$gcc", + "dependsOn": "CMake: configure release" } ] } diff --git a/samples/auroras.lua b/samples/auroras.lua index a9301c1..6a28f6d 100644 --- a/samples/auroras.lua +++ b/samples/auroras.lua @@ -6,8 +6,6 @@ effect = nil function _create() local fxSrc = [[ -in vec2 vPosition; - #define time uTime mat2 mm2(in float a){float c = cos(a), s = sin(a);return mat2(c,s,-s,c);} diff --git a/samples/dry_rocky_gorge.lua b/samples/dry_rocky_gorge.lua index 9a07670..6945454 100644 --- a/samples/dry_rocky_gorge.lua +++ b/samples/dry_rocky_gorge.lua @@ -13,8 +13,6 @@ function _create() rockTexture:GenerateMipmaps() local fxSrc = [[ -in vec2 vPosition; - uniform sampler2D iChannel0; #define FAR 80. diff --git a/samples/simple.lua b/samples/simple.lua index ef04c39..865a018 100644 --- a/samples/simple.lua +++ b/samples/simple.lua @@ -1,8 +1,7 @@ effect = nil function _create() - local fxSrc = [[in vec2 vPosition; - + local fxSrc = [[ void main() { vec2 uv = vPosition * 0.5 + 0.5; FragColor = vec4(uv, 0.5 + 0.5 * sin(uTime), 1.0); diff --git a/samples/sunset_sea.lua b/samples/sunset_sea.lua index dc3b50c..5ea77fc 100644 --- a/samples/sunset_sea.lua +++ b/samples/sunset_sea.lua @@ -5,8 +5,6 @@ effect = nil function _create() local fxSrc = [[ -in vec2 vPosition; - const bool USE_MOUSE = false; const float PI = 3.14159265; diff --git a/samples/test.lua b/samples/test.lua index d154004..3faefcb 100644 --- a/samples/test.lua +++ b/samples/test.lua @@ -2,16 +2,22 @@ effect = nil video = nil function _create() - video = Texture.FromGStreamer("filesrc location=/media/diego/Data/Projects/live-wallpaper/samples/video.mp4") + video = Texture.FromGStreamer("filesrc location=" .. Resolve("sunset.mkv")) - local fxSrc = [[in vec2 vPosition; - - uniform sampler2D uTexture; + local fxSrc = [[uniform sampler2D uTexture; void main() { - vec2 uv = vPosition * 0.5 + 0.5; - uv.y = 1.0 - uv.y; - FragColor = texture(uTexture, uv); + vec2 uv = vUV; + + for (int i = 0; i < uNumDisplays; i++) { + vec4 displayNorm = uDisplayNorm[i]; + + if (vUV.x >= displayNorm.x && vUV.x <= displayNorm.x + displayNorm.z && + vUV.y >= displayNorm.y && vUV.y <= displayNorm.y + displayNorm.w) { + FragColor = texture(uTexture, GetDisplayUV(uTexture, i)); + break; + } + } } ]] effect = Effect.new(fxSrc) diff --git a/samples/video.mp4 b/samples/video.mp4 deleted file mode 100644 index 9ac25fb..0000000 Binary files a/samples/video.mp4 and /dev/null differ diff --git a/src/effect.cpp b/src/effect.cpp index 4b40f81..2000f2b 100644 --- a/src/effect.cpp +++ b/src/effect.cpp @@ -13,6 +13,106 @@ GLuint Effect::g_dummyVAO = 0; +static std::string shaderHeader = R"(#version 460 core + +out vec4 FragColor; + +in vec2 vUV; +in vec2 vPosition; + +uniform float uTime; +uniform vec4 uMouse; +uniform vec2 uDesktopSize; +uniform vec4 uDisplay[16]; // support up to 16 displays, more can be added if needed +uniform vec4 uDisplayNorm[16]; // pre-normalized display rects for convenience +uniform int uNumDisplays; + +#define MAX_DISPLAYS 16 + +// Background modes +#define BG_FIT_WIDTH 0 +#define BG_FIT_HEIGHT 1 +#define BG_COVER 2 +#define BG_STRETCH 3 +#define BG_CONTAIN 4 +#define BG_TILE 5 + +vec2 GetDisplayUV(sampler2D tex, int displayIndex, int mode) { + vec2 inUV = vec2(0.0); + + if (displayIndex < 0 || displayIndex >= uNumDisplays) { + // Entire desktop as fallback + inUV = vUV; + } else { + // Map global UV to display-local [0,1] + inUV = (vUV - uDisplayNorm[displayIndex].xy) / uDisplayNorm[displayIndex].zw; + } + + vec2 uv = inUV; + + if (mode == BG_STRETCH) { + // No adjustment needed + return uv; + } + + vec4 display = uDisplay[displayIndex]; + float displayRatio = display.z / display.w; + + vec2 texSize = vec2(textureSize(tex, 0)); + float textureRatio = texSize.x / texSize.y; + + if (mode == BG_COVER) { + if (displayRatio > textureRatio) { + // Display wider: crop top/bottom + float scale = textureRatio / displayRatio; + uv.y = uv.y * scale + (1.0 - scale) * 0.5; + } else { + // Display taller: crop left/right + float scale = displayRatio / textureRatio; + uv.x = uv.x * scale + (1.0 - scale) * 0.5; + } + } else if (mode == BG_CONTAIN) { + if (displayRatio > textureRatio) { + // Display wider: pillarbox left/right + float scale = displayRatio / textureRatio; + uv.x = uv.x * scale + (1.0 - scale) * 0.5; + } else { + // Display taller: letterbox top/bottom + float scale = textureRatio / displayRatio; + uv.y = uv.y * scale + (1.0 - scale) * 0.5; + } + } else if (mode == BG_FIT_WIDTH) { + // Scale to match display width, adjust Y + float scale = textureRatio / displayRatio; + uv.y = uv.y * scale + (1.0 - scale) * 0.5; + } else if (mode == BG_FIT_HEIGHT) { + // Scale to match display height, adjust X + float scale = displayRatio / textureRatio; + uv.x = uv.x * scale + (1.0 - scale) * 0.5; + } else if (mode == BG_TILE) { + // Tile at native texture resolution + uv = uv * display.zw / texSize; + } + + return uv; +} + +// Default mode = BG_COVER +vec2 GetDisplayUV(sampler2D tex, int displayIndex) { + return GetDisplayUV(tex, displayIndex, BG_COVER); +} + +vec2 GetFullUV(sampler2D tex) { + return GetDisplayUV(tex, -1, BG_COVER); +} + +vec2 GetFullUV(sampler2D tex, int mode) { + return GetDisplayUV(tex, -1, mode); +} + +#line 1 +)"; + static std::vector SplitLines(const std::string& str) { std::vector lines; size_t start = 0, end = 0; @@ -32,27 +132,13 @@ Effect::Effect(const std::string& fragmentShaderSource) #include "fx_vertex.glsl.h" ; - std::string code = - "#version 460 core\n\n" - "out vec4 FragColor;\n\n" - "uniform float uTime;\n" - "uniform vec4 uMouse;\n" - "uniform vec2 uDesktopSize;\n"; - - for (size_t i = 0; i < g_Displays.size(); i++) { - code += "uniform vec4 uDisplay" + std::to_string(i) + ";\n"; - } - - // reset line number - code += "#line 1\n"; - GLuint vertexShader = CreateShader(vertexShaderSource, GL_VERTEX_SHADER); if (!vertexShader) { throw std::runtime_error("Failed to compile vertex shader"); return; } - GLuint fragmentShader = CreateShader(code + fragmentShaderSource, GL_FRAGMENT_SHADER); + GLuint fragmentShader = CreateShader(shaderHeader + fragmentShaderSource, GL_FRAGMENT_SHADER); if (!fragmentShader) { glDeleteShader(vertexShader); throw std::runtime_error("Failed to compile fragment shader"); @@ -122,6 +208,11 @@ void Effect::SetTexture(const std::string &name, const Texture& texture, size_t glUniform1i(GetUniformLocation(name), static_cast(textureUnit)); } +void Effect::SetInt(const std::string &name, int value) +{ + glUniform1i(GetUniformLocation(name), value); +} + void Effect::SetFloat(const std::string &name, float value) { glUniform1f(GetUniformLocation(name), value); @@ -156,8 +247,10 @@ void Effect::Render() SetVector4("uMouse", g_Mouse); SetVector2("uDesktopSize", g_DesktopSize); for (size_t i = 0; i < g_Displays.size(); i++) { - SetVector4("uDisplay" + std::to_string(i), g_Displays[i]); + SetVector4("uDisplay[" + std::to_string(i) + "]", g_Displays[i]); + SetVector4("uDisplayNorm[" + std::to_string(i) + "]", g_Displays[i] / Vector4(g_DesktopSize, g_DesktopSize)); } + SetInt("uNumDisplays", static_cast(g_Displays.size())); glBindVertexArray(g_dummyVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); diff --git a/src/effect.h b/src/effect.h index b6c25d7..39694d1 100644 --- a/src/effect.h +++ b/src/effect.h @@ -19,6 +19,7 @@ public: GLuint GetUniformLocation(const std::string& name); void SetTexture(const std::string& name, const Texture& texture, size_t textureUnit); + void SetInt(const std::string& name, int value); void SetFloat(const std::string& name, float value); void SetVector2(const std::string& name, const Vector2& value); void SetVector3(const std::string& name, const Vector3& value); diff --git a/src/fx_vertex.glsl.h b/src/fx_vertex.glsl.h index 251b521..751edb3 100644 --- a/src/fx_vertex.glsl.h +++ b/src/fx_vertex.glsl.h @@ -1,5 +1,6 @@ R"(#version 460 core out vec2 vPosition; +out vec2 vUV; vec2 coords[] = vec2[]( vec2(-1.0, -1.0), @@ -10,5 +11,8 @@ vec2 coords[] = vec2[]( void main() { vPosition = coords[gl_VertexID]; + vUV = vPosition * 0.5 + 0.5; + vUV.y = 1.0 - vUV.y; // flip Y for texture coordinates + gl_Position = vec4(vPosition, 0.0, 1.0); })" \ No newline at end of file diff --git a/src/lua.cpp b/src/lua.cpp index 7a7b60e..4e24290 100644 --- a/src/lua.cpp +++ b/src/lua.cpp @@ -328,13 +328,12 @@ static void RegisterTexture(sol::state& lua) { tex["CreateTexture3D"] = static_cast(*)(uint32_t, uint32_t, uint32_t, TextureFormatType)>(&Texture::CreateTexture); tex["CreateCubeMap"] = &Texture::CreateCubeMap; tex["FromGStreamer"] = &Texture::FromGStreamer; - tex["FromFile"] = [](const std::string& filepath) -> std::unique_ptr { - std::filesystem::path p(filepath); - if (p.is_relative() && !g_ScriptDir.empty()) { - p = std::filesystem::path(g_ScriptDir) / p; - } - return Texture::FromFile(p.string()); - }; + tex["FromFile"] = &Texture::FromFile; + + // ── Useful functions ─────────────────────────────────────────────── + lua.set_function("Resolve", [](const std::string& path) { + return std::filesystem::absolute(std::filesystem::path(g_ScriptDir) / path).string(); + }); } static void RegisterFrameBuffer(sol::state& lua) { diff --git a/src/main.cpp b/src/main.cpp index bf76239..f760f2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -171,24 +171,17 @@ int main(int argc, char* argv[]) if (event.type == SDL_EVENT_QUIT) { running = false; } + } - // Mouse position and button state - switch (event.type) { - case SDL_EVENT_MOUSE_MOTION: - g_Mouse.x = static_cast(event.motion.x); - g_Mouse.y = static_cast(event.motion.y); - break; - case SDL_EVENT_MOUSE_BUTTON_DOWN: - case SDL_EVENT_MOUSE_BUTTON_UP: - if (event.button.button == SDL_BUTTON_LEFT) { - g_Mouse.z = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? 1.0f : 0.0f; - } else if (event.button.button == SDL_BUTTON_RIGHT) { - g_Mouse.w = (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? 1.0f : 0.0f; - } - break; - default: - break; - } + // Poll global mouse position (the window is click-through so it + // receives no input events — query the pointer directly instead). + { + float gx, gy; + SDL_MouseButtonFlags buttons = SDL_GetGlobalMouseState(&gx, &gy); + g_Mouse.x = gx; + g_Mouse.y = g_DesktopSize.y - gy; + g_Mouse.z = (buttons & SDL_BUTTON_LMASK) ? 1.0f : 0.0f; + g_Mouse.w = (buttons & SDL_BUTTON_RMASK) ? 1.0f : 0.0f; } bool canRender = false; diff --git a/src/tmath.hpp b/src/tmath.hpp index 72d28ee..4830877 100644 --- a/src/tmath.hpp +++ b/src/tmath.hpp @@ -271,6 +271,7 @@ public: constexpr Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} constexpr Vector4(const Vector2& v, float z = 0.0f, float w = 0.0f) : x(v.x), y(v.y), z(z), w(w) {} constexpr Vector4(const Vector3& v, float w = 0.0f) : x(v.x), y(v.y), z(v.z), w(w) {} + constexpr Vector4(const Vector2& v1, const Vector2& v2) : x(v1.x), y(v1.y), z(v2.x), w(v2.y) {} [[nodiscard]] constexpr Vector2 toVector2() const { return Vector2(x, y); } [[nodiscard]] constexpr Vector3 toVector3() const { return Vector3(x, y, z); }