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
+249
-28
No files matched your search
+2
-1
@@ -23,6 +23,7 @@ __pycache__/
|
|||||||
/Archive.zip
|
/Archive.zip
|
||||||
/vkd3d-proton.cache
|
/vkd3d-proton.cache
|
||||||
.vscode/
|
.vscode/
|
||||||
|
videos/
|
||||||
|
|
||||||
# Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published)
|
# Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published)
|
||||||
/private/
|
/private/
|
||||||
@@ -32,4 +33,4 @@ __pycache__/
|
|||||||
/kernels/out/
|
/kernels/out/
|
||||||
*.hsaco
|
*.hsaco
|
||||||
*.ptx
|
*.ptx
|
||||||
/kernels/extracted/
|
/kernels/extracted/
|
||||||
@@ -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).
|
[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 |
|
| 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
|
## 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
|
## 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)
|
## 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).
|
- ROCm with HIP and its clang (tested with ROCm 7.2).
|
||||||
- GE-Proton with OptiScaler integration (tested with GE-Proton11-3).
|
- 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.
|
- 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 |
|
| SILENT HILL Townfall | Works | Works | Works |
|
||||||
| Ghost of Tsushima DIRECTOR'S CUT | Works | Works | Works |
|
| Ghost of Tsushima DIRECTOR'S CUT | Works | Works | Works |
|
||||||
| Ready or Not | Works | Works; visible graphical artifacts with hdr enabled | 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.
|
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 HANDLE g_event;
|
||||||
static UINT64 g_fenceValue;
|
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)
|
static bool check(HRESULT hr, const char* what)
|
||||||
{
|
{
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
@@ -487,7 +516,27 @@ int main(int argc, char** argv)
|
|||||||
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight,
|
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight,
|
||||||
rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
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* 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,
|
ID3D12Resource* exposureTexture = create_texture(1, 1,
|
||||||
exposureRGBA32 ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
exposureRGBA32 ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||||
ID3D12Resource* outputTexture = create_texture(outWidth, outHeight,
|
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()),
|
upload(colorTexture, rgba8 ? static_cast<const void*>(color8.data()) : static_cast<const void*>(color.data()),
|
||||||
resourceWidth * (rgba8 ? 4 : 8), srv);
|
resourceWidth * (rgba8 ? 4 : 8), srv);
|
||||||
upload(depthTexture, depth.data(), resourceWidth * 4, 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())
|
upload(exposureTexture, exposureRGBA32 ? static_cast<const void*>(exposureRGBA.data())
|
||||||
: static_cast<const void*>(&exposureValue),
|
: static_cast<const void*>(&exposureValue),
|
||||||
exposureRGBA32 ? 16 : 4, srv);
|
exposureRGBA32 ? 16 : 4, srv);
|
||||||
@@ -662,7 +711,7 @@ int main(int argc, char** argv)
|
|||||||
motion[pixel * 2 + 0] = mvX;
|
motion[pixel * 2 + 0] = mvX;
|
||||||
motion[pixel * 2 + 1] = mvY;
|
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.X", signX * jx);
|
||||||
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", signY * jy);
|
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(colorTexture, color.data(), resourceWidth * 8, srv);
|
||||||
update_texture(depthTexture, depth.data(), resourceWidth * 4, 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)
|
if (replayDir != nullptr)
|
||||||
{
|
{
|
||||||
@@ -711,7 +760,7 @@ int main(int argc, char** argv)
|
|||||||
return 1;
|
return 1;
|
||||||
update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv);
|
update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv);
|
||||||
update_texture(depthTexture, replay.depth.data(), resourceWidth * 4, 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.X", replay.jitterX);
|
||||||
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY);
|
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY);
|
||||||
d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX);
|
d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX);
|
||||||
@@ -836,6 +885,50 @@ int main(int argc, char** argv)
|
|||||||
nonzero += value != 0;
|
nonzero += value != 0;
|
||||||
std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]);
|
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);
|
release(feature);
|
||||||
shutdown();
|
shutdown();
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+92
-10
@@ -836,7 +836,8 @@ static bool supported_input(Plane plane, DXGI_FORMAT format)
|
|||||||
case Plane::Motion:
|
case Plane::Motion:
|
||||||
return format == DXGI_FORMAT_R16G16_FLOAT || format == DXGI_FORMAT_R16G16_TYPELESS ||
|
return format == DXGI_FORMAT_R16G16_FLOAT || format == DXGI_FORMAT_R16G16_TYPELESS ||
|
||||||
format == DXGI_FORMAT_R32G32_FLOAT || format == DXGI_FORMAT_R32G32_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:
|
case Plane::Exposure:
|
||||||
return format == DXGI_FORMAT_R32_FLOAT || format == DXGI_FORMAT_R32_TYPELESS ||
|
return format == DXGI_FORMAT_R32_FLOAT || format == DXGI_FORMAT_R32_TYPELESS ||
|
||||||
format == DXGI_FORMAT_R16_FLOAT || format == DXGI_FORMAT_R16_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);
|
std::memcpy(half + x * 2, source + x * 4, 4);
|
||||||
break;
|
break;
|
||||||
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
||||||
|
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
|
||||||
std::memcpy(half + x * 2, source + x * 8, 4);
|
std::memcpy(half + x * 2, source + x * 8, 4);
|
||||||
break;
|
break;
|
||||||
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
||||||
@@ -1556,6 +1558,7 @@ struct Feature
|
|||||||
// formats later stop qualifying.
|
// formats later stop qualifying.
|
||||||
bool vram = false, vramDecided = false;
|
bool vram = false, vramDecided = false;
|
||||||
VramImage colorConversion;
|
VramImage colorConversion;
|
||||||
|
VramImage motionConversion;
|
||||||
VramImage exposureConversion;
|
VramImage exposureConversion;
|
||||||
VramImage outputConversion;
|
VramImage outputConversion;
|
||||||
// Split frames: the rest of frame N's command list is submitted only once
|
// 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;
|
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.
|
// Game thread. The CUDA import runs on the worker, which owns the context.
|
||||||
static bool create_vram_buffer(VramBuffer& target, size_t bytes)
|
static bool create_vram_buffer(VramBuffer& target, size_t bytes)
|
||||||
{
|
{
|
||||||
|
if (take_pooled_vram_buffer(target, bytes))
|
||||||
|
return true;
|
||||||
VramBuffer buffer;
|
VramBuffer buffer;
|
||||||
VkExternalMemoryBufferCreateInfo external = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
|
VkExternalMemoryBufferCreateInfo external = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
|
||||||
external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
|
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)
|
if (buffer.external != nullptr)
|
||||||
g.worker.call([&buffer] {
|
g.worker.call([&buffer] {
|
||||||
g.cu.memFree(buffer.device);
|
const int freed = g.cu.memFree(buffer.device);
|
||||||
return g_vk.release(buffer.external);
|
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)
|
if (buffer.memory != VK_NULL_HANDLE)
|
||||||
g_vk.free(g_vk.device, buffer.memory, nullptr);
|
g_vk.free(g_vk.device, buffer.memory, nullptr);
|
||||||
@@ -2222,6 +2252,34 @@ static void destroy_vram_buffer(VramBuffer& buffer)
|
|||||||
buffer = VramBuffer{};
|
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)
|
static void destroy_vram_image(VramImage& image)
|
||||||
{
|
{
|
||||||
if (image.memory != VK_NULL_HANDLE)
|
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`.
|
// to/from the canonical layout of `plane`.
|
||||||
static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy, bool output = false)
|
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)
|
if (plane == Plane::Exposure)
|
||||||
copy.convert = vram_exposure_blit_supported(format);
|
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);
|
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);
|
g_vk.copyImageToBuffer(cmd, copy.image, layout, slot.vram[index].buffer, 1, ®ion);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Convert colour to RGBA16F or exposure's first component to R32F,
|
// Convert colour to RGBA16F, motion to RG16F or exposure's first component
|
||||||
// then copy from the canonical image into the shared buffer.
|
// to R32F, then copy from the canonical image into the shared buffer.
|
||||||
VkImage conversion = kPlanes[index] == Plane::Exposure ? feature.exposureConversion.image
|
VkImage conversion = kPlanes[index] == Plane::Exposure ? feature.exposureConversion.image
|
||||||
|
: kPlanes[index] == Plane::Motion ? feature.motionConversion.image
|
||||||
: feature.colorConversion.image;
|
: feature.colorConversion.image;
|
||||||
VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||||
toDestination.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
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);
|
g_vk.destroySemaphore(g_vk.device, feature.splitSemaphore, nullptr);
|
||||||
for (InputSlot& slot : feature.inputs)
|
for (InputSlot& slot : feature.inputs)
|
||||||
for (VramBuffer& buffer : slot.vram)
|
for (VramBuffer& buffer : slot.vram)
|
||||||
destroy_vram_buffer(buffer);
|
recycle_vram_buffer(buffer);
|
||||||
for (OutputSlot& slot : feature.outputs)
|
for (OutputSlot& slot : feature.outputs)
|
||||||
destroy_vram_buffer(slot.vram);
|
recycle_vram_buffer(slot.vram);
|
||||||
destroy_vram_buffer(feature.gpuMarker);
|
recycle_vram_buffer(feature.gpuMarker);
|
||||||
for (VramBuffer& buffer : feature.retiredBuffers)
|
for (VramBuffer& buffer : feature.retiredBuffers)
|
||||||
destroy_vram_buffer(buffer);
|
recycle_vram_buffer(buffer);
|
||||||
feature.retiredBuffers.clear();
|
feature.retiredBuffers.clear();
|
||||||
destroy_vram_image(feature.colorConversion);
|
destroy_vram_image(feature.colorConversion);
|
||||||
|
destroy_vram_image(feature.motionConversion);
|
||||||
destroy_vram_image(feature.exposureConversion);
|
destroy_vram_image(feature.exposureConversion);
|
||||||
destroy_vram_image(feature.outputConversion);
|
destroy_vram_image(feature.outputConversion);
|
||||||
for (VramImage& image : feature.retiredImages)
|
for (VramImage& image : feature.retiredImages)
|
||||||
@@ -4437,6 +4513,10 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
|||||||
vramInputs[0].width, vramInputs[0].height,
|
vramInputs[0].width, vramInputs[0].height,
|
||||||
VK_FORMAT_R16G16B16A16_SFLOAT))
|
VK_FORMAT_R16G16B16A16_SFLOAT))
|
||||||
return NGX_FAIL_PLATFORM_ERROR;
|
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 &&
|
if (inputCount == 4 && vramInputs[3].convert &&
|
||||||
!ensure_conversion_image(*feature, feature->exposureConversion,
|
!ensure_conversion_image(*feature, feature->exposureConversion,
|
||||||
vramInputs[3].width, vramInputs[3].height, VK_FORMAT_R32_SFLOAT))
|
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)
|
for (Feature* feature : features)
|
||||||
release_feature(feature);
|
release_feature(feature);
|
||||||
|
if (g_vk.ready)
|
||||||
|
free_vram_pool();
|
||||||
std::lock_guard<std::mutex> lock(g.mutex);
|
std::lock_guard<std::mutex> lock(g.mutex);
|
||||||
if (g.ngxInitialized)
|
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
|
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 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
|
pointer; every unix object starts with its host handle), the host fd is
|
||||||
exported with vkGetMemoryFdKHR and imported into HIP, which takes ownership
|
exported with vkGetMemoryFdKHR and imported into HIP. HIP does not take
|
||||||
of the fd. */
|
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
|
typedef struct
|
||||||
{
|
{
|
||||||
int type; /* hipExternalMemoryHandleTypeOpaqueFd = 1 */
|
int type; /* hipExternalMemoryHandleTypeOpaqueFd = 1 */
|
||||||
@@ -2697,11 +2699,11 @@ CUresult WINAPI d4rImportVulkanMemory(void* client_device, uint64_t client_memor
|
|||||||
desc.flags = 1; /* dedicated allocation */
|
desc.flags = 1; /* dedicated allocation */
|
||||||
void* external = NULL;
|
void* external = NULL;
|
||||||
int result = import(&external, &desc);
|
int result = import(&external, &desc);
|
||||||
|
close(fd);
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
{
|
{
|
||||||
tracef("d4rImportVulkanMemory: hipImportExternalMemory(fd=%d, %llu bytes) failed: %d", fd,
|
tracef("d4rImportVulkanMemory: hipImportExternalMemory(fd=%d, %llu bytes) failed: %d", fd,
|
||||||
(unsigned long long)bytes, result);
|
(unsigned long long)bytes, result);
|
||||||
close(fd);
|
|
||||||
return CUDA_ERROR_INVALID_VALUE;
|
return CUDA_ERROR_INVALID_VALUE;
|
||||||
}
|
}
|
||||||
D4rHipExternalMemoryBufferDesc buffer;
|
D4rHipExternalMemoryBufferDesc buffer;
|
||||||
@@ -2835,5 +2837,7 @@ CUresult WINAPI d4rSetArrayRedirect(CUarray array, CUdeviceptr pointer, uint32_t
|
|||||||
CUresult WINAPI d4rReleaseVulkanMemory(void* memory)
|
CUresult WINAPI d4rReleaseVulkanMemory(void* memory)
|
||||||
{
|
{
|
||||||
HIP_DESTROY_EXTERNAL_MEMORY_FN destroy = (HIP_DESTROY_EXTERNAL_MEMORY_FN)hip_symbol("hipDestroyExternalMemory");
|
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