Compare commits

...
Author SHA1 Message Date
Ludovic MarieandClaude Opus 5.5 81baa3e0cb Screen overlays: wrap texels with a select, not integer modulo
The camera overlays (the vignette every frame) read up to four texels a
pixel, each wrapped with two integer modulos. Intel GPUs emulate integer
division. Reducing the coordinate to one period first (fract, as the shader
did before) leaves every read at most one texel outside the sprite, so a
select wraps it. Same texels and weights; clamp-to-edge is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-23 00:55:22 +02:00
+6 -3
View File
@@ -23,9 +23,11 @@ struct Out {
}
const SAMPLE_BLUR: u32 = 1u;
const SAMPLE_CLAMP: u32 = 2u;
// A texel of the sprite, addressed by repeat or clamp-to-edge.
// A texel of the sprite, addressed by repeat or clamp-to-edge. With repeat, `p` is at most one
// texel outside the sprite (`sample` reduces the coordinate first), so wrapping is a select: no
// integer division, which Intel GPUs emulate (it cost 0.85 ms a frame on the vignette on an HD 620).
fn texel(origin: vec2<i32>, size: vec2<i32>, p: vec2<i32>, clamp_edge: bool) -> vec4<f32> {
var q = ((p % size) + size) % size;
var q = select(select(p, p - size, p >= size), p + size, p < vec2(0));
if clamp_edge { q = clamp(p, vec2(0), size - 1); }
return textureLoad(atlas, origin + q, 0);
}
@@ -34,7 +36,8 @@ fn sample(in: Out) -> vec4<f32> {
let origin = vec2<i32>(round(in.rect.xy * dims));
let size = max(vec2<i32>(round((in.rect.zw - in.rect.xy) * dims)), vec2(1));
let clamp_edge = (in.sampling & SAMPLE_CLAMP) != 0u;
let p = in.uv * vec2<f32>(size);
// Repeat: one period of the sprite, so every texel read is at most one outside it.
let p = select(fract(in.uv), in.uv, clamp_edge) * vec2<f32>(size);
if (in.sampling & SAMPLE_BLUR) == 0u {
return texel(origin, size, vec2<i32>(floor(p)), clamp_edge) * in.color;
}