mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-09 03:39:38 -10:00
* feat(video): hi-res Stage 2 — fractional-walk source supersampling (#338)
Implements Stage 2 of docs/hires-upscaling-design.md: blits whose A1
SOURCE walk consumes a fraction (XADDINC with fractional FINC, or the
XADD0 + fractional-FSTEP column scaler) onto a 16bpp CRY destination
now sample the source at 2x density for the shadow surface, producing
real sub-pixel detail instead of Stage 1's box replication. The stock
walk, stock writes and every control decision (transparency, colour
key, Z, clip, LFU select) are untouched and made once at 1x; the
shadow only carries content (design sections 4/5).
- shadowfb.{c,h}: ShadowHiresStoreCryBlock() stores an N*N block with
per-subpixel {value16, frac16} under the stock value's tag.
- blitter.c fast engine (blitter_generic, DSTA2 branch): qualifying
uninhibited pixels derive sub-samples by re-reading the source at
pos + inc/2 (and pos + step/2 for the FSTEP shape) and applying the
same data path (LFU / ADDDSEL / SRCSHADE incl. its frac16); GOURD and
PATDSEL are source-independent and keep replication.
- blitter.c accurate engine (BlitterMidsummer2 pixel-mode dwrite):
same shapes; sub-samples read through ADDRGEN and apply
SRCSHADE-lane-0 + LFU replicas (a direct ADDARRAY call would clobber
its persistent carry state). The sread states now also cache
a1_frac at read time, and the collapsed-copy fast path routes DSTA2
UPDA1F blits through the state machine only while hi-res is active.
- tom.c: the Nx CRY renderer consumes per-subpixel entries under
true-color too (ShadowFBCryRGB per entry), completing the frac16
composition Stage 1 deferred.
- test/tools/hires_shot.c: evidence tool — scripted-input screenshots
plus % of non-uniform 2x2 blocks per frame (the box-replication
property failing on beneficiaries is the feature).
All new work is gated behind shadowHiresActive: option off is
structurally bit-identical to develop.
Co-Authored-By: Claude Fable 5 <[email protected]>
* fix(video): hi-res epoch trusted window 2 -> 16 frames (#338 Stage 2)
The design's now/now-1 trusted window assumed the 3D view is redrawn
every presented frame. Doom -- the Stage 2 anchor -- runs its engine
at ~10-15Hz double-buffered, so displayed blocks are routinely 3-8
presented frames old and the 2-frame window rejected every one of
them: measured 0.00% supersampled output on E1M1 gameplay, vs 8.68%
with the window widened. 16 frames (~267ms NTSC) covers slow engines
while still bounding the R3 stale-structure class (a false positive
needs a value-coincident dead tag AND 16 frames without a rewrite,
and self-heals at the next real write). Window comparison measured:
2 -> 0.00%, 16 -> 8.68%, infinity -> 8.68% (16 saturates the benefit).
Co-Authored-By: Claude Fable 5 <[email protected]>
* chore: gitignore hires_shot tool binary
Co-Authored-By: Claude Fable 5 <[email protected]>
* fix(blitter): exact ADDDSEL semantics in the hi-res sub-sample mirror
Copilot review: the Stage 2 ADDDSEL sub-sample derivation diverged from
the stock DSTA2 data path in two ways -- an unsigned clamp where stock
does a SIGNED saturated byte add (source sign-extended) when TOPBEN is
clear, and a saturate where stock MASKS to 12 bits when TOPNEN is clear.
Mirrored exactly. Also documents why PIXEL_OFFSET_16(sub) is correct:
the macro token-pastes into sub_x/sub_y/sub_width/sub_pitch, reusing the
stock addressing formula over the explicit 16.16 position rather than
transcribing it.
make TEST_EXPORTS=1 test exit 0; Checkered Flag 2x box-replication gate
still exact (398/398, 0 bad blocks).
Co-Authored-By: Claude Fable 5 <[email protected]>
* docs: AvP renderer analysis — Stage 2 already reaches it (#338)
Commissioned to explain a measured 0.0000% supersampled-block result for
Alien vs Predator and to find an extension that would reach it. The
premise does not hold at branch HEAD: AvP measures 30.62% supersampled
2x2 blocks in a static corridor and 13.01% in a moving scene, on BOTH
blitter engines, producing bit-identical output.
The 0.0000% is reproduced exactly by reverting HIRES_EPOCH_WINDOW 16 -> 2
in src/tom/shadowfb.c -- the constant 404cb11 changed for the identical
symptom in Doom. A/B with everything else held fixed: gate passes
150,480 blits either way, 13,117,200 Stage 2 blocks stored either way,
6,087,600 of them non-uniform either way -- and OP resolve hits go
42,827,520 -> 0. The detail was produced and then discarded at the
value+epoch check, 100% of it. AvP double-buffers and takes more than
one presented frame per rendered view, so the displayed buffer is always
older than a 2-frame window.
Also answers the rest of the brief, which stands regardless:
- Blit census in gameplay: 252 blits/frame, 99.4% SRCSHADE textured spans
onto a 16bpp CRY destination with a fractional inner source walk; zero
GOURD, zero Z, zero UPDA1F, zero ADDDSEL. 99.92% of census-qualifying
blits pass the Stage 2 gate; the only rejects are 120 !SRCEN blits.
No census-vs-gate predicate gap exists for this title.
- Renderer shape: Doom-style, two families -- vertical wall columns
(A2 XADD0 + YADD1, source Y fraction) and horizontal floor/ceiling
spans (A2 XADDPIX, source X fraction), 128px textures onto a 320px
framebuffer, OUTER==1 on 99.8% of blits.
- R2 for AvP (unmeasured by the Stage 0 census): 100% blitter-rasterized.
GPU stores 1,256 B/frame into main RAM and 0 B/frame into any buffer
the OP scans; its role is to program the blitter (151,200 of 151,440
blits launched with who == GPU).
- No composite copy: the rasterizer writes straight into the displayed
double-buffered surface at $094000-$0DDFFF; OP resolve hit rate 97.8%.
- GPU disassembly of the rasterizer proves sub-texel information exists:
it splits 16.16 fixed-point texture coordinates into A1_PIXEL/A1_FPIXEL
and 16.16 steps into A1_INC/A1_FINC, handing the blitter the full
16-bit fraction of both start phase and step.
- Honesty check: 52% (static) / 36% (moving) of Stage 2 sub-samples land
on a different source texel -- real recovery. The rest are magnified,
where no N can invent detail.
Verdict: no extension needed, and each hypothesised one is measured
false (GPU-store hook, phrase-mode dwrite, composite-copy propagation).
Transferable finding: check the OP resolve hit rate before investigating
blit shapes on any future "title X shows no supersampling" report --
the epoch-window failure is silent, total, and logs nothing.
Research only. Instrumentation was a throwaway compile-time patch and
is not part of this commit.
Co-Authored-By: Claude Fable 5 <[email protected]>
* docs: AvP hi-res evidence screenshots + review fixes (#338)
Adds the visual evidence the report was missing and addresses the PR
review threads.
Screenshots (docs/site/pr-avp-hires-*.png, embedded in section 0.1):
matched 1x/2x captures of frame 6000 from the section 9 reproduction
command, plus two 6x-magnified crops. The 1x panel is nearest-neighbour
replicated to the 2x geometry -- exactly what the Stage 1 path presents --
so every difference between panels is Stage 2 content. The doc says
plainly that the full-frame difference is subtle and that part of what the
door crop adds is a half-step value rather than new structure.
The measurement holds: 30.6237% non-uniform 2x2 blocks at frame 6000,
reproduced exactly from the section 9 command.
Metric caveats, all measured, in a new "Reading the number" subsection:
- The percentage is 2x-ONLY. At 1x it compares independent scene pixels
and reads 90.0051% on the same frame; the tool header claimed 0.000
there, which was wrong. Corrected in hires_shot.c.
- A video_stall log line does NOT mean the metric is invalid. AvP
re-blits an identical view when the player stands still, so the frame
freezes while the shadow surface stays fresh: frames 3200/4000/5000/
5800/6000 of the fixture are byte-identical PPMs and all read 25.0601%.
The 30.6237% frame sits inside a video_stall window too.
- A real 0.0000% at 2x has four causes besides "Stage 2 missed this
title": a non-3D scene (AvP's menus measure exactly 0.0000%), zero
frames dumped (total_blocks=0 prints 0.0000), a Stage-2-less build, and
the aged-out shadow this report explains.
test/fixtures/avp_reach_gameplay.press gains keep-alive input to frame
~6300. Without it the scene freezes after frame 3000 -- harmless for the
3000-frame gate, a trap for anything longer. With it the same frames read
23.59/10.94/22.46/21.13/21.05%: non-zero and varying, i.e. live. Presses
past the run length never fire, so existing runs are unaffected.
Review fixes:
- blitter.c: drop the duplicated PIXEL_OFFSET_16 comment block.
- hires_shot.c: size the framebuffer with sizeof(*st->fb).
- The report's status line now explains that this branch is stacked on
#357, so a diff against develop carries that PR's product code.
Co-Authored-By: Claude Fable 5 <[email protected]>
* docs: verify panel alignment + re-measure both AvP figures (#338)
Follow-up evidence for the section 0.1 screenshots.
Panel alignment is now proven rather than asserted: across frame 6000, all
54,280 uniform 2x2 blocks in the 2x capture equal their 1x source pixel
exactly, and so does the top-left sub-pixel of all 23,960 non-uniform ones.
That establishes the two runs are the same machine state at the same frame
(so any panel difference is Stage 2 content) and, separately, that Stage 2
is strictly additive -- it never alters the stock sample.
Both headline figures re-measured from a clean rebuild: static 30.6237% at
frame 6000, moving 13.0138% over frames 5650-6100 with --press 5600:up:400.
Section 8 no longer contradicts the new section 9 note: its 'no log line'
claim is scoped to a line naming the Stage 2 failure, since video_stall
fires on healthy runs too.
Co-Authored-By: Claude Fable 5 <[email protected]>
---------
Co-authored-by: Claude Fable 5 <[email protected]>