2026-01-12 05:18:12 -05:00
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2024-07-30 13:42:36 +02:00
// SPDX-License-Identifier: GPL-3.0+
2009-02-09 21:15:56 +00:00
#pragma once
2023-07-09 01:40:05 +10:00
#include "GS/GS.h"
2025-10-04 09:41:51 -04:00
#include "GS/GSPerfMon.h"
2023-07-09 01:40:05 +10:00
#include "GS/GSLocalMemory.h"
2026-07-19 08:12:53 -07:00
#include "GS/GSVertexKick.h"
2026-07-19 11:26:57 -07:00
#include "GS/GSBackQueue.h"
2023-07-09 01:40:05 +10:00
#include "GS/GSDrawingContext.h"
#include "GS/GSDrawingEnvironment.h"
#include "GS/Renderers/Common/GSVertex.h"
#include "GS/Renderers/Common/GSVertexTrace.h"
#include "GS/Renderers/Common/GSDevice.h"
#include "GS/GSVector.h"
2009-02-09 21:15:56 +00:00
#include "GSAlignedClass.h"
2023-07-09 01:40:05 +10:00
2026-07-19 13:05:11 -07:00
#include "common/Threading.h"
2026-07-25 00:48:56 -04:00
#include <array>
2026-07-19 13:05:11 -07:00
#include <atomic>
#include <cstring>
2026-07-25 00:48:56 -04:00
#include <memory>
#include <mutex>
2026-07-19 13:05:11 -07:00
#include <thread>
2026-07-19 12:51:57 -07:00
#include <vector>
2023-07-09 01:40:05 +10:00
class GSDumpBase ;
2009-02-09 21:15:56 +00:00
2011-02-07 01:59:05 +00:00
class GSState : public GSAlignedClass < 32 >
2009-02-09 21:15:56 +00:00
{
2026-07-19 09:49:24 -07:00
// GSVertexTrace::Update consumes the per-buffer fused FindMinMax accumulator
// (m_vertex->fmm_*) directly.
friend class GSVertexTrace ;
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: the front parser object delegates protected queries/seams to
// the back renderer through a GSState*.
friend class GSFrontState ;
2026-07-19 09:49:24 -07:00
2022-08-13 01:11:39 -05:00
public :
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: shared_chan aims this object at another GSState's channel — the
// front parser object of the two-object split passes the back object's
// channel so its records land in the consumed ring. Default (nullptr) uses
// this object's own channel storage, exactly as before.
2026-08-02 19:14:03 -07:00
/// `is_front_parser` suppresses the asynchronous-readback shadow allocation: the front
/// object of the pipelined split reaches the back's shadow through m_mem_target, so its
/// own copy would be written once and never read. It cannot be inferred here — the
/// derived constructor only repoints m_mem_target after this one returns.
GSState ( GSBackQueue :: Channel * shared_chan = nullptr , bool is_front_parser = false );
2022-08-13 01:11:39 -05:00
virtual ~ GSState ();
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: channel/back-thread visibility for the front-object lifecycle
// in GS.cpp (create the front only when the back thread actually engaged).
GSBackQueue :: Channel * GetBackChannel () { return m_chan ; }
bool IsBackThreadRunning () const { return m_chan -> consumer_running ; }
2026-07-19 14:58:49 -07:00
// GV7-2: external sync points (settings apply, screenshot-to-memory) that
// touch renderer/device state from the MTGS thread must drain queued records
// first — the back thread may otherwise be mid-draw on the same GSDevice.
void DrainBackQueue ();
2025-05-31 23:53:54 +01:00
static constexpr int GetSaveStateSize ( int version );
2023-02-12 13:04:18 +10:00
2022-08-13 01:11:39 -05:00
private :
2011-12-20 14:33:28 +00:00
// RESTRICT prevents multiple loads of the same part of the register when accessing its bitfields (the compiler is happy to know that memory writes in-between will not go there)
typedef void ( GSState ::* GIFPackedRegHandler )( const GIFPackedReg * RESTRICT r );
2011-02-18 01:56:05 +00:00
2023-02-12 13:04:18 +10:00
GIFPackedRegHandler m_fpGIFPackedRegHandlers [ 16 ] = {};
GIFPackedRegHandler m_fpGIFPackedRegHandlerXYZ [ 8 ][ 4 ] = {};
2009-02-09 21:15:56 +00:00
2022-03-25 21:45:22 +00:00
void CheckFlushes ();
2011-12-20 14:33:28 +00:00
void GIFPackedRegHandlerNull ( const GIFPackedReg * RESTRICT r );
void GIFPackedRegHandlerRGBA ( const GIFPackedReg * RESTRICT r );
void GIFPackedRegHandlerSTQ ( const GIFPackedReg * RESTRICT r );
void GIFPackedRegHandlerUV ( const GIFPackedReg * RESTRICT r );
2013-06-06 11:36:01 +00:00
void GIFPackedRegHandlerUV_Hack ( const GIFPackedReg * RESTRICT r );
2025-07-06 23:20:09 -04:00
template < u32 prim , u32 adc , bool auto_flush > void GIFPackedRegHandlerXYZF2 ( const GIFPackedReg * RESTRICT r );
template < u32 prim , u32 adc , bool auto_flush > void GIFPackedRegHandlerXYZ2 ( const GIFPackedReg * RESTRICT r );
2011-12-20 14:33:28 +00:00
void GIFPackedRegHandlerFOG ( const GIFPackedReg * RESTRICT r );
void GIFPackedRegHandlerA_D ( const GIFPackedReg * RESTRICT r );
void GIFPackedRegHandlerNOP ( const GIFPackedReg * RESTRICT r );
2009-02-09 21:15:56 +00:00
2011-12-20 14:33:28 +00:00
typedef void ( GSState ::* GIFRegHandler )( const GIFReg * RESTRICT r );
2009-02-09 21:15:56 +00:00
2023-02-12 13:04:18 +10:00
GIFRegHandler m_fpGIFRegHandlers [ 256 ] = {};
GIFRegHandler m_fpGIFRegHandlerXYZ [ 8 ][ 4 ] = {};
2009-02-09 21:15:56 +00:00
2021-08-30 21:45:52 -05:00
typedef void ( GSState ::* GIFPackedRegHandlerC )( const GIFPackedReg * RESTRICT r , u32 size );
2012-01-18 11:47:31 +00:00
2023-02-12 13:04:18 +10:00
GIFPackedRegHandlerC m_fpGIFPackedRegHandlersC [ 2 ] = {};
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZF2 [ 8 ] = {};
GIFPackedRegHandlerC m_fpGIFPackedRegHandlerSTQRGBAXYZ2 [ 8 ] = {};
2012-01-18 11:47:31 +00:00
2025-07-06 23:20:09 -04:00
template < u32 prim , bool auto_flush > void GIFPackedRegHandlerSTQRGBAXYZF2 ( const GIFPackedReg * RESTRICT r , u32 size );
template < u32 prim , bool auto_flush > void GIFPackedRegHandlerSTQRGBAXYZ2 ( const GIFPackedReg * RESTRICT r , u32 size );
2021-08-30 21:45:52 -05:00
void GIFPackedRegHandlerNOP ( const GIFPackedReg * RESTRICT r , u32 size );
2012-01-18 11:47:31 +00:00
2012-01-08 17:10:00 +00:00
template < int i > void ApplyTEX0 ( GIFRegTEX0 & TEX0 );
2021-08-30 21:45:52 -05:00
void ApplyPRIM ( u32 prim );
2010-07-19 15:49:30 +00:00
2011-12-20 14:33:28 +00:00
void GIFRegHandlerNull ( const GIFReg * RESTRICT r );
void GIFRegHandlerPRIM ( const GIFReg * RESTRICT r );
void GIFRegHandlerRGBAQ ( const GIFReg * RESTRICT r );
void GIFRegHandlerST ( const GIFReg * RESTRICT r );
void GIFRegHandlerUV ( const GIFReg * RESTRICT r );
2013-06-06 11:36:01 +00:00
void GIFRegHandlerUV_Hack ( const GIFReg * RESTRICT r );
2025-07-06 23:20:09 -04:00
template < u32 prim , u32 adc , bool auto_flush > void GIFRegHandlerXYZF2 ( const GIFReg * RESTRICT r );
template < u32 prim , u32 adc , bool auto_flush > void GIFRegHandlerXYZ2 ( const GIFReg * RESTRICT r );
2011-12-20 14:33:28 +00:00
template < int i > void GIFRegHandlerTEX0 ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerCLAMP ( const GIFReg * RESTRICT r );
void GIFRegHandlerFOG ( const GIFReg * RESTRICT r );
void GIFRegHandlerNOP ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerTEX1 ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerTEX2 ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerXYOFFSET ( const GIFReg * RESTRICT r );
void GIFRegHandlerPRMODECONT ( const GIFReg * RESTRICT r );
void GIFRegHandlerPRMODE ( const GIFReg * RESTRICT r );
void GIFRegHandlerTEXCLUT ( const GIFReg * RESTRICT r );
void GIFRegHandlerSCANMSK ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerMIPTBP1 ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerMIPTBP2 ( const GIFReg * RESTRICT r );
void GIFRegHandlerTEXA ( const GIFReg * RESTRICT r );
void GIFRegHandlerFOGCOL ( const GIFReg * RESTRICT r );
void GIFRegHandlerTEXFLUSH ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerSCISSOR ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerALPHA ( const GIFReg * RESTRICT r );
void GIFRegHandlerDIMX ( const GIFReg * RESTRICT r );
void GIFRegHandlerDTHE ( const GIFReg * RESTRICT r );
void GIFRegHandlerCOLCLAMP ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerTEST ( const GIFReg * RESTRICT r );
void GIFRegHandlerPABE ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerFBA ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerFRAME ( const GIFReg * RESTRICT r );
template < int i > void GIFRegHandlerZBUF ( const GIFReg * RESTRICT r );
void GIFRegHandlerBITBLTBUF ( const GIFReg * RESTRICT r );
void GIFRegHandlerTRXPOS ( const GIFReg * RESTRICT r );
void GIFRegHandlerTRXREG ( const GIFReg * RESTRICT r );
void GIFRegHandlerTRXDIR ( const GIFReg * RESTRICT r );
void GIFRegHandlerHWREG ( const GIFReg * RESTRICT r );
2009-02-09 21:15:56 +00:00
2026-07-30 10:34:21 -07:00
template < bool auto_flush , bool sprites_only > void SetPrimHandlers ();
2021-12-23 21:35:05 +10:00
2009-04-18 09:34:25 +00:00
struct GSTransferBuffer
{
2023-02-12 13:04:18 +10:00
int x = 0 , y = 0 ;
2025-05-31 22:07:32 +01:00
int w = 0 , h = 0 ;
2023-02-12 13:04:18 +10:00
int start = 0 , end = 0 , total = 0 ;
u8 * buff = nullptr ;
2025-05-31 22:07:32 +01:00
GSVector4i rect = GSVector4i :: zero ();
2023-02-12 13:04:18 +10:00
GIFRegBITBLTBUF m_blit = {};
2025-05-31 22:07:32 +01:00
GIFRegTRXPOS m_pos = {};
GIFRegTRXREG m_reg = {};
2023-04-07 03:25:09 +01:00
bool write = false ;
2009-04-18 09:34:25 +00:00
GSTransferBuffer ();
2023-07-09 01:34:50 +10:00
~ GSTransferBuffer ();
2009-04-18 09:34:25 +00:00
2025-05-31 22:07:32 +01:00
void Init ( GIFRegTRXPOS & TRXPOS , GIFRegTRXREG & TRXREG , const GIFRegBITBLTBUF & blit , bool is_write );
2009-04-18 09:34:25 +00:00
bool Update ( int tw , int th , int bpp , int & len );
} m_tr ;
2009-02-09 21:15:56 +00:00
2026-07-25 00:48:56 -04:00
// GSHardwareDownloadMode::Asynchronous shadow of local memory.
//
// Completed GPU downloads are swizzled in here instead of directly becoming the EE
// thread's view of live GS memory. That stops a frame-old download from racing with
// the next frame's writes and exposing a half-old/half-new result. Every access is
// CPU-only and under m_async_readback_mutex — it must NEVER become a GPU wait.
//
// Deviation from the upstream port: allocated on demand (a second GSLocalMemory is a
// 4MB wrapped mapping, and the two-object split would otherwise pay for two of them
// even though this mode is opt-in). Never freed once allocated, because the EE thread
// may be inside a shadow read while the GS thread turns the mode off.
std :: unique_ptr < GSLocalMemory > m_async_readback_mem ;
std :: mutex m_async_readback_mutex ;
std :: atomic < bool > m_async_readback_ready { false };
std :: array < u64 , GS_MAX_PAGES > m_async_readback_page_generations = {};
u64 m_async_readback_generation = 0 ;
/// Bumps the generation of every page in `rect`. Caller must hold m_async_readback_mutex.
void MarkAsyncReadbackPagesWritten ( const GSOffset & offset , const GSVector4i & rect );
/// Allocates + seeds the shadow if this object doesn't have one yet. GS thread only.
bool EnsureAsyncReadbackMemory ();
2009-02-09 21:15:56 +00:00
protected :
2026-07-19 11:26:57 -07:00
// Executor-owned HOST->LOCAL write cursor (advanced by wi() across transfer
// slices; mirrored back into m_tr.x/y inline for savestate coherence).
int m_exec_tr_x = 0 ;
int m_exec_tr_y = 0 ;
2024-04-01 01:12:24 +10:00
static constexpr int INVALID_ALPHA_MINMAX = 500 ;
2026-01-25 08:35:36 +00:00
static constexpr int MAX_DRAW_BUFFERS = 3 ;
2024-04-01 01:12:24 +10:00
2023-02-12 13:04:18 +10:00
GSVertex m_v = {};
float m_q = 1.0f ;
2023-06-18 01:40:04 +10:00
GSVector4i m_xyof = {};
2026-01-25 08:35:36 +00:00
int m_used_buffers_idx = 0 ;
int m_current_buffer_idx = 0 ;
bool m_recent_buffer_switch = false ;
2016-05-05 16:03:48 +02:00
2026-07-19 11:48:28 -07:00
// Definitions hoisted to GSBackQueue.h (DRAW record payload types).
using GSVertexBuff = GSBackQueue :: VertexBuff ;
using GSIndexBuff = GSBackQueue :: IndexBuff ;
2012-01-08 17:10:00 +00:00
2026-01-25 08:35:36 +00:00
GSVertexBuff m_vertex_buffers [ MAX_DRAW_BUFFERS ];
2026-07-19 08:12:53 -07:00
GSVertexBuff * m_vertex = nullptr ;
2026-01-25 08:35:36 +00:00
GSIndexBuff m_index_buffers [ MAX_DRAW_BUFFERS ];
GSIndexBuff * m_index ;
2026-07-29 15:43:50 -07:00
// Draw-time staging snapshot of the live vertex/index arrays, for the draws
// that must read the vertices while the backend also writes them. Contents are
// write-then-consume: fully overwritten before every use, so they are never
// preserved across a reallocation. Their capacity is deliberately NOT tied to
// m_vertex/m_index — on the pipelined split m_vertex points at pooled node
// arrays grown by the *front* object, which can be far larger than anything
// this object ever allocated — so EnsureDrawStaging sizes them at the point of
// use, from what is actually about to be staged, and only ever upwards.
2026-01-25 08:35:36 +00:00
GSVertexBuff m_draw_vertex = {};
2025-01-23 15:48:35 +00:00
struct
{
u16 * buff ;
u32 tail ;
} m_draw_index = {};
2026-07-29 15:43:50 -07:00
// Allocated element counts of the two staging arrays above (0 = not allocated;
// they stay unallocated in sessions that never stage a draw).
u32 m_draw_vertex_alloc = 0 ;
u32 m_draw_index_alloc = 0 ;
void EnsureDrawStaging ( u32 vertex_count , u32 index_count );
2026-01-25 08:35:36 +00:00
struct GSDrawBufferEnv
{
GSDrawingEnvironment m_env ;
int m_backed_up_ctx = 0 ;
u32 m_dirty_regs = 0 ;
GSVector4i draw_rect = GSVector4i :: zero ();
bool related_draw = false ;
};
GSDrawBufferEnv m_env_buffers [ MAX_DRAW_BUFFERS ] = {};
2012-01-09 08:41:33 +00:00
void UpdateContext ();
void UpdateScissor ();
2016-09-28 09:45:22 +02:00
void UpdateVertexKick ();
2011-02-07 01:59:05 +00:00
2012-01-05 02:40:24 +00:00
void GrowVertexBuffer ();
2025-08-11 09:02:29 -04:00
bool IsAutoFlushDraw ( u32 prim , int & tex_layer );
2025-07-06 23:20:09 -04:00
template < u32 prim > void HandleAutoFlush ();
2025-09-03 23:00:48 -04:00
bool EarlyDetectShuffle ( u32 prim );
2023-09-25 04:22:03 +01:00
void CheckCLUTValidity ( u32 prim );
2026-01-25 08:35:36 +00:00
bool CheckOverlapVerts ( u32 n );
2026-07-01 23:31:51 -07:00
bool CheckOverlapVertsSlow ( u32 n );
2022-09-13 21:20:25 -05:00
2026-07-19 06:57:25 -07:00
void ApplyDepthClamp ( u32 & z );
2026-07-19 07:35:20 -07:00
GSLimit24BitDepth GetDepthClampMode () const ;
static __fi void ApplyDepthClampMode ( GSLimit24BitDepth mode , u32 & z )
{
if ( mode == GSLimit24BitDepth :: PrioritizeUpper )
z = (( z >> 8 ) & ~ 0xFF ) | ( z & 0xFF );
else if ( mode == GSLimit24BitDepth :: PrioritizeLower )
z &= 0x00FFFFFF ;
}
// Batch cursor: caches the hot vertex/index buffer fields in locals so they live
// in registers across a fused packed-handler batch instead of round-tripping
// through m_vertex/m_index per vertex. Store() must run before ANY call that can
// flush, grow or switch draw buffers (Flush, GrowVertexBuffer,
// CheckOverlapVertsSlow, HandleAutoFlush — GrowVertexBuffer reads tail for the
// preserved-copy size), and Load() again after. buff/maxcount are only ever
// changed by those callees, so Store() never writes them back.
struct VertexKickCursor
{
GSVertexBuff * vb ;
GSIndexBuff * ib ;
GSVertex * vbuff ;
u16 * ibuff ;
u32 head , tail , next , xy_tail , maxcount , itail ;
2026-07-19 08:19:14 -07:00
// Deferred draw_rect accumulation: accepted prims union their (already
// subpixel-shifted, exclusive) rects here; Store() folds the result into
// temp_draw_rect with one scissor clamp. Exact because rintersect is
// monotone and idempotent, so clamping once over the union equals the
// per-prim clamp-then-union chain, and because a draw's first prim (which
// replaces temp_draw_rect instead of unioning) can only be the first
// accumulated after a seam — the index buffer only empties behind
// flush seams.
GSVector4i acc_rect ;
u32 acc_state ; // 0 = empty, 1 = union into temp_draw_rect, 2 = replace it
GSVector4i * temp_rect ;
const GSVector4i * scissor_in ;
2026-07-19 07:35:20 -07:00
__fi void Load ( GSState & s )
{
vb = s . m_vertex ;
ib = s . m_index ;
vbuff = vb -> buff ;
ibuff = ib -> buff ;
head = vb -> head ;
tail = vb -> tail ;
next = vb -> next ;
xy_tail = vb -> xy_tail ;
maxcount = vb -> maxcount ;
itail = ib -> tail ;
2026-07-19 08:19:14 -07:00
acc_state = 0 ;
temp_rect = & s . temp_draw_rect ;
scissor_in = & s . m_context -> scissor . in ;
2026-07-19 07:35:20 -07:00
}
__fi void Store () const
{
vb -> head = head ;
vb -> tail = tail ;
vb -> next = next ;
vb -> xy_tail = xy_tail ;
ib -> tail = itail ;
2026-07-19 08:19:14 -07:00
if ( acc_state != 0 )
{
const GSVector4i merged = ( acc_state == 2 ) ? acc_rect : temp_rect -> runion ( acc_rect );
* temp_rect = merged . rintersect ( * scissor_in );
}
2026-07-19 07:35:20 -07:00
}
};
2026-07-19 08:12:53 -07:00
// Pre-adjusted scissor bounds for the scalar-outcode cull (GSVertexKick.h),
2026-07-19 09:12:14 -07:00
// re-derived when the cull rect changes. band = triangle/sprite native-res
// space, raw = point/line 12.4 space. m_cull_bounds_src is the cull rect the
// bounds were derived from (poison-initialized so the first update always
// refreshes).
GSVector4i m_cull_bounds_src = GSVector4i :: cxpr ( - 2 , - 2 , - 2 , - 2 );
2026-07-19 08:12:53 -07:00
GSVertexKernels :: CullBounds m_cull_bounds_band = {};
GSVertexKernels :: CullBounds m_cull_bounds_raw = {};
void RefreshKickMirror ();
2025-07-06 23:20:09 -04:00
template < u32 prim , bool auto_flush > void VertexKick ( u32 skip );
2026-07-19 08:12:53 -07:00
template < u32 prim , bool auto_flush > void VertexKickDirect ( u32 skip , u32 xraw , u32 yraw , const GSVector4i & v0 , const GSVector4i & v1 , VertexKickCursor & c );
2012-01-05 02:40:24 +00:00
// following functions need m_vt to be initialized
2012-01-19 04:53:36 +00:00
GSVertexTrace m_vt ;
2021-12-21 01:45:37 +01:00
GSVertexTrace :: VertexAlpha & GetAlphaMinMax ()
{
if ( ! m_vt . m_alpha . valid )
2024-04-01 01:12:24 +10:00
CalcAlphaMinMax ( 0 , INVALID_ALPHA_MINMAX );
2021-12-21 01:45:37 +01:00
return m_vt . m_alpha ;
}
2022-01-10 23:47:38 -06:00
struct TextureMinMaxResult
{
enum UsesBoundary
{
USES_BOUNDARY_LEFT = 1 << 0 ,
USES_BOUNDARY_TOP = 1 << 1 ,
USES_BOUNDARY_RIGHT = 1 << 2 ,
USES_BOUNDARY_BOTTOM = 1 << 3 ,
USES_BOUNDARY_U = USES_BOUNDARY_LEFT | USES_BOUNDARY_RIGHT ,
USES_BOUNDARY_V = USES_BOUNDARY_TOP | USES_BOUNDARY_BOTTOM ,
};
GSVector4i coverage ; ///< Part of the texture used
u8 uses_boundary ; ///< Whether or not the usage touches the left, top, right, or bottom edge (and therefore needs wrap modes preserved)
};
2024-06-29 17:53:14 +01:00
TextureMinMaxResult GetTextureMinMax ( GIFRegTEX0 TEX0 , GIFRegCLAMP CLAMP , bool linear , bool clamp_to_tsize );
2023-09-17 09:51:31 +02:00
bool TryAlphaTest ( u32 & fm , u32 & zm );
2026-04-04 22:19:02 -04:00
bool IsFlatShaded ();
2012-01-05 02:40:24 +00:00
bool IsOpaque ();
2016-10-02 13:23:38 +02:00
bool IsMipMapDraw ();
2015-08-04 13:27:08 +02:00
bool IsMipMapActive ();
2022-07-18 02:20:08 +01:00
bool IsCoverageAlpha ();
2026-03-25 18:49:46 -04:00
bool IsCoverageAlphaFixedOne ();
virtual bool IsCoverageAlphaSupported ();
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: back-half of the split front's kick-time coverage-alpha query
// (HW only): cached-ctx/alpha-minmax from this object's last executed draw,
// the caller's live ALPHA passed in.
virtual bool IsRTWrittenLive ( const GIFRegALPHA & ALPHA );
2023-07-08 14:26:40 +01:00
void CalcAlphaMinMax ( const int tex_min , const int tex_max );
2024-03-11 02:52:53 +00:00
void CorrectATEAlphaMinMax ( const u32 atst , const int aref );
2009-02-09 21:15:56 +00:00
2026-03-31 22:57:39 -04:00
// Utility functions for getting position/texture coordinates.
GSVector4 GetXYWindow ( const GSVertex & v );
template < bool fst >
GSVector4 GetTexCoordsImpl ( const GSVertex & v , float q );
template < bool fst >
GSVector4 GetTexCoordsImpl ( const GSVertex & v );
GSVector4 GetTexCoords ( const GSVertex & v , float q );
GSVector4 GetTexCoords ( const GSVertex & v );
// Utility functions to detect and get corners of quads.
template < u32 primclass , bool tme = false , bool fst = false >
static bool GetQuadCornersImpl ( const GSVertex * v , const u16 * i , GSVertex & vout0 , GSVertex & vout1 );
bool GetQuadCorners ( const GSVertex * v , const u16 * i , GSVertex & vout0 , GSVertex & vout1 );
// Utility functions to get window/texture coordinates of a quad.
template < u32 primclass >
void GetQuadBBoxWindowImpl ( const GSVertex & v0 , const GSVertex & v1 , GSVector4 & xyout );
template < u32 primclass , bool tme = false , bool fst = false >
void GetQuadBBoxWindowImpl ( const GSVertex & v0 , const GSVertex & v1 , GSVector4 & xyout , GSVector4 & texout , bool keep_tex_order = true );
void GetQuadBBoxWindow ( const GSVertex & v0 , const GSVertex & v1 , GSVector4 & xyout );
void GetQuadBBoxWindow ( const GSVertex & v0 , const GSVertex & v1 , GSVector4 & xyout , GSVector4 & texout , bool keep_tex_order = true );
// Adjusts a quad so that it contains exactly the centers of the pixels that the GS would rasterize.
static void GetQuadRasterizedPoints ( GSVector4 & xy , bool keep_order = true );
static void GetQuadRasterizedPoints ( GSVector4 & xy , GSVector4 & tex , bool keep_order = true );
2009-02-09 21:15:56 +00:00
public :
2026-01-08 10:15:37 +00:00
enum EEGS_TransferType
{
EE_to_GS ,
GS_to_GS ,
2026-05-06 23:33:21 +01:00
GS_to_EE ,
Clear
2026-01-08 10:15:37 +00:00
};
2023-01-31 20:46:11 +00:00
struct GSUploadQueue
{
GIFRegBITBLTBUF blit ;
2025-12-16 00:00:09 +00:00
u64 draw ;
2026-05-06 23:33:21 +01:00
GSVector4i rect ;
2026-01-08 10:15:37 +00:00
EEGS_TransferType transfer_type ;
2023-01-31 20:46:11 +00:00
};
2024-06-28 02:33:25 +01:00
enum NoGapsType
{
2024-06-29 13:10:02 +01:00
Uninitialized = 0 ,
GapsFound ,
2024-06-28 02:33:25 +01:00
SpriteNoGaps ,
FullCover ,
};
2023-02-12 13:04:18 +10:00
GIFPath m_path [ 4 ] = {};
2023-04-03 02:13:40 +10:00
const GIFRegPRIM * PRIM = nullptr ;
2023-02-12 13:04:18 +10:00
GSPrivRegSet * m_regs = nullptr ;
2009-02-09 21:15:56 +00:00
GSLocalMemory m_mem ;
2023-02-12 13:04:18 +10:00
GSDrawingEnvironment m_env = {};
GSDrawingEnvironment m_prev_env = {};
2026-01-25 08:35:36 +00:00
GSDrawingEnvironment m_temp_env = {};
2023-04-03 02:13:40 +10:00
const GSDrawingEnvironment * m_draw_env = & m_env ;
2023-02-12 13:04:18 +10:00
GSDrawingContext * m_context = nullptr ;
2026-01-25 08:35:36 +00:00
GSVector4i temp_draw_rect ;
2026-07-30 21:22:22 -07:00
// Owned by the renderer, which opens and closes it on the present path. The transfer
// and ReadFIFO packets that fill it are produced on the parse path, which is the front
// object under the split — hence GetDumpSink() rather than a bare m_dump read. Both
// paths run on the MTGS thread, so the front writes straight into the back's dump.
2017-04-30 21:00:23 +02:00
std :: unique_ptr < GSDumpBase > m_dump ;
2026-07-30 21:22:22 -07:00
GSDumpBase * GetDumpSink () const { return m_mem_target -> m_dump . get (); }
2023-05-08 22:23:12 +10:00
bool m_scissor_invalid = false ;
2025-04-04 21:44:07 +01:00
bool m_quad_check_valid = false ;
2025-09-23 10:59:41 -04:00
bool m_quad_check_valid_shuffle = false ;
2025-04-04 21:44:07 +01:00
bool m_are_quads = false ;
2025-09-23 10:59:41 -04:00
bool m_are_quads_shuffle = false ;
2023-02-12 13:04:18 +10:00
bool m_nativeres = false ;
bool m_mipmap = false ;
2023-04-23 17:29:01 +10:00
bool m_texflush_flag = false ;
bool m_isPackedUV_HackFlag = false ;
2023-05-03 23:41:03 +10:00
bool m_channel_shuffle = false ;
2025-02-20 18:34:10 +00:00
bool m_using_temp_z = false ;
bool m_temp_z_full_copy = false ;
2024-03-30 11:44:10 +00:00
bool m_in_target_draw = false ;
2025-12-29 12:51:50 +00:00
bool m_channel_shuffle_finish = false ;
2025-01-23 15:48:35 +00:00
2024-06-26 12:23:35 +01:00
u32 m_target_offset = 0 ;
2023-04-23 17:29:01 +10:00
u8 m_scanmask_used = 0 ;
2023-02-12 13:04:18 +10:00
u32 m_dirty_gs_regs = 0 ;
int m_backed_up_ctx = 0 ;
2023-01-31 20:46:11 +00:00
std :: vector < GSUploadQueue > m_draw_transfers ;
2024-06-29 13:10:02 +01:00
NoGapsType m_primitive_covers_without_gaps ;
2024-03-31 02:57:52 +01:00
GSVector4i m_r = {};
GSVector4i m_r_no_scissor = {};
2009-02-09 21:15:56 +00:00
2026-07-19 14:33:20 -07:00
// GV7-1d-ii-c: per-object serial counters (were process statics). The
// front assigns draw/transfer order and carries serials in records; the
// back installs them at execution, so its TC/heuristic reads see the
// executing draw's serial, not the front's runahead position.
u64 s_n = 0 ;
u64 s_last_transfer_draw_n = 0 ;
u64 s_transfer_n = 0 ;
2012-01-29 10:12:20 +00:00
2025-10-04 09:41:51 -04:00
GSPerfMon m_perfmon_frame ; // Track stat across a frame.
GSPerfMon m_perfmon_draw ; // Track stat across a draw.
2025-05-31 23:53:54 +01:00
static constexpr u32 STATE_VERSION = 9 ;
2022-03-05 14:25:43 +10:00
2025-11-25 12:26:47 +00:00
#define PRIM_REG_MASK 0x7FF
#define MIPTBP_REG_MASK ((1ULL << 60) - 1ULL)
#define CLAMP_REG_MASK ((1ULL << 44) - 1ULL)
#define TEX1_REG_MASK 0xFFF001803FDULL
#define XYOFFSET_REG_MASK 0x0000FFFF0000FFFFULL
#define TEXA_REG_MASK 0xFF000080FFULL
#define FOGCOL_REG_MASK 0xFFFFFF
#define SCISSOR_REG_MASK 0x7FF07FF07FF07FFULL
#define ALPHA_REG_MASK 0xFF000000FFULL
#define DIMX_REG_MASK 0x7777777777777777ULL
#define FRAME_REG_MASK 0xFFFFFFFF3F3F01FFULL
#define ZBUF_REG_MASK 0x10F0001FFULL
#define TEST_REG_MASK 0x7FFFF
2022-06-10 22:03:43 +01:00
enum REG_DIRTY
{
DIRTY_REG_ALPHA ,
DIRTY_REG_CLAMP ,
DIRTY_REG_COLCLAMP ,
DIRTY_REG_DIMX ,
DIRTY_REG_DTHE ,
DIRTY_REG_FBA ,
DIRTY_REG_FOGCOL ,
DIRTY_REG_FRAME ,
DIRTY_REG_MIPTBP1 ,
DIRTY_REG_MIPTBP2 ,
DIRTY_REG_PABE ,
DIRTY_REG_PRIM ,
DIRTY_REG_SCANMSK ,
DIRTY_REG_SCISSOR ,
DIRTY_REG_TEST ,
DIRTY_REG_TEX0 ,
DIRTY_REG_TEX1 ,
DIRTY_REG_TEXA ,
DIRTY_REG_XYOFFSET ,
DIRTY_REG_ZBUF
};
2022-10-11 11:05:24 +01:00
enum GSFlushReason
{
UNKNOWN = 1 << 0 ,
RESET = 1 << 1 ,
CONTEXTCHANGE = 1 << 2 ,
CLUTCHANGE = 1 << 3 ,
2023-04-23 17:29:01 +10:00
GSTRANSFER = 1 << 4 ,
UPLOADDIRTYTEX = 1 << 5 ,
2023-06-11 06:10:47 +01:00
UPLOADDIRTYFRAME = 1 << 6 ,
UPLOADDIRTYZBUF = 1 << 7 ,
LOCALTOLOCALMOVE = 1 << 8 ,
DOWNLOADFIFO = 1 << 9 ,
SAVESTATE = 1 << 10 ,
LOADSTATE = 1 << 11 ,
AUTOFLUSH = 1 << 12 ,
VSYNC = 1 << 13 ,
GSREOPEN = 1 << 14 ,
VERTEXCOUNT = 1 << 15 ,
2022-10-11 11:05:24 +01:00
};
2023-02-12 13:04:18 +10:00
GSFlushReason m_state_flush_reason = UNKNOWN ;
2022-10-11 11:05:24 +01:00
2022-01-22 16:19:32 +00:00
enum PRIM_OVERLAP
{
PRIM_OVERLAP_UNKNOW ,
PRIM_OVERLAP_YES ,
PRIM_OVERLAP_NO
};
2023-02-12 13:04:18 +10:00
PRIM_OVERLAP m_prim_overlap = PRIM_OVERLAP_UNKNOW ;
2022-01-22 16:19:32 +00:00
std :: vector < size_t > m_drawlist ;
2025-09-23 11:08:21 -04:00
std :: vector < GSVector4i > m_drawlist_bbox ;
2022-01-22 16:19:32 +00:00
2026-07-19 11:53:04 -07:00
// Definition hoisted to GSBackQueue.h (PCRTC_SYNC record payload type).
using GSPCRTCRegs = GSBackQueue :: GSPCRTCRegs ;
2022-12-17 11:05:32 +00:00
2026-07-19 11:53:04 -07:00
GSPCRTCRegs PCRTCDisplays ;
2022-04-10 15:30:55 +01:00
2009-02-09 21:15:56 +00:00
public :
2022-12-24 13:47:10 +10:00
/// Returns the appropriate directory for draw dumping.
static std :: string GetDrawDumpPath ( const char * format , ...);
2023-04-03 02:08:09 +10:00
/// Expands dither matrix, suitable for software renderer.
static void ExpandDIMX ( GSVector4i * dimx , const GIFRegDIMX DIMX );
2023-04-28 18:41:39 +10:00
/// Returns a string representing the flush reason.
static const char * GetFlushReasonString ( GSFlushReason reason );
2009-02-09 21:15:56 +00:00
void ResetHandlers ();
2022-12-17 11:05:32 +00:00
void ResetPCRTC ();
2009-02-09 21:15:56 +00:00
2016-05-19 14:02:50 +05:30
GSVideoMode GetVideoMode ();
2009-05-14 16:41:52 +00:00
2016-05-19 14:02:50 +05:30
bool isinterlaced ();
2022-06-05 05:17:16 +01:00
bool isReallyInterlaced ();
2009-05-14 16:41:52 +00:00
2015-09-18 04:07:58 +03:00
float GetTvRefreshRate ();
2009-02-09 21:15:56 +00:00
2022-05-27 21:20:32 +10:00
virtual void Reset ( bool hardware_reset );
2022-03-08 18:14:12 +10:00
virtual void UpdateSettings ( const Pcsx2Config :: GSOptions & old_config );
2026-01-25 08:35:36 +00:00
void ResetDrawBuffers ();
void ResetDrawBufferIdx ();
void FlushBuffers ( bool flush_base_only = false , bool use_flush_reason = false , GSFlushReason flush_reason = GSFlushReason :: CONTEXTCHANGE );
void PushBuffer ();
void SetDrawBufferEnv ();
void SetDrawBuffDirty ();
bool CanBufferNewDraw ();
2022-10-11 11:05:24 +01:00
void Flush ( GSFlushReason reason );
2026-01-25 08:35:36 +00:00
void FlushDraw ( GSFlushReason reason );
2023-11-16 23:16:21 +00:00
u32 CalcMask ( int exp , int max_exp );
2022-06-10 22:03:43 +01:00
void FlushPrim ();
2022-06-10 10:16:02 +01:00
bool TestDrawChanged ();
2016-09-28 09:45:22 +02:00
void FlushWrite ();
2012-01-05 02:40:24 +00:00
virtual void Draw () = 0 ;
2023-11-04 21:32:27 +10:00
virtual void PurgeTextureCache ( bool sources , bool targets , bool hash_cache );
2023-02-23 21:00:48 +10:00
virtual void ReadbackTextureCache ();
2023-06-24 10:41:01 +01:00
virtual void InvalidateVideoMem ( const GIFRegBITBLTBUF & BITBLTBUF , const GSVector4i & r ) {}
2011-12-20 14:33:28 +00:00
virtual void InvalidateLocalMem ( const GIFRegBITBLTBUF & BITBLTBUF , const GSVector4i & r , bool clut = false ) {}
2009-02-09 21:15:56 +00:00
2022-04-25 22:31:30 +10:00
virtual void Move ();
2026-07-19 11:26:57 -07:00
// GV-7 front/back seam (SEAM-AUDIT.md): the front builds a self-contained
// record, the Exec*Record executor consumes it — inline today, on the back
// thread once GV7-1 lands. The executor owns the HOST->LOCAL write cursor
// across transfer slices.
void ExecTransferRecord ( const GSBackQueue :: TransferRecord & rec );
void SubmitMove ();
void ExecMoveRecord ( const GSBackQueue :: MoveRecord & rec );
2026-07-19 11:36:56 -07:00
void SubmitClutLoad ( const GIFRegTEX0 & TEX0 , const GIFRegTEXCLUT & TEXCLUT );
void ExecClutLoadRecord ( const GSBackQueue :: ClutLoadRecord & rec );
2026-07-19 11:48:28 -07:00
void ExecDrawRecord ( const GSBackQueue :: DrawRecord & rec );
2026-07-19 12:45:24 -07:00
void DrawRecordTail ( u64 draw_serial );
2026-07-19 11:53:04 -07:00
void SubmitPcrtcSync ();
void ExecPcrtcSyncRecord ( const GSBackQueue :: PcrtcSyncRecord & rec );
2026-07-19 11:26:57 -07:00
2026-07-19 12:45:24 -07:00
// GV7-1: sampled from GSConfig.BackThreadMode at construction (the option is
// restart-required, so it can't change under a live GSState). Off = the
// front-side seam functions skip the record round-trip entirely and call the
// executor tails against live state; any other mode builds records.
bool m_back_records = false ;
2026-07-19 13:42:51 -07:00
// GV7-1d-ii: the front<->back channel (record ring + wake semaphore + pool
// arenas/free rings, GSBackQueue.h). Single-object modes use this object's
// own storage; the two-object pipelined split points the front parser
// object's m_chan at the back object's channel. The destructor frees
// m_chan_storage's pooled arrays — only ever this object's own storage, so
// a front pointing elsewhere frees nothing it doesn't own.
GSBackQueue :: Channel m_chan_storage ;
GSBackQueue :: Channel * m_chan = & m_chan_storage ;
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: the object owning local memory, the CLUT palette, and the
// texture cache for this session. Single-object modes: this. On the front
// parser object it points at the back renderer, so the drained seams
// (readbacks, savestates) reach the authoritative m_mem/TC while every
// register decision stays front-side. Only ever dereferenced after a drain.
GSState * m_mem_target = this ;
// GV7-1d-ii: set on the back renderer when a front parser object exists.
// The draw executor then aims m_draw_env/PRIM/m_context around the tail
// itself (on a single object FlushDraw owns that aiming, and the front's
// carry-over rebuild depends on FlushDraw's restore happening after).
bool m_split_back = false ;
2026-07-30 21:22:22 -07:00
// The inverse of m_mem_target: the object holding the authoritative parse
// state (env, vertex, transfer cursor). On the back renderer under the split
// it points at the front; everywhere else it is this. Used where the back
// needs the state a savestate would record — the GS dump's initial freeze.
GSState * m_parse_target = this ;
2026-07-19 12:51:57 -07:00
// GV7-1c: draw-node pool. Acquire is front-side (free ring first, then arena
// growth up to the ring capacity, then backpressure); Release is the consume
// site (inline modes: FlushPrim right after the executor returns; pipelined:
2026-07-19 13:42:51 -07:00
// the back thread after DrawRecordTail).
2026-07-19 12:51:57 -07:00
GSBackQueue :: DrawNode * AcquireDrawNode ();
void ReleaseDrawNode ( GSBackQueue :: DrawNode * node );
2026-07-19 12:56:35 -07:00
// GV7-1c: transfer payload pool (record modes only; mode 0 keeps
// GSTransferBuffer's own allocation untouched). m_tr.buff aliases the
// current node's 4MB buffer; RotateTransferPayload runs at transfer Init and
2026-07-19 13:42:51 -07:00
// swaps to a fresh node once records reference the current one.
// AdoptTransferBuffer (run by the staging object at construction) hands
// m_tr's original buffer to the channel as node 0 (the dtor nulls m_tr.buff
// before the arena walk so it isn't freed twice).
2026-07-19 12:56:35 -07:00
GSBackQueue :: PayloadNode * m_tr_payload_node = nullptr ;
bool m_tr_payload_referenced = false ;
2026-07-19 13:42:51 -07:00
void AdoptTransferBuffer ();
2026-07-19 12:56:35 -07:00
GSBackQueue :: PayloadNode * AcquirePayloadNode ();
void RotateTransferPayload ();
void ExecReleasePayloadRecord ( const GSBackQueue :: ReleasePayloadRecord & rec );
2026-07-19 13:05:11 -07:00
// GV7-1d: the back thread (modes Lockstep and, for now, Pipelined — true
// pipelining needs the front-object split, so Pipelined runs lockstep until
// then). Lockstep = drain after every push, which is what makes executing
// against the shared single-object state safe. VSYNC records are NOT queued:
// present runs on the MTGS thread after a drain, so the back thread never
// touches the GSDevice on present paths (and for SW, at all). Queued modes
// engage only for Vulkan and SW renderers — a GL device is context-bound to
// the MTGS thread and HW draws would issue GL calls from the wrong thread.
bool m_back_queued = false ;
bool m_back_lockstep = false ;
std :: thread m_back_thread ;
std :: atomic < bool > m_back_thread_exit { false };
void StartBackThread ();
void StopBackThread ();
void BackThreadLoop ();
void ExecRecordSlot ( const GSBackQueue :: RecordSlot & slot );
virtual void ExecVsyncRecord ( const GSBackQueue :: VsyncRecord & rec );
template < typename T >
void PushRecord ( GSBackQueue :: RecordType type , const T & rec )
{
for (;;)
{
2026-07-19 13:42:51 -07:00
GSBackQueue :: RecordSlot * slot = m_chan -> ring . BeginPush ();
2026-07-19 13:05:11 -07:00
if ( slot )
{
slot -> type = type ;
std :: memcpy ( slot -> As < T > (), & rec , sizeof ( T ));
2026-07-19 13:42:51 -07:00
m_chan -> ring . CommitPush ();
m_chan -> sema . NotifyOfWork ();
2026-07-19 13:05:11 -07:00
break ;
}
std :: this_thread :: yield (); // ring full — backpressure
}
2026-07-19 13:28:29 -07:00
// Spin-then-sleep: records usually execute in microseconds, so the spin
// catches nearly every drain without the futex round-trip. Lockstep is
// still per-record synchronization and inherently slow (measured 30->6
// fps on MQ65 with plain WaitForEmpty) — it's the bisect rung, not a
// shipping mode.
2026-07-19 13:05:11 -07:00
if ( m_back_lockstep )
2026-07-19 13:42:51 -07:00
m_chan -> sema . WaitForEmptyWithSpin ();
2026-07-19 13:05:11 -07:00
}
2026-01-25 08:35:36 +00:00
GSVector4i GetTEX0Rect ( GSDrawingContext prev_ctx );
2023-08-03 19:14:20 +01:00
void CheckWriteOverlap ( bool req_write , bool req_read );
2021-08-30 21:45:52 -05:00
void Write ( const u8 * mem , int len );
void Read ( u8 * mem , int len );
void InitReadFIFO ( u8 * mem , int len );
2009-02-09 21:15:56 +00:00
2021-08-30 21:45:52 -05:00
void SoftReset ( u32 mask );
void WriteCSR ( u32 csr ) { m_regs -> CSR . U32 [ 1 ] = csr ; }
void ReadFIFO ( u8 * mem , int size );
2022-05-27 18:53:14 +10:00
void ReadLocalMemoryUnsync ( u8 * mem , int qwc , GIFRegBITBLTBUF BITBLTBUF , GIFRegTRXPOS TRXPOS , GIFRegTRXREG TRXREG );
2026-07-25 00:48:56 -04:00
// Asynchronous-readback shadow. Every accessor routes through m_mem_target so the front
// parser object and the back renderer object always agree on the one authoritative shadow.
GSLocalMemory & GetAsyncReadbackMemory () { return * m_mem_target -> m_async_readback_mem ; }
std :: mutex & GetAsyncReadbackMutex () { return m_mem_target -> m_async_readback_mutex ; }
bool IsAsyncReadbackReady () const
{
return m_mem_target -> m_async_readback_ready . load ( std :: memory_order_acquire );
}
/// Snapshot of the per-page write generations, taken when a GPU download is queued.
std :: array < u64 , GS_MAX_PAGES > CaptureAsyncReadbackPageGenerations ();
/// False when any page covered by (TEX0, rect) was written after `generations` was taken,
/// i.e. a CPU upload or local->local move superseded the in-flight download.
bool AreAsyncReadbackPagesCurrent ( const std :: array < u64 , GS_MAX_PAGES >& generations ,
const GIFRegTEX0 & TEX0 , const GSVector4i & rect );
/// Marks (TEX0, rect) written. Caller must already hold GetAsyncReadbackMutex().
void MarkAsyncReadbackPagesWrittenLocked ( const GIFRegTEX0 & TEX0 , const GSVector4i & rect );
/// Re-seeds the whole shadow from live local memory (boot, savestate load, mode enable).
void SyncAsyncReadbackMemory ();
2021-08-30 21:45:52 -05:00
template < int index > void Transfer ( const u8 * mem , u32 size );
2021-06-15 17:54:24 +02:00
int Freeze ( freezeData * fd , bool sizeonly );
int Defrost ( const freezeData * fd );
2021-12-19 00:43:50 +10:00
u8 * GetRegsMem () const { return reinterpret_cast < u8 *> ( m_regs ); }
void SetRegsMem ( u8 * basemem ) { m_regs = reinterpret_cast < GSPrivRegSet *> ( basemem ); }
2025-08-24 15:31:34 -04:00
void DumpDrawInfo ( bool dump_regs , bool dump_verts , bool dump_transfers );
2022-03-13 04:48:16 +00:00
void DumpVertices ( const std :: string & filename );
2025-08-24 19:37:24 -04:00
void DumpTransferList ( const std :: string & filename );
void DumpTransferImages ();
2025-09-23 10:59:41 -04:00
template < bool shuffle_check >
bool TrianglesAreQuadsImpl ();
2025-04-04 21:44:07 +01:00
bool TrianglesAreQuads ( bool shuffle_check = false );
2025-09-23 11:08:21 -04:00
template < u32 primclass >
2026-05-13 20:13:19 -04:00
PRIM_OVERLAP GetPrimitiveOverlapDrawlistImpl ( bool save_drawlist = false , bool save_bbox = false ,
float bbox_scale = 1.0f , u32 * max_size = nullptr );
PRIM_OVERLAP GetPrimitiveOverlapDrawlist ( bool save_drawlist = false , bool save_bbox = false ,
float bbox_scale = 1.0f , u32 * max_size = nullptr );
2025-09-23 11:08:21 -04:00
PRIM_OVERLAP PrimitiveOverlap ( bool save_drawlist = false );
2024-06-06 13:47:55 +01:00
bool SpriteDrawWithoutGaps ();
2024-06-29 17:53:14 +01:00
void CalculatePrimitiveCoversWithoutGaps ();
2022-02-17 19:27:12 +10:00
GIFRegTEX0 GetTex0Layer ( u32 lod );
2009-02-09 21:15:56 +00:00
};
2023-04-03 02:08:09 +10:00
2026-07-19 14:20:49 -07:00
// GV7-1d-ii: the front parser object of the two-object pipelined split
// (SEAM-AUDIT.md §7). Owns all parse state (env, vertex kick, draw buffering,
// transfer staging, CLUT decision) and emits records into the back renderer's
// channel; the back object executes them on the back thread, installing record
// state into its own members. The front never draws, and reaches the
// authoritative local memory / texture cache only through m_mem_target after a
// drain. Created by GS.cpp only when the back thread engaged under
// GSBackThreadMode::Pipelined.
class GSFrontState final : public GSState
{
public :
GSFrontState ( GSState * back );
~ GSFrontState () override ;
void Draw () override ;
2026-07-19 14:33:20 -07:00
// Kick-time coverage-alpha query. Mixed live/stale semantics (see the
// implementation); needs last-flushed-draw state that only exists after
// that draw EXECUTED, so it drains the back queue — memoized per
// (draw epoch, live ALPHA) so at most one drain per AA1 draw.
2026-07-19 14:20:49 -07:00
bool IsCoverageAlphaSupported () override ;
// Once per frame, after the (drained) vsync executed on the back object:
// re-mirror present-side state the back mutated (Merge's scanmask
// decrement) so next frame's front digestion sees what a single object
// would have.
void MirrorPostVsyncState ();
private :
GSState * m_back ;
2026-07-19 14:33:20 -07:00
// IsCoverageAlphaSupported memo (see above).
u64 m_cov_epoch = ~ 0ULL ;
u64 m_cov_alpha = 0 ;
bool m_cov_answer = false ;
2026-07-19 14:20:49 -07:00
};
extern std :: unique_ptr < GSFrontState > g_gs_front ;
2023-04-03 02:08:09 +10:00
// We put this in the header because of Multi-ISA.
inline void GSState :: ExpandDIMX ( GSVector4i * dimx , const GIFRegDIMX DIMX )
{
dimx [ 1 ] = GSVector4i ( DIMX . DM00 , 0 , DIMX . DM01 , 0 , DIMX . DM02 , 0 , DIMX . DM03 , 0 );
dimx [ 0 ] = dimx [ 1 ]. xxzzlh ();
dimx [ 3 ] = GSVector4i ( DIMX . DM10 , 0 , DIMX . DM11 , 0 , DIMX . DM12 , 0 , DIMX . DM13 , 0 );
dimx [ 2 ] = dimx [ 3 ]. xxzzlh ();
dimx [ 5 ] = GSVector4i ( DIMX . DM20 , 0 , DIMX . DM21 , 0 , DIMX . DM22 , 0 , DIMX . DM23 , 0 );
dimx [ 4 ] = dimx [ 5 ]. xxzzlh ();
dimx [ 7 ] = GSVector4i ( DIMX . DM30 , 0 , DIMX . DM31 , 0 , DIMX . DM32 , 0 , DIMX . DM33 , 0 );
dimx [ 6 ] = dimx [ 7 ]. xxzzlh ();
}