The frame's lists are ta_list.c's (C89): List<T> is a typed facade over
it. A strip's indices are ta_index.c's C89 kernel: one branch-free check
of the whole strip, then a straight run of indices; the per-vertex loop
only where a vertex is bad or the room runs out. The parse is about 20%
faster (2.74 to 2.17 ns a vertex on the heavy disc), its output the same
byte for byte.
With Vulkan on a machine whose video memory the CPU reads back fast (one
memory, as lavapipe's and integrated GPUs'), the vertices and indices
are parsed straight into a slice of memory the card reads, one slice a
frame in flight (rend/parse_arena.h, vulkan/parse_arena_vk.h), and the
translucent sort writes after them: no copy into the main buffer. Other
machines copy as ever.
Tests: tools/pvr/ta_index_test.sh (against the old loop, 1M strips),
tools/rend/parse_arena_test.sh, tools/vulkan/vk_parse_arena.sh (every
render in place, pictures those of the copy, each renderer, threaded,
upscaler, a change of size), tools/pvr/ta_parse_bench.sh; vk_hdr gains
VKH_UNCACHED, VKH_OPTS_AT and VKH_BUFFERS.
The core option is now "PowerVR2 scaler, flicker filter and dither"
(reicast_pvr2_scaler, still off by default). It does what the
PowerVR2 does to the picture as it writes the tiles to video memory,
in the chip's order: the X scaler, the Y filter, then the packing to a
framebuffer of 16 bits a pixel and its dither (FB_W_CTRL). The picture
shown is that framebuffer as the video output reads it back, with
FB_R_CTRL's fb_concat in the low bits.
- rend/tile/pvr_scaler.h: pvr_fb_word() and pvr_fb_write(), the
packing to 0555/1555 or 565 with rend/rtt_watch.cpp's 4x4 pattern
(the same arithmetic as the renders to a texture). At internal
resolution N the pattern goes by the chip's pixel (N picture pixels
to one). 4444 and the 24/32-bit formats are left as they are.
Checked against an independent copy of rtt_watch.cpp's packing plus
TexCache.cpp's read-back: no difference in 195072 cases.
- The compute renderer's in-place pass (PVRC_K_YFILTER) does the
packing and dither after the Y filter, and runs on any frame with
either (ComputeFrame::WriteOut()). tile_ref and refsw do the same.
refsw did not dither the picture before: it does now.
- The raster renderers run the same pass in place over their picture
after the frame (before the VMUs and crosshairs on Direct3D and
OpenGL; on Vulkan they are in the frame's render pass, so the pass
runs over them too):
- Vulkan, both renderers: the compute renderer's own SPIR-V
(vulkan/writeout.cpp).
- Direct3D 12, both renderers: the compute renderer's own DXIL
(dx12/dx12_writeout.cpp). Needs typed UAV loads of R8G8B8A8_UNORM.
- Direct3D 11, both renderers: the same pass in HLSL, compiled when
the renderer starts (dx11/dx11_writeout.cpp). Needs typed UAV
loads of R8G8B8A8_UNORM (TypedUAVLoadAdditionalFormats).
- OpenGL 4.3 and later, both renderers: the same pass in GLSL,
compiled (and cached) when the renderer starts
(gles/gl_writeout.cpp). The frontend's framebuffer is not a
texture of the core's, so the picture is blitted to the pass's
texture and back.
Not on OpenGL ES, OpenGL 3 or Direct3D 9.
The pictures are made writable by a compute shader only when the
option is on. Turning the option on or off counts as a change of size:
the renderer makes its pictures again between frames, never during
one. Off, the renderers do what they did before, at no cost.
- The rasterizers' X scaler is not done here: they still squash the
game's double-width drawing in the transform, as before. No dumped
game sets SCALER_CTL's horizontal scaling.
The old "PowerVR2 Post-processing Filter" (reicast_pvr2_filtering,
OpenGL only) is removed: PostProcessor, gles/postprocess.cpp/.h, the
PowerVR2Filter setting, the option in every language, and its
Makefile entry. It guessed at a 3-tap vertical blur, which the exact Y
filter now replaces. It also dithered by its own rules and added a made-up
"VGA signal loss" smear, which the hardware does not do. It compiled its
shaders mid-game and drew through an extra framebuffer.
RetroArch ignores old values of the option left in users' configs.
Gates (lavapipe/llvmpipe and vkd3d, not a real GPU):
- tile_test VF3tb 1x on Vulkan, at 1 and 4 samples, and on Direct3D 12,
at 1 and 4 samples: off, filter + 565 dither (VF3tb's own
registers), 555 dither alone, and taps 3 lines apart with fb_concat 5.
16/16 runs are the same as tile_ref on each.
- RetroArch (Vulkan, then glcore) with VF3tb booted, frame 900, for each
of Vulkan per-triangle, Vulkan per-pixel, OpenGL per-triangle and
OpenGL 4 per-pixel. With the option off, the picture is the same as
master's, byte for byte. With it on, the picture is exactly the
option-off picture through an independent model of the filter and
dither (Y_COEFF 0x8040, 565, dither on). No validation errors.
- Direct3D 11 and 12 rasterizers: syntax-checked with mingw, and the
Direct3D 11 pass compiled with d3dcompiler_47 (cs_5_0). Not run.
With the flicker filter on, the compute renderer at 8x shades extra
lines around every tile for the filter's taps, which is too slow for
now. Off until the filter is done without them; a core that has not
been given the option yet also starts with it off.
The PowerVR2 passes every tile through its X and Y scalers on the way
from the accumulation buffer to video memory (SCALER_CTL, Y_COEFF; the
Dreamcast System Architecture manual, 3.4.11). refsw does since the last
change; now the compute renderer does too, with the same arithmetic
(rend/tile/pvr_scaler.h), behind a new core option "PVR2 scaler /
flicker filter" (reicast_pvr2_scaler, enabled by default; the compute
renderer only - the raster renderers ignore it).
- X scaler (SCALER_CTL bit 16): the game draws twice as wide; the frame
is drawn so, and each pair of pixels is resolved as one box of 2S x S
samples (S the SSAA samples), with no pass or image of its own.
- Y filter (vertical scale factor above 0x400; most Sega games set 0x401
and Y_COEFF 0x8040): each line blended with the lines above and below
by Y_COEFF (centre bits 15:8, neighbours 7:0, in 256ths), its taps N
lines apart at internal resolution N (the picture's height over 480),
the picture's first and last lines in the place of the missing one.
The walk's fine tile shades a halo of N lines above and below the
lines it writes, the taps in its own lanes (shared memory), so there
is no new pass, image or readback; a coarse tile's lists are binned N
lines taller at each side to cover the halo, and its rows of fine
tiles each write their lines less the halo (pvrc_walk_down(), so the
variants' dispatches across, pvrc_walk_x(), take the frame's flags).
Where the halo would be large the fine tile is made narrower and
taller (pvrc_fine_yfilter()); Direct3D 12's table of dispatches across
has a row for each count of lines, made at Init(). The filter is in
spare bits of a frame word (PVRC_F_FLAGS): turning the
option on or off mid-game compiles and allocates nothing.
Disabled, the frame words and the kernels' work are what they were, and
the picture is the same byte for byte. Nothing is added to the save
state: the registers are already the render context's.
tile_ref.c does the same, and tile_test draws each frame with the filter
off and on (TILE_YFILTER, default "off 1:8040"); tile_bench takes
BENCH_YFILTER to time it.
ComputeFrame::Pack(), which makes the kernels' frame words from the
render context, was the Vulkan drawer's, built on its BaseDrawer for the
transform, the base scissor, the tile clip and the fragment constants.
It is now rend/tile/compute_frame.cpp, built in every configuration,
with a renderer giving only what is its own: its matrices made for the
frame (Begin()), its base scissor and its flat shading's colours. The
tile clip is ::GetTileClip() against that scissor and the fragment
constants are the formulas both raster renderers have, as they were.
VulkanComputeFrame is the Vulkan renderers' (and tile_dump's).
The words are the same: the new and the old Pack() compared word for word
on ten frames of five Jet Grind Radio states (tile_dump with both).