Skip to content

Commit e8edd92

Browse files
committed
renderer/image: add interruptible transition rendering with custom shaders
Adds an animated wallpaper transition path to CImageElement: a two-texture blend (start -> end) driven by fadeLayersIn/Out durations, with an optional custom GLSL fragment shader supplied by path. - CImageElement::transitionTo captures the in-flight blend to an FBO when a transition is interrupted mid-way, using that as the new start texture so a fresh transition can begin to the new target (no flicker, no black flash). - IRenderer gains renderTransition / captureTransitionState / ensureTransitionShader. Custom shaders are stat()-checked once at transition start and recompiled only on mtime change; the per-frame render path is a single cache lookup + draw, with no filesystem or compile work. - CFramebuffer sets m_tex->m_size after glTexImage2D so captured textures report the right size to the shader's tex1Size/tex2Size uniforms. - A captured texture keeps its owning CFramebuffer alive via m_tempFramebuffer. - All fit modes (stretch/cover/contain/tile) are honoured; the host pushes the config duration to u_duration each frame (GLES cannot initialize uniforms).
1 parent 795d06e commit e8edd92

10 files changed

Lines changed: 578 additions & 0 deletions

File tree

include/hyprtoolkit/element/Image.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ namespace Hyprtoolkit {
4848
Hyprutils::Memory::CSharedPointer<CImageBuilder> rebuild();
4949
virtual Hyprutils::Math::Vector2D size();
5050

51+
void transitionTo(const std::string& path,
52+
eImageFitMode fitMode,
53+
float durationOut,
54+
float durationIn,
55+
const std::string& shaderPath = "");
56+
57+
bool isTransitioning() const;
58+
59+
float getTransitionProgressOut() const;
60+
float getTransitionProgressIn() const;
61+
5162
private:
5263
CImageElement(const SImageData& data);
5364
static Hyprutils::Memory::CSharedPointer<CImageElement> create(const SImageData& data);
@@ -64,6 +75,8 @@ namespace Hyprtoolkit {
6475

6576
void renderTex();
6677

78+
void renderTransition();
79+
6780
Hyprutils::Memory::CUniquePointer<SImageImpl> m_impl;
6881

6982
friend class CImageBuilder;

src/element/image/Image.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "../../system/Icons.hpp"
88
#include "../../resource/assetCache/AssetCache.hpp"
99
#include "../../renderer/RendererTexture.hpp"
10+
#include <random>
1011

1112
#include "../Element.hpp"
1213

@@ -27,6 +28,11 @@ void CImageElement::paint() {
2728
if (m_impl->failed)
2829
return;
2930

31+
if (m_impl->transition.active) {
32+
renderTransition();
33+
return;
34+
}
35+
3036
auto assetToUse = m_impl->cacheEntry;
3137

3238
if (!assetToUse || !assetToUse->tex())
@@ -244,3 +250,130 @@ Vector2D SImageImpl::preferredSvgSize() {
244250

245251
return Vector2D{max * lastScale, max * lastScale}.round();
246252
}
253+
254+
void CImageElement::transitionTo(const std::string& path,
255+
eImageFitMode fitMode,
256+
float durationOut,
257+
float durationIn,
258+
const std::string& shaderPath) {
259+
if (m_impl->transition.active) {
260+
// Capture the in-flight blend to an FBO and use it as the new start texture.
261+
if (m_impl->transition.startTexture && m_impl->transition.endTexture) {
262+
IRenderer::STransitionRenderData captureData = {
263+
.box = impl->position,
264+
.startTexture = m_impl->transition.startTexture,
265+
.endTexture = m_impl->transition.endTexture,
266+
.progress = m_impl->transition.progress,
267+
.a = 1.F,
268+
.fitMode = m_impl->data.fitMode,
269+
.shaderPath = m_impl->transition.shaderPath,
270+
.randomPixel = m_impl->transition.randomPixel,
271+
.duration = m_impl->transition.duration,
272+
};
273+
274+
auto capturedTex = g_renderer->captureTransitionState(captureData);
275+
if (capturedTex)
276+
m_impl->transition.startTexture = capturedTex;
277+
}
278+
} else if (m_impl->cacheEntry && m_impl->cacheEntry->tex()) {
279+
m_impl->transition.startTexture = m_impl->cacheEntry->tex();
280+
}
281+
282+
m_impl->transition.active = true;
283+
m_impl->transition.progress = 0.0f;
284+
m_impl->transition.shaderPath = shaderPath;
285+
m_impl->transition.startTime = std::chrono::steady_clock::now();
286+
287+
m_impl->transition.randomPixel = Hyprutils::Math::Vector2D(
288+
static_cast<float>(rand() % 1000) / 1000.0f,
289+
static_cast<float>(rand() % 1000) / 1000.0f
290+
);
291+
292+
// GLES cannot initialize uniform values, so u_duration is pushed to the shader
293+
// each frame; a shader may further remap progress via a local const.
294+
m_impl->transition.duration = std::max(durationOut, durationIn);
295+
296+
// Stat the shader file once here so renderTransition can reuse the cached
297+
// program per-frame without touching the filesystem.
298+
g_renderer->ensureTransitionShader(shaderPath);
299+
300+
// Populated once the new asset finishes loading.
301+
m_impl->transition.endTexture.reset();
302+
303+
m_impl->data.path = path;
304+
m_impl->data.fitMode = fitMode;
305+
renderTex();
306+
307+
if (impl->window)
308+
impl->window->scheduleReposition(impl->self.lock());
309+
}
310+
311+
bool CImageElement::isTransitioning() const {
312+
return m_impl->transition.active;
313+
}
314+
315+
float CImageElement::getTransitionProgressOut() const {
316+
return m_impl->transition.progress;
317+
}
318+
319+
float CImageElement::getTransitionProgressIn() const {
320+
return m_impl->transition.progress;
321+
}
322+
323+
void CImageElement::renderTransition() {
324+
auto& trans = m_impl->transition;
325+
326+
auto now = std::chrono::steady_clock::now();
327+
float elapsed = std::chrono::duration<float>(now - trans.startTime).count();
328+
trans.progress = std::clamp(elapsed / trans.duration, 0.0f, 1.0f);
329+
330+
if (trans.progress >= 1.0f) {
331+
trans.active = false;
332+
trans.startTexture.reset();
333+
trans.endTexture.reset();
334+
335+
if (m_impl->cacheEntry && m_impl->cacheEntry->tex()) {
336+
g_renderer->renderTexture({
337+
.box = impl->position,
338+
.texture = m_impl->cacheEntry->tex(),
339+
.a = 1.F,
340+
.rounding = 0,
341+
});
342+
}
343+
return;
344+
}
345+
346+
// The end texture is resolved once the new asset finishes loading.
347+
SP<IRendererTexture> endTex;
348+
if (m_impl->cacheEntry && m_impl->cacheEntry->tex()) {
349+
endTex = m_impl->cacheEntry->tex();
350+
trans.endTexture = endTex;
351+
} else
352+
endTex = trans.endTexture;
353+
354+
if (trans.startTexture && endTex) {
355+
g_renderer->renderTransition({
356+
.box = impl->position,
357+
.startTexture = trans.startTexture,
358+
.endTexture = endTex,
359+
.progress = trans.progress,
360+
.a = 1.F,
361+
.rounding = 0,
362+
.fitMode = m_impl->data.fitMode,
363+
.shaderPath = trans.shaderPath,
364+
.randomPixel = trans.randomPixel,
365+
.duration = trans.duration,
366+
});
367+
} else if (trans.startTexture) {
368+
// End texture not loaded yet, keep showing the start.
369+
g_renderer->renderTexture({
370+
.box = impl->position,
371+
.texture = trans.startTexture,
372+
.a = 1.F,
373+
.rounding = 0,
374+
});
375+
}
376+
377+
if (impl->window)
378+
impl->window->scheduleReposition(impl->self.lock());
379+
}

src/element/image/Image.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,17 @@ namespace Hyprtoolkit {
4040
struct {
4141
Hyprutils::Signal::CHyprSignalListener cacheEntryDone;
4242
} listeners;
43+
44+
struct {
45+
bool active = false;
46+
float progress = 0.0f;
47+
float duration = 1.0f;
48+
std::string shaderPath;
49+
std::chrono::steady_clock::time_point startTime;
50+
Hyprutils::Math::Vector2D randomPixel;
51+
52+
Hyprutils::Memory::CSharedPointer<IRendererTexture> startTexture;
53+
Hyprutils::Memory::CSharedPointer<IRendererTexture> endTexture;
54+
} transition;
4355
};
4456
}

src/renderer/Renderer.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace Hyprtoolkit {
1818
class IToolkitWindow;
1919
class IRendererTexture;
2020
class CSyncTimeline;
21+
class CFramebuffer;
2122

2223
class IRenderer {
2324
public:
@@ -62,14 +63,31 @@ namespace Hyprtoolkit {
6263
int thick = 2;
6364
};
6465

66+
struct STransitionRenderData {
67+
CBox box;
68+
SP<IRendererTexture> startTexture;
69+
SP<IRendererTexture> endTexture;
70+
float progress = 0.0f;
71+
float a = 1.F;
72+
int rounding = 0;
73+
eImageFitMode fitMode = IMAGE_FIT_MODE_COVER;
74+
std::string shaderPath;
75+
Vector2D randomPixel;
76+
float duration = 0.0f; // passed to shader via u_duration
77+
};
78+
6579
virtual void beginRendering(SP<IToolkitWindow> window, SP<Aquamarine::IBuffer> buf) = 0;
6680
virtual void render(bool ignoreSync = false) = 0;
6781
virtual void endRendering() = 0;
6882
virtual void renderRectangle(const SRectangleRenderData& data) = 0;
6983
virtual SP<IRendererTexture> uploadTexture(const STextureData& data) = 0;
7084
virtual void renderTexture(const STextureRenderData& data) = 0;
85+
virtual void renderTransition(const STransitionRenderData& data) = 0;
7186
virtual void renderBorder(const SBorderRenderData& data) = 0;
7287
virtual void renderPolygon(const SPolygonRenderData& data) = 0;
88+
virtual SP<IRendererTexture> captureTransitionState(const STransitionRenderData& data) = 0;
89+
// Stat the shader file once and recompile only when its mtime changed.
90+
virtual void ensureTransitionShader(const std::string& path) = 0;
7391
virtual void renderLine(const SLineRenderData& data) = 0;
7492
virtual void signalRenderPoint(SP<CSyncTimeline> timeline) = 0;
7593

src/renderer/gl/Framebuffer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) {
4747
if (firstAlloc || m_size != Vector2D(w, h)) {
4848
m_tex->bind();
4949
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr);
50+
m_tex->m_size = Vector2D(w, h);
5051
glBindFramebuffer(GL_FRAMEBUFFER, m_fb);
5152
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0);
5253

src/renderer/gl/GLTexture.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "../../helpers/Memory.hpp"
1212

1313
namespace Hyprtoolkit {
14+
class CFramebuffer;
15+
1416
enum eGLTextureType : uint8_t {
1517
TEXTURE_INVALID, // Invalid
1618
TEXTURE_RGBA, // 4 channels
@@ -46,6 +48,9 @@ namespace Hyprtoolkit {
4648

4749
ASP<Hyprgraphics::IAsyncResource> m_resource;
4850

51+
// Keeps the owning FBO alive when this texture was produced by captureTransitionState.
52+
SP<CFramebuffer> m_tempFramebuffer;
53+
4954
void upload();
5055
void allocate();
5156
void bind();

0 commit comments

Comments
 (0)