mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-12 00:59:11 +01:00
83f307ec7e
Verify that DXFramebuffer's integer RTV descriptor's cpu_handle has been allocated before using it, and if it hasn't use the non-integer RTV descriptor instead. This fixes a Dolphin crash in Twilight Princess, and possibly other games (Issue 13312). As an optimization to save space in the descriptor heap, DXFramebuffer's integer descriptor is only initialized if the given abstract texture format has different integer and non-integer RTV formats. This previously wasn't accounted for by GetIntRTVDescriptorArray, which could cause DX12::Gfx::BindFramebuffer to call OMSetRenderTargets with an invalid descriptor which would lead to a crash. Triggering the bug was fortunately rare because integer formats are only used when blending is disabled and logic ops are enabled. Furthermore, the standard integer abstract format is RGBA8 which has different integer and non-integer RTV formats, causing the integer descriptor to be initialized and avoiding the bug. The crash started appearing in a2702c6 because it changed the swapchain's abstract texture format from RGBA8 to RGB10_A2. Unlike RGBA8, RGB10_A2 has the same integer and non-integer RTV formats and so the bug can be triggered if the other requirements are met.
797 lines
31 KiB
C++
797 lines
31 KiB
C++
// Copyright 2019 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "VideoBackends/D3D12/DX12Texture.h"
|
|
|
|
#include "Common/Align.h"
|
|
#include "Common/Assert.h"
|
|
#include "Common/StringUtil.h"
|
|
|
|
#include "VideoBackends/D3D12/Common.h"
|
|
#include "VideoBackends/D3D12/D3D12Gfx.h"
|
|
#include "VideoBackends/D3D12/D3D12StreamBuffer.h"
|
|
#include "VideoBackends/D3D12/DX12Context.h"
|
|
#include "VideoBackends/D3D12/DescriptorHeapManager.h"
|
|
|
|
namespace DX12
|
|
{
|
|
static D3D12_BOX RectangleToBox(const MathUtil::Rectangle<int>& rc)
|
|
{
|
|
return D3D12_BOX{static_cast<UINT>(rc.left), static_cast<UINT>(rc.top), 0,
|
|
static_cast<UINT>(rc.right), static_cast<UINT>(rc.bottom), 1};
|
|
}
|
|
|
|
static ComPtr<ID3D12Resource> CreateTextureUploadBuffer(u32 buffer_size)
|
|
{
|
|
const D3D12_HEAP_PROPERTIES heap_properties = {D3D12_HEAP_TYPE_UPLOAD};
|
|
const D3D12_RESOURCE_DESC desc = {D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
0,
|
|
buffer_size,
|
|
1,
|
|
1,
|
|
1,
|
|
DXGI_FORMAT_UNKNOWN,
|
|
{1, 0},
|
|
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
|
D3D12_RESOURCE_FLAG_NONE};
|
|
|
|
ComPtr<ID3D12Resource> resource;
|
|
HRESULT hr = g_dx_context->GetDevice()->CreateCommittedResource(
|
|
&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,
|
|
IID_PPV_ARGS(&resource));
|
|
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to create texture upload buffer: {}", DX12HRWrap(hr));
|
|
return resource;
|
|
}
|
|
|
|
DXTexture::DXTexture(const TextureConfig& config, ID3D12Resource* resource,
|
|
D3D12_RESOURCE_STATES state, std::string_view name)
|
|
: AbstractTexture(config), m_resource(resource), m_state(state), m_name(UTF8ToWString(name))
|
|
{
|
|
if (!m_name.empty())
|
|
{
|
|
resource->SetName(m_name.c_str());
|
|
}
|
|
}
|
|
|
|
DXTexture::~DXTexture()
|
|
{
|
|
if (m_uav_descriptor)
|
|
{
|
|
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetDescriptorHeapManager(),
|
|
m_uav_descriptor.index);
|
|
}
|
|
|
|
if (m_srv_descriptor)
|
|
{
|
|
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetDescriptorHeapManager(),
|
|
m_srv_descriptor.index);
|
|
}
|
|
if (m_resource)
|
|
g_dx_context->DeferResourceDestruction(m_resource.Get());
|
|
}
|
|
|
|
std::unique_ptr<DXTexture> DXTexture::Create(const TextureConfig& config, std::string_view name)
|
|
{
|
|
constexpr D3D12_HEAP_PROPERTIES heap_properties = {D3D12_HEAP_TYPE_DEFAULT};
|
|
D3D12_RESOURCE_STATES resource_state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
|
|
D3D12_RESOURCE_FLAGS resource_flags = D3D12_RESOURCE_FLAG_NONE;
|
|
if (config.IsRenderTarget())
|
|
{
|
|
if (IsDepthFormat(config.format))
|
|
{
|
|
resource_state = D3D12_RESOURCE_STATE_DEPTH_WRITE;
|
|
resource_flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
|
|
}
|
|
else
|
|
{
|
|
resource_state = D3D12_RESOURCE_STATE_RENDER_TARGET;
|
|
resource_flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
|
}
|
|
}
|
|
if (config.IsComputeImage())
|
|
resource_flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
|
|
|
const D3D12_RESOURCE_DESC resource_desc = {
|
|
D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
|
0,
|
|
config.width,
|
|
config.height,
|
|
static_cast<u16>(config.layers),
|
|
static_cast<u16>(config.levels),
|
|
D3DCommon::GetDXGIFormatForAbstractFormat(config.format, config.IsRenderTarget()),
|
|
{config.samples, 0},
|
|
D3D12_TEXTURE_LAYOUT_UNKNOWN,
|
|
resource_flags};
|
|
|
|
D3D12_CLEAR_VALUE optimized_clear_value = {};
|
|
if (config.IsRenderTarget())
|
|
{
|
|
optimized_clear_value.Format =
|
|
IsDepthFormat(config.format) ?
|
|
D3DCommon::GetDSVFormatForAbstractFormat(config.format) :
|
|
D3DCommon::GetRTVFormatForAbstractFormat(config.format, false);
|
|
}
|
|
|
|
ComPtr<ID3D12Resource> resource;
|
|
HRESULT hr = g_dx_context->GetDevice()->CreateCommittedResource(
|
|
&heap_properties, D3D12_HEAP_FLAG_NONE, &resource_desc, resource_state,
|
|
config.IsRenderTarget() ? &optimized_clear_value : nullptr, IID_PPV_ARGS(&resource));
|
|
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to create D3D12 texture resource: {}", DX12HRWrap(hr));
|
|
if (FAILED(hr))
|
|
return nullptr;
|
|
|
|
auto tex =
|
|
std::unique_ptr<DXTexture>(new DXTexture(config, resource.Get(), resource_state, name));
|
|
if (!tex->CreateSRVDescriptor() || (config.IsComputeImage() && !tex->CreateUAVDescriptor()))
|
|
return nullptr;
|
|
|
|
return tex;
|
|
}
|
|
|
|
std::unique_ptr<DXTexture> DXTexture::CreateAdopted(ID3D12Resource* resource)
|
|
{
|
|
const D3D12_RESOURCE_DESC desc = resource->GetDesc();
|
|
const AbstractTextureFormat format = D3DCommon::GetAbstractFormatForDXGIFormat(desc.Format);
|
|
if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D ||
|
|
format == AbstractTextureFormat::Undefined)
|
|
{
|
|
PanicAlertFmt("Unknown format for adopted texture");
|
|
return nullptr;
|
|
}
|
|
|
|
TextureConfig config(static_cast<u32>(desc.Width), desc.Height, desc.MipLevels,
|
|
desc.DepthOrArraySize, desc.SampleDesc.Count, format, 0);
|
|
if (desc.Flags &
|
|
(D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL))
|
|
{
|
|
config.flags |= AbstractTextureFlag_RenderTarget;
|
|
}
|
|
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)
|
|
config.flags |= AbstractTextureFlag_ComputeImage;
|
|
|
|
auto tex =
|
|
std::unique_ptr<DXTexture>(new DXTexture(config, resource, D3D12_RESOURCE_STATE_COMMON, ""));
|
|
if (!tex->CreateSRVDescriptor())
|
|
return nullptr;
|
|
|
|
return tex;
|
|
}
|
|
|
|
bool DXTexture::CreateSRVDescriptor()
|
|
{
|
|
if (!g_dx_context->GetDescriptorHeapManager().Allocate(&m_srv_descriptor))
|
|
{
|
|
PanicAlertFmt("Failed to allocate SRV descriptor");
|
|
return false;
|
|
}
|
|
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC desc = {D3DCommon::GetSRVFormatForAbstractFormat(m_config.format),
|
|
m_config.IsMultisampled() ?
|
|
D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY :
|
|
D3D12_SRV_DIMENSION_TEXTURE2DARRAY,
|
|
D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING};
|
|
if (m_config.IsMultisampled())
|
|
{
|
|
desc.Texture2DMSArray.ArraySize = m_config.layers;
|
|
}
|
|
else
|
|
{
|
|
desc.Texture2DArray.MipLevels = m_config.levels;
|
|
desc.Texture2DArray.ArraySize = m_config.layers;
|
|
}
|
|
g_dx_context->GetDevice()->CreateShaderResourceView(m_resource.Get(), &desc,
|
|
m_srv_descriptor.cpu_handle);
|
|
return true;
|
|
}
|
|
|
|
bool DXTexture::CreateUAVDescriptor()
|
|
{
|
|
if (!g_dx_context->GetDescriptorHeapManager().Allocate(&m_uav_descriptor))
|
|
{
|
|
PanicAlertFmt("Failed to allocate UAV descriptor");
|
|
return false;
|
|
}
|
|
|
|
D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {
|
|
D3DCommon::GetSRVFormatForAbstractFormat(m_config.format),
|
|
D3D12_UAV_DIMENSION_TEXTURE2DARRAY};
|
|
desc.Texture2DArray.ArraySize = m_config.layers;
|
|
g_dx_context->GetDevice()->CreateUnorderedAccessView(m_resource.Get(), nullptr, &desc,
|
|
m_uav_descriptor.cpu_handle);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DXTexture::Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer,
|
|
size_t buffer_size, u32 layer)
|
|
{
|
|
// Textures greater than 1024*1024 will be put in staging textures that are released after
|
|
// execution instead. A 2048x2048 texture is 16MB, and we'd only fit four of these in our
|
|
// streaming buffer and be blocking frequently. Games are unlikely to have textures this
|
|
// large anyway, so it's only really an issue for HD texture packs, and memory is not
|
|
// a limiting factor in these scenarios anyway.
|
|
constexpr u32 STAGING_BUFFER_UPLOAD_THRESHOLD = 1024 * 1024 * 4;
|
|
|
|
// Determine the stride in the stream buffer. It must be aligned to 256 bytes.
|
|
const u32 block_size = GetBlockSizeForFormat(GetFormat());
|
|
const u32 num_rows = Common::AlignUp(height, block_size) / block_size;
|
|
const u32 source_stride = CalculateStrideForFormat(m_config.format, row_length);
|
|
const u32 upload_stride = Common::AlignUp(source_stride, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT);
|
|
const u32 upload_size = upload_stride * num_rows;
|
|
|
|
// Both paths need us in COPY_DEST state, and avoids switching back and forth for mips.
|
|
TransitionToState(D3D12_RESOURCE_STATE_COPY_DEST);
|
|
|
|
ComPtr<ID3D12Resource> staging_buffer;
|
|
ID3D12Resource* upload_buffer_resource;
|
|
void* upload_buffer_ptr;
|
|
u32 upload_buffer_offset;
|
|
if (upload_size >= STAGING_BUFFER_UPLOAD_THRESHOLD)
|
|
{
|
|
const D3D12_RANGE read_range = {0, 0};
|
|
staging_buffer = CreateTextureUploadBuffer(upload_size);
|
|
if (!staging_buffer)
|
|
{
|
|
PanicAlertFmt("Failed to allocate temporary texture upload buffer");
|
|
return;
|
|
}
|
|
HRESULT hr = staging_buffer->Map(0, &read_range, &upload_buffer_ptr);
|
|
if (FAILED(hr))
|
|
{
|
|
PanicAlertFmt("Failed to map temporary texture upload buffer: {}", DX12HRWrap(hr));
|
|
return;
|
|
}
|
|
|
|
// We defer releasing the buffer until after the command list with the copy has executed.
|
|
g_dx_context->DeferResourceDestruction(staging_buffer.Get());
|
|
upload_buffer_resource = staging_buffer.Get();
|
|
upload_buffer_offset = 0;
|
|
}
|
|
else
|
|
{
|
|
if (!g_dx_context->GetTextureUploadBuffer().ReserveMemory(
|
|
upload_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT))
|
|
{
|
|
WARN_LOG_FMT(VIDEO,
|
|
"Executing command list while waiting for space in texture upload buffer");
|
|
Gfx::GetInstance()->ExecuteCommandList(false);
|
|
if (!g_dx_context->GetTextureUploadBuffer().ReserveMemory(
|
|
upload_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT))
|
|
{
|
|
PanicAlertFmt("Failed to allocate texture upload buffer");
|
|
return;
|
|
}
|
|
}
|
|
|
|
upload_buffer_resource = g_dx_context->GetTextureUploadBuffer().GetBuffer();
|
|
upload_buffer_ptr = g_dx_context->GetTextureUploadBuffer().GetCurrentHostPointer();
|
|
upload_buffer_offset = g_dx_context->GetTextureUploadBuffer().GetCurrentOffset();
|
|
}
|
|
|
|
// Copy in, slow path if the pitch differs.
|
|
if (source_stride != upload_stride)
|
|
{
|
|
const u8* src_ptr = buffer;
|
|
const u32 copy_size = std::min(source_stride, upload_stride);
|
|
u8* dst_ptr = reinterpret_cast<u8*>(upload_buffer_ptr);
|
|
for (u32 i = 0; i < num_rows; i++)
|
|
{
|
|
std::memcpy(dst_ptr, src_ptr, copy_size);
|
|
src_ptr += source_stride;
|
|
dst_ptr += upload_stride;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::memcpy(upload_buffer_ptr, buffer, std::min<size_t>(buffer_size, upload_size));
|
|
}
|
|
|
|
if (staging_buffer)
|
|
{
|
|
const D3D12_RANGE write_range = {0, std::min<size_t>(buffer_size, upload_size)};
|
|
staging_buffer->Unmap(0, &write_range);
|
|
}
|
|
else
|
|
{
|
|
g_dx_context->GetTextureUploadBuffer().CommitMemory(upload_size);
|
|
}
|
|
|
|
// Issue copy from buffer->texture.
|
|
const u32 aligned_width = Common::AlignUp(width, block_size);
|
|
const u32 aligned_height = Common::AlignUp(height, block_size);
|
|
const D3D12_TEXTURE_COPY_LOCATION dst_loc = {m_resource.Get(),
|
|
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
{static_cast<UINT>(CalcSubresource(level, layer))}};
|
|
const D3D12_TEXTURE_COPY_LOCATION src_loc = {
|
|
upload_buffer_resource,
|
|
D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
|
{{upload_buffer_offset, D3DCommon::GetDXGIFormatForAbstractFormat(m_config.format, false),
|
|
aligned_width, aligned_height, 1, upload_stride}}};
|
|
const D3D12_BOX src_box{0, 0, 0, aligned_width, aligned_height, 1};
|
|
g_dx_context->GetCommandList()->CopyTextureRegion(&dst_loc, 0, 0, 0, &src_loc, &src_box);
|
|
|
|
// Preemptively transition to shader read only after uploading the last mip level, as we're
|
|
// likely finished with writes to this texture for now.
|
|
if (level == (m_config.levels - 1))
|
|
TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
|
}
|
|
|
|
void DXTexture::CopyRectangleFromTexture(const AbstractTexture* src,
|
|
const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
|
|
u32 src_level, const MathUtil::Rectangle<int>& dst_rect,
|
|
u32 dst_layer, u32 dst_level)
|
|
{
|
|
const DXTexture* src_dxtex = static_cast<const DXTexture*>(src);
|
|
ASSERT(static_cast<u32>(src_rect.right) <= src->GetWidth() &&
|
|
static_cast<u32>(src_rect.bottom) <= src->GetHeight() && src_layer <= src->GetLayers() &&
|
|
src_level <= src->GetLevels() && static_cast<u32>(dst_rect.right) <= GetWidth() &&
|
|
static_cast<u32>(dst_rect.bottom) <= GetHeight() && dst_layer <= GetLayers() &&
|
|
dst_level <= GetLevels() && src_rect.GetWidth() == dst_rect.GetWidth() &&
|
|
src_rect.GetHeight() == dst_rect.GetHeight());
|
|
const D3D12_TEXTURE_COPY_LOCATION dst_loc = {
|
|
m_resource.Get(),
|
|
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
{static_cast<UINT>(CalcSubresource(dst_level, dst_layer))}};
|
|
const D3D12_TEXTURE_COPY_LOCATION src_loc = {
|
|
src_dxtex->m_resource.Get(),
|
|
D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
{static_cast<UINT>(src_dxtex->CalcSubresource(src_level, src_layer))}};
|
|
const D3D12_BOX src_box = RectangleToBox(src_rect);
|
|
const D3D12_RESOURCE_STATES old_src_state = src_dxtex->m_state;
|
|
src_dxtex->TransitionToState(D3D12_RESOURCE_STATE_COPY_SOURCE);
|
|
TransitionToState(D3D12_RESOURCE_STATE_COPY_DEST);
|
|
|
|
g_dx_context->GetCommandList()->CopyTextureRegion(&dst_loc, dst_rect.left, dst_rect.top, 0,
|
|
&src_loc, &src_box);
|
|
|
|
// Only restore the source layout. Destination is restored by FinishedRendering().
|
|
src_dxtex->TransitionToState(old_src_state);
|
|
}
|
|
|
|
void DXTexture::ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& rect,
|
|
u32 layer, u32 level)
|
|
{
|
|
const DXTexture* src_dxtex = static_cast<const DXTexture*>(src);
|
|
|
|
D3D12_RESOURCE_STATES old_src_state = src_dxtex->m_state;
|
|
src_dxtex->TransitionToState(D3D12_RESOURCE_STATE_RESOLVE_SOURCE);
|
|
TransitionToState(D3D12_RESOURCE_STATE_RESOLVE_DEST);
|
|
|
|
g_dx_context->GetCommandList()->ResolveSubresource(
|
|
m_resource.Get(), CalcSubresource(level, layer), src_dxtex->m_resource.Get(),
|
|
src_dxtex->CalcSubresource(level, layer),
|
|
D3DCommon::GetDXGIFormatForAbstractFormat(m_config.format, false));
|
|
|
|
// Only restore the source layout. Destination is restored by FinishedRendering().
|
|
src_dxtex->TransitionToState(old_src_state);
|
|
}
|
|
|
|
void DXTexture::FinishedRendering()
|
|
{
|
|
if (m_state != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
|
|
TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
|
|
}
|
|
|
|
void DXTexture::TransitionToState(D3D12_RESOURCE_STATES state) const
|
|
{
|
|
if (m_state == state)
|
|
return;
|
|
|
|
ResourceBarrier(g_dx_context->GetCommandList(), m_resource.Get(), m_state, state);
|
|
m_state = state;
|
|
}
|
|
|
|
void DXTexture::DestroyResource()
|
|
{
|
|
if (m_uav_descriptor)
|
|
g_dx_context->GetDescriptorHeapManager().Free(m_uav_descriptor);
|
|
|
|
if (m_srv_descriptor)
|
|
g_dx_context->GetDescriptorHeapManager().Free(m_srv_descriptor);
|
|
|
|
m_resource.Reset();
|
|
}
|
|
|
|
DXFramebuffer::DXFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment,
|
|
std::vector<AbstractTexture*> additional_color_attachments,
|
|
AbstractTextureFormat color_format, AbstractTextureFormat depth_format,
|
|
u32 width, u32 height, u32 layers, u32 samples)
|
|
: AbstractFramebuffer(color_attachment, depth_attachment,
|
|
std::move(additional_color_attachments), color_format, depth_format,
|
|
width, height, layers, samples)
|
|
{
|
|
}
|
|
|
|
DXFramebuffer::~DXFramebuffer()
|
|
{
|
|
if (m_depth_attachment)
|
|
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetDSVHeapManager(),
|
|
m_dsv_descriptor.index);
|
|
if (m_color_attachment)
|
|
{
|
|
if (m_int_rtv_descriptor)
|
|
{
|
|
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetRTVHeapManager(),
|
|
m_int_rtv_descriptor.index);
|
|
}
|
|
}
|
|
for (auto render_target : m_render_targets)
|
|
{
|
|
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetRTVHeapManager(),
|
|
render_target.index);
|
|
}
|
|
}
|
|
|
|
const D3D12_CPU_DESCRIPTOR_HANDLE* DXFramebuffer::GetIntRTVDescriptorArray() const
|
|
{
|
|
if (m_color_attachment == nullptr)
|
|
return nullptr;
|
|
|
|
const auto& handle = m_int_rtv_descriptor.cpu_handle;
|
|
|
|
// To save space in the descriptor heap, m_int_rtv_descriptor.cpu_handle.ptr is only allocated
|
|
// when the integer RTV format corresponding to the current abstract format differs from the
|
|
// non-integer RTV format. Only use the integer handle if it has been allocated.
|
|
if (handle.ptr != 0)
|
|
return &handle;
|
|
|
|
// The integer and non-integer RTV formats are the same, so use the non-integer descriptor.
|
|
return GetRTVDescriptorArray();
|
|
}
|
|
|
|
void DXFramebuffer::Unbind()
|
|
{
|
|
static const D3D12_DISCARD_REGION dr = {0, nullptr, 0, 1};
|
|
if (HasColorBuffer())
|
|
{
|
|
g_dx_context->GetCommandList()->DiscardResource(
|
|
static_cast<DXTexture*>(GetColorAttachment())->GetResource(), &dr);
|
|
}
|
|
for (auto additional_color_attachment : m_additional_color_attachments)
|
|
{
|
|
g_dx_context->GetCommandList()->DiscardResource(
|
|
static_cast<DXTexture*>(additional_color_attachment)->GetResource(), &dr);
|
|
}
|
|
if (HasDepthBuffer())
|
|
{
|
|
g_dx_context->GetCommandList()->DiscardResource(
|
|
static_cast<DXTexture*>(GetDepthAttachment())->GetResource(), &dr);
|
|
}
|
|
}
|
|
|
|
void DXFramebuffer::ClearRenderTargets(const ClearColor& color_value,
|
|
const D3D12_RECT* rectangle) const
|
|
{
|
|
for (auto render_target : m_render_targets_raw)
|
|
{
|
|
g_dx_context->GetCommandList()->ClearRenderTargetView(render_target, color_value.data(),
|
|
rectangle ? 1 : 0, rectangle);
|
|
}
|
|
}
|
|
|
|
void DXFramebuffer::ClearDepth(float depth_value, const D3D12_RECT* rectangle) const
|
|
{
|
|
if (HasDepthBuffer())
|
|
{
|
|
g_dx_context->GetCommandList()->ClearDepthStencilView(GetDSVDescriptor().cpu_handle,
|
|
D3D12_CLEAR_FLAG_DEPTH, depth_value, 0,
|
|
rectangle ? 1 : 0, rectangle);
|
|
}
|
|
}
|
|
|
|
void DXFramebuffer::TransitionRenderTargets() const
|
|
{
|
|
if (HasColorBuffer())
|
|
{
|
|
static_cast<DXTexture*>(GetColorAttachment())
|
|
->TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
|
|
}
|
|
for (auto additional_color_attachment : m_additional_color_attachments)
|
|
{
|
|
static_cast<DXTexture*>(additional_color_attachment)
|
|
->TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<DXFramebuffer>
|
|
DXFramebuffer::Create(DXTexture* color_attachment, DXTexture* depth_attachment,
|
|
std::vector<AbstractTexture*> additional_color_attachments)
|
|
{
|
|
if (!ValidateConfig(color_attachment, depth_attachment, additional_color_attachments))
|
|
return nullptr;
|
|
|
|
const AbstractTextureFormat color_format =
|
|
color_attachment ? color_attachment->GetFormat() : AbstractTextureFormat::Undefined;
|
|
const AbstractTextureFormat depth_format =
|
|
depth_attachment ? depth_attachment->GetFormat() : AbstractTextureFormat::Undefined;
|
|
const DXTexture* either_attachment = color_attachment ? color_attachment : depth_attachment;
|
|
const u32 width = either_attachment->GetWidth();
|
|
const u32 height = either_attachment->GetHeight();
|
|
const u32 layers = either_attachment->GetLayers();
|
|
const u32 samples = either_attachment->GetSamples();
|
|
|
|
std::unique_ptr<DXFramebuffer> fb(
|
|
new DXFramebuffer(color_attachment, depth_attachment, std::move(additional_color_attachments),
|
|
color_format, depth_format, width, height, layers, samples));
|
|
if (!fb->CreateRTVDescriptors() || (color_attachment && !fb->CreateIRTVDescriptor()) ||
|
|
(depth_attachment && !fb->CreateDSVDescriptor()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return fb;
|
|
}
|
|
|
|
bool DXFramebuffer::CreateRTVDescriptors()
|
|
{
|
|
if (m_color_attachment)
|
|
{
|
|
if (!CreateRTVDescriptor(m_layers, m_color_attachment))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (auto* attachment : m_additional_color_attachments)
|
|
{
|
|
if (!CreateRTVDescriptor(1, attachment))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DXFramebuffer::CreateRTVDescriptor(u32 layers, AbstractTexture* attachment)
|
|
{
|
|
DescriptorHandle rtv;
|
|
if (!g_dx_context->GetRTVHeapManager().Allocate(&rtv))
|
|
{
|
|
PanicAlertFmt("Failed to allocate RTV descriptor");
|
|
return false;
|
|
}
|
|
m_render_targets.push_back(std::move(rtv));
|
|
m_render_targets_raw.push_back(m_render_targets.back().cpu_handle);
|
|
|
|
const bool multisampled = m_samples > 1;
|
|
D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = {
|
|
D3DCommon::GetRTVFormatForAbstractFormat(m_color_format, false),
|
|
multisampled ? D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY : D3D12_RTV_DIMENSION_TEXTURE2DARRAY};
|
|
if (multisampled)
|
|
rtv_desc.Texture2DMSArray.ArraySize = layers;
|
|
else
|
|
rtv_desc.Texture2DArray.ArraySize = layers;
|
|
g_dx_context->GetDevice()->CreateRenderTargetView(
|
|
static_cast<DXTexture*>(attachment)->GetResource(), &rtv_desc, m_render_targets_raw.back());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DXFramebuffer::CreateIRTVDescriptor()
|
|
{
|
|
const bool multisampled = m_samples > 1;
|
|
DXGI_FORMAT non_int_format = D3DCommon::GetRTVFormatForAbstractFormat(m_color_format, false);
|
|
DXGI_FORMAT int_format = D3DCommon::GetRTVFormatForAbstractFormat(m_color_format, true);
|
|
|
|
// If the integer and non-integer RTV formats are the same for a given abstract format we can save
|
|
// space in the descriptor heap by only allocating the non-integer descriptor and using it for
|
|
// the integer RTV too.
|
|
if (int_format != non_int_format)
|
|
{
|
|
if (!g_dx_context->GetRTVHeapManager().Allocate(&m_int_rtv_descriptor))
|
|
return false;
|
|
|
|
D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = {int_format, multisampled ?
|
|
D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY :
|
|
D3D12_RTV_DIMENSION_TEXTURE2DARRAY};
|
|
if (multisampled)
|
|
rtv_desc.Texture2DMSArray.ArraySize = m_layers;
|
|
else
|
|
rtv_desc.Texture2DArray.ArraySize = m_layers;
|
|
g_dx_context->GetDevice()->CreateRenderTargetView(
|
|
static_cast<DXTexture*>(m_color_attachment)->GetResource(), &rtv_desc,
|
|
m_int_rtv_descriptor.cpu_handle);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DXFramebuffer::CreateDSVDescriptor()
|
|
{
|
|
if (!g_dx_context->GetDSVHeapManager().Allocate(&m_dsv_descriptor))
|
|
{
|
|
PanicAlertFmt("Failed to allocate RTV descriptor");
|
|
return false;
|
|
}
|
|
|
|
const bool multisampled = m_samples > 1;
|
|
D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc = {
|
|
D3DCommon::GetDSVFormatForAbstractFormat(m_depth_format),
|
|
multisampled ? D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY : D3D12_DSV_DIMENSION_TEXTURE2DARRAY,
|
|
D3D12_DSV_FLAG_NONE};
|
|
if (multisampled)
|
|
dsv_desc.Texture2DMSArray.ArraySize = m_layers;
|
|
else
|
|
dsv_desc.Texture2DArray.ArraySize = m_layers;
|
|
g_dx_context->GetDevice()->CreateDepthStencilView(
|
|
static_cast<DXTexture*>(m_depth_attachment)->GetResource(), &dsv_desc,
|
|
m_dsv_descriptor.cpu_handle);
|
|
return true;
|
|
}
|
|
|
|
DXStagingTexture::DXStagingTexture(StagingTextureType type, const TextureConfig& config,
|
|
ID3D12Resource* resource, u32 stride, u32 buffer_size)
|
|
: AbstractStagingTexture(type, config), m_resource(resource), m_buffer_size(buffer_size)
|
|
{
|
|
m_map_stride = stride;
|
|
}
|
|
|
|
DXStagingTexture::~DXStagingTexture()
|
|
{
|
|
g_dx_context->DeferResourceDestruction(m_resource.Get());
|
|
}
|
|
|
|
void DXStagingTexture::CopyFromTexture(const AbstractTexture* src,
|
|
const MathUtil::Rectangle<int>& src_rect, u32 src_layer,
|
|
u32 src_level, const MathUtil::Rectangle<int>& dst_rect)
|
|
{
|
|
const DXTexture* src_tex = static_cast<const DXTexture*>(src);
|
|
ASSERT(m_type == StagingTextureType::Readback || m_type == StagingTextureType::Mutable);
|
|
ASSERT(src_rect.GetWidth() == dst_rect.GetWidth() &&
|
|
src_rect.GetHeight() == dst_rect.GetHeight());
|
|
ASSERT(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= src_tex->GetWidth() &&
|
|
src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= src_tex->GetHeight());
|
|
ASSERT(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= m_config.width &&
|
|
dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= m_config.height);
|
|
|
|
const D3D12_RESOURCE_STATES old_state = src_tex->GetState();
|
|
src_tex->TransitionToState(D3D12_RESOURCE_STATE_COPY_SOURCE);
|
|
|
|
// Can't copy while it's mapped like in Vulkan.
|
|
Unmap();
|
|
|
|
// Copy from VRAM -> host-visible memory.
|
|
const D3D12_TEXTURE_COPY_LOCATION dst_loc = {
|
|
m_resource.Get(),
|
|
D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
|
{0,
|
|
{D3DCommon::GetDXGIFormatForAbstractFormat(m_config.format, false), m_config.width,
|
|
m_config.height, 1u, static_cast<UINT>(m_map_stride)}}};
|
|
const D3D12_TEXTURE_COPY_LOCATION src_loc = {
|
|
src_tex->GetResource(), D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
static_cast<UINT>(src_tex->CalcSubresource(src_level, src_layer))};
|
|
const D3D12_BOX src_box = RectangleToBox(src_rect);
|
|
g_dx_context->GetCommandList()->CopyTextureRegion(&dst_loc, dst_rect.left, dst_rect.top, 0,
|
|
&src_loc, &src_box);
|
|
|
|
// Restore old source texture layout.
|
|
src_tex->TransitionToState(old_state);
|
|
|
|
// Data is ready when the current command list is complete.
|
|
m_needs_flush = true;
|
|
m_completed_fence = g_dx_context->GetCurrentFenceValue();
|
|
}
|
|
|
|
void DXStagingTexture::CopyToTexture(const MathUtil::Rectangle<int>& src_rect, AbstractTexture* dst,
|
|
const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer,
|
|
u32 dst_level)
|
|
{
|
|
const DXTexture* dst_tex = static_cast<const DXTexture*>(dst);
|
|
ASSERT(m_type == StagingTextureType::Upload || m_type == StagingTextureType::Mutable);
|
|
ASSERT(src_rect.GetWidth() == dst_rect.GetWidth() &&
|
|
src_rect.GetHeight() == dst_rect.GetHeight());
|
|
ASSERT(src_rect.left >= 0 && static_cast<u32>(src_rect.right) <= m_config.width &&
|
|
src_rect.top >= 0 && static_cast<u32>(src_rect.bottom) <= m_config.height);
|
|
ASSERT(dst_rect.left >= 0 && static_cast<u32>(dst_rect.right) <= dst_tex->GetWidth() &&
|
|
dst_rect.top >= 0 && static_cast<u32>(dst_rect.bottom) <= dst_tex->GetHeight());
|
|
|
|
const D3D12_RESOURCE_STATES old_state = dst_tex->GetState();
|
|
dst_tex->TransitionToState(D3D12_RESOURCE_STATE_COPY_DEST);
|
|
|
|
// Can't copy while it's mapped like in Vulkan.
|
|
Unmap();
|
|
|
|
// Copy from VRAM -> host-visible memory.
|
|
const D3D12_TEXTURE_COPY_LOCATION dst_loc = {
|
|
dst_tex->GetResource(), D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
|
|
static_cast<UINT>(dst_tex->CalcSubresource(dst_level, dst_layer))};
|
|
const D3D12_TEXTURE_COPY_LOCATION src_loc = {
|
|
m_resource.Get(),
|
|
D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
|
|
{0,
|
|
{D3DCommon::GetDXGIFormatForAbstractFormat(m_config.format, false), m_config.width,
|
|
m_config.height, 1u, static_cast<UINT>(m_map_stride)}}};
|
|
const D3D12_BOX src_box = RectangleToBox(src_rect);
|
|
g_dx_context->GetCommandList()->CopyTextureRegion(&dst_loc, dst_rect.left, dst_rect.top, 0,
|
|
&src_loc, &src_box);
|
|
|
|
// Restore old source texture layout.
|
|
dst_tex->TransitionToState(old_state);
|
|
|
|
// Data is ready when the current command list is complete.
|
|
m_needs_flush = true;
|
|
m_completed_fence = g_dx_context->GetCurrentFenceValue();
|
|
}
|
|
|
|
bool DXStagingTexture::Map()
|
|
{
|
|
if (m_map_pointer)
|
|
return true;
|
|
|
|
const D3D12_RANGE read_range = {0u, m_type == StagingTextureType::Upload ? 0u : m_buffer_size};
|
|
HRESULT hr = m_resource->Map(0, &read_range, reinterpret_cast<void**>(&m_map_pointer));
|
|
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Map resource failed: {}", DX12HRWrap(hr));
|
|
if (FAILED(hr))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void DXStagingTexture::Unmap()
|
|
{
|
|
if (!m_map_pointer)
|
|
return;
|
|
|
|
const D3D12_RANGE write_range = {0u, m_type != StagingTextureType::Upload ? 0 : m_buffer_size};
|
|
m_resource->Unmap(0, &write_range);
|
|
m_map_pointer = nullptr;
|
|
}
|
|
|
|
void DXStagingTexture::Flush()
|
|
{
|
|
if (!m_needs_flush)
|
|
return;
|
|
|
|
m_needs_flush = false;
|
|
|
|
// If the completed fence is the same as the current command buffer fence, we need to execute
|
|
// the current list and wait for it to complete. This is the slowest path. Otherwise, if the
|
|
// command list with the copy has been submitted, we only need to wait for the fence.
|
|
if (m_completed_fence == g_dx_context->GetCurrentFenceValue())
|
|
Gfx::GetInstance()->ExecuteCommandList(true);
|
|
else
|
|
g_dx_context->WaitForFence(m_completed_fence);
|
|
}
|
|
|
|
std::unique_ptr<DXStagingTexture> DXStagingTexture::Create(StagingTextureType type,
|
|
const TextureConfig& config)
|
|
{
|
|
ASSERT(config.levels == 1 && config.layers == 1 && config.samples == 1);
|
|
|
|
// Readback and mutable share the same heap type.
|
|
const bool is_upload = type == StagingTextureType::Upload;
|
|
const D3D12_HEAP_PROPERTIES heap_properties = {is_upload ? D3D12_HEAP_TYPE_UPLOAD :
|
|
D3D12_HEAP_TYPE_READBACK};
|
|
|
|
const u32 texel_size = AbstractTexture::GetTexelSizeForFormat(config.format);
|
|
const u32 stride = Common::AlignUp(config.width * texel_size, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
|
|
const u32 size = stride * config.height;
|
|
|
|
const D3D12_RESOURCE_DESC desc = {D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
0,
|
|
size,
|
|
1,
|
|
1,
|
|
1,
|
|
DXGI_FORMAT_UNKNOWN,
|
|
{1, 0},
|
|
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
|
D3D12_RESOURCE_FLAG_NONE};
|
|
|
|
// Readback textures are stuck in COPY_DEST and are never GPU readable.
|
|
// Upload textures are stuck in GENERIC_READ, and are never CPU readable.
|
|
ComPtr<ID3D12Resource> resource;
|
|
HRESULT hr = g_dx_context->GetDevice()->CreateCommittedResource(
|
|
&heap_properties, D3D12_HEAP_FLAG_NONE, &desc,
|
|
is_upload ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
|
|
IID_PPV_ARGS(&resource));
|
|
ASSERT_MSG(VIDEO, SUCCEEDED(hr), "Failed to create staging texture resource: {}", DX12HRWrap(hr));
|
|
if (FAILED(hr))
|
|
return nullptr;
|
|
|
|
return std::unique_ptr<DXStagingTexture>(
|
|
new DXStagingTexture(type, config, resource.Get(), stride, size));
|
|
}
|
|
|
|
} // namespace DX12
|