Fix reflection lag from RGBA exposure

This commit is contained in:
ayo committed 2026-09-27 18:31:42 -04:00
1 parent 00cb497ede
commit bc31abdd50
5 files changed
+65 -27

No files matched your search

+3 -1
View File
@@ -2,7 +2,9 @@
d4r runs NVIDIA's official DLSS Super Resolution library (`nvngx_dlss.dll`) in Windows games on AMD Radeon GPUs under Linux and Proton. The game asks for DLSS as usual; the DLSS network runs on the AMD GPU through [ZLUDA](https://github.com/vosen/ZLUDA) (CUDA on ROCm/HIP), with the heaviest DLSS kernels replaced by hand-written RDNA3 code.
It is an experiment. It has been tested on one GPU in two games, it depends on unreleased patches to ZLUDA and vkd3d-proton, and it needs NVIDIA files that are not part of this repository.
**Supported DLSS models:** DLSS 3 CNN (E), DLSS 4 transformer (K, default), and DLSS 4.5 transformer (M). DLSS 5 is not supported.
It is an experiment. It has been tested on one GPU in a handful of games, it depends on unreleased patches to ZLUDA and vkd3d-proton.
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
+2 -1
View File
@@ -1,11 +1,12 @@
# Supported games
The following DirectX 12 games run DLSS Super Resolution through d4r on the tested Radeon RX 7700 XT Linux setup with GE-Proton 11-3. All three documented DLSS models work in both games.
The following DirectX 12 games run DLSS Super Resolution through d4r on the tested Radeon RX 7700 XT Linux setup with GE-Proton 11-3. All three documented DLSS models run in each game; image-quality issues are noted in the table.
| Game | E: DLSS 3 CNN | K: DLSS 4 | M: DLSS 4.5 |
|---|---|---|---|
| SILENT HILL Townfall | Works | Works | Works |
| Ghost of Tsushima DIRECTOR'S CUT | Works | Works | Works |
| Ready or Not | Works | Works; visible graphical artifacts | Works; minor artifacts |
| | | | |
| | | | |
| | | | |
+1 -1
View File
@@ -20,7 +20,7 @@ d4r keeps NVIDIA's DLSS library unmodified and gives it what it expects: an NGX
With VRAM interop and split frames (the defaults when the patched vkd3d-proton is present):
1. **The game records DLSS.** Its `EvaluateFeature` call reaches the shim with the game's command list. Into that list the shim records:
- Vulkan commands (vkd3d-proton interop) that copy colour, depth and motion vectors into buffers exported to HIP. Linear R11G11B10, RGB10A2, and RGBA8/BGRA8 colour inputs are blitted to RGBA16F on the way when the device supports the conversion.
- Vulkan commands (vkd3d-proton interop) that copy colour, depth, motion vectors and optional exposure into buffers exported to HIP. Linear R11G11B10, RGB10A2, and RGBA8/BGRA8 colour inputs are blitted to RGBA16F; multichannel float exposure is blitted to R32F using its first component when the device supports the conversion.
- A frame marker.
- A split. vkd3d-proton submits everything the game records after the DLSS call separately, gated on a timeline semaphore reaching this frame's number.
- At the start of that second part, a copy of the DLSS result into the game's output texture. Linear R11G11B10, RGB10A2, and RGBA8/BGRA8 outputs are converted from RGBA16F on the GPU. sRGB formats continue through host staging to preserve their existing byte interpretation.
+7 -2
View File
@@ -385,6 +385,7 @@ int main(int argc, char** argv)
}
const bool qualityScene = std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr;
const bool poleScene = std::getenv("D4R_HARNESS_POLE_SCENE") != nullptr;
const bool exposureRGBA32 = std::getenv("D4R_HARNESS_EXPOSURE_RGBA32") != nullptr;
const char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR");
const bool rgba8 = std::getenv("D4R_HARNESS_RGBA8") != nullptr;
if (rgba8 && (qualityScene || motionScene || jitterScene || replayDir != nullptr))
@@ -469,6 +470,7 @@ int main(int argc, char** argv)
const UINT motionWidth = motionHighRes ? outWidth : resourceWidth, motionHeight = motionHighRes ? outHeight : inHeight;
std::vector<uint16_t> motion(static_cast<size_t>(motionWidth) * motionHeight * 2, 0);
const float exposureValue = 1.0f;
const std::array<float, 4> exposureRGBA = {exposureValue, 0.25f, 0.5f, 0.75f};
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
std::vector<uint8_t> color8;
std::vector<uint8_t> outputInit8;
@@ -486,7 +488,8 @@ int main(int argc, char** argv)
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);
ID3D12Resource* exposureTexture = create_texture(1, 1, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
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,
rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT,
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
@@ -494,7 +497,9 @@ int main(int argc, char** argv)
resourceWidth * (rgba8 ? 4 : 8), srv);
upload(depthTexture, depth.data(), resourceWidth * 4, srv);
upload(motionTexture, motion.data(), motionWidth * 4, srv);
upload(exposureTexture, &exposureValue, 4, srv);
upload(exposureTexture, exposureRGBA32 ? static_cast<const void*>(exposureRGBA.data())
: static_cast<const void*>(&exposureValue),
exposureRGBA32 ? 16 : 4, srv);
upload(outputTexture, rgba8 ? static_cast<const void*>(outputInit8.data()) : static_cast<const void*>(outputInit.data()),
outWidth * (rgba8 ? 4 : 8), D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
std::printf("synthetic inputs uploaded\n");
+52 -22
View File
@@ -1418,12 +1418,13 @@ struct VramBuffer
size_t bytes = 0;
};
// An RGBA16F image used to convert a noncanonical color input or output.
// A temporary image for colour or exposure format conversion.
struct VramImage
{
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
UINT width = 0, height = 0;
VkFormat format = VK_FORMAT_UNDEFINED;
};
struct Staging
@@ -1551,6 +1552,7 @@ struct Feature
// formats later stop qualifying.
bool vram = false, vramDecided = false;
VramImage colorConversion;
VramImage exposureConversion;
VramImage outputConversion;
// Split frames: the rest of frame N's command list is submitted only once
// splitSemaphore reaches N (signalled when N retires, or by the watchdog).
@@ -2225,11 +2227,11 @@ static void destroy_vram_image(VramImage& image)
image = VramImage{};
}
static bool create_vram_image(VramImage& image, UINT width, UINT height)
static bool create_vram_image(VramImage& image, UINT width, UINT height, VkFormat format)
{
VkImageCreateInfo info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
info.imageType = VK_IMAGE_TYPE_2D;
info.format = VK_FORMAT_R16G16B16A16_SFLOAT;
info.format = format;
info.extent = {width, height, 1};
info.mipLevels = 1;
info.arrayLayers = 1;
@@ -2260,6 +2262,7 @@ static bool create_vram_image(VramImage& image, UINT width, UINT height)
}
image.width = width;
image.height = height;
image.format = format;
return true;
}
@@ -2297,11 +2300,19 @@ struct VramCopy
ID3D12Resource* resource = nullptr;
VkImage image = VK_NULL_HANDLE;
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
bool convert = false; // colour blit between this format and RGBA16F
bool convert = false; // blit to/from the plane's canonical format
UINT width = 0, height = 0;
};
// Whether a linear color format can be blitted to/from the canonical RGBA16F.
static bool vram_blit_supported(VkFormat source, VkFormat destination)
{
VkFormatProperties sourceProperties = {}, destinationProperties = {};
g_vk.formatProperties(g_vk.physical, source, &sourceProperties);
g_vk.formatProperties(g_vk.physical, destination, &destinationProperties);
return (sourceProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) != 0 &&
(destinationProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) != 0;
}
// sRGB is deliberately excluded: the host staging path treats its bytes as
// UNORM and a Vulkan blit would apply an sRGB transfer function instead.
static bool vram_color_blit_supported(VkFormat format, bool output)
@@ -2316,16 +2327,25 @@ static bool vram_color_blit_supported(VkFormat format, bool output)
default:
return false;
}
VkFormatProperties resourceProperties = {}, canonicalProperties = {};
g_vk.formatProperties(g_vk.physical, format, &resourceProperties);
g_vk.formatProperties(g_vk.physical, VK_FORMAT_R16G16B16A16_SFLOAT, &canonicalProperties);
const VkFormatFeatureFlags resourceFeature = output ? VK_FORMAT_FEATURE_BLIT_DST_BIT : VK_FORMAT_FEATURE_BLIT_SRC_BIT;
const VkFormatFeatureFlags canonicalFeature = output ? VK_FORMAT_FEATURE_BLIT_SRC_BIT : VK_FORMAT_FEATURE_BLIT_DST_BIT;
return (resourceProperties.optimalTilingFeatures & resourceFeature) != 0 &&
(canonicalProperties.optimalTilingFeatures & canonicalFeature) != 0;
return output ? vram_blit_supported(VK_FORMAT_R16G16B16A16_SFLOAT, format)
: vram_blit_supported(format, VK_FORMAT_R16G16B16A16_SFLOAT);
}
// Whether `resource` can be copied raw (or, for linear colour, blitted)
// The host path takes only the first (R) component of an exposure texel.
static bool vram_exposure_blit_supported(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R16_SFLOAT:
case VK_FORMAT_R16G16B16A16_SFLOAT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
return vram_blit_supported(format, VK_FORMAT_R32_SFLOAT);
default:
return false;
}
}
// Whether `resource` can be copied raw (or, for colour/exposure, blitted)
// to/from the canonical layout of `plane`.
static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy, bool output = false)
{
@@ -2348,7 +2368,9 @@ static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy&
copy.convert = vram_color_blit_supported(format, output);
return copy.convert || format == VK_FORMAT_R16G16B16A16_SFLOAT;
}
return vk_texel_bytes(format) == canonical_texel_bytes(plane);
if (plane == Plane::Exposure)
copy.convert = vram_exposure_blit_supported(format);
return copy.convert || vk_texel_bytes(format) == canonical_texel_bytes(plane);
}
static const VkMemoryBarrier kBeforeTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_MEMORY_WRITE_BIT,
@@ -2389,9 +2411,10 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list
g_vk.copyImageToBuffer(cmd, copy.image, layout, slot.vram[index].buffer, 1, &region);
continue;
}
// Colour conversion: blit (exact for R11G11B10 -> half) into the
// RGBA16F image, then copy that.
VkImage conversion = feature.colorConversion.image;
// Convert colour to RGBA16F 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
: feature.colorConversion.image;
VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
toDestination.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
toDestination.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
@@ -2590,14 +2613,14 @@ static bool ensure_vram_buffer(Feature& feature, VramBuffer& buffer, size_t byte
return create_vram_buffer(buffer, bytes);
}
static bool ensure_conversion_image(Feature& feature, VramImage& image, UINT width, UINT height)
static bool ensure_conversion_image(Feature& feature, VramImage& image, UINT width, UINT height, VkFormat format)
{
if (image.image != VK_NULL_HANDLE && image.width == width && image.height == height)
if (image.image != VK_NULL_HANDLE && image.width == width && image.height == height && image.format == format)
return true;
if (image.image != VK_NULL_HANDLE)
feature.retiredImages.push_back(image);
image = VramImage{};
return create_vram_image(image, width, height);
return create_vram_image(image, width, height, format);
}
static void release_vram(Feature& feature)
@@ -2614,6 +2637,7 @@ static void release_vram(Feature& feature)
destroy_vram_buffer(buffer);
feature.retiredBuffers.clear();
destroy_vram_image(feature.colorConversion);
destroy_vram_image(feature.exposureConversion);
destroy_vram_image(feature.outputConversion);
for (VramImage& image : feature.retiredImages)
destroy_vram_image(image);
@@ -4406,10 +4430,16 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
return NGX_FAIL_PLATFORM_ERROR;
}
if (vramInputs[0].convert && !ensure_conversion_image(*feature, feature->colorConversion,
vramInputs[0].width, vramInputs[0].height))
vramInputs[0].width, vramInputs[0].height,
VK_FORMAT_R16G16B16A16_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))
return NGX_FAIL_PLATFORM_ERROR;
if (vramOutput.convert && !ensure_conversion_image(*feature, feature->outputConversion,
vramOutput.width, vramOutput.height))
vramOutput.width, vramOutput.height,
VK_FORMAT_R16G16B16A16_SFLOAT))
return NGX_FAIL_PLATFORM_ERROR;
}
if ((!p.vram || verify) &&