Files
renderdoc/renderdoc/core/sparse_page_table.h
T
2023-02-01 12:23:32 +00:00

309 lines
14 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2021-2023 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#pragma once
#include "api/replay/data_types.h"
#include "api/replay/rdcarray.h"
#include "api/replay/rdcpair.h"
#include "api/replay/resourceid.h"
#include "api/replay/stringise.h"
namespace Sparse
{
class PageTable;
}; // namespace Sparse
// we pre-declare this function so we can make it a friend inside the PageTable implementation
template <class SerialiserType>
void DoSerialise(SerialiserType &ser, Sparse::PageTable &el);
namespace Sparse
{
// used for co-ordinates as well as dimensions
struct Coord
{
uint32_t x, y, z;
bool operator==(const Coord &o) const { return x == o.x && y == o.y && z == o.z; }
};
struct Page
{
ResourceId memory;
uint64_t offset;
bool operator==(const Page &o) const { return memory == o.memory && offset == o.offset; }
};
struct PageRangeMapping
{
bool hasSingleMapping() const { return pages.empty(); }
// the memory mapping if there's a single mapping. Only valid if pages below is empty
Page singleMapping;
// since with a single mapping we only store the 'base' page, we need an additional bool to
// indicate if it's a single page re-used or if it's all subsequent pages used.
bool singlePageReused = false;
// the memory mappings per-page if there are different mappings per-page
rdcarray<Page> pages;
bool isMapped() const { return !pages.empty() || singleMapping.memory != ResourceId(); }
void simplifyUnmapped()
{
// if we're already using singleMapping, don't check anything
if(pages.empty())
return;
// if we find a single page with memory mapped, we're not entirely unmapped
for(size_t i = 0; i < pages.size(); i++)
if(pages[i].memory != ResourceId())
return;
// we're entirely unmapped - revert back to a single page mapping
pages.clear();
singleMapping = Page();
singlePageReused = false;
}
Page getPage(uint32_t idx, uint32_t pageSize) const
{
if(pages.empty())
{
if(singlePageReused)
return singleMapping;
Page ret = singleMapping;
ret.offset += pageSize * idx;
return ret;
}
return pages[idx];
}
void createPages(uint32_t numPages, uint32_t pageSize);
};
struct MipTail
{
// the first mip that is in the mip tail
uint32_t firstMip = 0;
// the offset in bytes for the mip tail.
uint64_t byteOffset = 0;
// the stride in bytes for the mip tail between each array slice's mip stride. This is set to 0
// if there is only one mip tail
uint64_t byteStride = 0;
// the size in bytes for the mip tail
uint64_t totalPackedByteSize = 0;
// the pages for the mip tail (or buffer pages for buffers). If byteStride is 0 this is the
// single set of pages for all array slices. If byteStride is non-zero then the first N pages
// (with N = byteStride / pageByteSize) are for slice 0, the next N for slice 1, etc.
rdcarray<PageRangeMapping> mappings;
};
// shadows the page table for a sparse resource - buffer or texture - and handles updates and
// retrieval. Currently the system is simple - we first store a page mapping per subresource. This
// should hopefully cover most applications and avoids needing to allocate, update, and store/apply
// a whole page table. If we detect a partial update we allocate a full page table and store the
// mapping for each page.
class PageTable
{
public:
// initialise the page table for a buffer. We just need to know its size and the page size
void Initialise(uint64_t bufferByteSize, uint32_t pageByteSize);
// initialise the page table for a texture. We specify various properties about the texture and
// its page shape/size and mip tail.
// The mip tail starts at firstTailMip - if this is >= numMips then there is no mip tail.
// mipTailOffset is an arbitrary offset which all mip tail bindings are relative to. It does
// nothing but rebase the offset provided setMipTailRange.
// mipTailStride is the byte stride between mip tails if they are stored separately per slice in
// an array texture. Again this is arbitrary to offset the binding resource offset. If the stride
// is 0 this means all slices have their mip tails consecutively packed.
// mipTailTotalPackedSize is the *total packed size of the mip tails*. This can be calculated with
// the size of one array slice's mip tail multiplied by the number of array slices. If the stride
// is 0 this is the size of the whole mip tail of the resource.
void Initialise(const Coord &overallTexelDim, uint32_t numMips, uint32_t numArraySlices,
uint32_t pageByteSize, const Coord &pageTexelDim, uint32_t firstTailMip,
uint64_t mipTailOffset, uint64_t mipTailStride, uint64_t mipTailTotalPackedSize);
uint64_t GetSerialiseSize() const;
inline uint32_t getPageByteSize() const { return m_PageByteSize; }
inline Coord getPageTexelSize() const { return m_PageTexelSize; }
inline Coord getResourceSize() const { return m_TextureDim; }
// useful for D3D where the mip tail is indexed by subresource/array slice even if we treat it all
// as one
inline uint64_t getMipTailByteOffsetForSubresource(uint32_t subresource) const
{
const uint32_t arraySlice = (subresource / m_MipCount) % m_ArraySize;
return m_MipTail.byteOffset + m_MipTail.byteStride * arraySlice;
}
inline uint32_t calcSubresource(uint32_t arraySlice, uint32_t mipLevel) const
{
return arraySlice * m_MipCount + mipLevel;
}
Coord calcSubresourcePageDim(uint32_t subresource) const;
// is this subresource in the mip tail
inline bool isSubresourceInMipTail(uint32_t subresource) const
{
const uint32_t mipLevel = subresource % m_MipCount;
return mipLevel >= m_MipTail.firstMip;
}
// is this byte offset in the resource (according to the mip tail - no other offsets are known)
inline bool isByteOffsetInResource(uint64_t byteOffset) const
{
const uint64_t mipTailSize = m_MipTail.byteStride == 0 ? m_MipTail.totalPackedByteSize
: m_MipTail.byteStride * m_ArraySize;
return byteOffset >= m_MipTail.byteOffset && byteOffset < m_MipTail.byteOffset + mipTailSize;
}
// read-only accessors to get current state
uint32_t getNumSubresources() const { return (uint32_t)m_Subresources.size(); }
uint32_t getArraySize() const { return m_ArraySize; }
uint32_t getMipCount() const { return m_MipCount; }
const PageRangeMapping &getSubresource(uint32_t subresource) const
{
return m_Subresources[subresource];
}
const PageRangeMapping &getPageRangeMapping(uint32_t subresource) const
{
if(isSubresourceInMipTail(subresource))
return getMipTailMapping(subresource);
else
return getSubresource(subresource);
}
const MipTail &getMipTail() const { return m_MipTail; }
const PageRangeMapping &getMipTailMapping(uint32_t subresource) const
{
const uint32_t arraySlice = (subresource / m_MipCount) % m_ArraySize;
return m_MipTail.mappings[arraySlice];
}
uint64_t getMipTailSliceSize() const { return m_MipTail.totalPackedByteSize / m_ArraySize; }
uint64_t getSubresourceByteSize(uint32_t subresource) const
{
const Coord subresourcePageDim = calcSubresourcePageDim(subresource);
return subresourcePageDim.x * subresourcePageDim.y * subresourcePageDim.z * m_PageByteSize;
}
// set a contiguous range of pages, with offsets and sizes applied in bytes.
// This is when you are setting XYZ resource pages to point to ABC memory pages.
// useSinglePage means only one page of memory will be used for all pages in the resource. Think
// of the case of mapping a single 'black' page to large areas of the resource, or NULL'ing out
// mappings.
// as a convenience it returns the resource offset where it finishes, since D3D allows
// mismatched boundaries for tile ranges and memory regions
uint64_t setMipTailRange(uint64_t resourceByteOffset, ResourceId memory,
uint64_t memoryByteOffset, uint64_t byteSize, bool useSinglePage);
inline uint64_t setBufferRange(uint64_t resourceByteOffset, ResourceId memory,
uint64_t memoryByteOffset, uint64_t byteSize, bool useSinglePage)
{
return setMipTailRange(resourceByteOffset, memory, memoryByteOffset, byteSize, useSinglePage);
}
// set a 3D box of texel pages to a range of memory.
// useSinglePage means only one page of memory will be used for all pages in the resource. Think
// of the case of mapping a single 'black' page to large areas of the resource, or NULL'ing out
// mappings.
void setImageBoxRange(uint32_t subresource, const Coord &coord, const Coord &dim,
ResourceId memory, uint64_t memoryByteOffset, bool useSinglePage);
// set a series of tiles in x, y, z order starting at a given point and wrapping around the
// overall image dimensions. In theory you could e.g. map 10 pages starting at page 6, when there
// are only 8 pages to a row. It would update pages 6 and 7 in the first row then all pages in the
// next row. This also allows wrapping from one subresource to the next
// we accept a byte size for consistency with all other calls that work in bytes not pages, even
// though this is only expected to be used by D3D which has this wrapping update operation and it
// operates in pages - the calling code can mutiply by getPageByteSize().
// useSinglePage means only one page of memory will be used for all pages in the resource. Think
// of the case of mapping a single 'black' page to large areas of the resource, or NULL'ing out
// mappings
// as a convenience it returns the co-ordinate and subresource where it finishes, since D3D allows
// mismatched boundaries for tile ranges and memory regions. This interacts with the
// updateMappings parameter which can skip applying any bindings and only advances the coord
// 'cursor'
rdcpair<uint32_t, Coord> setImageWrappedRange(uint32_t dstSubresource, const Coord &coord,
uint64_t byteSize, ResourceId memory,
uint64_t memoryByteOffset, bool useSinglePage,
bool updateMappings = true);
// copy pages from another page table, in D3D fashion. These take co-ordinates and dimensions in
// tiles because copying between textures with different tile shapes seems possible and if we
// expect inputs in texels we have to specify which page table's texel dimensions we're using,
// which is probably more error prone than breaking the convention of not accepting parameters in
// tiles.
void copyImageBoxRange(uint32_t dstSubresource, const Coord &coordInTiles,
const Coord &dimInTiles, const PageTable &srcPageTable,
uint32_t srcSubresource, const Coord &srcCoordInTiles);
void copyImageWrappedRange(uint32_t subresource, const Coord &coordInTiles, uint64_t numTiles,
const PageTable &srcPageTable, uint32_t srcSubresource,
const Coord &srcCoordInTiles);
private:
PageRangeMapping &getMipTailMapping(uint32_t subresource)
{
const uint32_t arraySlice = (subresource / m_MipCount) % m_ArraySize;
return m_MipTail.mappings[arraySlice];
}
// The image dimensions that we need. We don't care about the format or anything, we just need to
// know the size in pages and the subresource setup
Coord m_TextureDim = {};
uint32_t m_MipCount = 1;
uint32_t m_ArraySize = 1;
// the byte size of a page, constant over a resource
uint32_t m_PageByteSize = 0;
// the size of a page in texels
Coord m_PageTexelSize = {};
// the page tables for each subresource, if this is an image. Note for buffers everything goes in
// the "mipTail".
// For simplicity and robustness of access every subresource has an entry here, even those
// corresponding to mips that are in mip tails - the overhead is nominal with each entry being
// 5*ptrsize = 40 bytes.
rdcarray<PageRangeMapping> m_Subresources;
MipTail m_MipTail;
template <typename SerialiserType>
friend void ::DoSerialise(SerialiserType &ser, PageTable &el);
};
}; // namespace Sparse
DECLARE_REFLECTION_STRUCT(Sparse::Coord);
DECLARE_REFLECTION_STRUCT(Sparse::Page);
DECLARE_REFLECTION_STRUCT(Sparse::PageRangeMapping);
DECLARE_REFLECTION_STRUCT(Sparse::MipTail);
DECLARE_REFLECTION_STRUCT(Sparse::PageTable);