Fix 8-bit DLSS VRAM interop
This commit is contained in:
1 parent
bcc1fa6560
commit
818dd8bf42
7 files changed
+130
-37
No files matched your search
@@ -22,6 +22,7 @@ __pycache__/
|
|||||||
.venv/
|
.venv/
|
||||||
/Archive.zip
|
/Archive.zip
|
||||||
/vkd3d-proton.cache
|
/vkd3d-proton.cache
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# 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/
|
||||||
|
|||||||
@@ -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.
|
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.**
|
> **Not affiliated with NVIDIA or AMD.**
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
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:
|
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 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.
|
- 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:
|
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`);
|
- a wait for the marker (`GpuWait`);
|
||||||
- the input copies into CUDA arrays, or none when NGX samples the buffers in place (`LinearInputs`);
|
- the input copies into CUDA arrays, or none when NGX samples the buffers in place (`LinearInputs`);
|
||||||
|
|||||||
@@ -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
|
d4r. d4r then runs NVIDIA's DLSS on the Radeon through ZLUDA, with the heaviest parts replaced by
|
||||||
kernels written for RDNA3.
|
kernels written for RDNA3.
|
||||||
|
|
||||||
This is an early release. It has been tested on one GPU (Radeon RX 7700 XT) and one game
|
This is an early release. It has been tested on one GPU (Radeon RX 7700 XT) in
|
||||||
(SILENT HILL Townfall). Expect problems in other games.
|
SILENT HILL Townfall and Ghost of Tsushima DIRECTOR'S CUT. Expect problems in other games.
|
||||||
|
|
||||||
|
|
||||||
What you need
|
What you need
|
||||||
|
|||||||
@@ -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.
|
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
|
## 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.
|
- **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.
|
- 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.
|
- `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.
|
- **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.
|
- **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.
|
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
|
## Known limitations
|
||||||
|
|
||||||
- Only DLSS Super Resolution in DirectX 12 games. No Frame Generation or Ray Reconstruction.
|
- 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.
|
- DLSS 4.5 (M) is much slower than FSR 4 on RDNA3.
|
||||||
|
|
||||||
## Licenses
|
## 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.
|
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`
|
||||||
@@ -386,6 +386,12 @@ int main(int argc, char** argv)
|
|||||||
const bool qualityScene = std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr;
|
const bool qualityScene = std::getenv("D4R_HARNESS_QUALITY_SCENE") != nullptr;
|
||||||
const bool poleScene = std::getenv("D4R_HARNESS_POLE_SCENE") != nullptr;
|
const bool poleScene = std::getenv("D4R_HARNESS_POLE_SCENE") != nullptr;
|
||||||
const char* replayDir = std::getenv("D4R_HARNESS_REPLAY_DIR");
|
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
|
const uint32_t replayStart = std::getenv("D4R_HARNESS_REPLAY_START") != nullptr
|
||||||
? static_cast<uint32_t>(std::atoi(std::getenv("D4R_HARNESS_REPLAY_START"))) : 1;
|
? static_cast<uint32_t>(std::atoi(std::getenv("D4R_HARNESS_REPLAY_START"))) : 1;
|
||||||
const bool temporal = std::getenv("D4R_HARNESS_TEMPORAL") != nullptr || motionScene || jitterScene || qualityScene ||
|
const bool temporal = std::getenv("D4R_HARNESS_TEMPORAL") != nullptr || motionScene || jitterScene || qualityScene ||
|
||||||
@@ -464,19 +470,33 @@ int main(int argc, char** argv)
|
|||||||
std::vector<uint16_t> motion(static_cast<size_t>(motionWidth) * motionHeight * 2, 0);
|
std::vector<uint16_t> motion(static_cast<size_t>(motionWidth) * motionHeight * 2, 0);
|
||||||
const float exposureValue = 1.0f;
|
const float exposureValue = 1.0f;
|
||||||
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
|
std::vector<uint16_t> outputInit(static_cast<size_t>(outWidth) * outHeight * 4, 0);
|
||||||
|
std::vector<uint8_t> color8;
|
||||||
|
std::vector<uint8_t> outputInit8;
|
||||||
|
if (rgba8)
|
||||||
|
{
|
||||||
|
color8.resize(color.size());
|
||||||
|
for (size_t index = 0; index < color.size(); ++index)
|
||||||
|
color8[index] = static_cast<uint8_t>(std::lround(
|
||||||
|
std::clamp(static_cast<float>(std::bit_cast<_Float16>(color[index])), 0.0f, 1.0f) * 255.0f));
|
||||||
|
outputInit8.resize(static_cast<size_t>(outWidth) * outHeight * 4);
|
||||||
|
}
|
||||||
|
|
||||||
const auto srv = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
|
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* 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* 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, 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);
|
D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS);
|
||||||
upload(colorTexture, color.data(), resourceWidth * 8, srv);
|
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(depthTexture, depth.data(), resourceWidth * 4, srv);
|
||||||
upload(motionTexture, motion.data(), motionWidth * 4, srv);
|
upload(motionTexture, motion.data(), motionWidth * 4, srv);
|
||||||
upload(exposureTexture, &exposureValue, 4, srv);
|
upload(exposureTexture, &exposureValue, 4, srv);
|
||||||
upload(outputTexture, outputInit.data(), outWidth * 8, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
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");
|
std::printf("synthetic inputs uploaded\n");
|
||||||
trace_stage("synthetic inputs uploaded");
|
trace_stage("synthetic inputs uploaded");
|
||||||
|
|
||||||
@@ -501,7 +521,9 @@ int main(int argc, char** argv)
|
|||||||
|
|
||||||
wchar_t dataPath[MAX_PATH];
|
wchar_t dataPath[MAX_PATH];
|
||||||
GetTempPathW(MAX_PATH, dataPath);
|
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");
|
trace_stage(result == NGX_SUCCESS ? "NGX init succeeded" : "NGX init failed");
|
||||||
std::printf("NVSDK_NGX_D3D12_Init_Ext -> 0x%08x\n", result);
|
std::printf("NVSDK_NGX_D3D12_Init_Ext -> 0x%08x\n", result);
|
||||||
if (result != NGX_SUCCESS)
|
if (result != NGX_SUCCESS)
|
||||||
@@ -786,7 +808,7 @@ int main(int argc, char** argv)
|
|||||||
if (saveFrames)
|
if (saveFrames)
|
||||||
{
|
{
|
||||||
const std::vector<uint8_t> frameOutput =
|
const std::vector<uint8_t> 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);
|
const std::string framePath = std::string(argv[2]) + ".frame" + std::to_string(frame);
|
||||||
if (FILE* frameFile = std::fopen(framePath.c_str(), "wb"))
|
if (FILE* frameFile = std::fopen(framePath.c_str(), "wb"))
|
||||||
{
|
{
|
||||||
@@ -796,7 +818,8 @@ int main(int argc, char** argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8_t> output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, outWidth * 8);
|
const std::vector<uint8_t> output = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
||||||
|
outWidth * (rgba8 ? 4 : 8));
|
||||||
FILE* file = std::fopen(argv[2], "wb");
|
FILE* file = std::fopen(argv[2], "wb");
|
||||||
if (file != nullptr)
|
if (file != nullptr)
|
||||||
{
|
{
|
||||||
@@ -804,9 +827,9 @@ int main(int argc, char** argv)
|
|||||||
std::fclose(file);
|
std::fclose(file);
|
||||||
}
|
}
|
||||||
size_t nonzero = 0;
|
size_t nonzero = 0;
|
||||||
for (size_t index = 0; index < output.size(); index += 2)
|
for (uint8_t value : output)
|
||||||
nonzero += (output[index] | output[index + 1]) != 0;
|
nonzero += value != 0;
|
||||||
std::printf("output read back: %zu of %zu halves nonzero, written to %s\n", nonzero, output.size() / 2, argv[2]);
|
std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]);
|
||||||
|
|
||||||
release(feature);
|
release(feature);
|
||||||
shutdown();
|
shutdown();
|
||||||
|
|||||||
+85
-17
@@ -1418,8 +1418,7 @@ struct VramBuffer
|
|||||||
size_t bytes = 0;
|
size_t bytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// An RGBA16F image the color input is converted into when its format is not
|
// An RGBA16F image used to convert a noncanonical color input or output.
|
||||||
// the canonical one (R11G11B10_FLOAT in Townfall).
|
|
||||||
struct VramImage
|
struct VramImage
|
||||||
{
|
{
|
||||||
VkImage image = VK_NULL_HANDLE;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
@@ -1552,6 +1551,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 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
|
||||||
// splitSemaphore reaches N (signalled when N retires, or by the watchdog).
|
// splitSemaphore reaches N (signalled when N retires, or by the watchdog).
|
||||||
bool split = false;
|
bool split = false;
|
||||||
@@ -2025,6 +2025,8 @@ struct VulkanInterop
|
|||||||
ID3D12DXVKInteropDeviceD4R* split = nullptr; // only with the d4r vkd3d-proton patch
|
ID3D12DXVKInteropDeviceD4R* split = nullptr; // only with the d4r vkd3d-proton patch
|
||||||
VkDevice device = VK_NULL_HANDLE;
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
VkPhysicalDeviceMemoryProperties memory = {};
|
VkPhysicalDeviceMemoryProperties memory = {};
|
||||||
|
VkPhysicalDevice physical = VK_NULL_HANDLE;
|
||||||
|
PFN_vkGetPhysicalDeviceFormatProperties formatProperties = nullptr;
|
||||||
PFN_vkCreateBuffer createBuffer = nullptr;
|
PFN_vkCreateBuffer createBuffer = nullptr;
|
||||||
PFN_vkDestroyBuffer destroyBuffer = nullptr;
|
PFN_vkDestroyBuffer destroyBuffer = nullptr;
|
||||||
PFN_vkGetBufferMemoryRequirements bufferRequirements = nullptr;
|
PFN_vkGetBufferMemoryRequirements bufferRequirements = nullptr;
|
||||||
@@ -2096,7 +2098,10 @@ static void init_vram_interop()
|
|||||||
if (deviceProc == nullptr || memoryProperties == nullptr)
|
if (deviceProc == nullptr || memoryProperties == nullptr)
|
||||||
return;
|
return;
|
||||||
memoryProperties(physical, &g_vk.memory);
|
memoryProperties(physical, &g_vk.memory);
|
||||||
bool ok = true;
|
g_vk.physical = physical;
|
||||||
|
g_vk.formatProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceFormatProperties>(
|
||||||
|
instanceProc(instance, "vkGetPhysicalDeviceFormatProperties"));
|
||||||
|
bool ok = g_vk.formatProperties != nullptr;
|
||||||
auto load = [&](auto& function, const char* name) {
|
auto load = [&](auto& function, const char* name) {
|
||||||
function = reinterpret_cast<std::remove_reference_t<decltype(function)>>(deviceProc(g_vk.device, name));
|
function = reinterpret_cast<std::remove_reference_t<decltype(function)>>(deviceProc(g_vk.device, name));
|
||||||
ok &= function != nullptr;
|
ok &= function != nullptr;
|
||||||
@@ -2292,13 +2297,37 @@ struct VramCopy
|
|||||||
ID3D12Resource* resource = nullptr;
|
ID3D12Resource* resource = nullptr;
|
||||||
VkImage image = VK_NULL_HANDLE;
|
VkImage image = VK_NULL_HANDLE;
|
||||||
VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
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;
|
UINT width = 0, height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Whether `resource` can be copied raw (or, for colour, blitted) into the
|
// Whether a linear color format can be blitted to/from the canonical RGBA16F.
|
||||||
// canonical layout of `plane`; plane Color with rgba16 also describes the output.
|
// sRGB is deliberately excluded: the host staging path treats its bytes as
|
||||||
static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy)
|
// 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;
|
UINT64 handle = 0, offset = 0;
|
||||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||||
@@ -2314,8 +2343,12 @@ static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy&
|
|||||||
copy.height = desc.Height;
|
copy.height = desc.Height;
|
||||||
copy.aspect = (format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
copy.aspect = (format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT)
|
||||||
? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
copy.convert = plane == Plane::Color && format == VK_FORMAT_B10G11R11_UFLOAT_PACK32;
|
if (plane == Plane::Color)
|
||||||
return copy.convert || vk_texel_bytes(format) == canonical_texel_bytes(plane);
|
{
|
||||||
|
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,
|
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
|
// Records the copy of a finished result into the output texture, which is in
|
||||||
// COPY_DEST state already.
|
// 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;
|
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||||
if (FAILED(g_vk.interop->BeginVkCommandBufferInterop(list, &cmd)))
|
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);
|
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,
|
g_vk.barrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &kBeforeTransfer, 0,
|
||||||
nullptr, 0, nullptr);
|
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<int32_t>(copy.width), static_cast<int32_t>(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,
|
g_vk.barrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &kAfterTransfer, 0,
|
||||||
nullptr, 0, nullptr);
|
nullptr, 0, nullptr);
|
||||||
return SUCCEEDED(g_vk.interop->EndVkCommandBufferInterop(list));
|
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);
|
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)
|
if (image.image != VK_NULL_HANDLE && image.width == width && image.height == height)
|
||||||
return true;
|
return true;
|
||||||
if (image.image != VK_NULL_HANDLE)
|
if (image.image != VK_NULL_HANDLE)
|
||||||
@@ -2551,6 +2614,7 @@ static void release_vram(Feature& feature)
|
|||||||
destroy_vram_buffer(buffer);
|
destroy_vram_buffer(buffer);
|
||||||
feature.retiredBuffers.clear();
|
feature.retiredBuffers.clear();
|
||||||
destroy_vram_image(feature.colorConversion);
|
destroy_vram_image(feature.colorConversion);
|
||||||
|
destroy_vram_image(feature.outputConversion);
|
||||||
for (VramImage& image : feature.retiredImages)
|
for (VramImage& image : feature.retiredImages)
|
||||||
destroy_vram_image(image);
|
destroy_vram_image(image);
|
||||||
feature.retiredImages.clear();
|
feature.retiredImages.clear();
|
||||||
@@ -4291,7 +4355,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
|||||||
VramCopy vramInputs[4], vramOutput;
|
VramCopy vramInputs[4], vramOutput;
|
||||||
if ((!feature->vramDecided || feature->vram) && vram_interop_available())
|
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)
|
for (int index = 0; index < inputCount && p.vram; ++index)
|
||||||
p.vram = describe_vram_copy(inputs[index], planes[index], vramInputs[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()))
|
if (!ensure_vram_buffer(*feature, slot.vram[index], geometry.size()))
|
||||||
return NGX_FAIL_PLATFORM_ERROR;
|
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;
|
return NGX_FAIL_PLATFORM_ERROR;
|
||||||
}
|
}
|
||||||
if ((!p.vram || verify) &&
|
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.
|
// Runs in the second half of the list, once frame's result is in place.
|
||||||
const int target = static_cast<int>(frame % kOutputSlots);
|
const int target = static_cast<int>(frame % kOutputSlots);
|
||||||
transition(list, output, outputState, D3D12_RESOURCE_STATE_COPY_DEST);
|
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);
|
logf("frame %u: BeginVkCommandBufferInterop failed for the output", frame);
|
||||||
transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState);
|
transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState);
|
||||||
if (timing.enabled)
|
if (timing.enabled)
|
||||||
@@ -4441,7 +4509,7 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
|||||||
else if (latest >= 0 && p.vram)
|
else if (latest >= 0 && p.vram)
|
||||||
{
|
{
|
||||||
transition(list, output, outputState, D3D12_RESOURCE_STATE_COPY_DEST);
|
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);
|
logf("frame %u: BeginVkCommandBufferInterop failed for the output", frame);
|
||||||
transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState);
|
transition(list, output, D3D12_RESOURCE_STATE_COPY_DEST, outputState);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user