mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-10 11:21:28 -10:00
master
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8c2e5a410e |
Vulkan: make the fixed and utility pipelines on the pipeline thread
A renderer hands its general, modifier volume, prepass and other fixed pipelines to the pipeline thread when it starts, and no longer waits for them; a draw takes one made, makes it itself if the thread has not started on it, or waits for the thread if it has. The quad, SSAA resolve, motion vector, texture decoder, TA decoder and compute renderer pipelines go the same way, and one the thread could not make is made where it is first used, failing there as it did at start. The compute renderer's kernels are handed over first, as every frame uses them. |
||
|
|
4fcbf561c8 |
vulkan: the depth is the position's, as the chip has it, in every pass
The raster renderers wrote a depth of their own from the fragment shader, log2(1 + 100000/w)/34, in every pass, and tested EQUAL between passes: the per-pixel renderer's colour pass over its depth pass, a polygon drawn again for its shadow, the depth prepass of a sorted translucent list under its colours. Each pass has its own fragment shader and so its own pipeline, and a driver may round the same expression differently in each one: where a pass's depth missed the other's by a unit in the last place, the EQUAL test failed and the clear colour showed through decals and shadow blobs as black specks. Only on real GPUs - a software device compiles every pipeline alike - and only in the raster renderers: the compute renderer derives a depth once. The reference renderer (tools/rend/refsw) says how the chip has it: 1/w as a 32-bit float, interpolated by a plane equation in screen space, compared once in the ISP; the layers of the scenes with the specks are thousands of units apart, so they were never confused. Now the depth is the vertex position's, by the fixed functions, and no fragment shader writes one. A vertex goes out as (x*w, y*w, S, w) with w = 1/z, so the depth the device interpolates is S*z: the chip's 1/w, linear in screen space, in the D32 float depth buffer with larger nearer (reversed Z, the viewport's depth range 0 to 1), the eight depth modes the device's own compare operations as before. S is 1/fZ_max, the parse's; a frame decoded on the GPU has fZ_max only on the GPU, so the decode writes 1/fZ_max into a word of its frame (TAG_O_ZSCALE), and the drawers copy that word over the host's into the vertex uniforms before the render pass - nothing read back. The vertex uniforms are a small buffer of their own for that, one an image, written before the render pass begins (the main buffer's layout is only known after). The vertex shaders clamp the position's z to w, so a z beyond fZ_max is at depth 1 and not clipped; the matrix's z scale (ExtraDepthScale) is undone for the depth by a second uniform. One text, shaders/position.glsl, places the vertices of every vertex module (polygons, modifier volumes, the per-pixel renderer's), from the same uniform block, and declares gl_Position invariant: the depth pass, the colour pass, the shadow redraw, the prepass and the modifier volumes are placed identically by construction, so EQUAL holds. Where a fragment shader keeps a depth in the per-pixel lists or compares one (the translucent modifier volumes, the lists' manual depth test) it is gl_FragCoord.z, the same value the depth test took; fog keeps gl_FragCoord.w. The modifier volume fragment shaders are empty but for the final pass's tint. With no depth written by a fragment shader, early depth testing works in every pass; on lavapipe the per-triangle renderer's frames are 10-25% less GPU time, the per-pixel renderer's 10-35% (Jet Grind Radio, three scenes). Ties stay as they were: draw order (opaque: later wins on >=; sorted translucency: the stable sort), as refsw does. raster_spv.inc made again (tools/rend/kernels_spv.sh), the GPU-side TA kernels too for the new word; tools/pvr/ta_gpu_test compares the word with the parse's 1/fZ_max. tools/rend/depth_test.sh shows the mechanism on a device (lavapipe): a floor drawn and drawn again by another pipeline with EQUAL misses no pixel with the position's depth, nor with the old fragment depth while both passes round alike, misses them all when the second pass's fragment depth is a unit off (what the driver did), and misses many when the second pass's position is a unit off (why the position is one invariant text); and it reads the shipped modules: every polygon vertex module declares its position invariant, none writes gl_FragDepth. On lavapipe the pictures of Jet Grind Radio's select screen, train and street scenes (per-pixel and per-triangle, TA processing on the CPU and on the GPU, the validation layer on) are the same pixels as before: the new depth orders every pixel as the old one did there, and so is as close to refsw as before (per-pixel: mean 4.29, 3.11, 5.37; the compute renderer 3.55, 2.92, 5.19). What changes on real GPUs - the specks - is to be seen there. |
||
|
|
a963f1f16d |
vulkan: the TA decode's frame words filled without two writes to one word
Each frame decoded on the GPU starts with its output words filled: all of them with zero, then the first, fZ_max, with 1.0. Two transfer writes to the same word with no barrier between them are not ordered: a GPU that runs the two fills at once can leave fZ_max at zero. The Khronos synchronization validation reports it on every frame (SYNC-HAZARD-WRITE-AFTER-WRITE, vkCmdFillBuffer). fZ_max is now filled alone and the zeros start at the word after it: the same values, no word written twice, one fill command as before plus none. Tested on lavapipe with synchronization validation on, Jet Grind Radio's character select, TA processing on the GPU: the hazard was the only message before, and there is none after. |
||
|
|
4ad3ad4316 |
pvr: the depth prepass leaves out on the GPU the strips no vertex makes opaque
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. |
||
|
|
b5a3caa3ae |
vulkan: the depth prepass of sorted translucent polygons, GPU decode too
The prepass of rend/sorter.h for the Vulkan per-triangle renderer, by triangle and by strip, and for frames whose vertices the GPU decodes and whose triangles the CPU sorts (TA_GPU_SORT). Its pipelines are all made with the render pass, never in the middle of a frame: one fragment shader that works out a polygon's alpha as the colour pass's does - the same expressions in the same precisions, with what that one has as macros read from the push constants (where the colour pass has its trilinear alpha, which no polygon of the prepass needs) - made textured or not, flat or Gouraud, and at each SSAA rate; pipelines for each of those and each culling, depth test and write on, no colour writes. The push constants the colour pass left are put back after it. Pipelines made (PipelineManager::CreatePrepassPipelines(), for the screen's and the render-to-texture pass): prepassPipelines[textured] [Gouraud][no culling, front, back][per-sample rate], 12 without SSAA and 24 with, from 4 (8) prepass shaders (ShaderManager::GetPrepassShader()). They are not made again when the way of sorting changes. GPU decode: ta_gpu_prepass(), the sorted passes' triangles grouped as GenPrepass() does, from their headers (no vertex alpha looked at: the vertices are the GPU's), their indices after the sorted ones in the decoder's room, which is twice what it was. |
||
|
|
35866c21d6 |
vulkan: the renderer and GPU-side TA processing set up between frames, not in the first one
The renderer was made in rend_frame(), on the first render after the context was made, and GPU-side TA processing was set up in its Process() for that same frame: about half a second of the main thread inside retro_run() on lavapipe, most of it making the 14 pipelines, the rest the kernels, the buffers and the texture kernels. With the option switched on while a game ran, the same in the next frame drawn. - retro_vk_context_reset() makes the renderer once the context is made (rend_prepare_renderer(), what rend_frame() did, which it still does where nothing has). The machine is not running there. - GPU-side TA processing is set up with the renderer (BaseInit(), for its context and again for a change of size) and when the options change: retro_run() hands a change of options to the renderer between frames (Renderer::OptionsChanged()), before the machine runs, and makes a renderer the options have changed again there too. With the option off it is never made; where the device cannot do it, it is found out before a pipeline is made, as before. - TaDecoder::BeginFrame() makes nothing: a frame it is not ready for is parsed on the CPU. A texture pool of another size takes it down. - Setting it up is logged with the time it took. On lavapipe (Jet Grind Radio, per-triangle, TA processing on the GPU), the first frame drawn takes 0.24 s of retro_run() instead of 1.06 s; the setup (0.85 s) is in the context reset, before the game's first frame. |
||
|
|
553edfcf75 |
vulkan: the TA decode's and the textures' kernels as SPIR-V made when the core is built
The GPU-side TA processing's 14 kernels and the 3 that make textures on the GPU were compiled by the core itself, with glslang, from the text of rend/ta_gpu.h and rend/texdecode.h, every time the renderer set them up: 30 to 110 ms of the main thread on lavapipe, on top of making the pipelines. They are SPIR-V now, made from the same headers by tools/rend/kernels_spv.sh (glslangValidator -V -g0 --target-env vulkan1.0: what the core's glslang made of them, SPIR-V 1.0 without the names) and committed as core/rend/ta_gpu_spv.inc and core/rend/texdecode_spv.inc, in place of the headers' text. The renderer makes its shader modules from the words; nothing is compiled when the core runs, and a build of the core needs no glslangValidator. The files say which glslangValidator made them and the SHA-1 of each kernel's text. "kernels_spv.sh check" compares those sums with the headers as they are, which needs no glslangValidator, and with the same glslangValidator every word too. tools/pvr/ta_gpu_test.sh and tools/rend/texdecode_test.sh run it where they compared the text before, their "update" makes both files again, and they compile the kernels they test the same way, so that what they test is what the core has. The pipelines go through the context's pipeline cache, as they did (vulkan_pipeline.cache in the system directory's dc folder, loaded when the context is made and saved when it goes). |
||
|
|
65295bfb80 |
vulkan: the per-triangle renderer's sorted passes and shadows from the GPU
With TA Processing = GPU, the per-triangle renderer now asks the header pass for TA_GPU_SORT (unless translucent polygons are sorted by strip) and TA_GPU_SHADOWS: - Sorted passes: the CPU's sort writes its indices straight into the TA decoder's room for them (one slice a texture-pool slot, host-visible, made at init; a slot is written again only after BeginFrame() has waited for its fence, which the draws that read it were submitted with). DrawSorted() binds that slice as the index buffer and draws the runs as it draws GenSorted()'s, with the GPU's vertices, and binds the decode's indices back. No vertex or index of the frame is uploaded except the sorted indices themselves (12 bytes a triangle). - Shadows: DrawModVols() takes the volumes' bounds from the header pass and scissors the second drawing to them as for a CPU frame, and DrawShadowed() draws each group by the second command TAG_K_SHADOW wrote, which has no indices where the group does not reach into the volumes. - A frame decoded on the GPU with a sorted pass always draws it from the CPU's sort (it never falls to SortPParams(), which needs the CPU's vertices). The decoder's command buffer doubles for the second commands, and the decode records TAG_K_SHADOW after the draw commands. The per-pixel renderer does not ask for either and is unchanged. |
||
|
|
f153f936ec |
vulkan: both raster renderers draw from the GPU's TA decode
With TA Processing = GPU, a frame the GPU decodes is no longer also parsed on the CPU: the header pass makes the render context ready to draw in the full parse's place, and the per-triangle and per-pixel renderers draw it from the decode's vertex, index and modifier volume buffers with indirect draws whose counts the GPU wrote. Nothing is read back, nothing is uploaded for it from the CPU, and nothing is allocated or compiled while a game runs. The header pass (ta_gpu_prepare(), TA_GPU_DRAW): - The polygon parameter lists hold a draw group each, in the lists' order. For opaque and punch-through, a group is what make_index() joins, and its first is the slot of its draw command. For translucent, a group is a run of headers in a render pass that are drawn the same way; its strips stay separate strips, and its first is its place in the translucent draw records: a count word, then room for a draw a vertex. The render passes count the groups. The modifier volumes and the clipping rectangle are the header pass's; fZ_max is the GPU's, and the Vulkan renderers do not use it. - A frame the full parse would drop for want of room is left to it, since only the CPU can keep the last picture on the screen. That covers vertices, modifier volume triangles and volumes exactly, and indices by at most four a vertex. Polygon parameters are bounded by a strip a vertex, or, where that is too many, by counting the strips' end bits, one word a vertex. - Also left to it: frames the parse skips (frame skipping), the flat strips and the region table, and a parameter that comes where a strip has not ended (the parse takes it for a vertex, the decode for what it is). With TA_GPU_NO_SORT, so are frames that may have a sorted pass. The decode (rend/ta_gpu.h) gets two kernels: - trdraw: each translucent strip's record copied to its group's place, with the group's count, and its polygon parameter's words for the per-pixel renderer. With TA_GPU_INSTANCE, firstInstance is the strip's place in the list. - provoke: the colours of a flat-shaded triangle's last vertex moved to its first, as SetProvokingVertices() does on the CPU, in its order of reads and writes. The drawers (BaseDrawer::BindGeometry(), DrawIndices()): - Opaque and punch-through groups: one vkCmdDrawIndexedIndirect() of the group's command. - Translucent groups: one vkCmdDrawIndexedIndirectCountKHR() of the group's records. Without the extension, one multi-draw of the group's whole room, whose records past the count are zeroes. A device with neither keeps TA processing on the CPU. There are never more calls than the CPU path makes draws, and consecutive strips drawn the same way are one call. - The per-pixel renderer reads the translucent polygon parameters from the GPU's buffer and numbers each strip by its draw's firstInstance. Without drawIndirectFirstInstance it takes TA_GPU_NO_SORT. - The per-triangle renderer sorts translucent polygons on the CPU from the CPU's vertices (GenSorted(), SortPParams()). It takes TA_GPU_NO_SORT: a frame whose region array has any sorted pass is parsed and sorted on the CPU as ever. Sorting on the GPU is S1's. - Shadows (the per-triangle renderer's second drawing of polygons in a modifier volume): the volumes' triangles are not on the CPU to bound, so every shadowed group is drawn again over the whole frame, and the stencil limits it to the same pixels. The decode's outputs gain the translucent draw records (a vertex's worth plus a header's of 20-byte records) and the translucent polygon words. The decode's barrier waits for the fragment shaders as well, since the per-pixel renderer reads those words there. The log gives why frames were parsed on the CPU, and the pool's counts. tools/pvr/ta_gpu_test also checks: - the provoking vertex against the CPU's SetProvokingVertices(); - the translucent draw records and polygon words against the parse; - the frame made ready to draw against the parse, group by group; - the overrun verdict at the exact boundary, against the parse run with the same room and with one less. On lavapipe with the validation layer: the T2 corpus byte for byte; RetroArch screenshots the same with TA Processing CPU and GPU, both renderers, threaded rendering on and off. |
||
|
|
846046a16b |
pvr: spare TA contexts for a full GPU pool, made with it
With TA Processing on the GPU, a frame's data goes into a slice of the renderer's mapped buffer, and a slice is written again only once the GPU work that read it is done. If every slice was taken, the frame got a context of its own memory: taken from the spare contexts if there was one, and otherwise allocated there and then, in the middle of a game. Now nothing is allocated for that: - Attaching the pool also makes one spare context of ordinary memory (or keeps one that is already spare), for the frame that finds every slice taken. That frame is drawn from the CPU's parse, as before. - The renderer makes up to four more slices than frames in flight + 4, as many as a thirty-second of the memory budget left (vram_budget.h) allows. - Only a game that writes to more frames at once than the slices and the spares together still gets a context allocated, as it would without the pool. That is counted, and said once in the log. tactx_gpu_pool_stats() gives the two counts (frames that found the pool full, contexts allocated) for the renderer's log. |
||
|
|
ebbfd8211f |
vulkan: the TA decode on the GPU, behind TA Processing
A new core option, TA Processing (reicast_ta_processing: cpu, the default, or gpu; Vulkan builds only), has the Vulkan renderers decode each frame's tile accelerator data on the GPU (rend/ta_gpu.h). - At its first frame with the option on, TaDecoder makes everything: a pool of TA context slices (frames in flight + 4) in one persistently mapped buffer of host-cached memory, given to the TA side; a ring of tables, one piece per frame in flight; the outputs on the device, one copy, sized for the largest frame the machine has (vertices, indices, modifier volume triangles, the records, the draw commands, the work buffer); the eleven kernels; the descriptor sets; two timestamp queries per frame. A device without host-cached memory, or whose limits are too small, keeps TA processing on the CPU. - Each frame: the header pass writes the tables before the full parse; the decode is recorded into a command buffer of the texture pool's, submitted with the frame before its draws: clears of three small ranges, then the kernels with a barrier between each, the strip- counted ones dispatched indirectly from counts the GPU wrote. Nothing is read back; the GPU timestamps are read once their frame is known to be done, without waiting. - The texture pool's fences are polled each frame to say how far the GPU has got, so that the TA side reuses a slice only after the work that read it. CommandPool gains IsDone(); an Allocation can say what kind of memory it is. - Turned off while a game runs, the pool is detached and the buffers go once the frames that may read them are done (DeferDelete()). Until the raster renderers draw from the GPU's buffers (the next stage), every frame is still parsed on the CPU and drawn from that, so with the option on the picture is the same and the decode only runs: the option is for testing and measuring for now. Off, the renderer makes nothing and pays one call a frame. |