[iterate-3AA] Fix logo upside-down: no Y-flip on the clip-enabled NDC path
DEFECT 1 (logo upside down) ROOT + FIX. The publisher "SQUARE ENIX" logo rendered vertically mirrored vs the canary oracle (white upright on black). Measured (env-gated readback + texture-row + per-vertex dumps, all removed): - The K8888 logo texture decodes UPRIGHT (text in the top rows 1..161; the red dots sit at ~43% from the texture top). NOT a decoder row-order bug. - The logo geometry is a centered QuadList whose vertices are emitted in *clip space* (Y-UP, e.g. pos.y +0.085 top / -0.104 bottom), with the texture V mapped top->bottom (UV v 0.001 at the top vertex, 0.090 at the bottom). On both the Xbox 360 (D3D9) and wgpu, clip +Y maps to the framebuffer top — so a clip-space position is portable with NO Y-flip. - `compute_ndc_xy` unconditionally negated Y (the flip the *screen-space* pixel path legitimately needs). For the clip-enabled logo this swapped top<->bottom vertices while leaving the texture V unchanged, so the sampled sub-rect read bottom-up: red dots rendered at 58% from the top (a clean vertical mirror) instead of 43%. FIX: keep the Y-flip only on the clip_disable (screen-space pixel) branch where the framebuffer Y-down->wgpu Y-up flip is real; the clip-enabled branch now passes clip-Y-up through identity. Readback after the fix: red dots at 42% from the top (= texture's 43%) -> logo UPRIGHT, still centered. DEFECT 2 (background) was already correct + faithful; 3Z's contradiction is REFUTED by direct readback: the bg fill (vs 0x36660986 / ps 0xed732b5a, fullscreen RectangleList) reads its real vertex color (raw 0x818000c7 = -32896.5 as float) into r0, the PS exports it, and the GPUBUG-115 RB-UNORM saturate (canary spirv_shader_translator.cc:3607) clamps it to 0 -> BLACK, matching canary. The seed r0=(gvidx,...) does NOT show through (it's overwritten by the color vfetch). No code change needed. Readback of the full frame now matches canary: WHITE upright "SQUARE ENIX" + red dots on a BLACK field. UI-capture-path only (`compute_ndc_xy` runs solely when frame_captures is Some, i.e. --ui; None headless) -> deterministic core untouched, n50m --gpu-inline --stable-digest exit 0 (DRAW_INDX 275 / K8888 decode 137, identical across runs). cargo test --workspace green. Temp probes removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -155,29 +155,40 @@ pub fn compute_ndc_xy(rf: &RegisterFile) -> ([f32; 2], [f32; 2]) {
|
|||||||
|
|
||||||
let (s, o);
|
let (s, o);
|
||||||
if clip_cntl & (1 << 16) != 0 {
|
if clip_cntl & (1 << 16) != 0 {
|
||||||
// clip_disable: VS outputs render-target-pixel coords. Rescale the
|
// clip_disable: VS outputs render-target-*pixel* coords (Y-DOWN: pixel
|
||||||
// whole RT extent to [-1,1] (canary's huge-host-viewport path).
|
// y=0 is the top row of the render target). Rescale the whole RT extent
|
||||||
|
// to [-1,1] and FLIP Y so pixel-top → wgpu clip-top (canary's
|
||||||
|
// huge-host-viewport path; the framebuffer→clip flip is real here).
|
||||||
let px2ndc_x = 2.0 / x_max;
|
let px2ndc_x = 2.0 / x_max;
|
||||||
let px2ndc_y = 2.0 / y_max;
|
let px2ndc_y = 2.0 / y_max;
|
||||||
let sx = scale_x * px2ndc_x;
|
let sx = scale_x * px2ndc_x;
|
||||||
let ox = (off_x - x_max * 0.5 + add_x) * px2ndc_x;
|
let ox = (off_x - x_max * 0.5 + add_x) * px2ndc_x;
|
||||||
let sy = scale_y * px2ndc_y;
|
let sy = scale_y * px2ndc_y;
|
||||||
let oy = (off_y - y_max * 0.5 + add_y) * px2ndc_y;
|
let oy = (off_y - y_max * 0.5 + add_y) * px2ndc_y;
|
||||||
s = [sx, sy];
|
// Flip Y: pixel-Y-down → wgpu clip-Y-up.
|
||||||
o = [ox, oy];
|
s = [sx, -sy];
|
||||||
|
o = [ox, -oy];
|
||||||
} else {
|
} else {
|
||||||
// Clipping enabled: the VS already emits clip space; the viewport
|
// iterate-3AA (DEFECT 1 ROOT): clipping enabled → the VS already emits
|
||||||
// scale/offset map clip→pixels. Convert to the host clip directly:
|
// *clip-space* coordinates (Y-UP: +Y is the top of the screen), exactly
|
||||||
// host_ndc = guest_ndc (scale ~ 1) but still apply the abs-scale based
|
// the convention the Xbox 360's D3D9 and wgpu BOTH use for clip space
|
||||||
// remap canary uses. For the common enabled case the guest already
|
// (NDC +Y → framebuffer top in each API; the framebuffer Y-direction is
|
||||||
// outputs [-1,1] so scale=1/offset=0 except sign of Y. We approximate
|
// an internal viewport detail handled identically by both). A clip-space
|
||||||
// with identity XY + Y-flip (sufficient for non-screen-space draws;
|
// position is therefore portable to wgpu with NO Y-flip. The previous
|
||||||
// refined alongside depth in a follow-up).
|
// code unconditionally negated Y (the same flip the screen-space pixel
|
||||||
|
// path needs), which mirrored the publisher logo vertically: its quad is
|
||||||
|
// centered (±0.085 around 0) so the *position* stayed centered, but the
|
||||||
|
// negation swapped top↔bottom vertices while the texture V was unchanged
|
||||||
|
// → the sampled sub-rect (UV v 0.001→0.090) read bottom-up → "SQUARE
|
||||||
|
// ENIX" rendered upside down in place. Measured (readback): the red dots
|
||||||
|
// sit at 43% from the texture top but rendered at 58% from the top
|
||||||
|
// (= a clean vertical mirror); removing the flip restores them to 43%.
|
||||||
|
// Identity XY (no flip) maps guest clip-Y-up straight to wgpu clip-Y-up.
|
||||||
s = [1.0, 1.0];
|
s = [1.0, 1.0];
|
||||||
o = [0.0, 0.0];
|
o = [0.0, 0.0];
|
||||||
|
return (s, o);
|
||||||
}
|
}
|
||||||
// Flip Y for wgpu (render-target Y-down → clip Y-up).
|
(s, o)
|
||||||
([s[0], -s[1]], [o[0], -o[1]])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode a [`PrimitiveType`] as the raw Xenos code used across the bridge.
|
/// Encode a [`PrimitiveType`] as the raw Xenos code used across the bridge.
|
||||||
|
|||||||
Reference in New Issue
Block a user