aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/texture_cache
Commit message (Collapse)AuthorAgeFilesLines
* video_core: NVIDIA DLSS through NVIDIA Streamline in place of a game's DLSSDuck91213 hours1-2/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Adds a Graphics setting, "Use NVIDIA DLSS in place of a game's DLSS" (nvidia_dlss, off by default). When it is on, in Windows builds configured with EGLANTINE_STREAMLINE_SDK, the evaluation Eglantine already replaces with FSR 3.1 goes to NVIDIA DLSS on GeForce RTX cards, with the same inputs (colour, depth, motion vectors, jitter). When DLSS can't run, FSR 3.1 runs as before. Only Streamline's MIT-licensed headers are compiled in; nothing of NVIDIA's is linked or shipped. At run time Eglantine looks for sl.interposer.dll in the user folder's "streamline" folder (or EDEN_STREAMLINE_DIR, or "streamline" next to the executable), checks its Streamline signature and starts Streamline with manual hooking. Streamline's plugins come from that folder or the NVIDIA app's downloads. DLSS itself is nvngx_dlss.dll in the same folder, else the newest DLSS 310 or later that the NVIDIA app has downloaded, checked for NVIDIA's Authenticode signature and copied as streamline/nvidia-app/nvngx_dlss.dll. Like other tools that call DLSS outside a game, it uses its own fixed project id with the custom engine type. - Preset K for all modes (EDEN_NX2_DLSS_PRESET overrides it): the default Performance model (M) and L draw thin dark horizontal lines in some frames at 2x resolution scale; K doesn't. - HDR input with auto exposure; the mode follows the render/output ratio. - Buffer device address stays enabled when DLSS needs it, for DLSS only. - The upscaler waits for the device instead of submitting when it is destroyed: by then the game's channels are released, and a submit read the state tracker's stale flags (a crash when stopping a game). With the setting off, or in builds without the SDK path (the setting is hidden there), nothing changes. Testing: Windows 11, RTX 5080, Fire Emblem: Fortune's Weave docked at 1x (720p -> 1440p) and 2x (1440p -> 2880p), CLI runs and GUI play, stopping and restarting games with DLSS on. Not tested on other GPUs or games. Assisted-by: Claude Opus 5.5 (implementation, debugging) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
* video_core: cut per-draw work on the GPU back thread in crowded scenes (#374)Duck23 hours2-3/+13
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * video_core: demote the texture cache's per-draw debug logs to trace The invalid sampler and image view index messages fire for unused binding slots on every draw. Filtered LOG_DEBUG messages still cost a call and argument packing: in a Fire Emblem: Fortune's Weave city scene (about 2,100 draws per frame) they were 3.1% of the GPUBack thread's samples. LOG_TRACE compiles them out of release builds. Assisted-by: Claude Opus 5.5 (profiling, porting and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> * texture_cache: read a descriptor with one page lookup DescriptorTable::Read runs for every texture and sampler of every draw, and it read each 32-byte TIC/TSC entry through the generic ReadBlockUnsafe page walk. In a Fire Emblem: Fortune's Weave city scene (about 2,100 draws per frame) that was 5.0% of the GPUBack thread's samples. An entry within one page is contiguous in host memory, so it is now a single page lookup and memcpy. Entries that cross a page and pages without host memory still take the block read, so unmapped and unbacked entries still read as zero. The same change in another Eden-based build cut these reads from 6.4% to 0.4% of its GPU thread. Assisted-by: Claude Opus 5.5 (profiling, porting and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com> --------- Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
* Merge remote-tracking branch 'origin/nx2' into fe-fsr31Duck91231 hours3-7/+15
|\ | | | | | | | | | | # Conflicts: # src/video_core/renderer_vulkan/blit_image.cpp # src/video_core/renderer_vulkan/blit_image.h
| * Merge pull request #295 from rdenno/fix/texture-x8z24-componentsJonas31 hours1-0/+1
| |\ | | | | | | texture_cache: look up X8Z24 with UNORM, UINT, UNORM, UNORM components
| | * texture_cache: look up X8Z24 with UNORM, UINT, UNORM, UNORM componentsrdenno43 hours1-0/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | X8Z24 was only known with UINT in the last three components. Mario Tennis Fever samples its depth buffer as X8Z24 with components UNORM, UINT, UNORM, UNORM (as S8Z24 already is in the table), which hit the "texture format=42" fallback to A8B8G8R8. The depth image was then copied into a colour image of the wrong format every frame (with "Unimplemented" layer-count asserts) and read as colour, and distant scenery around the court (the arch and the castle stadium) wasn't drawn. Map it to X8_D24_UNORM like the other X8Z24 entry. Assisted-by: Claude Opus 5.5 (diagnosis, fix and testing)
| * | Merge pull request #334 from snowstormxd/codex/upstream-mkw-testJonas32 hours1-6/+12
| |\ \ | | | | | | | | MKW performance improvements
| | * | texture_cache: clear only exposed descriptor bitmap wordssnowstormxd36 hours1-4/+9
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Avoid zeroing unused preallocation during invalidation and pool refresh. Clear newly exposed words whenever limits grow, bound clearing to allocated storage, and test word/allocation boundaries, shrink/regrowth and malformed large limits. Assisted-by: Codex (implementation, review, commit text and automated validation) (cherry picked from commit 4bc1ae01f79cef43be016504c850ca8a6998457a)
| | * | tests: verify deferred descriptors against eager invalidationsnowstormxd36 hours1-2/+3
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Exercise 40,000 mutations and table switches plus repeated deletion across five channels. Make the existing descriptor read work with a memory reader fixture and add an explicit CPU benchmark with opaque invalidation calls. Assisted-by: Codex (implementation, review, commit text and automated validation) (cherry picked from commit 3b62a551da8ec0b4edf19a1c4e14fa2519dd20e2)
| * | | video_core: read texture handles as header offsets only on Switch 2Sssnowyyy35 hours1-1/+2
| |/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 049c9633ed made TextureHeaderIndex take index / 32 for any texture index past the pool that is a multiple of 32, in VisitImageView and in the shader environment. That was written for Switch 2 handles that hold a header byte offset, but it ran for Switch 1 games too, where such an out-of-range index used to give a null view (and fail the shader environment's range check) and now binds an unrelated header. Pass whether the memory manager uses native tiling, which is how Switch 2 titles are set up, and keep the previous behaviour otherwise. Follow-up to 049c9633ed. Assisted-by: Claude Sonnet 5.5 (implementation and commit message)
* | | video_core: replace Fortune's Weave's DLSS with AMD FSR 3.1Duck91233 hours2-8/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fire Emblem: Fortune's Weave's DLSS network can't run here, and the temporal stand-in is only an approximation of it. FSR 3.1 takes the same inputs DLSS does: the jittered colour, motion vectors, the jitter and the depth, which the game's first DLSS pass reads through cbuf0+0x298 (Z32, reversed, the colour's size). Kepler compute keeps it next to the motion vectors. Fsr3Upscaler ports the host side of the SDK's ffx_fsr3upscaler.cpp: the eight passes (prepare inputs, luma and shading-change pyramids, shading change, reactivity, luma instability, accumulate, RCAS), the internal images and the constants, recorded through the scheduler with the shaders from externals/FidelityFX-SDK. It takes HDR colour, render-size motion vectors and reversed depth, computes its own exposure, and uses the no-wave SPD variants on devices without compute quad operations. The SDK's GLSL uses separate textures and samplers, so DescriptorBankInfo gains sampled image and sampler counts (default 0). FSR's RCAS follows the RCAS Sharpness slider. The result goes to FSR's own RGBA16F image and is drawn into the game's output, which keeps its format. TextureCacheRuntime::TemporalUpscale runs FSR 3.1 first. Without the depth, at mismatched scales, on a device lacking FSR's formats or features, or if its pipelines fail to build, the stand-in runs instead, and the bilinear blit stays last. EDEN_NX2_DLSS_REPLACEMENT=standin forces the stand-in. A gap of more than 250 ms between evaluations restarts the history. The BlitColor overload that takes a raw image view transitions its source to SHADER_READ_ONLY_OPTIMAL but described it as GENERAL in the descriptor (a validation error); it now describes the layout it uses. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
* | | video_core: sharpen the temporal DLSS stand-inDuck91233 hours1-2/+18
|/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fire Emblem: Fortune's Weave's DLSS input matches Eden's (same 720p image, same detail), but the stand-in's 1440p output was blurrier than a plain bilinear 2x of it (Laplacian 0.0066 vs 0.0081; Eden's real DLSS 0.0111), and the image drifted darker over time: - the sample kernel was about twice too wide for the output resolution; - the history was kept in the output's B10G11R11 format, whose 5-6 bit mantissas round away a frame's 0.03-0.1 share; - the 1-sigma neighbourhood clip removed detail that only some jitter positions sample (railings, thin edges). The history is now an RGBA16F pair the shader writes as a storage image, with no per-frame copy. Still pixels use a third of the kernel width and a 1.5-sigma clip, so detail from many jitter positions accumulates. Moving pixels keep the old kernel and 1-sigma clip, and so do pixels that moved recently or whose history falls far outside the neighbourhood (content that changes without motion vectors), remembered in the history's alpha. Static output goes from 0.0066 to 0.0102 with no brightness drift and no new ghosting. With resolution scaling the stand-in used to fall back to a bilinear blit. The images are now scaled up as BlitImage does, the jitter is scaled to host pixels and the history follows the framebuffer's render area. RCAS (temporal_sharpen.frag) sharpens a copy of each result, never the history, in range-compressed space since the output is HDR. Its strength is the sharpness slider, relabelled "RCAS Sharpness" (0% turns it off). EDEN_NX2_TEMPORAL_KERNEL, _CLIP, _KERNEL_MOTION, _CLIP_MOTION and _WEIGHT tune the filter (all four filter values at 1 give the old one), and EDEN_NX2_TEMPORAL_SHARPEN=<stops>|off overrides the slider. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
* | Merge nx2 into nx2-experimentalkraibse41 hours2-2/+88
|\ \ | |/ |/| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Resolved by area: - audio feature_support.h: nx2's native revision handling. - fiber.cpp: ours, which also drops the unused rewind stack. - host_memory.cpp: ours granule bitmap and HintCold, plus nx2's fallocate hole punching per whole dirty granule on Linux. - texture_cache.h: nx2's old-age eviction pass, then our Collect(). - kepler_compute.cpp: shared upscale slots, then Fortune's Weave's, then the Kirby Air Riders scan. - nx2_gsv.h: ours, plus nx2's GetBufferMaxResolution and graphics device ids (1-based). - nvhost_ctrl_gpu.cpp: engine support flag bits 40-44. - lower_to_maxwell.h: ours as the base, with nx2's seven lowering commits applied one at a time. Second texture descriptor buffers are read bindless in TEX, TLD.SCR (level zero) and SUST; FSEL keeps its operand modifiers; TXD takes any channel mask. lower_to_maxwell.h lowers 837 Fortune's Weave and 1183 Mario Kart World shader dumps with the same rejects as nx2 and ours (12 and 0). Assisted-by: Claude Sonnet 5.5 (conflict analysis and drafting)
| * Linux and AMD: BC3 flashing on RADV, ASTC decode stalls, lower RAM usage, a ↵Whovian6447 hours1-2/+14
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | buffer cache crash (#260) * host_memory: punch holes in the backing on Linux instead of zero filling freed memory Freeing guest memory zero filled the memfd backing with memset, which commits every page it touches. fallocate(PUNCH_HOLE) hands the pages back to the kernel instead, and they read as zero afterwards. Only the partial pages at the ends are still cleared by hand. Mario Kart World on Linux (RX 9070 XT, 31 GB): peak RSS 23.1 -> 15.9 GB. EDEN_PUNCH_HOLE=0 restores the memset. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * fiber: don't zero fill the stacks, untouched pages stay unbacked The value initialised stack arrays were zero filled when each fiber was made, which commits the whole stack. Left uninitialised, the pages a fiber never touches stay unbacked. Together with the descriptor ring change, about 1.2 GB less RSS in Mario Kart World. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * vulkan: don't zero fill the descriptor update ring Same as the fiber stacks: make_unique value initialised the whole ring for every frame in flight. Entries are always written before they are read, so allocate it without initialising it. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * vulkan: wait for the GPU after an ASTC transcode decode like the other ASTC decodes TranscodeAstcToBcn (astc_recompression Bc1/Bc3 with GPU decode) was the only ASTC decode recorded with wait = false. On RADV (RX 9070 XT, Mesa) Mario Kart World with BC3 flashed heavily all over the screen, while the uncompressed GPU decode, which waits, did not. CPU decode with BC3 does not flash either. Waiting after the transcode removes the flashing; full pipeline barriers around its copy did not, so the wait hides a race somewhere else rather than fixing one in the transcode itself. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * vulkan: submit after an ASTC decode instead of waiting for the GPU by default Every ASTC decode ended with scheduler.Finish(). Over a 19 minute Mario Kart World Grand Prix that stalled the GPU thread 30.7 s in total, up to 1.2 s out of every 5 s while new textures stream in (track sections, race starts), which is where the hitches were. Flushing keeps the ordering that stops the BC3 flashing on RADV without the CPU waiting: no flashing in play, and frames over 100 ms went from dozens to 3 in about 7 minutes. EDEN_NX2_ASTC_FLUSH=0 restores the wait. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * texture_cache: over the expected use, evict costly images unused for 1800 frames first The collector never deletes CostlyLoad images outside aggressive mode, so once a cup moves on, the previous tracks' textures stay resident and the cache sits over its expected use deleting the cheap images still in use. When over the expected use, costly images unused for 1800 frames (half a minute) now go first. Mario Kart World Grand Prix on an RX 9070 XT: frames over 20 ms 8.8% -> 4.0%, 58.7 fps average. EDEN_NX2_TC_OLD_AGE=<frames> sets the age, 0 turns it off. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts) * buffer_cache: no buffer for device addresses past the page table FindBuffer indexed page_table, which covers 2^36 bytes, with any address. Mario Kart World crashed after about 4.5 minutes with a uniform buffer at 0x72253b2f2890 (FindBuffer <- UpdateUniformBuffers <- DrawEnd from a macro), reading far past the table. Treat such an address as no buffer. Where that address comes from is still unknown; this only stops the crash. Assisted-by: Claude Opus 5.5 (debugging, profiling and code drafts)
| * video_core: temporal upscale stand-in for Fortune's Weave's DLSSDuck9122 days2-0/+74
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The one-frame blit left 3D edges aliased and shaking: the game jitters its camera every frame for DLSS to accumulate. Fire Emblem: Fortune's Weave's first DLSS pass (also rejected) has the motion vectors (cbuf0+0x290, R16G16) and the frame's jitter (+0x1b0/+0x1b4, in input pixels). Kepler compute keeps them, and the stand-in for the last network pass now draws temporal_upscale.frag: input samples placed where the jitter put them and weighted by distance, blended with the previous result reprojected along the motion vectors (Catmull-Rom) and clipped to the 3x3 neighbourhood in YCoCg. The history is copied from the result right after drawing it, so post-processing the game later applies to its output does not feed back. EDEN_NX2_TEMPORAL_UPSCALE=0 restores the blit. Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting) Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
* | texture_cache: keep the allocator's block slack out of the usable VRAMkraibse2 days2-2/+22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The collector's usage leaves out the unused part of VMA's blocks, but the driver counts it and a heap at its budget cannot give it to a new image. On an RX 7800 XT that slack was 1.06 GiB, the whole 1 GiB margin: the heap sat at its budget for a minute while the collector saw room, then images stopped fitting and the process died. Reserve the slack (capped at 1.5 GiB) on top of the margin: at once when it grows, slowly when it shrinks, so eviction leaving partly used blocks doesn't chase the threshold down. Assisted-by: Claude Sonnet 5.5 (diagnosis and code) (cherry picked from commit 73fd5a489ea28226fa16a55f5008e622d631b54c)
* | vulkan: don't make small images unbacked when device memory runs outkraibse2 days2-2/+24
| | | | | | | | | | | | | | | | | | | | When VRAM ran out, any image that failed to allocate became a sparse image with nothing bound, which AMD's drivers don't survive being sampled (an RX 6600 died mid-flood, an RX 7800 XT made 211 of them, 8x8 ones included). Try host memory first, and count the images that didn't fit: the texture cache then keeps more room under the budget (512 MiB a step, up to 3 GiB, decaying) so its collector runs earlier. The sparse fallback stays for images too large to back.
* | texture_cache: collect images in the emptiest device memory blocks firstTakoroka2 days2-2/+57
| | | | | | | | | | | | | | | | | | | | | | | | Deleting by age alone left every block partly used: the collector deletes thousands of images a minute and new ones land in the holes, so blocks seldom empty (title 1: ~0.8 GB of holes in ~190 32 MiB blocks after ten minutes, counted by the driver as used). Among the oldest candidates, 8 per image a pass may delete, those in the emptiest blocks go first, so blocks drain and VMA returns them; placement already prefers the fullest blocks. (cherry picked from commit 2f8f0716fafd874cfbb3cc9086141aea61aff2df)
* | texture_cache: cold-hint the guest memory of sparse images tooTakoroka2 days1-2/+9
| | | | | | | | | | | | | | | | | | Images whose guest memory is not contiguous were left out of the cold hint. With sparse tile remaps Mario Kart World maps its own memory into such images, and its game memory in RAM rose by ~1.9 GB in one run. Each mapped piece is now hinted. (cherry picked from commit 97f034615c174178301a4999c334fb39cd03e568)
* | Merge remote-tracking branch 'origin/fix/texture-cache-free-slots' into ↵Takoroka3 days1-0/+2
|\ \ | | | | | | | | | test/linux-all-prs
| * | texture_cache: free the slots of deleted allocations and framebuffersTakoroka3 days1-0/+2
| |/ | | | | | | | | | | | | | | | | Deleting the last image of an allocation erased its lookup but kept its slot, and removing a framebuffer moved it to the deletion queue but kept its slot, so both slot vectors grew with every allocation and framebuffer ever made (title 1: 8.3k allocations and 1k framebuffers made in its first 4 minutes, 5.2k and 600 alive).
* | Merge remote-tracking branch 'origin/fix/tc-view-ref-index' into ↵Takoroka3 days2-10/+27
|\ \ | | | | | | | | | test/linux-all-prs
| * | texture_cache: find a deleted image's view entries without a scanTakoroka3 days2-10/+27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Deleting an image erased the channels' descriptor -> view entries pointing to its views by scanning every channel's map (Mario Kart World: ~5,000 entries, 50-130 us per deleted image, dozens a frame while the collector runs). The cache keeps, per view, the (channel, descriptor) entries that point to it and erases just those, skipping entries of channels that are gone or that point to another view since. (cherry picked from commit c3d22e8e09dacf39000c742d03dfc7517bd6f343)
* | | Merge remote-tracking branch 'origin/fix/tc-count-sentenced' into ↵Takoroka3 days2-1/+41
|\ \ \ | | | | | | | | | | | | test/linux-all-prs
| * | | texture_cache: free deleted images once the GPU is past themTakoroka3 days2-2/+27
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Deleted images kept their device memory for TICKS_TO_DESTROY (8) frames. While the collector deletes dozens a frame that held 1-1.5 GB of VRAM on top of its threshold, which the collector does not count (Mario Kart World on an 8 GB card: 6.6-7.0 GB in use against 5 GiB). On runtimes that report GPU progress (Vulkan) an image is destroyed as soon as the GPU is past it. Title screen, 10 minutes: VRAM in use 6.75 -> 6.34 GB on average, frames over 100 ms 22 -> 15. (cherry picked from commit 8c899541baf398deb892a857a932590f32d2aa4f)
| * | | texture_cache: don't count deleted images still waiting to be destroyedTakoroka3 days2-0/+15
| |/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The collector deletes images into a ring that destroys them TICKS_TO_DESTROY frames later, and until then the driver still counts their memory. Reading the driver's usage every frame, the collector saw no drop after deleting and kept deleting 40+ images a frame in aggressive mode long after it had freed enough: once a session reached the critical threshold, frames took over 100 ms for seconds at a time. Keep the bytes deleted into each slot of the ring and leave them out of the reported usage until the ring destroys them.
* | | Merge remote-tracking branch 'origin/fix/tc-cheap-image-delete' into ↵Takoroka3 days2-1/+19
|\ \ \ | | | | | | | | | | | | test/linux-all-prs
| * | | texture_cache: delete images without walking every channel's tablesTakoroka3 days2-1/+19
| |/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Deleting an image stamped every entry of every channel's view-id map with CORRUPT_ID (validation, which is on in release builds) and cleared every channel's descriptor table bitmaps on the spot. On Mario Kart World's title screen (~5,400 entries in 5 channels) that took 362 us per deleted image, with dozens deleted a frame while the collector runs: ~10% of the time in its frames over 100 ms. The stamp is debug-only now. The tables are invalidated along with it, so every entry is written again before it is read. Invalidate() marks a table and its bitmap is cleared on the next read, once however many images were deleted in between. Same title screen: 3.7 us per deleted image.
* | | Merge remote-tracking branch 'origin/fix/tc-cap-device-memory' into ↵Takoroka3 days2-0/+11
|\ \ \ | | | | | | | | | | | | test/linux-all-prs
| * | | texture_cache: collect from 5 GiB of device memory on every GPUTakoroka4 days2-0/+11
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The collector's thresholds follow the card's size and budget (critical at budget - 768 MiB), so the cache keeps textures of earlier scenes until the card is nearly full: Mario Kart World used 6.7 GB of an 8 GB card, and more on larger cards. Cap the critical threshold at 5 GiB (and expected/minimum below it) whatever the card. Same build, back to back: peak device memory 6.7 -> 5.6 GB, frames over 100 ms 114 -> 64 in six minutes, average frame time unchanged. The EDEN_NX2_TC_*_MIB overrides still apply.
* | | | Merge remote-tracking branch 'origin/fix/nx2-texture-header-offset-handles' ↵Takoroka3 days1-0/+1
|\ \ \ \ | | | | | | | | | | | | | | | into test/linux-all-prs
| * | | | nx2: read texture handles that hold a header byte offsetTakoroka4 days1-0/+1
| |/ / / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Switch Sports' scenery shaders take their 64-bit texture handles from a uniform register, and the texture part of those handles is the header's byte offset in the pool (index * 32), not its index: 0x96e46000 names header 8960 (a 640x152 2D texture), while the plain reading, 286720, runs far past the 33067-entry pool. Eden skipped them at draw time (null view) and built the shaders for a garbage header, so the island's ground, cliffs and buildings drew black. When a handle's index falls past the pool but index / 32 lands inside it, take that header, both when building the shader and when binding the view. Handles that already resolve keep their meaning.
* | | | Merge remote-tracking branch 'origin/fix/cold-hint-uploaded-memory' into ↵Takoroka3 days2-0/+16
|\ \ \ \ | |_|_|/ |/| | | | | | | test/linux-all-prs
| * | | texture_cache: mark guest memory textures were uploaded from coldTakoroka3 days2-0/+16
| |/ / | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Once a texture is uploaded the host GPU has its own copy, and the guest rarely reads that memory again, but it stayed in the emulator's working set. Every 600 frames the guest ranges of images uploaded since (64 KiB or more, not GPU-written or sparse) are marked cold through HostMemory::HintCold, backing and CPU view, from a low-priority worker, so the OS pages them out before anything else when it needs RAM. Their contents stay; using them brings them back. Mario Kart World's title screen at ~10 minutes: game memory in RAM 4.8-5.5 -> 3.6 GB, the emulator's real RAM 10.6-11.9 -> 9.4 GB, frames over 100 ms not worse. (cherry picked from commit ea36b2c5ca352f8c8cdcc829a2d3bde9bdc06ece)
* | | nx2: guard empty and overflowing 216 map requests, log failed whole-backingkraibse3 days1-0/+5
| | | | | | | | | | | | | | | | | | | | | A zero-size request at a region start wrapped last_block and wrote past the region's blocks; the offset + size memory bounds check could overflow. Both are guest-controlled. Also log an error when a sparse tiled image sharing the zeroed block can't be backed whole before a GPU write.
* | | vulkan: share one zeroed block between empty sparse blocks on AMDTakoroka3 days2-0/+29
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | AMD's drivers can't sample images with unbound memory, so large sparse images are backed whole there, Mario Kart World's nine sparse masks included (about 2.9 GB, re-uploaded whole for every tile the game maps). Where the driver supports sparseResidencyAliased, SparseTiles images are now created with VK_IMAGE_CREATE_SPARSE_ALIASED_BIT and every block is bound to one zeroed 64 KiB block when the image is created (the image is cleared once); a block gets memory of its own when data arrives, as on other drivers. Nothing is ever left unbound. If the GPU renders into such an image, every block gets memory of its own first, cleared, and the data is loaded again, so the shared block stays zero. EDEN_NX2_SPARSE_ALIASED=1 uses the shared block wherever it is supported, =0 never. Radeon 780M (proprietary driver 32.0.31041), Mario Kart World title screen, 200 s: no hang or device loss, world lit, shared memory 12.7 GB at the end growing 1.8 GB/min, against 18.7 GB and 5.1 GB/min with the masks backed whole. With every sparse image unbound instead (no AMD fallback at all) the static world renders dark there, so the other large sparse images stay backed whole on AMD.
* | | texture_cache: back large sparse 2D images by sparse blockTakoroka3 days6-0/+315
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Mario Kart World's nine world-sized masks (R8 16384x16384, RG16 and R16 8192x8192, 170-341 MB each) live in sparse regions the game maps its own memory into, a few MiB of each. Since a mapped tile counts as written, the texture cache backed and uploaded each mask in full: about 2.9 GB of device memory, and a whole-mask upload for every tile the game streamed in. Such images (single layer, one texel per block, colour, in a sparse region, 32 MiB or more) are now unbacked SparseTiles images. The texture cache records the guest ranges written to them; Image::BindTiles gives the host sparse blocks under those ranges memory, and only those rectangles are unswizzled, a row of whole GOB blocks at a time, and uploaded. A new image starts with the tiles the game maps there (nx2:gpu 216 now records them per 64 KiB), or the written blocks where it maps none. Unmapped tiles keep their blocks and are loaded as zeros. Checked against the full unswizzle on 400 ordinary game images (row padding, mip chains, odd sizes): no row differs. Not on AMD drivers, which cannot sample images with unbound memory. Mario Kart World title screen, 150 s, against backing the masks whole: dedicated VRAM 6.6 -> 6.2 GB, shared 1.0 -> 0.3 GB, peak 8.2 -> 6.9 GB.
* | | Fix Compilation and crash on MACOSAdya3 days1-0/+4
| |/ |/|
* | 1st pass of review: code cleanup & windows testnéo Luigi4 days1-5/+5
| | | | | | | | Assisted-by: Claude Opus 5.5 (review, cleanup and log analysis)
* | texture_cache: keep Switch 2 VRAM use under the driver's budgetWael3rd4 days2-22/+60
|/ | | | | | | | | | | | | | | | | | Mario Kart World filled the GPU after a few minutes on 8 GB cards, and on 16 GB cards with uncompressed ASTC: textures were created without memory (garbage), then the emulator crashed. The Switch 2 collector thresholds came from the sum of all heap budgets, but on AMD the host-visible device-local heap is the same VRAM, so the critical threshold ended above the real budget and the collector never kicked in. Unbacked images were also counted at their full size. Thresholds now follow the main device-local heap's budget, re-read every 30 frames, and images count the memory they really have. Switch 2 titles on GPUs under 12 GiB re-compress ASTC to BC3 for the session when it is set to Uncompressed (EDEN_NX2_KEEP_ASTC=1 opts out), and EDEN_NX2_VRAM_LIMIT_MIB simulates a smaller GPU for testing. Assisted-by: Claude Opus 5.5 (diagnosis and code)
* texture_cache: log unbacked layers over the image's memory capkraibse5 days1-0/+1
|
* vulkan: bind memory to sparse terrain arrays layer by layerTakoroka5 days3-4/+91
| | | | | | | | | | | | | | | | | | | | | | | The written part of Mario Kart World's sparse terrain arrays is small: 13-26% of their blocks. The two 1.45 GB ASTC arrays were backed in full on the host, and the two 5.8 GB BC6H arrays, too large to back, stayed unbacked, so what the game streamed into them never showed. A color array of 32 MiB or more lying in one sparse region, in a format uploaded layer by layer (huge ASTC arrays decoded on the GPU, or formats sampled as they are), is now a sparse resident image (SparseLayers). When it is refreshed, the layers written since (or, for a new image, the layers any of whose blocks was written) get device memory bound with vkQueueBindSparse (under the submit lock, waited on, as the multi-range buffer cache does) and only those layers are uploaded. Unwritten layers read zero. One image binds at most 768 MiB (384 MiB on GPUs with less than 7 GiB); rendering into one binds all its layers. Mario Kart World, 180 s: the ASTC arrays hold 1.4 MiB per written layer, four BC6H layers (341 MiB) now have their data, device memory in use 4.8-5.0 GB instead of 6.0-7.1 GB with the texture cache below its collection threshold, 16.8-17.2 ms average frames, peak working set 13.4 GB.
* texture_cache: give images over unwritten sparse blocks no device memoryTakoroka5 days2-0/+17
| | | | | | | | | | | | | | | | | | | | | Mario Kart World creates nine terrain layer pools in sparse regions (four R8 16384x16384, four RG16 8192x8192, one R16 8192x8192: 2.9 GB of device memory). In 180 s of title and attract demo not one of their blocks is written and nothing renders into them, so they hold zeros; on the console only the few tiles the game maps have memory. nx2:gpu now publishes which 4 MiB blocks of each sparse region have been written (nx2.h, a leaf lock the texture cache may take). An image of 32 MiB or more lying in one sparse region with no written block is created as a sparse resident image with nothing bound: reads return zero, as an unmapped tile does. The first write into its range, or the first time the GPU renders into it (that range then counts as written), marks it remapped, so a backed image replaces it at its next use. Mario Kart World, 180 s: the collector deletes 0-80 images per 600 frames instead of thousands, frames over 100 ms per 600 presents drop from 1-9 to 0-1, peak working set 14.7 GB. One render target in a small sparse region gets replaced once (logged).
* texture_cache: re-decode only the written layers of large ASTC arraysTakoroka5 days3-2/+125
| | | | | | | | | | | | | | | | | | | Any write into an image marked all of it CPU-modified, so each terrain tile Mario Kart World streams into its 8192x8192x68 ASTC arrays re-read and re-decoded the whole 1.45 GB array: a 2 GiB staging buffer and a full GPU wait each time, 15-16 GB of uploads per 600 frames. Large layered images on the accelerated ASTC path (64 MiB and up) now record the layers written since their last upload and stay tracked; a refresh reads and decodes only those layers, one dispatch per layer and level into a single-layer storage view. The texture cache's 600-frame report counts uploads from guest memory, their size and how many went by layer. Upload scratch buffers grown past 64 MiB for one huge image (341 MiB for the terrain layer pools) are freed at the next frame instead of kept for the session. Mario Kart World, 180 s: after loading, uploads drop from 15-16 GB to 0.2-0.5 GB per 600 frames, the largest from 1453 MiB to 2-15 MiB.
* nx2: log per-call and periodic diagnostic lines at debug levelTakoroka5 days1-4/+4
| | | | | | | | | | | A 9-minute Mario Kart World session wrote 36k log lines at the default level, most of them one per GPU call: nx2:syncpt 201/202 (12k), gpu submit jobs (7k), memory registrations (7k), "Lowered native shader" (1.6k), and 4.7k "Storage buffer tracked without bias" warnings, which every Switch 2 shader hits. These, the texture cache's per-image and per-format detail lines, the 3D image shapes and the per-type VMA lines are now debug level. The once-a-report summary lines (texture cache memory, images created/deleted, VMA heap budget) stay at info.
* texture_cache: don't shadow ImageBase::info in FindViewTakoroka5 days1-2/+2
| | | | | | The clang-cl build fix named the lambda parameter info, which shadows the member. GCC builds with -Werror=shadow (the Linux release workflow) failed on it.
* build: fixes for clang-clTakoroka5 days1-1/+3
| | | | | | | | | - (std::min)/(std::max) where windows.h's macros are visible - LAMBDA_FORCEINLINE: always_inline under clang (clang-cl defines _MSC_VER but rejects [[msvc::forceinline]] on lambdas) - ImageBase::FindView: find_if, as MSVC's STL takes its vectorized find for ImageViewInfo under clang-cl and fails a size assertion - parentheses for -Wlogical-op-parentheses in the Ampere lowering
* texture_cache: count evictions that download; EDEN_NX2_TC_MIN_AGE knobTakoroka5 days2-2/+11
| | | | | | The collector's periodic log now also counts evictions that downloaded the image first (each a full GPU wait). EDEN_NX2_TC_MIN_AGE=<frames> keeps images used more recently than that (a VRAM thrash test on 8 GB: 120 frames made no difference over three runs).
* video_core: less waiting on the GPU caches' locksTakoroka6 days2-1/+10
| | | | | | | | | | | | | | | - The texture and buffer caches' mutexes spin briefly before sleeping (Common::SpinRecursiveMutex): the GPU thread, the fencing thread and CPU write traps take them tens of thousands of times a second for microseconds, and a sleeping waiter's wake-up took longer than that. Title 1's GPU thread slept 7.7% of its time there, now 3.7%. - Fences commit and pop texture and buffer downloads separately, and only when there are any (one fence per compute dispatch in Switch 2 titles): the fencing thread no longer holds both caches while it copies buffer downloads. - Inline uploads and pre-draw invalidation take the caches one at a time again: holding one while std::lock waited for the other waited longer.
* texture_cache: Switch 2 collector thresholds follow the device memory budgetTakoroka6 days2-9/+20
| | | | | | | | | Native titles' thresholds came from Eden's device memory figure, which is capped near 7 GB: on a tester's RTX 4070 SUPER (11.2 GB budget) title 1 kept evicting 800-1,600 textures per 600 frames with 3 GB unused. They now follow the budget (critical at budget - 768 MiB, expected at budget - 1536 MiB, re-read every 60 frames); the EDEN_NX2_TC_* overrides still win, and the thresholds are logged only when they move by 256 MiB.
* nx2: GPU-thread zone timers and an in-process sampling profilerTakoroka6 days1-0/+2
| | | | | | | | | | EDEN_NX2_GPU_PROFILE=1 times the GPU thread's stages (command parsing, macros, draw setup by step, compute, texture uploads) and logs each one's share of every second. EDEN_NX2_SAMPLE=<thread names> (Windows) pauses the named threads about 600 times a second for EDEN_NX2_SAMPLE_SECONDS, walks their stacks and writes per-function counts plus folded stacks (EDEN_NX2_SAMPLE_OUT).