G2: improve editing reliability and Windows rendering fidelity

This commit is contained in:
Dexter
2026-10-06 00:00:31 -05:00
parent b46be83ab4
commit 39e6907b5f
101 changed files with 1727 additions and 351 deletions
+9 -2
View File
@@ -2,10 +2,17 @@
// the CPU effect in effectcraft-effects operation for operation. Every name here is prefixed
// `fxk_` (the family files share one module).
// Correct the approximate quotient before bitwise/threshold operations.
fn fxk_div3(a: vec3<f32>, b: f32) -> vec3<f32> {
let q = a / b;
let r = fma(-q, vec3<f32>(b), a);
return fma(r, vec3<f32>(1.0 / b), q);
}
// util::unpremul: (straight colour, alpha); colour 0 when alpha ≤ 1e-6.
fn fxk_straight(px: vec4<f32>) -> vec3<f32> {
if (px.w > 1e-6) {
return px.xyz / px.w;
return fxk_div3(px.xyz, px.w);
}
return vec3<f32>(0.0);
}
@@ -457,7 +464,7 @@ fn fxk_point(@builtin(global_invocation_id) gid: vec3<u32>) {
textureStore(out, p, px);
return;
}
let s = px.xyz / px.w;
let s = fxk_div3(px.xyz, px.w);
var r = vec3<f32>(0.0);
for (var i = 0u; i < 3u; i++) {
let v = s[i];
+5 -2
View File
@@ -15,11 +15,14 @@ fn fxn_pixel(gid: vec3<u32>) -> vec2<i32> {
}
// a / b rounded like the CPU's IEEE division (GPU division may be approximate): one
// residual correction with fma. Used where a threshold follows the quotient.
// residual correction with fma, followed by a second refinement for half-step cases.
// Used where a strict threshold follows the quotient.
fn fxn_div4(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
let q = a / b;
let r = fma(-q, b, a);
return fma(r, 1.0 / b, q);
let corrected = fma(r, 1.0 / b, q);
let residual = fma(-corrected, b, a);
return fma(residual, 1.0 / b, corrected);
}
// util::unpremul: (straight colour, alpha).
+6 -1
View File
@@ -453,10 +453,15 @@ fn fxs_warp(@builtin(global_invocation_id) gid: vec3<u32>) {
u = bu + (u - bu) * persp;
v = bv + (v - bv) * persp;
}
if (u < -1e-9 || u > 1.0 + 1e-9 || v < -1e-9 || v > 1.0 + 1e-9) {
// The CPU's 1e-9 edge allowance disappears in f32 at 1.0. Allow eight
// f32 ULPs for the inverse solve so exact quad-edge pixels remain covered.
let edge = 0.00000095367431640625;
if (u < -edge || u > 1.0 + edge || v < -edge || v > 1.0 + edge) {
textureStore(out, p, vec4<f32>(0.0));
return;
}
u = clamp(u, 0.0, 1.0);
v = clamp(v, 0.0, 1.0);
let rc = P.f[5];
let lx = rc.x + u * rc.z;
let ly = rc.y + v * rc.w;
+16 -16
View File
@@ -33,7 +33,7 @@ fn warp_blend(@builtin(global_invocation_id) gid: vec3<u32>) {
if ((flags & 2u) != 0u) {
d = vec4<f32>(0.0);
}
store_q(vec2<i32>(x, y), d, P.f[3].z);
store_q(vec2<i32>(x, y), d, P.f[3].z, P.f[3].w);
return;
}
let r0 = P.f[0];
@@ -52,7 +52,7 @@ fn warp_blend(@builtin(global_invocation_id) gid: vec3<u32>) {
if (stencil && (flags & 1u) == 0u) {
d = blend_pixel(mode, d, vec4<f32>(0.0), 0.5);
}
store_q(vec2<i32>(x, y), d, P.f[3].z);
store_q(vec2<i32>(x, y), d, P.f[3].z, P.f[3].w);
return;
}
var s = sample(src, sampling, px, py);
@@ -60,11 +60,11 @@ fn warp_blend(@builtin(global_invocation_id) gid: vec3<u32>) {
if (s.w > 0.0) {
d = d + s * P.f[3].y;
}
store_q(vec2<i32>(x, y), d, P.f[3].z);
store_q(vec2<i32>(x, y), d, P.f[3].z, P.f[3].w);
return;
}
if (s.w <= 0.0 && !stencil) {
store_q(vec2<i32>(x, y), d, P.f[3].z);
store_q(vec2<i32>(x, y), d, P.f[3].z, P.f[3].w);
return;
}
let op = P.f[3].x;
@@ -75,7 +75,7 @@ fn warp_blend(@builtin(global_invocation_id) gid: vec3<u32>) {
if (mode == 1u || mode == 2u) {
n = hash_noise(u32(x), u32(y), seed);
}
store_q(vec2<i32>(x, y), blend_pixel(mode, d, s, n), P.f[3].z);
store_q(vec2<i32>(x, y), blend_pixel(mode, d, s, n), P.f[3].z, P.f[3].w);
}
// Blend `src` (same size) onto `aux` (Image::blend_from).
@@ -91,7 +91,7 @@ fn blend_full(@builtin(global_invocation_id) gid: vec3<u32>) {
let d = textureLoad(aux, p, 0);
var s = textureLoad(src, p, 0);
if (s.w <= 0.0 && !is_stencil(mode)) {
store_q(p, d, P.f[0].y);
store_q(p, d, P.f[0].y, P.f[0].z);
return;
}
s = s * P.f[0].x;
@@ -99,7 +99,7 @@ fn blend_full(@builtin(global_invocation_id) gid: vec3<u32>) {
if (mode == 1u || mode == 2u) {
n = hash_noise(gid.x, gid.y, P.u[0].z);
}
store_q(p, blend_pixel(mode, d, s, n), P.f[0].y);
store_q(p, blend_pixel(mode, d, s, n), P.f[0].y, P.f[0].z);
}
// Track matte: `src` × matte factor of `aux`. u[0].x = 0 alpha, 1 alpha inverted, 2 luma,
@@ -179,24 +179,24 @@ fn quantize(@builtin(global_invocation_id) gid: vec3<u32>) {
if (p.x >= dims.x || p.y >= dims.y) {
return;
}
textureStore(out, p, quantize_px(textureLoad(src, p, 0), P.f[0].x));
textureStore(out, p, quantize_px(textureLoad(src, p, 0), P.f[0].x, P.f[0].y));
}
fn quantize_px(s: vec4<f32>, levels: f32) -> vec4<f32> {
let inv = 1.0 / levels;
let a = round_away(clamp(s.w, 0.0, 1.0) * levels) * inv;
fn quantize_px(s: vec4<f32>, levels: f32, inv: f32) -> vec4<f32> {
// Use the CPU-rounded reciprocal to avoid backend-dependent division.
let a = floor(clamp(s.w, 0.0, 1.0) * levels + 0.5) * inv;
var o = vec4<f32>(0.0, 0.0, 0.0, a);
o.x = round_away(clamp(s.x, 0.0, a) * levels) * inv;
o.y = round_away(clamp(s.y, 0.0, a) * levels) * inv;
o.z = round_away(clamp(s.z, 0.0, a) * levels) * inv;
o.x = floor(clamp(s.x, 0.0, a) * levels + 0.5) * inv;
o.y = floor(clamp(s.y, 0.0, a) * levels + 0.5) * inv;
o.z = floor(clamp(s.z, 0.0, a) * levels + 0.5) * inv;
return o;
}
// Store a compositing result, clamped and quantised to `levels` when > 0 (8/16 bpc: the comp
// after a layer, fused into the layer's composite).
fn store_q(p: vec2<i32>, v: vec4<f32>, levels: f32) {
fn store_q(p: vec2<i32>, v: vec4<f32>, levels: f32, inv: f32) {
if (levels > 0.0) {
textureStore(out, p, quantize_px(v, levels));
textureStore(out, p, quantize_px(v, levels, inv));
} else {
textureStore(out, p, v);
}