// Copyright (c) Wojciech Figat. All rights reserved. #if GRAPHICS_API_DIRECTX12 #include "UploadBufferDX12.h" #include "GPUTextureDX12.h" #include "../RenderToolsDX.h" #include "Engine/Graphics/GPUResource.h" #include "Engine/Profiler/ProfilerMemory.h" /// /// Single page for the upload buffer /// class UploadBufferPageDX12 : public GPUResourceBase, public ResourceOwnerDX12 { public: UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size); public: /// /// Last generation that has been using that page /// uint64 LastGen; /// /// CPU memory address of the page /// void* CPUAddress; /// /// GPU memory address of the page /// D3D12_GPU_VIRTUAL_ADDRESS GPUAddress; /// /// Page size in bytes /// uint64 Size; public: // [GPUResourceDX12] GPUResourceType GetResourceType() const final override { return GPUResourceType::Buffer; } // [ResourceOwnerDX12] GPUResource* AsGPUResource() const override { return (GPUResource*)this; } protected: // [GPUResourceDX12] void OnReleaseGPU() final override; }; UploadBufferDX12::UploadBufferDX12(GPUDeviceDX12* device) : _device(device) , _currentPage(nullptr) , _currentOffset(0) , _currentGeneration(0) { } UploadBufferDX12::Allocation UploadBufferDX12::Allocate(uint64 size, uint64 align) { const uint64 alignmentMask = align - 1; ASSERT_LOW_LAYER((alignmentMask & align) == 0); const uint64 pageSize = Math::Max(size, DX12_DEFAULT_UPLOAD_PAGE_SIZE); const uint64 alignedSize = Math::AlignUpWithMask(size, alignmentMask); // Align the allocation _currentOffset = Math::AlignUpWithMask(_currentOffset, alignmentMask); // Check if there is enough space for that chunk of the data in the current page if (_currentPage && _currentOffset + alignedSize > _currentPage->Size) _currentPage = nullptr; // Check if need to get new page if (_currentPage == nullptr) { // Try reusing existing page for (int32 i = 0; i < _freePages.Count(); i++) { UploadBufferPageDX12* page = _freePages.Get()[i]; if (page->Size == pageSize) { _freePages.RemoveAt(i); _currentPage = page; break; } } if (_currentPage == nullptr) _currentPage = New(_device, pageSize); _usedPages.Add(_currentPage); ASSERT_LOW_LAYER(_currentPage->GetResource()); _currentOffset = 0; } // Mark page as used in this generation _currentPage->LastGen = _currentGeneration; // Create allocation result const Allocation result { (byte*)_currentPage->CPUAddress + _currentOffset, _currentOffset, size, _currentPage->GPUAddress + _currentOffset, _currentPage->GetResource(), _currentGeneration }; // Move within a page _currentOffset += size; return result; } void UploadBufferDX12::UploadBuffer(ID3D12GraphicsCommandList* commandList, ID3D12Resource* buffer, uint32 bufferOffset, const void* data, uint64 size) { // Allocate data const auto allocation = Allocate(size, GPU_SHADER_DATA_ALIGNMENT); // Copy data Platform::MemoryCopy(allocation.CPUAddress, data, size); // Copy buffer region commandList->CopyBufferRegion(buffer, bufferOffset, allocation.Resource, allocation.Offset, size); } void UploadBufferDX12::UploadTexture(ID3D12GraphicsCommandList* commandList, ID3D12Resource* texture, const void* srcData, uint32 srcRowPitch, uint32 srcSlicePitch, int32 mipIndex, int32 arrayIndex) { D3D12_RESOURCE_DESC resourceDesc = texture->GetDesc(); const UINT subresourceIndex = RenderToolsDX::CalcSubresourceIndex(mipIndex, arrayIndex, resourceDesc.MipLevels); D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; uint32 numRows; uint64 rowPitchAligned, mipSizeAligned; _device->GetDevice()->GetCopyableFootprints(&resourceDesc, subresourceIndex, 1, 0, &footprint, &numRows, &rowPitchAligned, &mipSizeAligned); rowPitchAligned = footprint.Footprint.RowPitch; mipSizeAligned = rowPitchAligned * footprint.Footprint.Height; const uint32 numSlices = resourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D ? Math::Max(1, resourceDesc.DepthOrArraySize >> mipIndex) : 1; const uint64 sliceSizeAligned = numSlices * mipSizeAligned; // Allocate data const auto allocation = Allocate(sliceSizeAligned, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); byte* ptr = (byte*)srcData; ASSERT(srcSlicePitch <= sliceSizeAligned); if (srcSlicePitch == sliceSizeAligned) { // Copy data at once Platform::MemoryCopy(allocation.CPUAddress, ptr, srcSlicePitch); } else { // Copy data per-row byte* dst = static_cast(allocation.CPUAddress); ASSERT(srcRowPitch <= rowPitchAligned); const uint32 numCopies = numSlices * numRows; for (uint32 i = 0; i < numCopies; i++) { Platform::MemoryCopy(dst, ptr, srcRowPitch); dst += rowPitchAligned; ptr += srcRowPitch; } } // Destination texture copy location description D3D12_TEXTURE_COPY_LOCATION dstLocation; dstLocation.pResource = texture; dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; dstLocation.SubresourceIndex = subresourceIndex; // Source buffer copy location description D3D12_TEXTURE_COPY_LOCATION srcLocation; srcLocation.pResource = allocation.Resource; srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; srcLocation.PlacedFootprint.Offset = allocation.Offset; srcLocation.PlacedFootprint.Footprint = footprint.Footprint; // Copy texture region commandList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr); } void UploadBufferDX12::BeginGeneration(uint64 generation) { // Restore ready pages to be reused for (int32 i = 0; _usedPages.HasItems() && i < _usedPages.Count(); i++) { auto page = _usedPages[i]; if (page->LastGen + DX12_UPLOAD_PAGE_GEN_TIMEOUT < generation) { _usedPages.RemoveAt(i); i--; _freePages.Add(page); } } // Remove old pages for (int32 i = _freePages.Count() - 1; i >= 0 && _freePages.HasItems(); i--) { auto page = _freePages[i]; if (page->LastGen + DX12_UPLOAD_PAGE_GEN_TIMEOUT + DX12_UPLOAD_PAGE_NOT_USED_FRAME_TIMEOUT < generation) { _freePages.RemoveAt(i); i--; page->ReleaseGPU(); Delete(page); } } // Set new generation _currentGeneration = generation; } void UploadBufferDX12::ReleaseGPU() { _freePages.Add(_usedPages); for (auto page : _freePages) { page->ReleaseGPU(); Delete(page); } } UploadBufferPageDX12::UploadBufferPageDX12(GPUDeviceDX12* device, uint64 size) : GPUResourceBase(device, TEXT("Upload Buffer Page")) , LastGen(0) , CPUAddress(nullptr) , GPUAddress(0) , Size(size) { // Create page buffer D3D12_HEAP_PROPERTIES heapProperties; heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD; heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heapProperties.CreationNodeMask = 1; heapProperties.VisibleNodeMask = 1; D3D12_RESOURCE_DESC resourceDesc; resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; resourceDesc.Alignment = 0; resourceDesc.Width = size; resourceDesc.Height = 1; resourceDesc.DepthOrArraySize = 1; resourceDesc.MipLevels = 1; resourceDesc.Format = DXGI_FORMAT_UNKNOWN; resourceDesc.SampleDesc.Count = 1; resourceDesc.SampleDesc.Quality = 0; resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; ID3D12Resource* resource; VALIDATE_DIRECTX_CALL(_device->GetDevice()->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&resource))); // Set state initResource(resource, D3D12_RESOURCE_STATE_GENERIC_READ, 1); DX_SET_DEBUG_NAME(_resource, GetName()); _memoryUsage = size; PROFILE_MEM_INC(GraphicsCommands, _memoryUsage); GPUAddress = _resource->GetGPUVirtualAddress(); // Map buffer VALIDATE_DIRECTX_CALL(_resource->Map(0, nullptr, &CPUAddress)); } void UploadBufferPageDX12::OnReleaseGPU() { PROFILE_MEM_DEC(GraphicsCommands, _memoryUsage); // Unmap if (_resource && CPUAddress) _resource->Unmap(0, nullptr); GPUAddress = 0; CPUAddress = nullptr; // Release releaseResource(); } #endif