diff --git a/renderdoc/core/resource_manager.h b/renderdoc/core/resource_manager.h index 6587b4752..76eeb7ab1 100644 --- a/renderdoc/core/resource_manager.h +++ b/renderdoc/core/resource_manager.h @@ -477,7 +477,7 @@ struct ResourceRecord return MarkResourceFrameReferenced(id, refType, ComposeFrameRefs); } void AddResourceReferences(ResourceRecordHandler *mgr); - void AddReferencedIDs(std::set &ids) + void AddReferencedIDs(std::unordered_set &ids) { for(auto it = m_FrameRefs.begin(); it != m_FrameRefs.end(); ++it) ids.insert(it->first); diff --git a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp index 7a4256bd6..05caf25c7 100644 --- a/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_command_list_wrap.cpp @@ -5351,7 +5351,79 @@ bool WrappedID3D12GraphicsCommandList::Serialise_CopyTiles( const D3D12_TILE_REGION_SIZE *pTileRegionSize, ID3D12Resource *pBuffer, UINT64 BufferStartOffsetInBytes, D3D12_TILE_COPY_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); + ID3D12GraphicsCommandList *pCommandList = this; + SERIALISE_ELEMENT(pCommandList); + SERIALISE_ELEMENT(pTiledResource); + SERIALISE_ELEMENT_LOCAL(TileRegionStartCoordinate, *pTileRegionStartCoordinate); + SERIALISE_ELEMENT_LOCAL(TileRegionSize, *pTileRegionSize); + SERIALISE_ELEMENT(pBuffer); + SERIALISE_ELEMENT(BufferStartOffsetInBytes); + SERIALISE_ELEMENT(Flags); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + m_Cmd->m_LastCmdListID = GetResourceManager()->GetOriginalID(GetResID(pCommandList)); + + if(IsActiveReplaying(m_State)) + { + if(m_Cmd->InRerecordRange(m_Cmd->m_LastCmdListID)) + { + ID3D12GraphicsCommandListX *list = m_Cmd->RerecordCmdList(m_Cmd->m_LastCmdListID); + Unwrap(list)->CopyTiles(Unwrap(pTiledResource), &TileRegionStartCoordinate, &TileRegionSize, + Unwrap(pBuffer), BufferStartOffsetInBytes, Flags); + } + } + else + { + Unwrap(pCommandList) + ->CopyTiles(Unwrap(pTiledResource), &TileRegionStartCoordinate, &TileRegionSize, + Unwrap(pBuffer), BufferStartOffsetInBytes, Flags); + GetCrackedList()->CopyTiles(Unwrap(pTiledResource), &TileRegionStartCoordinate, + &TileRegionSize, Unwrap(pBuffer), BufferStartOffsetInBytes, Flags); + + { + m_Cmd->AddEvent(); + + ResourceId liveSrc = GetResID(pBuffer); + ResourceId liveDst = GetResID(pTiledResource); + + if(Flags & D3D12_TILE_COPY_FLAG_SWIZZLED_TILED_RESOURCE_TO_LINEAR_BUFFER) + std::swap(liveSrc, liveDst); + + ResourceId origSrc = GetResourceManager()->GetOriginalID(liveSrc); + ResourceId origDst = GetResourceManager()->GetOriginalID(liveDst); + + DrawcallDescription draw; + draw.name = StringFormat::Fmt("CopyTiles(src=%s, dst=%s)", ToStr(origDst).c_str(), + ToStr(origSrc).c_str()); + draw.flags |= DrawFlags::Copy; + + draw.copySource = origSrc; + draw.copyDestination = origDst; + + Subresource tileSub = Subresource( + GetMipForSubresource(pTiledResource, TileRegionStartCoordinate.Subresource), + GetSliceForSubresource(pTiledResource, TileRegionStartCoordinate.Subresource)); + + if(Flags & D3D12_TILE_COPY_FLAG_SWIZZLED_TILED_RESOURCE_TO_LINEAR_BUFFER) + draw.copySourceSubresource = tileSub; + else + draw.copyDestinationSubresource = tileSub; + + m_Cmd->AddDrawcall(draw, true); + + D3D12DrawcallTreeNode &drawNode = m_Cmd->GetDrawcallStack().back()->children.back(); + + drawNode.resourceUsage.push_back( + make_rdcpair(liveSrc, EventUsage(drawNode.draw.eventId, ResourceUsage::CopySrc))); + drawNode.resourceUsage.push_back( + make_rdcpair(liveDst, EventUsage(drawNode.draw.eventId, ResourceUsage::CopyDst))); + } + } + } + return true; } @@ -5360,9 +5432,22 @@ void WrappedID3D12GraphicsCommandList::CopyTiles( const D3D12_TILE_REGION_SIZE *pTileRegionSize, ID3D12Resource *pBuffer, UINT64 BufferStartOffsetInBytes, D3D12_TILE_COPY_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); - m_pList->CopyTiles(Unwrap(pTiledResource), pTileRegionStartCoordinate, pTileRegionSize, - Unwrap(pBuffer), BufferStartOffsetInBytes, Flags); + SERIALISE_TIME_CALL(m_pList->CopyTiles(Unwrap(pTiledResource), pTileRegionStartCoordinate, + pTileRegionSize, Unwrap(pBuffer), BufferStartOffsetInBytes, + Flags)); + + if(IsCaptureMode(m_State)) + { + CACHE_THREAD_SERIALISER(); + ser.SetDrawChunk(); + SCOPED_SERIALISE_CHUNK(D3D12Chunk::List_CopyTiles); + Serialise_CopyTiles(ser, pTiledResource, pTileRegionStartCoordinate, pTileRegionSize, pBuffer, + BufferStartOffsetInBytes, Flags); + + m_ListRecord->AddChunk(scope.Get(m_ListRecord->cmdInfo->alloc)); + m_ListRecord->MarkResourceFrameReferenced(GetResID(pTiledResource), eFrameRef_PartialWrite); + m_ListRecord->MarkResourceFrameReferenced(GetResID(pBuffer), eFrameRef_Read); + } } #pragma endregion Copies diff --git a/renderdoc/driver/d3d12/d3d12_command_queue.h b/renderdoc/driver/d3d12/d3d12_command_queue.h index 1d388f0a1..d7e05f7a5 100644 --- a/renderdoc/driver/d3d12/d3d12_command_queue.h +++ b/renderdoc/driver/d3d12/d3d12_command_queue.h @@ -156,6 +156,8 @@ class WrappedID3D12CommandQueue : public ID3D12CommandQueue, rdcarray m_CmdListRecords; + std::unordered_set m_SparseBindResources; + // D3D12 guarantees that queues are thread-safe Threading::CriticalSection m_Lock; @@ -196,6 +198,11 @@ public: uint32_t GetMaxEID() { return m_Cmd.m_Events.back().eventId; } void ClearAfterCapture(); + bool IsSparseUpdatedResource(ResourceId id) const + { + return m_SparseBindResources.find(id) != m_SparseBindResources.end(); + } + ReplayStatus ReplayLog(CaptureState readType, uint32_t startEventID, uint32_t endEventID, bool partial); void SetFrameReader(StreamReader *reader) { m_FrameReader = reader; } diff --git a/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp b/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp index 7f4137498..318f2f22c 100644 --- a/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_command_queue_wrap.cpp @@ -38,7 +38,33 @@ bool WrappedID3D12CommandQueue::Serialise_UpdateTileMappings( const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); + ID3D12CommandQueue *pQueue = this; + SERIALISE_ELEMENT(pQueue); + SERIALISE_ELEMENT(pResource); + SERIALISE_ELEMENT(NumResourceRegions); + SERIALISE_ELEMENT_ARRAY(pResourceRegionStartCoordinates, NumResourceRegions); + SERIALISE_ELEMENT_ARRAY(pResourceRegionSizes, NumResourceRegions); + SERIALISE_ELEMENT(pHeap); + SERIALISE_ELEMENT(NumRanges); + SERIALISE_ELEMENT_ARRAY(pRangeFlags, NumRanges); + SERIALISE_ELEMENT_ARRAY(pHeapRangeStartOffsets, NumRanges); + SERIALISE_ELEMENT_ARRAY(pRangeTileCounts, NumRanges); + SERIALISE_ELEMENT(Flags); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + if(IsLoading(m_State)) + m_SparseBindResources.insert(GetResID(pResource)); + + // don't replay with NO_HAZARD + m_pReal->UpdateTileMappings(Unwrap(pResource), NumResourceRegions, + pResourceRegionStartCoordinates, pResourceRegionSizes, + Unwrap(pHeap), NumRanges, pRangeFlags, pHeapRangeStartOffsets, + pRangeTileCounts, Flags & ~D3D12_TILE_MAPPING_FLAG_NO_HAZARD); + } + return true; } @@ -49,10 +75,257 @@ void STDMETHODCALLTYPE WrappedID3D12CommandQueue::UpdateTileMappings( const D3D12_TILE_RANGE_FLAGS *pRangeFlags, const UINT *pHeapRangeStartOffsets, const UINT *pRangeTileCounts, D3D12_TILE_MAPPING_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); - m_pReal->UpdateTileMappings(Unwrap(pResource), NumResourceRegions, pResourceRegionStartCoordinates, - pResourceRegionSizes, Unwrap(pHeap), NumRanges, pRangeFlags, - pHeapRangeStartOffsets, pRangeTileCounts, Flags); + SERIALISE_TIME_CALL(m_pReal->UpdateTileMappings( + Unwrap(pResource), NumResourceRegions, pResourceRegionStartCoordinates, pResourceRegionSizes, + Unwrap(pHeap), NumRanges, pRangeFlags, pHeapRangeStartOffsets, pRangeTileCounts, Flags)); + + if(IsActiveCapturing(m_State)) + { + CACHE_THREAD_SERIALISER(); + ser.SetDrawChunk(); + SCOPED_SERIALISE_CHUNK(D3D12Chunk::Queue_UpdateTileMappings); + Serialise_UpdateTileMappings(ser, pResource, NumResourceRegions, pResourceRegionStartCoordinates, + pResourceRegionSizes, pHeap, NumRanges, pRangeFlags, + pHeapRangeStartOffsets, pRangeTileCounts, Flags); + + m_QueueRecord->AddChunk(scope.Get()); + GetResourceManager()->MarkResourceFrameReferenced(GetResID(pResource), eFrameRef_Read); + GetResourceManager()->MarkResourceFrameReferenced(GetResID(pHeap), eFrameRef_Read); + } + + // update our internal page tables + if(IsCaptureMode(m_State)) + { + Sparse::PageTable &pageTable = *GetRecord(pResource)->sparseTable; + ResourceId memoryId = GetResID(pHeap); + + // register this heap as having been used for sparse binding + m_pDevice->AddSparseHeap(GetResID(pHeap)); + +// define macros to help provide the defaults for NULL arrays +#define REGION_START(i) \ + (pResourceRegionStartCoordinates ? pResourceRegionStartCoordinates[i] \ + : D3D12_TILED_RESOURCE_COORDINATE({0, 0, 0, 0})) +// The default for size depends on whether a co-ordinate is set (ughhhh). If we do have co-ordinates +// then the sizes are all 1 tile. If we don't, then the size is the whole resource. Ideally we'd +// provide the exact number of tiles, but instead we just set ~0U and the sparse table interprets +// this as 'unbounded tiles' +#define REGION_SIZE(i) \ + (pResourceRegionSizes \ + ? pResourceRegionSizes[i] \ + : (pResourceRegionStartCoordinates ? D3D12_TILE_REGION_SIZE({1, FALSE, 1, 1, 1}) \ + : D3D12_TILE_REGION_SIZE({~0U, FALSE, 1, 1, 1}))) +#define RANGE_FLAGS(i) (pRangeFlags ? pRangeFlags[i] : D3D12_TILE_RANGE_FLAG_NONE) +// don't think there is any default for this one, but we just return 0 for consistency and safety +// since the array CAN be NULL when it's ignored +#define RANGE_OFFSET(i) (pHeapRangeStartOffsets ? pHeapRangeStartOffsets[i] : 0) +#define RANGE_SIZE(i) (pRangeTileCounts ? pRangeTileCounts[i] : ~0U) + + const UINT pageSize = 64 * 1024; + const Sparse::Coord texelShape = pageTable.getPageTexelSize(); + + // this persists from loop to loop. The effective offset is rangeBaseOffset + + // curRelativeRangeOffset. That allows us to partially use a range in one region then another. + // This goes from 0 to whatever rangeSize is + UINT curRelativeRangeOffset = 0; + + // iterate region at a time + UINT curRange = 0; + for(UINT curRegion = 0; curRegion < NumResourceRegions && curRange < NumRanges; curRegion++) + { + D3D12_TILED_RESOURCE_COORDINATE regionStart = REGION_START(curRegion); + D3D12_TILE_REGION_SIZE regionSize = REGION_SIZE(curRegion); + + // sanitise the region size according to the dimensions of the texture + // clamp inputs that may be invalid for buffers or 2D to sensible values + regionSize.Width = RDCCLAMP(1U, regionSize.Width, pageTable.getResourceSize().x); + regionSize.Height = + (uint16_t)RDCCLAMP(1U, (uint32_t)regionSize.Height, pageTable.getResourceSize().y); + regionSize.Depth = + (uint16_t)RDCCLAMP(1U, (uint32_t)regionSize.Depth, pageTable.getResourceSize().z); + + UINT rangeBaseOffset = RANGE_OFFSET(0); + UINT rangeSize = RANGE_SIZE(curRange); + D3D12_TILE_RANGE_FLAGS rangeFlags = RANGE_FLAGS(curRange); + + // get the memory ID, respecting the NULL flag + ResourceId memId = memoryId; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_NULL) + memId = ResourceId(); + + // store if we're skipping for this range + bool skip = false; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_SKIP) + skip = true; + + // take the current range offset (which might be partway into the current range even at + // the start of a region). Unless we're re-using a single tile in which case it's always the + // start of the region + bool singlePage = false; + uint32_t memoryOffsetInTiles = rangeBaseOffset + curRelativeRangeOffset; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE) + { + memoryOffsetInTiles = RANGE_OFFSET(curRange); + singlePage = true; + } + + // if the region is a box region, contained within a subresource + if(regionSize.UseBox) + { + // if this region is entirely within the current range, set it as one + if(regionSize.NumTiles <= rangeSize) + { + if(skip) + { + // do no binding if we're skipping, because this whole range covers the region + } + else + { + pageTable.setImageBoxRange( + regionStart.Subresource, {regionStart.X * texelShape.x, regionStart.Y * texelShape.y, + regionStart.Z * texelShape.z}, + {regionSize.Width * texelShape.x, regionSize.Height * texelShape.y, + regionSize.Depth * texelShape.z}, + memId, memoryOffsetInTiles * pageSize, singlePage); + } + + // consume the number of tiles in the range, which might not be all of them + curRelativeRangeOffset += regionSize.NumTiles; + + // however if it is, then move to the next range. We don't need to reset most range + // parameters because they'll be refreshed on the next region, however the exception is + // the range offset which is persistent region-to-region because we might use only part of + // a range on one region. + if(curRelativeRangeOffset >= rangeSize) + { + curRange++; + + curRelativeRangeOffset = 0; + if(curRange < NumRanges) + rangeBaseOffset = RANGE_OFFSET(curRange); + } + + // we're done with this region, we'll loop around now + } + // if the region isn't contained within a single range, iterate tile-by-tile + else + { + // the region spans multiple ranges. Fall back to tile-by-tile setting + for(UINT z = 0; z < regionSize.Depth; z++) + { + for(UINT y = 0; y < regionSize.Height; y++) + { + for(UINT x = 0; x < regionSize.Width; x++) + { + if(skip) + { + // do nothing + } + else + { + pageTable.setImageBoxRange( + regionStart.Subresource, + {(regionStart.X + x) * texelShape.x, (regionStart.Y + y) * texelShape.y, + (regionStart.Z + z) * texelShape.z}, + texelShape, memId, memoryOffsetInTiles * pageSize, singlePage); + } + + // consume one tile, and also advance the memory offset if we're not in single page + // mode + curRelativeRangeOffset += 1; + if(!singlePage) + memoryOffsetInTiles += 1; + + // if we've consumed everything in the current range, move to the next one + if(curRelativeRangeOffset >= rangeSize) + { + curRange++; + curRelativeRangeOffset = 0; + + if(curRange < NumRanges) + { + rangeBaseOffset = RANGE_OFFSET(curRange); + rangeFlags = RANGE_FLAGS(curRange); + rangeSize = RANGE_SIZE(curRange); + + skip = false; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_SKIP) + skip = true; + + memId = memoryId; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_NULL) + memId = ResourceId(); + + memoryOffsetInTiles = rangeBaseOffset + curRelativeRangeOffset; + singlePage = false; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE) + singlePage = true; + } + } + } + } + } + + // done with the x,y,z loop. Continue to the next region. We handled any range wrapping in + // the innermost loop so we don't have to do anything here + } + } + // the region isn't a box region, so it can wrap + else + { + // set up the starting co-ord. setImageWrappedRange will help us iterate from here + rdcpair curCoord = { + regionStart.Subresource, + {regionStart.X * texelShape.x, regionStart.Y * texelShape.y, + regionStart.Z * texelShape.z}}; + + // consume a region at a time setting it. The page table will handle detecting any + // whole-subresource sets + for(UINT i = 0; i < regionSize.NumTiles;) + { + // we consume either the rest of the range or the rest of the region, whichever is least + UINT tilesToConsume = RDCMIN(regionSize.NumTiles - i, rangeSize - curRelativeRangeOffset); + + RDCASSERT(tilesToConsume > 0); + + curCoord = pageTable.setImageWrappedRange( + curCoord.first, curCoord.second, tilesToConsume * pageSize, memId, + memoryOffsetInTiles * pageSize, singlePage, !skip); + + // consume the number of tiles from the region and range + i += tilesToConsume; + curRelativeRangeOffset += tilesToConsume; + + // if we've consumed everything in the current range, move to the next one + if(curRelativeRangeOffset >= rangeSize) + { + curRange++; + curRelativeRangeOffset = 0; + + if(curRange < NumRanges) + { + rangeBaseOffset = RANGE_OFFSET(curRange); + rangeFlags = RANGE_FLAGS(curRange); + rangeSize = RANGE_SIZE(curRange); + + skip = false; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_SKIP) + skip = true; + + memId = memoryId; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_NULL) + memId = ResourceId(); + + memoryOffsetInTiles = rangeBaseOffset + curRelativeRangeOffset; + singlePage = false; + if(rangeFlags & D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE) + singlePage = true; + } + } + } + } + } + } } template @@ -62,7 +335,28 @@ bool WrappedID3D12CommandQueue::Serialise_CopyTileMappings( const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); + ID3D12CommandQueue *pQueue = this; + SERIALISE_ELEMENT(pQueue); + SERIALISE_ELEMENT(pDstResource); + SERIALISE_ELEMENT_LOCAL(DstRegionStartCoordinate, *pDstRegionStartCoordinate); + SERIALISE_ELEMENT(pSrcResource); + SERIALISE_ELEMENT_LOCAL(SrcRegionStartCoordinate, *pSrcRegionStartCoordinate); + SERIALISE_ELEMENT_LOCAL(RegionSize, *pRegionSize); + SERIALISE_ELEMENT(Flags); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + if(IsLoading(m_State)) + m_SparseBindResources.insert(GetResID(pDstResource)); + + // don't replay with NO_HAZARD + m_pReal->CopyTileMappings(Unwrap(pDstResource), &DstRegionStartCoordinate, Unwrap(pSrcResource), + &SrcRegionStartCoordinate, &RegionSize, + Flags & ~D3D12_TILE_MAPPING_FLAG_NO_HAZARD); + } + return true; } @@ -71,9 +365,69 @@ void STDMETHODCALLTYPE WrappedID3D12CommandQueue::CopyTileMappings( ID3D12Resource *pSrcResource, const D3D12_TILED_RESOURCE_COORDINATE *pSrcRegionStartCoordinate, const D3D12_TILE_REGION_SIZE *pRegionSize, D3D12_TILE_MAPPING_FLAGS Flags) { - D3D12NOTIMP("Tiled Resources"); - m_pReal->CopyTileMappings(Unwrap(pDstResource), pDstRegionStartCoordinate, Unwrap(pSrcResource), - pSrcRegionStartCoordinate, pRegionSize, Flags); + SERIALISE_TIME_CALL(m_pReal->CopyTileMappings(Unwrap(pDstResource), pDstRegionStartCoordinate, + Unwrap(pSrcResource), pSrcRegionStartCoordinate, + pRegionSize, Flags)); + + if(IsActiveCapturing(m_State)) + { + CACHE_THREAD_SERIALISER(); + ser.SetDrawChunk(); + SCOPED_SERIALISE_CHUNK(D3D12Chunk::Queue_CopyTileMappings); + Serialise_CopyTileMappings(ser, pDstResource, pDstRegionStartCoordinate, pSrcResource, + pSrcRegionStartCoordinate, pRegionSize, Flags); + + m_QueueRecord->AddChunk(scope.Get()); + GetResourceManager()->MarkResourceFrameReferenced(GetResID(pDstResource), eFrameRef_Read); + GetResourceManager()->MarkResourceFrameReferenced(GetResID(pSrcResource), eFrameRef_Read); + } + + // update our internal page tables + if(IsCaptureMode(m_State)) + { + Sparse::PageTable tmp; + + // if we're moving within a subresource the regions can overlap. Take a temporary copy for the + // source + if(pSrcResource == pDstResource) + tmp = *GetRecord(pSrcResource)->sparseTable; + + const Sparse::PageTable &srcPageTable = + (pSrcResource == pDstResource) ? tmp : *GetRecord(pSrcResource)->sparseTable; + Sparse::PageTable &dstPageTable = *GetRecord(pDstResource)->sparseTable; + + const UINT srcSub = pSrcRegionStartCoordinate->Subresource; + const UINT dstSub = pDstRegionStartCoordinate->Subresource; + + if(pRegionSize->UseBox) + { + D3D12_TILE_REGION_SIZE size = *pRegionSize; + + if(pRegionSize->Width == 0) + return; + + // clamp inputs that may be invalid for buffers or 2D to sensible values + size.Width = RDCCLAMP(1U, pRegionSize->Width, dstPageTable.getResourceSize().x); + size.Height = + (uint16_t)RDCCLAMP(1U, (uint32_t)pRegionSize->Height, dstPageTable.getResourceSize().y); + size.Depth = + (uint16_t)RDCCLAMP(1U, (uint32_t)pRegionSize->Depth, dstPageTable.getResourceSize().z); + + dstPageTable.copyImageBoxRange( + dstSub, + {pDstRegionStartCoordinate->X, pDstRegionStartCoordinate->Y, pDstRegionStartCoordinate->Z}, + {size.Width, size.Height, size.Depth}, srcPageTable, srcSub, + {pSrcRegionStartCoordinate->X, pSrcRegionStartCoordinate->Y, pSrcRegionStartCoordinate->Z}); + } + else + { + dstPageTable.copyImageWrappedRange( + dstSub, + {pDstRegionStartCoordinate->X, pDstRegionStartCoordinate->Y, pDstRegionStartCoordinate->Z}, + pRegionSize->NumTiles * 64 * 1024, srcPageTable, srcSub, + {pSrcRegionStartCoordinate->X, pSrcRegionStartCoordinate->Y, pSrcRegionStartCoordinate->Z}); + } + } } template @@ -387,7 +741,7 @@ void WrappedID3D12CommandQueue::ExecuteCommandListsInternal(UINT NumCommandLists m_Lock.Lock(); bool capframe = IsActiveCapturing(m_State); - std::set refdIDs; + std::unordered_set refdIDs; for(UINT i = 0; i < NumCommandLists; i++) { @@ -531,7 +885,8 @@ void WrappedID3D12CommandQueue::ExecuteCommandListsInternal(UINT NumCommandLists byte *ref = res->GetShadow(subres); byte *data = res->GetMap(subres); - // check we actually have map data. It's possible that over the course of the loop iteration + // check we actually have map data. It's possible that over the course of the loop + // iteration // the resource has been unmapped on another thread before we got here. if(data) { @@ -570,6 +925,76 @@ void WrappedID3D12CommandQueue::ExecuteCommandListsInternal(UINT NumCommandLists res->UnlockMaps(); } + std::unordered_set sparsePageHeaps; + std::unordered_set sparseResources; + + // this returns the list of current live sparse resources, and the list of heaps *that have + // ever been used for sparse binding*. The latter list may be way too big, in which case we + // look at the referenced sparse resources and pull in the heaps they are currently using. + // However many applications may use only a few large heaps for sparse binding so if the + // list + // is small enough then we just use it directly even if technically some heaps may not be + // used + // by any resources we are referencing. + m_pDevice->GetSparseResources(sparseResources, sparsePageHeaps); + + if(sparsePageHeaps.size() > refdIDs.size() || sparsePageHeaps.size() > sparseResources.size()) + { + // intersect sparse resources with ref'd IDs, and pull in the referenced heaps from its + // current page table + const std::unordered_set &smaller = + sparseResources.size() < refdIDs.size() ? sparseResources : refdIDs; + const std::unordered_set &larger = + sparseResources.size() >= refdIDs.size() ? sparseResources : refdIDs; + for(const ResourceId id : smaller) + { + if(larger.find(id) != larger.end()) + { + D3D12ResourceRecord *record = GetResourceManager()->GetResourceRecord(id); + RDCASSERT(record->sparseTable); + + const Sparse::PageTable &table = *record->sparseTable; + + for(uint32_t sub = 0; sub < RDCMAX(1U, table.getNumSubresources());) + { + const Sparse::PageRangeMapping &mapping = table.isSubresourceInMipTail(sub) + ? table.getMipTailMapping(sub) + : table.getSubresource(sub); + + if(mapping.hasSingleMapping()) + { + if(mapping.singleMapping.memory != ResourceId()) + sparsePageHeaps.insert(mapping.singleMapping.memory); + } + else + { + // this is a huge perf cliff as we've lost any batching and we perform as badly as + // if every page was mapped to a different resource, so we hope applications don't + // hit this often. + for(const Sparse::Page &page : mapping.pages) + { + sparsePageHeaps.insert(page.memory); + } + } + + if(table.isSubresourceInMipTail(sub)) + { + // move to the next subresource after the miptail, since we handle the miptail all + // at once + sub = ((sub / table.getMipCount()) + 1) * table.getMipCount(); + } + else + { + sub++; + } + } + } + } + } + + for(const ResourceId id : sparsePageHeaps) + GetResourceManager()->MarkResourceFrameReferenced(id, eFrameRef_Read); + { WriteSerialiser &ser = GetThreadSerialiser(); ser.SetDrawChunk(); @@ -858,7 +1283,8 @@ HRESULT STDMETHODCALLTYPE WrappedID3D12CommandQueue::Present( if(m_pPresentHWND != NULL) { - // don't let the device actually release any refs on the resource, just make it release internal + // don't let the device actually release any refs on the resource, just make it release + // internal // resources m_pPresentSource->AddRef(); m_pDevice->ReleaseSwapchainResources(this, 0, NULL, NULL); diff --git a/renderdoc/driver/d3d12/d3d12_common.cpp b/renderdoc/driver/d3d12/d3d12_common.cpp index d0e68b746..f2267e1e2 100644 --- a/renderdoc/driver/d3d12/d3d12_common.cpp +++ b/renderdoc/driver/d3d12/d3d12_common.cpp @@ -223,6 +223,10 @@ bool D3D12InitParams::IsSupportedVersion(uint64_t ver) if(ver == 0x9) return true; + // 0xA -> 0xB - Added support for sparse/reserved/tiled resources + if(ver == 0x9) + return true; + return false; } diff --git a/renderdoc/driver/d3d12/d3d12_common.h b/renderdoc/driver/d3d12/d3d12_common.h index c1a566ca0..53e7b9166 100644 --- a/renderdoc/driver/d3d12/d3d12_common.h +++ b/renderdoc/driver/d3d12/d3d12_common.h @@ -665,6 +665,9 @@ DECLARE_REFLECTION_ENUM(D3D12_SHADER_VISIBILITY); DECLARE_REFLECTION_ENUM(D3D12_STATIC_BORDER_COLOR); DECLARE_REFLECTION_ENUM(D3D12_DESCRIPTOR_RANGE_TYPE); DECLARE_REFLECTION_ENUM(D3D12_DESCRIPTOR_RANGE_FLAGS); +DECLARE_REFLECTION_ENUM(D3D12_TILE_COPY_FLAGS); +DECLARE_REFLECTION_ENUM(D3D12_TILE_RANGE_FLAGS); +DECLARE_REFLECTION_ENUM(D3D12_TILE_MAPPING_FLAGS); DECLARE_REFLECTION_STRUCT(D3D12_RESOURCE_DESC); DECLARE_REFLECTION_STRUCT(D3D12_COMMAND_QUEUE_DESC); diff --git a/renderdoc/driver/d3d12/d3d12_debug.cpp b/renderdoc/driver/d3d12/d3d12_debug.cpp index bd1182b2c..d9b94e7c4 100644 --- a/renderdoc/driver/d3d12/d3d12_debug.cpp +++ b/renderdoc/driver/d3d12/d3d12_debug.cpp @@ -1222,8 +1222,10 @@ void D3D12DebugManager::GetBufferData(ID3D12Resource *buffer, uint64_t offset, u return; D3D12_RESOURCE_DESC desc = buffer->GetDesc(); - D3D12_HEAP_PROPERTIES heapProps; - buffer->GetHeapProperties(&heapProps, NULL); + D3D12_HEAP_PROPERTIES heapProps = {}; + // can't call GetHeapProperties on sparse resources + if(!m_pDevice->IsSparseResource(GetResID(buffer))) + buffer->GetHeapProperties(&heapProps, NULL); if(offset >= desc.Width) { diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index 3ea79ae80..052c724ab 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -2626,6 +2626,12 @@ void WrappedID3D12Device::ReleaseResource(ID3D12DeviceChild *res) m_ResourceStates.erase(id); } + { + SCOPED_LOCK(m_SparseLock); + m_SparseResources.erase(id); + m_SparseHeaps.erase(id); + } + D3D12ResourceRecord *record = GetRecord(res); if(record) diff --git a/renderdoc/driver/d3d12/d3d12_device.h b/renderdoc/driver/d3d12/d3d12_device.h index 390a299dd..55169d152 100644 --- a/renderdoc/driver/d3d12/d3d12_device.h +++ b/renderdoc/driver/d3d12/d3d12_device.h @@ -52,7 +52,7 @@ struct D3D12InitParams uint32_t VendorUAVSpace = ~0U; // check if a frame capture section version is supported - static const uint64_t CurrentVersion = 0xA; + static const uint64_t CurrentVersion = 0xB; static bool IsSupportedVersion(uint64_t ver); }; @@ -612,6 +612,10 @@ private: Threading::CriticalSection m_MapsLock; rdcarray m_Maps; + Threading::CriticalSection m_SparseLock; + std::unordered_set m_SparseResources; + std::unordered_set m_SparseHeaps; + Threading::CriticalSection m_WrapDeduplicateLock; bool ProcessChunk(ReadSerialiser &ser, D3D12Chunk context); @@ -1049,6 +1053,25 @@ public: } void CheckForDeath(); + void GetSparseResources(std::unordered_set &resources, + std::unordered_set &heaps) + { + SCOPED_LOCK(m_SparseLock); + resources = m_SparseResources; + heaps = m_SparseHeaps; + } + bool IsSparseResource(ResourceId id) + { + SCOPED_LOCK_OPTIONAL(m_SparseLock, IsCaptureMode(m_State)); + return m_SparseResources.find(id) != m_SparseResources.end(); + } + + void AddSparseHeap(ResourceId heap) + { + SCOPED_LOCK(m_SparseLock); + m_SparseHeaps.insert(heap); + } + void ReleaseResource(ID3D12DeviceChild *pResource); // helper function that takes an expanded descriptor, but downcasts it to the regular descriptor diff --git a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp index c4a97dbff..35530681a 100644 --- a/renderdoc/driver/d3d12/d3d12_device_wrap.cpp +++ b/renderdoc/driver/d3d12/d3d12_device_wrap.cpp @@ -1817,8 +1817,102 @@ bool WrappedID3D12Device::Serialise_CreateReservedResource( SerialiserType &ser, const D3D12_RESOURCE_DESC *pDesc, D3D12_RESOURCE_STATES InitialState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) { - D3D12NOTIMP("Tiled Resources"); - APIProps.SparseResources = true; + SERIALISE_ELEMENT_LOCAL(Descriptor, *pDesc).Named("pDesc"_lit); + SERIALISE_ELEMENT(InitialState); + SERIALISE_ELEMENT_OPT(pOptimizedClearValue); + SERIALISE_ELEMENT_LOCAL(guid, riid).Named("riid"_lit); + SERIALISE_ELEMENT_LOCAL(pResource, ((WrappedID3D12Resource *)*ppvResource)->GetResourceID()) + .TypedAs("ID3D12Resource *"_lit); + + SERIALISE_ELEMENT_LOCAL(gpuAddress, + ((WrappedID3D12Resource *)*ppvResource)->GetGPUVirtualAddressIfBuffer()) + .Hidden(); + + SERIALISE_CHECK_READ_ERRORS(); + + if(IsReplayingAndReading()) + { + APIProps.SparseResources = true; + + if(Descriptor.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + GPUAddressRange range; + range.start = gpuAddress; + range.end = gpuAddress + Descriptor.Width; + range.id = pResource; + + m_GPUAddresses.AddTo(range); + } + + APIProps.YUVTextures |= IsYUVFormat(Descriptor.Format); + + // always allow SRVs on replay so we can inspect resources + Descriptor.Flags &= ~D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE; + + ID3D12Resource *ret = NULL; + HRESULT hr = m_pDevice->CreateReservedResource(&Descriptor, InitialState, pOptimizedClearValue, + guid, (void **)&ret); + + if(FAILED(hr)) + { + RDCERR("Failed on resource serialise-creation, HRESULT: %s", ToStr(hr).c_str()); + return false; + } + else + { + SetObjName(ret, + StringFormat::Fmt("Reserved Resource %s %s", ToStr(Descriptor.Dimension).c_str(), + ToStr(pResource).c_str())); + + ret = new WrappedID3D12Resource(ret, this); + + GetResourceManager()->AddLiveResource(pResource, ret); + + SubresourceStateVector &states = m_ResourceStates[GetResID(ret)]; + states.fill(GetNumSubresources(m_pDevice, &Descriptor), InitialState); + } + + m_SparseResources.insert(GetResID(ret)); + + ResourceType type = ResourceType::Texture; + const char *prefix = "Texture"; + + if(Descriptor.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + type = ResourceType::Buffer; + prefix = "Buffer"; + } + else if(Descriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE1D) + { + prefix = Descriptor.DepthOrArraySize > 1 ? "1D TextureArray" : "1D Texture"; + + if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) + prefix = "1D Render Target"; + else if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + prefix = "1D Depth Target"; + } + else if(Descriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D) + { + prefix = Descriptor.DepthOrArraySize > 1 ? "2D TextureArray" : "2D Texture"; + + if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) + prefix = "2D Render Target"; + else if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + prefix = "2D Depth Target"; + } + else if(Descriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D) + { + prefix = "3D Texture"; + + if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) + prefix = "3D Render Target"; + else if(Descriptor.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) + prefix = "3D Depth Target"; + } + + AddResource(pResource, type, prefix); + } + return true; } @@ -1827,8 +1921,125 @@ HRESULT WrappedID3D12Device::CreateReservedResource(const D3D12_RESOURCE_DESC *p const D3D12_CLEAR_VALUE *pOptimizedClearValue, REFIID riid, void **ppvResource) { - RDCERR("Tiled Resources are not currently implemented on D3D12"); - return E_NOINTERFACE; + if(ppvResource == NULL) + return m_pDevice->CreateReservedResource(pDesc, InitialState, pOptimizedClearValue, riid, NULL); + + if(riid != __uuidof(ID3D12Resource) && riid != __uuidof(ID3D12Resource1) && + riid != __uuidof(ID3D12Resource2)) + return E_NOINTERFACE; + + const D3D12_RESOURCE_DESC *pCreateDesc = pDesc; + D3D12_RESOURCE_DESC localDesc; + + if(pDesc && pDesc->Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && pDesc->SampleDesc.Count > 1) + { + localDesc = *pDesc; + // need to be able to create SRVs of MSAA textures to copy out their contents + localDesc.Flags &= ~D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE; + pCreateDesc = &localDesc; + } + + ID3D12Resource *real = NULL; + HRESULT ret; + SERIALISE_TIME_CALL( + ret = m_pDevice->CreateReservedResource(pCreateDesc, InitialState, pOptimizedClearValue, + __uuidof(ID3D12Resource), (void **)&real)); + + if(SUCCEEDED(ret)) + { + WrappedID3D12Resource *wrapped = new WrappedID3D12Resource(real, this); + + if(IsCaptureMode(m_State)) + { + CACHE_THREAD_SERIALISER(); + + SCOPED_SERIALISE_CHUNK(D3D12Chunk::Device_CreateReservedResource); + Serialise_CreateReservedResource(ser, pDesc, InitialState, pOptimizedClearValue, riid, + (void **)&wrapped); + + D3D12ResourceRecord *record = GetResourceManager()->AddResourceRecord(wrapped->GetResourceID()); + record->type = Resource_Resource; + record->Length = 0; + wrapped->SetResourceRecord(record); + + record->m_MapsCount = GetNumSubresources(this, pDesc); + record->m_Maps = new D3D12ResourceRecord::MapData[record->m_MapsCount]; + + const UINT pageSize = 64 * 1024; + + if(pDesc->Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + record->sparseTable = new Sparse::PageTable; + record->sparseTable->Initialise(pDesc->Width, pageSize); + } + else + { + D3D12_PACKED_MIP_INFO mipTail = {}; + D3D12_TILE_SHAPE tileShape = {}; + + m_pDevice->GetResourceTiling(wrapped->GetReal(), NULL, &mipTail, &tileShape, NULL, 0, NULL); + + UINT texDepth = 1; + UINT texSlices = pDesc->DepthOrArraySize; + if(pDesc->Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE3D) + { + texDepth = pDesc->DepthOrArraySize; + texSlices = 1; + } + + RDCASSERT(mipTail.NumStandardMips + mipTail.NumPackedMips == pDesc->MipLevels, + mipTail.NumStandardMips, mipTail.NumPackedMips, pDesc->MipLevels); + record->sparseTable = new Sparse::PageTable; + record->sparseTable->Initialise( + {(uint32_t)pDesc->Width, pDesc->Height, texDepth}, pDesc->MipLevels, texSlices, + pageSize, {tileShape.WidthInTexels, tileShape.HeightInTexels, tileShape.DepthInTexels}, + mipTail.NumStandardMips, mipTail.StartTileIndexInOverallResource * pageSize, + (mipTail.StartTileIndexInOverallResource + mipTail.NumTilesForPackedMips) * pageSize, + mipTail.NumTilesForPackedMips * pageSize * texSlices); + } + + { + SCOPED_LOCK(m_SparseLock); + m_SparseResources.insert(wrapped->GetResourceID()); + } + + record->AddChunk(scope.Get()); + + GetResourceManager()->MarkDirtyResource(wrapped->GetResourceID()); + } + else + { + GetResourceManager()->AddLiveResource(wrapped->GetResourceID(), wrapped); + } + + { + SCOPED_LOCK(m_ResourceStatesLock); + SubresourceStateVector &states = m_ResourceStates[wrapped->GetResourceID()]; + + states.fill(GetNumSubresources(m_pDevice, pDesc), InitialState); + } + + if(riid == __uuidof(ID3D12Resource)) + *ppvResource = (ID3D12Resource *)wrapped; + else if(riid == __uuidof(ID3D12Resource1)) + *ppvResource = (ID3D12Resource1 *)wrapped; + else if(riid == __uuidof(ID3D12Resource2)) + *ppvResource = (ID3D12Resource2 *)wrapped; + + // while actively capturing we keep all buffers around to prevent the address lookup from + // losing addresses we might need (or the manageable but annoying problem of an address being + // re-used) + { + SCOPED_READLOCK(m_CapTransitionLock); + if(IsActiveCapturing(m_State)) + { + wrapped->AddRef(); + m_RefBuffers.push_back(wrapped); + } + } + } + + return ret; } template @@ -2955,26 +3166,7 @@ HRESULT WrappedID3D12Device::CheckFeatureSupport(D3D12_FEATURE Feature, void *pF return hr; } - else if(Feature == D3D12_FEATURE_D3D12_OPTIONS) - { - HRESULT hr = m_pDevice->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); - if(SUCCEEDED(hr)) - { - D3D12_FEATURE_DATA_D3D12_OPTIONS *opts = - (D3D12_FEATURE_DATA_D3D12_OPTIONS *)pFeatureSupportData; - if(FeatureSupportDataSize != sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)) - return E_INVALIDARG; - - // renderdoc doesn't support tiled resources (calls to CreateReservedResource will fail), so - // don't report it as supported - opts->TiledResourcesTier = D3D12_TILED_RESOURCES_TIER_NOT_SUPPORTED; - - return S_OK; - } - - return hr; - } return m_pDevice->CheckFeatureSupport(Feature, pFeatureSupportData, FeatureSupportDataSize); } diff --git a/renderdoc/driver/d3d12/d3d12_initstate.cpp b/renderdoc/driver/d3d12/d3d12_initstate.cpp index c585b6ba5..3061ea919 100644 --- a/renderdoc/driver/d3d12/d3d12_initstate.cpp +++ b/renderdoc/driver/d3d12/d3d12_initstate.cpp @@ -59,10 +59,14 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res) D3D12_RESOURCE_DESC desc = r->GetDesc(); + D3D12InitialContents initContents; + if(desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) { - D3D12_HEAP_PROPERTIES heapProps; - r->GetHeapProperties(&heapProps, NULL); + D3D12_HEAP_PROPERTIES heapProps = {}; + + if(GetRecord(r)->sparseTable == NULL) + r->GetHeapProperties(&heapProps, NULL); HRESULT hr = S_OK; @@ -167,8 +171,7 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res) #endif } - SetInitialContents(GetResID(r), D3D12InitialContents(copyDst)); - return true; + initContents = D3D12InitialContents(copyDst); } else { @@ -364,9 +367,16 @@ bool D3D12ResourceManager::Prepare_InitialState(ID3D12DeviceChild *res) SAFE_RELEASE(arrayTexture); SAFE_DELETE_ARRAY(layouts); - SetInitialContents(GetResID(r), D3D12InitialContents(copyDst)); - return true; + initContents = D3D12InitialContents(copyDst); } + + if(GetRecord(r)->sparseTable) + { + initContents.sparseTable = new Sparse::PageTable(*GetRecord(r)->sparseTable); + } + + SetInitialContents(GetResID(r), initContents); + return true; } else { @@ -394,7 +404,13 @@ uint64_t D3D12ResourceManager::GetSize_InitialState(ResourceId id, const D3D12In if(data.tag == D3D12InitialContents::MapDirect) return WriteSerialiser::GetChunkAlignment() + 16 + uint64_t(data.dataSize); - return WriteSerialiser::GetChunkAlignment() + 16 + uint64_t(buf ? buf->GetDesc().Width : 0); + uint64_t ret = + WriteSerialiser::GetChunkAlignment() + 16 + uint64_t(buf ? buf->GetDesc().Width : 0); + + if(data.sparseTable) + ret += 16 + data.sparseTable->GetSerialiseSize(); + + return ret; } else { @@ -404,6 +420,121 @@ uint64_t D3D12ResourceManager::GetSize_InitialState(ResourceId id, const D3D12In return 16; } +SparseBinds::SparseBinds(const Sparse::PageTable &table) +{ + const uint32_t pageSize = 64 * 1024; + + // in theory some of these subresources may share a single binding but we don't try to extract + // that out again. If we can get one bind per subresource and avoid falling down to per-page + // mappings we're happy + for(uint32_t sub = 0; sub < RDCMAX(1U, table.getNumSubresources());) + { + const Sparse::PageRangeMapping &mapping = + table.isSubresourceInMipTail(sub) ? table.getMipTailMapping(sub) : table.getSubresource(sub); + + if(mapping.hasSingleMapping()) + { + Bind bind; + bind.heap = mapping.singleMapping.memory; + bind.rangeOffset = uint32_t(mapping.singleMapping.offset / pageSize); + bind.rangeCount = uint32_t(table.isSubresourceInMipTail(sub) + ? table.getMipTailSliceSize() / pageSize + : table.getSubresourceByteSize(sub) / pageSize); + bind.regionStart = {0, 0, 0, sub}; + bind.regionSize = {bind.rangeCount, FALSE, bind.rangeCount, 1, 1}; + bind.rangeFlag = D3D12_TILE_RANGE_FLAG_NONE; + if(bind.heap == ResourceId()) + bind.rangeFlag = D3D12_TILE_RANGE_FLAG_NULL; + else if(mapping.singlePageReused) + bind.rangeFlag = D3D12_TILE_RANGE_FLAG_REUSE_SINGLE_TILE; + binds.push_back(bind); + } + else + { + Sparse::Coord texelShape = table.calcSubresourcePageDim(sub); + + // march the pages for this subresource in linear order + for(uint32_t page = 0; page < mapping.pages.size(); page++) + { + Bind bind; + bind.heap = mapping.pages[page].memory; + bind.rangeOffset = uint32_t(mapping.pages[page].offset / pageSize); + + // do simple coalescing. If the previous bind was in the same heap, one tile back, make it + // cover this tile + if(page > 0 && binds.back().heap == bind.heap && + binds.back().rangeOffset + binds.back().rangeCount == bind.rangeOffset) + { + binds.back().regionSize.NumTiles++; + binds.back().regionSize.Width++; + binds.back().rangeCount++; + continue; + } + + // otherwise add a new bind + if(table.isSubresourceInMipTail(sub)) + { + bind.regionStart = {page, 0, 0, sub}; + } + else + { + bind.regionStart.Subresource = sub; + // set the starting co-ord as appropriate for this page + bind.regionStart.X = page % texelShape.x; + bind.regionStart.Y = (page / texelShape.x) % texelShape.y; + bind.regionStart.Z = page / (texelShape.x * texelShape.y); + } + + bind.rangeCount = 1; + bind.regionSize = {1, FALSE, 1, 1, 1}; + bind.rangeFlag = D3D12_TILE_RANGE_FLAG_NONE; + if(bind.heap == ResourceId()) + bind.rangeFlag = D3D12_TILE_RANGE_FLAG_NULL; + + binds.push_back(bind); + } + } + + if(table.isSubresourceInMipTail(sub)) + { + // move to the next subresource after the miptail, since we handle the miptail all at once + sub = ((sub / table.getMipCount()) + 1) * table.getMipCount(); + } + else + { + sub++; + } + } +} + +SparseBinds::SparseBinds(int) +{ + null = true; +} + +void SparseBinds::Apply(WrappedID3D12Device *device, ID3D12Resource *resource) +{ + if(null) + { + D3D12_TILE_RANGE_FLAGS rangeFlags = D3D12_TILE_RANGE_FLAG_NULL; + + // do a single whole-resource bind of NULL + device->GetQueue()->UpdateTileMappings(Unwrap(resource), 1, NULL, NULL, NULL, 1, &rangeFlags, + NULL, NULL, D3D12_TILE_MAPPING_FLAG_NONE); + } + else + { + D3D12ResourceManager *rm = device->GetResourceManager(); + for(const Bind &bind : binds) + { + device->GetQueue()->UpdateTileMappings( + resource, 1, &bind.regionStart, &bind.regionSize, + bind.heap == ResourceId() ? NULL : (ID3D12Heap *)rm->GetLiveResource(bind.heap), 1, + &bind.rangeFlag, &bind.rangeOffset, &bind.rangeCount, D3D12_TILE_MAPPING_FLAG_NONE); + } + } +} + template bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId id, D3D12ResourceRecord *record, @@ -494,6 +625,18 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI liveRes = (ID3D12Resource *)GetLiveResource(id); } + SparseBinds *sparseBinds = NULL; + + if(ser.VersionAtLeast(0xB)) + { + Sparse::PageTable *sparseTable = initial ? initial->sparseTable : NULL; + + SERIALISE_ELEMENT_OPT(sparseTable); + + if(sparseTable) + sparseBinds = new SparseBinds(*sparseTable); + } + if(ser.IsWriting()) { m_Device->ExecuteLists(NULL, true); @@ -536,7 +679,8 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI D3D12_RESOURCE_DESC resDesc = liveRes->GetDesc(); D3D12_HEAP_PROPERTIES heapProps = {}; - liveRes->GetHeapProperties(&heapProps, NULL); + if(!m_Device->IsSparseResource(GetResID(liveRes))) + liveRes->GetHeapProperties(&heapProps, NULL); if(heapProps.Type == D3D12_HEAP_TYPE_UPLOAD) { @@ -624,6 +768,8 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI initContents.resourceType = Resource_Resource; initContents.resource = mappedBuffer; + initContents.sparseBinds = sparseBinds; + D3D12_RESOURCE_DESC resDesc = liveRes->GetDesc(); // for MSAA textures we upload to an MSAA texture here so we're ready to copy the image in @@ -639,7 +785,8 @@ bool D3D12ResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceI else { D3D12_HEAP_PROPERTIES heapProps = {}; - liveRes->GetHeapProperties(&heapProps, NULL); + if(!m_Device->IsSparseResource(GetResID(liveRes))) + liveRes->GetHeapProperties(&heapProps, NULL); ID3D12GraphicsCommandList *list = Unwrap(m_Device->GetInitialStateList()); @@ -806,7 +953,8 @@ void D3D12ResourceManager::Create_InitialState(ResourceId id, ID3D12DeviceChild D3D12_RESOURCE_DESC resDesc = res->GetDesc(); D3D12_HEAP_PROPERTIES heapProps = {}; - res->GetHeapProperties(&heapProps, NULL); + if(!m_Device->IsSparseResource(GetResID(live))) + res->GetHeapProperties(&heapProps, NULL); if(heapProps.Type == D3D12_HEAP_TYPE_UPLOAD) { @@ -855,6 +1003,10 @@ void D3D12ResourceManager::Create_InitialState(ResourceId id, ID3D12DeviceChild D3D12InitialContents initContents(D3D12InitialContents::ForceCopy, type); initContents.resourceType = Resource_Resource; initContents.resource = copy; + + if(m_Device->IsSparseResource(GetResID(live))) + initContents.sparseBinds = new SparseBinds(0); + SetInitialContents(id, initContents); } } @@ -896,7 +1048,15 @@ void D3D12ResourceManager::Apply_InitialState(ID3D12DeviceChild *live, } D3D12_HEAP_PROPERTIES heapProps = {}; - copyDst->GetHeapProperties(&heapProps, NULL); + if(data.sparseBinds) + { + if(IsLoading(m_State) || m_Device->GetQueue()->IsSparseUpdatedResource(GetResID(live))) + data.sparseBinds->Apply(m_Device, (ID3D12Resource *)live); + } + else + { + copyDst->GetHeapProperties(&heapProps, NULL); + } // if destination is on the upload heap, it's impossible to copy via the device, // so we have to CPU copy. We assume that we detected this case above and never uploaded a diff --git a/renderdoc/driver/d3d12/d3d12_manager.h b/renderdoc/driver/d3d12/d3d12_manager.h index 10a642128..37af55025 100644 --- a/renderdoc/driver/d3d12/d3d12_manager.h +++ b/renderdoc/driver/d3d12/d3d12_manager.h @@ -27,6 +27,7 @@ #include "common/wrapped_pool.h" #include "core/core.h" #include "core/resource_manager.h" +#include "core/sparse_page_table.h" #include "driver/d3d12/d3d12_common.h" #include "serialise/serialiser.h" @@ -493,6 +494,7 @@ struct D3D12ResourceRecord : public ResourceRecord type(Resource_Unknown), ContainsExecuteIndirect(false), cmdInfo(NULL), + sparseTable(NULL), m_Maps(NULL), m_MapsCount(0), bakedCommands(NULL) @@ -501,6 +503,7 @@ struct D3D12ResourceRecord : public ResourceRecord ~D3D12ResourceRecord() { SAFE_DELETE(cmdInfo); + SAFE_DELETE(sparseTable); SAFE_DELETE_ARRAY(m_Maps); } void Bake() @@ -517,6 +520,7 @@ struct D3D12ResourceRecord : public ResourceRecord bool ContainsExecuteIndirect; D3D12ResourceRecord *bakedCommands; CmdListRecordingInfo *cmdInfo; + Sparse::PageTable *sparseTable; struct MapData { @@ -533,6 +537,29 @@ struct D3D12ResourceRecord : public ResourceRecord typedef rdcarray SubresourceStateVector; +struct SparseBinds +{ + SparseBinds(const Sparse::PageTable &table); + // tagged constructor meaning 'null binds everywhere' + SparseBinds(int); + + void Apply(WrappedID3D12Device *device, ID3D12Resource *resource); + +private: + bool null = false; + + struct Bind + { + ResourceId heap; + D3D12_TILED_RESOURCE_COORDINATE regionStart; + D3D12_TILE_REGION_SIZE regionSize; + D3D12_TILE_RANGE_FLAGS rangeFlag; + UINT rangeOffset; + UINT rangeCount; + }; + rdcarray binds; +}; + struct D3D12InitialContents { enum Tag @@ -551,7 +578,9 @@ struct D3D12InitialContents numDescriptors(n), resource(NULL), srcData(NULL), - dataSize(0) + dataSize(0), + sparseTable(NULL), + sparseBinds(NULL) { } D3D12InitialContents(ID3D12DescriptorHeap *r) @@ -561,7 +590,9 @@ struct D3D12InitialContents numDescriptors(0), resource(r), srcData(NULL), - dataSize(0) + dataSize(0), + sparseTable(NULL), + sparseBinds(NULL) { } D3D12InitialContents(ID3D12Resource *r) @@ -571,7 +602,9 @@ struct D3D12InitialContents numDescriptors(0), resource(r), srcData(NULL), - dataSize(0) + dataSize(0), + sparseTable(NULL), + sparseBinds(NULL) { } D3D12InitialContents(byte *data, size_t size) @@ -581,7 +614,9 @@ struct D3D12InitialContents numDescriptors(0), resource(NULL), srcData(data), - dataSize(size) + dataSize(size), + sparseTable(NULL), + sparseBinds(NULL) { } D3D12InitialContents(Tag tg, D3D12ResourceType type) @@ -591,7 +626,9 @@ struct D3D12InitialContents numDescriptors(0), resource(NULL), srcData(NULL), - dataSize(0) + dataSize(0), + sparseTable(NULL), + sparseBinds(NULL) { } D3D12InitialContents() @@ -601,13 +638,16 @@ struct D3D12InitialContents numDescriptors(0), resource(NULL), srcData(NULL), - dataSize(0) + dataSize(0), + sparseTable(NULL), + sparseBinds(NULL) { } template void Free(ResourceManager *rm) { SAFE_DELETE_ARRAY(descriptors); + SAFE_DELETE(sparseTable); SAFE_RELEASE(resource); FreeAlignedBuffer(srcData); } @@ -619,6 +659,11 @@ struct D3D12InitialContents ID3D12DeviceChild *resource; byte *srcData; size_t dataSize; + + // only valid on capture - the snapshotted table at prepare time + Sparse::PageTable *sparseTable; + // only valid on replay, the table above converted into a set of binds + SparseBinds *sparseBinds; }; struct D3D12ResourceManagerConfiguration diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index 2a6ee9b5a..c0afe8b54 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -1005,7 +1005,6 @@ public: // replay interface bool Prepare_InitialState(WrappedVkRes *res); uint64_t GetSize_InitialState(ResourceId id, const VkInitialContents &initial); - uint64_t GetSize_SparseInitialState(ResourceId id, const VkInitialContents &initial); template bool Serialise_InitialState(SerialiserType &ser, ResourceId id, VkResourceRecord *record, const VkInitialContents *initial); diff --git a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp index dd83a9f28..5ba00e1e7 100644 --- a/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp +++ b/renderdoc/driver/vulkan/wrappers/vk_queue_funcs.cpp @@ -840,7 +840,7 @@ void WrappedVulkan::CaptureQueueSubmit(VkQueue queue, bool capframe = IsActiveCapturing(m_State); bool backframe = IsBackgroundCapturing(m_State); - std::set refdIDs; + std::unordered_set refdIDs; std::set descriptorSets;