From 818dd8bf429875ce0db9429b265968f8b9eb3b06 Mon Sep 17 00:00:00 2001 From: ayo <93222141+countervolts@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:22:04 -0400 Subject: [PATCH] Fix 8-bit DLSS VRAM interop --- .gitignore | 1 + README.md | 4 +- docs/architecture.md | 4 +- packaging/D4R_README.txt | 4 +- packaging/RELEASE_NOTES.md | 9 ++-- tools/d3d12_dlss_harness.cpp | 43 +++++++++++---- tools/d4r_nvngx_shim.cpp | 102 +++++++++++++++++++++++++++++------ 7 files changed, 130 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 62dc6c9..6c6cba3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ __pycache__/ .venv/ /Archive.zip /vkd3d-proton.cache +.vscode/ # Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published) /private/ diff --git a/README.md b/README.md index bc311df..8fca9a5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# d4r +# d4r (dlss 4 radeon) 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 and one game, it depends on unreleased patches to ZLUDA and vkd3d-proton, and it needs NVIDIA files that are not part of this repository. +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. > **Not affiliated with NVIDIA or AMD.** diff --git a/docs/architecture.md b/docs/architecture.md index 52215e5..c50ef90 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -20,10 +20,10 @@ 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. An R11G11B10 colour target is converted to RGBA16F on the way. + - 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. - 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. + - 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. 2. **The CUDA worker queues DLSS.** A worker thread owns the CUDA context. For each frame it queues on the GPU, without waiting on the CPU: - a wait for the marker (`GpuWait`); - the input copies into CUDA arrays, or none when NGX samples the buffers in place (`LinearInputs`); diff --git a/packaging/D4R_README.txt b/packaging/D4R_README.txt index 49b684b..6600e6d 100644 --- a/packaging/D4R_README.txt +++ b/packaging/D4R_README.txt @@ -6,8 +6,8 @@ DirectX 12 games under Proton. OptiScaler (included) catches the game's DLSS cal d4r. d4r then runs NVIDIA's DLSS on the Radeon through ZLUDA, with the heaviest parts replaced by kernels written for RDNA3. -This is an early release. It has been tested on one GPU (Radeon RX 7700 XT) and one game -(SILENT HILL Townfall). Expect problems in other games. +This is an early release. It has been tested on one GPU (Radeon RX 7700 XT) in +SILENT HILL Townfall and Ghost of Tsushima DIRECTOR'S CUT. Expect problems in other games. What you need diff --git a/packaging/RELEASE_NOTES.md b/packaging/RELEASE_NOTES.md index 2e63e91..d5bc886 100644 --- a/packaging/RELEASE_NOTES.md +++ b/packaging/RELEASE_NOTES.md @@ -2,7 +2,7 @@ NVIDIA's own DLSS Super Resolution on AMD Radeon RX 7000 (RDNA3) GPUs, in DirectX 12 games under Proton. This is the first release packaged as a drop-in zip, to install extract it next to the game's executable and set one line of launch options. -It is an early release, tested on one GPU (RX 7700 XT) and one game (SILENT HILL Townfall). +It is an early release, tested on one GPU (RX 7700 XT) in SILENT HILL Townfall and Ghost of Tsushima DIRECTOR'S CUT. ## Install @@ -25,6 +25,7 @@ It is an early release, tested on one GPU (RX 7700 XT) and one game (SILENT HILL - **d4r:** the NGX shim, the Wine CUDA bridge, and hand-written RDNA3 kernels for the DLSS 4 (K) and DLSS 4.5 (M) network layers. - Each kernel is used only when the DLSS library's code for it matches the code it was written for. Swapping in another DLSS version therefore still works, only slower. + - Linear 8-bit colour inputs and outputs can stay in VRAM when Vulkan supports the required blits, fixing the slow staging path seen in Ghost of Tsushima. - `d4r/d4r.ini` holds the settings for that game. The default model is DLSS 4 (K); E (DLSS 3 CNN) and M (DLSS 4.5) are one line away. - **NVIDIA:** DLSS 310.7.0 (`nvngx_dlss.dll`), the NGX runtime from driver 596.36 (`_nvngx.dll`), and five DLSS kernels built from NVIDIA's code with parts replaced by d4r's. - **ZLUDA** with d4r's patches: CUDA on ROCm. @@ -46,12 +47,12 @@ Radeon RX 7700 XT, SILENT HILL Townfall at 2560×1440, average fps over the same Native 2560×1440 without upscaling (the game's TSR at 100%) runs at 49 fps, so DLSS 4 at Quality is 40% faster than native. -DLSS 4 in every mode, and E and M at Quality, were measured with this release. The other E and M figures come from the development setup, which runs the same kernels. +The Townfall figures were measured before the 8-bit VRAM conversion update, using the same native kernels. Ghost of Tsushima was checked in a live run with the updated shim; its VRAM path stayed on and gameplay performance improved, but no matched benchmark was recorded. ## Known limitations - Only DLSS Super Resolution in DirectX 12 games. No Frame Generation or Ray Reconstruction. -- Tested with one game. Do not use it in games with anti-cheat. +- Tested with two games. Do not use it in games with anti-cheat. - DLSS 4.5 (M) is much slower than FSR 4 on RDNA3. ## Licenses @@ -60,4 +61,4 @@ d4r is Apache 2.0. The zip also contains ZLUDA (Apache 2.0 or MIT), vkd3d-proton NVIDIA's files in the zip, and the kernels built from NVIDIA's code, belong to NVIDIA and are not covered by any of these licenses. d4r is not affiliated with NVIDIA, AMD or the OptiScaler project. -**SHA-256** `d4r-0.1.0.zip`: `ddaa0866273c6e31f2f1282b2cc2ac370c804b04e8895cdda27c096747edf5a1` +**SHA-256** `d4r-0.1.0.zip`: `487f8c98c0c5617449dddd5f573bfd4b7333c8c2f5a87198d485256673cabdba` diff --git a/tools/d3d12_dlss_harness.cpp b/tools/d3d12_dlss_harness.cpp index c278ffe..43f2d6a 100644 --- a/tools/d3d12_dlss_harness.cpp +++ b/tools/d3d12_dlss_harness.cpp @@ -386,6 +386,12 @@ 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 char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR"); + const bool rgba8 = std::getenv("D4R_HARNESS_RGBA8") != nullptr; + if (rgba8 && (qualityScene || motionScene || jitterScene || replayDir != nullptr)) + { + std::fprintf(stderr, "RGBA8 format probe requires the static synthetic scene\n"); + return 2; + } const uint32_t replayStart = std::getenv("D4R_HARNESS_REPLAY_START") != nullptr ? static_cast(std::atoi(std::getenv("D4R_HARNESS_REPLAY_START"))) : 1; const bool temporal = std::getenv("D4R_HARNESS_TEMPORAL") != nullptr || motionScene || jitterScene || qualityScene || @@ -464,19 +470,33 @@ int main(int argc, char** argv) std::vector motion(static_cast(motionWidth) * motionHeight * 2, 0); const float exposureValue = 1.0f; std::vector outputInit(static_cast(outWidth) * outHeight * 4, 0); + std::vector color8; + std::vector outputInit8; + if (rgba8) + { + color8.resize(color.size()); + for (size_t index = 0; index < color.size(); ++index) + color8[index] = static_cast(std::lround( + std::clamp(static_cast(std::bit_cast<_Float16>(color[index])), 0.0f, 1.0f) * 255.0f)); + outputInit8.resize(static_cast(outWidth) * outHeight * 4); + } const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; - ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE); + 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); ID3D12Resource* exposureTexture = create_texture(1, 1, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE); - ID3D12Resource* outputTexture = create_texture(outWidth, outHeight, DXGI_FORMAT_R16G16B16A16_FLOAT, + ID3D12Resource* outputTexture = create_texture(outWidth, outHeight, + rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); - upload(colorTexture, color.data(), resourceWidth * 8, srv); + 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(exposureTexture, &exposureValue, 4, srv); - upload(outputTexture, outputInit.data(), outWidth * 8, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + upload(outputTexture, rgba8 ? static_cast(outputInit8.data()) : static_cast(outputInit.data()), + outWidth * (rgba8 ? 4 : 8), D3D12_RESOURCE_STATE_UNORDERED_ACCESS); std::printf("synthetic inputs uploaded\n"); trace_stage("synthetic inputs uploaded"); @@ -501,7 +521,9 @@ int main(int argc, char** argv) wchar_t dataPath[MAX_PATH]; GetTempPathW(MAX_PATH, dataPath); - NgxResult result = init(241534723ULL, dataPath, g_device, 0x15, nullptr); + const char* appIdSetting = std::getenv("D4R_HARNESS_APP_ID"); + const unsigned long long appId = appIdSetting != nullptr ? std::strtoull(appIdSetting, nullptr, 10) : 241534723ULL; + NgxResult result = init(appId, dataPath, g_device, 0x15, nullptr); trace_stage(result == NGX_SUCCESS ? "NGX init succeeded" : "NGX init failed"); std::printf("NVSDK_NGX_D3D12_Init_Ext -> 0x%08x\n", result); if (result != NGX_SUCCESS) @@ -786,7 +808,7 @@ int main(int argc, char** argv) if (saveFrames) { const std::vector frameOutput = - read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8); + read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * (rgba8 ? 4 : 8)); const std::string framePath = std::string(argv[2]) + ".frame" + std::to_string(frame); if (FILE* frameFile = std::fopen(framePath.c_str(), "wb")) { @@ -796,7 +818,8 @@ int main(int argc, char** argv) } } - const std::vector output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8); + const std::vector output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + outWidth * (rgba8 ? 4 : 8)); FILE* file = std::fopen(argv[2], "wb"); if (file != nullptr) { @@ -804,9 +827,9 @@ int main(int argc, char** argv) std::fclose(file); } size_t nonzero = 0; - for (size_t index = 0; index < output.size(); index += 2) - nonzero += (output[index] | output[index + 1]) != 0; - std::printf("output read back: %zu of %zu halves nonzero, written to %s\n", nonzero, output.size() / 2, argv[2]); + for (uint8_t value : output) + nonzero += value != 0; + std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]); release(feature); shutdown(); diff --git a/tools/d4r_nvngx_shim.cpp b/tools/d4r_nvngx_shim.cpp index cc6a7fb..7da91fa 100644 --- a/tools/d4r_nvngx_shim.cpp +++ b/tools/d4r_nvngx_shim.cpp @@ -1418,8 +1418,7 @@ struct VramBuffer size_t bytes = 0; }; -// An RGBA16F image the color input is converted into when its format is not -// the canonical one (R11G11B10_FLOAT in Townfall). +// An RGBA16F image used to convert a noncanonical color input or output. struct VramImage { VkImage image = VK_NULL_HANDLE; @@ -1552,6 +1551,7 @@ struct Feature // formats later stop qualifying. bool vram = false, vramDecided = false; VramImage colorConversion; + 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). bool split = false; @@ -2025,6 +2025,8 @@ struct VulkanInterop ID3D12DXVKInteropDeviceD4R* split = nullptr; // only with the d4r vkd3d-proton patch VkDevice device = VK_NULL_HANDLE; VkPhysicalDeviceMemoryProperties memory = {}; + VkPhysicalDevice physical = VK_NULL_HANDLE; + PFN_vkGetPhysicalDeviceFormatProperties formatProperties = nullptr; PFN_vkCreateBuffer createBuffer = nullptr; PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkGetBufferMemoryRequirements bufferRequirements = nullptr; @@ -2096,7 +2098,10 @@ static void init_vram_interop() if (deviceProc == nullptr || memoryProperties == nullptr) return; memoryProperties(physical, &g_vk.memory); - bool ok = true; + g_vk.physical = physical; + g_vk.formatProperties = reinterpret_cast( + instanceProc(instance, "vkGetPhysicalDeviceFormatProperties")); + bool ok = g_vk.formatProperties != nullptr; auto load = [&](auto& function, const char* name) { function = reinterpret_cast>(deviceProc(g_vk.device, name)); ok &= function != nullptr; @@ -2292,13 +2297,37 @@ struct VramCopy ID3D12Resource* resource = nullptr; VkImage image = VK_NULL_HANDLE; VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; - bool convert = false; // colour blit into RGBA16F first + bool convert = false; // colour blit between this format and RGBA16F UINT width = 0, height = 0; }; -// Whether `resource` can be copied raw (or, for colour, blitted) into the -// canonical layout of `plane`; plane Color with rgba16 also describes the output. -static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy) +// Whether a linear color format can be blitted to/from the canonical RGBA16F. +// 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) +{ + switch (format) + { + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + break; + 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; +} + +// Whether `resource` can be copied raw (or, for linear colour, blitted) +// to/from the canonical layout of `plane`. +static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy, bool output = false) { UINT64 handle = 0, offset = 0; VkFormat format = VK_FORMAT_UNDEFINED; @@ -2314,8 +2343,12 @@ static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy.height = desc.Height; copy.aspect = (format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - copy.convert = plane == Plane::Color && format == VK_FORMAT_B10G11R11_UFLOAT_PACK32; - return copy.convert || vk_texel_bytes(format) == canonical_texel_bytes(plane); + if (plane == Plane::Color) + { + 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); } static const VkMemoryBarrier kBeforeTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_MEMORY_WRITE_BIT, @@ -2397,7 +2430,8 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list // Records the copy of a finished result into the output texture, which is in // COPY_DEST state already. -static bool record_vram_output(ID3D12GraphicsCommandList* list, const VramBuffer& buffer, const VramCopy& copy) +static bool record_vram_output(ID3D12GraphicsCommandList* list, const VramBuffer& buffer, const VramCopy& copy, + const VramImage& conversion) { VkCommandBuffer cmd = VK_NULL_HANDLE; if (FAILED(g_vk.interop->BeginVkCommandBufferInterop(list, &cmd))) @@ -2407,7 +2441,37 @@ static bool record_vram_output(ID3D12GraphicsCommandList* list, const VramBuffer const VkBufferImageCopy region = full_region(copy); g_vk.barrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &kBeforeTransfer, 0, nullptr, 0, nullptr); - g_vk.copyBufferToImage(cmd, buffer.buffer, copy.image, layout, 1, ®ion); + if (!copy.convert) + g_vk.copyBufferToImage(cmd, buffer.buffer, copy.image, layout, 1, ®ion); + else + { + VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + toDestination.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + toDestination.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toDestination.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + toDestination.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toDestination.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toDestination.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toDestination.image = conversion.image; + toDestination.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, + &toDestination); + g_vk.copyBufferToImage(cmd, buffer.buffer, conversion.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + VkImageMemoryBarrier toSource = toDestination; + toSource.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toSource.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + toSource.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + toSource.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, + &toSource); + VkImageBlit blit = {}; + blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + blit.srcOffsets[1] = {static_cast(copy.width), static_cast(copy.height), 1}; + blit.dstSubresource = blit.srcSubresource; + blit.dstOffsets[1] = blit.srcOffsets[1]; + g_vk.blit(cmd, conversion.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy.image, layout, 1, &blit, + VK_FILTER_NEAREST); + } g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &kAfterTransfer, 0, nullptr, 0, nullptr); return SUCCEEDED(g_vk.interop->EndVkCommandBufferInterop(list)); @@ -2526,9 +2590,8 @@ static bool ensure_vram_buffer(Feature& feature, VramBuffer& buffer, size_t byte return create_vram_buffer(buffer, bytes); } -static bool ensure_color_conversion(Feature& feature, UINT width, UINT height) +static bool ensure_conversion_image(Feature& feature, VramImage& image, UINT width, UINT height) { - VramImage& image = feature.colorConversion; if (image.image != VK_NULL_HANDLE && image.width == width && image.height == height) return true; if (image.image != VK_NULL_HANDLE) @@ -2551,6 +2614,7 @@ static void release_vram(Feature& feature) destroy_vram_buffer(buffer); feature.retiredBuffers.clear(); destroy_vram_image(feature.colorConversion); + destroy_vram_image(feature.outputConversion); for (VramImage& image : feature.retiredImages) destroy_vram_image(image); feature.retiredImages.clear(); @@ -4291,7 +4355,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand VramCopy vramInputs[4], vramOutput; if ((!feature->vramDecided || feature->vram) && vram_interop_available()) { - p.vram = describe_vram_copy(output, Plane::Color, vramOutput) && !vramOutput.convert; + p.vram = describe_vram_copy(output, Plane::Color, vramOutput, true); for (int index = 0; index < inputCount && p.vram; ++index) p.vram = describe_vram_copy(inputs[index], planes[index], vramInputs[index]); } @@ -4341,7 +4405,11 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand if (!ensure_vram_buffer(*feature, slot.vram[index], geometry.size())) return NGX_FAIL_PLATFORM_ERROR; } - if (vramInputs[0].convert && !ensure_color_conversion(*feature, vramInputs[0].width, vramInputs[0].height)) + if (vramInputs[0].convert && !ensure_conversion_image(*feature, feature->colorConversion, + vramInputs[0].width, vramInputs[0].height)) + return NGX_FAIL_PLATFORM_ERROR; + if (vramOutput.convert && !ensure_conversion_image(*feature, feature->outputConversion, + vramOutput.width, vramOutput.height)) return NGX_FAIL_PLATFORM_ERROR; } if ((!p.vram || verify) && @@ -4432,7 +4500,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand // Runs in the second half of the list, once frame's result is in place. const int target = static_cast(frame % kOutputSlots); transition(list, output, outputState, D3D12_RESOURCE_STATE_COPY_DEST); - if (!record_vram_output(list, feature->outputs[target].vram, vramOutput)) + if (!record_vram_output(list, feature->outputs[target].vram, vramOutput, feature->outputConversion)) logf("frame %u: BeginVkCommandBufferInterop failed for the output", frame); transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState); if (timing.enabled) @@ -4441,7 +4509,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand else if (latest >= 0 && p.vram) { transition(list, output, outputState, D3D12_RESOURCE_STATE_COPY_DEST); - if (!record_vram_output(list, feature->outputs[latest].vram, vramOutput)) + if (!record_vram_output(list, feature->outputs[latest].vram, vramOutput, feature->outputConversion)) logf("frame %u: BeginVkCommandBufferInterop failed for the output", frame); transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState); }