Skip to main content

UNPREMULTIPLY

Constant UNPREMULTIPLY 

Source
pub const UNPREMULTIPLY: &str = "// Frust\'s own program, not a port of a `vello_sparse_shaders` module \u{2014} which\n// is why it carries no upstream attribution header and pulls in no helper\n// prelude (`gpu::shader_src`): it generates its own geometry and reads exactly\n// one texture.\n//\n// Converts a PREMULTIPLIED colour buffer into a STRAIGHT-alpha one, for a\n// swapchain that stores straight alpha (iOS\'s `PostMultiplied` composite alpha\n// mode). Every engine pipeline blends and writes premultiplied, so the frame\n// arrives here as `(C\u{b7}a, a)` and such a compositor would read it as `(C, a)` \u{2014}\n// every partial-alpha pixel too dark. One full-screen triangle over the whole\n// destination writes `(rgb / max(a, ALPHA_FLOOR), a)` instead.\n//\n// A render pass, deliberately: the destination is a swapchain image that is\n// `RENDER_ATTACHMENT`-only, and a compute/storage-texture conversion is\n// refused by the engine\'s downlevel design rules (E1/E2) \u{2014} the whole tier\n// exists to run where compute does not.\n\n// The divisor\'s floor. `a` at or near zero carries no colour information at\n// all (a premultiplied `(0,0,0,0)` is the only value it can hold), so the\n// quotient is arbitrary \u{2014} but an unguarded `0 / 0` is a NaN, and a NaN written\n// to the swapchain is undefined content rather than a transparent pixel.\n// Flooring the divisor keeps the result finite and, since the numerator is\n// zero there, exactly transparent black. Small enough that no representable\n// 8-bit alpha (the smallest is 1/255 \u{2248} 3.9e-3) is ever clamped by it, so the\n// guard never perturbs a pixel that carries real colour.\n//\n// Must stay in step with `gpu::present`\'s `ALPHA_FLOOR`, which its own test\n// pins against this file.\nconst ALPHA_FLOOR: f32 = 1e-4;\n\n@group(0) @binding(0)\nvar source_texture: texture_2d<f32>;\n\n// The full-screen triangle: three vertices at (-1,-1), (-1,3), (3,-1), whose\n// interior covers the whole clip volume with one primitive. No vertex buffer\n// and no instance data \u{2014} the pipeline declares no vertex layout at all.\n@vertex\nfn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> {\n    let index = i32(vertex_index);\n    let x = f32(index / 2) * 4.0 - 1.0;\n    let y = f32(index & 1) * 4.0 - 1.0;\n    return vec4<f32>(x, y, 0.0, 1.0);\n}\n\n// Source and destination are the same extent in the same framebuffer space, so\n// the fragment\'s own position is the source texel to read: a `textureLoad` at\n// integer coordinates, never a sampled read, so no filtering or coordinate\n// convention can shift the image by half a texel.\n//\n// The quotient is written to a `unorm` target, which clamps it \u{2014} a premultiplied\n// pixel whose rounded `rgb` sits a hair above its own `a` therefore lands at 1.0\n// rather than wrapping.\n@fragment\nfn fs_main(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {\n    let premultiplied = textureLoad(source_texture, vec2<i32>(position.xy), 0);\n    let alpha = max(premultiplied.a, ALPHA_FLOOR);\n    return vec4<f32>(premultiplied.rgb / alpha, premultiplied.a);\n}\n";
Expand description

The conversion program, loaded from the shader directory like every other engine module so frust_gpu::lint::lint_wgsl_dir sees every line the GPU compiles.

Not assembled in super::shader_src with the pipeline modules: this is the host’s present pass rather than one of the frame’s own, it needs no helper prelude, and it is compiled per surface by UnpremultiplyPass::new rather than registered in the renderer’s shared library.