graphics: cut native-RHI churn stalls (batched view retirement, bounded drains, buffer bind classes, rebind skip)
This commit is contained in:
parent
7466d3c75e
commit
aa0f046ecf
3 changed files with 197 additions and 61 deletions
|
|
@ -235,9 +235,20 @@ class Pipeline {
|
||||||
// Descriptors.
|
// Descriptors.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Binding classes a buffer will be used with. kFull is the historical
|
||||||
|
// default (constant buffer / buffer SRV / vertex / index / copy source).
|
||||||
|
// Narrower classes let backends drop the descriptor-window padding that only
|
||||||
|
// dynamic-offset constant/SRV binds need, and request narrower usage.
|
||||||
|
enum class BufferBindClass : uint32_t {
|
||||||
|
kFull,
|
||||||
|
kVertexIndex, // vertex/index binds and copy source only
|
||||||
|
kCopySrc, // copy source only (texture upload staging)
|
||||||
|
};
|
||||||
|
|
||||||
struct BufferDesc {
|
struct BufferDesc {
|
||||||
uint64_t size = 0;
|
uint64_t size = 0;
|
||||||
HeapKind heap = HeapKind::kDefault;
|
HeapKind heap = HeapKind::kDefault;
|
||||||
|
BufferBindClass bind_class = BufferBindClass::kFull;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextureDesc {
|
struct TextureDesc {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
@ -395,7 +396,26 @@ class NrCmdD3D12 : public nrhi::Cmd {
|
||||||
ResourceState after) override;
|
ResourceState after) override;
|
||||||
void FlushBarriers() override;
|
void FlushBarriers() override;
|
||||||
|
|
||||||
|
// Called at frame begin and on root-signature changes: the fresh deferred
|
||||||
|
// command list / new root signature carries no bindings, so the latched
|
||||||
|
// tuples must not suppress the first re-bind.
|
||||||
|
void ResetFrameState() {
|
||||||
|
std::memset(last_table_views_, 0, sizeof(last_table_views_));
|
||||||
|
std::memset(last_table_counts_, 0, sizeof(last_table_counts_));
|
||||||
|
}
|
||||||
|
|
||||||
NrDeviceD3D12* device = nullptr;
|
NrDeviceD3D12* device = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void BindTextureTable(uint32_t param, NrTextureViewD3D12* const* views, uint32_t count);
|
||||||
|
|
||||||
|
// Last tuple bound per root param: consecutive draws mostly rebind the
|
||||||
|
// same views (shadow atlas, cube map, white fallback), skipping both the
|
||||||
|
// binding-cache lookup and the root-table re-record. A zero count is the
|
||||||
|
// empty/invalid state; real bindings always have count >= 1.
|
||||||
|
NrTextureViewD3D12* last_table_views_[nrhi::kMaxBindingParams]
|
||||||
|
[nrhi::kMaxTextureTableSize] = {};
|
||||||
|
uint32_t last_table_counts_[nrhi::kMaxBindingParams] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class NrDeviceD3D12 : public nrhi::Device {
|
class NrDeviceD3D12 : public nrhi::Device {
|
||||||
|
|
@ -432,6 +452,7 @@ class NrDeviceD3D12 : public nrhi::Device {
|
||||||
|
|
||||||
~NrDeviceD3D12() override {
|
~NrDeviceD3D12() override {
|
||||||
// Callers guarantee GPU idle; release everything immediately.
|
// Callers guarantee GPU idle; release everything immediately.
|
||||||
|
FlushDissolvedViews();
|
||||||
DrainRetired(~0ull);
|
DrainRetired(~0ull);
|
||||||
for (auto& entry : guest_outputs_) {
|
for (auto& entry : guest_outputs_) {
|
||||||
entry.second->resource->Release();
|
entry.second->resource->Release();
|
||||||
|
|
@ -585,22 +606,37 @@ class NrDeviceD3D12 : public nrhi::Device {
|
||||||
|
|
||||||
void DestroyDeferred(nrhi::TextureView* view) override {
|
void DestroyDeferred(nrhi::TextureView* view) override {
|
||||||
if (view == nullptr) return;
|
if (view == nullptr) return;
|
||||||
auto* v = static_cast<NrTextureViewD3D12*>(view);
|
// Destruction is batched: the view object stays allocated (so its
|
||||||
|
// address cannot be reused while stale binding-cache keys still hold it)
|
||||||
|
// and FlushDissolvedViews sweeps the binding cache ONCE per frame for
|
||||||
|
// the whole batch instead of once per destroyed view.
|
||||||
|
dissolved_views_.push_back(static_cast<NrTextureViewD3D12*>(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render thread, once per frame (and at device destruction): retire every
|
||||||
|
// shader-visible binding referencing a view destroyed since the last
|
||||||
|
// flush, then retire the views themselves.
|
||||||
|
void FlushDissolvedViews() {
|
||||||
|
if (dissolved_views_.empty()) return;
|
||||||
|
std::unordered_set<const NrTextureViewD3D12*> dissolved(dissolved_views_.begin(),
|
||||||
|
dissolved_views_.end());
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
const uint64_t submission = cp_->GetCurrentSubmission();
|
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) {
|
std::erase_if(bindings_, [&](auto& entry) {
|
||||||
for (uint32_t i = 0; i < entry.first.count; ++i) {
|
for (uint32_t i = 0; i < entry.first.count; ++i) {
|
||||||
if (entry.first.views[i] == v) {
|
if (dissolved.count(entry.first.views[i]) != 0) {
|
||||||
srv_slots_.Retire(entry.second.first_slot, entry.second.count, submission);
|
srv_slots_.Retire(entry.second.first_slot, entry.second.count, submission);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
for (NrTextureViewD3D12* v : dissolved_views_) {
|
||||||
|
staging_slots_.Retire(v->staging_slot, 1, submission);
|
||||||
delete v;
|
delete v;
|
||||||
}
|
}
|
||||||
|
dissolved_views_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void DestroyDeferred(nrhi::Pipeline* pipeline) override {
|
void DestroyDeferred(nrhi::Pipeline* pipeline) override {
|
||||||
if (pipeline == nullptr) return;
|
if (pipeline == nullptr) return;
|
||||||
|
|
@ -867,9 +903,14 @@ class NrDeviceD3D12 : public nrhi::Device {
|
||||||
D3D12_RESOURCE_STATES guest_output_internal_state, uint32_t width,
|
D3D12_RESOURCE_STATES guest_output_internal_state, uint32_t width,
|
||||||
uint32_t height, nrhi::Texture** guest_output_out) {
|
uint32_t height, nrhi::Texture** guest_output_out) {
|
||||||
guest_output_state_ = guest_output_internal_state;
|
guest_output_state_ = guest_output_internal_state;
|
||||||
|
cmd_.ResetFrameState();
|
||||||
|
FlushDissolvedViews();
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
DrainRetired(cp_->GetCompletedSubmission());
|
// Bounded per frame: the eviction sweeps retire thousands of objects
|
||||||
|
// at once, and releasing them all in one frame is its own hitch (the
|
||||||
|
// remainder drains over the following frames).
|
||||||
|
DrainRetired(cp_->GetCompletedSubmission(), 512);
|
||||||
}
|
}
|
||||||
NrTextureD3D12*& wrapper = guest_outputs_[guest_output_resource];
|
NrTextureD3D12*& wrapper = guest_outputs_[guest_output_resource];
|
||||||
if (wrapper != nullptr &&
|
if (wrapper != nullptr &&
|
||||||
|
|
@ -1006,9 +1047,11 @@ class NrDeviceD3D12 : public nrhi::Device {
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
};
|
};
|
||||||
|
|
||||||
void DrainRetired(uint64_t completed) {
|
void DrainRetired(uint64_t completed, size_t max_objects = SIZE_MAX) {
|
||||||
|
size_t released = 0;
|
||||||
std::erase_if(retired_, [&](const RetiredObject& r) {
|
std::erase_if(retired_, [&](const RetiredObject& r) {
|
||||||
if (r.submission < completed) {
|
if (r.submission < completed && released < max_objects) {
|
||||||
|
++released;
|
||||||
if (r.resource) r.resource->Release();
|
if (r.resource) r.resource->Release();
|
||||||
if (r.pso) r.pso->Release();
|
if (r.pso) r.pso->Release();
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1053,6 +1096,10 @@ class NrDeviceD3D12 : public nrhi::Device {
|
||||||
SlotAllocator dsv_slots_;
|
SlotAllocator dsv_slots_;
|
||||||
std::vector<RetiredObject> retired_;
|
std::vector<RetiredObject> retired_;
|
||||||
std::map<BindingKey, Binding> bindings_;
|
std::map<BindingKey, Binding> bindings_;
|
||||||
|
// Views destroyed since the last FlushDissolvedViews (render thread only).
|
||||||
|
// The objects stay allocated until the flush so stale binding-cache keys
|
||||||
|
// can never collide with a newly created view at the same address.
|
||||||
|
std::vector<NrTextureViewD3D12*> dissolved_views_;
|
||||||
std::map<ID3D12Resource*, NrTextureD3D12*> guest_outputs_;
|
std::map<ID3D12Resource*, NrTextureD3D12*> guest_outputs_;
|
||||||
D3D12_RESOURCE_STATES guest_output_state_ = D3D12_RESOURCE_STATE_COMMON;
|
D3D12_RESOURCE_STATES guest_output_state_ = D3D12_RESOURCE_STATE_COMMON;
|
||||||
};
|
};
|
||||||
|
|
@ -1062,6 +1109,8 @@ void NrCmdD3D12::SetBindingLayout(nrhi::BindingLayout* layout) {
|
||||||
DeferredCommandList& list = device->cp()->GetDeferredCommandList();
|
DeferredCommandList& list = device->cp()->GetDeferredCommandList();
|
||||||
list.SetDescriptorHeaps(device->srv_heap(), nullptr);
|
list.SetDescriptorHeaps(device->srv_heap(), nullptr);
|
||||||
list.D3DSetGraphicsRootSignature(l->root_signature);
|
list.D3DSetGraphicsRootSignature(l->root_signature);
|
||||||
|
// Root-signature semantics: all bindings reset.
|
||||||
|
ResetFrameState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NrCmdD3D12::SetPipeline(nrhi::Pipeline* pipeline) {
|
void NrCmdD3D12::SetPipeline(nrhi::Pipeline* pipeline) {
|
||||||
|
|
@ -1085,22 +1134,31 @@ void NrCmdD3D12::SetBufferSrv(uint32_t param, nrhi::Buffer* buffer, uint64_t off
|
||||||
param, static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset);
|
param, static_cast<NrBufferD3D12*>(buffer)->gpu_va + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NrCmdD3D12::BindTextureTable(uint32_t param, NrTextureViewD3D12* const* views,
|
||||||
|
uint32_t count) {
|
||||||
|
if (param >= nrhi::kMaxBindingParams) return;
|
||||||
|
if (count == last_table_counts_[param] &&
|
||||||
|
std::memcmp(last_table_views_[param], views, count * sizeof(views[0])) == 0) {
|
||||||
|
return; // identical tuple already bound on this root param
|
||||||
|
}
|
||||||
|
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
||||||
|
if (device->GetBinding(views, count, &handle)) {
|
||||||
|
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
||||||
|
std::memcpy(last_table_views_[param], views, count * sizeof(views[0]));
|
||||||
|
last_table_counts_[param] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NrCmdD3D12::SetTexture(uint32_t param, nrhi::TextureView* view) {
|
void NrCmdD3D12::SetTexture(uint32_t param, nrhi::TextureView* view) {
|
||||||
NrTextureViewD3D12* views[1] = {static_cast<NrTextureViewD3D12*>(view)};
|
NrTextureViewD3D12* views[1] = {static_cast<NrTextureViewD3D12*>(view)};
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
BindTextureTable(param, views, 1);
|
||||||
if (device->GetBinding(views, 1, &handle)) {
|
|
||||||
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NrCmdD3D12::SetTexturePair(uint32_t param, nrhi::TextureView* first,
|
void NrCmdD3D12::SetTexturePair(uint32_t param, nrhi::TextureView* first,
|
||||||
nrhi::TextureView* second) {
|
nrhi::TextureView* second) {
|
||||||
NrTextureViewD3D12* views[2] = {static_cast<NrTextureViewD3D12*>(first),
|
NrTextureViewD3D12* views[2] = {static_cast<NrTextureViewD3D12*>(first),
|
||||||
static_cast<NrTextureViewD3D12*>(second)};
|
static_cast<NrTextureViewD3D12*>(second)};
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
BindTextureTable(param, views, 2);
|
||||||
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) {
|
void NrCmdD3D12::SetTextures(uint32_t param, nrhi::TextureView* const* views, uint32_t count) {
|
||||||
|
|
@ -1109,10 +1167,7 @@ void NrCmdD3D12::SetTextures(uint32_t param, nrhi::TextureView* const* views, ui
|
||||||
for (uint32_t i = 0; i < count; ++i) {
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
typed[i] = static_cast<NrTextureViewD3D12*>(views[i]);
|
typed[i] = static_cast<NrTextureViewD3D12*>(views[i]);
|
||||||
}
|
}
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE handle;
|
BindTextureTable(param, typed, count);
|
||||||
if (device->GetBinding(typed, count, &handle)) {
|
|
||||||
device->cp()->GetDeferredCommandList().D3DSetGraphicsRootDescriptorTable(param, handle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NrCmdD3D12::SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) {
|
void NrCmdD3D12::SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) {
|
||||||
|
|
|
||||||
|
|
@ -248,8 +248,9 @@ class NrBufferVulkan : public nrhi::Buffer {
|
||||||
VkBuffer buffer = VK_NULL_HANDLE;
|
VkBuffer buffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = VK_NULL_HANDLE;
|
VmaAllocation allocation = VK_NULL_HANDLE;
|
||||||
void* mapping = nullptr;
|
void* mapping = nullptr;
|
||||||
uint64_t size_ = 0; // the app-requested size (allocation is padded)
|
uint64_t size_ = 0; // the app-requested size (kFull allocations are padded)
|
||||||
HeapKind heap = HeapKind::kDefault;
|
HeapKind heap = HeapKind::kDefault;
|
||||||
|
nrhi::BufferBindClass bind_class = nrhi::BufferBindClass::kFull;
|
||||||
bool device_local = false; // memory-type DEVICE_LOCAL (diagnostics)
|
bool device_local = false; // memory-type DEVICE_LOCAL (diagnostics)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -384,10 +385,11 @@ struct NrFrameProf {
|
||||||
Bucket view_create;
|
Bucket view_create;
|
||||||
Bucket const_slice;
|
Bucket const_slice;
|
||||||
Bucket pipeline_build;
|
Bucket pipeline_build;
|
||||||
|
Bucket drain;
|
||||||
void Reset() { *this = NrFrameProf{}; }
|
void Reset() { *this = NrFrameProf{}; }
|
||||||
uint64_t Total() const {
|
uint64_t Total() const {
|
||||||
return pass_open.us + flush_barriers.us + copies.us + table_miss.us + set0_miss.us +
|
return pass_open.us + flush_barriers.us + copies.us + table_miss.us + set0_miss.us +
|
||||||
view_destroy.us + view_create.us + const_slice.us + pipeline_build.us;
|
view_destroy.us + view_create.us + const_slice.us + pipeline_build.us + drain.us;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -535,6 +537,7 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
// Callers guarantee GPU idle; release everything immediately.
|
// Callers guarantee GPU idle; release everything immediately.
|
||||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions();
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions();
|
||||||
const VkDevice device = vulkan_device_->device();
|
const VkDevice device = vulkan_device_->device();
|
||||||
|
FlushDissolvedViews();
|
||||||
DrainRetired(~0ull);
|
DrainRetired(~0ull);
|
||||||
for (auto& entry : set0_sets_) {
|
for (auto& entry : set0_sets_) {
|
||||||
dfn.vkFreeDescriptorSets(device, entry.second.pool, 1, &entry.second.set);
|
dfn.vkFreeDescriptorSets(device, entry.second.pool, 1, &entry.second.set);
|
||||||
|
|
@ -649,12 +652,31 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
VmaAllocationCreateInfo alloc_info = {};
|
VmaAllocationCreateInfo alloc_info = {};
|
||||||
switch (desc.heap) {
|
// The descriptor-window padding only matters for buffers that can be
|
||||||
case HeapKind::kUpload:
|
// bound through dynamic-offset UBO/SSBO descriptors; narrower bind
|
||||||
info.size = desc.size + kBufferPadding;
|
// classes skip it (and the descriptor usage bits).
|
||||||
info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
|
const bool descriptor_bindable = desc.bind_class == nrhi::BufferBindClass::kFull;
|
||||||
|
const uint64_t padding = descriptor_bindable ? kBufferPadding : 0;
|
||||||
|
VkBufferUsageFlags app_usage;
|
||||||
|
switch (desc.bind_class) {
|
||||||
|
case nrhi::BufferBindClass::kCopySrc:
|
||||||
|
app_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||||
|
break;
|
||||||
|
case nrhi::BufferBindClass::kVertexIndex:
|
||||||
|
app_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
|
||||||
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||||
|
break;
|
||||||
|
case nrhi::BufferBindClass::kFull:
|
||||||
|
default:
|
||||||
|
app_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
||||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (desc.heap) {
|
||||||
|
case HeapKind::kUpload:
|
||||||
|
info.size = desc.size + padding;
|
||||||
|
info.usage = app_usage;
|
||||||
alloc_info.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
alloc_info.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
alloc_info.usage = VMA_MEMORY_USAGE_AUTO;
|
alloc_info.usage = VMA_MEMORY_USAGE_AUTO;
|
||||||
|
|
@ -676,13 +698,14 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
break;
|
break;
|
||||||
case HeapKind::kDefault:
|
case HeapKind::kDefault:
|
||||||
default:
|
default:
|
||||||
info.size = desc.size + kBufferPadding;
|
info.size = desc.size + padding;
|
||||||
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
|
info.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | app_usage;
|
||||||
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
|
|
||||||
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
|
||||||
alloc_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
alloc_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Unpadded classes could otherwise reach vkCreateBuffer with size 0
|
||||||
|
// (invalid); the padded classes never could.
|
||||||
|
info.size = std::max<VkDeviceSize>(info.size, 4);
|
||||||
VkBuffer buffer = VK_NULL_HANDLE;
|
VkBuffer buffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = VK_NULL_HANDLE;
|
VmaAllocation allocation = VK_NULL_HANDLE;
|
||||||
VmaAllocationInfo result_info = {};
|
VmaAllocationInfo result_info = {};
|
||||||
|
|
@ -712,6 +735,7 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
b->mapping = result_info.pMappedData; // persistent map for host heaps
|
b->mapping = result_info.pMappedData; // persistent map for host heaps
|
||||||
b->size_ = desc.size;
|
b->size_ = desc.size;
|
||||||
b->heap = desc.heap;
|
b->heap = desc.heap;
|
||||||
|
b->bind_class = desc.bind_class;
|
||||||
b->device_local = mem_dl;
|
b->device_local = mem_dl;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
|
@ -806,8 +830,10 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
// Retire cached set-0 descriptor sets whose buffer tuple references this
|
// Retire cached set-0 descriptor sets whose buffer tuple references this
|
||||||
// buffer. set0_sets_ is render-thread-only, like the D3D12 backend's
|
// buffer. set0_sets_ is render-thread-only, like the D3D12 backend's
|
||||||
// bindings_: buffer destruction of bound buffers happens on the render
|
// bindings_: buffer destruction of bound buffers happens on the render
|
||||||
// thread.
|
// thread. Non-kFull buffers can never appear in a descriptor tuple, so
|
||||||
|
// their destruction (the mesh/staging churn path) skips the scan.
|
||||||
const uint64_t submission = cp_->GetCurrentSubmission();
|
const uint64_t submission = cp_->GetCurrentSubmission();
|
||||||
|
if (b->bind_class == nrhi::BufferBindClass::kFull) {
|
||||||
for (auto it = set0_sets_.begin(); it != set0_sets_.end();) {
|
for (auto it = set0_sets_.begin(); it != set0_sets_.end();) {
|
||||||
bool references = false;
|
bool references = false;
|
||||||
for (uint32_t i = 0; i < it->first.count; ++i) {
|
for (uint32_t i = 0; i < it->first.count; ++i) {
|
||||||
|
|
@ -824,6 +850,7 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (b->heap == HeapKind::kUpload) {
|
if (b->heap == HeapKind::kUpload) {
|
||||||
(b->device_local ? g_buf_bytes_upload_dl : g_buf_bytes_upload_host)
|
(b->device_local ? g_buf_bytes_upload_dl : g_buf_bytes_upload_host)
|
||||||
.fetch_sub(b->size_, std::memory_order_relaxed);
|
.fetch_sub(b->size_, std::memory_order_relaxed);
|
||||||
|
|
@ -864,15 +891,33 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
|
|
||||||
void DestroyDeferred(nrhi::TextureView* view) override {
|
void DestroyDeferred(nrhi::TextureView* view) override {
|
||||||
if (view == nullptr) return;
|
if (view == nullptr) return;
|
||||||
NrProfScope prof_scope(prof_.view_destroy);
|
// Destruction is batched: the view object stays allocated (so its
|
||||||
|
// address cannot be reused by a new view while stale cache keys still
|
||||||
|
// hold it) and FlushDissolvedViews sweeps the descriptor-set cache ONCE
|
||||||
|
// per frame for the whole batch. The eviction sweeps retire ~1500 views
|
||||||
|
// in a single frame; a per-view cache scan was a measured 10-15 ms
|
||||||
|
// recurring hitch.
|
||||||
auto* v = static_cast<NrTextureViewVulkan*>(view);
|
auto* v = static_cast<NrTextureViewVulkan*>(view);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
live_views_.erase(v);
|
||||||
|
}
|
||||||
|
dissolved_views_.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render thread, once per frame (and at device destruction): retire every
|
||||||
|
// cached texture descriptor set referencing a view destroyed since the
|
||||||
|
// last flush, then retire the views themselves.
|
||||||
|
void FlushDissolvedViews() {
|
||||||
|
if (dissolved_views_.empty()) return;
|
||||||
|
NrProfScope prof_scope(prof_.view_destroy);
|
||||||
const uint64_t submission = cp_->GetCurrentSubmission();
|
const uint64_t submission = cp_->GetCurrentSubmission();
|
||||||
// Retire every cached texture descriptor set this view participates in
|
std::unordered_set<const NrTextureViewVulkan*> dissolved(dissolved_views_.begin(),
|
||||||
// (render-thread-only cache, mirroring the D3D12 backend's bindings_).
|
dissolved_views_.end());
|
||||||
for (auto it = table_sets_.begin(); it != table_sets_.end();) {
|
for (auto it = table_sets_.begin(); it != table_sets_.end();) {
|
||||||
bool references = false;
|
bool references = false;
|
||||||
for (uint32_t i = 0; i < it->first.count; ++i) {
|
for (uint32_t i = 0; i < it->first.count; ++i) {
|
||||||
if (it->first.views[i] == v) {
|
if (dissolved.count(it->first.views[i]) != 0) {
|
||||||
references = true;
|
references = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -886,13 +931,15 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
live_views_.erase(v);
|
for (NrTextureViewVulkan* v : dissolved_views_) {
|
||||||
RetiredObject r;
|
RetiredObject r;
|
||||||
r.submission = submission;
|
r.submission = submission;
|
||||||
r.view = v->view;
|
r.view = v->view;
|
||||||
retired_.push_back(r);
|
retired_.push_back(r);
|
||||||
delete v;
|
delete v;
|
||||||
}
|
}
|
||||||
|
dissolved_views_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void DestroyDeferred(nrhi::Pipeline* pipeline) override {
|
void DestroyDeferred(nrhi::Pipeline* pipeline) override {
|
||||||
if (pipeline == nullptr) return;
|
if (pipeline == nullptr) return;
|
||||||
|
|
@ -1210,9 +1257,13 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
bool guest_output_ever_written, uint32_t width, uint32_t height,
|
bool guest_output_ever_written, uint32_t width, uint32_t height,
|
||||||
nrhi::Texture** guest_output_out) {
|
nrhi::Texture** guest_output_out) {
|
||||||
prof_.Reset();
|
prof_.Reset();
|
||||||
|
FlushDissolvedViews();
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
DrainRetired(cp_->GetCompletedSubmission());
|
// Bounded per frame: the eviction sweeps retire thousands of objects
|
||||||
|
// at once, and destroying them all in one frame is its own hitch (the
|
||||||
|
// remainder drains over the following frames).
|
||||||
|
DrainRetired(cp_->GetCompletedSubmission(), 512);
|
||||||
}
|
}
|
||||||
++frame_index_;
|
++frame_index_;
|
||||||
if ((frame_index_ % 600) == 0) {
|
if ((frame_index_ % 600) == 0) {
|
||||||
|
|
@ -1307,13 +1358,14 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
REXLOG_INFO(
|
REXLOG_INFO(
|
||||||
"nrhi-vulkan: SLOW frame {}us: pass_open={}us/{} flush={}us/{} copies={}us/{} "
|
"nrhi-vulkan: SLOW frame {}us: pass_open={}us/{} flush={}us/{} copies={}us/{} "
|
||||||
"table_miss={}us/{} set0_miss={}us/{} view_destroy={}us/{} view_create={}us/{} "
|
"table_miss={}us/{} set0_miss={}us/{} view_destroy={}us/{} view_create={}us/{} "
|
||||||
"const={}us/{} pso={}us/{}",
|
"const={}us/{} pso={}us/{} drain={}us/{}",
|
||||||
total_us, prof_.pass_open.us, prof_.pass_open.count, prof_.flush_barriers.us,
|
total_us, prof_.pass_open.us, prof_.pass_open.count, prof_.flush_barriers.us,
|
||||||
prof_.flush_barriers.count, prof_.copies.us, prof_.copies.count, prof_.table_miss.us,
|
prof_.flush_barriers.count, prof_.copies.us, prof_.copies.count, prof_.table_miss.us,
|
||||||
prof_.table_miss.count, prof_.set0_miss.us, prof_.set0_miss.count,
|
prof_.table_miss.count, prof_.set0_miss.us, prof_.set0_miss.count,
|
||||||
prof_.view_destroy.us, prof_.view_destroy.count, prof_.view_create.us,
|
prof_.view_destroy.us, prof_.view_destroy.count, prof_.view_create.us,
|
||||||
prof_.view_create.count, prof_.const_slice.us, prof_.const_slice.count,
|
prof_.view_create.count, prof_.const_slice.us, prof_.const_slice.count,
|
||||||
prof_.pipeline_build.us, prof_.pipeline_build.count);
|
prof_.pipeline_build.us, prof_.pipeline_build.count, prof_.drain.us,
|
||||||
|
prof_.drain.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1742,11 +1794,14 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
retired_.push_back(r);
|
retired_.push_back(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrainRetired(uint64_t completed) {
|
void DrainRetired(uint64_t completed, size_t max_objects = SIZE_MAX) {
|
||||||
|
NrProfScope prof_scope(prof_.drain);
|
||||||
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions();
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device_->functions();
|
||||||
const VkDevice device = vulkan_device_->device();
|
const VkDevice device = vulkan_device_->device();
|
||||||
|
size_t destroyed = 0;
|
||||||
std::erase_if(retired_, [&](const RetiredObject& r) {
|
std::erase_if(retired_, [&](const RetiredObject& r) {
|
||||||
if (r.submission >= completed) return false;
|
if (r.submission >= completed || destroyed >= max_objects) return false;
|
||||||
|
++destroyed;
|
||||||
if (r.framebuffer != VK_NULL_HANDLE) dfn.vkDestroyFramebuffer(device, r.framebuffer, nullptr);
|
if (r.framebuffer != VK_NULL_HANDLE) dfn.vkDestroyFramebuffer(device, r.framebuffer, nullptr);
|
||||||
if (r.view != VK_NULL_HANDLE) dfn.vkDestroyImageView(device, r.view, nullptr);
|
if (r.view != VK_NULL_HANDLE) dfn.vkDestroyImageView(device, r.view, nullptr);
|
||||||
for (VkPipeline pipeline : r.pipelines) {
|
for (VkPipeline pipeline : r.pipelines) {
|
||||||
|
|
@ -2027,6 +2082,11 @@ class NrDeviceVulkan : public nrhi::Device {
|
||||||
std::unordered_set<NrPipelineVulkan*> live_pipelines_;
|
std::unordered_set<NrPipelineVulkan*> live_pipelines_;
|
||||||
std::unordered_set<NrShaderVulkan*> live_shaders_;
|
std::unordered_set<NrShaderVulkan*> live_shaders_;
|
||||||
|
|
||||||
|
// Views destroyed since the last FlushDissolvedViews (render thread only).
|
||||||
|
// The objects stay allocated until the flush so stale descriptor-set cache
|
||||||
|
// keys can never collide with a newly created view at the same address.
|
||||||
|
std::vector<NrTextureViewVulkan*> dissolved_views_;
|
||||||
|
|
||||||
std::map<RenderPassKey, VkRenderPass> render_passes_;
|
std::map<RenderPassKey, VkRenderPass> render_passes_;
|
||||||
std::map<FramebufferKey, VkFramebuffer> framebuffers_;
|
std::map<FramebufferKey, VkFramebuffer> framebuffers_;
|
||||||
std::vector<VkDescriptorPool> descriptor_pools_;
|
std::vector<VkDescriptorPool> descriptor_pools_;
|
||||||
|
|
@ -2312,14 +2372,24 @@ void NrCmdVulkan::SetTextures(uint32_t param, nrhi::TextureView* const* views, u
|
||||||
if (layout_ == nullptr || param >= layout_->param_count) return;
|
if (layout_ == nullptr || param >= layout_->param_count) return;
|
||||||
const NrBindingLayoutVulkan::ParamInfo& p = layout_->params[param];
|
const NrBindingLayoutVulkan::ParamInfo& p = layout_->params[param];
|
||||||
if (p.kind != nrhi::BindingParamKind::kTextureTable) return;
|
if (p.kind != nrhi::BindingParamKind::kTextureTable) return;
|
||||||
|
// Consecutive draws mostly rebind the same tuple (shadow atlas, cube map,
|
||||||
|
// white fallback); leaving the table clean skips the per-draw descriptor
|
||||||
|
// set lookup and rebind entirely.
|
||||||
|
bool changed = false;
|
||||||
for (uint32_t i = 0; i < p.table_size; ++i) {
|
for (uint32_t i = 0; i < p.table_size; ++i) {
|
||||||
// Missing tail entries (table declares N, bound M<N) and null entries
|
// Missing tail entries (table declares N, bound M<N) and null entries
|
||||||
// fall back to the backend's 1x1 white.
|
// fall back to the backend's 1x1 white.
|
||||||
table_views_[p.table_index][i] =
|
NrTextureViewVulkan* view =
|
||||||
i < count ? static_cast<NrTextureViewVulkan*>(views[i]) : nullptr;
|
i < count ? static_cast<NrTextureViewVulkan*>(views[i]) : nullptr;
|
||||||
|
if (table_views_[p.table_index][i] != view) {
|
||||||
|
table_views_[p.table_index][i] = view;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
table_dirty_[p.table_index] = true;
|
table_dirty_[p.table_index] = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NrCmdVulkan::SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) {
|
void NrCmdVulkan::SetRenderTargets(nrhi::Texture* color, nrhi::Texture* depth) {
|
||||||
EndRenderPassIfOpen();
|
EndRenderPassIfOpen();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue