/****************************************************************************** * 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 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 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 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 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 m_Subresources; MipTail m_MipTail; template 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);