dolphin/Source/Core/VideoBackends/D3D12/DX12Texture.cpp

681 lines
27 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,
AbstractTextureFormat color_format, AbstractTextureFormat depth_format,
u32 width, u32 height, u32 layers, u32 samples)
: AbstractFramebuffer(color_attachment, depth_attachment, 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);
}
g_dx_context->DeferDescriptorDestruction(g_dx_context->GetRTVHeapManager(),
m_rtv_descriptor.index);
}
}
std::unique_ptr<DXFramebuffer> DXFramebuffer::Create(DXTexture* color_attachment,
DXTexture* depth_attachment)
{
if (!ValidateConfig(color_attachment, depth_attachment))
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,
color_format, depth_format, width, height,
layers, samples));
if ((color_attachment && !fb->CreateRTVDescriptor()) ||
(depth_attachment && !fb->CreateDSVDescriptor()))
{
return nullptr;
}
return fb;
}
bool DXFramebuffer::CreateRTVDescriptor()
{
if (!g_dx_context->GetRTVHeapManager().Allocate(&m_rtv_descriptor))
{
PanicAlertFmt("Failed to allocate RTV descriptor");
return false;
}
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 = m_layers;
else
rtv_desc.Texture2DArray.ArraySize = m_layers;
g_dx_context->GetDevice()->CreateRenderTargetView(
static_cast<DXTexture*>(m_color_attachment)->GetResource(), &rtv_desc,
m_rtv_descriptor.cpu_handle);
DXGI_FORMAT int_format = D3DCommon::GetRTVFormatForAbstractFormat(m_color_format, true);
if (int_format != rtv_desc.Format)
{
if (!g_dx_context->GetRTVHeapManager().Allocate(&m_int_rtv_descriptor))
return false;
rtv_desc.Format = int_format;
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