duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

d3d12_texture.cpp (35860B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "d3d12_texture.h"
      5 #include "d3d12_builders.h"
      6 #include "d3d12_device.h"
      7 #include "d3d_common.h"
      8 
      9 #include "common/align.h"
     10 #include "common/assert.h"
     11 #include "common/bitutils.h"
     12 #include "common/error.h"
     13 #include "common/log.h"
     14 #include "common/string_util.h"
     15 
     16 #include "D3D12MemAlloc.h"
     17 
     18 Log_SetChannel(D3D12Device);
     19 
     20 D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
     21                            DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,
     22                            ComPtr<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
     23                            const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& uav_descriptor,
     24                            WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
     25   : GPUTexture(static_cast<u16>(width), static_cast<u16>(height), static_cast<u8>(layers), static_cast<u8>(levels),
     26                static_cast<u8>(samples), type, format),
     27     m_resource(std::move(resource)), m_allocation(std::move(allocation)), m_srv_descriptor(srv_descriptor),
     28     m_write_descriptor(write_descriptor), m_uav_descriptor(uav_descriptor), m_dxgi_format(dxgi_format),
     29     m_resource_state(resource_state), m_write_descriptor_type(wdtype)
     30 {
     31 }
     32 
     33 D3D12Texture::~D3D12Texture()
     34 {
     35   Destroy(true);
     36 }
     37 
     38 std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
     39                                                        GPUTexture::Type type, GPUTexture::Format format,
     40                                                        const void* data /* = nullptr */, u32 data_stride /* = 0 */)
     41 {
     42   if (!GPUTexture::ValidateConfig(width, height, layers, levels, samples, type, format))
     43     return {};
     44 
     45   const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(format);
     46 
     47   D3D12_RESOURCE_DESC desc = {};
     48   desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
     49   desc.Width = width;
     50   desc.Height = height;
     51   desc.DepthOrArraySize = 1;
     52   desc.MipLevels = static_cast<u8>(levels);
     53   desc.Format = fm.resource_format;
     54   desc.SampleDesc.Count = samples;
     55   desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
     56 
     57   D3D12MA::ALLOCATION_DESC allocationDesc = {};
     58   allocationDesc.Flags = D3D12MA::ALLOCATION_FLAG_WITHIN_BUDGET;
     59   allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
     60 
     61   D3D12_CLEAR_VALUE optimized_clear_value = {};
     62   D3D12_RESOURCE_STATES state;
     63 
     64   switch (type)
     65   {
     66     case GPUTexture::Type::Texture:
     67     case GPUTexture::Type::DynamicTexture:
     68     {
     69       desc.Flags = D3D12_RESOURCE_FLAG_NONE;
     70       state = D3D12_RESOURCE_STATE_COPY_DEST;
     71     }
     72     break;
     73 
     74     case GPUTexture::Type::RenderTarget:
     75     {
     76       // RT's tend to be larger, so we'll keep them committed for speed.
     77       DebugAssert(levels == 1);
     78       allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
     79       desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
     80       optimized_clear_value.Format = fm.rtv_format;
     81       state = D3D12_RESOURCE_STATE_RENDER_TARGET;
     82     }
     83     break;
     84 
     85     case GPUTexture::Type::DepthStencil:
     86     {
     87       DebugAssert(levels == 1);
     88       allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
     89       desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
     90       optimized_clear_value.Format = fm.dsv_format;
     91       state = D3D12_RESOURCE_STATE_DEPTH_WRITE;
     92     }
     93     break;
     94 
     95     case GPUTexture::Type::RWTexture:
     96     {
     97       DebugAssert(levels == 1);
     98       allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
     99       desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
    100       optimized_clear_value.Format = fm.rtv_format;
    101       state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
    102     }
    103     break;
    104 
    105     default:
    106       return {};
    107   }
    108 
    109   ComPtr<ID3D12Resource> resource;
    110   ComPtr<D3D12MA::Allocation> allocation;
    111   HRESULT hr = m_allocator->CreateResource(
    112     &allocationDesc, &desc, state,
    113     (type == GPUTexture::Type::RenderTarget || type == GPUTexture::Type::DepthStencil) ? &optimized_clear_value :
    114                                                                                          nullptr,
    115     allocation.GetAddressOf(), IID_PPV_ARGS(resource.GetAddressOf()));
    116   if (FAILED(hr)) [[unlikely]]
    117   {
    118     // OOM isn't fatal.
    119     if (hr != E_OUTOFMEMORY)
    120       ERROR_LOG("Create texture failed: 0x{:08X}", static_cast<unsigned>(hr));
    121 
    122     return {};
    123   }
    124 
    125   D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor;
    126   D3D12Texture::WriteDescriptorType write_descriptor_type = D3D12Texture::WriteDescriptorType::None;
    127   if (fm.srv_format != DXGI_FORMAT_UNKNOWN)
    128   {
    129     if (!CreateSRVDescriptor(resource.Get(), layers, levels, samples, fm.srv_format, &srv_descriptor))
    130       return {};
    131   }
    132 
    133   switch (type)
    134   {
    135     case GPUTexture::Type::RenderTarget:
    136     {
    137       write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV;
    138       if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor))
    139       {
    140         m_descriptor_heap_manager.Free(&srv_descriptor);
    141         return {};
    142       }
    143     }
    144     break;
    145 
    146     case GPUTexture::Type::DepthStencil:
    147     {
    148       write_descriptor_type = D3D12Texture::WriteDescriptorType::DSV;
    149       if (!CreateDSVDescriptor(resource.Get(), samples, fm.dsv_format, &write_descriptor))
    150       {
    151         m_descriptor_heap_manager.Free(&srv_descriptor);
    152         return {};
    153       }
    154     }
    155     break;
    156 
    157     case GPUTexture::Type::RWTexture:
    158     {
    159       write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV;
    160       if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor))
    161       {
    162         m_descriptor_heap_manager.Free(&srv_descriptor);
    163         return {};
    164       }
    165 
    166       if (!CreateUAVDescriptor(resource.Get(), samples, fm.srv_format, &uav_descriptor))
    167       {
    168         m_descriptor_heap_manager.Free(&write_descriptor);
    169         m_descriptor_heap_manager.Free(&srv_descriptor);
    170         return {};
    171       }
    172     }
    173     break;
    174 
    175     default:
    176       break;
    177   }
    178 
    179   std::unique_ptr<D3D12Texture> tex(new D3D12Texture(
    180     width, height, layers, levels, samples, type, format, fm.resource_format, std::move(resource),
    181     std::move(allocation), srv_descriptor, write_descriptor, uav_descriptor, write_descriptor_type, state));
    182 
    183   if (data)
    184   {
    185     tex->Update(0, 0, width, height, data, data_stride);
    186     tex->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
    187   }
    188 
    189   return tex;
    190 }
    191 
    192 bool D3D12Device::CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
    193                                       D3D12DescriptorHandle* dh)
    194 {
    195   if (!m_descriptor_heap_manager.Allocate(dh))
    196   {
    197     ERROR_LOG("Failed to allocate SRV descriptor");
    198     return false;
    199   }
    200 
    201   D3D12_SHADER_RESOURCE_VIEW_DESC desc;
    202   desc.Format = format;
    203   desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    204 
    205   if (layers > 1)
    206   {
    207     if (samples > 1)
    208     {
    209       desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY;
    210       desc.Texture2DMSArray = {0u, layers};
    211     }
    212     else
    213     {
    214       desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY;
    215       desc.Texture2DArray = {0u, levels, 0u, layers, 0u, 0.0f};
    216     }
    217   }
    218   else
    219   {
    220     if (samples > 1)
    221     {
    222       desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS;
    223     }
    224     else
    225     {
    226       desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    227       desc.Texture2D = {0u, levels, 0u, 0.0f};
    228     }
    229   }
    230 
    231   m_device->CreateShaderResourceView(resource, &desc, dh->cpu_handle);
    232   return true;
    233 }
    234 
    235 bool D3D12Device::CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
    236                                       D3D12DescriptorHandle* dh)
    237 {
    238   if (!m_rtv_heap_manager.Allocate(dh))
    239   {
    240     ERROR_LOG("Failed to allocate SRV descriptor");
    241     return false;
    242   }
    243 
    244   const D3D12_RENDER_TARGET_VIEW_DESC desc = {
    245     format, (samples > 1) ? D3D12_RTV_DIMENSION_TEXTURE2DMS : D3D12_RTV_DIMENSION_TEXTURE2D, {}};
    246   m_device->CreateRenderTargetView(resource, &desc, dh->cpu_handle);
    247   return true;
    248 }
    249 
    250 bool D3D12Device::CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
    251                                       D3D12DescriptorHandle* dh)
    252 {
    253   if (!m_dsv_heap_manager.Allocate(dh))
    254   {
    255     ERROR_LOG("Failed to allocate SRV descriptor");
    256     return false;
    257   }
    258 
    259   const D3D12_DEPTH_STENCIL_VIEW_DESC desc = {
    260     format, (samples > 1) ? D3D12_DSV_DIMENSION_TEXTURE2DMS : D3D12_DSV_DIMENSION_TEXTURE2D, D3D12_DSV_FLAG_NONE, {}};
    261   m_device->CreateDepthStencilView(resource, &desc, dh->cpu_handle);
    262   return true;
    263 }
    264 
    265 bool D3D12Device::CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
    266                                       D3D12DescriptorHandle* dh)
    267 {
    268   if (!m_descriptor_heap_manager.Allocate(dh))
    269   {
    270     ERROR_LOG("Failed to allocate UAV descriptor");
    271     return false;
    272   }
    273 
    274   DebugAssert(samples == 1);
    275   const D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {format, D3D12_UAV_DIMENSION_TEXTURE2D, {}};
    276   m_device->CreateUnorderedAccessView(resource, nullptr, &desc, dh->cpu_handle);
    277   return true;
    278 }
    279 
    280 void D3D12Texture::Destroy(bool defer)
    281 {
    282   D3D12Device& dev = D3D12Device::GetInstance();
    283   dev.UnbindTexture(this);
    284 
    285   if (defer)
    286   {
    287     dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_srv_descriptor);
    288 
    289     switch (m_write_descriptor_type)
    290     {
    291       case WriteDescriptorType::RTV:
    292         dev.DeferDescriptorDestruction(dev.GetRTVHeapManager(), &m_write_descriptor);
    293         break;
    294       case WriteDescriptorType::DSV:
    295         dev.DeferDescriptorDestruction(dev.GetDSVHeapManager(), &m_write_descriptor);
    296         break;
    297       case WriteDescriptorType::None:
    298       default:
    299         break;
    300     }
    301 
    302     if (m_uav_descriptor)
    303       dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_uav_descriptor);
    304 
    305     dev.DeferResourceDestruction(std::move(m_allocation), std::move(m_resource));
    306   }
    307   else
    308   {
    309     dev.GetDescriptorHeapManager().Free(&m_srv_descriptor);
    310 
    311     switch (m_write_descriptor_type)
    312     {
    313       case WriteDescriptorType::RTV:
    314         dev.GetRTVHeapManager().Free(&m_write_descriptor);
    315         break;
    316       case WriteDescriptorType::DSV:
    317         dev.GetDSVHeapManager().Free(&m_write_descriptor);
    318         break;
    319       case WriteDescriptorType::None:
    320       default:
    321         break;
    322     }
    323 
    324     if (m_uav_descriptor)
    325       dev.GetDescriptorHeapManager().Free(&m_uav_descriptor);
    326 
    327     m_resource.Reset();
    328     m_allocation.Reset();
    329   }
    330 
    331   m_write_descriptor_type = WriteDescriptorType::None;
    332 }
    333 
    334 ID3D12GraphicsCommandList4* D3D12Texture::GetCommandBufferForUpdate()
    335 {
    336   D3D12Device& dev = D3D12Device::GetInstance();
    337   if ((m_type != Type::Texture && m_type != Type::DynamicTexture) || m_use_fence_counter == dev.GetCurrentFenceValue())
    338   {
    339     // Console.WriteLn("Texture update within frame, can't use do beforehand");
    340     if (dev.InRenderPass())
    341       dev.EndRenderPass();
    342     return dev.GetCommandList();
    343   }
    344 
    345   return dev.GetInitCommandList();
    346 }
    347 
    348 void D3D12Texture::CopyTextureDataForUpload(void* dst, const void* src, u32 width, u32 height, u32 pitch,
    349                                             u32 upload_pitch) const
    350 {
    351   StringUtil::StrideMemCpy(dst, upload_pitch, src, pitch, GetPixelSize() * width, height);
    352 }
    353 
    354 ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32 pitch, u32 upload_pitch, u32 width,
    355                                                           u32 height) const
    356 {
    357   const u32 size = upload_pitch * height;
    358   ComPtr<ID3D12Resource> resource;
    359   ComPtr<D3D12MA::Allocation> allocation;
    360 
    361   const D3D12MA::ALLOCATION_DESC allocation_desc = {D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_UPLOAD,
    362                                                     D3D12_HEAP_FLAG_NONE, nullptr, nullptr};
    363   const D3D12_RESOURCE_DESC resource_desc = {
    364     D3D12_RESOURCE_DIMENSION_BUFFER, 0, size, 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
    365     D3D12_RESOURCE_FLAG_NONE};
    366   HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
    367     &allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, allocation.GetAddressOf(),
    368     IID_PPV_ARGS(resource.GetAddressOf()));
    369   if (FAILED(hr)) [[unlikely]]
    370   {
    371     ERROR_LOG("CreateResource() failed with {:08X}", static_cast<unsigned>(hr));
    372     return nullptr;
    373   }
    374 
    375   void* map_ptr;
    376   hr = resource->Map(0, nullptr, &map_ptr);
    377   if (FAILED(hr)) [[unlikely]]
    378   {
    379     ERROR_LOG("Map() failed with {:08X}", static_cast<unsigned>(hr));
    380     return nullptr;
    381   }
    382 
    383   CopyTextureDataForUpload(map_ptr, data, width, height, pitch, upload_pitch);
    384 
    385   const D3D12_RANGE write_range = {0, size};
    386   resource->Unmap(0, &write_range);
    387 
    388   // Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy.
    389   // This adds the reference needed to keep the buffer alive.
    390   ID3D12Resource* ret = resource.Get();
    391   D3D12Device::GetInstance().DeferResourceDestruction(std::move(allocation), std::move(resource));
    392   return ret;
    393 }
    394 
    395 bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer, u32 level)
    396 {
    397   DebugAssert(layer < m_layers && level < m_levels);
    398   DebugAssert((x + width) <= GetMipWidth(level) && (y + height) <= GetMipHeight(level));
    399 
    400   D3D12Device& dev = D3D12Device::GetInstance();
    401   D3D12StreamBuffer& sbuffer = dev.GetTextureUploadBuffer();
    402 
    403   const u32 upload_pitch = Common::AlignUpPow2<u32>(pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
    404   const u32 required_size = height * upload_pitch;
    405 
    406   D3D12_TEXTURE_COPY_LOCATION srcloc;
    407   srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
    408   srcloc.PlacedFootprint.Footprint.Width = width;
    409   srcloc.PlacedFootprint.Footprint.Height = height;
    410   srcloc.PlacedFootprint.Footprint.Depth = 1;
    411   srcloc.PlacedFootprint.Footprint.Format = m_dxgi_format;
    412   srcloc.PlacedFootprint.Footprint.RowPitch = upload_pitch;
    413 
    414   // If the texture is larger than half our streaming buffer size, use a separate buffer.
    415   // Otherwise allocation will either fail, or require lots of cmdbuffer submissions.
    416   if (required_size > (sbuffer.GetSize() / 2))
    417   {
    418     srcloc.pResource = AllocateUploadStagingBuffer(data, pitch, upload_pitch, width, height);
    419     if (!srcloc.pResource)
    420       return false;
    421 
    422     srcloc.PlacedFootprint.Offset = 0;
    423   }
    424   else
    425   {
    426     if (!sbuffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]]
    427     {
    428       D3D12Device::GetInstance().SubmitCommandList(
    429         false, TinyString::from_format("Needs {} bytes in texture upload buffer", required_size));
    430       if (!sbuffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]]
    431       {
    432         ERROR_LOG("Failed to reserve texture upload memory ({} bytes).", required_size);
    433         return false;
    434       }
    435     }
    436 
    437     srcloc.pResource = sbuffer.GetBuffer();
    438     srcloc.PlacedFootprint.Offset = sbuffer.GetCurrentOffset();
    439     CopyTextureDataForUpload(sbuffer.GetCurrentHostPointer(), data, width, height, pitch, upload_pitch);
    440     sbuffer.CommitMemory(required_size);
    441   }
    442 
    443   ID3D12GraphicsCommandList4* cmdlist = GetCommandBufferForUpdate();
    444 
    445   // if we're an rt and have been cleared, and the full rect isn't being uploaded, do the clear
    446   if (m_type == Type::RenderTarget)
    447   {
    448     if (x != 0 || y != 0 || width != m_width || height != m_height)
    449       CommitClear(cmdlist);
    450     else
    451       m_state = State::Dirty;
    452   }
    453 
    454   GPUDevice::GetStatistics().buffer_streamed += required_size;
    455   GPUDevice::GetStatistics().num_uploads++;
    456 
    457   // first time the texture is used? don't leave it undefined
    458   if (m_resource_state == D3D12_RESOURCE_STATE_COMMON)
    459     TransitionToState(cmdlist, D3D12_RESOURCE_STATE_COPY_DEST);
    460   else if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST)
    461     TransitionSubresourceToState(cmdlist, layer, level, m_resource_state, D3D12_RESOURCE_STATE_COPY_DEST);
    462 
    463   D3D12_TEXTURE_COPY_LOCATION dstloc;
    464   dstloc.pResource = m_resource.Get();
    465   dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    466   dstloc.SubresourceIndex = layer;
    467 
    468   const D3D12_BOX srcbox{0u, 0u, 0u, width, height, 1u};
    469   cmdlist->CopyTextureRegion(&dstloc, x, y, 0, &srcloc, &srcbox);
    470 
    471   if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST)
    472     TransitionSubresourceToState(cmdlist, layer, level, D3D12_RESOURCE_STATE_COPY_DEST, m_resource_state);
    473 
    474   return true;
    475 }
    476 
    477 bool D3D12Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer, u32 level)
    478 {
    479   // TODO: linear textures for dynamic?
    480   if ((x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || level > m_levels)
    481   {
    482     return false;
    483   }
    484 
    485   D3D12Device& dev = D3D12Device::GetInstance();
    486   if (m_state == State::Cleared && (x != 0 || y != 0 || width != m_width || height != m_height))
    487     CommitClear(GetCommandBufferForUpdate());
    488 
    489   // see note in Update() for the reason why.
    490   const u32 aligned_pitch = Common::AlignUpPow2(width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
    491   const u32 req_size = height * aligned_pitch;
    492   D3D12StreamBuffer& buffer = dev.GetTextureUploadBuffer();
    493   if (req_size >= (buffer.GetSize() / 2))
    494     return false;
    495 
    496   if (!buffer.ReserveMemory(req_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]]
    497   {
    498     dev.SubmitCommandList(false, TinyString::from_format("Needs {} bytes in texture upload buffer", req_size));
    499     if (!buffer.ReserveMemory(req_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]]
    500       Panic("Failed to reserve texture upload memory");
    501   }
    502 
    503   // map for writing
    504   *map = buffer.GetCurrentHostPointer();
    505   *map_stride = aligned_pitch;
    506   m_map_x = static_cast<u16>(x);
    507   m_map_y = static_cast<u16>(y);
    508   m_map_width = static_cast<u16>(width);
    509   m_map_height = static_cast<u16>(height);
    510   m_map_layer = static_cast<u8>(layer);
    511   m_map_level = static_cast<u8>(level);
    512   m_state = State::Dirty;
    513   return true;
    514 }
    515 
    516 void D3D12Texture::Unmap()
    517 {
    518   D3D12Device& dev = D3D12Device::GetInstance();
    519   D3D12StreamBuffer& sb = dev.GetTextureUploadBuffer();
    520   const u32 aligned_pitch = Common::AlignUpPow2(m_map_width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
    521   const u32 req_size = m_map_height * aligned_pitch;
    522   const u32 offset = sb.GetCurrentOffset();
    523   sb.CommitMemory(req_size);
    524 
    525   GPUDevice::GetStatistics().buffer_streamed += req_size;
    526   GPUDevice::GetStatistics().num_uploads++;
    527 
    528   ID3D12GraphicsCommandList4* cmdlist = GetCommandBufferForUpdate();
    529 
    530   // first time the texture is used? don't leave it undefined
    531   if (m_resource_state == D3D12_RESOURCE_STATE_COMMON)
    532     TransitionToState(cmdlist, D3D12_RESOURCE_STATE_COPY_DEST);
    533   else if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST)
    534     TransitionSubresourceToState(cmdlist, m_map_layer, m_map_level, m_resource_state, D3D12_RESOURCE_STATE_COPY_DEST);
    535 
    536   D3D12_TEXTURE_COPY_LOCATION srcloc;
    537   srcloc.pResource = sb.GetBuffer();
    538   srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
    539   srcloc.PlacedFootprint.Offset = offset;
    540   srcloc.PlacedFootprint.Footprint.Width = m_map_width;
    541   srcloc.PlacedFootprint.Footprint.Height = m_map_height;
    542   srcloc.PlacedFootprint.Footprint.Depth = 1;
    543   srcloc.PlacedFootprint.Footprint.Format = m_dxgi_format;
    544   srcloc.PlacedFootprint.Footprint.RowPitch = aligned_pitch;
    545 
    546   D3D12_TEXTURE_COPY_LOCATION dstloc;
    547   dstloc.pResource = m_resource.Get();
    548   dstloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    549   dstloc.SubresourceIndex = m_map_level;
    550 
    551   const D3D12_BOX srcbox{0u, 0u, 0u, m_map_width, m_map_height, 1};
    552   cmdlist->CopyTextureRegion(&dstloc, m_map_x, m_map_y, 0, &srcloc, &srcbox);
    553 
    554   if (m_resource_state != D3D12_RESOURCE_STATE_COPY_DEST)
    555     TransitionSubresourceToState(cmdlist, m_map_layer, m_map_level, D3D12_RESOURCE_STATE_COPY_DEST, m_resource_state);
    556 
    557   m_map_x = 0;
    558   m_map_y = 0;
    559   m_map_width = 0;
    560   m_map_height = 0;
    561   m_map_layer = 0;
    562   m_map_level = 0;
    563 }
    564 
    565 void D3D12Texture::CommitClear()
    566 {
    567   if (m_state != GPUTexture::State::Cleared)
    568     return;
    569 
    570   D3D12Device& dev = D3D12Device::GetInstance();
    571   if (dev.InRenderPass())
    572     dev.EndRenderPass();
    573 
    574   ActuallyCommitClear(dev.GetCommandList());
    575 }
    576 
    577 void D3D12Texture::CommitClear(ID3D12GraphicsCommandList* cmdlist)
    578 {
    579   if (m_state != GPUTexture::State::Cleared)
    580     return;
    581 
    582   ActuallyCommitClear(cmdlist);
    583 }
    584 
    585 void D3D12Texture::ActuallyCommitClear(ID3D12GraphicsCommandList* cmdlist)
    586 {
    587   if (IsDepthStencil())
    588   {
    589     TransitionToState(cmdlist, D3D12_RESOURCE_STATE_DEPTH_WRITE);
    590     cmdlist->ClearDepthStencilView(GetWriteDescriptor(), D3D12_CLEAR_FLAG_DEPTH, m_clear_value.depth, 0, 0, nullptr);
    591   }
    592   else
    593   {
    594     TransitionToState(cmdlist, D3D12_RESOURCE_STATE_RENDER_TARGET);
    595     cmdlist->ClearRenderTargetView(GetWriteDescriptor(), D3D12Device::RGBA8ToFloat(m_clear_value.color).data(), 0,
    596                                    nullptr);
    597   }
    598 
    599   SetState(State::Dirty);
    600 }
    601 
    602 void D3D12Texture::SetDebugName(std::string_view name)
    603 {
    604   D3D12::SetObjectName(m_resource.Get(), name);
    605 }
    606 
    607 u32 D3D12Texture::CalculateSubresource(u32 layer, u32 level, u32 num_levels)
    608 {
    609   // D3D11CalcSubresource
    610   return level + layer * num_levels;
    611 }
    612 
    613 u32 D3D12Texture::CalculateSubresource(u32 layer, u32 level) const
    614 {
    615   return CalculateSubresource(layer, level, m_levels);
    616 }
    617 
    618 void D3D12Texture::TransitionToState(D3D12_RESOURCE_STATES state)
    619 {
    620   TransitionToState(D3D12Device::GetInstance().GetCommandList(), state);
    621 }
    622 
    623 void D3D12Texture::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RESOURCE_STATES state)
    624 {
    625   if (m_resource_state == state)
    626     return;
    627 
    628   const D3D12_RESOURCE_STATES prev_state = m_resource_state;
    629   m_resource_state = state;
    630 
    631   const D3D12_RESOURCE_BARRIER barrier = {
    632     D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
    633     D3D12_RESOURCE_BARRIER_FLAG_NONE,
    634     {{m_resource.Get(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, prev_state, state}}};
    635   cmdlist->ResourceBarrier(1, &barrier);
    636 }
    637 
    638 void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 layer, u32 level,
    639                                                 D3D12_RESOURCE_STATES before_state,
    640                                                 D3D12_RESOURCE_STATES after_state) const
    641 {
    642   TransitionSubresourceToState(cmdlist, m_resource.Get(), CalculateSubresource(layer, level), before_state,
    643                                after_state);
    644 }
    645 
    646 void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, u32 subresource,
    647                                                 D3D12_RESOURCE_STATES before_state,
    648                                                 D3D12_RESOURCE_STATES after_state) const
    649 {
    650   TransitionSubresourceToState(cmdlist, m_resource.Get(), subresource, before_state, after_state);
    651 }
    652 
    653 void D3D12Texture::TransitionSubresourceToState(ID3D12GraphicsCommandList* cmdlist, ID3D12Resource* resource,
    654                                                 u32 subresource, D3D12_RESOURCE_STATES before_state,
    655                                                 D3D12_RESOURCE_STATES after_state)
    656 {
    657   const D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
    658                                           D3D12_RESOURCE_BARRIER_FLAG_NONE,
    659                                           {{resource, subresource, before_state, after_state}}};
    660   cmdlist->ResourceBarrier(1, &barrier);
    661 }
    662 
    663 void D3D12Texture::MakeReadyForSampling()
    664 {
    665   if (m_resource_state == D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
    666     return;
    667 
    668   D3D12Device& dev = D3D12Device::GetInstance();
    669   if (dev.InRenderPass())
    670     dev.EndRenderPass();
    671 
    672   TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
    673 }
    674 
    675 D3D12Sampler::D3D12Sampler(D3D12DescriptorHandle descriptor) : m_descriptor(descriptor)
    676 {
    677 }
    678 
    679 D3D12Sampler::~D3D12Sampler()
    680 {
    681   // Cleaned up by main class.
    682 }
    683 
    684 void D3D12Sampler::SetDebugName(std::string_view name)
    685 {
    686 }
    687 
    688 D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config)
    689 {
    690   const auto it = m_sampler_map.find(config.key);
    691   if (it != m_sampler_map.end())
    692     return it->second;
    693 
    694   static constexpr std::array<D3D12_TEXTURE_ADDRESS_MODE, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{
    695     D3D12_TEXTURE_ADDRESS_MODE_WRAP,   // Repeat
    696     D3D12_TEXTURE_ADDRESS_MODE_CLAMP,  // ClampToEdge
    697     D3D12_TEXTURE_ADDRESS_MODE_BORDER, // ClampToBorder
    698     D3D12_TEXTURE_ADDRESS_MODE_MIRROR, // MirrorRepeat
    699   }};
    700 
    701   static constexpr u8 filter_count = static_cast<u8>(GPUSampler::Filter::MaxCount);
    702   static constexpr D3D12_FILTER filters[filter_count][filter_count][filter_count] = {
    703     {
    704       {D3D12_FILTER_MIN_MAG_MIP_POINT, D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT},
    705       {D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT, D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT},
    706     },
    707     {
    708       {D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR, D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR},
    709       {D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, D3D12_FILTER_MIN_MAG_MIP_LINEAR},
    710     }};
    711 
    712   D3D12_SAMPLER_DESC desc = {};
    713   desc.AddressU = ta[static_cast<u8>(config.address_u.GetValue())];
    714   desc.AddressV = ta[static_cast<u8>(config.address_v.GetValue())];
    715   desc.AddressW = ta[static_cast<u8>(config.address_w.GetValue())];
    716   std::memcpy(desc.BorderColor, RGBA8ToFloat(config.border_color).data(), sizeof(desc.BorderColor));
    717   desc.MinLOD = static_cast<float>(config.min_lod);
    718   desc.MaxLOD = static_cast<float>(config.max_lod);
    719 
    720   if (config.anisotropy > 1)
    721   {
    722     desc.Filter = D3D12_FILTER_ANISOTROPIC;
    723     desc.MaxAnisotropy = config.anisotropy;
    724   }
    725   else
    726   {
    727     desc.Filter = filters[static_cast<u8>(config.mip_filter.GetValue())][static_cast<u8>(config.min_filter.GetValue())]
    728                          [static_cast<u8>(config.mag_filter.GetValue())];
    729     desc.MaxAnisotropy = 1;
    730   }
    731 
    732   D3D12DescriptorHandle handle;
    733   if (m_sampler_heap_manager.Allocate(&handle))
    734     m_device->CreateSampler(&desc, handle);
    735 
    736   m_sampler_map.emplace(config.key, handle);
    737   return handle;
    738 }
    739 
    740 void D3D12Device::DestroySamplers()
    741 {
    742   for (auto& it : m_sampler_map)
    743   {
    744     if (it.second)
    745       m_sampler_heap_manager.Free(&it.second);
    746   }
    747   m_sampler_map.clear();
    748 }
    749 
    750 std::unique_ptr<GPUSampler> D3D12Device::CreateSampler(const GPUSampler::Config& config)
    751 {
    752   const D3D12DescriptorHandle handle = GetSampler(config);
    753   if (!handle)
    754     return {};
    755 
    756   return std::unique_ptr<GPUSampler>(new D3D12Sampler(handle));
    757 }
    758 
    759 D3D12TextureBuffer::D3D12TextureBuffer(Format format, u32 size_in_elements) : GPUTextureBuffer(format, size_in_elements)
    760 {
    761 }
    762 
    763 D3D12TextureBuffer::~D3D12TextureBuffer()
    764 {
    765   Destroy(true);
    766 }
    767 
    768 bool D3D12TextureBuffer::Create(D3D12Device& dev)
    769 {
    770   static constexpr std::array<DXGI_FORMAT, static_cast<u8>(GPUTextureBuffer::Format::MaxCount)> format_mapping = {{
    771     DXGI_FORMAT_R16_UINT, // R16UI
    772   }};
    773 
    774   Error error;
    775   if (!m_buffer.Create(GetSizeInBytes(), &error)) [[unlikely]]
    776   {
    777     ERROR_LOG("Failed to create stream buffer: {}", error.GetDescription());
    778     return false;
    779   }
    780 
    781   if (!dev.GetDescriptorHeapManager().Allocate(&m_descriptor)) [[unlikely]]
    782     return {};
    783 
    784   D3D12_SHADER_RESOURCE_VIEW_DESC desc = {format_mapping[static_cast<u8>(m_format)],
    785                                           D3D12_SRV_DIMENSION_BUFFER,
    786                                           D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
    787                                           {}};
    788   desc.Buffer.NumElements = m_size_in_elements;
    789   dev.GetDevice()->CreateShaderResourceView(m_buffer.GetBuffer(), &desc, m_descriptor);
    790   return true;
    791 }
    792 
    793 void D3D12TextureBuffer::Destroy(bool defer)
    794 {
    795   D3D12Device& dev = D3D12Device::GetInstance();
    796   if (m_descriptor)
    797   {
    798     if (defer)
    799       dev.DeferDescriptorDestruction(dev.GetDescriptorHeapManager(), &m_descriptor);
    800     else
    801       dev.GetDescriptorHeapManager().Free(&m_descriptor);
    802   }
    803 }
    804 
    805 void* D3D12TextureBuffer::Map(u32 required_elements)
    806 {
    807   const u32 esize = GetElementSize(m_format);
    808   const u32 req_size = esize * required_elements;
    809   if (!m_buffer.ReserveMemory(req_size, esize))
    810   {
    811     D3D12Device::GetInstance().SubmitCommandListAndRestartRenderPass("out of space in texture buffer");
    812     if (!m_buffer.ReserveMemory(req_size, esize))
    813       Panic("Failed to allocate texture buffer space.");
    814   }
    815 
    816   m_current_position = m_buffer.GetCurrentOffset() / esize;
    817   return m_buffer.GetCurrentHostPointer();
    818 }
    819 
    820 void D3D12TextureBuffer::Unmap(u32 used_elements)
    821 {
    822   const u32 size = GetElementSize(m_format) * used_elements;
    823   GPUDevice::GetStatistics().buffer_streamed += size;
    824   GPUDevice::GetStatistics().num_uploads++;
    825   m_buffer.CommitMemory(size);
    826 }
    827 
    828 void D3D12TextureBuffer::SetDebugName(std::string_view name)
    829 {
    830   D3D12::SetObjectName(m_buffer.GetBuffer(), name);
    831 }
    832 
    833 std::unique_ptr<GPUTextureBuffer> D3D12Device::CreateTextureBuffer(GPUTextureBuffer::Format format,
    834                                                                    u32 size_in_elements)
    835 {
    836 
    837   std::unique_ptr<D3D12TextureBuffer> tb = std::make_unique<D3D12TextureBuffer>(format, size_in_elements);
    838   if (!tb->Create(*this))
    839     tb.reset();
    840 
    841   return tb;
    842 }
    843 
    844 D3D12DownloadTexture::D3D12DownloadTexture(u32 width, u32 height, GPUTexture::Format format,
    845                                            ComPtr<D3D12MA::Allocation> allocation, ComPtr<ID3D12Resource> buffer,
    846                                            size_t buffer_size)
    847   : GPUDownloadTexture(width, height, format, false), m_allocation(std::move(allocation)), m_buffer(std::move(buffer)),
    848     m_buffer_size(buffer_size)
    849 {
    850 }
    851 
    852 D3D12DownloadTexture::~D3D12DownloadTexture()
    853 {
    854   if (IsMapped())
    855     D3D12DownloadTexture::Unmap();
    856 
    857   if (m_buffer)
    858     D3D12Device::GetInstance().DeferResourceDestruction(m_allocation.Get(), m_buffer.Get());
    859 }
    860 
    861 std::unique_ptr<D3D12DownloadTexture> D3D12DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format)
    862 {
    863   const u32 buffer_size = GetBufferSize(width, height, format, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
    864 
    865   D3D12MA::ALLOCATION_DESC allocation_desc = {};
    866   allocation_desc.HeapType = D3D12_HEAP_TYPE_READBACK;
    867 
    868   const D3D12_RESOURCE_DESC resource_desc = {D3D12_RESOURCE_DIMENSION_BUFFER,
    869                                              0,
    870                                              buffer_size,
    871                                              1,
    872                                              1,
    873                                              1,
    874                                              DXGI_FORMAT_UNKNOWN,
    875                                              {1, 0},
    876                                              D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
    877                                              D3D12_RESOURCE_FLAG_NONE};
    878 
    879   ComPtr<D3D12MA::Allocation> allocation;
    880   ComPtr<ID3D12Resource> buffer;
    881 
    882   HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
    883     &allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, allocation.GetAddressOf(),
    884     IID_PPV_ARGS(buffer.GetAddressOf()));
    885   if (FAILED(hr))
    886   {
    887     ERROR_LOG("CreateResource() failed with HRESULT {:08X}", hr);
    888     return {};
    889   }
    890 
    891   return std::unique_ptr<D3D12DownloadTexture>(
    892     new D3D12DownloadTexture(width, height, format, std::move(allocation), std::move(buffer), buffer_size));
    893 }
    894 
    895 void D3D12DownloadTexture::CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width,
    896                                            u32 height, u32 src_layer, u32 src_level, bool use_transfer_pitch)
    897 {
    898   D3D12Texture* const src12 = static_cast<D3D12Texture*>(src);
    899   D3D12Device& dev = D3D12Device::GetInstance();
    900 
    901   DebugAssert(src12->GetFormat() == m_format);
    902   DebugAssert(src_level < src12->GetLevels());
    903   DebugAssert((src_x + width) <= src12->GetMipWidth(src_level) && (src_y + height) <= src12->GetMipHeight(src_level));
    904   DebugAssert((dst_x + width) <= m_width && (dst_y + height) <= m_height);
    905   DebugAssert((dst_x == 0 && dst_y == 0) || !use_transfer_pitch);
    906 
    907   u32 copy_offset, copy_size, copy_rows;
    908   m_current_pitch = GetTransferPitch(use_transfer_pitch ? width : m_width, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
    909   GetTransferSize(dst_x, dst_y, width, height, m_current_pitch, &copy_offset, &copy_size, &copy_rows);
    910 
    911   dev.GetStatistics().num_downloads++;
    912   if (dev.InRenderPass())
    913     dev.EndRenderPass();
    914   src12->CommitClear();
    915 
    916   if (IsMapped())
    917     Unmap();
    918 
    919   ID3D12GraphicsCommandList* cmdlist = dev.GetCommandList();
    920   GL_INS_FMT("ReadbackTexture: {{{},{}}} {}x{} => {{{},{}}}", src_x, src_y, width, height, dst_x, dst_y);
    921 
    922   D3D12_TEXTURE_COPY_LOCATION srcloc;
    923   srcloc.pResource = src12->GetResource();
    924   srcloc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
    925   srcloc.SubresourceIndex = src12->CalculateSubresource(src_layer, src_level);
    926 
    927   D3D12_TEXTURE_COPY_LOCATION dstloc;
    928   dstloc.pResource = m_buffer.Get();
    929   dstloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
    930   dstloc.PlacedFootprint.Offset = copy_offset;
    931   dstloc.PlacedFootprint.Footprint.Format = src12->GetDXGIFormat();
    932   dstloc.PlacedFootprint.Footprint.Width = width;
    933   dstloc.PlacedFootprint.Footprint.Height = height;
    934   dstloc.PlacedFootprint.Footprint.Depth = 1;
    935   dstloc.PlacedFootprint.Footprint.RowPitch = m_current_pitch;
    936 
    937   const D3D12_RESOURCE_STATES old_layout = src12->GetResourceState();
    938   if (old_layout != D3D12_RESOURCE_STATE_COPY_SOURCE)
    939     src12->TransitionSubresourceToState(cmdlist, src_level, old_layout, D3D12_RESOURCE_STATE_COPY_SOURCE);
    940 
    941   // TODO: Rules for depth buffers here?
    942   const D3D12_BOX srcbox{static_cast<UINT>(src_x),         static_cast<UINT>(src_y),          0u,
    943                          static_cast<UINT>(src_x + width), static_cast<UINT>(src_y + height), 1u};
    944   cmdlist->CopyTextureRegion(&dstloc, 0, 0, 0, &srcloc, &srcbox);
    945 
    946   if (old_layout != D3D12_RESOURCE_STATE_COPY_SOURCE)
    947     src12->TransitionSubresourceToState(cmdlist, src_level, D3D12_RESOURCE_STATE_COPY_SOURCE, old_layout);
    948 
    949   m_copy_fence_value = dev.GetCurrentFenceValue();
    950   m_needs_flush = true;
    951 }
    952 
    953 bool D3D12DownloadTexture::Map(u32 x, u32 y, u32 width, u32 height)
    954 {
    955   if (IsMapped())
    956     return true;
    957 
    958   // Never populated?
    959   if (!m_current_pitch)
    960     return false;
    961 
    962   u32 copy_offset, copy_size, copy_rows;
    963   GetTransferSize(x, y, width, height, m_current_pitch, &copy_offset, &copy_size, &copy_rows);
    964 
    965   const D3D12_RANGE read_range{copy_offset, copy_offset + m_current_pitch * copy_rows};
    966   const HRESULT hr = m_buffer->Map(0, &read_range, reinterpret_cast<void**>(const_cast<u8**>(&m_map_pointer)));
    967   if (FAILED(hr))
    968   {
    969     ERROR_LOG("Map() failed with HRESULT {:08X}", hr);
    970     return false;
    971   }
    972 
    973   return true;
    974 }
    975 
    976 void D3D12DownloadTexture::Unmap()
    977 {
    978   if (!IsMapped())
    979     return;
    980 
    981   const D3D12_RANGE write_range = {};
    982   m_buffer->Unmap(0, &write_range);
    983   m_map_pointer = nullptr;
    984 }
    985 
    986 void D3D12DownloadTexture::Flush()
    987 {
    988   if (!m_needs_flush)
    989     return;
    990 
    991   m_needs_flush = false;
    992 
    993   D3D12Device& dev = D3D12Device::GetInstance();
    994   if (dev.GetCompletedFenceValue() >= m_copy_fence_value)
    995     return;
    996 
    997   // Need to execute command buffer.
    998   if (dev.GetCurrentFenceValue() == m_copy_fence_value)
    999     dev.SubmitCommandList(true);
   1000   else
   1001     dev.WaitForFence(m_copy_fence_value);
   1002 }
   1003 
   1004 void D3D12DownloadTexture::SetDebugName(std::string_view name)
   1005 {
   1006   if (name.empty())
   1007     return;
   1008 
   1009   D3D12::SetObjectName(m_buffer.Get(), name);
   1010 }
   1011 
   1012 std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format)
   1013 {
   1014   return D3D12DownloadTexture::Create(width, height, format);
   1015 }
   1016 
   1017 std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
   1018                                                                        void* memory, size_t memory_size,
   1019                                                                        u32 memory_stride)
   1020 {
   1021   ERROR_LOG("D3D12 cannot import memory for download textures");
   1022   return {};
   1023 }