Every sampler a TSP can ask for - filtered or not, each axis clamped,
mirrored or repeated, the 16 mipmap biases, 288 - is made at the
Vulkan renderer's start into a plain table (SamplerManager::Init()), in
place of a std::map filled as polygons first asked. A drawer's texture
descriptor cache has its tables and a batch of sets with the drawer. The
translucent sort's room is reserved before any renderer's first frame,
the translucent list's polygons and 64K triangles (SortReserve()), and
each renderer's lists of the passes' draws (SortPassesReserve()): a
frame up to them allocates nothing.
Six sites come off the list of allocations during play, 26 left.
The sorter keeps its lists in memory only ever grown (grow_buf.h): a
SortList of draws a render pass, no std::vector anywhere in it or in the
renderers that call it. Triangles are sorted by depth with a stable
radix sort of (key, index) pairs (rend/sort_pairs.h, C89: three passes of
11 bits, a pass of one digit skipped; merged runs below 512, insertion
below 64), the same order a stable sort by < gives - about 8 times
std::stable_sort at 20,000 triangles. The strips (per-strip sorting) and
the prepass's runs go the same way, by the radix sort and a C merge sort.
SortPassesTo() sorts every pass and its prepass into one index array, and
SortPassesRoom() says how large it can be: Vulkan, Direct3D 11 and 12 call
these instead of each having the loop. Direct3D 9 now sorts straight into
its locked index buffer, as the others do into their mapped ones, instead
of into a vector per pass copied in after. OpenGL sorts into one buffer
and narrows it to 16 bits in place where the indices are.
tools/rend/sort_pairs_test.sh checks the sort against a stable sort by <
(CI). tools/rend/d3d9_frames.c is a Direct3D 9 frontend that checksums
every frame, and tools/rend/d3d_same.sh compares two Windows builds with
it and with d3d_hdr (Direct3D 9 needs Microsoft's d3dcompiler_47 under
wine: D3DCOMPILER).
GenSortedTo() and GenPrepassTo() write the indices to a pointer, with
room for them from SortedTriangles(), once each and in order. Vulkan,
Direct3D 11 and Direct3D 12 sort into the mapped buffer the card reads
them from - from the vertices as the game sent them, before their
colours are moved - instead of into vectors copied after. The vertices,
indices and volumes keep their copy: the CPU reads them again.
GenPrepass() leaves out a polygon whose alpha is its vertices' (or
theirs times the texture's) when none of its vertices has 255
(PrepassVerticesOpaque()): no pixel of it can hide what is behind it.
The header pass of GPU-side TA processing has not got the vertices, so
its prepass drew such polygons and the shader discarded every pixel.
Now the CPU writes, after the prepass's indices in the sorted indices'
buffer, a check for each translucent strip in the prepass whose alpha
is its vertices' (PrepassAlphaFromVertices(), the test
PrepassVerticesOpaque() starts with): its vertices and where its
triangles are among the prepass's (TAG_T_PREP, TAG_PC_*). A new kernel
(TAG_K_PREPASS), dispatched after the unpacking and before anything
moves the colours, looks at each strip's vertices, a workgroup a strip,
and where none that is drawn has an alpha of 255 makes its triangles
(a, b, c) into (a, a, a), which draw nothing. The draws and their
counts stay the CPU's; nothing is read back. A translucent strip is a
polygon parameter of its own, so this is GenPrepass()'s choice exactly.
The sorted indices are written when the decode is recorded, before the
kernel that reads them (they were written when the frame was drawn).
Vulkan and OpenGL both: the buffer is bound to the kernel as set 1
binding 10 and binding 13.
tools/pvr/ta_gpu_test: GenPrepass() is given the vertices' own alphas
again; the checks are run on the device and by the model, which must
agree word for word, and the prepass's draws must be GenPrepass()'s as
before, a draw all of whose triangles were left out being one it has
not got. It counts the area of what is left out.
The prepass's area per sorted frame at 640 x 480 (ta_gpu_test, the
device's vertices): Jet Grind Radio s9 13 of 1390264 pixels left out,
s10 107714 of 583534 (18%), Virtua Fighter 4 29384 of 268756 (11%), Sega
Rally 2 15156 of 285594 (5%), Dead or Alive 2 none.
Sorting translucent polygons by triangle (or by strip) draws one that is
behind another after it now and then, and as sorted polygons write no
depth, it shows through. Jet Grind Radio's train shows it on every
renderer that sorts this way: the train is in the translucent list, its
roof is one long triangle reaching from near the horizon to past the
bottom of the screen, which sorts by its farthest corner as far away,
and the doors and side panels under it are drawn after it, over it. The
same picture on OpenGL, Vulkan, Direct3D 11 and Direct3D 9 at any
resolution; the per-pixel renderers have the roof.
The hardware sorts each pixel, and a layer whose blending leaves nothing
of what is behind it - destination factor zero, or one minus the source
alpha with the alpha at 1 - hides everything behind it whatever the
order. So before a sorted list's colours are drawn, the depth of such
pixels is written: colour writes off, depth test and write on, each
pixel's alpha worked out by the colour pass's own shader, and the pixel
discarded unless that alpha writes as 255 in 8 bits. The colour pass
then draws nothing behind them. What was drawn in the right order is
drawn as before.
rend/sorter (GenPrepass()): the polygons that can hide - blended into the
picture, not an accumulation buffer; not a trilinear pass whose alpha is
scaled down; not one whose alpha is its vertices' with no vertex at 255 -
as triangles with GenSorted()'s provoking vertex, grouped by what goes
into their alpha, culling and clipping, so that a model's polygons are
one draw (depth written in any order comes out the same). In a render
pass before the last, only with TranslucentPolygonDepthMask and only for
polygons whose depth that writes anyway, so a later pass never sees a
depth it did not see before.
Direct3D 11 and 9, and OpenGL where the shader writes the depth: the
colour pass's pixel shader with one more constant, the least alpha
(0 for the colours), and a discard below it - the shaders already write
their depth, so the discard costs no early depth test. No shader more is
compiled; the depth/blend states are made at start. The constants the
colour pass leaves to the last polygon that set them are put back after
the prepass. OpenGL ES 2, whose shaders do not write the depth, has no
prepass (a discard there would cost every polygon the tile GPUs' hidden
surface removal). The OpenGL fragment shader no longer fits 8 KB: its
buffer is 16 KB, as gl4's.
Cost, Direct3D 11, per frame: train.state 48 draws more over 744 sorted
ones, about 65 us of CPU against GenSorted()'s 460; Jet Grind Radio s9
34 over 416, 70-100 us against 490-680; Virtua Fighter 4 15 over 744,
57 us against 594 (wine, two shared CPUs).
Two triangles that come one after the other in the sorted order are one
draw when their polygons are drawn the same way: the same drawing bits of
the control word, depth, shading and texture words, and clipping. The
rule moves to sorter.h as SortSameDraw(), so that a sort done elsewhere
(the GPU-side TA processing's) joins its draws by the very same words.
No change to what is drawn.
- A render to a texture with FB_X_CLIP or FB_Y_CLIP max < min sized its
target as a huge number; the power-of-two loop never ended. Such a
render now draws nothing, as the chip does (GL, GL4, Vulkan, OIT).
- Vulkan: the frontend can lengthen its swap chain while running
(fast-forward): the rings stay at the size of the start and what is
kept per frame index grows with it (quad, OSD, framebuffer textures).
- Per-pixel renderers: no early discard for polygons whose source is the
second accumulator or that have two volumes, and none for "destination
times white source", which a shadow scales later.
- A polygon clipped inside its tile clip is also kept inside the pixel
clip (upstream 4fdd28fab, done on pixel rectangles).
- Vulkan sorted triangles: odd triangles of flat strips took the next
triangle's colour; they are emitted with the provoking vertex first.
- Vulkan: translucent depth mask (Cosmic Smash), as GL has it.
A number of settings are given a value once, in LoadSettings(), and
never another: there is no core option behind them and nothing else
writes them. Each is replaced by its value where it was read, and the
branch that value never took is removed.
- dynarec.idleskip, always on: the idle loop detection in the block
decoder is unconditional, and the branch for it being off is gone.
- dynarec.unstable_opt, always off: the store queue shortcut in the
ARM recompiler that it guarded is removed.
- pvr.MaxThreads, always 3: a constant in the texture upscaler.
- pvr.subdivide_transp, always off, and only read inside an #if 0
block of the triangle sorter, which goes with it.
- rend.Clipping, always on: tile clipping no longer asks.
- rend.AutoExtraDepthScale, always on.
- debug.SerialConsole, always off: the serial port's echo to stdout.
- imgread.PatchRegion, never set: the two functions that would have
rewritten a disc's region strings returned at once, and are removed
with the calls to them.
- validate.OpenGlChecks and pvr.Emulation.zMin/zMax were written and
never read.
LoadSettings() also loses a block for builds that are not libretro,
which this core is not, and which set a field that no longer exists.
core/rend/soft/softrend.cpp, a software renderer that used one of
these settings, was not in any makefile. It is deleted, and its
declaration with it.
get rid of PixerPacker templates
gl4: no need for GLSL precision qualifier in GL4
vk: Refactor base and oit renderers to avoid code dup
vk: Linear tiling is faster for small, frequently updated textures
Fix Hello Kitty crash with empty first TR polygon
Fix Test Drive Le Mans crash on invalid textures