diff --git a/backends/imgui_impl_dx12.cpp b/backends/imgui_impl_dx12.cpp index 1e122a76e..d4da6ab02 100644 --- a/backends/imgui_impl_dx12.cpp +++ b/backends/imgui_impl_dx12.cpp @@ -20,6 +20,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-10-11: DirectX12: Reuse texture upload buffer and grow it only when necessary. (#9002) // 2025-09-29: DirectX12: Rework synchronization logic. (#8961) // 2025-09-29: DirectX12: Enable swapchain tearing to eliminate viewports framerate throttling. (#8965) // 2025-09-29: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time. (#8963) @@ -108,6 +109,9 @@ struct ImGui_ImplDX12_Data ID3D12CommandAllocator* pTexCmdAllocator; ID3D12GraphicsCommandList* pTexCmdList; + ID3D12Resource* pTexUploadBuffer; + UINT pTexUploadBufferSize; + void* pTexUploadBufferMapped; ImGui_ImplDX12_RenderBuffers* pFrameResources; UINT frameIndex; @@ -438,44 +442,53 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) UINT upload_pitch_dst = (upload_pitch_src + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); UINT upload_size = upload_pitch_dst * upload_h; - D3D12_RESOURCE_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; - desc.Alignment = 0; - desc.Width = upload_size; - desc.Height = 1; - desc.DepthOrArraySize = 1; - desc.MipLevels = 1; - desc.Format = DXGI_FORMAT_UNKNOWN; - desc.SampleDesc.Count = 1; - desc.SampleDesc.Quality = 0; - desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; - desc.Flags = D3D12_RESOURCE_FLAG_NONE; + if (bd->pTexUploadBuffer == nullptr || upload_size > bd->pTexUploadBufferSize) + { + if (bd->pTexUploadBufferMapped) + { + D3D12_RANGE range = { 0, bd->pTexUploadBufferSize }; + bd->pTexUploadBuffer->Unmap(0, &range); + bd->pTexUploadBufferMapped = nullptr; + } + SafeRelease(bd->pTexUploadBuffer); - D3D12_HEAP_PROPERTIES props; - memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); - props.Type = D3D12_HEAP_TYPE_UPLOAD; - props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; - props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + D3D12_RESOURCE_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = upload_size; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = D3D12_RESOURCE_FLAG_NONE; - // FIXME-OPT: Could upload buffer be kept around, reused, and grown only when needed? Would that be worth it? - ID3D12Resource* uploadBuffer = nullptr; - HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, - D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)); - IM_ASSERT(SUCCEEDED(hr)); + D3D12_HEAP_PROPERTIES props; + memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES)); + props.Type = D3D12_HEAP_TYPE_UPLOAD; + props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; + props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; + + HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&bd->pTexUploadBuffer)); + IM_ASSERT(SUCCEEDED(hr)); + + D3D12_RANGE range = {0, upload_size}; + hr = bd->pTexUploadBuffer->Map(0, &range, &bd->pTexUploadBufferMapped); + IM_ASSERT(SUCCEEDED(hr)); + bd->pTexUploadBufferSize = upload_size; + } bd->pTexCmdAllocator->Reset(); bd->pTexCmdList->Reset(bd->pTexCmdAllocator, nullptr); ID3D12GraphicsCommandList* cmdList = bd->pTexCmdList; // Copy to upload buffer - void* mapped = nullptr; - D3D12_RANGE range = { 0, upload_size }; - hr = uploadBuffer->Map(0, &range, &mapped); - IM_ASSERT(SUCCEEDED(hr)); for (int y = 0; y < upload_h; y++) - memcpy((void*)((uintptr_t)mapped + y * upload_pitch_dst), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch_src); - uploadBuffer->Unmap(0, &range); + memcpy((void*)((uintptr_t)bd->pTexUploadBufferMapped + y * upload_pitch_dst), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch_src); if (need_barrier_before_copy) { @@ -492,7 +505,7 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) D3D12_TEXTURE_COPY_LOCATION srcLocation = {}; D3D12_TEXTURE_COPY_LOCATION dstLocation = {}; { - srcLocation.pResource = uploadBuffer; + srcLocation.pResource = bd->pTexUploadBuffer; srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srcLocation.PlacedFootprint.Footprint.Width = upload_w; @@ -516,9 +529,8 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) cmdList->ResourceBarrier(1, &barrier); } - hr = cmdList->Close(); + HRESULT hr = cmdList->Close(); IM_ASSERT(SUCCEEDED(hr)); - ID3D12CommandQueue* cmdQueue = bd->pCommandQueue; cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList); hr = cmdQueue->Signal(bd->Fence, ++bd->FenceLastSignaledValue); @@ -531,7 +543,6 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex) bd->Fence->SetEventOnCompletion(bd->FenceLastSignaledValue, bd->FenceEvent); ::WaitForSingleObject(bd->FenceEvent, INFINITE); - uploadBuffer->Release(); tex->SetStatus(ImTextureStatus_OK); } @@ -804,6 +815,13 @@ void ImGui_ImplDX12_InvalidateDeviceObjects() bd->commandQueueOwned = false; SafeRelease(bd->pRootSignature); SafeRelease(bd->pPipelineState); + if (bd->pTexUploadBufferMapped) + { + D3D12_RANGE range = { 0, bd->pTexUploadBufferSize }; + bd->pTexUploadBuffer->Unmap(0, &range); + bd->pTexUploadBufferMapped = nullptr; + } + SafeRelease(bd->pTexUploadBuffer); SafeRelease(bd->pTexCmdList); SafeRelease(bd->pTexCmdAllocator); SafeRelease(bd->Fence); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9c5b50988..e72b81462 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -99,6 +99,8 @@ Other Changes: of recreating them each time. (#8963, #8465) [@RT2Code] - Backends: DirectX12: Rework synchronization logic. (#8961) [@RT2Code] (presumably fixes old hard-to-repro crash issues such as #3463, #5018) +- Backends: DirectX12: Reuse texture upload buffer and grow it only when + necessary. (#9002) [@RT2Code] - Backends: DirectX12: Enable swapchain tearing if available. (#8965) [@RT2Code] - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes]