// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "d3d12_texture.h" #include "d3d12_builders.h" #include "d3d12_device.h" #include "d3d_common.h" #include "common/align.h" #include "common/assert.h" #include "common/bitutils.h" #include "common/log.h" #include "common/string_util.h" #include "D3D12MemAlloc.h" Log_SetChannel(D3D12Device); D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, DXGI_FORMAT dxgi_format, ComPtr resource, ComPtr allocation, const D3D12DescriptorHandle& srv_descriptor, const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& uav_descriptor, WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state) : GPUTexture(static_cast(width), static_cast(height), static_cast(layers), static_cast(levels), static_cast(samples), type, format), m_resource(std::move(resource)), m_allocation(std::move(allocation)), m_srv_descriptor(srv_descriptor), m_write_descriptor(write_descriptor), m_uav_descriptor(uav_descriptor), m_dxgi_format(dxgi_format), m_resource_state(resource_state), m_write_descriptor_type(wdtype) { } D3D12Texture::~D3D12Texture() { Destroy(true); } std::unique_ptr D3D12Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type, GPUTexture::Format format, const void* data /* = nullptr */, u32 data_stride /* = 0 */) { if (!GPUTexture::ValidateConfig(width, height, layers, levels, samples, type, format)) return {}; const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(format); const DXGI_FORMAT uav_format = (type == GPUTexture::Type::RWTexture) ? fm.resource_format : DXGI_FORMAT_UNKNOWN; D3D12_RESOURCE_DESC desc = {}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Width = width; desc.Height = height; desc.DepthOrArraySize = 1; desc.MipLevels = static_cast(levels); desc.Format = fm.resource_format; desc.SampleDesc.Count = samples; desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; D3D12MA::ALLOCATION_DESC allocationDesc = {}; allocationDesc.Flags = D3D12MA::ALLOCATION_FLAG_WITHIN_BUDGET; allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; D3D12_CLEAR_VALUE optimized_clear_value = {}; D3D12_RESOURCE_STATES state; switch (type) { case GPUTexture::Type::Texture: case GPUTexture::Type::DynamicTexture: { desc.Flags = D3D12_RESOURCE_FLAG_NONE; state = D3D12_RESOURCE_STATE_COPY_DEST; } break; case GPUTexture::Type::RenderTarget: { // RT's tend to be larger, so we'll keep them committed for speed. DebugAssert(levels == 1); allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; optimized_clear_value.Format = fm.rtv_format; state = D3D12_RESOURCE_STATE_RENDER_TARGET; } break; case GPUTexture::Type::DepthStencil: { DebugAssert(levels == 1); allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; optimized_clear_value.Format = fm.dsv_format; state = D3D12_RESOURCE_STATE_DEPTH_WRITE; } break; case GPUTexture::Type::RWTexture: { DebugAssert(levels == 1); allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED; state = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; } break; default: return {}; } if (uav_format != DXGI_FORMAT_UNKNOWN) desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; ComPtr resource; ComPtr allocation; HRESULT hr = m_allocator->CreateResource( &allocationDesc, &desc, state, (type == GPUTexture::Type::RenderTarget || type == GPUTexture::Type::DepthStencil) ? &optimized_clear_value : nullptr, allocation.GetAddressOf(), IID_PPV_ARGS(resource.GetAddressOf())); if (FAILED(hr)) [[unlikely]] { // OOM isn't fatal. if (hr != E_OUTOFMEMORY) ERROR_LOG("Create texture failed: 0x{:08X}", static_cast(hr)); return {}; } D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor; D3D12Texture::WriteDescriptorType write_descriptor_type = D3D12Texture::WriteDescriptorType::None; if (fm.srv_format != DXGI_FORMAT_UNKNOWN) { if (!CreateSRVDescriptor(resource.Get(), layers, levels, samples, fm.srv_format, &srv_descriptor)) return {}; } switch (type) { case GPUTexture::Type::RenderTarget: { write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV; if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor)) { m_descriptor_heap_manager.Free(&srv_descriptor); return {}; } } break; case GPUTexture::Type::DepthStencil: { write_descriptor_type = D3D12Texture::WriteDescriptorType::DSV; if (!CreateDSVDescriptor(resource.Get(), samples, fm.dsv_format, &write_descriptor)) { m_descriptor_heap_manager.Free(&srv_descriptor); return {}; } } break; default: break; } if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.Get(), samples, fm.dsv_format, &uav_descriptor)) { m_descriptor_heap_manager.Free(&write_descriptor); m_descriptor_heap_manager.Free(&srv_descriptor); return {}; } std::unique_ptr tex(new D3D12Texture( width, height, layers, levels, samples, type, format, fm.resource_format, std::move(resource), std::move(allocation), srv_descriptor, write_descriptor, uav_descriptor, write_descriptor_type, state)); if (data) { tex->Update(0, 0, width, height, data, data_stride); tex->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); } return tex; } bool D3D12Device::CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh) { if (!m_descriptor_heap_manager.Allocate(dh)) { ERROR_LOG("Failed to allocate SRV descriptor"); return false; } D3D12_SHADER_RESOURCE_VIEW_DESC desc; desc.Format = format; desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; if (layers > 1) { if (samples > 1) { desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY; desc.Texture2DMSArray = {0u, layers}; } else { desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; desc.Texture2DArray = {0u, levels, 0u, layers, 0u, 0.0f}; } } else { if (samples > 1) { desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; } else { desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; desc.Texture2D = {0u, levels, 0u, 0.0f}; } } m_device->CreateShaderResourceView(resource, &desc, dh->cpu_handle); return true; } bool D3D12Device::CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh) { if (!m_rtv_heap_manager.Allocate(dh)) { ERROR_LOG("Failed to allocate SRV descriptor"); return false; } const D3D12_RENDER_TARGET_VIEW_DESC desc = { format, (samples > 1) ? D3D12_RTV_DIMENSION_TEXTURE2DMS : D3D12_RTV_DIMENSION_TEXTURE2D, {}}; m_device->CreateRenderTargetView(resource, &desc, dh->cpu_handle); return true; } bool D3D12Device::CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh) { if (!m_dsv_heap_manager.Allocate(dh)) { ERROR_LOG("Failed to allocate SRV descriptor"); return false; } const D3D12_DEPTH_STENCIL_VIEW_DESC desc = { format, (samples > 1) ? D3D12_DSV_DIMENSION_TEXTURE2DMS : D3D12_DSV_DIMENSION_TEXTURE2D, D3D12_DSV_FLAG_NONE, {}}; m_device->CreateDepthStencilView(resource, &desc, dh->cpu_handle); return true; } bool D3D12Device::CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh) { if (!m_descriptor_heap_manager.Allocate(dh)) { ERROR_LOG("Failed to allocate UAV descriptor"); return false; } DebugAssert(samples == 1); const D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {format, D3D12_UAV_DIMENSION_TEXTURE2D, {}}; m_device->CreateUnorderedAccessView(resource, nullptr, &desc, dh->cpu_handle); return true; } void D3D12Texture::Destroy(bool defer) { D3D12Device& dev = D3D12Device::GetInstance(); dev.UnbindTexture(this); if (defer) { dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_srv_descriptor); switch (m_write_descriptor_type) { case WriteDescriptorType::RTV: dev.DeferDescriptorDestruction(dev.GetRTVHeapManager(), &m_write_descriptor); break; case WriteDescriptorType::DSV: dev.DeferDescriptorDestruction(dev.GetDSVHeapManager(), &m_write_descriptor); break; case WriteDescriptorType::None: default: break; } if (m_uav_descriptor) dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_uav_descriptor); dev.DeferResourceDestruction(std::move(m_allocation), std::move(m_resource)); } else { dev.GetDescriptorHeapManager().Free(&m_srv_descriptor); switch (m_write_descriptor_type) { case WriteDescriptorType::RTV: dev.GetRTVHeapManager().Free(&m_write_descriptor); break; case WriteDescriptorType::DSV: dev.GetDSVHeapManager().Free(&m_write_descriptor); break; case WriteDescriptorType::None: default: break; } if (m_uav_descriptor) dev.GetDescriptorHeapManager().Free(&m_uav_descriptor); m_resource.Reset(); m_allocation.Reset(); } m_write_descriptor_type = WriteDescriptorType::None; } ID3D12GraphicsCommandList4* D3D12Texture::GetCommandBufferForUpdate() { D3D12Device& dev = D3D12Device::GetInstance(); if ((m_type != Type::Texture && m_type != Type::DynamicTexture) || m_use_fence_counter == dev.GetCurrentFenceValue()) { // Console.WriteLn("Texture update within frame, can't use do beforehand"); if (dev.InRenderPass()) dev.EndRenderPass(); return dev.GetCommandList(); } return dev.GetInitCommandList(); } void D3D12Texture::CopyTextureDataForUpload(void* dst, const void* src, u32 width, u32 height, u32 pitch, u32 upload_pitch) const { StringUtil::StrideMemCpy(dst, upload_pitch, src, pitch, GetPixelSize() * width, height); } ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32 pitch, u32 upload_pitch, u32 width, u32 height) const { const u32 size = upload_pitch * height; ComPtr resource; ComPtr allocation; const D3D12MA::ALLOCATION_DESC allocation_desc = {D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, nullptr, nullptr}; const D3D12_RESOURCE_DESC resource_desc = { D3D12_RESOURCE_DIMENSION_BUFFER, 0, size, 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE}; HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource( &allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, allocation.GetAddressOf(), IID_PPV_ARGS(resource.GetAddressOf())); if (FAILED(hr)) [[unlikely]] { ERROR_LOG("CreateResource() failed with {:08X}", static_cast(hr)); return nullptr; } void* map_ptr; hr = resource->Map(0, nullptr, &map_ptr); if (FAILED(hr)) [[unlikely]] { ERROR_LOG("Map() failed with {:08X}", static_cast(hr)); return nullptr; } CopyTextureDataForUpload(map_ptr, data, width, height, pitch, upload_pitch); const D3D12_RANGE write_range = {0, size}; resource->Unmap(0, &write_range); // Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy. // This adds the reference needed to keep the buffer alive. ID3D12Resource* ret = resource.Get(); D3D12Device::GetInstance().DeferResourceDestruction(std::move(allocation), std::move(resource)); return ret; } bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer, u32 level) { DebugAssert(layer < m_layers && level < m_levels); DebugAssert((x + width) <= GetMipWidth(level) && (y + height) <= GetMipHeight(level)); D3D12Device& dev = D3D12Device::GetInstance(); D3D12StreamBuffer& sbuffer = dev.GetTextureUploadBuffer(); const u32 upload_pitch = Common::AlignUpPow2(pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); const u32 required_size = height * upload_pitch; D3D12_TEXTURE_COPY_LOCATION srcloc; srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcloc.PlacedFootprint.Footprint.Width = width; srcloc.PlacedFootprint.Footprint.Height = height; srcloc.PlacedFootprint.Footprint.Depth = 1; srcloc.PlacedFootprint.Footprint.Format = m_dxgi_format; srcloc.PlacedFootprint.Footprint.RowPitch = upload_pitch; // If the texture is larger than half our streaming buffer size, use a separate buffer. // Otherwise allocation will either fail, or require lots of cmdbuffer submissions. if (required_size > (sbuffer.GetSize() / 2)) { srcloc.pResource = AllocateUploadStagingBuffer(data, pitch, upload_pitch, width, height); if (!srcloc.pResource) return false; srcloc.PlacedFootprint.Offset = 0; } else { if (!sbuffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) { D3D12Device::GetInstance().SubmitCommandList(false, "While waiting for %u bytes in texture upload buffer", required_size); if (!sbuffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]] { ERROR_LOG("Failed to reserve texture upload memory ({} bytes).", required_size); return false; } } srcloc.pResource = sbuffer.GetBuffer(); srcloc.PlacedFootprint.Offset = sbuffer.GetCurrentOffset(); CopyTextureDataForUpload(sbuffer.GetCurrentHostPointer(), data, width, height, pitch, upload_pitch); sbuffer.CommitMemory(required_size); } ID3D12GraphicsCommandList4* cmdlist = GetCommandBufferForUpdate(); // if we're an rt and have been cleared, and the full rect isn't being uploaded, do the clear if (m_type == Type::RenderTarget) { if (x != 0 || y != 0 || width != m_width || height != m_height) CommitClear(cmdlist); else m_state = State::Dirty; } GPUDevice::GetStatistics().buffer_streamed += required_size; GPUDevice::GetStatistics().num_uploads++; // first time the texture is used? don't leave it undefined if (m_resource_state == D3D12_RESOURCE_STATE_COMMON) TransitionToState(cmdlist, D3D12_RESOURCE_STATE_COPY_DEST); else if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST) TransitionSubresourceToState(cmdlist, layer, level, m_resource_state, D3D12_RESOURCE_STATE_COPY_DEST); D3D12_TEXTURE_COPY_LOCATION dstloc; dstloc.pResource = m_resource.Get(); dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstloc.SubresourceIndex = layer; const D3D12_BOX srcbox{0u, 0u, 0u, width, height, 1u}; cmdlist->CopyTextureRegion(&dstloc, x, y, 0, &srcloc, &srcbox); if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST) TransitionSubresourceToState(cmdlist, layer, level, D3D12_RESOURCE_STATE_COPY_DEST, m_resource_state); return true; } bool D3D12Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer, u32 level) { // TODO: linear textures for dynamic? if ((x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || level > m_levels) { return false; } D3D12Device& dev = D3D12Device::GetInstance(); if (m_state == State::Cleared && (x != 0 || y != 0 || width != m_width || height != m_height)) CommitClear(GetCommandBufferForUpdate()); // see note in Update() for the reason why. const u32 aligned_pitch = Common::AlignUpPow2(width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); const u32 req_size = height * aligned_pitch; D3D12StreamBuffer& buffer = dev.GetTextureUploadBuffer(); if (req_size >= (buffer.GetSize() / 2)) return false; if (!buffer.ReserveMemory(req_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) { dev.SubmitCommandList(false, "While waiting for %u bytes in texture upload buffer", req_size); if (!buffer.ReserveMemory(req_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) Panic("Failed to reserve texture upload memory"); } // map for writing *map = buffer.GetCurrentHostPointer(); *map_stride = aligned_pitch; m_map_x = static_cast(x); m_map_y = static_cast(y); m_map_width = static_cast(width); m_map_height = static_cast(height); m_map_layer = static_cast(layer); m_map_level = static_cast(level); m_state = State::Dirty; return true; } void D3D12Texture::Unmap() { D3D12Device& dev = D3D12Device::GetInstance(); D3D12StreamBuffer& sb = dev.GetTextureUploadBuffer(); const u32 aligned_pitch = Common::AlignUpPow2(m_map_width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); const u32 req_size = m_map_height * aligned_pitch; const u32 offset = sb.GetCurrentOffset(); sb.CommitMemory(req_size); GPUDevice::GetStatistics().buffer_streamed += req_size; GPUDevice::GetStatistics().num_uploads++; ID3D12GraphicsCommandList4* cmdlist = GetCommandBufferForUpdate(); // first time the texture is used? don't leave it undefined if (m_resource_state == D3D12_RESOURCE_STATE_COMMON) TransitionToState(cmdlist, D3D12_RESOURCE_STATE_COPY_DEST); else if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST) TransitionSubresourceToState(cmdlist, m_map_layer, m_map_level, m_resource_state, D3D12_RESOURCE_STATE_COPY_DEST); D3D12_TEXTURE_COPY_LOCATION srcloc; srcloc.pResource = sb.GetBuffer(); srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcloc.PlacedFootprint.Offset = offset; srcloc.PlacedFootprint.Footprint.Width = m_map_width; srcloc.PlacedFootprint.Footprint.Height = m_map_height; srcloc.PlacedFootprint.Footprint.Depth = 1; srcloc.PlacedFootprint.Footprint.Format = m_dxgi_format; srcloc.PlacedFootprint.Footprint.RowPitch = aligned_pitch; D3D12_TEXTURE_COPY_LOCATION dstloc; dstloc.pResource = m_resource.Get(); dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstloc.SubresourceIndex = m_map_level; const D3D12_BOX srcbox{0u, 0u, 0u, m_map_width, m_map_height, 1}; cmdlist->CopyTextureRegion(&dstloc, m_map_x, m_map_y, 0, &srcloc, &srcbox); if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST) TransitionSubresourceToState(cmdlist, m_map_layer, m_map_level, D3D12_RESOURCE_STATE_COPY_DEST, m_resource_state); m_map_x = 0; m_map_y = 0; m_map_width = 0; m_map_height = 0; m_map_layer = 0; m_map_level = 0; } void D3D12Texture::CommitClear() { if (m_state != GPUTexture::State::Cleared) return; D3D12Device& dev = D3D12Device::GetInstance(); if (dev.InRenderPass()) dev.EndRenderPass(); ActuallyCommitClear(dev.GetCommandList()); } void D3D12Texture::CommitClear(ID3D12GraphicsCommandList* cmdlist) { if (m_state != GPUTexture::State::Cleared) return; ActuallyCommitClear(cmdlist); } void D3D12Texture::ActuallyCommitClear(ID3D12GraphicsCommandList* cmdlist) { if (IsDepthStencil()) { TransitionToState(cmdlist, D3D12_RESOURCE_STATE_DEPTH_WRITE); cmdlist->ClearDepthStencilView(GetWriteDescriptor(), D3D12_CLEAR_FLAG_DEPTH, m_clear_value.depth, 0, 0, nullptr); } else { TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET); cmdlist->ClearRenderTargetView(GetWriteDescriptor(), D3D12Device::RGBA8ToFloat(m_clear_value.color).data(), 0, nullptr); } SetState(State::Dirty); } void D3D12Texture::SetDebugName(std::string_view name) { D3D12::SetObjectName(m_resource.Get(), name); } u32 D3D12Texture::CalculateSubresource(u32 layer, u32 level, u32 num_levels) { // D3D11CalcSubresource return level + layer * num_levels; } u32 D3D12Texture::CalculateSubresource(u32 layer, u32 level) const { return CalculateSubresource(layer, level, m_levels); } void D3D12Texture::TransitionToState(D3D12_RESOURCE_STATES state) { TransitionToState(D3D12Device::GetInstance().GetCommandList(), state); } void D3D12Texture::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RESOURCE_STATES state) { if (m_resource_state == state) return; const D3D12_RESOURCE_STATES prev_state = m_resource_state; m_resource_state = state; const D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE, {{m_resource.Get(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, prev_state, state}}}; cmdlist->ResourceBarrier(1, &barrier); } void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 layer, u32 level, D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state) const { TransitionSubresourceToState(cmdlist, m_resource.Get(), CalculateSubresource(layer, level), before_state, after_state); } void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 subresource, D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state) const { TransitionSubresourceToState(cmdlist, m_resource.Get(), subresource, before_state, after_state); } void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, ID3D12Resource* resource, u32 subresource, D3D12_RESOURCE_STATES before_state, D3D12_RESOURCE_STATES after_state) { const D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE, {{resource, subresource, before_state, after_state}}}; cmdlist->ResourceBarrier(1, &barrier); } void D3D12Texture::MakeReadyForSampling() { if (m_resource_state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) return; D3D12Device& dev = D3D12Device::GetInstance(); if (dev.InRenderPass()) dev.EndRenderPass(); TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); } D3D12Sampler::D3D12Sampler(D3D12DescriptorHandle descriptor) : m_descriptor(descriptor) { } D3D12Sampler::~D3D12Sampler() { // Cleaned up by main class. } void D3D12Sampler::SetDebugName(std::string_view name) { } D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config) { const auto it = m_sampler_map.find(config.key); if (it != m_sampler_map.end()) return it->second; static constexpr std::array(GPUSampler::AddressMode::MaxCount)> ta = {{ D3D12_TEXTURE_ADDRESS_MODE_WRAP, // Repeat D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // ClampToEdge D3D12_TEXTURE_ADDRESS_MODE_BORDER, // ClampToBorder D3D12_TEXTURE_ADDRESS_MODE_MIRROR, // MirrorRepeat }}; static constexpr u8 filter_count = static_cast(GPUSampler::Filter::MaxCount); static constexpr D3D12_FILTER filters[filter_count][filter_count][filter_count] = { { {D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT}, {D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT, D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT}, }, { {D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR, D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR}, {D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, D3D12_FILTER_MIN_MAG_MIP_LINEAR}, }}; D3D12_SAMPLER_DESC desc = {}; desc.AddressU = ta[static_cast(config.address_u.GetValue())]; desc.AddressV = ta[static_cast(config.address_v.GetValue())]; desc.AddressW = ta[static_cast(config.address_w.GetValue())]; std::memcpy(desc.BorderColor, RGBA8ToFloat(config.border_color).data(), sizeof(desc.BorderColor)); desc.MinLOD = static_cast(config.min_lod); desc.MaxLOD = static_cast(config.max_lod); if (config.anisotropy > 1) { desc.Filter = D3D12_FILTER_ANISOTROPIC; desc.MaxAnisotropy = config.anisotropy; } else { desc.Filter = filters[static_cast(config.mip_filter.GetValue())][static_cast(config.min_filter.GetValue())] [static_cast(config.mag_filter.GetValue())]; desc.MaxAnisotropy = 1; } D3D12DescriptorHandle handle; if (m_sampler_heap_manager.Allocate(&handle)) m_device->CreateSampler(&desc, handle); m_sampler_map.emplace(config.key, handle); return handle; } void D3D12Device::DestroySamplers() { for (auto& it : m_sampler_map) { if (it.second) m_sampler_heap_manager.Free(&it.second); } m_sampler_map.clear(); } std::unique_ptr D3D12Device::CreateSampler(const GPUSampler::Config& config) { const D3D12DescriptorHandle handle = GetSampler(config); if (!handle) return {}; return std::unique_ptr(new D3D12Sampler(std::move(handle))); } D3D12TextureBuffer::D3D12TextureBuffer(Format format, u32 size_in_elements) : GPUTextureBuffer(format, size_in_elements) { } D3D12TextureBuffer::~D3D12TextureBuffer() { Destroy(true); } bool D3D12TextureBuffer::Create(D3D12Device& dev) { static constexpr std::array(GPUTextureBuffer::Format::MaxCount)> format_mapping = {{ DXGI_FORMAT_R16_UINT, // R16UI }}; if (!m_buffer.Create(GetSizeInBytes())) return false; if (!dev.GetDescriptorHeapManager().Allocate(&m_descriptor)) return {}; D3D12_SHADER_RESOURCE_VIEW_DESC desc = {format_mapping[static_cast(m_format)], D3D12_SRV_DIMENSION_BUFFER, D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING, {}}; desc.Buffer.NumElements = m_size_in_elements; dev.GetDevice()->CreateShaderResourceView(m_buffer.GetBuffer(), &desc, m_descriptor); return true; } void D3D12TextureBuffer::Destroy(bool defer) { D3D12Device& dev = D3D12Device::GetInstance(); if (m_descriptor) { if (defer) dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_descriptor); else dev.GetDescriptorHeapManager().Free(&m_descriptor); } } void* D3D12TextureBuffer::Map(u32 required_elements) { const u32 esize = GetElementSize(m_format); const u32 req_size = esize * required_elements; if (!m_buffer.ReserveMemory(req_size, esize)) { D3D12Device::GetInstance().SubmitCommandListAndRestartRenderPass("out of space in texture buffer"); if (!m_buffer.ReserveMemory(req_size, esize)) Panic("Failed to allocate texture buffer space."); } m_current_position = m_buffer.GetCurrentOffset() / esize; return m_buffer.GetCurrentHostPointer(); } void D3D12TextureBuffer::Unmap(u32 used_elements) { const u32 size = GetElementSize(m_format) * used_elements; GPUDevice::GetStatistics().buffer_streamed += size; GPUDevice::GetStatistics().num_uploads++; m_buffer.CommitMemory(size); } void D3D12TextureBuffer::SetDebugName(std::string_view name) { D3D12::SetObjectName(m_buffer.GetBuffer(), name); } std::unique_ptr D3D12Device::CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements) { std::unique_ptr tb = std::make_unique(format, size_in_elements); if (!tb->Create(*this)) tb.reset(); return tb; } D3D12DownloadTexture::D3D12DownloadTexture(u32 width, u32 height, GPUTexture::Format format, ComPtr allocation, ComPtr buffer, size_t buffer_size) : GPUDownloadTexture(width, height, format, false), m_allocation(std::move(allocation)), m_buffer(std::move(buffer)), m_buffer_size(buffer_size) { } D3D12DownloadTexture::~D3D12DownloadTexture() { if (IsMapped()) D3D12DownloadTexture::Unmap(); if (m_buffer) D3D12Device::GetInstance().DeferResourceDestruction(m_allocation.Get(), m_buffer.Get()); } std::unique_ptr D3D12DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format) { const u32 buffer_size = GetBufferSize(width, height, format, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); D3D12MA::ALLOCATION_DESC allocation_desc = {}; allocation_desc.HeapType = D3D12_HEAP_TYPE_READBACK; const D3D12_RESOURCE_DESC resource_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 allocation; ComPtr buffer; HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource( &allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, allocation.GetAddressOf(), IID_PPV_ARGS(buffer.GetAddressOf())); if (FAILED(hr)) { ERROR_LOG("CreateResource() failed with HRESULT {:08X}", hr); return {}; } return std::unique_ptr( new D3D12DownloadTexture(width, height, format, std::move(allocation), std::move(buffer), buffer_size)); } void D3D12DownloadTexture::CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width, u32 height, u32 src_layer, u32 src_level, bool use_transfer_pitch) { D3D12Texture* const src12 = static_cast(src); D3D12Device& dev = D3D12Device::GetInstance(); DebugAssert(src12->GetFormat() == m_format); DebugAssert(src_level < src12->GetLevels()); DebugAssert((src_x + width) <= src12->GetMipWidth(src_level) && (src_y + height) <= src12->GetMipHeight(src_level)); DebugAssert((dst_x + width) <= m_width && (dst_y + height) <= m_height); DebugAssert((dst_x == 0 && dst_y == 0) || !use_transfer_pitch); u32 copy_offset, copy_size, copy_rows; m_current_pitch = GetTransferPitch(use_transfer_pitch ? width : m_width, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); GetTransferSize(dst_x, dst_y, width, height, m_current_pitch, ©_offset, ©_size, ©_rows); dev.GetStatistics().num_downloads++; if (dev.InRenderPass()) dev.EndRenderPass(); src12->CommitClear(); if (IsMapped()) Unmap(); ID3D12GraphicsCommandList* cmdlist = dev.GetCommandList(); GL_INS_FMT("ReadbackTexture: {{{},{}}} {}x{} => {{{},{}}}", src_x, src_y, width, height, dst_x, dst_y); D3D12_TEXTURE_COPY_LOCATION srcloc; srcloc.pResource = src12->GetResource(); srcloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; srcloc.SubresourceIndex = src12->CalculateSubresource(src_layer, src_level); D3D12_TEXTURE_COPY_LOCATION dstloc; dstloc.pResource = m_buffer.Get(); dstloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dstloc.PlacedFootprint.Offset = copy_offset; dstloc.PlacedFootprint.Footprint.Format = src12->GetDXGIFormat(); dstloc.PlacedFootprint.Footprint.Width = width; dstloc.PlacedFootprint.Footprint.Height = height; dstloc.PlacedFootprint.Footprint.Depth = 1; dstloc.PlacedFootprint.Footprint.RowPitch = m_current_pitch; const D3D12_RESOURCE_STATES old_layout = src12->GetResourceState(); if (old_layout != D3D12_RESOURCE_STATE_COPY_SOURCE) src12->TransitionSubresourceToState(cmdlist, src_level, old_layout, D3D12_RESOURCE_STATE_COPY_SOURCE); // TODO: Rules for depth buffers here? const D3D12_BOX srcbox{static_cast(src_x), static_cast(src_y), 0u, static_cast(src_x + width), static_cast(src_y + height), 1u}; cmdlist->CopyTextureRegion(&dstloc, 0, 0, 0, &srcloc, &srcbox); if (old_layout != D3D12_RESOURCE_STATE_COPY_SOURCE) src12->TransitionSubresourceToState(cmdlist, src_level, D3D12_RESOURCE_STATE_COPY_SOURCE, old_layout); m_copy_fence_value = dev.GetCurrentFenceValue(); m_needs_flush = true; } bool D3D12DownloadTexture::Map(u32 x, u32 y, u32 width, u32 height) { if (IsMapped()) return true; // Never populated? if (!m_current_pitch) return false; u32 copy_offset, copy_size, copy_rows; GetTransferSize(x, y, width, height, m_current_pitch, ©_offset, ©_size, ©_rows); const D3D12_RANGE read_range{copy_offset, copy_offset + m_current_pitch * copy_rows}; const HRESULT hr = m_buffer->Map(0, &read_range, reinterpret_cast(const_cast(&m_map_pointer))); if (FAILED(hr)) { ERROR_LOG("Map() failed with HRESULT {:08X}", hr); return false; } return true; } void D3D12DownloadTexture::Unmap() { if (!IsMapped()) return; const D3D12_RANGE write_range = {}; m_buffer->Unmap(0, &write_range); m_map_pointer = nullptr; } void D3D12DownloadTexture::Flush() { if (!m_needs_flush) return; m_needs_flush = false; D3D12Device& dev = D3D12Device::GetInstance(); if (dev.GetCompletedFenceValue() >= m_copy_fence_value) return; // Need to execute command buffer. if (dev.GetCurrentFenceValue() == m_copy_fence_value) dev.SubmitCommandList(true); else dev.WaitForFence(m_copy_fence_value); } void D3D12DownloadTexture::SetDebugName(std::string_view name) { if (name.empty()) return; D3D12::SetObjectName(m_buffer.Get(), name); } std::unique_ptr D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format) { return D3D12DownloadTexture::Create(width, height, format); } std::unique_ptr D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, void* memory, size_t memory_size, u32 memory_stride) { ERROR_LOG("D3D12 cannot import memory for download textures"); return {}; }