From 7300a70bbffd3baa3485b6cbd43a5d962b9b20a3 Mon Sep 17 00:00:00 2001 From: CPritch Date: Sat, 10 Oct 2026 20:05:25 +0100 Subject: [PATCH] fix: don't pass textures to functions in the shader Mali's Vulkan driver segfaults compiling a texture passed to a function, so the helpers that read depth and ranks now pick the current or next wallpaper's texture themselves. --- CHANGELOG.md | 4 +++ crates/render/src/shader.wgsl | 52 +++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1677d76..b99aeaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - shiftpaperd no longer needs a GPU with 16-bit normalised textures, as depth maps now go to the GPU as 16-bit floats. - The renderer is now its own crate, `shiftpaper-render`, with no Wayland code, so other front ends can use it. +### Fixed + +- Mali's Vulkan driver no longer crashes compiling the shader. It couldn't cope with textures passed to functions. + ## 0.4.3 - 2026-10-09 ### Changed diff --git a/crates/render/src/shader.wgsl b/crates/render/src/shader.wgsl index d5dd1af..a7a9f45 100644 --- a/crates/render/src/shader.wgsl +++ b/crates/render/src/shader.wgsl @@ -106,19 +106,35 @@ fn crop(uv: vec2, scale: vec2) -> vec2 { return 0.5 + (uv - 0.5) * scale * (1.0 - 2.0 * MARGIN); } -fn load_depth(tex: texture_2d, uv: vec2) -> f32 { - let size = textureDimensions(tex); - return textureLoad(tex, vec2(uv * vec2(size)), 0).r; +// The helpers below read the current wallpaper's textures, or the next +// one's if `next`, rather than taking a texture as an argument. Mali's +// Vulkan driver crashes compiling a texture passed to a function. + +fn load_depth(next: bool, uv: vec2) -> f32 { + if next { + let size = textureDimensions(next_depth_tex); + return textureLoad(next_depth_tex, vec2(uv * vec2(size)), 0).r; + } + let size = textureDimensions(depth_tex); + return textureLoad(depth_tex, vec2(uv * vec2(size)), 0).r; +} + +// Depth and height ranks at index `i`. +fn load_ranks(next: bool, i: u32) -> vec2 { + if next { + return textureLoad(next_ranks, i, 0).rg; + } + return textureLoad(ranks, i, 0).rg; } // The fraction of a wallpaper's pixels farther away than `depth`, // interpolated from its 257 ranks. -fn depth_rank(table: texture_1d, depth: f32) -> f32 { +fn depth_rank(next: bool, depth: f32) -> f32 { let x = clamp(depth, 0.0, 1.0) * 256.0; let i = min(u32(x), 255u); - let farther = textureLoad(table, i, 0).r; - let next = textureLoad(table, i + 1u, 0).r; - return mix(farther, next, x - f32(i)); + let farther = load_ranks(next, i).r; + let further = load_ranks(next, i + 1u).r; + return mix(farther, further, x - f32(i)); } // How far a pixel has switched to the next wallpaper in a sweep. Think of @@ -135,8 +151,8 @@ fn depth_rank(table: texture_1d, depth: f32) -> f32 { // Depths are compared by rank, so the sweep spends similar time on each // part of the picture. fn sweep_weight(depth: f32, next_depth: f32, t: f32) -> f32 { - let rank = depth_rank(ranks, depth); - let next_rank = depth_rank(next_ranks, next_depth); + let rank = depth_rank(false, depth); + let next_rank = depth_rank(true, next_depth); // How far the plane has to travel before this pixel switches. let distance = select(max(rank, next_rank), 1.0 - next_rank, u.style == SWEEP_IN); return swept(distance, t); @@ -179,12 +195,12 @@ fn tide_height(uv: vec2, depth: f32, ground: vec4) -> f32 { // The fraction of a wallpaper's pixels lower in the scene than `height`, // from the second column of its ranks. -fn height_rank(table: texture_1d, height: f32) -> f32 { +fn height_rank(next: bool, height: f32) -> f32 { let x = clamp((height + 1.0) * 0.5, 0.0, 1.0) * 256.0; let i = min(u32(x), 255u); - let lower = textureLoad(table, i, 0).g; - let next = textureLoad(table, i + 1u, 0).g; - return mix(lower, next, x - f32(i)); + let lower = load_ranks(next, i).g; + let higher = load_ranks(next, i + 1u).g; + return mix(lower, higher, x - f32(i)); } // How far under the tide's water a pixel is, in height rank, so negative @@ -196,9 +212,9 @@ fn height_rank(table: texture_1d, height: f32) -> f32 { fn under_water(uv: vec2, depth: f32, next_uv: vec2, next_depth: f32, t: f32) -> f32 { let level = t * (1.0 + 2.0 * BAND) - BAND; if u.style == TIDE_IN { - return level - height_rank(ranks, tide_height(uv, depth, u.ground)); + return level - height_rank(false, tide_height(uv, depth, u.ground)); } - return (1.0 - level) - height_rank(next_ranks, tide_height(next_uv, next_depth, u.next_ground)); + return (1.0 - level) - height_rank(true, tide_height(next_uv, next_depth, u.next_ground)); } // Small waves across the tide's surface, which move as it does. @@ -276,7 +292,7 @@ fn portal_arrival(screen_uv: vec2, depth: f32) -> f32 { if start + pace(across + bump) * growth >= first { continue; } - let center = scene_point(point.xy, load_depth(depth_tex, crop(point.xy, u.uv_scale))); + let center = scene_point(point.xy, load_depth(false, crop(point.xy, u.uv_scale))); first = min(first, start + pace(distance(here, center) + bump) * growth); } return timing(first); @@ -363,7 +379,7 @@ fn mix_oklab(a: vec4, b: vec4, t: f32) -> vec4 { fn fs_main(in: VertexOutput) -> @location(0) vec4 { let uv = crop(in.uv, u.uv_scale); // Depth comes from the same image position as the colour. - var depth = load_depth(depth_tex, uv); + var depth = load_depth(false, uv); if u.progress <= 0.0 { // Cursor right (+x) shifts near pixels left. Scaled by uv_scale so a // cropped axis moves the same amount on screen as an uncropped one. @@ -372,7 +388,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } let next_uv = crop(in.uv, u.next_uv_scale); - let next_depth = load_depth(next_depth_tex, next_uv); + let next_depth = load_depth(true, next_uv); let blend = transition_blend(in.uv, depth, next_depth, u.progress); // Blending depth as well as colour morphs the parallax geometry, and // both images shift by it so they move together.