[D3D12] Render target binding

This commit is contained in:
Triang3l
2018-08-10 18:06:21 +03:00
parent c0c0ca263d
commit 952bb91c3f
8 changed files with 551 additions and 162 deletions

View File

@@ -27,8 +27,6 @@ RenderTargetCache::RenderTargetCache(D3D12CommandProcessor* command_processor,
RenderTargetCache::~RenderTargetCache() { Shutdown(); }
bool RenderTargetCache::Initialize() { return true; }
void RenderTargetCache::Shutdown() { ClearCache(); }
void RenderTargetCache::ClearCache() {
@@ -41,6 +39,19 @@ void RenderTargetCache::ClearCache() {
}
render_targets_.clear();
while (descriptor_heaps_depth_ != nullptr) {
auto heap = descriptor_heaps_depth_;
heap->heap->Release();
descriptor_heaps_depth_ = heap->previous;
delete heap;
}
while (descriptor_heaps_color_ != nullptr) {
auto heap = descriptor_heaps_color_;
heap->heap->Release();
descriptor_heaps_color_ = heap->previous;
delete heap;
}
for (uint32_t i = 0; i < xe::countof(heaps_); ++i) {
if (heaps_[i] != nullptr) {
heaps_[i]->Release();
@@ -51,7 +62,7 @@ void RenderTargetCache::ClearCache() {
void RenderTargetCache::BeginFrame() { ClearBindings(); }
void RenderTargetCache::UpdateRenderTargets() {
bool RenderTargetCache::UpdateRenderTargets() {
// There are two kinds of render target binding updates in this implementation
// in case something has been changed - full and partial.
//
@@ -90,16 +101,22 @@ void RenderTargetCache::UpdateRenderTargets() {
// made to the lower part of RT0. So, before draws 2 and 3, full updates must
// be done.
//
// Full updates are better for memory usage than partial updates though, as
// the render targets are re-allocated in the heaps, which means that they can
// be allocated more tightly, preventing too many 32 MB heaps from being
// created.
// Direct3D 12 also requires all render targets to have the same size, so the
// height is calculated from the EDRAM space available to the last render
// target available in it. However, to make toggling render targets like in
// the Banjo-Kazooie case possible, the height may be decreased only in full
// updates.
// TODO(Triang3l): Check if it's safe to calculate the smallest EDRAM region
// without aliasing and use it for the height. This won't work if games
// actually alias active render targets for some reason.
//
// To summarize, a full update happens if:
// - Starting a new frame.
// - Drawing after resolving.
// - Surface pitch changed.
// - Sample count changed.
// - Render target is disabled and another render target got more space than
// is currently available in the textures.
// - EDRAM base of a currently used RT changed.
// - Format of a currently used RT changed.
// - Current viewport contains unsaved data from previously used render
@@ -112,18 +129,18 @@ void RenderTargetCache::UpdateRenderTargets() {
//
// A partial update happens if:
// - New render target is added, but doesn't overlap unsaved data from other
// currently or previously used render targets.
// currently or previously used render targets, and it doesn't require a
// bigger size.
auto command_list = command_processor_->GetCurrentCommandList();
if (command_list == nullptr) {
return;
return false;
}
auto& regs = *register_file_;
uint32_t rb_surface_info = regs[XE_GPU_REG_RB_SURFACE_INFO].u32;
uint32_t surface_pitch = std::min(rb_surface_info & 0x3FFF, 2560u);
if (surface_pitch == 0) {
assert_always();
return;
return false;
}
MsaaSamples msaa_samples = MsaaSamples((rb_surface_info >> 16) & 0x3);
uint32_t msaa_samples_x = msaa_samples >= MsaaSamples::k4X ? 2 : 1;
@@ -178,10 +195,26 @@ void RenderTargetCache::UpdateRenderTargets() {
// clamp the dirty region heights.
uint32_t edram_row_tiles_32bpp = (surface_pitch * msaa_samples_x + 79) / 80;
uint32_t edram_row_tiles[5];
uint32_t edram_max_rows[5];
uint32_t edram_max_rows = UINT32_MAX;
for (uint32_t i = 0; i < 5; ++i) {
edram_row_tiles[i] = edram_row_tiles_32bpp * (formats_are_64bpp[i] ? 2 : 1);
edram_max_rows[i] = (2048 - edram_bases[i]) / edram_row_tiles[i];
if (enabled[i]) {
// Direct3D 12 doesn't allow render targets with different sizes, so
// calculate the height from the render target closest to the end of
// EDRAM.
edram_max_rows = std::min(edram_max_rows,
(2048 - edram_bases[i]) / edram_row_tiles[i]);
}
}
if (edram_max_rows == 0 || edram_max_rows == UINT32_MAX) {
// Some render target is totally in the end of EDRAM, or nothing is drawn.
return false;
}
// Check the following full update conditions:
// - Render target is disabled and another render target got more space than
// is currently available in the textures.
if (edram_max_rows > current_edram_max_rows_) {
full_update = true;
}
// Get EDRAM usage of the current draw so dirty regions can be calculated.
@@ -210,7 +243,8 @@ void RenderTargetCache::UpdateRenderTargets() {
}
uint32_t dirty_bottom =
std::min(std::min(viewport_bottom, scissor_bottom), 2560u);
uint32_t edram_rows = (dirty_bottom * msaa_samples_y + 15) >> 4;
uint32_t edram_dirty_rows =
std::min((dirty_bottom * msaa_samples_y + 15) >> 4, edram_max_rows);
// Check the following full update conditions:
// - EDRAM base of a currently used RT changed.
@@ -257,8 +291,7 @@ void RenderTargetCache::UpdateRenderTargets() {
}
// Checking if the new render target is overlapping any bound one.
// binding_1 is the new render target.
edram_length_1 =
std::min(edram_rows, edram_max_rows[i]) * edram_row_tiles[i];
edram_length_1 = edram_dirty_rows * edram_row_tiles[i];
}
for (uint32_t j = 0; j < 5; ++j) {
const RenderTargetBinding& binding_2 = current_bindings_[j];
@@ -272,8 +305,7 @@ void RenderTargetCache::UpdateRenderTargets() {
}
// Checking if now overlapping a previously used render target.
// binding_2 is a currently used render target.
edram_length_2 =
std::min(edram_rows, edram_max_rows[j]) * edram_row_tiles[i];
edram_length_2 = edram_dirty_rows * edram_row_tiles[i];
} else {
// Checking if the new render target is overlapping any bound one.
// binding_2 is another bound render target.
@@ -295,82 +327,200 @@ void RenderTargetCache::UpdateRenderTargets() {
}
}
// If no need to attach any new render targets, update dirty regions and exit.
if (!full_update && !render_targets_to_attach) {
// Need to change the bindings.
if (full_update || render_targets_to_attach) {
uint32_t heap_usage[5] = {};
if (full_update) {
// Export the currently bound render targets before we ruin the bindings.
WriteRenderTargetsToEDRAM();
ClearBindings();
current_surface_pitch_ = surface_pitch;
current_msaa_samples_ = msaa_samples;
current_edram_max_rows_ = edram_max_rows;
// If updating fully, need to reattach all the render targets and allocate
// from scratch.
for (uint32_t i = 0; i < 5; ++i) {
if (enabled[i]) {
render_targets_to_attach |= 1 << i;
}
}
} else {
// If updating partially, only need to attach new render targets.
for (uint32_t i = 0; i < 5; ++i) {
const RenderTargetBinding& binding = current_bindings_[i];
if (!binding.is_bound) {
continue;
}
const RenderTarget* render_target = binding.render_target;
if (render_target != nullptr) {
// There are no holes between 4 MB pages in each heap.
heap_usage[render_target->heap_page_first >> 3] +=
render_target->heap_page_count;
continue;
}
}
}
XELOGGPU("RT Cache: %s update - pitch %u, samples %u, RTs to attach %u",
full_update ? "Full" : "Partial", surface_pitch, msaa_samples,
render_targets_to_attach);
auto device =
command_processor_->GetD3D12Context()->GetD3D12Provider()->GetDevice();
D3D12_RESOURCE_BARRIER barriers[5];
uint32_t barrier_count = 0;
// Allocate new render targets and add them to the bindings list.
for (uint32_t i = 0; i < 5; ++i) {
if (!enabled[i] || (i == 4 && depth_readonly)) {
if (!(render_targets_to_attach & (1 << i))) {
continue;
}
RenderTargetBinding& binding = current_bindings_[i];
binding.edram_dirty_length = std::max(
binding.edram_dirty_length,
std::min(edram_rows, edram_max_rows[i]) * edram_row_tiles[i]);
}
return;
}
// From this point, the function MUST NOT FAIL, otherwise bindings will be
// left in an incomplete state.
uint32_t heap_usage[5] = {};
if (full_update) {
// Export the currently bound render targets before we ruin the bindings.
WriteRenderTargetsToEDRAM();
ClearBindings();
current_surface_pitch_ = surface_pitch;
current_msaa_samples_ = msaa_samples;
// If updating fully, need to reattach all the render targets and allocate
// from scratch.
for (uint32_t i = 0; i < 5; ++i) {
if (enabled[i]) {
render_targets_to_attach |= 1 << i;
}
}
} else {
// If updating partially, only need to attach new render targets.
for (uint32_t i = 0; i < 5; ++i) {
const RenderTargetBinding& binding = current_bindings_[i];
if (!binding.is_bound) {
continue;
}
const RenderTarget* render_target = binding.render_target;
if (render_target != nullptr) {
// There are no holes between 4 MB pages in each heap.
heap_usage[render_target->heap_page_first >> 3] +=
render_target->heap_page_count;
continue;
}
}
}
XELOGGPU("RT Cache: %s update - pitch %u, samples %u, RTs to attach %u",
full_update ? "Full" : "Partial", surface_pitch, msaa_samples,
render_targets_to_attach);
// Allocate the new render targets.
// TODO(Triang3l): Actually allocate them.
// TODO(Triang3l): Load the contents from the EDRAM.
// TODO(Triang3l): Bind the render targets to the command list.
// Write the new bindings and update the dirty regions.
for (uint32_t i = 0; i < 5; ++i) {
if (!enabled[i]) {
continue;
}
RenderTargetBinding& binding = current_bindings_[i];
if (render_targets_to_attach & (1 << i)) {
binding.is_bound = true;
binding.edram_base = edram_bases[i];
binding.edram_dirty_length = 0;
binding.format = formats[i];
binding.render_target = nullptr;
RenderTargetKey key;
key.width_ss_div_80 = edram_row_tiles_32bpp;
key.height_ss_div_16 = current_edram_max_rows_;
key.is_depth = i == 4;
key.format = formats[i];
D3D12_RESOURCE_DESC resource_desc;
if (!GetResourceDesc(key, resource_desc)) {
// Invalid format.
continue;
}
// Calculate the number of 4 MB pages of 32 MB heaps this RT will use.
D3D12_RESOURCE_ALLOCATION_INFO allocation_info =
device->GetResourceAllocationInfo(0, 1, &resource_desc);
if (allocation_info.SizeInBytes == 0 ||
allocation_info.SizeInBytes > (32 << 20)) {
assert_always();
continue;
}
uint32_t heap_page_count =
(uint32_t(allocation_info.SizeInBytes) + ((4 << 20) - 1)) >> 22;
// Find the heap page range for this render target.
uint32_t heap_page_first = UINT32_MAX;
for (uint32_t j = 0; j < 5; ++j) {
if (heap_usage[j] + heap_page_count <= 8) {
heap_page_first = j * 8 + heap_usage[j];
break;
}
}
if (heap_page_first == UINT32_MAX) {
assert_always();
continue;
}
// Get the render target.
binding.render_target = FindOrCreateRenderTarget(key, heap_page_first);
if (binding.render_target == nullptr) {
continue;
}
// Inform Direct3D that we're reusing the heap for this render target.
D3D12_RESOURCE_BARRIER& barrier = barriers[barrier_count++];
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_ALIASING;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Aliasing.pResourceBefore = nullptr;
barrier.Aliasing.pResourceAfter = binding.render_target->resource;
}
if (!(i == 4 && depth_readonly)) {
binding.edram_dirty_length = std::max(
binding.edram_dirty_length,
std::min(edram_rows, edram_max_rows[i]) * edram_row_tiles[i]);
if (barrier_count != 0) {
command_list->ResourceBarrier(barrier_count, barriers);
}
barrier_count = 0;
// Load the contents of the new render targets from the EDRAM buffer and
// switch their state to RTV/DSV.
for (uint32_t i = 0; i < 5; ++i) {
if (!(render_targets_to_attach & (1 << i))) {
continue;
}
RenderTarget* render_target = current_bindings_[i].render_target;
if (render_target == nullptr) {
continue;
}
// TODO(Triang3l): Load the contents from the EDRAM buffer.
// After loading from the EDRAM buffer (which may make this render target
// a copy destination), switch it to RTV/DSV if needed.
D3D12_RESOURCE_STATES state = i == 4 ? D3D12_RESOURCE_STATE_DEPTH_WRITE
: D3D12_RESOURCE_STATE_RENDER_TARGET;
if (render_target->state != state) {
D3D12_RESOURCE_BARRIER& barrier = barriers[barrier_count++];
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = render_target->resource;
barrier.Transition.Subresource =
D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = render_target->state;
barrier.Transition.StateAfter = state;
render_target->state = state;
}
}
if (barrier_count != 0) {
command_list->ResourceBarrier(barrier_count, barriers);
}
// Compress the list of the render target because null RTV descriptors are
// broken in Direct3D 12 and bind the render targets to the command list.
D3D12_CPU_DESCRIPTOR_HANDLE rtv_handles[4];
uint32_t rtv_count = 0;
for (uint32_t i = 0; i < 4; ++i) {
const RenderTargetBinding& binding = current_bindings_[i];
if (!binding.is_bound || binding.render_target == nullptr) {
continue;
}
rtv_handles[rtv_count] = binding.render_target->handle;
current_pipeline_render_targets_[rtv_count].guest_render_target = i;
current_pipeline_render_targets_[rtv_count].format =
GetColorDXGIFormat(ColorRenderTargetFormat(formats[4]));
++rtv_count;
}
for (uint32_t i = rtv_count; i < 4; ++i) {
current_pipeline_render_targets_[i].guest_render_target = i;
current_pipeline_render_targets_[i].format = DXGI_FORMAT_UNKNOWN;
}
const D3D12_CPU_DESCRIPTOR_HANDLE* dsv_handle;
const RenderTargetBinding& depth_binding = current_bindings_[4];
current_pipeline_render_targets_[4].guest_render_target = 4;
if (depth_binding.is_bound && depth_binding.render_target != nullptr) {
dsv_handle = &depth_binding.render_target->handle;
current_pipeline_render_targets_[4].format =
GetDepthDXGIFormat(DepthRenderTargetFormat(formats[4]));
} else {
dsv_handle = nullptr;
current_pipeline_render_targets_[4].format = DXGI_FORMAT_UNKNOWN;
}
command_list->OMSetRenderTargets(rtv_count, rtv_handles, FALSE, dsv_handle);
}
// Update the dirty regions.
for (uint32_t i = 0; i < 5; ++i) {
if (!enabled[i] || (i == 4 && depth_readonly)) {
continue;
}
RenderTargetBinding& binding = current_bindings_[i];
if (binding.render_target == nullptr) {
// Nothing to store to the EDRAM buffer if there was an error.
continue;
}
binding.edram_dirty_length = std::max(
binding.edram_dirty_length, edram_dirty_rows * edram_row_tiles[i]);
}
return true;
}
void RenderTargetCache::EndFrame() {
@@ -410,9 +560,155 @@ DXGI_FORMAT RenderTargetCache::GetColorDXGIFormat(
void RenderTargetCache::ClearBindings() {
current_surface_pitch_ = 0;
current_msaa_samples_ = MsaaSamples::k1X;
current_edram_max_rows_ = 0;
std::memset(current_bindings_, 0, sizeof(current_bindings_));
}
bool RenderTargetCache::GetResourceDesc(RenderTargetKey key,
D3D12_RESOURCE_DESC& desc) {
if (key.width_ss_div_80 == 0 || key.height_ss_div_16 == 0) {
return false;
}
DXGI_FORMAT dxgi_format =
key.is_depth ? GetDepthDXGIFormat(DepthRenderTargetFormat(key.format))
: GetColorDXGIFormat(ColorRenderTargetFormat(key.format));
if (dxgi_format == DXGI_FORMAT_UNKNOWN) {
return false;
}
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
// TODO(Triang3l): If real MSAA is added, alignment must be 4 MB.
desc.Alignment = 0;
desc.Width = key.width_ss_div_80 * 80;
desc.Height = key.height_ss_div_16 * 16;
desc.DepthOrArraySize = 1;
desc.MipLevels = 1;
desc.Format = dxgi_format;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
desc.Flags = key.is_depth ? D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
: D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
return true;
}
RenderTargetCache::RenderTarget* RenderTargetCache::FindOrCreateRenderTarget(
RenderTargetKey key, uint32_t heap_page_first) {
assert_true(heap_page_first <= 8 * 5);
// TODO(Triang3l): Find an existing render target.
D3D12_RESOURCE_DESC resource_desc;
if (!GetResourceDesc(key, resource_desc)) {
return nullptr;
}
auto provider = command_processor_->GetD3D12Context()->GetD3D12Provider();
auto device = provider->GetDevice();
// Get the number of heap pages needed for the render target.
D3D12_RESOURCE_ALLOCATION_INFO allocation_info =
device->GetResourceAllocationInfo(0, 1, &resource_desc);
uint32_t heap_page_count =
(uint32_t(allocation_info.SizeInBytes) + ((4 << 20) - 1)) >> 22;
if (heap_page_count == 0 || (heap_page_first & 7) + heap_page_count > 8) {
assert_always();
return nullptr;
}
// Create a new descriptor heap if needed, and get a place for the descriptor.
auto& descriptor_heap =
key.is_depth ? descriptor_heaps_depth_ : descriptor_heaps_color_;
if (descriptor_heap == nullptr ||
descriptor_heap->descriptors_used >= kRenderTargetDescriptorHeapSize) {
D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc;
descriptor_heap_desc.Type = key.is_depth ? D3D12_DESCRIPTOR_HEAP_TYPE_DSV
: D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
descriptor_heap_desc.NumDescriptors = kRenderTargetDescriptorHeapSize;
descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
descriptor_heap_desc.NodeMask = 0;
ID3D12DescriptorHeap* new_d3d_descriptor_heap;
if (FAILED(device->CreateDescriptorHeap(
&descriptor_heap_desc, IID_PPV_ARGS(&new_d3d_descriptor_heap)))) {
XELOGE("Failed to create a heap for %u %s buffer descriptors",
kRenderTargetDescriptorHeapSize, key.is_depth ? "depth" : "color");
return nullptr;
}
RenderTargetDescriptorHeap* new_descriptor_heap =
new RenderTargetDescriptorHeap;
new_descriptor_heap->heap = new_d3d_descriptor_heap;
new_descriptor_heap->start_handle =
new_d3d_descriptor_heap->GetCPUDescriptorHandleForHeapStart();
new_descriptor_heap->descriptors_used = 0;
new_descriptor_heap->previous = descriptor_heap;
descriptor_heap = new_descriptor_heap;
}
// Create the memory heap if it doesn't exist yet.
ID3D12Heap* heap = heaps_[heap_page_first >> 3];
if (heap == nullptr) {
D3D12_HEAP_DESC heap_desc = {};
heap_desc.SizeInBytes = 32 << 20;
heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
// TODO(Triang3l): If real MSAA is added, alignment must be 4 MB.
heap_desc.Alignment = 0;
heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES;
if (FAILED(device->CreateHeap(&heap_desc, IID_PPV_ARGS(&heap)))) {
XELOGE("Failed to create a 32 MB heap for render targets");
return nullptr;
}
heaps_[heap_page_first >> 3] = heap;
}
// The first action likely to be done is EDRAM buffer load.
D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COPY_DEST;
ID3D12Resource* resource;
if (FAILED(device->CreatePlacedResource(heap, (heap_page_first & 7) << 22,
&resource_desc, state, nullptr,
IID_PPV_ARGS(&resource)))) {
XELOGE(
"Failed to create a placed resource for %ux%u %s render target with "
"format %u at heap 4 MB pages %u:%u",
uint32_t(resource_desc.Width), resource_desc.Height,
key.is_depth ? "depth" : "color", key.format, heap_page_first,
heap_page_first + heap_page_count - 1);
return nullptr;
}
// Create the descriptor for the render target.
D3D12_CPU_DESCRIPTOR_HANDLE descriptor_handle;
if (key.is_depth) {
descriptor_handle.ptr =
descriptor_heap->start_handle.ptr +
descriptor_heap->descriptors_used * provider->GetDescriptorSizeDSV();
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc;
dsv_desc.Format = resource_desc.Format;
dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
dsv_desc.Flags = D3D12_DSV_FLAG_NONE;
dsv_desc.Texture2D.MipSlice = 0;
device->CreateDepthStencilView(resource, &dsv_desc, descriptor_handle);
} else {
descriptor_handle.ptr =
descriptor_heap->start_handle.ptr +
descriptor_heap->descriptors_used * provider->GetDescriptorSizeRTV();
D3D12_RENDER_TARGET_VIEW_DESC rtv_desc;
rtv_desc.Format = resource_desc.Format;
rtv_desc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
rtv_desc.Texture2D.MipSlice = 0;
rtv_desc.Texture2D.PlaneSlice = 0;
device->CreateRenderTargetView(resource, &rtv_desc, descriptor_handle);
}
++descriptor_heap->descriptors_used;
RenderTarget* render_target = new RenderTarget;
render_target->resource = resource;
render_target->state = state;
render_target->handle = descriptor_handle;
render_target->key = key;
render_target->heap_page_first = heap_page_first;
render_target->heap_page_count = heap_page_count;
render_targets_.insert(std::make_pair(key.value, render_target));
return render_target;
}
void RenderTargetCache::WriteRenderTargetsToEDRAM() {}
} // namespace d3d12