mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-10 03:11:26 -10:00
master
34
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
195f56511a |
Renders to textures: the screen's attachments, and only their own pixels
A render to a texture draws with the screen's attachments - the depth with the per-triangle and per-strip renderers, the colour and depth the per-pixel renderer keeps between passes - made at least as large as a render of a screen, 640x480 times Render To Texture Upscaling (TextureDrawer::ShareExtent()). It draws its own pixels, not the whole power-of-two texture they are in; the rest is cleared black once, when the image is new or was written since (Texture::ClearOutside()). Nothing is made for a render during play, and the video memory is the screen's: no more than before renders to textures had attachments made up front, and up to 16 MB less than with them, at any resolution where the screen is at least as large as the renders. A render larger than the screen has attachments of its own made when it comes, logged. With SSAA or the upscaler the texture drawers keep their own. tools/vulkan/vk_vram.sh: the test disc built with -DLIVE_ODD, a render of no power of two drawn as its whole texture, each raster renderer at 1x, 2x and with 2x renders: every render drew with the screen's attachments, black past it (tools/rend/odd_check.py), validation clean, the image memory at the most printed (vk_hdr VKH_IMAGE_BYTES). In CI. |
||
|
|
b01ee57541 |
Samplers, texture sets and the sort's room made before the first frame
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. |
||
|
|
daa88b139f |
Vulkan: nothing made on the draw path
The start waits, on the pipeline thread, for every fixed pipeline the first frame needs, so no frame makes one or waits for one. A device without VK_EXT_extended_dynamic_state or _state3 has the general pipeline of every fixed state a draw can reach made then too: Prepare() runs Key() over the drawer's draws and every value of the fields it reads. Where they are too many for the start (neither extension), those of earlier runs are made, until the blend is folded. HDR output's post effects are made with it, not in the frame they first turn on. The quads' pipelines are dropped before the drawers' render passes go: the pipeline thread could make one with a render pass already destroyed. tools/vulkan/vk_draw_path.sh runs every renderer on every test disc cold, without each dynamic state and with HDR output, and fails on a pipeline made in retro_run (CI). VKH_HIDE_EXT takes a list. The Direct3D tests give each test disc its own directory: their tracks share names, and every disc ran the last one made. |
||
|
|
b48265ebd6 |
TA parse: C89 lists and strip indices; Vulkan draws from where it parses
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. |
||
|
|
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. |
||
|
|
e675ea22fe |
Vulkan: the compute renderer draws for the temporal upscaler too
The compute renderer's frames go to the frontend's temporal upscaler as the raster renderers' do: drawn at the size it asks for under its jitter (the frame's transform in pixels moved with it), the screen's walk storing each pixel's depth in an image of the frame slot's (pvr_compute.h, PVRC_DEPTH_ON, as the raster renderers' depth buffer has it), and the motion vector pass drawn over that depth in the frame's command buffer, from the frame's vertices and indices in its words. That depth is no attachment: the pass samples it and tests against it in its fragment shader (shaders/mv_depth.frag), with a layout of its own. The depth blocks are Vulkan's: the Direct3D 12 kernels' text is without them, and their bytecode is as it was. tools/vulkan/vk_hdr.sh and vk_sanitize.sh run the compute renderer with the upscaler; vk_hdr takes a depth stored as a float. |
||
|
|
fab5ee678c |
Vulkan: the per-pixel renderer draws for the temporal upscaler too
The per-pixel (OIT) renderer renders at the size the frontend's temporal upscaler asks for, under its jitter, keeps each frame's depth and draws its motion vectors over it, as the per-triangle renderer does. Its depth is one image a frame slot while the upscaler has the frames, stored by the last render pass instead of dropped, with the render passes' framebuffers made for it; SSAA is off then, as the upscaler is the anti-aliasing and reads a single-sample depth. The motion vector pass is one piece of code for both renderers (rend/vulkan/motion_pass.cpp): the pass, its pipelines by cull mode, the vectors' images and the strips' previous positions. While the upscaler is active the Internal Resolution and SSAA core options have no effect, and are hidden. tools/vulkan/vk_hdr.sh and vk_sanitize.sh run the per-pixel renderer with the upscaler, the inputs read back and checked under the validation layer and the sanitizers. |
||
|
|
e7cde6e176 |
HDR: ACES 2.0's tables off the frame; SSAA and the X scaler exact
ACES 2.0's tables (the JMh parameters, the tone scale's LUT and the gamut scales) were made on the frame that changed the peak, the gamut or the surround. They are now made by the HDR worker, like the cube, through the same lock-free hand-over: a slot for each kind of job taken by atomic exchange, a CAS-pushed list of what is done, a stop flag for each job, the worker parked on an eventcount. Nothing waits for it. Until the new tables are done the encode keeps the old ones, and their cube, with the gamut they were made for; before any, the tone scale alone (HDR_DT_ACES2). A fill takes back what the worker finished before it reads the tables, so a block is never half of the old tables and half of the new ones. ACES2_InitSurround() is the reentrant init the worker calls. hdr_dt_finish() builds in place, for the tests. Without threads (TARGET_NO_THREADS) the build is done in place as before. With the write-out off, what SSAA resolved is now encoded from the exact average of its samples, not from the byte the picture rounded it to, and is not dithered. Where something was drawn over the picture after the resolve (the VMUs, the crosshairs), the picture is encoded as before: the encode checks that the picture is the samples' resolve by the shared rule (pvr_resolve_join()). - Vulkan per-triangle and per-strip: the HDR subpass reads the samples as a second input attachment. - Vulkan per-pixel: the last pass also stores each pixel's channel sums (OIT_FINAL_FRAG_S1_SUMS, an R16G16B16A16_UINT image in the OIT buffers' set), which the encode samples. - Vulkan compute: the walk stores the sums too, under a specialisation constant, which is 0 at 24-bit. It does the same for the X scaler's pairs of pixels, at one sample a pixel as well. Renders to a texture have walks of their own that store none. - Direct3D 12 per-triangle: the encode reads the multisampled target (PS_SSAA, hdr_encode_dxbc.inc). The sums are scaled to 16 samples, so one encode pipeline takes any count. Direct3D 12's compute kernels are unchanged: kernels_dx12.sh leaves the PVRC_SUMS blocks out of their source, so their DXIL and sums are as they were. At 24-bit none of this runs. tools/vulkan/vk_hdr.sh gains --exact cases on two new discs built from live_prog.c (live_disc.py --slant and --hscale). The first has a slanted shaded triangle and is run on every renderer at 4x. The second adds the X scaler into a 32-bit framebuffer and runs the compute renderer at one sample a pixel. The old code fails both: it dithers, at most 1.08 of a step off. tools/hdr/hdr_d3d.sh 12ssaa checks Direct3D 12's encode with samples, with an overdrawn strip. tools/hdr/hdr_cube.sh checks the tables: the first fill and a gamut change keep what there is, and both converge on the tables made in place, and no block between is a mix of two sets of tables. vk_sanitize.sh adds the per-pixel and compute renderers with SSAA at scRGB. |
||
|
|
70304fcd02 |
vulkan: the chip's write-out before the VMUs and crosshairs, as Direct3D and OpenGL have it
On Vulkan the VMU screens and lightgun crosshairs are drawn in the frame's render pass (its last subpass), so the write-out pass, recorded after the render pass, filtered and dithered them too. On the console they are not part of the picture at all; Direct3D and OpenGL already draw them after the pass. Now, when the frame has a write-out and there is something to draw over it, the screen drawer ends the frame's render pass, records the pass, and the VMUs and crosshairs go in a render pass of their own that loads the picture (VulkanWriteOut::BeginOverlay()), with a quad pipeline made for it. The render pass, its framebuffers (one per picture) and the pipeline are made with the pictures, when the renderer starts or changes size. Nothing is copied, and nothing is made in a frame. A frame without a write-out, or with nothing over the picture, records what it did before. With the option off, nothing changes. Gates (lavapipe; not a real GPU): RetroArch, VF3tb booted to frame 900 with VMU screen 1 shown (opaque), for the per-triangle and per-pixel renderers: - option off: the same picture as before, byte for byte; - option on: outside the VMU screen (and the line under it, whose filter taps reach it), the option-off picture through the model of the filter and dither; on the VMU screen, the VMU as drawn, unfiltered. No validation errors. |
||
|
|
50eae0541e |
vulkan: a renderer's shaders go after the pipelines being made of them
When a renderer is taken down - a change of Rendering Method, of alpha sorting, the context going - its shader modules were destroyed while the pipeline thread could still be making pipelines of them for its drawers. The per-triangle renderer's drawers kept their pipeline managers until the renderer object was deleted, after Term() had destroyed the modules; the per-pixel renderer destroyed its modules before taking its drawers down. The thread then called vkCreateGraphicsPipelines() with destroyed modules: the validation layer's "module is not a valid VkShaderModule" (VUID-VkPipelineShaderStageCreateInfo-stage-08771), found from the pipeline thread in PipelineManager::BuildPoly() while the libretro thread was in BaseVulkanRenderer::Term(). On a GPU driver that is a read of freed memory. The per-triangle drawers now let go of their pipeline managers in their Term() - a manager going finishes what the thread is making for it and destroys its pipelines - and the per-pixel renderer destroys its shader modules after its drawers. On lavapipe with the validation layer, Jet Grind Radio, 52 changes of Rendering Method (rasterizer <-> compute, so per-triangle <-> per-pixel renderer) each with threaded video on and off: 24 of these errors before, none after. |
||
|
|
2cec1266a1 |
vulkan: the frontend lets go of the picture before a renderer is taken down
Changing the Rendering Method (rasterizer <-> compute), the alpha sorting or anything else that has the renderer made again while a game runs destroyed the old renderer's pictures - the images and views last handed to the frontend with set_image - while the frontend could still draw them. RetroArch draws the last picture again for as long as no new one comes (paused, behind its menu, on a frame the game draws nothing in), and with threaded video its video thread draws frames the core thread has already moved past. The renderer's Term() only called wait_sync_index, which waits for the index the core draws into next and is a no-op in RetroArch's own driver. RetroArch's video thread then built its filter chain's descriptors from a destroyed image view: an access violation in the NVIDIA driver under slang_pass_build_commands, reported on Windows with threaded video on. Before the renderer destroys anything, the frontend is now told to let go of its picture with set_image(NULL) - RetroArch's threaded video draws what it has queued and waits for its own submissions then - and the queue the frontend and core share is waited idle, under the queue lock, for a frontend that only forgets the image. It happens only when a renderer is taken down; no frame waits. On lavapipe with the validation layer and threaded video, switching the Rendering Method while running and while paused, Jet Grind Radio: before, "vkUpdateDescriptorSets(): Invalid VkImageView" from RetroArch's filter chain and a crash at the seventh switch; after, twelve switches with no validation error. |
||
|
|
593d919328 |
vulkan: SSAA's per-pixel lists test a translucent fragment at its samples
With SSAA the per-pixel renderer puts a translucent fragment in its lists once a pixel, with the samples it is seen at, and tested each sample the polygon covers against the opaque polygons' depth there with the fragment's depth at the pixel's centre, which it also kept and sorted it with. Where a triangle only clips the pixel, the centre's depth is the plane extrapolated past the triangle: samples were tested with the wrong depth, and the fragments of a model's neighbouring polygons were sorted before or after each other wrongly at its edges - the dotted line of wrong colours Direct3D 12 drew along the hem of the skater's jacket in Jet Grind Radio (fixed there in "dx12: SSAA, as the Vulkan renderers have it"). Vulkan was not seen doing it only because lavapipe shades a shader that reads a sample of an input attachment at each sample, with gl_FragCoord at the sample; a GPU shades it once a pixel, as asked. Now (vulkan/shaders/oit_sample_depth.glsl): - Where the device has VK_EXT_post_depth_coverage (enabled when the core makes the device), the lists' pass takes the samples the device's own depth test passed: the pass already tests greater or equal against the opaque depth, read-only, and early fragment tests with post-depth coverage put that test before the shader and its result in gl_SampleMaskIn. That is the test the shader did by hand, done by the device that wrote the depth, so it is exact at every sample, and a fragment hidden at all of them is not shaded. - Elsewhere each sample is tested by hand with the depth there: the depth is a plane across the screen, so gl_FragCoord.z and its differences to the next pixels (dFdxFine/dFdyFine, taken before anything is discarded) give it at any sample, from where gl_FragCoord is (the centre when shaded once a pixel; the sample itself where a device shades it at each sample, so lavapipe's depth there is its own, untouched). That plane comes out a few units in the last place off the device's own depth at a sample (5 at most on lavapipe), so a polygon drawn again on the opaque one's plane would lose a fifth of its samples at random; a depth within 16 units of 2^-23 of the opaque one counts as equal. - Either way the fragment is kept and sorted with its depth at the centroid of the samples it is seen at, which is in the triangle; the centre's where it is seen at all of them (the standard patterns are centred), as before. Without SSAA nothing changes: the modules of one sample are the same words (tools/rend/kernels_spv.sh), and at one sample gl_FragCoord.z is at the only sample, the centre, which the fragment exists only for covering: never extrapolated, and to the bit the depth test's (tools/rend/oit_depth_test.sh, the next commit). Two modules more, made when the renderer starts and only on a device with the extension; the pipeline keys are as they were; nothing compiled, allocated or waited for in a frame. Direct3D 11 and the OpenGL 4 per-pixel renderer have no SSAA and keep the centre's depth, which is the sample's. The log says which way the renderer takes: "Vulkan per-pixel renderer: with SSAA, a translucent fragment takes the samples the depth test passed (VK_EXT_post_depth_coverage)". |
||
|
|
eb19604d57 |
vulkan: the compute renderer draws renders to a texture itself
Every render to a texture was left to the raster renderer when Rendering Method is Compute. Now the kernels draw it, with the same frame words as a screen frame, into the very texture the per-pixel renderer would have drawn it in: - The texture is OITTextureDrawer's: the frame from its first pixel to the pixel clip's last, in powers of two, times Render To Texture Upscaling, taken from the texture cache the same way (the code that takes it is now one function both use, ComputeBegin() and NewFrame()). After the render it is locked in video memory and watched the same way (rtt_read.h), so the texture cache, the copy to video memory when the game first touches it, and what a later CPU write over the render does (the texture is made again from video memory, as with the raster renderer) are unchanged. Nothing is read back otherwise. - It is drawn at one sample a pixel, as the per-pixel renderer draws renders to a texture whatever SSAA is. With SSAA on, the kernels for one sample are made at Init() as well; nothing is compiled while a game runs. - The frame's words for such a render: only the pixel clip is drawn (times the upscaling, as the raster renderer's scissor), the rest of the texture is the clear colour as the raster renderer's render pass leaves it, and the transform is the raster renderer's viewport's, that many times larger. - The bins are made at Init() for the largest texture rendered to there can be (1024 x 1024 at 8x), a few words a coarse tile; each frame clears only the part its own tiles use, so a screen frame clears what it did. - The images rendered to are made with storage usage as well when the compute renderer is on (the pool's, TexturePool::SetTargetStorage(), and those the drawer makes outside it), so the kernels write them in place: no copy. When the device cannot write that format from a compute shader such renders still go to the raster renderer, counted under that reason. - The texture leaves the walk in the layout textures are sampled in, made ready for fragment and compute shaders and for the copy to video memory. The log says how many of the frames drawn were renders to a texture. tools/rend/tile_dump.cpp writes renders to a texture too (rtt_NNNNN.bin, TILE_DUMP_RTT, TILE_DUMP_RTT_EVERY), packed as the renderer packs them at the size of their texture, and tools/rend/tile_test.sh draws and compares them with the reference like the screen's frames. |
||
|
|
c829b5e223 |
vulkan: the compute renderer's fallbacks counted by reason, and a kept picture of another size drawn from
Each frame Rendering Method: Compute leaves to the raster renderer is now counted with its reason, and logged at the end with the frames drawn and how many of them were the GPU's TA decode's: renders to a texture, the VMUs or crosshairs drawn over the picture, more render passes than the kernels take, more textures than their slots, a frame too large for the ring or the setup. So a run says how many frames fell back and why. One of them goes: a frame that draws over part of the screen right after the context was made again starts from the picture kept from the context before, which can be of another size, and the raster renderer drew it. The walk now starts such a frame from that picture filtered over the screen (PVRC_FF_LAST_SCALED: textureLod at the pixel's centre with a linear sampler, as the per-pixel renderer's quad samples it); the picture before of the same size is still read texel for texel. The setup also counts the triangles it clipped at the eye (PVRC_ST_CLIPPED, read when the frame's slot comes round, as the other counts are), which the log gives and tools/rend/tile_test compares with the reference's. |
||
|
|
5cc42babf8 |
vulkan: the compute renderer draws the frames the GPU's TA decode made, from its buffers
With TA Processing on GPU (the default) every frame the decode made was left to the raster renderer, so Rendering Method: Compute drew only the frames the decode passed to the CPU's parse. Now it draws them, and nothing of such a frame is parsed, sorted or copied on the CPU. - The kernels read vertices, indices and modifier volume triangles from bindings of their own (7-9). A frame of the parse's binds its own words there, at PVRC_F_O_VTX and the others, as before; a frame of the decode's binds the decode's device-local buffers, read in place. - Such a frame's draw groups have their index ranges only on the GPU. The CPU writes the polygon parameters of the groups (the header pass's, with their textures), the volumes and the render passes, and a table whose entries name each group's indirect command or translucent draw records; a new first kernel, PVRC_K_GPUSRC (one workgroup), counts each entry's triangles from the decode's commands and records (bindings 10-12), keeps them inside the decode's index room (none if it ran out), makes the prefix sum the table's starts, turns the render passes' and the volumes' entries into triangle numbers, writes the frame's count of triangles and chunks, the clip's depth scale from the decode's 1/fZ_max word, and the setup's, scatter's and sort's indirect dispatches. - A translucent draw group is one entry whose triangles are its index range from its first strip to its last; the setup looks up a triangle's strip in the group's records and leaves out (without reading its indices) one that reaches past the end of its strip. The translucent sort is the walk's own, per pixel, so the CPU's sort has no part in it. - The table of starts has a third word, the first index (the decode's, or the parse's polygon's), which the setup now reads. - Ordering: the decode's last barrier is for compute shaders' reads, the next decode's first waits for them, and the compute renderer's own first barrier covers all writes before it. Everything is made in Init(): six more storage buffers in the set (the device has to have nine a stage), the sets' buffers rewritten only when a slot changes between the ring and the decode. Nothing is read back, nothing waits. tools/rend/tile_test now draws every frame twice: as dumped, and made into what the CPU writes for a frame of the decode's (commands for the opaque and punch-through strips, the translucent strips that follow one another with the same parameters as one group of draw records), numbered on the device by PVRC_K_GPUSRC and dispatched from its counts; both have to be the reference's picture bit for bit, and the count of triangles what the groups give. The reference counts the triangles it clipped at the eye. |
||
|
|
70198e6a70 |
vulkan: the per-pixel renderer's fragment buffer a quarter of a layer when Compute draws the screen
With Rendering Method: Compute the per-pixel renderer draws only the frames the compute renderer leaves it (renders to textures, the framebuffer's, GPU-side TA processing's, those with a triangle behind the eye or more textures than the kernels' array), yet its fragment buffer was made for every frame: two layers of the whole picture, 16 bytes a fragment - at 8x internal resolution 629 MB on a card with the room, at 4x 157 MB, where the compute renderer itself takes 17 MB. The buffer is now planned at a quarter of a layer when the compute renderer will draw (the method is Compute and the device has what its kernels need): 79 MB at 8x, and the 32 MB floor at 4x and under. A frame the per-pixel renderer draws that needs more is drawn in bands, as a frame over the buffer always was: the same picture, slower for that frame. Decided where the renderer is set up or made again for a size, never while a game runs; the other per-pixel renderers (Direct3D 11, OpenGL 4) have no compute renderer and are unchanged. Video memory with Compute, before and after, the fragment buffer alone: 1x 32 MB -> 32 MB; 2x 39 MB -> 32 MB; 4x 157 MB -> 32 MB; 8x 629 MB -> 79 MB (where the card's budget allowed the full buffer). The pointer image, depth and colour attachments the fallback frames draw into stay. |
||
|
|
23c3cce66f |
vulkan: the compute renderer, drawn by the per-pixel renderer
With rend_compute_wanted, the Vulkan per-pixel renderer draws the screen's frames with the compute renderer (rend/tile/pvr_compute.h): ComputeFrame packs the parse's frame into a ring slot of host memory, ComputeDrawer records setup, binning, the walk and the clear into the frame's command buffer, writing the picture the per-pixel renderer would draw, which is presented the same way. Buffers and pipelines are made at init from the frame size and samples; nothing is read back. The frames it does not draw yet are drawn by the rasterizer as before: renders to textures, GPU-side TA processing's, the framebuffer's, the VMU and crosshair OSD's, those starting from a picture kept from another context, those with a triangle behind the eye (which the rasterizer clips and the kernels would leave out), and those with more textures than the kernels' array. The flat shading's colours are moved onto the first vertex only once nothing can leave the frame to the rasterizer, which moves them itself. |
||
|
|
068183f906 |
vulkan: per-pixel fragment buffer sized from the screen, and a frame that overflows it is drawn in bands
The per-pixel renderer keeps every translucent fragment of a render pass in one buffer, 16 bytes each. It was as large as the "Accumulation Pixel Buffer Size" option said (512 MB by default), cut down to what the device can bind as one storage buffer. A render pass with more fragments than that dropped the ones past the end: the polygons drawn last lost them. Measured on Jet Grind Radio's character select (charsel.state) at 8x (5120x3840), with the count the shaders already keep, read back as below: the frame asks for 12.1 million fragments, 0.62 a pixel. A 512 MB buffer holds 33.5 million, so this frame does not overflow it on a device that binds 512 MB. On one that binds 128 MB (Mesa's software Vulkan, many mobile GPUs) it holds 8.4 million, and the frame loses its last-drawn translucent sprites outright: the MEW / GUM / TAB names at the bottom of the screen are missing. - The buffer is sized when the renderer starts or the screen changes size, never while a game runs: two fragments a pixel of the screen (rend/oit_size.h), at least 32 MB, at most half of the video memory budget (rend/vram_budget.h) that was left. At 1x that is 32 MB; at 8x 629 MB where the device can bind it. - A device that cannot bind that much as one storage buffer (the lesser of maxStorageBufferRange and maxMemoryAllocationSize) gets it in up to four storage buffers of one size, binding 0 an array of four. The shaders find a fragment's buffer by comparing its index with the first's length: a branch every GPU takes the same way where there is one buffer. - Every render pass's count (the shaders count the fragments that find no room too) is copied out after it ends, into host-visible memory for its frame slot. When the slot comes round again its fence has passed, and the CPU reads what that frame asked for. Nothing waits for it. - A frame that asked for more than there is room for is followed by frames drawn in horizontal bands: each band is all of the frame's render passes, clipped to the band by the scissor, with lists that start empty and need the room of one band's fragments. The count goes up at once and down after a second of frames that would have fitted in fewer. Nothing is dropped and nothing is allocated. - The picture's attachment is cleared by the first band and loaded by the others: a render pass variant (@resumed) differs from the usual one in that attachment's load op and initial layout only, so every pipeline works with both. The last band's final render pass covers the whole picture, as the frame's final render pass always has, for what is drawn after it (the VMU screens, the texture copy). A frame with room for its fragments, which is every frame where the buffer fits, is drawn exactly as before, plus a 4-byte copy for each render pass. Three frames back is the earliest a count is seen, so the first frame or two of a scene that overflows still lose fragments. Tested on Mesa's software Vulkan at 8x with the buffer cut to 128 MB (2 bands) and at 2x cut to 8 MB, without SSAA (2 bands) and with 4x SSAA (15 bands): each picture is identical, to the pixel, to the same frame drawn with room to spare. Without bands the 128 MB picture lost the bottom names. |
||
|
|
24cb5e2e51 |
vulkan: SSAA in both raster renderers
A new core option, SSAA (reicast_ssaa): Off, 2x, 4x, 8x; Off by default. Each pixel of the Internal Resolution is drawn from that many samples and resolved by the shared rule (rend/tile/pvr_resolve.h). It is taken up when the renderer starts and when the option changes, which is a change of size (Resize()); nothing is made, compiled or read back while a game runs for it. How many samples (VulkanContext::SsaaSamples()): the most of those asked for that the device can draw colour, depth and stencil with (ssaa_device_samples()), and then that the video memory budget the context read at start-up has room for (ssaa_clamp_samples()): the screen's colour and depth, per sample, after the per-pixel renderer's lists; nothing per sample for the per-triangle renderer where the device has lazily allocated memory, since its sample attachments are transient. One line in the log says what was asked and what was taken. It needs sample rate shading, which the core enables on the device it makes (VkCreateDevice); a device the frontend made without the core gets no SSAA, and says so. The per-triangle renderer (drawer.cpp, pipeline.cpp): - The screen's and the textures' render passes (MakeSsaaRenderPass()) draw into colour and depth attachments of N samples, transient, cleared and never stored (load CLEAR, store DONT_CARE), and a second subpass resolves the colour into the picture (SsaaResolve, quad.cpp): a triangle over the framebuffer reads each sample through a subpassInputMS, as the unorm8 the attachment holds (packUnorm4x8()), and writes (sum + N/2) / N per channel. Never the device's resolve. The picture is all written by it, so it is not loaded. - Pipelines get the sample count and a rate bit in their key (bit 30): shaded at each sample (sample shading, minSampleShading 1, the inputs "sample") or once a pixel. Once a pixel are the polygons drawn at a single rate (SsaaSingleRate(): sprites, the background, and flat strips where the decoder looks for them); their inputs are taken at the pixel's centre (interpolateAtOffset(v, 0)) where they cover all of a pixel's samples, which is where a frame without SSAA takes them (with more than one sample the device may take them elsewhere), and at the centroid where they cover only some, so that an edge pixel is not shaded from beyond the polygon. Both rates of a key are made together, so SSAA adds no compile of its own to the lazy creation that is there already. - The last picture under a partial render, and modifier volumes, go into the samples; the VMUs and the OSD are drawn after the resolve. - Renders to a texture are supersampled only where their sample attachments cost nothing (lazily allocated memory: tilers), with the attachments made at start-up as large as such a texture can be (1024 x the render-to-texture upscale a side). Elsewhere they are drawn with one sample, as without SSAA: at 4x that preallocation is 48 MB at 1024 x 1024, more than the whole of what brute force (twice the Internal Resolution) adds, and what a game reads back of such a render stays what it is without SSAA. The per-pixel renderer (oit/): - Four subpasses with SSAA (RenderPasses::MakeSsaaRenderPass()): 0 depth and stencil, 1 colour and the translucent lists, 2 the translucent modifier volumes, 3 the picture put together and resolved, in one pass: the lists are read and sorted once a pixel, blended at each sample onto that sample's opaque colour, and the samples resolved by the shared rule. The sample attachments are one image each for all the render passes of a frame: a frame of one render pass never stores them; one that is not the last puts each sample together into them in subpass 2 (a per-sample variant of the same shader) and writes the translucent depth there, for the next render pass to draw over. They are not transient, since such a frame stores them. - Textures are rendered to with one sample a pixel, as without SSAA, with shaders of their own (rttShaderManager): see the per-triangle renderer for why. - A translucent fragment of a sorted pass is put in the lists once a pixel, with the samples it covers and is in front of the opaque depth at in eight bits of its polygon number's word (polygon numbers are under 2^22 then): the lists do not grow by N. Where triangles that share an edge cross a pixel each has a fragment there, with the samples that are its own; small triangles make a pixel's lists longer that way, and the most fragments sorted a pixel goes from 32 to 96 with SSAA (without it, unchanged). Translucent modifier volumes count where they cover a pixel's first sample, so that two triangles of a volume sharing an edge count once. - Stencil and depth are read as subpassInputMS at the sample a shader runs for (or the first one it covers, once a pixel). Shaders without SSAA are built from the same text as before but for the names of their inputs: a frame with SSAA off is drawn as it was (checked against the build before, byte for byte, below). GPU-side TA processing: the GPU's decode does not look for flat strips (it would have to read every vertex on the CPU to choose a pipeline), so with TA Processing = GPU only sprites and the background are drawn once a pixel, and flat strips are shaded at each sample; ta_set_flat_strips() is asked for only when frames are parsed on the CPU. The two give the same picture with SSAA off, and pictures that differ by that with it on. The log, for measuring: the GPU time of the screen's frames (two timestamps a frame, read when the frame's slot of the command pool comes round again, after its fence; never waited for) as mean, 99th percentile and longest at the end (GpuFrameTimer), and the video memory in use then (LogVramUsage()). No change to the save state; run-ahead stays exact. Tested on lavapipe (Mesa's software Vulkan, samples 1 and 4 only: 8x is 4x there, 2x off), RetroArch with the validation layer, over saved and booted scenes (Jet Grind Radio, Sega Rally 2, Virtua Fighter 4, the live test's disc), both renderers, threaded rendering on and off, TA processing on the CPU and the GPU, run-ahead, Internal Resolution 2x: - no validation error in any of them, SSAA on or off; - SSAA off: the screenshots are the build before's, byte for byte; - the live test's disc (2D: sprites and flat polygons, two render passes, two volumes, modifier volumes, a render to a texture, punch-through and sorted translucent polygons) with SSAA 4x: the build before's picture, byte for byte, in every one of the eight ways it was run; - tools/threads/headless.sh, with and without reicast_ssaa=4x: the verdicts and the sound of the build before; ta_gpu_test and texdecode_test pass. GPU time (the log above; lavapipe, so the figures only show the direction), Jet Grind Radio, 175 frames, against brute force - SSAA off at twice the Internal Resolution, the same samples a pixel: - per-triangle: SSAA off 38.0 ms mean (p99 111.1), SSAA 4x 128.1 (p99 174.9), brute force 111.0 (p99 154.6); - per-pixel: off 110.9 (p99 155.7), 4x 472.1 (p99 575.0), brute force 369.1 (p99 441.5). A software rasteriser shades every sample of a pixel as it shades a pixel, has no compressed samples and no faster MSAA path, so SSAA cannot win there; the comparison that decides it is on the NVIDIA machines. Video memory, worked out from the attachments made (lavapipe's heap is the host's, shared): SSAA 4x at 640 x 480 adds 12 MB to the per-triangle renderer (colour and depth of 4 samples, 14.7 MB, in the place of one sample's depth) and 10 MB to the per-pixel one (in the place of its two colour attachments and its depth); brute force adds 15 to 22 MB and 26 to 30 MB (every image at four times the pixels - the two or three pictures of the frontend's ring, depth, and for the per-pixel renderer its colour attachments and list heads), and the frontend's own scaling on top. |
||
|
|
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. |
||
|
|
b04783667e |
rend: empty clip hang, Vulkan swap chain growth, per-pixel discard, clips
- 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. |
||
|
|
5274546c9d |
vulkan: memory a smaller render size no longer needs goes back to the driver
The heap takes device memory in 64 MB blocks and gave an empty block
back only when an allocation found no room anywhere. Turn the internal
resolution up and down again and the blocks the large images were in
stayed with the core for the rest of the session, empty.
Thirty-two frames after the renderer changes size - by when the frames
that used the old images are long done with, and the images freed (two
frames, with Mesa's software Vulkan) - the blocks that are empty are
given back, all but the last of each kind (memory type; linear or not),
so that the next allocation of every kind is still an offset and not a
trip to the driver. vk_heap_trim_spare() is the new call; once, not every
frame: a block that comes empty in ordinary running is left alone.
Run in RetroArch on Mesa's software Vulkan with the test disc, the render
size taken 1x, 4x, 1x, 4x, 1x, 2x by a line of test code since removed:
per-triangle 192 MB reserved at 4x; back at 1x, 14 MB in use and 192
reserved before, "64 MB given back, 128 MB reserved" now,
both times
per-pixel 384 MB at 4x; back at 1x 320 MB, both times (its pixel
buffer stays at the size it grew to)
and the picture at the end is the same file with the trim and without, in
both renderers.
tools/vkheap: the three empty blocks beside one in use go back; with
none in use one of each kind stays, and the next allocation does not ask
the driver.
(Throughput item 4 of the audit, the part of it that can be seen here.
Its budgets per memory heap and a smaller first reservation - 128 MB are
taken at the start for the 14 to 22 MB this disc uses - are not in this.)
|
||
|
|
72b1b708d2 |
vk: a replaced quad descriptor set is destroyed after its frames' fences, not after a count of draws
The quad drawer (the kept picture, the VMU and crosshair overlays) makes a new descriptor set when the image or sampler it draws changes, because the old one may be in use by frames still on their way through the GPU. It then kept the old one for 2 x swapchain + 1 more draws and freed it. A number of draws says nothing about what the GPU has finished: a game that renders several times to one of the frontend's frames gets through that many at once. The old set goes to the command pool the draw's command buffer is of (CommandPool::DeferDelete, as the per-pixel renderer's buffers already do with theirs): it is destroyed when the pool comes round to the same frame slot again, having waited on the way for the fence of every frame submitted before then - this one included, which can itself have bound the old set earlier in the same command buffer. Draw() and VulkanOSD::DrawOSD() take the pool. Run: tools/threads/live.sh on Vulkan with the validation layer (VALIDATE=1, per-triangle renderer, the frontend's threaded video too): passes, no validation error. Virtua Fighter 4 draws the frame it drew. Not counted: how many replacements those runs contain. |
||
|
|
39a506b9b1 |
rend: the screen's picture is kept when the context is destroyed and made again
A game that renders only part of the screen relies on the rest of the picture staying as it was. Here the rest is in images that belong to the graphics context, and going fullscreen, or changing a video setting, destroys the context: the part the game was not rendering came back black and stayed so until the game drew it again - which on a loading screen or a menu can be never. The picture is now fetched when the context is about to go (rend/last_picture.h), and the first render to the screen in the new context starts from it. In all four renderers: - OpenGL, both: read from the framebuffer the frontend gave, and put into the one the first render is about to draw in. (OpenGL ES 2 cannot put it back and is as it was.) - Vulkan, both: copied from the image the last frame was drawn into, made a texture in the new context, and drawn in first where the last frame is drawn in for a render in part. The screen's images are made to be copied from, without the read-back memory other such images get. - The first render to each framebuffer after a new renderer starts from an empty one, as before - unless a picture was kept for it. One read of the picture for each context destroyed, nothing a frame. A kept picture of another size is scaled to the new one's. The test disc renders in part from frame 260 to its end again; the last patch had it stop at frame 320 so that the fullscreen pass did not come to depend on when its toggles fell. That pass now sends three, the later ones while the disc renders in part, and the per-pixel renderers have a fullscreen pass of their own. The core says in its log when it keeps a picture, and the passes look for it. With the putting-back switched off, the fullscreen pass fails on OpenGL and on Vulkan (tried on the per-triangle renderers). With it, the live test passes on gl, glcore and vulkan, both renderers each; the Vulkan passes also with the validation layer on. The headless test passes. |
||
|
|
96c9916c26 |
vk: a render that covers part of the screen leaves the rest of the picture
A game does not have to render the whole screen: with the pixel clip or the region array it can render a part, and the console leaves the rest of the framebuffer as it was. Games do it for loading screens and for menus where one corner moves. The Vulkan renderers draw every frame into a fresh image, cleared, so the part that was not rendered came out black - on the screen for as long as the game went on rendering in part. (The OpenGL renderers draw over the last picture and had it right.) When a render will not cover the screen, the last frame is now drawn into the new image first, whole, and the render goes over it: - per-triangle: in the render pass, before the first polygon, with the quad pipeline the VMU screens are drawn with. - per-pixel: in the colour subpass of the frame's first pass, with a quad pipeline of its own for that subpass, and what the frame had bound is bound again after it. A render that covers the screen - nearly all of them - does none of this, and a framebuffer the game has not drawn to lately still starts empty. The test disc renders only the left 200 pixels of its screen from frame 260 to frame 320, with a white polygon drawn nearest over most of the right-hand side. Everything on the right has to stay as the last whole frame drew it, and the white polygon must not appear. On the old code the Vulkan renderers show the right-hand side black. The live test passes on vulkan, both renderers, with the validation layer on, and on gl and glcore; the headless test passes. Not covered: the part of the picture that is kept does not survive the context being destroyed and made again (fullscreen), on any renderer - it is black until the game renders it again. That is why the disc stops rendering in part after frame 320, before the test's fullscreen pass could look at it. |
||
|
|
a2d967c7ed |
vulkan: wait for the core's own frames before a renderer is torn down or resized
When a Vulkan renderer is taken down, or resized, it destroys what it drew with: framebuffers and their images, pipelines, descriptor sets, the overlays, the command pools themselves. Before taking itself down it called context->WaitIdle(), which is the frontend's wait_sync_index and waits for the frontend's frames; RetroArch's does nothing at all. Before a resize it waited for nothing. The frames that use those things are the core's own, which it submits itself, and the last one or two can still be on the GPU. CommandPool::WaitAll() waits on the fences of everything submitted through the pool, and both renderers call it first thing in Term() and Resize(). It gives up after a second, so a frame begun and never submitted cannot hang it. These are not per-frame paths: a renderer is torn down when the per-pixel option is switched or the frontend recreates its context, and resized when the internal resolution changes. Not reproduced here. The test setup has no way to change a core option while running, and the teardown it can cause, a fullscreen toggle, is one where RetroArch has already let the device go idle. That toggle, three times over under the validation layer on both renderers, is clean and does not hang with the wait in. |
||
|
|
7b081630db |
vulkan per-pixel: growing the buffers leaves alone what frames in flight use
When the picture grows, the per-pixel renderer makes a larger image for its per-pixel list heads. It destroyed the old one and rewrote the descriptor set to name the new one, after a wait that was meant to make that safe: context->WaitIdle(), which waits for the frontend's frames. The frames that use the image and the set are the core's own, which it submits itself and waits for one ring slot at a time. They could still be on their way through the GPU. The old image and the old descriptor set are now retired through the command pool, the way the drawer's other attachments already are, and destroyed when the frames that could use them are done. A new set is allocated for the new image. Nothing waits. |
||
|
|
4bd929c953 |
threaded rendering: make one frame per retro_run, the one non-threaded makes
With Threaded Rendering on, nothing tied the emulation to retro_run. The
emulation thread ran free, held back only by the audio it pushed to the
frontend itself, 512 samples at a time, from its own thread, whenever
that call blocked. retro_run waited up to 100 ms for whatever render
turned up and returned a duplicate frame if none did. A render the game
started while the last one was still being drawn was thrown away. So a
retro_run could last any length, carry a new frame or not, and its audio
arrived some other time from some other thread.
Each retro_run now makes exactly one frame, the same one it makes with
Threaded Rendering off:
- retro_run wakes the emulation thread, which runs the machine up to
vblank and goes back to sleep, as dc_run() does in retro_run itself
when rendering is not threaded;
- when the game starts a render the emulation thread hands it to the
libretro thread and sleeps; that thread reads what the frame takes
from the game's memory (display lists, textures, framebuffer), lets
the emulation thread go on, and draws. A render to a texture, which
ends up back in the game's memory, keeps it asleep until it is done;
- every render is drawn. The emulation thread waits for the one before
to be finished; none is dropped;
- at vblank the frame's audio is sent from the libretro thread in one
batch, together with its video.
The renderers read some PVR registers while they draw, by which time the
emulation has moved on and may have changed them. Each render now
carries a copy of the registers made when the game started it, and the
renderers' files read through that (PVR_REGS_FOR_RENDERER), so they draw
from what non-threaded rendering would have read.
EmuBaton (core/libretro/emu_baton.h) is that handshake. There is no
lock, no sleep and no timeout in it: every change of hands is one atomic
operation and a thread with nothing to do sleeps on an eventcount. A
save state from the frontend's task thread takes the machine between
two frames.
The "synchronous rendering" and "delay frame swapping" options have
nothing left to choose and are hidden.
tools/threads/live.sh runs the test disc for the same 300 frames with
Threaded Rendering on and off and requires the same screenshot, byte
for byte, and the same save state (live_same.py leaves out the host
addresses a state carries). The old threaded path fails that: its state
differs from the non-threaded one. tools/threads/run.sh puts the new
EmuBaton under ThreadSanitizer.
|
||
|
|
c2f735809c |
handle some palette textures on the GPU. vulkan: some linear tiling
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 |
||
|
|
b018b13cfd | recreate the gl/vk texture when loading a custom one | ||
|
|
60a4cf60d1 |
upload all texture mipmap levels to gpu
implemented for gl/gl4/vulkan/oit don't auto-generate mipmaps in vulkan simpler/smaller detwiddle table |
||
|
|
f268376547 | vulkan: VMU screens and lightgun crosshairs support | ||
|
|
bc61520174 |
vulkan: delay destroying in-flight images
Keep in-use imave and image views when the texture is updated. Wait until the graphics queue is idle before changing shared buffers and attachments. Refactoring |
||
|
|
dce6d1c0ba | vulkan: oit renderer |