feat: Add reflect clock sample and enhance framebuffer handling

- Introduced a new Lua script `reflect_clock.lua` that displays a clock with a reflection effect.
- Updated `rdoc.cap` to use the new sample and modified the executable path for debugging.
- Enhanced `FrameBuffer` class to support dynamic depth-stencil formats, allowing for more flexible rendering options.
- Added a new enum `DepthStencilFormat` to manage different depth-stencil configurations.
- Updated OpenGL clear function to conditionally clear depth and stencil buffers based on their enabled state.
- Improved the `Effect` class to ensure proper texture updates for GStreamer textures.
- Adjusted the `GraphicsContext` to set the clip space to match NanoVG's coordinate system.
- Cleaned up texture loading functions by removing unnecessary comments and ensuring clarity.
This commit is contained in:
Diego Lopes
2026-03-21 17:02:40 -04:00
parent 6200b74e77
commit 1ad076b969
13 changed files with 2697 additions and 26 deletions

4
.vscode/launch.json vendored
View File

@@ -7,7 +7,7 @@
"request": "launch",
"program": "${workspaceFolder}/build/debug/live-wallpaper",
"args": [
"${workspaceFolder}/samples/test.lua"
"${workspaceFolder}/samples/reflect_clock.lua"
],
"cwd": "${workspaceFolder}",
"environment": [],
@@ -26,7 +26,7 @@
"request": "launch",
"program": "${workspaceFolder}/build/release/live-wallpaper",
"args": [
"${workspaceFolder}/samples/test.lua"
"${workspaceFolder}/samples/reflect_clock.lua"
],
"cwd": "${workspaceFolder}",
"environment": [],

File diff suppressed because it is too large Load Diff

821
external/glad/src/gl.c vendored

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,10 @@
"rdocCaptureSettings": 1,
"settings": {
"autoStart": false,
"commandLine": "/media/diego/Data/Projects/live-wallpaper/samples/test.lua",
"commandLine": "/media/diego/Data/Projects/live-wallpaper/samples/reflect_clock.lua",
"environment": [
],
"executable": "/home/diego/Projects/live-wallpaper/build/live-wallpaper",
"executable": "/home/diego/Projects/live-wallpaper/build/debug/live-wallpaper",
"inject": false,
"numQueuedFrames": 1,
"options": {

126
samples/reflect_clock.lua Normal file
View File

@@ -0,0 +1,126 @@
effect = nil
ctxFBO = nil
ctx = nil
clockTexture = nil
clockText = ""
timer = 0.0
function _create()
local display0 = Displays[1]
ctxFBO = FrameBuffer.new(math.floor(display0.z), math.floor(display0.w))
local att = ColorAttachment.new()
att.format = TextureFormatType.RGBA8
att.minFilter = TextureFilter.LinearMipmapLinear
att.magFilter = TextureFilter.Linear
att.wrapS = TextureWrap.ClampToEdge
att.wrapT = TextureWrap.ClampToEdge
ctxFBO:SetDepthStencilFormat(DepthStencilFormat.Depth24Stencil8)
ctxFBO:AddColorAttachment(att)
clockTexture = ctxFBO:GetColorTexture(0)
ctx = GraphicsContext.new()
ctx:CreateFont("font", Resolve("font.otf"))
local fxSrc = [[uniform sampler2D uTexture;
const float floorPosition = 0.55;
float waves(in vec2 uv) {
float speed = 4.0;
float topright = sin(uTime*(speed+1.0) - sin(length(uv-vec2(1.0,1.0)))*53.0);
float topleft = sin(uTime*(speed+1.0) - sin(length(uv-vec2(0.0,1.0)))*37.0);
float bottomright = sin(uTime*(speed) - sin(length(uv-vec2(1.0,0.0)))*61.0);
float bottomleft = sin(uTime*(speed+2.0) - sin(length(uv-vec2(0.0,0.0)))*47.0);
float horizontalWaves = sin(uTime * (speed + 2.0) - sin(uv.y) * 47.0);
float temp = horizontalWaves + bottomleft * 0.4 + bottomright * 0.2 + topleft * 0.6 + topright * 0.3;
float b=smoothstep(-2.5,5.0,temp);
return b*3.0;
}
void main() {
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) {
vec2 duv = GetDisplayUV(uTexture, i);
// Base
FragColor = texture(uTexture, duv);
// Reflection
if (duv.y > floorPosition) {
float reflectY = floorPosition - (duv.y - floorPosition);
vec2 reflectUV = vec2(duv.x, reflectY);
// deform the reflection based on the height function
float wv = waves(vec2(reflectUV.x * 2.0, reflectUV.y * 3.6 - 0.4));
float dx = dFdx(wv);
float dy = dFdy(wv);
reflectUV += vec2(dx, dy) * 0.25; // Adjust deformation
float lod = clamp(reflectUV.y * 3.0, 0.0, 3.0); // Adjust LOD based on distance
vec4 reflectColor = textureLod(uTexture, reflectUV, lod);
// Apply a simple fade based on distance from the floor
float fade = 1.0 - smoothstep(floorPosition, floorPosition + 0.1, duv.y);
FragColor += reflectColor * fade * 0.5; // Adjust reflection intensity
}
break;
}
}
}
]]
effect = Effect.new(fxSrc)
clockText = os.date("%H:%M")
end
function _update(dt)
timer = timer + dt
if timer >= 0.5 then
timer = 0.0
clockText = os.date("%H:%M")
end
end
function _render()
local display0 = Displays[1]
ctxFBO:Bind()
gl.Clear(0, 0, 0, 0)
ctx:BeginFrame(display0.z, display0.w, display0.z / display0.w)
ctx:FontSize(144)
ctx:FontFace("font")
ctx:FillColor(RGBAf(1, 1, 1, 0.75))
ctx:TextAlign(Align.CENTER | Align.MIDDLE)
ctx:Text(display0.z / 2, display0.w / 2, clockText)
ctx:EndFrame()
ctxFBO:Unbind()
clockTexture:Bind()
clockTexture:GenerateMipmaps()
gl.SetViewport(0, 0, math.floor(DesktopSize.x), math.floor(DesktopSize.y))
gl.Clear(0, 0, 0, 1.0)
effect:Use()
effect:SetTexture("uTexture", clockTexture, 0)
effect:Render()
end

View File

@@ -201,7 +201,6 @@ void Effect::SetTexture(const std::string &name, const Texture& texture, size_t
GStreamerTexture* gstTexture = dynamic_cast<GStreamerTexture*>(const_cast<Texture*>(&texture));
if (gstTexture) {
// For GStreamerTexture, we need to call Update() to upload the latest frame data
gstTexture->Update();
}

View File

@@ -22,7 +22,8 @@ FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept
: m_width(other.m_width), m_height(other.m_height),
m_attachments(std::move(other.m_attachments)),
m_colorTextures(std::move(other.m_colorTextures)),
m_depthRenderbuffer(other.m_depthRenderbuffer)
m_depthRenderbuffer(other.m_depthRenderbuffer),
m_depthStencilFormat(other.m_depthStencilFormat)
{
m_id = other.m_id;
other.m_id = 0;
@@ -42,6 +43,7 @@ FrameBuffer& FrameBuffer::operator=(FrameBuffer&& other) noexcept
m_attachments = std::move(other.m_attachments);
m_colorTextures = std::move(other.m_colorTextures);
m_depthRenderbuffer = other.m_depthRenderbuffer;
m_depthStencilFormat = other.m_depthStencilFormat;
other.m_id = 0;
other.m_depthRenderbuffer = 0;
@@ -86,6 +88,23 @@ void FrameBuffer::AddColorAttachment(const ColorAttachment& attachment)
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void FrameBuffer::SetDepthStencilFormat(DepthStencilFormat format)
{
if (m_depthStencilFormat == format) return;
m_depthStencilFormat = format;
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
DestroyDepthRenderbuffer();
CreateDepthRenderbuffer();
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
throw std::runtime_error("Framebuffer is not complete");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void FrameBuffer::Resize(uint32_t width, uint32_t height)
{
if (m_width == width && m_height == height) return;
@@ -141,10 +160,30 @@ std::unique_ptr<Texture2D> FrameBuffer::CreateColorTexture(const ColorAttachment
void FrameBuffer::CreateDepthRenderbuffer()
{
if (m_depthStencilFormat == DepthStencilFormat::None) return;
GLenum internalFormat;
GLenum attachmentPoint;
switch (m_depthStencilFormat) {
case DepthStencilFormat::Depth24:
internalFormat = GL_DEPTH_COMPONENT24;
attachmentPoint = GL_DEPTH_ATTACHMENT;
break;
case DepthStencilFormat::Depth24Stencil8:
internalFormat = GL_DEPTH24_STENCIL8;
attachmentPoint = GL_DEPTH_STENCIL_ATTACHMENT;
break;
case DepthStencilFormat::Stencil8:
internalFormat = GL_STENCIL_INDEX8;
attachmentPoint = GL_STENCIL_ATTACHMENT;
break;
default: return;
}
glGenRenderbuffers(1, &m_depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_width, m_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
glRenderbufferStorage(GL_RENDERBUFFER, internalFormat, m_width, m_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoint,
GL_RENDERBUFFER, m_depthRenderbuffer);
}

View File

@@ -7,6 +7,13 @@
#include "gpu.hpp"
#include "texture.h"
enum class DepthStencilFormat {
None,
Depth24,
Depth24Stencil8,
Stencil8,
};
struct ColorAttachment {
TextureFormatType format = TextureFormatType::RGBA8;
TextureFilter minFilter = TextureFilter::Linear;
@@ -29,6 +36,7 @@ public:
void Unbind() const override;
void AddColorAttachment(const ColorAttachment& attachment = {});
void SetDepthStencilFormat(DepthStencilFormat format);
void Resize(uint32_t width, uint32_t height);
const Texture2D& GetColorTexture(uint32_t index) const;
@@ -43,6 +51,7 @@ private:
std::vector<ColorAttachment> m_attachments;
std::vector<std::unique_ptr<Texture2D>> m_colorTextures;
GLuint m_depthRenderbuffer = 0;
DepthStencilFormat m_depthStencilFormat = DepthStencilFormat::Depth24Stencil8;
void Rebuild();
std::unique_ptr<Texture2D> CreateColorTexture(const ColorAttachment& attachment) const;

View File

@@ -338,6 +338,14 @@ static void RegisterTexture(sol::state& lua) {
}
static void RegisterFrameBuffer(sol::state& lua) {
// ── DepthStencilFormat ──────────────────────────────────────────────
lua.new_enum<DepthStencilFormat>("DepthStencilFormat", {
{"None", DepthStencilFormat::None},
{"Depth24", DepthStencilFormat::Depth24},
{"Depth24Stencil8", DepthStencilFormat::Depth24Stencil8},
{"Stencil8", DepthStencilFormat::Stencil8}
});
// ── ColorAttachment ─────────────────────────────────────────────────
auto ca = lua.new_usertype<ColorAttachment>("ColorAttachment",
sol::constructors<ColorAttachment()>(),
@@ -354,6 +362,7 @@ static void RegisterFrameBuffer(sol::state& lua) {
"Bind", &FrameBuffer::Bind,
"Unbind", &FrameBuffer::Unbind,
"AddColorAttachment", &FrameBuffer::AddColorAttachment,
"SetDepthStencilFormat", &FrameBuffer::SetDepthStencilFormat,
"Resize", &FrameBuffer::Resize,
"GetColorTexture", &FrameBuffer::GetColorTexture,
"GetColorAttachmentCount", &FrameBuffer::GetColorAttachmentCount,
@@ -391,6 +400,8 @@ static void RegisterOpenGL(sol::state& lua) {
gl["DisableBlending"] = &OpenGL::DisableBlending;
gl["SetBlendFunc"] = &OpenGL::SetBlendFunc;
gl["SetViewport"] = &OpenGL::SetViewport;
// Blend factors
gl["ZERO"] = GL_ZERO;
gl["ONE"] = GL_ONE;

View File

@@ -18,6 +18,7 @@ GraphicsContext::~GraphicsContext()
void GraphicsContext::BeginFrame(float width, float height, float devicePixelRatio)
{
nvgBeginFrame(ctx, width, height, devicePixelRatio);
glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); // Set clip space to match NanoVG's coordinate system
}
void GraphicsContext::CancelFrame() { nvgCancelFrame(ctx); }
@@ -25,10 +26,7 @@ void GraphicsContext::CancelFrame() { nvgCancelFrame(ctx); }
void GraphicsContext::EndFrame()
{
nvgEndFrame(ctx);
glBindVertexArray(0);
glDisable(GL_STENCIL_TEST);
glDisable(GL_CULL_FACE);
glUseProgram(0);
glClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); // Restore default OpenGL clip space
}
// Composite operations

View File

@@ -5,7 +5,11 @@ namespace OpenGL {
// ── Clear ───────────────────────────────────────────────────────────────
void Clear(float r, float g, float b, float a) {
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
GLenum flags = GL_COLOR_BUFFER_BIT;
if (glIsEnabled(GL_DEPTH_TEST)) flags |= GL_DEPTH_BUFFER_BIT;
if (glIsEnabled(GL_STENCIL_TEST)) flags |= GL_STENCIL_BUFFER_BIT;
glClear(flags);
}
void Clear(const Vector3& color) {
@@ -29,4 +33,9 @@ void SetBlendFunc(GLenum src, GLenum dst) {
glBlendFunc(src, dst);
}
void SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
} // namespace OpenGL

View File

@@ -15,4 +15,6 @@ void EnableBlending();
void DisableBlending();
void SetBlendFunc(GLenum src, GLenum dst);
void SetViewport(int x, int y, int width, int height);
} // namespace OpenGL

View File

@@ -227,7 +227,6 @@ void Texture1D::LoadData(TextureFormatType format, const void *data)
{
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
if (!m_hasInitialData) {
// Initialize texture storage with null data to allocate GPU memory
glTexImage1D(
m_target,
0,
@@ -238,7 +237,6 @@ void Texture1D::LoadData(TextureFormatType format, const void *data)
);
m_hasInitialData = true;
} else {
// Update existing texture data
glTexSubImage1D(
m_target,
0,
@@ -253,7 +251,6 @@ void Texture2D::LoadData(TextureFormatType format, const void *data)
{
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
if (!m_hasInitialData) {
// Initialize texture storage and optionally upload data
glTexImage2D(
m_target,
0,
@@ -264,7 +261,6 @@ void Texture2D::LoadData(TextureFormatType format, const void *data)
);
m_hasInitialData = true;
} else {
// Update existing texture data
glTexSubImage2D(
m_target,
0,
@@ -279,7 +275,6 @@ void Texture3D::LoadData(TextureFormatType format, const void *data)
{
const TextureFormat& fmt = textureFormatMap[static_cast<int>(format)];
if (!m_hasInitialData) {
// Initialize texture storage with null data to allocate GPU memory
glTexImage3D(
m_target,
0,
@@ -290,7 +285,6 @@ void Texture3D::LoadData(TextureFormatType format, const void *data)
);
m_hasInitialData = true;
} else {
// Update existing texture data
glTexSubImage3D(
m_target,
0,