GsDevice class
#include <nCine/Graphics/RHI/GS/GsDevice.h>
Pipeline-state and draw-call facade of the GS backend (aliased as RHI::Device).
The PlayStation 2 twin of the PVR and GX devices: each draw decodes the bound program's instance block(s) exactly like SwDevice::Dispatch, CPU-transforms the four sprite corners to display coordinates and writes them into a GIF packet as Graphics Synthesizer primitives. The GS takes screen space directly - there is no transform unit in the rasterizer and no matrix register - so the whole model-view-projection is applied on the EE, the same way the PowerVR backend does it on the SH4.
Register encoding is left to PS2SDK's libdraw rather than hand-assembled, in the same way the PVR backend builds on KallistiOS's pvr.h: the backend's own logic is corner synthesis, effect dispatch and residency, not GIF tag layout. Two traps of that library are load-bearing here and are repeated wherever they apply, because neither fails loudly:
draw_texture_transfer()builds a DMA chain, so its packet must be sent withdma_channel_send_chain(); sending it as a normal transfer wedges the channel silently.texbuffer_t::widthandframebuffer_t::widthare in texels, notTBWunits.
Because the GS rasterizes as packets arrive rather than deferring a whole scene like the tile accelerator, a texture whose pages are reused mid-frame could be sampled by a primitive that was already submitted. What makes that safe here is ORDER rather than planning: the GIF consumes everything on its DMA path strictly in sequence, so as long as the re-upload is issued after the draws that sample the previous contents - which FlushPendingPackets() is what guarantees - the GS has finished with those draws before the new texels land. A store may still be evicted while it is resident, but never while a submitted primitive is still reading it.
Palette handling has no bank limit to work around: a CLUT is a 1 KB region of local memory selected per draw through TEX0.CBP, so the layout's slab holds many at once and the PowerVR's four-bank LRU becomes a straight slot cache keyed by palette row.
Public types
- struct ScissorState
- struct BlendingState
- struct DepthTestState
- struct CullFaceState
Public static functions
-
static auto GetFrameCounter() -> std::
uint32_t - Monotonic count of finished frames, used to detect "still referenced by the current frame" resources.
- static auto FindGeneratedEffect(const char* program, const char* variant) -> const FixedFunctionGeneratedEffect*
- Returns the generated fixed-function effect of a (program, variant) key, or
nullptr. - static void SetBlendingEnabled(bool enabled)
-
static void SetBlendingFactors(nCine::
BlendingFactor srcRgb, nCine:: BlendingFactor dstRgb, nCine:: BlendingFactor srcAlpha, nCine:: BlendingFactor dstAlpha) - static auto GetBlendingState() -> BlendingState
- static void SetBlendingState(const BlendingState& state)
- static void SetDepthTestEnabled(bool enabled)
- static void SetDepthMaskEnabled(bool enabled)
- static auto GetDepthTestState() -> DepthTestState
- static void SetDepthTestState(const DepthTestState& state)
- static void SetCullFaceEnabled(bool enabled)
- static auto GetCullFaceState() -> CullFaceState
- static void SetCullFaceState(const CullFaceState& state)
- static auto GetScissorState() -> ScissorState
- static void SetScissorState(const ScissorState& state)
- static void SetScissor(const Recti& rect)
- static void SetScissorTestEnabled(bool enabled)
- static auto GetViewport() -> Recti
- static void SetViewport(const Recti& rect)
-
static void InitViewport(std::
int32_t x, std:: int32_t y, std:: int32_t width, std:: int32_t height) - static auto GetClearColor() -> Colorf
- static void SetClearColor(const Colorf& color)
- static void Clear(ClearFlags flags)
-
static void DrawArrays(PrimitiveType primitive,
std::
int32_t firstVertex, std:: int32_t numVertices) -
static void DrawArraysInstanced(PrimitiveType primitive,
std::
int32_t firstVertex, std:: int32_t numVertices, std:: int32_t numInstances) -
static void DrawElements(PrimitiveType primitive,
std::
uint32_t numIndices, IndexFormat indexFormat, std:: uintptr_t indexOffset, std:: int32_t baseVertex) -
static void DrawElements(PrimitiveType primitive,
std::
uint32_t numIndices, std:: uintptr_t indexOffset, std:: int32_t baseVertex) -
static void DrawElementsInstanced(PrimitiveType primitive,
std::
uint32_t numIndices, IndexFormat indexFormat, std:: uintptr_t indexOffset, std:: int32_t numInstances, std:: int32_t baseVertex) -
static void DrawElementsInstanced(PrimitiveType primitive,
std::
uint32_t numIndices, std:: uintptr_t indexOffset, std:: int32_t numInstances, std:: int32_t baseVertex) - static auto InsertFence() -> FenceHandle
- static void DeleteFence(FenceHandle& fence)
-
static auto ClientWaitFence(FenceHandle fence,
std::
uint64_t timeoutNs) -> bool - static void SetupInitialState()
-
static auto CreateSwapchain(void* windowHandle,
std::
int32_t width, std:: int32_t height, bool vsync) -> bool - No-op (the window backend owns the presentation path).
- static void DestroySwapchain()
- No-op.
-
static void ResizeSwapchain(std::
int32_t width, std:: int32_t height) - No-op (the logical resolution is driven by ResizeScreenFramebuffer() from the render pipeline).
- static void PresentFrame()
- Flushes the frame's remaining GIF packets and flips the display buffer.
-
static auto GetMaxTextureDimension() -> std::
int32_t - Returns the maximum supported texture dimension (drives the tileset chunking).
-
static void ResizeScreenFramebuffer(std::
int32_t width, std:: int32_t height) - Sets the logical resolution the scene is rendered at (scaled to the display at submit).
- static void InitializeGs()
- Brings up the Graphics Synthesizer.
- static void BindProgram(GsShaderProgram* program)
- Records the currently bound shader program.
- static auto CurrentProgram() -> GsShaderProgram*
- Returns the currently bound shader program.
-
static void BindTexture(std::
uint32_t unit, const GsTexture* texture) - Records the texture bound to a texture unit.
- static void UnbindTexture(const GsTexture* texture)
- Clears a texture from every unit it is bound to (called from ~GsTexture).
-
static auto GetBoundTexture(std::
uint32_t unit) -> const GsTexture* - Returns the texture bound to a texture unit.
-
static void BindUniformRange(std::
uint32_t index, const std:: uint8_t* data, std:: uint32_t size) - Records the host data range bound to a uniform binding point.
- static void SetRenderTarget(GsRenderTarget* renderTarget)
- Records the current draw render target (points
FRAME.FBPat its attachment). - static void UnbindRenderTarget(const GsRenderTarget* renderTarget)
- Clears a render target from the device if it is the current one (called from ~GsRenderTarget).
- static void FlushPendingPackets()
- Sends whatever draws and register writes are still queued, so a later transfer cannot overtake them.
-
static void WritebackForDma(const void* start,
std::
size_t bytes) - Writes the EE data cache back so a DMA can read
bytesbytes atstart. - static void RegisterPaletteTexture(GsTexture* texture)
- Registers the intercepted shared palette texture (rows become CLUTs).
-
static void NotifyPaletteTextureChanged(GsTexture* texture,
std::
int32_t firstRow, std:: int32_t rowCount) - Invalidates the CLUT slots (and RG8 bakes) of the given palette rows after an upload.
-
static void SetPendingSoftwareLighting(const float* lightmap,
std::
int32_t lmW, std:: int32_t lmH, std:: int32_t scale, std:: int32_t vpX, std:: int32_t vpY, std:: int32_t vpW, std:: int32_t vpH, float ambR, float ambG, float ambB, bool waterActive = false, float waterLevelPx = 0.0f, float waterTime = 0.0f, float waterCamY = 0.0f) - Queues the CPU lightmap/water combine for the next
Combinedraw (the direct-tier lighting contract). - static void EndFrame()
- Drops any lighting entries not consumed this frame (called by the window backend at present).
Constructors, destructors, conversion operators
Function documentation
static const FixedFunctionGeneratedEffect* nCine:: RHI:: GS:: GsDevice:: FindGeneratedEffect(const char* program,
const char* variant)
Returns the generated fixed-function effect of a (program, variant) key, or nullptr.
Scans the table transpiled from the shaders' fixed_function blocks (Shaders/Generated/GsGeneratedEffects.h). Called once per program load from GsShaderProgram::SetObjectLabel(), which maps the object label onto the key through its exact-name table - the draw path only ever reads the stored pointer.
static void nCine:: RHI:: GS:: GsDevice:: InitializeGs()
Brings up the Graphics Synthesizer.
Places the static regions through GsVram::libgraph. The depth test is configured ALWAYS with writes masked: the engine's queue is already in painter's order, so a Z buffer would cost 560 KB of local memory for nothing.
static void nCine:: RHI:: GS:: GsDevice:: FlushPendingPackets()
Sends whatever draws and register writes are still queued, so a later transfer cannot overtake them.
The GS consumes GIF data strictly in order, which is the only thing that makes a mid-frame re-upload safe: the transfer must reach it AFTER the draws that sample the store's previous contents. Those draws sit in this device's pending packet until it is flushed, so any code path that hands the GIF a packet of its own - a texture or CLUT transfer, both of which are DMA chains and cannot be appended here - has to call this first. Skipping it inverts the order: the upload lands, and then the draws that were queued before it sample the new data.
static void nCine:: RHI:: GS:: GsDevice:: WritebackForDma(const void* start,
std:: size_t bytes)
Writes the EE data cache back so a DMA can read bytes bytes at start.
The second thing every path that hands the GIF a packet of its own has to do, and the one with no symptom until it bites. PS2SDK synchronises the buffer it is handed - dma_channel_send_chain() calls SyncDCache() over the qwords of the chain itself - but draw_texture_transfer() builds a chain whose REF tags point somewhere else entirely, at the staging image in main memory, and nothing synchronises that. The EE's data cache is 8 KB and write-back, so the tail of an image the CPU has just filled is still sitting in it, and the DMA - which reads memory directly, never the cache - transfers whatever was in those addresses beforehand.
The corruption is therefore intermittent by nature: how much of the image is still dirty depends on what ran between filling it and sending it, and the stale bytes are the previous upload through the same staging buffer. A texture comes out garbled for as long as it stays resident and is correct the next time it happens to be re-transferred, which is what it looked like - a frame glitching at random and coming good by itself.