Files
effectcraft/crates/gpu/src/shaders/display.wgsl
T
bflatasticandClaude Opus 5.5 9d85d4f116 Viewer: Auto resolution follows the magnification; minified frames are averaged (#417)
Cause: Auto dropped to Half below 75 % magnification, so text between
50 % and 75 % was rendered with fewer pixels than the screen shows and
looked soft. Below 100 % the frame texture was minified with a plain
bilinear sampler and no mip levels (Full at 46 % skipped most frame
pixels), so edges stair-stepped.

Fix:
- Auto renders the pixels the magnification needs, as in After Effects:
  the coarsest whole factor that still gives every screen pixel a
  rendered one (Full above 50 %, Half down to 33.3 %, Third down to
  25 %, Quarter below).
- GPU frames: the display texture carries a full mip chain, each level
  the 2 x 2 mean of the one above (display.wgsl `mip`, same bind group
  layout as `display`), sampled trilinearly with Viewer Zoom Quality
  More Accurate (Faster keeps the nearest pixel of the full-size frame).
- CPU frames (egui-wgpu textures have no mips): uploaded averaged down
  by the whole factor that leaves about one texel per screen pixel, the
  same box average as the texture-limit fit (#201), re-uploaded when the
  magnification calls for another factor; drawn with texture coordinates
  that keep each texel over its pixels when the size isn't a multiple.
  Show Channel / exposure textures follow.

Tests: gpu tests::display_frames_are_mipmapped (level count and level 1
= 2 x 2 means of level 0), viewer_tools
auto_resolution_follows_the_magnification (60 % was Half) and
minified_frames_keep_their_texels_in_place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 23:17:33 +07:00

37 lines
1.6 KiB
WebGPU Shading Language
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Viewer display: premultiplied f32 → premultiplied RGBA8 (ui-egui's frames::to_color_image),
// written straight into a texture egui samples.
@group(0) @binding(1) var dsrc: texture_2d<f32>;
@group(0) @binding(3) var dout: texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(16, 16)
fn display(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = vec2<i32>(textureDimensions(dout));
let p = vec2<i32>(gid.xy);
if (p.x >= dims.x || p.y >= dims.y) {
return;
}
let s = textureLoad(dsrc, p, 0);
let a = clamp(s.w, 0.0, 1.0);
// (v · 255 + 0.5) truncated, as the CPU conversion.
let q = floor(vec4<f32>(clamp(s.xyz, vec3<f32>(0.0), vec3<f32>(a)), a) * 255.0 + 0.5) / 255.0;
textureStore(dout, p, q);
}
// The display texture's next mip level (`dsrc` = the level above, `dout` = this one): each texel
// the mean of the 2 × 2 premultiplied texels it covers, as ui-egui's fitted CPU textures average
// (#417). A level one texel wide or high repeats its edge.
@compute @workgroup_size(16, 16)
fn mip(@builtin(global_invocation_id) gid: vec3<u32>) {
let dims = vec2<i32>(textureDimensions(dout));
let p = vec2<i32>(gid.xy);
if (p.x >= dims.x || p.y >= dims.y) {
return;
}
let last = vec2<i32>(textureDimensions(dsrc)) - vec2<i32>(1);
let q = p * 2;
let s = textureLoad(dsrc, min(q, last), 0) + textureLoad(dsrc, min(q + vec2<i32>(1, 0), last), 0)
+ textureLoad(dsrc, min(q + vec2<i32>(0, 1), last), 0) + textureLoad(dsrc, min(q + vec2<i32>(1, 1), last), 0);
textureStore(dout, p, s * 0.25);
}