From da263ecf75a994b1271a24cd75c7dc30542f0245 Mon Sep 17 00:00:00 2001 From: ayo <93222141+countervolts@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:26:51 -0400 Subject: [PATCH] Fix VRAM leak on quality changes and RGBA16 motion vectors; more games - Shim: reuse VRAM interop buffers across features instead of freeing them; ROCm 7.2.x's hipExternalMemoryGetMappedBuffer retains its view twice, so freed buffers never released their memory (~130 MB per quality change) - Bridge: close the exported fd after hipImportExternalMemory - Shim: accept R16G16B16A16_TYPELESS motion vectors and convert 4-channel motion vectors to RG16F on the GPU, keeping VRAM interop (Dying Light: The Beast rendered black) - Harness: D4R_HARNESS_RECREATE=N quality-change cycles, D4R_HARNESS_MV_RGBA16=1 motion vector format - Supported games: TLOU, Control, Cyberpunk 2077, Rift Apart, HZD Remastered, Subnautica 2, Alan Wake 2, Silent Hill 2, Dying Light: The Beast; general notes - README: GPU support section (native kernels only for gfx1101); ignore videos/ --- .gitignore | 3 +- README.md | 19 ++++++- SUPPORTED_GAMES.md | 38 +++++++++++-- tools/d3d12_dlss_harness.cpp | 103 +++++++++++++++++++++++++++++++++-- tools/d4r_nvngx_shim.cpp | 102 ++++++++++++++++++++++++++++++---- tools/wine_nvcuda_bridge.c | 12 ++-- 6 files changed, 249 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 6c6cba3..104ea9d 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ __pycache__/ /Archive.zip /vkd3d-proton.cache .vscode/ +videos/ # Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published) /private/ @@ -32,4 +33,4 @@ __pycache__/ /kernels/out/ *.hsaco *.ptx -/kernels/extracted/ +/kernels/extracted/ \ No newline at end of file diff --git a/README.md b/README.md index 907f5e8..a3573df 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models. [DLSS Ultra Performance demo video](https://cdn.ayois.gay/dlss). -Radeon RX 7700 XT (RDNA3, gfx1101), SILENT HILL Townfall at 2560×1440, the same 62-second street walk for every run, frame rates from MangoHud frametime logs. Every DLSS result is presented in the frame it belongs to (no added latency). +Radeon RX 7700 XT (RDNA3, gfx1101), SILENT HILL Townfall at 2560×1440, every DLSS result is presented in the frame it belongs to (no added latency). | Mode | DLSS 3 CNN (preset E) | DLSS 4 (preset K) | DLSS 4.5 (preset M) | FSR 4 | |---|---|---|---|---| @@ -29,7 +29,20 @@ On the same GPU DLSS starts at a disadvantage: its networks were designed for NV ## Known issues -- Native upscaling can show visible artifacting. +- In some games native upscaling can show visible artifacting. + +## GPU support + +d4r needs RDNA3 (Radeon RX 7000 series), but full speed depends on the chip: the release ships its native kernels only for **gfx1101 (Navi 32)**, and on other GPUs DLSS 4 and 4.5 fall back to the much slower translated NVIDIA code. + +| GPU | Chip | DLSS 3 CNN (E) | DLSS 4 (K) | DLSS 4.5 (M) | +|---|---|---|---|---| +| RX 7700 XT (tested), RX 7800 XT, RX 7700, Radeon PRO W7700 | gfx1101 | full speed | full speed | full speed | +| RX 7900 GRE / XT / XTX, Radeon PRO W7800 / W7900 | gfx1100 | full speed | runs without its transformer (worse image, slower) | about half speed | +| RX 7600 / 7600 XT, RX 7650 GRE | gfx1102 | full speed | runs without its transformer (worse image, slower) | about half speed | +| RDNA4, RDNA2 and older | | not supported | not supported | not supported | + +Only the RX 7700 XT has been tested; the other gfx1101 cards use the same chip. Without the native kernels, NVIDIA's translated DLSS 4 layers produce invalid values and DLSS discards the network's output, so K loses the transformer's image quality. Preset E is unaffected on any RDNA3 GPU. The kernels can be built for gfx1100 and gfx1102 (`D4R_GPU_ARCHS` in `scripts/package_release.sh`), but those builds are untested. ## How it works @@ -68,7 +81,7 @@ ROCm does not need to be installed: the zip includes its runtime (from AMD's Ubu ## Requirements (building from source) -- Linux with an AMD RDNA3 GPU. The native kernels use gfx11 WMMA; they are built for gfx1101 by default (`D4R_GPU_ARCH` for other RDNA3 chips, untested). Without them DLSS still runs through ZLUDA, much more slowly. +- Linux with an AMD RDNA3 GPU. The native kernels use gfx11 WMMA; they are built for gfx1101 by default (`D4R_GPU_ARCH` for other RDNA3 chips, untested). Without them DLSS runs through ZLUDA alone: much more slowly, and DLSS 4 (K) without its transformer (see [GPU support](#gpu-support)). - ROCm with HIP and its clang (tested with ROCm 7.2). - GE-Proton with OptiScaler integration (tested with GE-Proton11-3). - Build tools: a Rust toolchain and git-lfs (ZLUDA), meson and ninja (vkd3d-proton), `winegcc`/`winebuild` (bridge), `x86_64-w64-mingw32-g++` and `clang-cl` (shim), Python 3. diff --git a/SUPPORTED_GAMES.md b/SUPPORTED_GAMES.md index 1efe5db..e7aa511 100644 --- a/SUPPORTED_GAMES.md +++ b/SUPPORTED_GAMES.md @@ -7,10 +7,38 @@ The following DirectX 12 games run DLSS Super Resolution through d4r on the test | SILENT HILL Townfall | Works | Works | Works | | Ghost of Tsushima DIRECTOR'S CUT | Works | Works | Works | | Ready or Not | Works | Works; visible graphical artifacts with hdr enabled | Works | -| | | | | -| | | | | -| | | | | -| | | | | -| | | | | +| Clair Obscur: Expedition 33 | Works | Works | Works | +| Marvel's Spider-Man: Miles Morales | Works | Works; minor artifacts when HDR enabled and while the game is paused | Works | +| The Last of Us Part I | Works | Works; visible graphical artifacts, including with HDR enabled | Works | +| Control Ultimate Edition | Works | Works | Works | +| Cyberpunk 2077 | Works; occasional visual artifacts during gameplay | Works | Works | +| Alan Wake 2 | Works | Works | Works | +| SILENT HILL 2 (2024) | Works | Works | Works | +| Subnautica 2 | Works | Works; visible artifacts at Quality (other quality modes are fine) | Works | +| Horizon Zero Dawn Remastered | Works; stutters | Works | Works | +| Ratchet & Clank: Rift Apart | Works; occasional artifacts in the pause menu | Works; minor artifacts with HDR enabled and occasionally in the pause menu | Works; occasional artifacts in the pause menu | +| Dying Light: The Beast | Works | Works | Works; small visual artifacts throughout | + +General notes: + +- Select DLSS in the game's graphics settings where it is offered. OptiScaler can also feed DLSS from a game's FSR or XeSS inputs, but those are a fallback for games that hide DLSS and can ghost or show a black screen. +- d4r supports DLSS Super Resolution only. Keep DLSS Ray Reconstruction off in games that offer it (such as Cyberpunk 2077 and Alan Wake 2); Control requests it whenever ray tracing is on and crashes. +- Saving settings from the OptiScaler overlay ("Save INI") rewrites `OptiScaler.ini` and resets d4r's values to `auto`, including frame generation (`[FrameGen] Enabled=false`, `FGInput=nofg`, `FGOutput=nofg`) and `[Inputs] EnableDlssInputs=true`. Restore them afterwards, or keep a copy of the original file. + +Game-specific setup: + +- **The Last of Us Part I:** the game's Streamline integration crashes on boot with OptiScaler. Rename `sl.common.dll` in the game folder to `sl.common.dll.bak` and select FSR 3.1 in the game's graphics settings; OptiScaler passes it to DLSS through d4r. + +- **Control Ultimate Edition:** turn ray tracing off. With ray tracing on, the game requests DLSS Ray Reconstruction, which d4r does not support, and crashes when loading into gameplay. + +- **Cyberpunk 2077:** GE-Proton 11-3 loads Wine's own ICU libraries instead of the game's, and the game aborts at launch (`unimplemented function icuuc.dll.u_setMemoryFunctions_65`). Add `WINEDLLOVERRIDES="icuuc,icuin=n,b"` to the launch options (fixed in GE-Proton 11-4). + +- **Alan Wake 2:** with the game's own DLSS inputs, every model shows heavy ghosting. Set `AutoExposure=true` under `[InitFlags]` in `OptiScaler.ini` (or enable auto exposure in the OptiScaler overlay), or select FSR 3.1 in the game instead so OptiScaler passes FSR inputs to DLSS. + +- **Horizon Zero Dawn Remastered:** the game loads `dxgi.dll` only from the Windows system folder, so OptiScaler is never loaded under that name. Rename `dxgi.dll` to `version.dll` and add `WINEDLLOVERRIDES="version=n,b"` to the launch options. d4r's `d3d12core.dll` is still picked up from the game folder. + +- **Ratchet & Clank: Rift Apart:** set `Dxgi=false` in `OptiScaler.ini`. With ray tracing on, OptiScaler's DXGI spoofing crashes the game at startup on AMD GPUs; the `PROTON_FORCE_NVAPI=1` launch option already reports an NVIDIA GPU. + +- **Dying Light: The Beast:** use DLSS inputs; with FSR or XeSS inputs the screen stays black. The game passes its motion vectors as `R16G16B16A16_TYPELESS`, which needs a d4r build from 2026-09-28 or later (older builds reject every frame and show a black screen). Choose a model with `Model = E`, `Model = K`, or `Model = M` under `[DLSS]` in the game's `d4r/d4r.ini`. Model compatibility does not imply equal performance; see the [Townfall measurements](docs/performance.md) for measured frame rates. diff --git a/tools/d3d12_dlss_harness.cpp b/tools/d3d12_dlss_harness.cpp index 9355bbd..3dba5f5 100644 --- a/tools/d3d12_dlss_harness.cpp +++ b/tools/d3d12_dlss_harness.cpp @@ -52,6 +52,35 @@ static ID3D12Fence* g_fence; static HANDLE g_event; static UINT64 g_fenceValue; +static unsigned long long process_vram_kib() +{ + std::vector clients; + unsigned long long total = 0; + for (int fd = 0; fd < 4096; ++fd) + { + char path[64]; + std::snprintf(path, sizeof(path), "Z:\\proc\\self\\fdinfo\\%d", fd); + FILE* file = std::fopen(path, "r"); + if (file == nullptr) + continue; + unsigned long long client = 0, vram = 0; + bool drm = false; + char line[256]; + while (std::fgets(line, sizeof(line), file) != nullptr) + if (std::sscanf(line, "drm-client-id: %llu", &client) == 1) + drm = true; + else + std::sscanf(line, "drm-memory-vram: %llu", &vram); + std::fclose(file); + if (drm && std::find(clients.begin(), clients.end(), client) == clients.end()) + { + clients.push_back(client); + total += vram; + } + } + return total; +} + static bool check(HRESULT hr, const char* what) { if (FAILED(hr)) @@ -487,7 +516,27 @@ int main(int argc, char** argv) ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight, rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE); ID3D12Resource* depthTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE); - ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight, DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE); + // D4R_HARNESS_MV_RGBA16=1: motion vectors in an R16G16B16A16_TYPELESS texture (as some games pass them), the + // unused components filled with junk that must not reach DLSS. + const bool mvRgba16 = std::getenv("D4R_HARNESS_MV_RGBA16") != nullptr && std::atoi(std::getenv("D4R_HARNESS_MV_RGBA16")) != 0; + ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight, + mvRgba16 ? DXGI_FORMAT_R16G16B16A16_TYPELESS : DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE); + std::vector motionWide; + auto motion_rows = [&](const uint16_t* rg) -> const void* { + if (!mvRgba16) + return rg; + const size_t pixels = static_cast(motionWidth) * motionHeight; + motionWide.resize(pixels * 4); + for (size_t pixel = 0; pixel < pixels; ++pixel) + { + motionWide[pixel * 4 + 0] = rg[pixel * 2 + 0]; + motionWide[pixel * 4 + 1] = rg[pixel * 2 + 1]; + motionWide[pixel * 4 + 2] = 0x5640; // 100.0 + motionWide[pixel * 4 + 3] = 0xd640; // -100.0 + } + return motionWide.data(); + }; + const UINT motionRowBytes = motionWidth * (mvRgba16 ? 8 : 4); ID3D12Resource* exposureTexture = create_texture(1, 1, exposureRGBA32 ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE); ID3D12Resource* outputTexture = create_texture(outWidth, outHeight, @@ -496,7 +545,7 @@ int main(int argc, char** argv) upload(colorTexture, rgba8 ? static_cast(color8.data()) : static_cast(color.data()), resourceWidth * (rgba8 ? 4 : 8), srv); upload(depthTexture, depth.data(), resourceWidth * 4, srv); - upload(motionTexture, motion.data(), motionWidth * 4, srv); + upload(motionTexture, motion_rows(motion.data()), motionRowBytes, srv); upload(exposureTexture, exposureRGBA32 ? static_cast(exposureRGBA.data()) : static_cast(&exposureValue), exposureRGBA32 ? 16 : 4, srv); @@ -662,7 +711,7 @@ int main(int argc, char** argv) motion[pixel * 2 + 0] = mvX; motion[pixel * 2 + 1] = mvY; } - update_texture(motionTexture, motion.data(), motionWidth * 4, srv); + update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv); } d4r_ngx_set_float(parameters, "Jitter.Offset.X", signX * jx); d4r_ngx_set_float(parameters, "Jitter.Offset.Y", signY * jy); @@ -700,7 +749,7 @@ int main(int argc, char** argv) } update_texture(colorTexture, color.data(), resourceWidth * 8, srv); update_texture(depthTexture, depth.data(), resourceWidth * 4, srv); - update_texture(motionTexture, motion.data(), motionWidth * 4, srv); + update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv); } if (replayDir != nullptr) { @@ -711,7 +760,7 @@ int main(int argc, char** argv) return 1; update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv); update_texture(depthTexture, replay.depth.data(), resourceWidth * 4, srv); - update_texture(motionTexture, replay.motion.data(), motionWidth * 4, srv); + update_texture(motionTexture, motion_rows(reinterpret_cast(replay.motion.data())), motionRowBytes, srv); d4r_ngx_set_float(parameters, "Jitter.Offset.X", replay.jitterX); d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY); d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX); @@ -836,6 +885,50 @@ int main(int argc, char** argv) nonzero += value != 0; std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]); + // D4R_HARNESS_RECREATE=N: N more release/create cycles at alternating render sizes (as when a game's DLSS + // quality setting changes), a few evaluations each, logging this process's VRAM to find leaks per cycle. + const int recreateCycles = std::getenv("D4R_HARNESS_RECREATE") != nullptr ? std::atoi(std::getenv("D4R_HARNESS_RECREATE")) : 0; + for (int cycle = 1; cycle <= recreateCycles; ++cycle) + { + release(feature); + feature = nullptr; + const UINT width = cycle % 2 != 0 ? inWidth * 10 / 13 : inWidth; + const UINT height = cycle % 2 != 0 ? inHeight * 10 / 13 : inHeight; + d4r_ngx_set_uint(parameters, "Width", width); + d4r_ngx_set_uint(parameters, "Height", height); + d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", width); + d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", height); + result = createFeature(g_list, 1, parameters, &feature); + if (result != NGX_SUCCESS) + { + std::printf("RECREATE cycle %d: CreateFeature -> 0x%08x\n", cycle, result); + return 1; + } + for (int frame = 0; frame < 4; ++frame) + { + d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0); + evaluate(g_list, feature, parameters, nullptr); + submit_and_wait(); + Sleep(frameWaitMs); + } + const std::vector cycleOutput = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + outWidth * (rgba8 ? 4 : 8)); + if (FILE* cycleFile = std::fopen((std::string(argv[2]) + ".cycle" + std::to_string(cycle)).c_str(), "wb")) + { + std::fwrite(cycleOutput.data(), 1, cycleOutput.size(), cycleFile); + std::fclose(cycleFile); + } + // Also to the quality report file, which survives when Proton drops stdout. + const char* reportPath = std::getenv("D4R_HARNESS_QUALITY_SCENE"); + FILE* report = reportPath != nullptr ? std::fopen(reportPath, "a") : nullptr; + for (FILE* out : {stdout, report}) + if (out != nullptr) + std::fprintf(out, "RECREATE cycle %d (%ux%u): process VRAM %llu KiB\n", cycle, width, height, + process_vram_kib()); + if (report != nullptr) + std::fclose(report); + } + release(feature); shutdown(); return 0; diff --git a/tools/d4r_nvngx_shim.cpp b/tools/d4r_nvngx_shim.cpp index f8420b4..66550a0 100644 --- a/tools/d4r_nvngx_shim.cpp +++ b/tools/d4r_nvngx_shim.cpp @@ -836,7 +836,8 @@ static bool supported_input(Plane plane, DXGI_FORMAT format) case Plane::Motion: return format == DXGI_FORMAT_R16G16_FLOAT || format == DXGI_FORMAT_R16G16_TYPELESS || format == DXGI_FORMAT_R32G32_FLOAT || format == DXGI_FORMAT_R32G32_TYPELESS || - format == DXGI_FORMAT_R16G16B16A16_FLOAT || format == DXGI_FORMAT_R32G32B32A32_FLOAT; + format == DXGI_FORMAT_R16G16B16A16_FLOAT || format == DXGI_FORMAT_R16G16B16A16_TYPELESS || + format == DXGI_FORMAT_R32G32B32A32_FLOAT; case Plane::Exposure: return format == DXGI_FORMAT_R32_FLOAT || format == DXGI_FORMAT_R32_TYPELESS || format == DXGI_FORMAT_R16_FLOAT || format == DXGI_FORMAT_R16_TYPELESS || @@ -959,6 +960,7 @@ static void convert_row_in(Plane plane, DXGI_FORMAT format, const uint8_t* sourc std::memcpy(half + x * 2, source + x * 4, 4); break; case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_TYPELESS: std::memcpy(half + x * 2, source + x * 8, 4); break; case DXGI_FORMAT_R32G32B32A32_FLOAT: @@ -1556,6 +1558,7 @@ struct Feature // formats later stop qualifying. bool vram = false, vramDecided = false; VramImage colorConversion; + VramImage motionConversion; VramImage exposureConversion; VramImage outputConversion; // Split frames: the rest of frame N's command list is submitted only once @@ -2159,9 +2162,33 @@ static int device_local_type(uint32_t typeBits) return -1; } +// Buffers of released features, reused by later ones. ROCm up to at least 7.2.4 never frees memory mapped with +// hipExternalMemoryGetMappedBuffer (the mapping's buffer view is retained twice but released once by hipFree), so +// destroying a buffer would leak its memory: each DLSS quality change (a new feature) would add ~100 MB of VRAM. +static std::mutex g_vramPoolMutex; +static std::vector g_vramPool; +constexpr size_t kVramPoolLimit = 64; + +// The smallest pooled buffer that holds `bytes`. +static bool take_pooled_vram_buffer(VramBuffer& target, size_t bytes) +{ + std::lock_guard lock(g_vramPoolMutex); + auto best = g_vramPool.end(); + for (auto it = g_vramPool.begin(); it != g_vramPool.end(); ++it) + if (it->bytes >= bytes && (best == g_vramPool.end() || it->bytes < best->bytes)) + best = it; + if (best == g_vramPool.end()) + return false; + target = *best; + g_vramPool.erase(best); + return true; +} + // Game thread. The CUDA import runs on the worker, which owns the context. static bool create_vram_buffer(VramBuffer& target, size_t bytes) { + if (take_pooled_vram_buffer(target, bytes)) + return true; VramBuffer buffer; VkExternalMemoryBufferCreateInfo external = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; @@ -2212,8 +2239,11 @@ static void destroy_vram_buffer(VramBuffer& buffer) { if (buffer.external != nullptr) g.worker.call([&buffer] { - g.cu.memFree(buffer.device); - return g_vk.release(buffer.external); + const int freed = g.cu.memFree(buffer.device); + const int released = g_vk.release(buffer.external); + if (freed != 0 || released != 0) + logf("VRAM interop: freeing %zu-byte buffer: cuMemFree %d, release %d", buffer.bytes, freed, released); + return released; }); if (buffer.memory != VK_NULL_HANDLE) g_vk.free(g_vk.device, buffer.memory, nullptr); @@ -2222,6 +2252,34 @@ static void destroy_vram_buffer(VramBuffer& buffer) buffer = VramBuffer{}; } +// Only once no command list or CUDA work uses the buffer: keeps it for a later feature (see g_vramPool). +static void recycle_vram_buffer(VramBuffer& buffer) +{ + if (buffer.buffer == VK_NULL_HANDLE) + return; + { + std::lock_guard lock(g_vramPoolMutex); + if (g_vramPool.size() < kVramPoolLimit) + { + g_vramPool.push_back(buffer); + buffer = VramBuffer{}; + return; + } + } + destroy_vram_buffer(buffer); +} + +static void free_vram_pool() +{ + std::vector pool; + { + std::lock_guard lock(g_vramPoolMutex); + pool.swap(g_vramPool); + } + for (VramBuffer& buffer : pool) + destroy_vram_buffer(buffer); +} + static void destroy_vram_image(VramImage& image) { if (image.memory != VK_NULL_HANDLE) @@ -2349,7 +2407,21 @@ static bool vram_exposure_blit_supported(VkFormat format) } } -// Whether `resource` can be copied raw (or, for colour/exposure, blitted) +// Motion vectors in a wider float format keep their first two components (as the host path does). +static bool vram_motion_blit_supported(VkFormat format) +{ + switch (format) + { + case VK_FORMAT_R16G16B16A16_SFLOAT: + case VK_FORMAT_R32G32_SFLOAT: + case VK_FORMAT_R32G32B32A32_SFLOAT: + return vram_blit_supported(format, VK_FORMAT_R16G16_SFLOAT); + default: + return false; + } +} + +// Whether `resource` can be copied raw (or, for colour/motion/exposure, blitted) // to/from the canonical layout of `plane`. static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy, bool output = false) { @@ -2374,6 +2446,8 @@ static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& } if (plane == Plane::Exposure) copy.convert = vram_exposure_blit_supported(format); + else if (plane == Plane::Motion) + copy.convert = vram_motion_blit_supported(format); return copy.convert || vk_texel_bytes(format) == canonical_texel_bytes(plane); } @@ -2415,9 +2489,10 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list g_vk.copyImageToBuffer(cmd, copy.image, layout, slot.vram[index].buffer, 1, ®ion); continue; } - // Convert colour to RGBA16F or exposure's first component to R32F, - // then copy from the canonical image into the shared buffer. + // Convert colour to RGBA16F, motion to RG16F or exposure's first component + // to R32F, then copy from the canonical image into the shared buffer. VkImage conversion = kPlanes[index] == Plane::Exposure ? feature.exposureConversion.image + : kPlanes[index] == Plane::Motion ? feature.motionConversion.image : feature.colorConversion.image; VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toDestination.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; @@ -2633,14 +2708,15 @@ static void release_vram(Feature& feature) g_vk.destroySemaphore(g_vk.device, feature.splitSemaphore, nullptr); for (InputSlot& slot : feature.inputs) for (VramBuffer& buffer : slot.vram) - destroy_vram_buffer(buffer); + recycle_vram_buffer(buffer); for (OutputSlot& slot : feature.outputs) - destroy_vram_buffer(slot.vram); - destroy_vram_buffer(feature.gpuMarker); + recycle_vram_buffer(slot.vram); + recycle_vram_buffer(feature.gpuMarker); for (VramBuffer& buffer : feature.retiredBuffers) - destroy_vram_buffer(buffer); + recycle_vram_buffer(buffer); feature.retiredBuffers.clear(); destroy_vram_image(feature.colorConversion); + destroy_vram_image(feature.motionConversion); destroy_vram_image(feature.exposureConversion); destroy_vram_image(feature.outputConversion); for (VramImage& image : feature.retiredImages) @@ -4437,6 +4513,10 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand vramInputs[0].width, vramInputs[0].height, VK_FORMAT_R16G16B16A16_SFLOAT)) return NGX_FAIL_PLATFORM_ERROR; + if (vramInputs[2].convert && !ensure_conversion_image(*feature, feature->motionConversion, + vramInputs[2].width, vramInputs[2].height, + VK_FORMAT_R16G16_SFLOAT)) + return NGX_FAIL_PLATFORM_ERROR; if (inputCount == 4 && vramInputs[3].convert && !ensure_conversion_image(*feature, feature->exposureConversion, vramInputs[3].width, vramInputs[3].height, VK_FORMAT_R32_SFLOAT)) @@ -4688,6 +4768,8 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown() } for (Feature* feature : features) release_feature(feature); + if (g_vk.ready) + free_vram_pool(); std::lock_guard lock(g.mutex); if (g.ngxInitialized) { diff --git a/tools/wine_nvcuda_bridge.c b/tools/wine_nvcuda_bridge.c index e164924..711f31e 100644 --- a/tools/wine_nvcuda_bridge.c +++ b/tools/wine_nvcuda_bridge.c @@ -2589,8 +2589,10 @@ CUresult WINAPI cuGetProcAddress(const char* symbol, void** pointer, int cuda_ve handles (Wine 11 layout: a VkDevice client object holds the unix object pointer at offset 8; a non-dispatchable client handle is the unix object pointer; every unix object starts with its host handle), the host fd is - exported with vkGetMemoryFdKHR and imported into HIP, which takes ownership - of the fd. */ + exported with vkGetMemoryFdKHR and imported into HIP. HIP does not take + ownership of the fd (hipDestroyExternalMemory leaves it open, and an open fd + keeps the memory alive after vkFreeMemory), so it is closed once imported, as + ROCm's own GL interop does after mapping. */ typedef struct { int type; /* hipExternalMemoryHandleTypeOpaqueFd = 1 */ @@ -2697,11 +2699,11 @@ CUresult WINAPI d4rImportVulkanMemory(void* client_device, uint64_t client_memor desc.flags = 1; /* dedicated allocation */ void* external = NULL; int result = import(&external, &desc); + close(fd); if (result != 0) { tracef("d4rImportVulkanMemory: hipImportExternalMemory(fd=%d, %llu bytes) failed: %d", fd, (unsigned long long)bytes, result); - close(fd); return CUDA_ERROR_INVALID_VALUE; } D4rHipExternalMemoryBufferDesc buffer; @@ -2835,5 +2837,7 @@ CUresult WINAPI d4rSetArrayRedirect(CUarray array, CUdeviceptr pointer, uint32_t CUresult WINAPI d4rReleaseVulkanMemory(void* memory) { HIP_DESTROY_EXTERNAL_MEMORY_FN destroy = (HIP_DESTROY_EXTERNAL_MEMORY_FN)hip_symbol("hipDestroyExternalMemory"); - return destroy != NULL && destroy(memory) == 0 ? CUDA_SUCCESS : CUDA_ERROR_INVALID_VALUE; + const int result = destroy != NULL ? destroy(memory) : -1; + tracef("d4rReleaseVulkanMemory: hipDestroyExternalMemory(%p) -> %d", memory, result); + return result == 0 ? CUDA_SUCCESS : CUDA_ERROR_INVALID_VALUE; }