GsVram class
#include <nCine/Graphics/RHI/GS/GsVram.h>
Page allocator for the Graphics Synthesizer's 4 MB of local memory.
The GS has no memory allocator of its own: a buffer is simply a base address written into a register (TEX0.TBP0 for a texture, FRAME.FBP for a render target), and it is the program's job to make sure two buffers never overlap. This class is that bookkeeping. It hands out pages, and only pages.
Why the granularity is a whole page
Local memory is organised as 512 pages of 8 KB, each page 32 blocks of 256 bytes. Within a page the mapping from a texel coordinate to a block is a swizzle that depends on the pixel storage mode - PSMT8 and PSMCT32 order the blocks of a page differently, and each mode covers a different number of texels per page (see GetPageGeometry()). Two buffers of different modes that share a page therefore interleave unpredictably and corrupt each other, which is a failure mode the Dreamcast's byte-granular pvr_mem_malloc() simply does not have.
Allocating whole pages removes the hazard by construction rather than by care: if no page is ever shared by two buffers, no page can ever mix two storage modes, and the swizzle stays a private matter inside each allocation. The cost is internal fragmentation of up to one page per texture - and for the sheets this engine actually loads it is usually zero, because a power-of-two sheet at least as large as one page in each direction covers an exact number of pages (a 256x256 PSMT8 sheet is exactly 8).
Placement
Every request takes exactly the pages it needs, placed in the shortest free run long enough to hold it (best fit, lowest address first).
It used to round the request up to a power of two and align it to its own size, which buys a buddy allocator's freedom from external fragmentation. That trade is wrong for a cache this small: the rounding costs up to twice the pages a buffer actually needs, and the page counts it was assumed to be free for - power-of-two sheets - are only half the content. The stores that are not power-of-two are exactly the large ones, where the waste is measured in hundreds of kilobytes: a 640x448 PSMCT32 cinematic frame is 140 pages and was rounded to 256, throwing away 928 KB of a 3.3 MB cache, and a level's own mix of sheets lost about a fifth of the window to the same rounding. External fragmentation costs nothing like that here, because it is not fatal: a request that cannot be placed makes the caller evict and retry (see Residency), and best fit keeps the long runs the big atlases need intact by consuming the short ones first.
Residency
This class only places and frees; it does not decide what should be resident. That is the caller's job - GsTexture keeps the same most-recently-used list, per-frame stamp and host-store rebuild the PVR backend uses, and calls AllocatePages() through it. A failed allocation is reported, not fatal: the caller evicts and retries, exactly as PvrTexture::AllocateVram() does.
Public static variables
-
static std::
uint32_t BlockBytes constexpr - Size of one block, the unit of
TEX0.TBP0andTEX0.CBP. -
static std::
uint32_t PageBytes constexpr - Size of one page, the unit of
FRAME.FBP. -
static std::
uint32_t BlocksPerPage constexpr - Blocks per page.
-
static std::
uint32_t TotalBytes constexpr - Total local memory of the Graphics Synthesizer.
-
static std::
uint32_t TotalPages constexpr - Total pages of local memory.
-
static std::
uint32_t TotalBlocks constexpr - Total blocks of local memory.
-
static std::
int32_t BufferWidthUnit constexpr - Texels of buffer width one unit of
TEX0.TBW/FRAME.FBWstands for. -
static std::
uint32_t ClutSlotBytes constexpr - Bytes of one CLUT slot (256 entries of 32 bits).
-
static std::
uint32_t InvalidPage constexpr - Returned by the allocators when the request could not be placed.
-
static std::
uint32_t InvalidBlock constexpr - Returned by AllocateClut() when the slab is full.
Public static functions
-
static void GetPageGeometry(GsPsm psm,
std::
int32_t& width, std:: int32_t& height) - Returns the number of texels one page covers horizontally and vertically in
psm. -
static auto GetPageCount(GsPsm psm,
std::
int32_t width, std:: int32_t height) -> std:: uint32_t - Returns the pages a
widthxheightbuffer ofpsmoccupies. -
static auto GetBufferWidth(GsPsm psm,
std::
int32_t width) -> std:: int32_t - Returns the
TBW/FBWvalue for a bufferwidthtexels wide inpsm. -
static auto GetPaddedWidth(GsPsm psm,
std::
int32_t width) -> std:: int32_t - Returns the buffer pitch of a
widthtexel wide buffer ofpsm, in texels. - static auto Initialize(const GsVramLayout& layout) -> bool
- Places the static regions of
layoutand gives the rest to the texture cache. - static auto IsInitialized() -> bool
- Returns
trueonce Initialize() has succeeded. -
static auto GetDisplayBufferPage(std::
int32_t index) -> std:: uint32_t - Returns the first page of display buffer
index(FRAME.FBPis a page number). -
static auto GetDisplayBufferWidth() -> std::
int32_t - Returns the
FBWof the display buffers. - static auto GetDisplayPsm() -> GsPsm
- Returns the pixel storage mode of the display buffers.
-
static auto AllocatePages(std::
uint32_t pageCount) -> std:: uint32_t - Allocates exactly
pageCountpages from the texture cache. -
static void FreePages(std::
uint32_t firstPage, std:: uint32_t pageCount) - Returns a run obtained from AllocatePages() to the cache.
-
static auto AllocateReservedPages(std::
uint32_t pageCount) -> std:: uint32_t - Allocates pages from the render-target reserve.
-
static void FreeReservedPages(std::
uint32_t firstPage, std:: uint32_t pageCount) - Returns a run obtained from AllocateReservedPages().
-
static auto AllocateClut() -> std::
uint32_t - Allocates one CLUT slot, returning its block address (the unit of
TEX0.CBP). -
static void FreeClut(std::
uint32_t block) - Returns a CLUT slot obtained from AllocateClut().
-
static auto GetCacheFirstPage() -> std::
uint32_t - Returns the first page of the texture cache.
-
static auto GetCachePageCount() -> std::
uint32_t - Returns the number of pages in the texture cache.
-
static auto GetUsedPageCount() -> std::
uint32_t - Returns the number of texture-cache pages currently allocated.
-
static auto GetPeakUsedPageCount() -> std::
uint32_t - Returns the high-water mark of GetUsedPageCount().
-
static auto GetLargestFreeRun() -> std::
uint32_t - Returns the length of the longest free run in the texture cache.
-
static auto GetFailedAllocationCount() -> std::
uint32_t - Returns how many allocations have failed since Initialize().
Function documentation
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: GetPageCount(GsPsm psm,
std:: int32_t width,
std:: int32_t height)
Returns the pages a width x height buffer of psm occupies.
Both dimensions are rounded up to the page geometry of the storage mode, so the result is the number of whole pages the buffer's addressing can reach - which is what has to be reserved.
static std:: int32_t nCine:: RHI:: GS:: GsVram:: GetBufferWidth(GsPsm psm,
std:: int32_t width)
Returns the TBW/FBW value for a buffer width texels wide in psm.
The buffer pitch is padded to the page geometry like GetPageCount() pads it, because the pages behind it were reserved for the padded pitch. Note that a padded pitch costs no texture coordinate compensation the way the PowerVR's power-of-two padding does: TBW is the pitch and TW/TH are the sampled extent, and the two are independent fields.
static std:: int32_t nCine:: RHI:: GS:: GsVram:: GetPaddedWidth(GsPsm psm,
std:: int32_t width)
Returns the buffer pitch of a width texel wide buffer of psm, in texels.
The same padded pitch GetBufferWidth() reports, but not divided into TBW units. Both forms are needed and they are easy to confuse: the raw register field wants TBW, while PS2SDK's libdraw takes texbuffer_t::width/framebuffer_t::width in texels and divides by 64 itself. Handing it a TBW value silently samples a 64-times-too-narrow buffer.
static bool nCine:: RHI:: GS:: GsVram:: Initialize(const GsVramLayout& layout)
Places the static regions of layout and gives the rest to the texture cache.
Resets every allocation, so it is also how a test starts from a known state. Returns false if the static regions alone do not fit in local memory, in which case nothing is initialised.
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: AllocatePages(std:: uint32_t pageCount)
Allocates exactly pageCount pages from the texture cache.
Returns the first page, or InvalidPage when no run that long is free - the caller is expected to evict and retry rather than treat it as fatal.
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: AllocateReservedPages(std:: uint32_t pageCount)
Allocates pages from the render-target reserve.
Kept apart from the texture cache so that a render target - which has no host copy and so cannot be evicted and rebuilt - can never be crowded out by streaming textures. A target that does not fit here is not fatal either: GsTexture::
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: AllocateClut()
Allocates one CLUT slot, returning its block address (the unit of TEX0.CBP).
Every slot holds a 256-entry 32-bit CLUT, so all of them share the one storage mode and may share pages - the slab is the single place this class sub-allocates a page, and it is safe for exactly that reason. Returns InvalidBlock when the slab is full.
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: GetLargestFreeRun()
Returns the length of the longest free run in the texture cache.
The difference between this and GetCachePageCount() - GetUsedPageCount() is the fragmentation, which is the number to look at when an allocation fails while the cache is not full.
static std:: uint32_t nCine:: RHI:: GS:: GsVram:: GetFailedAllocationCount()
Returns how many allocations have failed since Initialize().
The Dreamcast work used exactly this counter to tell a working set that fits from one that thrashes (a level went from 2972 failed allocations to none), and it is the first number to look at when bringing a level up here.