Backdrop Path + Cybersteel (#23)

Co-authored-by: Zachary Levy <zachary@sunforge.is>
Reviewed-on: #23
This commit was merged in pull request #23.
This commit is contained in:
2026-05-01 05:43:10 +00:00
parent e36229a3ef
commit 5317b8f142
66 changed files with 5806 additions and 2427 deletions
@@ -0,0 +1,67 @@
#version 450 core
// Backdrop downsample fragment shader.
// Reads source_texture (full-resolution snapshot of pre-bracket framebuffer contents) and
// writes a downsampled copy at factor 1, 2, or 4. The output is the working texture (sized
// at full swapchain resolution); larger factors only fill a sub-rect of it via the CPU-set
// viewport. See backdrop.odin for the factor selection table (Flutter-style).
//
// Shader paths by factor:
//
// factor=1: identity copy. One bilinear tap aligned to the source pixel center. Useful
// when sigma is small enough that any downsample round-trip would visibly soften
// the output (Flutter does this for sigma_phys ≤ 4).
//
// factor=2: each output covers a 2×2 source block. Single bilinear tap at the shared
// corner reads all 4 source pixels with 0.25 weight.
//
// factor=4: each output covers a 4×4 source block. We use 4 bilinear taps, each at the
// shared corner of a 2×2 sub-block. Each tap reads 4 source pixels uniformly;
// combined, the 4 taps sample 16 source pixels arranged uniformly across the
// block (full coverage at factor=4). The factor>=4 path is structured so the
// same shader code would extend to factor=8 (16 pixels of 64) or factor=16 (16
// of 256) if the CPU-side cap is ever raised, though the current cap is 4.
//
// The viewport+scissor are set by the CPU to limit output to the layer's work region in
// working-texture coords (work_region_phys / factor), clamped to the texture bounds.
layout(set = 3, binding = 0) uniform Uniforms {
vec2 inv_source_size; // 1.0 / source_texture pixel dimensions
uint downsample_factor; // 1, 2, 4, 8, or 16
uint _pad0;
};
layout(set = 2, binding = 0) uniform sampler2D source_tex;
layout(location = 0) out vec4 out_color;
void main() {
// Output pixel index (i): gl_FragCoord.xy - 0.5. Source-pixel block top-left for this
// output: i * factor. Center of the block: i*factor + factor/2 = gl_FragCoord.xy * factor.
vec2 src_block_center = gl_FragCoord.xy * float(downsample_factor);
if (downsample_factor == 1u) {
// Identity copy. UV at src_block_center hits the source pixel center directly.
vec2 uv = src_block_center * inv_source_size;
out_color = texture(source_tex, uv);
} else if (downsample_factor == 2u) {
// Single tap at the shared corner of the 2×2 source block; one bilinear sample reads
// all 4 source pixels with equal 0.25 weights — uniform 2×2 box filter for free.
vec2 uv = src_block_center * inv_source_size;
out_color = texture(source_tex, uv);
} else {
// Four taps at offsets ±(factor/4) from the block center. Each tap lands on a corner
// shared by 4 source pixels of a (factor/2)×(factor/2) sub-block (equivalent at the
// bilinear level), giving a 4-tap = 16-source-pixel uniform sample of the block.
float off = float(downsample_factor) * 0.25;
vec2 uv_tl = (src_block_center + vec2(-off, -off)) * inv_source_size;
vec2 uv_tr = (src_block_center + vec2(off, -off)) * inv_source_size;
vec2 uv_bl = (src_block_center + vec2(-off, off)) * inv_source_size;
vec2 uv_br = (src_block_center + vec2(off, off)) * inv_source_size;
vec4 c = texture(source_tex, uv_tl)
+ texture(source_tex, uv_tr)
+ texture(source_tex, uv_bl)
+ texture(source_tex, uv_br);
out_color = c * 0.25;
}
}