Convert native Windows D3D12 formats on gfx1201 with GPU validation
This commit is contained in:
1 parent
312c6bb50d
commit
e9eec6d4e0
12 files changed
+657
-29
No files matched your search
@@ -3,6 +3,7 @@
|
||||
#include "ngx_cuda_evaluate.h"
|
||||
#include <dxgi1_6.h>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
using namespace d4r::diag;
|
||||
@@ -17,6 +18,8 @@ int main(int argc, char** argv) {
|
||||
const std::filesystem::path directory(diagDir ? wide(diagDir) : std::filesystem::current_path().wstring());
|
||||
std::filesystem::create_directories(directory);
|
||||
const bool commandBackend = args.interop_mode == "command-list";
|
||||
if (args.interop_mode == "images" && args.pixel_profile != "baseline") throw std::runtime_error("Raw CUDA image diagnostic requires baseline formats");
|
||||
std::printf("D3D12_PIXEL_PROFILE %s\n", args.pixel_profile.c_str());
|
||||
if (commandBackend && _putenv_s("D4R_D3D12_COMMAND_BACKEND", "1")) throw std::runtime_error("Cannot enable command backend");
|
||||
for (const auto& pair : {std::make_pair("D4R_HIP_ROOT", args.hip_root),
|
||||
std::make_pair("D4R_NVCUDA_DLL", args.cuda_dll), std::make_pair("D4R_NVAPI_DLL", args.nvapi_dll),
|
||||
@@ -63,6 +66,10 @@ int main(int argc, char** argv) {
|
||||
desc.Height = i == 4 ? 1 : i == 3 ? outHeight : height;
|
||||
desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
|
||||
desc.Format = i == 0 || i == 3 ? DXGI_FORMAT_R16G16B16A16_FLOAT : i == 2 ? DXGI_FORMAT_R16G16_FLOAT : DXGI_FORMAT_R32_FLOAT;
|
||||
if (args.pixel_profile == "packed") desc.Format = i == 0 || i == 3 ? DXGI_FORMAT_R11G11B10_FLOAT :
|
||||
i == 2 ? DXGI_FORMAT_R32G32B32A32_FLOAT : i == 4 ? DXGI_FORMAT_R16_FLOAT : DXGI_FORMAT_R32_FLOAT;
|
||||
if (args.pixel_profile == "unorm") desc.Format = i == 3 ? DXGI_FORMAT_B8G8R8A8_UNORM :
|
||||
i == 2 ? DXGI_FORMAT_R16G16_SNORM : i == 4 ? DXGI_FORMAT_R32G32B32A32_FLOAT : desc.Format;
|
||||
if (i == 3) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
heap.CreationNodeMask = heap.VisibleNodeMask = 1;
|
||||
@@ -79,11 +86,16 @@ int main(int argc, char** argv) {
|
||||
if (i == 0) {
|
||||
uint16_t pixel[] = {uint16_t(((x / 16 + y / 16) & 1) ? 0x3a00 : 0x3400),
|
||||
uint16_t((x & 32) ? 0x3800 : 0x3400), uint16_t((y & 32) ? 0x3a00 : 0x3800), 0x3c00};
|
||||
std::memcpy(row + x * 8, pixel, sizeof(pixel));
|
||||
if (desc.Format == DXGI_FORMAT_R11G11B10_FLOAT) {
|
||||
const uint32_t packed = uint32_t(pixel[0] >> 4) | (uint32_t(pixel[1] >> 4) << 11) | (uint32_t(pixel[2] >> 5) << 22);
|
||||
std::memcpy(row + x * 4, &packed, 4);
|
||||
} else std::memcpy(row + x * 8, pixel, sizeof(pixel));
|
||||
} else if (i == 1 || i == 4) {
|
||||
const float value = i == 1 ? .5f : 1.f; std::memcpy(row + x * 4, &value, 4);
|
||||
if (desc.Format == DXGI_FORMAT_R16_FLOAT) { const uint16_t one = 0x3c00; std::memcpy(row + x * 2, &one, 2); }
|
||||
else { const float value = i == 1 ? .5f : 1.f; std::memcpy(row + x * 4, &value, 4); }
|
||||
} else if (i == 3) {
|
||||
const uint16_t pixel[] = {0x7e00, 0x7e00, 0x7e00, 0x7e00}; std::memcpy(row + x * 8, pixel, 8);
|
||||
if (desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT) { const uint16_t pixel[] = {0x7e00, 0x7e00, 0x7e00, 0x7e00}; std::memcpy(row + x * 8, pixel, 8); }
|
||||
else { const uint32_t sentinel = 0xffffffff; std::memcpy(row + x * 4, &sentinel, 4); }
|
||||
}
|
||||
}
|
||||
texture.upload->Unmap(0, nullptr);
|
||||
@@ -206,8 +218,30 @@ int main(int argc, char** argv) {
|
||||
void* raw = nullptr; D3D12_RANGE range{0, size_t(textures[3].bytes)};
|
||||
dx(readback->Map(0, &range, &raw), "Map(verification only)");
|
||||
std::vector<uint16_t> pixels(size_t(outWidth) * outHeight * 4);
|
||||
for (unsigned y = 0; y < outHeight; ++y) std::memcpy(pixels.data() + size_t(y) * outWidth * 4,
|
||||
static_cast<uint8_t*>(raw) + y * textures[3].footprint.Footprint.RowPitch, outWidth * 8);
|
||||
const auto format = resource_desc(textures[3].image.Get()).Format;
|
||||
if (format == DXGI_FORMAT_R16G16B16A16_FLOAT) {
|
||||
for (unsigned y = 0; y < outHeight; ++y) std::memcpy(pixels.data() + size_t(y) * outWidth * 4,
|
||||
static_cast<uint8_t*>(raw) + y * textures[3].footprint.Footprint.RowPitch, outWidth * 8);
|
||||
} else {
|
||||
std::ofstream native(directory / ("output-" + std::to_string(frame) + (args.pixel_profile == "packed" ? ".r11g11b10f" : ".bgra8")), std::ios::binary);
|
||||
// Independently decode the diagnostic readback for previews and
|
||||
// frame comparisons. No CPU image copies are in the backend.
|
||||
static const auto unormHalf = [] {
|
||||
std::array<uint16_t, 256> lut{};
|
||||
for (unsigned b = 0; b < 256; ++b) { double best = INFINITY; for (unsigned h = 0; h <= 0x3c00; ++h) { double error = std::abs(double(half_value(uint16_t(h))) - double(b)/255.); if (error < best || (error == best && !(h & 1))) { best = error; lut[b] = uint16_t(h); } } }
|
||||
return lut;
|
||||
}();
|
||||
for (unsigned y = 0; y < outHeight; ++y) {
|
||||
const auto* row = static_cast<uint8_t*>(raw) + y * textures[3].footprint.Footprint.RowPitch;
|
||||
native.write(reinterpret_cast<const char*>(row), outWidth * 4);
|
||||
for (unsigned x = 0; x < outWidth; ++x) {
|
||||
auto* p = pixels.data() + (size_t(y) * outWidth + x) * 4;
|
||||
if (format == DXGI_FORMAT_R11G11B10_FLOAT) { uint32_t value = 0; std::memcpy(&value,row + x * 4,4); p[0] = uint16_t((value & 2047) << 4); p[1] = uint16_t(((value >> 11) & 2047) << 4); p[2] = uint16_t((value >> 22) << 5); p[3] = 0x3c00; }
|
||||
else for (unsigned c = 0; c < 4; ++c) p[c] = unormHalf[row[x * 4 + (c == 0 ? 2 : c == 2 ? 0 : c)]];
|
||||
}
|
||||
}
|
||||
if (!native) throw std::runtime_error("Saving native output format failed");
|
||||
}
|
||||
D3D12_RANGE noWrite{}; readback->Unmap(0, &noWrite);
|
||||
double sum = 0, square = 0;
|
||||
for (size_t i = 0; i < pixels.size(); ++i) {
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
// Independent reference: D3D12 typed SRV loads and typed UAV stores.
|
||||
#include "pixel_conversion.h"
|
||||
#include <dxgi1_6.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
using namespace d4r::diag;
|
||||
using namespace d4r::win;
|
||||
namespace {
|
||||
constexpr unsigned width = 259, height = 17, count = width * height;
|
||||
struct Case { DXGI_FORMAT resource, view; unsigned plane; const char* name; };
|
||||
const Case cases[] = {
|
||||
{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, 0, "rgba16f"},
|
||||
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, "rgba32f"},
|
||||
{DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, 0, "rgba8"},
|
||||
{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, 0, "bgra8"},
|
||||
{DXGI_FORMAT_R11G11B10_FLOAT, DXGI_FORMAT_R11G11B10_FLOAT, 0, "r11g11b10f"},
|
||||
{DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, 0, "rgb10a2"},
|
||||
{DXGI_FORMAT_R16G16_FLOAT, DXGI_FORMAT_R16G16_FLOAT, 2, "rg16f"},
|
||||
{DXGI_FORMAT_R32G32_FLOAT, DXGI_FORMAT_R32G32_FLOAT, 2, "rg32f"},
|
||||
{DXGI_FORMAT_R16G16_SNORM, DXGI_FORMAT_R16G16_SNORM, 2, "rg16snorm"},
|
||||
{DXGI_FORMAT_R16G16_UNORM, DXGI_FORMAT_R16G16_UNORM, 2, "rg16unorm"},
|
||||
{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, 2, "motion-rgba16f"},
|
||||
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 2, "motion-rgba32f"},
|
||||
{DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 1, "depth-r32f"},
|
||||
{DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 1, "depth-r16unorm"},
|
||||
{DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 1, "depth24"},
|
||||
{DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_FLOAT, 4, "exposure-r16f"},
|
||||
{DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 4, "exposure-r32f"},
|
||||
{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, 4, "exposure-rgba16f"},
|
||||
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, 4, "exposure-rgba32f"},
|
||||
};
|
||||
struct List {
|
||||
ComPtr<ID3D12CommandAllocator> alloc;
|
||||
ComPtr<ID3D12GraphicsCommandList> list;
|
||||
explicit List(ID3D12Device* device) {
|
||||
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(alloc.GetAddressOf())), "Pixel allocator");
|
||||
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, alloc.Get(), nullptr, IID_PPV_ARGS(list.GetAddressOf())), "Pixel command list");
|
||||
}
|
||||
void submit(ID3D12CommandQueue* queue, SharedTimeline& timeline) {
|
||||
dx(list->Close(), "Close pixel list"); ID3D12CommandList* lists[] = {list.Get()};
|
||||
queue->ExecuteCommandLists(1, lists); timeline.drain(queue);
|
||||
}
|
||||
};
|
||||
ComPtr<ID3D12Resource> make_texture(ID3D12Device* device, DXGI_FORMAT fmt, bool uav = false) {
|
||||
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
desc.Width = width; desc.Height = height; desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
|
||||
desc.Format = fmt; desc.Flags = uav ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE;
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; heap.CreationNodeMask = heap.VisibleNodeMask = 1;
|
||||
ComPtr<ID3D12Resource> result;
|
||||
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON,
|
||||
nullptr, IID_PPV_ARGS(result.GetAddressOf())), "Pixel texture"); return result;
|
||||
}
|
||||
ComPtr<ID3D12Resource> uav_buffer(ID3D12Device* device) {
|
||||
D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = count * 16;
|
||||
desc.Height = desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; heap.CreationNodeMask = heap.VisibleNodeMask = 1;
|
||||
ComPtr<ID3D12Resource> result;
|
||||
dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
||||
nullptr, IID_PPV_ARGS(result.GetAddressOf())), "Pixel reference buffer"); return result;
|
||||
}
|
||||
void fixture(uint8_t* row, unsigned y, PixelSpec spec) {
|
||||
for (unsigned x = 0; x < width; ++x) {
|
||||
unsigned i = x + y * width; auto* p = row + x * spec.bytes;
|
||||
const uint16_t half[] = {0, 1, 0x3ff, 0x400, 0x1400, 0x3555, 0x3800, 0x3bff, 0x3c00, 0x3c01, 0x5c07, 0x7bff, 0x8001, 0xbc00};
|
||||
switch (spec.storage) {
|
||||
case d4r::pixel::rgba16f: case d4r::pixel::rg16f: case d4r::pixel::r16f:
|
||||
for (unsigned c = 0; c < spec.bytes / 2; ++c) reinterpret_cast<uint16_t*>(p)[c] = half[(i + c * 3) % std::size(half)]; break;
|
||||
case d4r::pixel::rgba32f: case d4r::pixel::rg32f: case d4r::pixel::r32f:
|
||||
for (unsigned c = 0; c < spec.bytes / 4; ++c) reinterpret_cast<float*>(p)[c] = float(int((i * 23 + c * 13) % 4097) - 2048) / 4096.f; break;
|
||||
case d4r::pixel::r11g11b10f:
|
||||
*reinterpret_cast<uint32_t*>(p) = (i % 1984) | (((i * 13) % 1984) << 11) | (((i * 7) % 992) << 22); break;
|
||||
case d4r::pixel::rgb10a2:
|
||||
*reinterpret_cast<uint32_t*>(p) = (i % 1024) | (((i * 13) % 1024) << 10) | (((i * 7) % 1024) << 20) | ((i % 4) << 30); break;
|
||||
case d4r::pixel::rgba8: case d4r::pixel::bgra8:
|
||||
for (unsigned c = 0; c < 4; ++c) p[c] = uint8_t(i * (2 * c + 1) + c * 43); break;
|
||||
case d4r::pixel::rg16snorm: case d4r::pixel::rg16unorm: case d4r::pixel::r16unorm:
|
||||
for (unsigned c = 0; c < spec.bytes / 2; ++c) reinterpret_cast<uint16_t*>(p)[c] = uint16_t(i < 4 ? i * 32767 : i * 193 + c * 37); break;
|
||||
case d4r::pixel::depth24: *reinterpret_cast<uint32_t*>(p) = (i * 3791) | ((i % 256) << 24); break;
|
||||
default: throw std::runtime_error("Missing diagnostic fixture");
|
||||
}
|
||||
}
|
||||
}
|
||||
uint16_t reference_half(uint32_t bits) {
|
||||
// Independent nearest-value search over the binary16 number line. HLSL
|
||||
// f32tof16 may truncate; it is not the CUDA RNE conversion being tested.
|
||||
static const std::vector<double> values = [] {
|
||||
std::vector<double> v(31744);
|
||||
for (unsigned h = 0; h < v.size(); ++h) v[h] = h < 1024 ? std::ldexp(double(h), -24) :
|
||||
std::ldexp(1. + double(h & 1023) / 1024., int(h >> 10) - 15);
|
||||
return v;
|
||||
}();
|
||||
float value = 0; std::memcpy(&value, &bits, 4);
|
||||
double magnitude = std::abs(double(value));
|
||||
if (!std::isfinite(magnitude) || magnitude > values.back()) throw std::runtime_error("Unexpected non-finite decode reference");
|
||||
auto upper = std::lower_bound(values.begin(), values.end(), magnitude);
|
||||
unsigned h = unsigned(upper - values.begin());
|
||||
if (h && (magnitude - values[h - 1] < values[h] - magnitude ||
|
||||
(magnitude - values[h - 1] == values[h] - magnitude && (h & 1)))) --h;
|
||||
return uint16_t(h | ((bits >> 16) & 0x8000));
|
||||
}
|
||||
struct Shaders {
|
||||
ComPtr<ID3D12RootSignature> root;
|
||||
ComPtr<ID3D12PipelineState> decode, encode;
|
||||
explicit Shaders(ID3D12Device* device) {
|
||||
wchar_t system[32768]{}; if (!GetSystemDirectoryW(system, 32768)) throw std::runtime_error("System path");
|
||||
Library compiler(std::filesystem::path(system) / L"d3dcompiler_47.dll");
|
||||
auto compile = compiler.symbol<decltype(&D3DCompile)>("D3DCompile");
|
||||
const char* source = R"(
|
||||
Texture2D<float4> Input : register(t0);
|
||||
StructuredBuffer<uint4> Canonical : register(t1);
|
||||
RWStructuredBuffer<uint4> Output : register(u0);
|
||||
RWTexture2D<float4> Expected : register(u1);
|
||||
cbuffer Constants : register(b0) { uint Width, Height, Plane; };
|
||||
[numthreads(64,1,1)] void read(uint3 t:SV_DispatchThreadID) {
|
||||
if (t.x >= Width*Height) return;
|
||||
float4 v = Input.Load(int3(t.x % Width,t.x / Width,0));
|
||||
Output[t.x] = asuint(v);
|
||||
}
|
||||
[numthreads(64,1,1)] void write(uint3 t:SV_DispatchThreadID) {
|
||||
if (t.x < Width*Height) Expected[uint2(t.x % Width,t.x / Width)] = f16tof32(Canonical[t.x]);
|
||||
})";
|
||||
D3D12_DESCRIPTOR_RANGE range[3]{};
|
||||
for (unsigned i = 0; i < 3; ++i) { range[i].RangeType = i == 2 ? D3D12_DESCRIPTOR_RANGE_TYPE_UAV : D3D12_DESCRIPTOR_RANGE_TYPE_SRV; range[i].NumDescriptors = 1; range[i].BaseShaderRegister = i == 2 ? 1 : i; }
|
||||
D3D12_ROOT_PARAMETER parameters[5]{};
|
||||
for (unsigned i = 0; i < 3; ++i) { parameters[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; parameters[i].DescriptorTable.NumDescriptorRanges = 1; parameters[i].DescriptorTable.pDescriptorRanges = &range[i]; }
|
||||
parameters[3].ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV;
|
||||
parameters[4].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; parameters[4].Constants.Num32BitValues = 3;
|
||||
D3D12_ROOT_SIGNATURE_DESC sd{}; sd.NumParameters = 5; sd.pParameters = parameters;
|
||||
ComPtr<ID3DBlob> blob, errors; dx(D3D12SerializeRootSignature(&sd, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddressOf(), errors.GetAddressOf()), "Pixel root serialize");
|
||||
dx(device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(root.GetAddressOf())), "Pixel root");
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
if (FAILED(compile(source, std::strlen(source), nullptr, nullptr, nullptr, i ? "write" : "read", "cs_5_1", D3DCOMPILE_OPTIMIZATION_LEVEL3,
|
||||
0, blob.ReleaseAndGetAddressOf(), errors.ReleaseAndGetAddressOf()))) throw std::runtime_error(errors ? std::string(static_cast<char*>(errors->GetBufferPointer())) : "Pixel HLSL compilation");
|
||||
D3D12_COMPUTE_PIPELINE_STATE_DESC pd{}; pd.pRootSignature = root.Get(); pd.CS = {blob->GetBufferPointer(), blob->GetBufferSize()};
|
||||
dx(device->CreateComputePipelineState(&pd, IID_PPV_ARGS((i ? encode : decode).GetAddressOf())), "Pixel PSO");
|
||||
}
|
||||
}
|
||||
};
|
||||
std::vector<uint32_t> read_reference(ID3D12Device* device, ID3D12CommandQueue* queue, SharedTimeline& timeline,
|
||||
Shaders& shader, ID3D12DescriptorHeap* heap, D3D12_GPU_DESCRIPTOR_HANDLE srv, ID3D12Resource* reference, unsigned plane) {
|
||||
auto readback = make_buffer(device, count * 16, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
List list(device); list.list->SetPipelineState(shader.decode.Get()); list.list->SetComputeRootSignature(shader.root.Get());
|
||||
list.list->SetDescriptorHeaps(1, &heap); list.list->SetComputeRootDescriptorTable(0, srv);
|
||||
list.list->SetComputeRootUnorderedAccessView(3, reference->GetGPUVirtualAddress());
|
||||
const unsigned constants[] = {width, height, plane}; list.list->SetComputeRoot32BitConstants(4, 3, constants, 0);
|
||||
list.list->Dispatch((count + 63) / 64, 1, 1);
|
||||
transition(list.list.Get(), reference, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
list.list->CopyResource(readback.Get(), reference);
|
||||
transition(list.list.Get(), reference, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
|
||||
list.submit(queue, timeline);
|
||||
void* data = nullptr; D3D12_RANGE range{0, count * 16}; dx(readback->Map(0, &range, &data), "Pixel readback");
|
||||
std::vector<uint32_t> result(count * 4); std::memcpy(result.data(), data, count * 16); D3D12_RANGE noWrite{}; readback->Unmap(0, &noWrite); return result;
|
||||
}
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
start();
|
||||
try {
|
||||
Args args(argc, argv); HipApi hip(args.hip_root); hipDeviceProp_t props{}; hip.select_gfx1201(args.device, props);
|
||||
ExternalApi external{hip}; PixelProgram program(hip, wide(args.module));
|
||||
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "Pixel DXGI");
|
||||
ComPtr<IDXGIAdapter1> adapter;
|
||||
for (UINT i = 0; ; ++i) { dx(factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()), "Pixel adapter"); DXGI_ADAPTER_DESC1 d{}; dx(adapter->GetDesc1(&d), "Pixel adapter desc"); if (!std::memcmp(&d.AdapterLuid, props.luid, sizeof(LUID))) break; }
|
||||
ComPtr<ID3D12Device> device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Pixel D3D12");
|
||||
ComPtr<ID3D12CommandQueue> queue; D3D12_COMMAND_QUEUE_DESC qd{}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; dx(device->CreateCommandQueue(&qd, IID_PPV_ARGS(queue.GetAddressOf())), "Pixel queue");
|
||||
SharedTimeline timeline(external, device.Get()); Shaders shader(device.Get()); auto reference = uav_buffer(device.Get());
|
||||
unsigned verified = 0;
|
||||
for (unsigned iteration = 0; iteration < args.iterations; ++iteration) for (const auto& item : cases) {
|
||||
PixelSpec spec = pixel_spec(item.plane, item.resource); if (!pixel_supported(item.plane, spec.storage)) throw std::runtime_error("Unsupported fixture plane");
|
||||
auto input = make_texture(device.Get(), item.resource); SharedPlane shared(external, device.Get(), resource_desc(input.Get()));
|
||||
auto upload = make_buffer(device.Get(), shared.bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
void* data = nullptr; D3D12_RANGE noRead{}; dx(upload->Map(0, &noRead, &data), "Pixel fixture upload");
|
||||
std::memset(data, 0x5a, size_t(shared.bytes)); for (unsigned y = 0; y < height; ++y) fixture(static_cast<uint8_t*>(data) + y * shared.footprint.Footprint.RowPitch, y, spec); upload->Unmap(0, nullptr);
|
||||
{ List list(device.Get()); D3D12_TEXTURE_COPY_LOCATION src{}, dst{}; src.pResource = upload.Get(); src.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; src.PlacedFootprint = shared.footprint; dst.pResource = input.Get(); dst.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
transition(list.list.Get(), input.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST); list.list->CopyTextureRegion(&dst, 0, 0, 0, &src, nullptr); transition(list.list.Get(), input.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); shared.copy_input(list.list.Get(), input.Get(), D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); list.submit(queue.Get(), timeline); }
|
||||
D3D12_DESCRIPTOR_HEAP_DESC hd{}; hd.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; hd.NumDescriptors = 4; hd.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
ComPtr<ID3D12DescriptorHeap> heap; dx(device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(heap.GetAddressOf())), "Pixel descriptors");
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpu{}, initial{}; D3D12_GPU_DESCRIPTOR_HANDLE gpu{};
|
||||
#if defined(__MINGW32__)
|
||||
heap->GetCPUDescriptorHandleForHeapStart(&cpu); heap->GetGPUDescriptorHandleForHeapStart(&gpu);
|
||||
#else
|
||||
cpu = heap->GetCPUDescriptorHandleForHeapStart(); gpu = heap->GetGPUDescriptorHandleForHeapStart();
|
||||
#endif
|
||||
initial = cpu; unsigned stride = device->GetDescriptorHandleIncrementSize(hd.Type);
|
||||
D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; srv.Format = item.view; srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.Texture2D.MipLevels = 1; device->CreateShaderResourceView(input.Get(), &srv, cpu);
|
||||
auto expected = read_reference(device.Get(), queue.Get(), timeline, shader, heap.Get(), gpu, reference.Get(), item.plane);
|
||||
unsigned channels = item.plane == 2 ? 2 : item.plane == 0 ? 4 : 1; unsigned element = item.plane == 0 || item.plane == 2 ? 2 : 4;
|
||||
PixelAllocation canonical(hip, width, height, channels * element);
|
||||
std::vector<uint8_t> guard(size_t(canonical.pitch * height), 0xcd);
|
||||
hip.check(hip.hipMemcpy(canonical.data, guard.data(), guard.size(), hipMemcpyHostToDevice), "Diagnostic pitch guard initialization");
|
||||
program.convert(false, shared.mapped, shared.footprint.Footprint.RowPitch, canonical.data, canonical.pitch, width, height, spec.storage, item.plane);
|
||||
std::vector<uint8_t> actual(size_t(canonical.pitch * height)); hip.check(hip.hipMemcpy(actual.data(), canonical.data, actual.size(), hipMemcpyDeviceToHost), "Diagnostic canonical readback");
|
||||
unsigned differs = 0; double maxAbs = 0;
|
||||
for (unsigned y = 0; y < height; ++y) for (uint64_t offset = width * channels * element; offset < canonical.pitch; ++offset)
|
||||
if (actual[size_t(y * canonical.pitch + offset)] != 0xcd) throw std::runtime_error("GPU decode wrote past the row/pixel range");
|
||||
for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x) for (unsigned c = 0; c < channels; ++c) {
|
||||
unsigned i = (y * width + x) * 4 + c; const auto* p = actual.data() + y * canonical.pitch + (x * channels + c) * element; uint32_t got = 0; std::memcpy(&got, p, element);
|
||||
if (element == 2) expected[i] = reference_half(expected[i]);
|
||||
bool mismatch = got != expected[i];
|
||||
if (element == 4) { float a = 0, b = 0; std::memcpy(&a, &got, 4); std::memcpy(&b, &expected[i], 4); double error = std::abs(double(a) - b); maxAbs = std::max(maxAbs, error); mismatch = !std::isfinite(a) || !std::isfinite(b) || error > 1.2e-7 * std::max(1., std::abs(double(b))); }
|
||||
if (mismatch && differs++ < 5) std::fprintf(stderr, "PIXEL_DIFFERENT format=%s pixel=%u channel=%u expected=%08x actual=%08x\n", item.name, y * width + x, c, expected[i], got);
|
||||
}
|
||||
if (differs) throw std::runtime_error(std::string("D3D12 typed decode mismatch: ") + item.name + " count=" + std::to_string(differs));
|
||||
if (item.plane == 0) {
|
||||
// BGRA uses the identical UNORM converter through an RGBA UAV;
|
||||
// the consumer SRV independently tests the channel permutation.
|
||||
DXGI_FORMAT encodedFormat = spec.storage == d4r::pixel::bgra8 ? DXGI_FORMAT_R8G8B8A8_UNORM : item.view;
|
||||
auto encoded = make_texture(device.Get(), encodedFormat, true); auto actualTexture = make_texture(device.Get(), item.resource);
|
||||
auto canonicalUpload = make_buffer(device.Get(), count * 16, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
dx(canonicalUpload->Map(0, &noRead, &data), "Encode fixture upload");
|
||||
auto* values = static_cast<uint32_t*>(data); std::vector<uint16_t> halves(count * 4);
|
||||
// Cover finite half values, rounding ties, negatives and overflow.
|
||||
for (unsigned i = 0; i < count; ++i) for (unsigned c = 0; c < 4; ++c) { uint16_t v = uint16_t(((i * 37 + c * 7919) % 31744) | (i % 11 == 0 ? 0x8000 : 0)); values[i * 4 + c] = v; halves[i * 4 + c] = v; }
|
||||
canonicalUpload->Unmap(0, nullptr);
|
||||
std::vector<uint8_t> padded(size_t(canonical.pitch * height), 0x5a); for (unsigned y = 0; y < height; ++y) std::memcpy(padded.data() + y * canonical.pitch, halves.data() + y * width * 4, width * 8);
|
||||
hip.check(hip.hipMemcpy(canonical.data, padded.data(), padded.size(), hipMemcpyHostToDevice), "Diagnostic encode input");
|
||||
program.convert(true, canonical.data, canonical.pitch, shared.mapped, shared.footprint.Footprint.RowPitch, width, height, spec.storage, 3);
|
||||
cpu.ptr = initial.ptr + stride; srv = {}; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; srv.Buffer.NumElements = count; srv.Buffer.StructureByteStride = 16; device->CreateShaderResourceView(canonicalUpload.Get(), &srv, cpu);
|
||||
cpu.ptr += stride; D3D12_UNORDERED_ACCESS_VIEW_DESC uv{}; uv.Format = encodedFormat; uv.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; device->CreateUnorderedAccessView(encoded.Get(), nullptr, &uv, cpu);
|
||||
{ List list(device.Get()); transition(list.list.Get(), encoded.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); list.list->SetPipelineState(shader.encode.Get()); list.list->SetComputeRootSignature(shader.root.Get()); ID3D12DescriptorHeap* heaps[] = {heap.Get()}; list.list->SetDescriptorHeaps(1, heaps); auto handle = gpu; handle.ptr += stride; list.list->SetComputeRootDescriptorTable(1, handle); handle.ptr += stride; list.list->SetComputeRootDescriptorTable(2, handle); unsigned constants[] = {width,height,0}; list.list->SetComputeRoot32BitConstants(4,3,constants,0); list.list->Dispatch((count+63)/64,1,1); transition(list.list.Get(), encoded.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); shared.copy_output(list.list.Get(), actualTexture.Get(), D3D12_RESOURCE_STATE_COMMON); transition(list.list.Get(), actualTexture.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); list.submit(queue.Get(),timeline); }
|
||||
srv = {}; srv.Format = item.view; srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srv.Texture2D.MipLevels = 1; device->CreateShaderResourceView(actualTexture.Get(), &srv, initial);
|
||||
auto encodedActual = read_reference(device.Get(), queue.Get(), timeline, shader, heap.Get(), gpu, reference.Get(), 4);
|
||||
srv.Format = encodedFormat; device->CreateShaderResourceView(encoded.Get(), &srv, initial);
|
||||
auto encodedExpected = read_reference(device.Get(), queue.Get(), timeline, shader, heap.Get(), gpu, reference.Get(), 4);
|
||||
for (unsigned i = 0; i < count * 4; ++i) if (encodedActual[i] != encodedExpected[i]) { if (differs++ < 5) std::fprintf(stderr, "ENCODE_DIFFERENT format=%s pixel=%u channel=%u expected=%08x actual=%08x\n", item.name, i/4,i%4,encodedExpected[i],encodedActual[i]); }
|
||||
if (differs) throw std::runtime_error(std::string("D3D12 typed encode mismatch: ") + item.name + " count=" + std::to_string(differs));
|
||||
}
|
||||
++verified; std::printf("PASS PIXEL_FORMAT name=%s plane=%u pixels=%u decode_max_abs=%.9g encode=%u\n", item.name,item.plane,count,maxAbs,item.plane==0);
|
||||
}
|
||||
std::printf("PASS D3D12_GPU_FORMATS architecture=gfx1201 cases=%u iterations=%u width=%u height=%u typed_srv_reference=1 typed_uav_reference=1\n", verified,args.iterations,width,height); return 0;
|
||||
} catch (const std::exception& error) { std::fprintf(stderr,"FAIL D3D12_GPU_FORMATS %s\n",error.what()); loaded_modules(); return 1; }
|
||||
}
|
||||
@@ -110,6 +110,7 @@ struct Args {
|
||||
std::string hip_root, cuda_dll, module, context = "primary";
|
||||
std::string stream_lifetime = "persistent";
|
||||
std::string interop_mode = "roundtrip";
|
||||
std::string pixel_profile = "baseline";
|
||||
std::string ngx_core, dlss_dll;
|
||||
std::string nvapi_dll;
|
||||
std::string ngx_abi = "driver";
|
||||
@@ -131,6 +132,10 @@ struct Args {
|
||||
else if (key == "--context") context = value;
|
||||
else if (key == "--stream-lifetime") stream_lifetime = value;
|
||||
else if (key == "--interop-mode") interop_mode = value;
|
||||
else if (key == "--pixel-profile") {
|
||||
if (value != "baseline" && value != "packed" && value != "unorm") throw std::runtime_error("pixel-profile must be baseline, packed or unorm");
|
||||
pixel_profile = value;
|
||||
}
|
||||
else if (key == "--ngx-core") ngx_core = value;
|
||||
else if (key == "--dlss-dll") dlss_dll = value;
|
||||
else if (key == "--nvapi-dll") nvapi_dll = value;
|
||||
|
||||
@@ -20,6 +20,16 @@ def main(args):
|
||||
raise RuntimeError(f'Output size mismatch for {path.name}')
|
||||
if not np.isfinite(reference).all() or not np.isfinite(output).all():
|
||||
raise RuntimeError(f'NaN/Inf in {path.name}')
|
||||
if args.output_storage == 'r11g11b10f':
|
||||
# D3D float narrowing truncates. Compare after the independent
|
||||
# resource-format quantisation, rather than relaxing the PSNR gate.
|
||||
bits = reference.reshape(-1, 4).view(np.uint16).copy()
|
||||
bits[bits & 0x8000 != 0] = 0
|
||||
bits[:, :2] &= 0x7ff0
|
||||
bits[:, 2] &= 0x7fe0
|
||||
reference = bits.view(np.float16).reshape(-1)
|
||||
elif args.output_storage == 'bgra8':
|
||||
reference = (np.rint(np.clip(reference.astype(np.float32), 0, 1) * 255) / 255).astype(np.float16)
|
||||
x = reference.reshape(-1, 4)[:, :3].astype(np.float64)
|
||||
y = output.reshape(-1, 4)[:, :3].astype(np.float64)
|
||||
error = np.abs(x - y)
|
||||
@@ -30,7 +40,7 @@ def main(args):
|
||||
print(f'FRAME_REFERENCE name={path.name} max_abs={error.max():.9g} '
|
||||
f'max_rel={(error / np.maximum(np.abs(x), .001)).max():.9g} rms={rms:.9g} psnr_db={psnr:.6g} '
|
||||
f'fraction_over_1pct={outliers:.9g}')
|
||||
if psnr < args.min_psnr or error.max() > .025 * peak or outliers > .0001:
|
||||
if (args.exact and np.any(error != 0)) or psnr < args.min_psnr or error.max() > .025 * peak or outliers > .0001:
|
||||
raise RuntimeError(f'Complete pipeline mismatch in {path.name}')
|
||||
print(f'PASS FRAME_REFERENCE frames={len(frames)} finite=1 min_psnr_db={args.min_psnr}')
|
||||
|
||||
@@ -42,6 +52,8 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--width', type=int, default=512)
|
||||
parser.add_argument('--height', type=int, default=288)
|
||||
parser.add_argument('--min-psnr', type=float, default=60.)
|
||||
parser.add_argument('--output-storage', choices=('rgba16f', 'r11g11b10f', 'bgra8'), default='rgba16f')
|
||||
parser.add_argument('--exact', action='store_true')
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
main(args)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include "pixel_conversion.h"
|
||||
#include "cuda_image_api.h"
|
||||
#include "ngx_parameters.h"
|
||||
#include "d3d12_command_hooks.h"
|
||||
@@ -18,6 +19,15 @@ inline void ngx_check(unsigned result, const char* operation) {
|
||||
std::printf("D4R_NGX %s result=0x%08x\n", operation, result);
|
||||
if (result != 1) throw std::runtime_error(std::string(operation) + " NGX=" + std::to_string(result));
|
||||
}
|
||||
inline std::filesystem::path pixel_module_path() {
|
||||
if (const char* path = std::getenv("D4R_FORMAT_MODULE")) return diag::wide(env_path("D4R_FORMAT_MODULE"));
|
||||
HMODULE module = nullptr;
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&pixel_module_path), &module)) throw std::runtime_error("Locate GPU conversion module directory");
|
||||
wchar_t path[32768]{}; const DWORD length = GetModuleFileNameW(module, path, 32768);
|
||||
if (!length || length == 32768) throw std::runtime_error("GPU conversion module path is invalid");
|
||||
return std::filesystem::path(path).parent_path() / L"pixel_convert_gfx1201.hsaco";
|
||||
}
|
||||
|
||||
struct Runtime {
|
||||
diag::HipApi hip;
|
||||
@@ -32,6 +42,7 @@ struct Runtime {
|
||||
CUcontext context = nullptr;
|
||||
bool initialized = false;
|
||||
std::unique_ptr<SharedTimeline> timeline;
|
||||
std::unique_ptr<PixelProgram> pixels;
|
||||
std::mutex mutex;
|
||||
using Init = unsigned(*)(unsigned long long, const wchar_t*, unsigned);
|
||||
using Shutdown = unsigned(*)();
|
||||
@@ -105,6 +116,7 @@ struct Runtime {
|
||||
if (context) (void)cuda.cuCtxSetCurrent(context);
|
||||
if (initialized) { (void)shutdown(); initialized = false; }
|
||||
timeline.reset();
|
||||
pixels.reset();
|
||||
if (context) {
|
||||
(void)cuda.cuCtxSynchronize(); (void)cuda.cuCtxSetCurrent(nullptr);
|
||||
(void)cuda.cuDevicePrimaryCtxRelease_v2(ordinal); context = nullptr;
|
||||
@@ -123,13 +135,15 @@ class Feature {
|
||||
D3D12_RESOURCE_DESC desc{};
|
||||
std::unique_ptr<SharedPlane> shared;
|
||||
std::unique_ptr<cuda::Image> image;
|
||||
std::unique_ptr<PixelAllocation> canonical;
|
||||
PixelSpec spec{};
|
||||
} planes_[5];
|
||||
void cleanup() noexcept {
|
||||
(void)rt_->cuda.cuCtxSetCurrent(rt_->context);
|
||||
(void)rt_->cuda.cuCtxSynchronize();
|
||||
if (handle_) { (void)rt_->release(handle_); handle_ = nullptr; }
|
||||
for (auto& plane : planes_) {
|
||||
plane.image.reset(); plane.shared.reset(); plane.texture.Reset();
|
||||
plane.image.reset(); plane.canonical.reset(); plane.shared.reset(); plane.texture.Reset();
|
||||
}
|
||||
if (scratch_) { (void)rt_->cuda.cuMemFree_v2(scratch_); scratch_ = 0; }
|
||||
if (parameters_) { (void)rt_->destroy(parameters_); parameters_ = nullptr; }
|
||||
@@ -209,17 +223,22 @@ private:
|
||||
throw std::runtime_error("Interop baseline requires single-sample 2D textures");
|
||||
const bool half = i == 0 || i == 2 || i == 3;
|
||||
const unsigned channels = i == 0 || i == 3 ? 4 : i == 2 ? 2 : 1;
|
||||
const DXGI_FORMAT format = channels == 4 ? DXGI_FORMAT_R16G16B16A16_FLOAT :
|
||||
channels == 2 ? DXGI_FORMAT_R16G16_FLOAT : DXGI_FORMAT_R32_FLOAT;
|
||||
if (desc.Format != format && !(i == 1 && (desc.Format == DXGI_FORMAT_R32_TYPELESS || desc.Format == DXGI_FORMAT_D32_FLOAT)))
|
||||
throw std::runtime_error(std::string("GPU conversion not yet implemented for ") + names[i] + " format=" + std::to_string(desc.Format));
|
||||
if (desc.Width > UINT32_MAX) throw std::runtime_error("Texture width exceeds GPU conversion ABI");
|
||||
const PixelSpec spec = pixel_spec(i, desc.Format);
|
||||
if (!pixel_supported(i, spec.storage)) throw std::runtime_error(std::string("Unsupported ") + names[i] + " DXGI format=" + std::to_string(desc.Format));
|
||||
auto& plane = planes_[i];
|
||||
if (!plane.shared || plane.desc.Width != desc.Width || plane.desc.Height != desc.Height || plane.desc.Format != desc.Format) {
|
||||
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "Synchronize(resize interop)");
|
||||
plane.image.reset(); plane.shared.reset();
|
||||
plane.image.reset(); plane.canonical.reset(); plane.shared.reset();
|
||||
plane.shared = std::make_unique<SharedPlane>(rt_->external, rt_->device.Get(), desc);
|
||||
if (!spec.direct) {
|
||||
if (!rt_->pixels) rt_->pixels = std::make_unique<PixelProgram>(rt_->hip, pixel_module_path());
|
||||
plane.canonical = std::make_unique<PixelAllocation>(rt_->hip, unsigned(desc.Width), desc.Height, channels * (half ? 2 : 4));
|
||||
}
|
||||
plane.image = std::make_unique<cuda::Image>(rt_->images, unsigned(desc.Width), desc.Height, half ? 16 : 32, channels, i == 3, i == 0 ? 1 : 0);
|
||||
plane.desc = desc;
|
||||
plane.desc = desc; plane.spec = spec;
|
||||
std::printf("D4R_FORMAT plane=%s dxgi=%u canonical_channels=%u canonical_bits=%u gpu_conversion=%u cpu_copy=0\n",
|
||||
names[i], unsigned(desc.Format), channels, half ? 16u : 32u, !spec.direct);
|
||||
}
|
||||
plane.texture = texture;
|
||||
d4r_ngx_set_void(parameters_, names[i], &plane.image->object);
|
||||
@@ -240,7 +259,14 @@ private:
|
||||
rt_->timeline->wait_input(queue);
|
||||
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "Interop input producer completion");
|
||||
for (unsigned i : {0u, 1u, 2u, 4u}) if (planes_[i].texture) {
|
||||
planes_[i].image->upload_device(reinterpret_cast<uintptr_t>(planes_[i].shared->mapped), planes_[i].shared->footprint.Footprint.RowPitch);
|
||||
auto& plane = planes_[i];
|
||||
void* data = plane.shared->mapped; uint64_t pitch = plane.shared->footprint.Footprint.RowPitch;
|
||||
if (plane.canonical) {
|
||||
rt_->pixels->convert(false, data, pitch, plane.canonical->data, plane.canonical->pitch,
|
||||
unsigned(plane.desc.Width), plane.desc.Height, plane.spec.storage, i);
|
||||
data = plane.canonical->data; pitch = plane.canonical->pitch;
|
||||
}
|
||||
plane.image->upload_device(reinterpret_cast<uintptr_t>(data), pitch);
|
||||
}
|
||||
if (std::getenv("D4R_INTEROP_VERIFY")) {
|
||||
for (unsigned i : {0u, 1u, 2u, 4u}) if (planes_[i].texture) {
|
||||
@@ -259,8 +285,12 @@ private:
|
||||
// NGX can use internal nonblocking streams. The default stream alone
|
||||
// does not establish completion of those streams.
|
||||
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "NGX all-stream completion");
|
||||
planes_[3].image->download_device(reinterpret_cast<uintptr_t>(planes_[3].shared->mapped), planes_[3].shared->footprint.Footprint.RowPitch);
|
||||
auto& output = planes_[3];
|
||||
output.image->download_device(reinterpret_cast<uintptr_t>(output.canonical ? output.canonical->data : output.shared->mapped),
|
||||
output.canonical ? output.canonical->pitch : output.shared->footprint.Footprint.RowPitch);
|
||||
rt_->cuda.check(rt_->cuda.cuCtxSynchronize(), "VRAM output completion");
|
||||
if (output.canonical) rt_->pixels->convert(true, output.canonical->data, output.canonical->pitch, output.shared->mapped,
|
||||
output.shared->footprint.Footprint.RowPitch, unsigned(output.desc.Width), output.desc.Height, output.spec.storage, 3);
|
||||
rt_->timeline->signal_output(queue);
|
||||
std::printf("D4R_FRAME cpu_frame_copies=%u frame_age=0 interop_ngx_ms=%.3f\n", std::getenv("D4R_INTEROP_VERIFY") ? 1u : 0u,
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count());
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include "pixel_format.h"
|
||||
|
||||
namespace d4r::win {
|
||||
struct PixelSpec {
|
||||
pixel::Storage storage;
|
||||
unsigned bytes;
|
||||
bool direct;
|
||||
};
|
||||
inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) {
|
||||
using namespace pixel;
|
||||
// Typeless resources use the corresponding floating-point/UNORM view,
|
||||
// as NGX's resource-only ABI provides no SRV descriptor to disambiguate.
|
||||
switch (format) {
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT: case DXGI_FORMAT_R16G16B16A16_TYPELESS:
|
||||
return {rgba16f, 8, plane == color || plane == output};
|
||||
case DXGI_FORMAT_R32G32B32A32_FLOAT: return {rgba32f, 16, false};
|
||||
case DXGI_FORMAT_R11G11B10_FLOAT: return {r11g11b10f, 4, false};
|
||||
case DXGI_FORMAT_R10G10B10A2_UNORM: case DXGI_FORMAT_R10G10B10A2_TYPELESS: return {rgb10a2, 4, false};
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM: case DXGI_FORMAT_R8G8B8A8_TYPELESS: return {rgba8, 4, false};
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM: case DXGI_FORMAT_B8G8R8A8_TYPELESS: return {bgra8, 4, false};
|
||||
case DXGI_FORMAT_R16G16_FLOAT: case DXGI_FORMAT_R16G16_TYPELESS: return {rg16f, 4, plane == motion};
|
||||
case DXGI_FORMAT_R32G32_FLOAT: case DXGI_FORMAT_R32G32_TYPELESS: return {rg32f, 8, false};
|
||||
case DXGI_FORMAT_R16G16_SNORM: return {rg16snorm, 4, false};
|
||||
case DXGI_FORMAT_R16G16_UNORM: return {rg16unorm, 4, false};
|
||||
case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT:
|
||||
return {r32f, 4, plane == depth || plane == exposure};
|
||||
case DXGI_FORMAT_R16_FLOAT: return {r16f, 2, false};
|
||||
case DXGI_FORMAT_D24_UNORM_S8_UINT: case DXGI_FORMAT_R24G8_TYPELESS: case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
|
||||
return {depth24, 4, false};
|
||||
case DXGI_FORMAT_R16_TYPELESS: return {plane == exposure ? r16f : r16unorm, 2, false};
|
||||
case DXGI_FORMAT_D16_UNORM: case DXGI_FORMAT_R16_UNORM:
|
||||
return {r16unorm, 2, false};
|
||||
default: throw std::runtime_error("No validated GPU conversion for DXGI format=" + std::to_string(format));
|
||||
}
|
||||
}
|
||||
inline bool pixel_supported(unsigned plane, pixel::Storage storage) {
|
||||
using namespace pixel;
|
||||
switch (plane) {
|
||||
case color: case output: return storage <= rgb10a2;
|
||||
case motion: return storage == rgba16f || storage == rgba32f || (storage >= rg16f && storage <= rg16unorm);
|
||||
case depth: return storage == r32f || storage == depth24 || storage == r16unorm;
|
||||
case exposure: return storage == r32f || storage == r16f || storage == rgba16f || storage == rgba32f;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
class PixelProgram {
|
||||
diag::HipApi& hip_;
|
||||
hipModule_t module_ = nullptr;
|
||||
hipFunction_t decode_ = nullptr, encode_ = nullptr;
|
||||
public:
|
||||
PixelProgram(diag::HipApi& hip, const std::filesystem::path& module) : hip_(hip) {
|
||||
try {
|
||||
hip.check(hip.hipModuleLoad(&module_, diag::utf8(module.c_str()).c_str()), "Load native GPU format conversion");
|
||||
hip.check(hip.hipModuleGetFunction(&decode_, module_, "d4r_pixel_decode"), "Resolve GPU pixel decode");
|
||||
hip.check(hip.hipModuleGetFunction(&encode_, module_, "d4r_pixel_encode"), "Resolve GPU pixel encode");
|
||||
} catch (...) { if (module_) (void)hip.hipModuleUnload(module_); throw; }
|
||||
}
|
||||
~PixelProgram() { if (module_) (void)hip_.hipModuleUnload(module_); }
|
||||
PixelProgram(const PixelProgram&) = delete;
|
||||
void convert(bool encode, const void* source, uint64_t sourcePitch, void* dest, uint64_t destPitch,
|
||||
unsigned width, unsigned height, pixel::Storage format, unsigned plane) {
|
||||
if (!width || !height || uint64_t(width) * height > UINT32_MAX) throw std::runtime_error("Invalid GPU conversion dimensions");
|
||||
unsigned storage = unsigned(format);
|
||||
void* args[] = {&source, &sourcePitch, &dest, &destPitch, &width, &height, &storage, &plane};
|
||||
hip_.check(hip_.hipModuleLaunchKernel(encode ? encode_ : decode_, unsigned((uint64_t(width) * height + 255) / 256), 1, 1,
|
||||
256, 1, 1, 0, nullptr, args, nullptr), encode ? "GPU pixel encode" : "GPU pixel decode");
|
||||
// CUDA may use other nonblocking streams. Establish completion before
|
||||
// giving the converted allocation to that API; images remain in VRAM.
|
||||
hip_.check(hip_.hipDeviceSynchronize(), "GPU conversion completion");
|
||||
}
|
||||
};
|
||||
class PixelAllocation {
|
||||
diag::HipApi& hip_;
|
||||
public:
|
||||
void* data = nullptr;
|
||||
uint64_t pitch;
|
||||
PixelAllocation(diag::HipApi& hip, unsigned width, unsigned height, unsigned canonicalBytes) : hip_(hip) {
|
||||
pitch = (uint64_t(width) * canonicalBytes + 255) & ~uint64_t(255);
|
||||
hip.check(hip.hipMalloc(&data, size_t(pitch * height)), "Allocate canonical GPU pixels");
|
||||
}
|
||||
~PixelAllocation() { if (data) (void)hip_.hipFree(data); }
|
||||
PixelAllocation(const PixelAllocation&) = delete;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#include "../../kernels/common/hip_device_minimal.h"
|
||||
#include "pixel_format.h"
|
||||
|
||||
namespace {
|
||||
using namespace d4r::pixel;
|
||||
using u8 = unsigned char;
|
||||
using u16 = unsigned short;
|
||||
using u32 = unsigned;
|
||||
using u64 = unsigned long long;
|
||||
__device__ inline float as_float(u32 bits) { return __builtin_bit_cast(float, bits); }
|
||||
__device__ inline u32 as_uint(float value) { return __builtin_bit_cast(u32, value); }
|
||||
__device__ inline float from_half(u16 bits) {
|
||||
u32 sign = u32(bits & 0x8000u) << 16, exp = (bits >> 10) & 31, m = bits & 1023;
|
||||
if (!exp) return as_float(as_uint(float(m) * 0x1p-24f) | sign);
|
||||
return as_float(sign | (exp == 31 ? 0x7f800000u : (exp + 112u) << 23) | (m << 13));
|
||||
}
|
||||
__device__ inline u16 to_half(float value) {
|
||||
u32 bits = as_uint(value), sign = (bits >> 16) & 0x8000u, exponent = (bits >> 23) & 255u;
|
||||
u32 mantissa = bits & 0x7fffffu;
|
||||
if (exponent == 255) return u16(sign | 0x7c00u | (mantissa ? 0x200u : 0));
|
||||
int e = int(exponent) - 112;
|
||||
if (e >= 31) return u16(sign | 0x7c00u);
|
||||
unsigned shift = 13;
|
||||
if (e <= 0) {
|
||||
if (e < -10) return u16(sign);
|
||||
mantissa |= 0x800000u; shift = unsigned(14 - e); e = 0;
|
||||
}
|
||||
u32 kept = mantissa >> shift, remainder = mantissa & ((1u << shift) - 1), midpoint = 1u << (shift - 1);
|
||||
u32 result = sign | (u32(e) << 10);
|
||||
result += kept & (e == 0 ? 0x7ffu : 0x3ffu);
|
||||
return u16(result + (remainder > midpoint || (remainder == midpoint && (kept & 1))));
|
||||
}
|
||||
__device__ inline u32 quantize(float value, unsigned maximum) {
|
||||
if (!(value > 0)) return 0;
|
||||
if (value >= 1) return maximum;
|
||||
float scaled = value * float(maximum);
|
||||
u32 integer = u32(scaled); float remainder = scaled - float(integer);
|
||||
return integer + (remainder > .5f || (remainder == .5f && (integer & 1)));
|
||||
}
|
||||
__device__ inline u32 half_to_unsigned(u16 h, unsigned mantissa_bits) {
|
||||
if ((h & 0x7c00u) == 0x7c00u && (h & 1023u)) return (31u << mantissa_bits) | (1u << (mantissa_bits - 1));
|
||||
if (h & 0x8000u) return 0;
|
||||
// D3D floating-point format narrowing truncates. RNE here differs from
|
||||
// the driver's typed UAV store and can turn finite HDR values into Inf.
|
||||
return h >> (10 - mantissa_bits);
|
||||
}
|
||||
__device__ inline unsigned pixel_bytes(Storage fmt) {
|
||||
switch (fmt) {
|
||||
case rgba32f: return 16;
|
||||
case rgba16f: case rg32f: return 8;
|
||||
case r16f: case r16unorm: return 2;
|
||||
default: return 4;
|
||||
}
|
||||
}
|
||||
__device__ inline void decode(const u8* p, Storage fmt, float* value) {
|
||||
const auto* halves = reinterpret_cast<const u16*>(p);
|
||||
const auto* singles = reinterpret_cast<const float*>(p);
|
||||
value[0] = value[1] = value[2] = 0; value[3] = 1;
|
||||
switch (fmt) {
|
||||
case rgba16f: for (unsigned i = 0; i < 4; ++i) value[i] = from_half(halves[i]); break;
|
||||
case rgba32f: for (unsigned i = 0; i < 4; ++i) value[i] = singles[i]; break;
|
||||
case rgba8: case bgra8:
|
||||
for (unsigned i = 0; i < 4; ++i) value[i] = float(p[i]) / 255.f;
|
||||
if (fmt == bgra8) { float swap = value[0]; value[0] = value[2]; value[2] = swap; } break;
|
||||
case r11g11b10f: {
|
||||
u32 packed = *reinterpret_cast<const u32*>(p);
|
||||
value[0] = from_half(u16((packed & 2047) << 4));
|
||||
value[1] = from_half(u16(((packed >> 11) & 2047) << 4));
|
||||
value[2] = from_half(u16((packed >> 22) << 5)); break;
|
||||
}
|
||||
case rgb10a2: {
|
||||
u32 packed = *reinterpret_cast<const u32*>(p);
|
||||
for (unsigned i = 0; i < 3; ++i) value[i] = float((packed >> (10 * i)) & 1023) / 1023.f;
|
||||
value[3] = float(packed >> 30) / 3.f; break;
|
||||
}
|
||||
case rg16f: value[0] = from_half(halves[0]); value[1] = from_half(halves[1]); break;
|
||||
case rg32f: value[0] = singles[0]; value[1] = singles[1]; break;
|
||||
case rg16snorm:
|
||||
for (unsigned i = 0; i < 2; ++i) {
|
||||
int v = reinterpret_cast<const short*>(p)[i];
|
||||
value[i] = v == -32768 ? -1.f : float(v) / 32767.f;
|
||||
} break;
|
||||
case rg16unorm: for (unsigned i = 0; i < 2; ++i) value[i] = float(halves[i]) / 65535.f; break;
|
||||
case r16f: value[0] = from_half(halves[0]); break;
|
||||
case r32f: value[0] = singles[0]; break;
|
||||
case depth24: value[0] = float(*reinterpret_cast<const u32*>(p) & 0xffffffu) / 16777215.f; break;
|
||||
case r16unorm: value[0] = float(halves[0]) / 65535.f; break;
|
||||
default: break; // Host rejects formats which have no tested decoder.
|
||||
}
|
||||
}
|
||||
__device__ inline void encode(u8* p, Storage fmt, const float* value) {
|
||||
auto* halves = reinterpret_cast<u16*>(p);
|
||||
auto* singles = reinterpret_cast<float*>(p);
|
||||
switch (fmt) {
|
||||
case rgba16f: for (unsigned i = 0; i < 4; ++i) halves[i] = to_half(value[i]); break;
|
||||
case rgba32f: for (unsigned i = 0; i < 4; ++i) singles[i] = value[i]; break;
|
||||
case rgba8: case bgra8:
|
||||
for (unsigned i = 0; i < 4; ++i) p[i] = u8(quantize(value[i], 255));
|
||||
if (fmt == bgra8) { u8 swap = p[0]; p[0] = p[2]; p[2] = swap; } break;
|
||||
case r11g11b10f:
|
||||
*reinterpret_cast<u32*>(p) = half_to_unsigned(to_half(value[0]), 6) |
|
||||
(half_to_unsigned(to_half(value[1]), 6) << 11) | (half_to_unsigned(to_half(value[2]), 5) << 22); break;
|
||||
case rgb10a2:
|
||||
*reinterpret_cast<u32*>(p) = quantize(value[0], 1023) | (quantize(value[1], 1023) << 10) |
|
||||
(quantize(value[2], 1023) << 20) | (quantize(value[3], 3) << 30); break;
|
||||
default: break; // Only color/output formats are encoded in the runtime.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __global__ void d4r_pixel_decode(const u8* source, u64 source_pitch, u8* dest, u64 dest_pitch,
|
||||
unsigned width, unsigned height, unsigned format, unsigned plane) {
|
||||
unsigned i = blockIdx.x * 256u + threadIdx.x;
|
||||
if (u64(i) >= u64(width) * height) return;
|
||||
unsigned x = i % width, y = i / width;
|
||||
float value[4]; decode(source + u64(y) * source_pitch + u64(x) * pixel_bytes(Storage(format)), Storage(format), value);
|
||||
if (plane == depth || plane == exposure) reinterpret_cast<float*>(dest + u64(y) * dest_pitch)[x] = value[0];
|
||||
else {
|
||||
unsigned channels = plane == motion ? 2 : 4;
|
||||
auto* halves = reinterpret_cast<u16*>(dest + u64(y) * dest_pitch) + x * channels;
|
||||
for (unsigned c = 0; c < channels; ++c) halves[c] = to_half(value[c]);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __global__ void d4r_pixel_encode(const u8* source, u64 source_pitch, u8* dest, u64 dest_pitch,
|
||||
unsigned width, unsigned height, unsigned format, unsigned plane) {
|
||||
unsigned i = blockIdx.x * 256u + threadIdx.x;
|
||||
if (u64(i) >= u64(width) * height) return;
|
||||
unsigned x = i % width, y = i / width;
|
||||
const auto* halves = reinterpret_cast<const u16*>(source + u64(y) * source_pitch) + x * 4;
|
||||
float value[4]; for (unsigned c = 0; c < 4; ++c) value[c] = from_half(halves[c]);
|
||||
encode(dest + u64(y) * dest_pitch + u64(x) * pixel_bytes(Storage(format)), Storage(format), value);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
// Numeric storage descriptions shared by the native HIP kernel and Windows
|
||||
// host. These are independent of Win32, Wine, KFD and NVIDIA SDK headers.
|
||||
namespace d4r::pixel {
|
||||
enum Storage : unsigned {
|
||||
rgba16f, rgba32f, rgba8, bgra8, r11g11b10f, rgb10a2,
|
||||
rg16f, rg32f, rg16snorm, rg16unorm, r16f, r32f,
|
||||
depth24, r16unorm, rgba8srgb, bgra8srgb
|
||||
};
|
||||
enum Plane : unsigned { color, depth, motion, output, exposure };
|
||||
}
|
||||
Reference in new issue
Block a user