#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; } }