1147 lines
46 KiB
C++
1147 lines
46 KiB
C++
// D3D12 implementation of the native-render RHI. A thin passthrough onto the
|
|
// D3D12 command processor's deferred command list, barrier queue and
|
|
// submission counters; the descriptor-field choices here reproduce exactly
|
|
// what the scene renderer set when it talked to D3D12 directly, so migrating
|
|
// the scene onto the RHI is behavior-preserving on this backend.
|
|
|
|
#include <rex/graphics/d3d12/native_rhi_d3d12.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <d3dcompiler.h>
|
|
|
|
#include <rex/graphics/d3d12/command_processor.h>
|
|
#include <rex/logging.h>
|
|
#include <rex/ui/d3d12/d3d12_provider.h>
|
|
#include <rex/ui/d3d12/d3d12_util.h>
|
|
|
|
namespace rex::graphics::d3d12 {
|
|
namespace {
|
|
|
|
using nrhi::Backend;
|
|
using nrhi::Format;
|
|
using nrhi::HeapKind;
|
|
using nrhi::ResourceState;
|
|
using nrhi::TextureKind;
|
|
|
|
DXGI_FORMAT ToDxgi(Format format) {
|
|
switch (format) {
|
|
case Format::kR8G8B8A8_UNORM:
|
|
return DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
case Format::kR8G8B8A8_UINT:
|
|
return DXGI_FORMAT_R8G8B8A8_UINT;
|
|
case Format::kR8_UNORM:
|
|
return DXGI_FORMAT_R8_UNORM;
|
|
case Format::kR8G8_UNORM:
|
|
return DXGI_FORMAT_R8G8_UNORM;
|
|
case Format::kB5G6R5_UNORM:
|
|
return DXGI_FORMAT_B5G6R5_UNORM;
|
|
case Format::kR16G16_UNORM:
|
|
return DXGI_FORMAT_R16G16_UNORM;
|
|
case Format::kR10G10B10A2_UNORM:
|
|
return DXGI_FORMAT_R10G10B10A2_UNORM;
|
|
case Format::kR32_FLOAT:
|
|
return DXGI_FORMAT_R32_FLOAT;
|
|
case Format::kR32G32_FLOAT:
|
|
return DXGI_FORMAT_R32G32_FLOAT;
|
|
case Format::kR32G32B32_FLOAT:
|
|
return DXGI_FORMAT_R32G32B32_FLOAT;
|
|
case Format::kR32G32B32A32_FLOAT:
|
|
return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
|
case Format::kD32_FLOAT:
|
|
return DXGI_FORMAT_D32_FLOAT;
|
|
case Format::kBC1_UNORM:
|
|
return DXGI_FORMAT_BC1_UNORM;
|
|
case Format::kBC2_UNORM:
|
|
return DXGI_FORMAT_BC2_UNORM;
|
|
case Format::kBC3_UNORM:
|
|
return DXGI_FORMAT_BC3_UNORM;
|
|
case Format::kBC4_UNORM:
|
|
return DXGI_FORMAT_BC4_UNORM;
|
|
case Format::kBC5_UNORM:
|
|
return DXGI_FORMAT_BC5_UNORM;
|
|
default:
|
|
return DXGI_FORMAT_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
Format FromDxgi(DXGI_FORMAT format) {
|
|
switch (format) {
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
|
return Format::kR8G8B8A8_UNORM;
|
|
case DXGI_FORMAT_R10G10B10A2_UNORM:
|
|
return Format::kR10G10B10A2_UNORM;
|
|
default:
|
|
return Format::kUnknown;
|
|
}
|
|
}
|
|
|
|
D3D12_RESOURCE_STATES ToStates(ResourceState state,
|
|
D3D12_RESOURCE_STATES guest_output_state) {
|
|
switch (state) {
|
|
case ResourceState::kRenderTarget:
|
|
return D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
case ResourceState::kDepthWrite:
|
|
return D3D12_RESOURCE_STATE_DEPTH_WRITE;
|
|
case ResourceState::kPixelShaderResource:
|
|
return D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
|
case ResourceState::kCopySource:
|
|
return D3D12_RESOURCE_STATE_COPY_SOURCE;
|
|
case ResourceState::kCopyDest:
|
|
return D3D12_RESOURCE_STATE_COPY_DEST;
|
|
case ResourceState::kGenericRead:
|
|
return D3D12_RESOURCE_STATE_GENERIC_READ;
|
|
case ResourceState::kGuestOutput:
|
|
return guest_output_state;
|
|
case ResourceState::kCommon:
|
|
default:
|
|
return D3D12_RESOURCE_STATE_COMMON;
|
|
}
|
|
}
|
|
|
|
UINT8 ToShaderComponentMapping(const nrhi::Swizzle swizzle) {
|
|
switch (swizzle) {
|
|
case nrhi::Swizzle::kX:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0;
|
|
case nrhi::Swizzle::kY:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_1;
|
|
case nrhi::Swizzle::kZ:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_2;
|
|
case nrhi::Swizzle::kW:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_3;
|
|
case nrhi::Swizzle::kZero:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_0;
|
|
case nrhi::Swizzle::kOne:
|
|
default:
|
|
return D3D12_SHADER_COMPONENT_MAPPING_FORCE_VALUE_1;
|
|
}
|
|
}
|
|
|
|
D3D12_BLEND ToBlend(nrhi::BlendFactor factor) {
|
|
switch (factor) {
|
|
case nrhi::BlendFactor::kZero:
|
|
return D3D12_BLEND_ZERO;
|
|
case nrhi::BlendFactor::kSrcAlpha:
|
|
return D3D12_BLEND_SRC_ALPHA;
|
|
case nrhi::BlendFactor::kInvSrcAlpha:
|
|
return D3D12_BLEND_INV_SRC_ALPHA;
|
|
case nrhi::BlendFactor::kOne:
|
|
default:
|
|
return D3D12_BLEND_ONE;
|
|
}
|
|
}
|
|
|
|
D3D12_BLEND_OP ToBlendOp(nrhi::BlendOp op) {
|
|
return op == nrhi::BlendOp::kMin ? D3D12_BLEND_OP_MIN : D3D12_BLEND_OP_ADD;
|
|
}
|
|
|
|
D3D12_COMPARISON_FUNC ToCompare(nrhi::CompareFunc func) {
|
|
switch (func) {
|
|
case nrhi::CompareFunc::kLess:
|
|
return D3D12_COMPARISON_FUNC_LESS;
|
|
case nrhi::CompareFunc::kLessEqual:
|
|
return D3D12_COMPARISON_FUNC_LESS_EQUAL;
|
|
case nrhi::CompareFunc::kAlways:
|
|
default:
|
|
return D3D12_COMPARISON_FUNC_ALWAYS;
|
|
}
|
|
}
|
|
|
|
class NrDeviceD3D12;
|
|
|
|
class NrBufferD3D12 : public nrhi::Buffer {
|
|
public:
|
|
uint64_t size() const override { return size_; }
|
|
|
|
ID3D12Resource* resource = nullptr;
|
|
D3D12_GPU_VIRTUAL_ADDRESS gpu_va = 0;
|
|
void* mapping = nullptr;
|
|
uint64_t size_ = 0;
|
|
HeapKind heap = HeapKind::kDefault;
|
|
};
|
|
|
|
class NrTextureD3D12 : public nrhi::Texture {
|
|
public:
|
|
uint32_t width() const override { return desc.width; }
|
|
uint32_t height() const override { return desc.height; }
|
|
Format format() const override { return desc.format; }
|
|
|
|
ID3D12Resource* resource = nullptr;
|
|
nrhi::TextureDesc desc;
|
|
DXGI_FORMAT dxgi_format = DXGI_FORMAT_UNKNOWN;
|
|
// RTV/DSV slots in the device's non-shader-visible heaps, ~0u when absent.
|
|
uint32_t rtv_slot = ~0u;
|
|
uint32_t dsv_slot = ~0u;
|
|
// Guest-output wrappers don't own their resource's lifetime semantics the
|
|
// same way (they hold a ref, but are never destroyed via DestroyDeferred).
|
|
bool is_guest_output = false;
|
|
};
|
|
|
|
class NrTextureViewD3D12 : public nrhi::TextureView {
|
|
public:
|
|
// Slot in the device's non-shader-visible staging CBV_SRV_UAV heap; the
|
|
// SRV descriptor is copied from here into shader-visible binding slots.
|
|
uint32_t staging_slot = ~0u;
|
|
NrTextureD3D12* texture = nullptr;
|
|
};
|
|
|
|
class NrBindingLayoutD3D12 : public nrhi::BindingLayout {
|
|
public:
|
|
ID3D12RootSignature* root_signature = nullptr;
|
|
};
|
|
|
|
class NrShaderD3D12 : public nrhi::Shader {
|
|
public:
|
|
ID3DBlob* blob = nullptr;
|
|
};
|
|
|
|
class NrPipelineD3D12 : public nrhi::Pipeline {
|
|
public:
|
|
ID3D12PipelineState* pso = nullptr;
|
|
};
|
|
|
|
// A simple slot allocator over a fixed-size descriptor heap with
|
|
// submission-keyed retirement (mirrors the scene renderer's original
|
|
// srv_next/srv_free/retired_srv_slots machinery).
|
|
struct SlotAllocator {
|
|
uint32_t capacity = 0;
|
|
uint32_t next = 0;
|
|
std::vector<uint32_t> free_list;
|
|
// (first_slot, count, submission)
|
|
std::vector<std::pair<std::pair<uint32_t, uint32_t>, uint64_t>> retired;
|
|
|
|
bool Alloc(uint32_t count, uint32_t* out) {
|
|
if (count == 1 && !free_list.empty()) {
|
|
*out = free_list.back();
|
|
free_list.pop_back();
|
|
return true;
|
|
}
|
|
if (next + count <= capacity) {
|
|
*out = next;
|
|
next += count;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void Retire(uint32_t first, uint32_t count, uint64_t submission) {
|
|
retired.emplace_back(std::make_pair(first, count), submission);
|
|
}
|
|
void Drain(uint64_t completed) {
|
|
std::erase_if(retired, [&](const auto& entry) {
|
|
if (entry.second < completed) {
|
|
for (uint32_t i = 0; i < entry.first.second; ++i) {
|
|
free_list.push_back(entry.first.first + i);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
};
|
|
|
|
class NrCmdD3D12 : public nrhi::Cmd {
|
|
public:
|
|
void SetBindingLayout(nrhi::BindingLayout* layout) override;
|
|
void SetPipeline(nrhi::Pipeline* pipeline) override;
|
|
void SetRootConstants(uint32_t param, uint32_t count, const void* values,
|
|
uint32_t dest_offset_in_values) override;
|
|
void SetConstantBuffer(uint32_t param, nrhi::Buffer* buffer,
|
|
uint64_t offset) override;
|
|
void SetBufferSrv(uint32_t param, nrhi::Buffer* buffer,
|
|
uint64_t offset) override;
|
|
void SetTexture(uint32_t param, nrhi::TextureView* view) override;
|
|
void SetTexturePair(uint32_t param, nrhi::TextureView* first,
|
|
nrhi::TextureView* second) override;
|
|
void SetTextures(uint32_t param, nrhi::TextureView* const* views,
|
|
uint32_t count) override;
|
|
void SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) override;
|
|
void ClearRenderTarget(nrhi::Texture* color, const float color4[4]) override;
|
|
void ClearDepth(nrhi::Texture* depth, float value) override;
|
|
void SetViewport(const nrhi::Viewport& viewport) override;
|
|
void SetScissor(const nrhi::Rect& rect) override;
|
|
void SetVertexBuffer(nrhi::Buffer* buffer, uint64_t offset,
|
|
uint32_t size_bytes, uint32_t stride) override;
|
|
void SetIndexBuffer(nrhi::Buffer* buffer, uint64_t offset,
|
|
uint32_t size_bytes) override;
|
|
void SetPrimitiveTopology(nrhi::PrimitiveTopology topology) override;
|
|
void Draw(uint32_t vertex_count, uint32_t start_vertex) override;
|
|
void DrawIndexed(uint32_t index_count, uint32_t start_index,
|
|
int32_t base_vertex) override;
|
|
void CopyBufferToTexture(nrhi::Texture* dst, uint32_t mip,
|
|
uint32_t array_slice, nrhi::Buffer* src,
|
|
uint64_t src_offset, uint32_t row_pitch,
|
|
uint32_t width, uint32_t height,
|
|
uint32_t depth) override;
|
|
void CopyTextureToBuffer(nrhi::Buffer* dst, uint64_t dst_offset,
|
|
uint32_t row_pitch, nrhi::Texture* src,
|
|
uint32_t mip, uint32_t width,
|
|
uint32_t height) override;
|
|
void Barrier(nrhi::Texture* texture, ResourceState before,
|
|
ResourceState after) override;
|
|
void FlushBarriers() override;
|
|
|
|
NrDeviceD3D12* device = nullptr;
|
|
};
|
|
|
|
class NrDeviceD3D12 : public nrhi::Device {
|
|
public:
|
|
explicit NrDeviceD3D12(D3D12CommandProcessor* cp) : cp_(cp) {
|
|
const ui::d3d12::D3D12Provider& provider = cp_->GetD3D12Provider();
|
|
device_ = provider.GetDevice();
|
|
cmd_.device = this;
|
|
|
|
D3D12_DESCRIPTOR_HEAP_DESC heap_desc{};
|
|
heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
|
heap_desc.NumDescriptors = kStagingViews;
|
|
device_->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&staging_heap_));
|
|
heap_desc.NumDescriptors = kShaderVisibleViews;
|
|
heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
|
device_->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&srv_heap_));
|
|
heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
|
heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
|
heap_desc.NumDescriptors = kRtvSlots;
|
|
device_->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&rtv_heap_));
|
|
heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
|
|
heap_desc.NumDescriptors = kDsvSlots;
|
|
device_->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&dsv_heap_));
|
|
|
|
view_size_ =
|
|
device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
|
rtv_size_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
|
dsv_size_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
|
|
staging_slots_.capacity = kStagingViews;
|
|
srv_slots_.capacity = kShaderVisibleViews;
|
|
rtv_slots_.capacity = kRtvSlots;
|
|
dsv_slots_.capacity = kDsvSlots;
|
|
}
|
|
|
|
~NrDeviceD3D12() override {
|
|
// Callers guarantee GPU idle; release everything immediately.
|
|
DrainRetired(~0ull);
|
|
for (auto& entry : guest_outputs_) {
|
|
entry.second->resource->Release();
|
|
delete entry.second;
|
|
}
|
|
if (staging_heap_) staging_heap_->Release();
|
|
if (srv_heap_) srv_heap_->Release();
|
|
if (rtv_heap_) rtv_heap_->Release();
|
|
if (dsv_heap_) dsv_heap_->Release();
|
|
}
|
|
|
|
Backend backend() const override { return Backend::kD3D12; }
|
|
|
|
nrhi::Buffer* CreateBuffer(const nrhi::BufferDesc& desc) override {
|
|
D3D12_RESOURCE_DESC rd{};
|
|
rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
|
rd.Width = desc.size;
|
|
rd.Height = 1;
|
|
rd.DepthOrArraySize = 1;
|
|
rd.MipLevels = 1;
|
|
rd.SampleDesc.Count = 1;
|
|
rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
|
const D3D12_HEAP_PROPERTIES* heap = &ui::d3d12::util::kHeapPropertiesDefault;
|
|
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON;
|
|
if (desc.heap == HeapKind::kUpload) {
|
|
heap = &ui::d3d12::util::kHeapPropertiesUpload;
|
|
state = D3D12_RESOURCE_STATE_GENERIC_READ;
|
|
} else if (desc.heap == HeapKind::kReadback) {
|
|
heap = &ui::d3d12::util::kHeapPropertiesReadback;
|
|
state = D3D12_RESOURCE_STATE_COPY_DEST;
|
|
}
|
|
ID3D12Resource* resource = nullptr;
|
|
if (FAILED(device_->CreateCommittedResource(
|
|
heap, cp_->GetD3D12Provider().GetHeapFlagCreateNotZeroed(), &rd, state, nullptr,
|
|
IID_PPV_ARGS(&resource)))) {
|
|
REXLOG_ERROR("nrhi-d3d12: buffer creation failed ({} bytes)", desc.size);
|
|
return nullptr;
|
|
}
|
|
auto* buffer = new NrBufferD3D12();
|
|
buffer->resource = resource;
|
|
buffer->gpu_va = resource->GetGPUVirtualAddress();
|
|
buffer->size_ = desc.size;
|
|
buffer->heap = desc.heap;
|
|
return buffer;
|
|
}
|
|
|
|
nrhi::Texture* CreateTexture(const nrhi::TextureDesc& desc) override {
|
|
D3D12_RESOURCE_DESC rd{};
|
|
rd.Dimension = desc.kind == TextureKind::k3D ? D3D12_RESOURCE_DIMENSION_TEXTURE3D
|
|
: D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
rd.Width = desc.width;
|
|
rd.Height = desc.height;
|
|
rd.DepthOrArraySize =
|
|
UINT16(desc.kind == TextureKind::kCube ? 6 : desc.kind == TextureKind::k3D ? desc.depth : 1);
|
|
rd.MipLevels = UINT16(desc.mip_levels);
|
|
rd.Format = ToDxgi(desc.format);
|
|
rd.SampleDesc.Count = desc.sample_count;
|
|
rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
D3D12_CLEAR_VALUE clear{};
|
|
const D3D12_CLEAR_VALUE* clear_ptr = nullptr;
|
|
if (desc.usage & nrhi::kTextureUsageRenderTarget) {
|
|
rd.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
|
clear.Format = rd.Format;
|
|
std::memcpy(clear.Color, desc.clear_color, sizeof(clear.Color));
|
|
clear_ptr = &clear;
|
|
}
|
|
if (desc.usage & nrhi::kTextureUsageDepthStencil) {
|
|
// The depth resource is created typeless and viewed as D32_FLOAT /
|
|
// R32_FLOAT; a typed depth SRV read the clear value on some drivers
|
|
// (the photo-editor DoF smear); preserve the proven arrangement.
|
|
rd.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
|
if (desc.format == Format::kD32_FLOAT) {
|
|
rd.Format = DXGI_FORMAT_R32_TYPELESS;
|
|
}
|
|
clear.Format = DXGI_FORMAT_D32_FLOAT;
|
|
clear.DepthStencil.Depth = desc.clear_depth;
|
|
clear_ptr = &clear;
|
|
}
|
|
ID3D12Resource* resource = nullptr;
|
|
if (FAILED(device_->CreateCommittedResource(
|
|
&ui::d3d12::util::kHeapPropertiesDefault,
|
|
cp_->GetD3D12Provider().GetHeapFlagCreateNotZeroed(), &rd,
|
|
ToStates(desc.initial_state, guest_output_state_), clear_ptr,
|
|
IID_PPV_ARGS(&resource)))) {
|
|
REXLOG_ERROR("nrhi-d3d12: texture creation failed ({}x{} fmt {})", desc.width,
|
|
desc.height, uint32_t(desc.format));
|
|
return nullptr;
|
|
}
|
|
auto* texture = new NrTextureD3D12();
|
|
texture->resource = resource;
|
|
texture->desc = desc;
|
|
texture->dxgi_format = ToDxgi(desc.format);
|
|
if (desc.usage & nrhi::kTextureUsageRenderTarget) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (rtv_slots_.Alloc(1, &texture->rtv_slot)) {
|
|
device_->CreateRenderTargetView(resource, nullptr, RtvHandle(texture->rtv_slot));
|
|
}
|
|
}
|
|
if (desc.usage & nrhi::kTextureUsageDepthStencil) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (dsv_slots_.Alloc(1, &texture->dsv_slot)) {
|
|
D3D12_DEPTH_STENCIL_VIEW_DESC dsv{};
|
|
dsv.Format = DXGI_FORMAT_D32_FLOAT;
|
|
dsv.ViewDimension = desc.sample_count > 1 ? D3D12_DSV_DIMENSION_TEXTURE2DMS
|
|
: D3D12_DSV_DIMENSION_TEXTURE2D;
|
|
device_->CreateDepthStencilView(resource, &dsv, DsvHandle(texture->dsv_slot));
|
|
}
|
|
}
|
|
return texture;
|
|
}
|
|
|
|
void* Map(nrhi::Buffer* buffer) override {
|
|
auto* b = static_cast<NrBufferD3D12*>(buffer);
|
|
if (b->mapping == nullptr) {
|
|
const D3D12_RANGE no_read{};
|
|
// Readback buffers are read through mapped memory; upload buffers are
|
|
// write-only (pass an empty read range).
|
|
b->resource->Map(0, b->heap == HeapKind::kReadback ? nullptr : &no_read, &b->mapping);
|
|
}
|
|
return b->mapping;
|
|
}
|
|
|
|
void Unmap(nrhi::Buffer* buffer) override {
|
|
auto* b = static_cast<NrBufferD3D12*>(buffer);
|
|
if (b->mapping != nullptr) {
|
|
b->resource->Unmap(0, nullptr);
|
|
b->mapping = nullptr;
|
|
}
|
|
}
|
|
|
|
void InvalidateForRead(nrhi::Buffer*, uint64_t, uint64_t) override {}
|
|
|
|
void DestroyDeferred(nrhi::Buffer* buffer) override {
|
|
if (buffer == nullptr) return;
|
|
auto* b = static_cast<NrBufferD3D12*>(buffer);
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
retired_.emplace_back(RetiredObject{b->resource, nullptr, cp_->GetCurrentSubmission()});
|
|
delete b;
|
|
}
|
|
|
|
void DestroyDeferred(nrhi::Texture* texture) override {
|
|
if (texture == nullptr) return;
|
|
auto* t = static_cast<NrTextureD3D12*>(texture);
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
const uint64_t submission = cp_->GetCurrentSubmission();
|
|
if (t->rtv_slot != ~0u) rtv_slots_.Retire(t->rtv_slot, 1, submission);
|
|
if (t->dsv_slot != ~0u) dsv_slots_.Retire(t->dsv_slot, 1, submission);
|
|
retired_.emplace_back(RetiredObject{t->resource, nullptr, submission});
|
|
delete t;
|
|
}
|
|
|
|
void DestroyDeferred(nrhi::TextureView* view) override {
|
|
if (view == nullptr) return;
|
|
auto* v = static_cast<NrTextureViewD3D12*>(view);
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
const uint64_t submission = cp_->GetCurrentSubmission();
|
|
staging_slots_.Retire(v->staging_slot, 1, submission);
|
|
// Retire every shader-visible binding this view participates in.
|
|
std::erase_if(bindings_, [&](auto& entry) {
|
|
for (uint32_t i = 0; i < entry.first.count; ++i) {
|
|
if (entry.first.views[i] == v) {
|
|
srv_slots_.Retire(entry.second.first_slot, entry.second.count, submission);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
delete v;
|
|
}
|
|
|
|
void DestroyDeferred(nrhi::Pipeline* pipeline) override {
|
|
if (pipeline == nullptr) return;
|
|
auto* p = static_cast<NrPipelineD3D12*>(pipeline);
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
retired_.emplace_back(RetiredObject{nullptr, p->pso, cp_->GetCurrentSubmission()});
|
|
delete p;
|
|
}
|
|
|
|
void DestroyDeferred(nrhi::Shader* shader) override {
|
|
if (shader == nullptr) return;
|
|
auto* s = static_cast<NrShaderD3D12*>(shader);
|
|
if (s->blob) s->blob->Release();
|
|
delete s;
|
|
}
|
|
|
|
nrhi::TextureView* CreateTextureView(nrhi::Texture* texture,
|
|
const nrhi::TextureViewDesc& desc) override {
|
|
auto* t = static_cast<NrTextureD3D12*>(texture);
|
|
uint32_t slot;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (!staging_slots_.Alloc(1, &slot)) {
|
|
REXLOG_ERROR("nrhi-d3d12: staging view heap exhausted");
|
|
return nullptr;
|
|
}
|
|
}
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC srv{};
|
|
srv.Format = desc.format != Format::kUnknown ? ToDxgi(desc.format)
|
|
: t->desc.format == Format::kD32_FLOAT ? DXGI_FORMAT_R32_FLOAT
|
|
: t->dxgi_format;
|
|
srv.Shader4ComponentMapping = D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(
|
|
ToShaderComponentMapping(desc.swizzle[0]), ToShaderComponentMapping(desc.swizzle[1]),
|
|
ToShaderComponentMapping(desc.swizzle[2]), ToShaderComponentMapping(desc.swizzle[3]));
|
|
const uint32_t mips = desc.mip_levels == ~0u
|
|
? (t->desc.mip_levels - desc.base_mip)
|
|
: desc.mip_levels;
|
|
switch (desc.dimension) {
|
|
case nrhi::ViewDimension::k2DMS:
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
|
|
break;
|
|
case nrhi::ViewDimension::kCube:
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
|
|
srv.TextureCube.MostDetailedMip = desc.base_mip;
|
|
srv.TextureCube.MipLevels = mips;
|
|
break;
|
|
case nrhi::ViewDimension::k3D:
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D;
|
|
srv.Texture3D.MostDetailedMip = desc.base_mip;
|
|
srv.Texture3D.MipLevels = mips;
|
|
break;
|
|
case nrhi::ViewDimension::k2D:
|
|
default:
|
|
srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
srv.Texture2D.MostDetailedMip = desc.base_mip;
|
|
srv.Texture2D.MipLevels = mips;
|
|
break;
|
|
}
|
|
device_->CreateShaderResourceView(t->resource, &srv, StagingHandle(slot));
|
|
auto* view = new NrTextureViewD3D12();
|
|
view->staging_slot = slot;
|
|
view->texture = t;
|
|
return view;
|
|
}
|
|
|
|
nrhi::BindingLayout* CreateBindingLayout(const nrhi::BindingLayoutDesc& desc) override {
|
|
D3D12_ROOT_PARAMETER params[nrhi::kMaxBindingParams] = {};
|
|
D3D12_DESCRIPTOR_RANGE ranges[nrhi::kMaxBindingParams] = {};
|
|
for (uint32_t i = 0; i < desc.param_count; ++i) {
|
|
const nrhi::BindingParamDesc& p = desc.params[i];
|
|
D3D12_SHADER_VISIBILITY visibility =
|
|
p.visibility == nrhi::Visibility::kVertex ? D3D12_SHADER_VISIBILITY_VERTEX
|
|
: p.visibility == nrhi::Visibility::kPixel ? D3D12_SHADER_VISIBILITY_PIXEL
|
|
: D3D12_SHADER_VISIBILITY_ALL;
|
|
params[i].ShaderVisibility = visibility;
|
|
switch (p.kind) {
|
|
case nrhi::BindingParamKind::kConstants:
|
|
params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
|
|
params[i].Constants.ShaderRegister = p.shader_register;
|
|
params[i].Constants.Num32BitValues = p.count;
|
|
break;
|
|
case nrhi::BindingParamKind::kConstantBuffer:
|
|
params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
|
|
params[i].Descriptor.ShaderRegister = p.shader_register;
|
|
break;
|
|
case nrhi::BindingParamKind::kBufferSrv:
|
|
params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV;
|
|
params[i].Descriptor.ShaderRegister = p.shader_register;
|
|
break;
|
|
case nrhi::BindingParamKind::kTextureTable:
|
|
ranges[i].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
|
|
ranges[i].NumDescriptors = p.count;
|
|
ranges[i].BaseShaderRegister = p.shader_register;
|
|
params[i].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
|
|
params[i].DescriptorTable.NumDescriptorRanges = 1;
|
|
params[i].DescriptorTable.pDescriptorRanges = &ranges[i];
|
|
break;
|
|
}
|
|
}
|
|
D3D12_STATIC_SAMPLER_DESC samplers[nrhi::kMaxStaticSamplers] = {};
|
|
for (uint32_t i = 0; i < desc.static_sampler_count; ++i) {
|
|
const nrhi::StaticSamplerDesc& s = desc.static_samplers[i];
|
|
samplers[i].Filter = s.filter == nrhi::Filter::kAnisotropic ? D3D12_FILTER_ANISOTROPIC
|
|
: s.filter == nrhi::Filter::kLinear
|
|
? D3D12_FILTER_MIN_MAG_MIP_LINEAR
|
|
: D3D12_FILTER_MIN_MAG_MIP_POINT;
|
|
samplers[i].MaxAnisotropy = s.max_anisotropy;
|
|
D3D12_TEXTURE_ADDRESS_MODE mode = s.address == nrhi::AddressMode::kWrap
|
|
? D3D12_TEXTURE_ADDRESS_MODE_WRAP
|
|
: D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
|
samplers[i].AddressU = mode;
|
|
samplers[i].AddressV = mode;
|
|
samplers[i].AddressW = mode;
|
|
samplers[i].MaxLOD = D3D12_FLOAT32_MAX;
|
|
samplers[i].ShaderRegister = s.shader_register;
|
|
samplers[i].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
|
|
}
|
|
D3D12_ROOT_SIGNATURE_DESC rs{};
|
|
rs.NumParameters = desc.param_count;
|
|
rs.pParameters = params;
|
|
rs.NumStaticSamplers = desc.static_sampler_count;
|
|
rs.pStaticSamplers = samplers;
|
|
rs.Flags = desc.allow_input_layout ? D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
|
|
: D3D12_ROOT_SIGNATURE_FLAG_NONE;
|
|
ID3D12RootSignature* root_signature =
|
|
ui::d3d12::util::CreateRootSignature(cp_->GetD3D12Provider(), rs);
|
|
if (root_signature == nullptr) {
|
|
REXLOG_ERROR("nrhi-d3d12: root signature creation failed");
|
|
return nullptr;
|
|
}
|
|
auto* layout = new NrBindingLayoutD3D12();
|
|
layout->root_signature = root_signature;
|
|
return layout;
|
|
}
|
|
|
|
nrhi::Shader* CreateShader(const nrhi::ShaderDesc& desc) override {
|
|
D3D_SHADER_MACRO macros[9] = {};
|
|
uint32_t macro_count = 0;
|
|
if (desc.macros != nullptr) {
|
|
while (desc.macros[macro_count].name != nullptr && macro_count < 8) {
|
|
macros[macro_count].Name = desc.macros[macro_count].name;
|
|
macros[macro_count].Definition = desc.macros[macro_count].value;
|
|
++macro_count;
|
|
}
|
|
}
|
|
ID3DBlob* blob = nullptr;
|
|
ID3DBlob* errors = nullptr;
|
|
const char* target = desc.stage == nrhi::ShaderStage::kVertex ? "vs_5_0" : "ps_5_0";
|
|
HRESULT hr = D3DCompile(desc.hlsl_source, std::strlen(desc.hlsl_source),
|
|
desc.name != nullptr ? desc.name : "nrhi_shader",
|
|
macro_count != 0 ? macros : nullptr, nullptr, desc.entry_point,
|
|
target, 0, 0, &blob, &errors);
|
|
if (FAILED(hr)) {
|
|
REXLOG_ERROR("nrhi-d3d12: shader compile failed ({} {}): {}",
|
|
desc.name != nullptr ? desc.name : "?", desc.entry_point,
|
|
errors != nullptr ? static_cast<const char*>(errors->GetBufferPointer())
|
|
: "no error blob");
|
|
if (errors) errors->Release();
|
|
return nullptr;
|
|
}
|
|
if (errors) errors->Release();
|
|
auto* shader = new NrShaderD3D12();
|
|
shader->blob = blob;
|
|
return shader;
|
|
}
|
|
|
|
nrhi::Pipeline* CreateGraphicsPipeline(const nrhi::GraphicsPipelineDesc& desc) override {
|
|
auto* layout = static_cast<NrBindingLayoutD3D12*>(desc.layout);
|
|
auto* vs = static_cast<NrShaderD3D12*>(desc.vs);
|
|
auto* ps = static_cast<NrShaderD3D12*>(desc.ps);
|
|
if (layout == nullptr || vs == nullptr || ps == nullptr) return nullptr;
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC pd{};
|
|
pd.pRootSignature = layout->root_signature;
|
|
pd.VS = {vs->blob->GetBufferPointer(), vs->blob->GetBufferSize()};
|
|
pd.PS = {ps->blob->GetBufferPointer(), ps->blob->GetBufferSize()};
|
|
pd.BlendState.RenderTarget[0].BlendEnable = desc.blend.enable;
|
|
pd.BlendState.RenderTarget[0].SrcBlend = ToBlend(desc.blend.src);
|
|
pd.BlendState.RenderTarget[0].DestBlend = ToBlend(desc.blend.dst);
|
|
pd.BlendState.RenderTarget[0].BlendOp = ToBlendOp(desc.blend.op);
|
|
pd.BlendState.RenderTarget[0].SrcBlendAlpha = ToBlend(desc.blend.src_alpha);
|
|
pd.BlendState.RenderTarget[0].DestBlendAlpha = ToBlend(desc.blend.dst_alpha);
|
|
pd.BlendState.RenderTarget[0].BlendOpAlpha = ToBlendOp(desc.blend.op_alpha);
|
|
pd.BlendState.RenderTarget[0].RenderTargetWriteMask = desc.blend.write_mask;
|
|
pd.SampleMask = UINT_MAX;
|
|
pd.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
|
|
pd.RasterizerState.CullMode = desc.cull == nrhi::CullMode::kFront ? D3D12_CULL_MODE_FRONT
|
|
: desc.cull == nrhi::CullMode::kBack ? D3D12_CULL_MODE_BACK
|
|
: D3D12_CULL_MODE_NONE;
|
|
pd.RasterizerState.DepthClipEnable = desc.depth_clip;
|
|
pd.DepthStencilState.DepthEnable = desc.depth.test_enable;
|
|
pd.DepthStencilState.DepthWriteMask = desc.depth.write_enable
|
|
? D3D12_DEPTH_WRITE_MASK_ALL
|
|
: D3D12_DEPTH_WRITE_MASK_ZERO;
|
|
pd.DepthStencilState.DepthFunc = ToCompare(desc.depth.func);
|
|
D3D12_INPUT_ELEMENT_DESC elements[16] = {};
|
|
if (desc.input_elements != nullptr && desc.input_element_count != 0 &&
|
|
desc.input_element_count <= 16) {
|
|
for (uint32_t i = 0; i < desc.input_element_count; ++i) {
|
|
const nrhi::InputElementDesc& e = desc.input_elements[i];
|
|
elements[i].SemanticName = e.semantic_name;
|
|
elements[i].SemanticIndex = e.semantic_index;
|
|
elements[i].Format = ToDxgi(e.format);
|
|
elements[i].AlignedByteOffset = e.byte_offset;
|
|
elements[i].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
|
|
}
|
|
pd.InputLayout.pInputElementDescs = elements;
|
|
pd.InputLayout.NumElements = desc.input_element_count;
|
|
}
|
|
pd.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
|
if (desc.rtv_format != Format::kUnknown) {
|
|
pd.NumRenderTargets = 1;
|
|
pd.RTVFormats[0] = ToDxgi(desc.rtv_format);
|
|
}
|
|
if (desc.dsv_format != Format::kUnknown) {
|
|
pd.DSVFormat = ToDxgi(desc.dsv_format);
|
|
}
|
|
pd.SampleDesc.Count = desc.sample_count;
|
|
ID3D12PipelineState* pso = nullptr;
|
|
if (FAILED(device_->CreateGraphicsPipelineState(&pd, IID_PPV_ARGS(&pso)))) {
|
|
REXLOG_ERROR("nrhi-d3d12: graphics pipeline creation failed");
|
|
return nullptr;
|
|
}
|
|
auto* pipeline = new NrPipelineD3D12();
|
|
pipeline->pso = pso;
|
|
return pipeline;
|
|
}
|
|
|
|
uint32_t GetSupportedSampleCount(Format format, uint32_t desired) override {
|
|
DXGI_FORMAT dxgi = ToDxgi(format);
|
|
uint32_t count = desired;
|
|
while (count > 1) {
|
|
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS levels{};
|
|
levels.Format = dxgi;
|
|
levels.SampleCount = count;
|
|
if (SUCCEEDED(device_->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
|
|
&levels, sizeof(levels))) &&
|
|
levels.NumQualityLevels > 0) {
|
|
break;
|
|
}
|
|
count >>= 1;
|
|
}
|
|
return std::max(count, 1u);
|
|
}
|
|
|
|
uint64_t CurrentSubmission() const override { return cp_->GetCurrentSubmission(); }
|
|
uint64_t CompletedSubmission() const override { return cp_->GetCompletedSubmission(); }
|
|
|
|
// --- frame handling (called from the command processor) ---
|
|
|
|
nrhi::Cmd* BeginFrame(ID3D12Resource* guest_output_resource, DXGI_FORMAT guest_output_format,
|
|
D3D12_RESOURCE_STATES guest_output_internal_state, uint32_t width,
|
|
uint32_t height, nrhi::Texture** guest_output_out) {
|
|
guest_output_state_ = guest_output_internal_state;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
DrainRetired(cp_->GetCompletedSubmission());
|
|
}
|
|
NrTextureD3D12*& wrapper = guest_outputs_[guest_output_resource];
|
|
if (wrapper != nullptr &&
|
|
(wrapper->desc.width != width || wrapper->desc.height != height)) {
|
|
// Same resource pointer, different geometry: a recreated image reusing
|
|
// the address. Retire the stale wrapper's views and slots.
|
|
DestroyGuestOutputWrapper(wrapper);
|
|
wrapper = nullptr;
|
|
}
|
|
if (wrapper == nullptr) {
|
|
wrapper = new NrTextureD3D12();
|
|
guest_output_resource->AddRef();
|
|
wrapper->resource = guest_output_resource;
|
|
wrapper->desc.width = width;
|
|
wrapper->desc.height = height;
|
|
wrapper->desc.format = FromDxgi(guest_output_format);
|
|
wrapper->desc.usage = nrhi::kTextureUsageRenderTarget;
|
|
wrapper->dxgi_format = guest_output_format;
|
|
wrapper->is_guest_output = true;
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (rtv_slots_.Alloc(1, &wrapper->rtv_slot)) {
|
|
device_->CreateRenderTargetView(guest_output_resource, nullptr,
|
|
RtvHandle(wrapper->rtv_slot));
|
|
}
|
|
// The presenter keeps a small guest-output mailbox; evict wrappers of
|
|
// resources it has replaced.
|
|
if (guest_outputs_.size() > 6) {
|
|
for (auto it = guest_outputs_.begin(); it != guest_outputs_.end();) {
|
|
if (it->second != wrapper && it->first != guest_output_resource) {
|
|
DestroyGuestOutputWrapperLocked(it->second);
|
|
it = guest_outputs_.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*guest_output_out = wrapper;
|
|
return &cmd_;
|
|
}
|
|
|
|
// --- internals shared with NrCmdD3D12 ---
|
|
|
|
D3D12_CPU_DESCRIPTOR_HANDLE StagingHandle(uint32_t slot) const {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle =
|
|
staging_heap_->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += size_t(slot) * view_size_;
|
|
return handle;
|
|
}
|
|
D3D12_CPU_DESCRIPTOR_HANDLE SrvCpuHandle(uint32_t slot) const {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle = srv_heap_->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += size_t(slot) * view_size_;
|
|
return handle;
|
|
}
|
|
D3D12_GPU_DESCRIPTOR_HANDLE SrvGpuHandle(uint32_t slot) const {
|
|
D3D12_GPU_DESCRIPTOR_HANDLE handle = srv_heap_->GetGPUDescriptorHandleForHeapStart();
|
|
handle.ptr += size_t(slot) * view_size_;
|
|
return handle;
|
|
}
|
|
D3D12_CPU_DESCRIPTOR_HANDLE RtvHandle(uint32_t slot) const {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle = rtv_heap_->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += size_t(slot) * rtv_size_;
|
|
return handle;
|
|
}
|
|
D3D12_CPU_DESCRIPTOR_HANDLE DsvHandle(uint32_t slot) const {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE handle = dsv_heap_->GetCPUDescriptorHandleForHeapStart();
|
|
handle.ptr += size_t(slot) * dsv_size_;
|
|
return handle;
|
|
}
|
|
|
|
// Shader-visible binding for an ordered view tuple: consecutive slots
|
|
// holding copies of the staging descriptors, cached and retired when a
|
|
// participating view is destroyed. Render thread only.
|
|
bool GetBinding(NrTextureViewD3D12* const* views, uint32_t count,
|
|
D3D12_GPU_DESCRIPTOR_HANDLE* gpu_out) {
|
|
if (count == 0 || count > nrhi::kMaxTextureTableSize || views[0] == nullptr) {
|
|
return false;
|
|
}
|
|
BindingKey key{};
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
key.views[i] = views[i];
|
|
}
|
|
key.count = count;
|
|
auto it = bindings_.find(key);
|
|
if (it == bindings_.end()) {
|
|
uint32_t first_slot;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
if (!srv_slots_.Alloc(count, &first_slot)) {
|
|
REXLOG_ERROR("nrhi-d3d12: shader-visible view heap exhausted");
|
|
return false;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (views[i] == nullptr) continue;
|
|
device_->CopyDescriptorsSimple(1, SrvCpuHandle(first_slot + i),
|
|
StagingHandle(views[i]->staging_slot),
|
|
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
|
}
|
|
it = bindings_.emplace(key, Binding{first_slot, count}).first;
|
|
}
|
|
*gpu_out = SrvGpuHandle(it->second.first_slot);
|
|
return true;
|
|
}
|
|
|
|
D3D12CommandProcessor* cp() { return cp_; }
|
|
ID3D12DescriptorHeap* srv_heap() { return srv_heap_; }
|
|
D3D12_RESOURCE_STATES guest_output_state() const { return guest_output_state_; }
|
|
|
|
private:
|
|
static constexpr uint32_t kStagingViews = 16384;
|
|
static constexpr uint32_t kShaderVisibleViews = 32768;
|
|
static constexpr uint32_t kRtvSlots = 64;
|
|
static constexpr uint32_t kDsvSlots = 8;
|
|
|
|
struct RetiredObject {
|
|
ID3D12Resource* resource;
|
|
ID3D12PipelineState* pso;
|
|
uint64_t submission;
|
|
};
|
|
struct BindingKey {
|
|
NrTextureViewD3D12* views[nrhi::kMaxTextureTableSize];
|
|
uint32_t count;
|
|
bool operator<(const BindingKey& other) const {
|
|
if (count != other.count) return count < other.count;
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (views[i] != other.views[i]) return views[i] < other.views[i];
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
struct Binding {
|
|
uint32_t first_slot;
|
|
uint32_t count;
|
|
};
|
|
|
|
void DrainRetired(uint64_t completed) {
|
|
std::erase_if(retired_, [&](const RetiredObject& r) {
|
|
if (r.submission < completed) {
|
|
if (r.resource) r.resource->Release();
|
|
if (r.pso) r.pso->Release();
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
staging_slots_.Drain(completed);
|
|
srv_slots_.Drain(completed);
|
|
rtv_slots_.Drain(completed);
|
|
dsv_slots_.Drain(completed);
|
|
}
|
|
|
|
void DestroyGuestOutputWrapperLocked(NrTextureD3D12* wrapper) {
|
|
const uint64_t submission = cp_->GetCurrentSubmission();
|
|
if (wrapper->rtv_slot != ~0u) rtv_slots_.Retire(wrapper->rtv_slot, 1, submission);
|
|
retired_.emplace_back(RetiredObject{wrapper->resource, nullptr, submission});
|
|
delete wrapper;
|
|
}
|
|
void DestroyGuestOutputWrapper(NrTextureD3D12* wrapper) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
DestroyGuestOutputWrapperLocked(wrapper);
|
|
}
|
|
|
|
D3D12CommandProcessor* cp_;
|
|
ID3D12Device* device_ = nullptr;
|
|
NrCmdD3D12 cmd_;
|
|
|
|
ID3D12DescriptorHeap* staging_heap_ = nullptr;
|
|
ID3D12DescriptorHeap* srv_heap_ = nullptr;
|
|
ID3D12DescriptorHeap* rtv_heap_ = nullptr;
|
|
ID3D12DescriptorHeap* dsv_heap_ = nullptr;
|
|
uint32_t view_size_ = 0;
|
|
uint32_t rtv_size_ = 0;
|
|
uint32_t dsv_size_ = 0;
|
|
|
|
// mutex_ guards the slot allocators and retired_ (creation and deferred
|
|
// destruction are thread-safe); bindings_ is render-thread-only.
|
|
std::mutex mutex_;
|
|
SlotAllocator staging_slots_;
|
|
SlotAllocator srv_slots_;
|
|
SlotAllocator rtv_slots_;
|
|
SlotAllocator dsv_slots_;
|
|
std::vector<RetiredObject> retired_;
|
|
std::map<BindingKey, Binding> bindings_;
|
|
std::map<ID3D12Resource*, NrTextureD3D12*> guest_outputs_;
|
|
D3D12_RESOURCE_STATES guest_output_state_ = D3D12_RESOURCE_STATE_COMMON;
|
|
};
|
|
|
|
void NrCmdD3D12::SetBindingLayout(nrhi::BindingLayout* layout) {
|
|
auto* l = static_cast<NrBindingLayoutD3D12*>(layout);
|
|
DeferredCommandList& list = device->cp()->GetDeferredCommandList();
|
|
list.SetDescriptorHeaps(device->srv_heap(), nullptr);
|
|
list.D3DSetGraphicsRootSignature(l->root_signature);
|
|
}
|
|
|
|
void NrCmdD3D12::SetPipeline(nrhi::Pipeline* pipeline) {
|
|
device->cp()->GetDeferredCommandList().D3DSetPipelineState(
|
|
static_cast<NrPipelineD3D12*>(pipeline)->pso);
|
|
}
|
|
|
|
void NrCmdD3D12::SetRootConstants(uint32_t param, uint32_t count, const void* values,
|
|
uint32_t dest_offset_in_values) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRoot32BitConstants(
|
|
param, count, values, dest_offset_in_values);
|
|
}
|
|
|
|
void NrCmdD3D12::SetConstantBuffer(uint32_t param, nrhi::Buffer* buffer, uint64_t offset) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootConstantBufferView(
|
|
param, static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset);
|
|
}
|
|
|
|
void NrCmdD3D12::SetBufferSrv(uint32_t param, nrhi::Buffer* buffer, uint64_t offset) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootShaderResourceView(
|
|
param, static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset);
|
|
}
|
|
|
|
void NrCmdD3D12::SetTexture(uint32_t param, nrhi::TextureView* view) {
|
|
NrTextureViewD3D12* views[1] = {static_cast<NrTextureViewD3D12*>(view)};
|
|
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
|
if (device->GetBinding(views, 1, &handle)) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
|
}
|
|
}
|
|
|
|
void NrCmdD3D12::SetTexturePair(uint32_t param, nrhi::TextureView* first,
|
|
nrhi::TextureView* second) {
|
|
NrTextureViewD3D12* views[2] = {static_cast<NrTextureViewD3D12*>(first),
|
|
static_cast<NrTextureViewD3D12*>(second)};
|
|
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
|
if (device->GetBinding(views, 2, &handle)) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
|
}
|
|
}
|
|
|
|
void NrCmdD3D12::SetTextures(uint32_t param, nrhi::TextureView* const* views, uint32_t count) {
|
|
NrTextureViewD3D12* typed[nrhi::kMaxTextureTableSize] = {};
|
|
if (count > nrhi::kMaxTextureTableSize) return;
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
typed[i] = static_cast<NrTextureViewD3D12*>(views[i]);
|
|
}
|
|
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
|
if (device->GetBinding(typed, count, &handle)) {
|
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
|
}
|
|
}
|
|
|
|
void NrCmdD3D12::SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) {
|
|
D3D12_CPU_DESCRIPTOR_HANDLE rtv;
|
|
D3D12_CPU_DESCRIPTOR_HANDLE dsv;
|
|
const D3D12_CPU_DESCRIPTOR_HANDLE* rtv_ptr = nullptr;
|
|
const D3D12_CPU_DESCRIPTOR_HANDLE* dsv_ptr = nullptr;
|
|
UINT num_rtvs = 0;
|
|
if (color != nullptr) {
|
|
rtv = device->RtvHandle(static_cast<NrTextureD3D12*>(color)->rtv_slot);
|
|
rtv_ptr = &rtv;
|
|
num_rtvs = 1;
|
|
}
|
|
if (depth != nullptr) {
|
|
dsv = device->DsvHandle(static_cast<NrTextureD3D12*>(depth)->dsv_slot);
|
|
dsv_ptr = &dsv;
|
|
}
|
|
device->cp()->GetDeferredCommandList().D3DOMSetRenderTargets(num_rtvs, rtv_ptr, FALSE,
|
|
dsv_ptr);
|
|
}
|
|
|
|
void NrCmdD3D12::ClearRenderTarget(nrhi::Texture* color, const float color4[4]) {
|
|
device->cp()->GetDeferredCommandList().D3DClearRenderTargetView(
|
|
device->RtvHandle(static_cast<NrTextureD3D12*>(color)->rtv_slot), color4, 0, nullptr);
|
|
}
|
|
|
|
void NrCmdD3D12::ClearDepth(nrhi::Texture* depth, float value) {
|
|
device->cp()->GetDeferredCommandList().D3DClearDepthStencilView(
|
|
device->DsvHandle(static_cast<NrTextureD3D12*>(depth)->dsv_slot),
|
|
D3D12_CLEAR_FLAG_DEPTH, value, 0, 0, nullptr);
|
|
}
|
|
|
|
void NrCmdD3D12::SetViewport(const nrhi::Viewport& viewport) {
|
|
D3D12_VIEWPORT vp;
|
|
vp.TopLeftX = viewport.x;
|
|
vp.TopLeftY = viewport.y;
|
|
vp.Width = viewport.width;
|
|
vp.Height = viewport.height;
|
|
vp.MinDepth = viewport.min_depth;
|
|
vp.MaxDepth = viewport.max_depth;
|
|
device->cp()->GetDeferredCommandList().RSSetViewport(vp);
|
|
}
|
|
|
|
void NrCmdD3D12::SetScissor(const nrhi::Rect& rect) {
|
|
D3D12_RECT r;
|
|
r.left = rect.left;
|
|
r.top = rect.top;
|
|
r.right = rect.right;
|
|
r.bottom = rect.bottom;
|
|
device->cp()->GetDeferredCommandList().RSSetScissorRect(r);
|
|
}
|
|
|
|
void NrCmdD3D12::SetVertexBuffer(nrhi::Buffer* buffer, uint64_t offset, uint32_t size_bytes,
|
|
uint32_t stride) {
|
|
D3D12_VERTEX_BUFFER_VIEW view;
|
|
view.BufferLocation = static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset;
|
|
view.SizeInBytes = size_bytes;
|
|
view.StrideInBytes = stride;
|
|
device->cp()->GetDeferredCommandList().D3DIASetVertexBuffers(0, 1, &view);
|
|
}
|
|
|
|
void NrCmdD3D12::SetIndexBuffer(nrhi::Buffer* buffer, uint64_t offset, uint32_t size_bytes) {
|
|
D3D12_INDEX_BUFFER_VIEW view;
|
|
view.BufferLocation = static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset;
|
|
view.SizeInBytes = size_bytes;
|
|
view.Format = DXGI_FORMAT_R16_UINT;
|
|
device->cp()->GetDeferredCommandList().D3DIASetIndexBuffer(&view);
|
|
}
|
|
|
|
void NrCmdD3D12::SetPrimitiveTopology(nrhi::PrimitiveTopology topology) {
|
|
device->cp()->GetDeferredCommandList().D3DIASetPrimitiveTopology(
|
|
topology == nrhi::PrimitiveTopology::kTriangleStrip ? D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
|
|
: D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
}
|
|
|
|
void NrCmdD3D12::Draw(uint32_t vertex_count, uint32_t start_vertex) {
|
|
device->cp()->GetDeferredCommandList().D3DDrawInstanced(vertex_count, 1, start_vertex, 0);
|
|
}
|
|
|
|
void NrCmdD3D12::DrawIndexed(uint32_t index_count, uint32_t start_index, int32_t base_vertex) {
|
|
device->cp()->GetDeferredCommandList().D3DDrawIndexedInstanced(index_count, 1, start_index,
|
|
base_vertex, 0);
|
|
}
|
|
|
|
void NrCmdD3D12::CopyBufferToTexture(nrhi::Texture* dst, uint32_t mip, uint32_t array_slice,
|
|
nrhi::Buffer* src, uint64_t src_offset, uint32_t row_pitch,
|
|
uint32_t width, uint32_t height, uint32_t depth) {
|
|
auto* t = static_cast<NrTextureD3D12*>(dst);
|
|
D3D12_TEXTURE_COPY_LOCATION dst_loc{};
|
|
dst_loc.pResource = t->resource;
|
|
dst_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
dst_loc.SubresourceIndex = mip + array_slice * t->desc.mip_levels;
|
|
D3D12_TEXTURE_COPY_LOCATION src_loc{};
|
|
src_loc.pResource = static_cast<NrBufferD3D12*>(src)->resource;
|
|
src_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
src_loc.PlacedFootprint.Offset = src_offset;
|
|
src_loc.PlacedFootprint.Footprint.Format = t->dxgi_format;
|
|
src_loc.PlacedFootprint.Footprint.Width = width;
|
|
src_loc.PlacedFootprint.Footprint.Height = height;
|
|
src_loc.PlacedFootprint.Footprint.Depth = depth;
|
|
src_loc.PlacedFootprint.Footprint.RowPitch = row_pitch;
|
|
device->cp()->GetDeferredCommandList().D3DCopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc,
|
|
nullptr);
|
|
}
|
|
|
|
void NrCmdD3D12::CopyTextureToBuffer(nrhi::Buffer* dst, uint64_t dst_offset, uint32_t row_pitch,
|
|
nrhi::Texture* src, uint32_t mip, uint32_t width,
|
|
uint32_t height) {
|
|
auto* t = static_cast<NrTextureD3D12*>(src);
|
|
D3D12_TEXTURE_COPY_LOCATION src_loc{};
|
|
src_loc.pResource = t->resource;
|
|
src_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
|
src_loc.SubresourceIndex = mip;
|
|
D3D12_TEXTURE_COPY_LOCATION dst_loc{};
|
|
dst_loc.pResource = static_cast<NrBufferD3D12*>(dst)->resource;
|
|
dst_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
|
dst_loc.PlacedFootprint.Offset = dst_offset;
|
|
dst_loc.PlacedFootprint.Footprint.Format = t->dxgi_format;
|
|
dst_loc.PlacedFootprint.Footprint.Width = width;
|
|
dst_loc.PlacedFootprint.Footprint.Height = height;
|
|
dst_loc.PlacedFootprint.Footprint.Depth = 1;
|
|
dst_loc.PlacedFootprint.Footprint.RowPitch = row_pitch;
|
|
device->cp()->GetDeferredCommandList().D3DCopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc,
|
|
nullptr);
|
|
}
|
|
|
|
void NrCmdD3D12::Barrier(nrhi::Texture* texture, ResourceState before, ResourceState after) {
|
|
auto* t = static_cast<NrTextureD3D12*>(texture);
|
|
device->cp()->PushTransitionBarrier(t->resource,
|
|
ToStates(before, device->guest_output_state()),
|
|
ToStates(after, device->guest_output_state()));
|
|
}
|
|
|
|
void NrCmdD3D12::FlushBarriers() { device->cp()->SubmitBarriers(); }
|
|
|
|
} // namespace
|
|
|
|
nrhi::Device* CreateNativeRhiDevice(D3D12CommandProcessor* command_processor) {
|
|
return new NrDeviceD3D12(command_processor);
|
|
}
|
|
|
|
void DestroyNativeRhiDevice(nrhi::Device* device) {
|
|
delete static_cast<NrDeviceD3D12*>(device);
|
|
}
|
|
|
|
nrhi::Cmd* NativeRhiBeginFrame(nrhi::Device* device, ID3D12Resource* guest_output_resource,
|
|
DXGI_FORMAT guest_output_format,
|
|
D3D12_RESOURCE_STATES guest_output_internal_state, uint32_t width,
|
|
uint32_t height, nrhi::Texture** guest_output_out) {
|
|
return static_cast<NrDeviceD3D12*>(device)->BeginFrame(
|
|
guest_output_resource, guest_output_format, guest_output_internal_state, width, height,
|
|
guest_output_out);
|
|
}
|
|
|
|
} // namespace rex::graphics::d3d12
|