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/
This commit is contained in:
1 parent
1952452830
commit
da263ecf75
6 files changed
+248
-27
No files matched your search
@@ -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/
|
||||
|
||||
@@ -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.
|
||||
|
||||
+33
-5
@@ -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.
|
||||
@@ -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<unsigned long long> 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<uint16_t> motionWide;
|
||||
auto motion_rows = [&](const uint16_t* rg) -> const void* {
|
||||
if (!mvRgba16)
|
||||
return rg;
|
||||
const size_t pixels = static_cast<size_t>(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<const void*>(color8.data()) : static_cast<const void*>(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<const void*>(exposureRGBA.data())
|
||||
: static_cast<const void*>(&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<const uint16_t*>(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<uint8_t> 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;
|
||||
|
||||
+92
-10
@@ -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<VramBuffer> 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<std::mutex> 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<std::mutex> 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<VramBuffer> pool;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> lock(g.mutex);
|
||||
if (g.ngxInitialized)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user