Files
libretroadmin 49648886d5 Log straight to the frontend's log_cb
The log macros call log_cb, and nothing more: no log manager, no
second formatting into a buffer of the core's, no locale switched, no
file, line or part of the machine in the line. Each format ends with
its "\n". INFO_LOG and DEBUG_LOG are left out of release builds, as
they were. The tests that stood in for GenericLog() set log_cb.

tools/log/log_check.py fails on a log format without its "\n", in
headless.yml.
2026-11-14 02:42:03 +01:00

5180 lines
149 KiB
C++

/*
TA-VTX handling
Parsing of the TA stream and generation of vertex data !
*/
#include "ta_index.h"
#include "ta.h"
#include "bg_plane.h"
#include "ta_ctx.h"
#include "pvr_mem.h"
#include "Renderer_if.h"
#include "rend/tile/pvr_tile.h"
#include "rend/ta_gpu.h"
#include "rend/sorter.h"
#include "rend/shadows.h"
#include "rend/modvol_clip.h"
#include <retro_atomic.h>
/* The larger and the smaller of two, as std::max() and std::min() have
* them (which of two equal ones, and what a NaN does, included). */
#define ta_max(a, b) ((a) < (b) ? (b) : (a))
#define ta_min(a, b) ((b) < (a) ? (b) : (a))
#include <cmath>
#include <algorithm>
// TODO/FIXME - should be moved later
u32 _pal_rev_256[4]={0};
u32 _pal_rev_16[64]={0};
u32 pal_rev_256[4]={0};
u32 pal_rev_16[64]={0};
//u32 palette_ram[1024];
u32 decoded_colors[3][65536];
extern rend_context vd_rc;
extern int screen_height;
#define TACALL DYNACALL
//cache state vars
static u32 tileclip_val = 0;
/* Whether flat strips are looked for (ta_set_flat_strips()), and the AND
* and the OR of the bits of the depths of the strip being decoded so far,
* for whether it is flat (PVR_FLAT_RATE_BIT()): the strip's end puts the
* answer in its control word's single-rate bit. Looked for, the vertices
* are decoded by another copy of the decoders, which does that; not, by
* the ones that do not, and nothing on the way costs a thing - a
* polygon's single-rate bit is then what the game left in that reserved
* bit, and only the sprites' and the background's mean anything. */
static bool flat_strips_wanted;
static u32 strip_z_and = PVR_FLAT_AND_INIT;
static u32 strip_z_or = PVR_FLAT_OR_INIT;
/* (from the thread that renders, which is the one that parses) */
void ta_set_flat_strips(bool on)
{
flat_strips_wanted = on;
}
bool ta_flat_strips(void)
{
return flat_strips_wanted;
}
/* Whether the region array's table of tiles is built (rend_context's
* region_tiles), from the same thread */
static bool region_table_wanted;
void ta_set_region_table(bool on)
{
region_table_wanted = on;
}
/* Whether the raster renderers' modifier volume triangles are made
* (rend_context's mv_clip), from the same thread */
static bool modvol_clip_wanted;
void ta_set_modvol_clip(bool on)
{
modvol_clip_wanted = on;
}
/* A colour component given as a float, as the hardware takes it: clamped to
* 0..1 (anything negative is 0; anything above 1, infinity and NaN are 1).
*
* NaN with either sign. The SH4's own NaN has the sign bit clear, and one
* a game computes there counts as above 1; computed on this host it can
* come out with the bit set, and was taken for negative: black where the
* console has full brightness (upstream: Gaiamaster Kessen!'s textures). */
static inline float saturate01(float val)
{
const s32 bits = (s32&)val;
if (bits < 0)
return (bits & 0x7fffffff) > 0x7f800000 ? 1.f : 0.f;
return bits > 0x3f800000 ? 1.f : val;
}
/* ...and as eight bits. This used to go through a table of 65536 entries
* looked up by the top 16 bits of the float, which leaves seven bits of
* mantissa: any colour from a half up came out as the value for the start
* of its 1/256th, one too low about half the time. */
static inline u8 float_to_satu8(float val)
{
return (u8)(saturate01(val) * 255.f);
}
//vdec state variables
static ModTriangle* lmr;
static PolyParam* CurrentPP;
static List<PolyParam>* CurrentPPlist;
/* ...and its strips the fast vertex loop knows to be whole and drawable */
static ta_strip_ok_t* CurrentStripOk;
//TA state vars
DECL_ALIGN(4) static u8 FaceBaseColor[4];
DECL_ALIGN(4) static u8 FaceOffsColor[4];
DECL_ALIGN(4) static u8 FaceBaseColor1[4];
DECL_ALIGN(4) static u8 FaceOffsColor1[4];
/* The same face colours as they were given, floats clamped to 0..1, for the
* intensity modes: red, green, blue. */
static float FaceBaseColorF[3];
static float FaceOffsColorF[3];
static float FaceBaseColor1F[3];
static float FaceOffsColor1F[3];
static u32 SFaceBaseColor;
static u32 SFaceOffsColor;
//misc ones
const u32 ListType_None = -1;
const u32 SZ32 = 1;
const u32 SZ64 = 2;
#include "ta_structs.h"
typedef Ta_Dma* DYNACALL TaListFP(Ta_Dma* data,Ta_Dma* data_end);
typedef void TACALL TaPolyParamFP(void* ptr);
static TaListFP* TaCmd;
static u32 CurrentList;
static TaListFP* VertexDataFP;
// (by list type as a parameter has it, three bits: 5 to 7 are no list, and can be asked for all the same)
static bool ListIsFinished[8];
static INLINE f32 f16(u16 v)
{
u32 z=v<<16;
return *(f32*)&z;
}
#define vdrc vd_rc
/* For the fast vertex loop (ta_poly_data): position straight to a vertex
* that is already there, and a packed colour turned from the PowerVR's
* blue, green, red, alpha to red, green, blue, alpha in one word where
* the host has its bytes in that order. */
#define vert_fast_base \
cv->x = vtx->xyz[0]; \
cv->y = vtx->xyz[1]; \
cv->z = vtx->xyz[2];
#if !defined(MSB_FIRST) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
#include <emmintrin.h>
#include <stddef.h>
#define TA_VERT_SSE2 1
/* (the vertex's fields where that code takes them to be) */
typedef char ta_vert_layout[(offsetof(Vertex, col) == 12 && offsetof(Vertex, vtx_spc) == 16
&& offsetof(Vertex, u) == 20 && offsetof(Vertex, v) == 24) ? 1 : -1];
#endif
/* A colour given as four floats - alpha, red, green, blue, the order the
* game sends them in - to the vertex's red, green, blue, alpha bytes, all
* four at once: float_to_satu8() of each, bit for bit, for every float
* there is. The clamp is done on the floats' bits, as saturate01() does it. */
#if defined(TA_VERT_SSE2)
#define TA_COLOUR_SIMD 1
static inline void float_argb_to_satu8(u8 *to, const void *argb)
{
const __m128i bits = _mm_loadu_si128((const __m128i *)argb);
/* above 1, infinity and NaN with either sign */
const __m128i one = _mm_or_si128(_mm_cmpgt_epi32(bits, _mm_set1_epi32(0x3f800000)),
_mm_cmpgt_epi32(_mm_and_si128(bits, _mm_set1_epi32(0x7fffffff)), _mm_set1_epi32(0x7f800000)));
/* anything else with the sign set is 0 */
__m128i c = _mm_andnot_si128(_mm_srai_epi32(bits, 31), bits);
c = _mm_or_si128(_mm_andnot_si128(one, c), _mm_and_si128(one, _mm_set1_epi32(0x3f800000)));
c = _mm_cvttps_epi32(_mm_mul_ps(_mm_castsi128_ps(c), _mm_set1_ps(255.f)));
c = _mm_shuffle_epi32(c, _MM_SHUFFLE(0, 3, 2, 1));
c = _mm_packs_epi32(c, c);
c = _mm_packus_epi16(c, c);
const u32 rgba = (u32)_mm_cvtsi128_si32(c);
memcpy(to, &rgba, 4);
}
#elif !defined(MSB_FIRST) && (defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(_M_ARM64))
#include <arm_neon.h>
#define TA_COLOUR_SIMD 1
static inline void float_argb_to_satu8(u8 *to, const void *argb)
{
const int32x4_t bits = vreinterpretq_s32_u8(vld1q_u8((const u8 *)argb));
/* above 1, infinity and NaN with either sign */
const uint32x4_t one = vorrq_u32(vcgtq_s32(bits, vdupq_n_s32(0x3f800000)),
vcgtq_s32(vandq_s32(bits, vdupq_n_s32(0x7fffffff)), vdupq_n_s32(0x7f800000)));
/* anything else with the sign set is 0 */
int32x4_t c = vbicq_s32(bits, vshrq_n_s32(bits, 31));
c = vbslq_s32(one, vdupq_n_s32(0x3f800000), c);
const uint16x4_t n = vmovn_u32(vcvtq_u32_f32(vmulq_n_f32(vreinterpretq_f32_s32(c), 255.f)));
const uint8x8_t b = vmovn_u16(vcombine_u16(n, n)); /* A R G B A R G B */
const u32 rgba = vget_lane_u32(vreinterpret_u32_u8(vext_u8(b, b, 1)), 0);
memcpy(to, &rgba, 4);
}
#endif
#ifdef MSB_FIRST
#define vert_fast_packed_color(to,src) vert_packed_color_(cv->to,vtx->src)
#else
#define vert_fast_packed_color(to,src) \
{ \
const u32 t = vtx->src; \
*(u32 *)cv->to = (t & 0xff00ff00u) | ((t >> 16) & 0xffu) | ((t & 0xffu) << 16); \
}
#endif
//Splitter function (normally ta_dma_main , modified for split dma's)
class FifoSplitter
{
static const u32 *ta_type_lut;
/* The vertex decoders by vertex type: [1] the ones that look for flat
* strips, and which of the two this frame uses */
static TaListFP* const poly_data_luts[2][15];
static TaListFP* const* poly_data_lut;
static void ta_list_start(u32 new_list)
{
//verify(CurrentList==ListType_None);
//verify(ListIsFinished[new_list]==false);
//printf("Starting list %d\n",new_list);
CurrentList=new_list;
StartList(CurrentList);
}
static Ta_Dma* DYNACALL NullVertexData(Ta_Dma* data,Ta_Dma* data_end)
{
INFO_LOG(PVR, "TA: Invalid state, ignoring VTX data\n");
return data+SZ32;
}
//part : 0 fill all data , 1 fill upper 32B , 2 fill lower 32B
//Poly decoder , will be moved to pvr code
template <u32 poly_type,u32 part>
__forceinline
static Ta_Dma* TACALL ta_handle_poly(Ta_Dma* data,Ta_Dma* data_end)
{
TA_VertexParam* vp=(TA_VertexParam*)data;
u32 rv=0;
if (part==2)
{
TaCmd=ta_main;
}
switch (poly_type)
{
#define ver_32B_def(num) \
case num : {\
AppendPolyVertex##num(&vp->vtx##num);\
rv=SZ32; }\
break;
//32b , always in one pass :)
ver_32B_def(0);//(Non-Textured, Packed Color)
ver_32B_def(1);//(Non-Textured, Floating Color)
ver_32B_def(2);//(Non-Textured, Intensity)
ver_32B_def(3);//(Textured, Packed Color)
ver_32B_def(4);//(Textured, Packed Color, 16bit UV)
ver_32B_def(7);//(Textured, Intensity)
ver_32B_def(8);//(Textured, Intensity, 16bit UV)
ver_32B_def(9);//(Non-Textured, Packed Color, with Two Volumes)
ver_32B_def(10);//(Non-Textured, Intensity, with Two Volumes)
#undef ver_32B_def
#define ver_64B_def(num) \
case num : {\
/*process first half*/\
if (part!=2)\
{\
rv+=SZ32;\
AppendPolyVertex##num##A(&vp->vtx##num##A);\
}\
/*process second half*/\
if (part==0)\
{\
AppendPolyVertex##num##B(&vp->vtx##num##B);\
rv+=SZ32;\
}\
else if (part==2)\
{\
AppendPolyVertex##num##B((TA_Vertex##num##B*)data);\
rv+=SZ32;\
}\
}\
break;
//64b , may be on 2 pass
ver_64B_def(5);//(Textured, Floating Color)
ver_64B_def(6);//(Textured, Floating Color, 16bit UV)
ver_64B_def(11);//(Textured, Packed Color, with Two Volumes)
ver_64B_def(12);//(Textured, Packed Color, 16bit UV, with Two Volumes)
ver_64B_def(13);//(Textured, Intensity, with Two Volumes)
ver_64B_def(14);//(Textured, Intensity, 16bit UV, with Two Volumes)
#undef ver_64B_def
}
return data+rv;
};
//Code Splitter/routers
//helper function for dummy dma's.Handles 32B and then switches to ta_main for next data
static Ta_Dma* TACALL ta_dummy_32(Ta_Dma* data,Ta_Dma* data_end)
{
TaCmd=ta_main;
return data+SZ32;
}
static Ta_Dma* TACALL ta_modvolB_32(Ta_Dma* data,Ta_Dma* data_end)
{
AppendModVolVertexB((TA_ModVolB*)data);
TaCmd=ta_main;
return data+SZ32;
}
static Ta_Dma* TACALL ta_mod_vol_data(Ta_Dma* data,Ta_Dma* data_end)
{
TA_VertexParam* vp=(TA_VertexParam*)data;
if (data==data_end)
{
AppendModVolVertexA(&vp->mvolA);
//32B more needed , 32B done :)
TaCmd=ta_modvolB_32;
return data+SZ32;
}
else
{
//all 64B done
AppendModVolVertexA(&vp->mvolA);
AppendModVolVertexB(&vp->mvolB);
return data+SZ64;
}
}
static Ta_Dma* TACALL ta_spriteB_data(Ta_Dma* data,Ta_Dma* data_end)
{
//32B more needed , 32B done :)
TaCmd=ta_main;
AppendSpriteVertexB((TA_Sprite1B*)data);
return data+SZ32;
}
static Ta_Dma* TACALL ta_sprite_data(Ta_Dma* data,Ta_Dma* data_end)
{
//verify(data->pcw.ParaType==ParamType_Vertex_Parameter);
if (data==data_end)
{
//32B more needed , 32B done :)
TaCmd=ta_spriteB_data;
TA_VertexParam* vp=(TA_VertexParam*)data;
AppendSpriteVertexA(&vp->spr1A);
return data+SZ32;
}
else
{
TA_VertexParam* vp=(TA_VertexParam*)data;
AppendSpriteVertexA(&vp->spr1A);
AppendSpriteVertexB(&vp->spr1B);
return data+SZ64;
}
}
/* The bits of the depth (1/w) of the vertex parameter at @data: every
* kind has it at the same place, in its first 32 bytes */
static inline u32 ta_vertex_z_bits(const Ta_Dma* data)
{
u32 z;
memcpy(&z, &((const TA_VertexParam*)data)->vtx0.xyz[2], sizeof(z));
return z;
}
/* @flat: the strips' flatness is worked out as they go (flat_strips_wanted) */
template <u32 poly_type,u32 poly_size,bool flat>
static Ta_Dma* TACALL ta_poly_data(Ta_Dma* data,Ta_Dma* data_end)
{
//If SZ64 && 32 bytes
#define IS_FIST_HALF ((poly_size!=SZ32) && (data==data_end))
/* (a strip that starts here starts from nothing: whatever a strip the
* game never ended left is not part of it) */
if (flat && CurrentPP != NULL && CurrentPP->first == (u32)vdrc.verts.used())
{
strip_z_and = PVR_FLAT_AND_INIT;
strip_z_or = PVR_FLAT_OR_INIT;
}
/* The formats nearly every vertex comes in - packed colour or
* intensity, with or without a texture - when the vertex list has
* room for all there are here: the list's place and the farthest
* depth are kept in registers for the strip, where the loop below
* reads and writes them in memory for every vertex (a vertex's
* colours are stored as bytes, which the compiler has to take as
* possibly being those very variables). The same vertices come out. */
if (poly_size == SZ32
&& (poly_type == 0 || poly_type == 2 || poly_type == 3 || poly_type == 4 || poly_type == 7 || poly_type == 8)
&& (ptrdiff_t)(data_end - data) < (ptrdiff_t)vdrc.verts.avail())
{
Vertex* const first = vdrc.verts.cur();
Vertex* const head = vdrc.verts.head();
Vertex* cv = first;
PolyParam* pp = CurrentPP;
s32 z_max = (s32&)vdrc.fZ_max;
u32 z_and = strip_z_and;
u32 z_or = strip_z_or;
/* Whether a vertex of the strip is left out of its indices
* (ta_vertex_bad()), from the words in hand: a strip begun here
* with none is marked known good, and make_index() reads none
* of its vertices again. One begun before, by another loop, is
* left to it. */
ta_strip_ok_t* const ok = CurrentStripOk;
PolyParam* const pp_head = CurrentPPlist != NULL ? CurrentPPlist->head() : NULL;
const u32 run_first = (u32)(first - head);
ta_bad_t bad = ta_bad_none();
for (;;)
{
s32 z;
bool end_of_strip;
bad = ta_bad_add(bad, (const u8*)data + 4);
vert_fast<poly_type>(data, cv);
z = (s32&)cv->z;
if (z_max < z && z < 0x49800000)
z_max = z;
if (flat)
{
z_and &= (u32)z;
z_or |= (u32)z;
}
cv++;
end_of_strip = data->pcw.EndOfStrip;
data++;
if (end_of_strip)
{
/* EndPolyStrip(), from what is in hand. The strip's
* parameters are read for the copy before its count is
* written: read after, the wide loads of the copy wait
* for that narrow store to reach memory. */
const u32 used = (u32)(cv - head);
const u32 count = used - pp->first;
const u32 pcw = flat ? (pp->pcw.full & ~PVR_PCW_SINGLE_RATE) | PVR_FLAT_RATE_BIT(z_and, z_or) : 0;
if (!ta_bad_any(bad) && pp->first >= run_first && ok != NULL)
ta_strip_ok_set(ok, (u32)(pp - pp_head));
bad = ta_bad_none();
if (count > 0)
{
const PolyParam copy = *pp;
PolyParam* d_pp = CurrentPPlist->Append();
pp->count = count;
if (flat)
pp->pcw.full = pcw; // (the copy's strip sets its own)
*d_pp = copy;
d_pp->first = used;
d_pp->count = 0;
pp = d_pp;
}
else
pp->count = count;
if (flat)
{
z_and = PVR_FLAT_AND_INIT;
z_or = PVR_FLAT_OR_INIT;
}
/* The next strip of the same polygon follows at once,
* more often than not: it is gone on with here, where
* ta_main would only come back for it. */
if (data > data_end || data->pcw.ParaType != ParamType_Vertex_Parameter)
break;
}
else if (data > data_end)
break;
}
vdrc.verts.set_cur(cv);
(s32&)vdrc.fZ_max = z_max;
if (flat)
{
strip_z_and = z_and;
strip_z_or = z_or;
}
CurrentPP = pp;
TaCmd=ta_main;
return data;
}
/* (with @flat, the strip's depths kept in registers here, and in
* memory only from one call to the next) */
u32 z_and = strip_z_and;
u32 z_or = strip_z_or;
if (IS_FIST_HALF)
goto fist_half;
do
{
ta_handle_poly<poly_type,0>(data, 0);
if (flat)
{
z_and &= ta_vertex_z_bits(data);
z_or |= ta_vertex_z_bits(data);
}
if (data->pcw.EndOfStrip)
goto strip_end;
data += poly_size;
} while (poly_size == SZ32 ? data <= data_end : data < data_end);
if (flat)
{
strip_z_and = z_and;
strip_z_or = z_or;
}
if (IS_FIST_HALF)
{
fist_half:
ta_handle_poly<poly_type,1>(data,0);
if (flat)
{
strip_z_and &= ta_vertex_z_bits(data);
strip_z_or |= ta_vertex_z_bits(data);
}
if (data->pcw.EndOfStrip) EndPolyStrip<flat>();
TaCmd=ta_handle_poly<poly_type,2>;
data+=SZ32;
}
return data;
strip_end:
if (flat)
{
strip_z_and = z_and;
strip_z_or = z_or;
}
TaCmd=ta_main;
if (data->pcw.EndOfStrip)
EndPolyStrip<flat>();
return data+poly_size;
}
static void TACALL AppendPolyParam2Full(void* vpp)
{
Ta_Dma* pp=(Ta_Dma*)vpp;
AppendPolyParam2A((TA_PolyParam2A*)&pp[0]);
AppendPolyParam2B((TA_PolyParam2B*)&pp[1]);
}
static void TACALL AppendPolyParam4Full(void* vpp)
{
Ta_Dma* pp=(Ta_Dma*)vpp;
AppendPolyParam4A((TA_PolyParam4A*)&pp[0]);
AppendPolyParam4B((TA_PolyParam4B*)&pp[1]);
}
//Second part of poly data
template <int t>
static Ta_Dma* TACALL ta_poly_B_32(Ta_Dma* data,Ta_Dma* data_end)
{
if (t==2)
AppendPolyParam2B((TA_PolyParam2B*)data);
else
AppendPolyParam4B((TA_PolyParam4B*)data);
TaCmd=ta_main;
return data+SZ32;
}
//Group_En bit seems ignored, thanks p1pkin
#define group_EN() /*if (data->pcw.Group_En) */{ TileClipMode(data->pcw.User_Clip);}
static Ta_Dma* TACALL ta_main(Ta_Dma* data,Ta_Dma* data_end)
{
do
{
switch (data->pcw.ParaType)
{
//Control parameter
//32Bw3
case ParamType_End_Of_List:
{
if (CurrentList==ListType_None)
{
CurrentList=data->pcw.ListType;
//printf("End_Of_List : list error\n");
}
else
{
//end of list should be all 0's ...
EndList(CurrentList);//end a list olny if it was realy started
}
//printf("End list %X\n",CurrentList);
ListIsFinished[CurrentList]=true;
CurrentList=ListType_None;
VertexDataFP=NullVertexData;
data+=SZ32;
}
break;
//32B
case ParamType_User_Tile_Clip:
{
SetTileClip(data->data_32[3]&63,data->data_32[4]&31,data->data_32[5]&63,data->data_32[6]&31);
data+=SZ32;
}
break;
//32B
case ParamType_Object_List_Set:
{
INFO_LOG(PVR, "Unsupported list type: ParamType_Object_List_Set\n"); // NAOMI Virtual on Oratorio Tangram
// *cough* ignore it :p
data+=SZ32;
}
break;
//Global Parameter
//ModVolue :32B
//PolyType :32B/64B
case ParamType_Polygon_or_Modifier_Volume:
{
group_EN();
//Yep , C++ IS lame & limited
#include "ta_const_df.h"
if (CurrentList == ListType_None && data->pcw.ListType > ListType_Punch_Through)
{
/* A list of a type there is none of (5 to 7): there
* is nowhere for its polygons to go, and the first
* of them used to be put there all the same -
* through a null pointer. Passed over, as upstream
* does (a0b50cd41, from a crash report). */
WARN_LOG(PVR, "Invalid TA list type %d\n", data->pcw.ListType);
data += SZ32;
break;
}
if (CurrentList==ListType_None)
ta_list_start(data->pcw.ListType); //start a list ;)
if (IsModVolList(CurrentList))
{
//accept mod data
StartModVol((TA_ModVolParam*)data);
VertexDataFP=ta_mod_vol_data;
data+=SZ32;
}
else
{
u32 uid = ta_type_lut[data->pcw.obj_ctrl];
u32 psz=uid>>30;
u32 pdid=(u8)(uid);
u32 ppid=(u8)(uid>>8);
if (uid == 0xFFFFFFFF)
{
/* A polygon of a type there is no such thing as.
* Its numbers would index past the ends of the
* tables below: it is passed over. */
WARN_LOG(PVR, "Invalid TA type %08x\n", data->pcw.full);
data+=SZ32;
continue;
}
VertexDataFP=poly_data_lut[pdid];
if (data != data_end || psz==1)
{
//poly , 32B/64B
ta_poly_param_lut[ppid](data);
data+=psz;
}
else
{
//AppendPolyParam64A((TA_PolyParamA*)data);
//64b , first part
ta_poly_param_a_lut[ppid](data);
//Handle next 32B ;)
TaCmd=ta_poly_param_b_lut[ppid];
data+=SZ32;
}
}
}
break;
//32B
//Sets Sprite info , and switches to ta_sprite_data function
case ParamType_Sprite:
{
group_EN();
if (CurrentList == ListType_None && data->pcw.ListType > ListType_Punch_Through)
{
// (as for a polygon)
WARN_LOG(PVR, "Invalid TA list type %d\n", data->pcw.ListType);
data += SZ32;
break;
}
if (CurrentList==ListType_None)
ta_list_start(data->pcw.ListType); //start a list ;)
VertexDataFP=ta_sprite_data;
AppendSpriteParam((TA_SpriteParam*)data);
data+=SZ32;
}
break;
//Variable size
case ParamType_Vertex_Parameter:
data = VertexDataFP(data, data_end);
break;
//not handled
//Assumed to be 32B
case 3:
case 6:
{
/* Passed over, which is what was meant: die() ended
* the emulator before the line after it was reached. */
WARN_LOG(PVR, "Unhandled TA parameter %08x\n", data->pcw.full);
data+=SZ32;
}
break;
}
}
while (data <= data_end);
return data;
}
public:
//Fill in lookup table
FifoSplitter()
{
VertexDataFP = NullVertexData;
ta_type_lut = TaTypeLut::instance().table;
}
/*
Volume,Col_Type,Texture,Offset,Gouraud,16bit_UV
0 0 0 (0) x invalid Polygon Type 0 Polygon Type 0
0 0 1 x x 0 Polygon Type 0 Polygon Type 3
0 0 1 x x 1 Polygon Type 0 Polygon Type 4
0 1 0 (0) x invalid Polygon Type 0 Polygon Type 1
0 1 1 x x 0 Polygon Type 0 Polygon Type 5
0 1 1 x x 1 Polygon Type 0 Polygon Type 6
0 2 0 (0) x invalid Polygon Type 1 Polygon Type 2
0 2 1 0 x 0 Polygon Type 1 Polygon Type 7
0 2 1 0 x 1 Polygon Type 1 Polygon Type 8
0 2 1 1 x 0 Polygon Type 2 Polygon Type 7
0 2 1 1 x 1 Polygon Type 2 Polygon Type 8
0 3 0 (0) x invalid Polygon Type 0 Polygon Type 2
0 3 1 x x 0 Polygon Type 0 Polygon Type 7
0 3 1 x x 1 Polygon Type 0 Polygon Type 8
1 0 0 (0) x invalid Polygon Type 3 Polygon Type 9
1 0 1 x x 0 Polygon Type 3 Polygon Type 11
1 0 1 x x 1 Polygon Type 3 Polygon Type 12
1 2 0 (0) x invalid Polygon Type 4 Polygon Type 10
1 2 1 x x 0 Polygon Type 4 Polygon Type 13
1 2 1 x x 1 Polygon Type 4 Polygon Type 14
1 3 0 (0) x invalid Polygon Type 3 Polygon Type 10
1 3 1 x x 0 Polygon Type 3 Polygon Type 13
1 3 1 x x 1 Polygon Type 3 Polygon Type 14
Sprites :
(0) (0) 0 (0) (0) invalid Sprite Sprite Type 0
(0) (0) 1 x (0) (1) Sprite Sprite Type 1
*/
//helpers 0-14
static u32 poly_data_type_id(PCW pcw)
{
if (pcw.Texture)
{
//textured
if (pcw.Volume==0)
{ //single volume
if (pcw.Col_Type==0)
{
if (pcw.UV_16bit==0)
return 3; //(Textured, Packed Color , 32b uv)
else
return 4; //(Textured, Packed Color , 16b uv)
}
else if (pcw.Col_Type==1)
{
if (pcw.UV_16bit==0)
return 5; //(Textured, Floating Color , 32b uv)
else
return 6; //(Textured, Floating Color , 16b uv)
}
else
{
if (pcw.UV_16bit==0)
return 7; //(Textured, Intensity , 32b uv)
else
return 8; //(Textured, Intensity , 16b uv)
}
}
else
{
//two volumes
if (pcw.Col_Type==0)
{
if (pcw.UV_16bit==0)
return 11; //(Textured, Packed Color, with Two Volumes)
else
return 12; //(Textured, Packed Color, 16bit UV, with Two Volumes)
}
else if (pcw.Col_Type==1)
{
//die ("invalid");
return 0xFFFFFFFF;
}
else
{
if (pcw.UV_16bit==0)
return 13; //(Textured, Intensity, with Two Volumes)
else
return 14; //(Textured, Intensity, 16bit UV, with Two Volumes)
}
}
}
else
{
//non textured
if (pcw.Volume==0)
{ //single volume
if (pcw.Col_Type==0)
return 0; //(Non-Textured, Packed Color)
else if (pcw.Col_Type==1)
return 1; //(Non-Textured, Floating Color)
else
return 2; //(Non-Textured, Intensity)
}
else
{
//two volumes
if (pcw.Col_Type==0)
return 9; //(Non-Textured, Packed Color, with Two Volumes)
else if (pcw.Col_Type==1)
{
//die ("invalid");
return 0xFFFFFFFF;
}
else
{
return 10; //(Non-Textured, Intensity, with Two Volumes)
}
}
}
}
//0-4 | 0x80
static u32 poly_header_type_size(PCW pcw)
{
if (pcw.Volume == 0)
{
if ( pcw.Col_Type<2 ) //0,1
{
return 0 | 0; //Polygon Type 0 -- SZ32
}
else if ( pcw.Col_Type == 2 )
{
if (pcw.Texture)
{
if (pcw.Offset)
{
return 2 | 0x80; //Polygon Type 2 -- SZ64
}
else
{
return 1 | 0; //Polygon Type 1 -- SZ32
}
}
else
{
return 1 | 0; //Polygon Type 1 -- SZ32
}
}
else //col_type ==3
{
return 0 | 0; //Polygon Type 0 -- SZ32
}
}
else
{
if ( pcw.Col_Type==0 ) //0
{
return 3 | 0; //Polygon Type 3 -- SZ32
}
else if ( pcw.Col_Type==2 ) //2
{
return 4 | 0x80; //Polygon Type 4 -- SZ64
}
else if ( pcw.Col_Type==3 ) //3
{
return 3 | 0; //Polygon Type 3 -- SZ32
}
else
{
return 0xFFDDEEAA;//die ("data->pcw.Col_Type==1 && volume ==1");
}
}
}
void vdec_init()
{
VDECInit();
TaCmd = ta_main;
CurrentList = ListType_None;
ListIsFinished[0] = ListIsFinished[1] = ListIsFinished[2] = ListIsFinished[3] = ListIsFinished[4] = false;
VertexDataFP = NullVertexData;
memset(FaceBaseColor, 0xff, sizeof(FaceBaseColor));
memset(FaceOffsColor, 0xff, sizeof(FaceOffsColor));
memset(FaceBaseColor1, 0xff, sizeof(FaceBaseColor1));
memset(FaceOffsColor1, 0xff, sizeof(FaceOffsColor1));
for (int i = 0; i < 3; i++)
FaceBaseColorF[i] = FaceOffsColorF[i] = FaceBaseColor1F[i] = FaceOffsColor1F[i] = 1.f;
SFaceBaseColor = 0;
SFaceOffsColor = 0;
lmr = NULL;
CurrentPP = NULL;
CurrentPPlist = NULL;
poly_data_lut = poly_data_luts[flat_strips_wanted ? 1 : 0];
strip_z_and = PVR_FLAT_AND_INIT;
strip_z_or = PVR_FLAT_OR_INIT;
/* Until the game gives a clipping rectangle, it is everything: 40
* by 15 tiles. It was whatever the last frame left, at first the
* one tile at the top left, and a polygon that asked to be clipped
* before any rectangle was given was clipped to that (upstream:
* Irides - Master of Blocks). */
SetTileClip(0, 0, 39, 14);
TileClipMode(0);
}
private:
__forceinline
static void SetTileClip(u32 xmin,u32 ymin,u32 xmax,u32 ymax)
{
u32 rv=tileclip_val & 0xF0000000;
rv|=xmin; //6 bits
rv|=xmax<<6; //6 bits
rv|=ymin<<12; //5 bits
rv|=ymax<<17; //5 bits
tileclip_val=rv;
}
__forceinline
static void TileClipMode(u32 mode)
{
tileclip_val=(tileclip_val&(~0xF0000000)) | (mode<<28);
}
//list handling
__forceinline
static void StartList(u32 ListType)
{
if (ListType==ListType_Opaque)
CurrentPPlist=&vdrc.global_param_op, CurrentStripOk=&vdrc.strip_ok[0];
else if (ListType==ListType_Punch_Through)
CurrentPPlist=&vdrc.global_param_pt, CurrentStripOk=&vdrc.strip_ok[1];
else if (ListType==ListType_Translucent)
CurrentPPlist=&vdrc.global_param_tr, CurrentStripOk=&vdrc.strip_ok[2];
CurrentPP = NULL;
}
__forceinline
static void EndList(u32 ListType)
{
if (CurrentPP != NULL && CurrentPP->count == 0)
CurrentPPlist->PopLast();
CurrentPP = NULL;
CurrentPPlist = NULL;
CurrentStripOk = NULL;
if (ListType == ListType_Opaque_Modifier_Volume
|| ListType == ListType_Translucent_Modifier_Volume)
EndModVol();
}
//Polys -- update code on sprites if that gets updated too --
template<class T>
static void glob_param_bdc_(T* pp)
{
PolyParam* d_pp = CurrentPP;
if (d_pp == NULL || d_pp->count != 0)
{
d_pp = CurrentPPlist->Append();
CurrentPP = d_pp;
}
d_pp->first = vdrc.verts.used();
d_pp->count = 0;
d_pp->isp = pp->isp;
d_pp->tsp = pp->tsp;
d_pp->tcw = pp->tcw;
d_pp->pcw = pp->pcw;
d_pp->tileclip = tileclip_val;
d_pp->texid = -1;
if (d_pp->pcw.Texture)
d_pp->texid = renderer->GetTexture(d_pp->tsp,d_pp->tcw);
d_pp->tsp1.full = -1;
d_pp->tcw1.full = -1;
d_pp->texid1 = -1;
}
#define glob_param_bdc(pp) glob_param_bdc_( (TA_PolyParam0*)pp)
#ifdef TA_COLOUR_SIMD
#define poly_float_color_(to,a,r,g,b) \
float_argb_to_satu8(to, &(a));
#else
#define poly_float_color_(to,a,r,g,b) \
to[0] = float_to_satu8(r); \
to[1] = float_to_satu8(g); \
to[2] = float_to_satu8(b); \
to[3] = float_to_satu8(a);
#endif
#define poly_float_color(to,src) \
poly_float_color_(to,pp->src##A,pp->src##R,pp->src##G,pp->src##B) \
to##F[0] = saturate01(pp->src##R); \
to##F[1] = saturate01(pp->src##G); \
to##F[2] = saturate01(pp->src##B);
// Poly param handling
// Packed/Floating Color
__forceinline
static void TACALL AppendPolyParam0(void* vpp)
{
TA_PolyParam0* pp=(TA_PolyParam0*)vpp;
glob_param_bdc(pp);
}
// Intensity, no Offset Color
__forceinline
static void TACALL AppendPolyParam1(void* vpp)
{
TA_PolyParam1* pp=(TA_PolyParam1*)vpp;
glob_param_bdc(pp);
poly_float_color(FaceBaseColor,FaceColor);
}
// Intensity, use Offset Color
__forceinline
static void TACALL AppendPolyParam2A(void* vpp)
{
TA_PolyParam2A* pp=(TA_PolyParam2A*)vpp;
glob_param_bdc(pp);
}
__forceinline
static void TACALL AppendPolyParam2B(void* vpp)
{
TA_PolyParam2B* pp=(TA_PolyParam2B*)vpp;
poly_float_color(FaceBaseColor,FaceColor);
poly_float_color(FaceOffsColor,FaceOffset);
}
// Packed Color, with Two Volumes
__forceinline
static void TACALL AppendPolyParam3(void* vpp)
{
TA_PolyParam3* pp=(TA_PolyParam3*)vpp;
glob_param_bdc(pp);
CurrentPP->tsp1.full = pp->tsp1.full;
CurrentPP->tcw1.full = pp->tcw1.full;
if (pp->pcw.Texture)
CurrentPP->texid1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
}
// Intensity, with Two Volumes
__forceinline
static void TACALL AppendPolyParam4A(void* vpp)
{
TA_PolyParam4A* pp=(TA_PolyParam4A*)vpp;
glob_param_bdc(pp);
CurrentPP->tsp1.full = pp->tsp1.full;
CurrentPP->tcw1.full = pp->tcw1.full;
if (pp->pcw.Texture)
CurrentPP->texid1 = renderer->GetTexture(pp->tsp1, pp->tcw1, 1);
}
__forceinline
static void TACALL AppendPolyParam4B(void* vpp)
{
TA_PolyParam4B* pp=(TA_PolyParam4B*)vpp;
poly_float_color(FaceBaseColor,FaceColor0);
poly_float_color(FaceBaseColor1, FaceColor1);
}
//Poly Strip handling
template <bool flat>
__forceinline
static void EndPolyStrip()
{
PolyParam* const pp = CurrentPP;
CurrentPP->count = vdrc.verts.used() - CurrentPP->first;
if (CurrentPP->count > 0)
{
PolyParam* d_pp = CurrentPPlist->Append();
*d_pp = *CurrentPP;
CurrentPP = d_pp;
d_pp->first = vdrc.verts.used();
d_pp->count = 0;
}
/* (after the copy, whose wide loads would otherwise wait for this
* narrow store to reach memory; the copy's strip sets its own) */
if (flat)
{
pp->pcw.full = (pp->pcw.full & ~PVR_PCW_SINGLE_RATE) | PVR_FLAT_RATE_BIT(strip_z_and, strip_z_or);
strip_z_and = PVR_FLAT_AND_INIT;
strip_z_or = PVR_FLAT_OR_INIT;
}
}
static inline void update_fz(float z)
{
if ((s32&)vdrc.fZ_max<(s32&)z && (s32&)z<0x49800000)
vdrc.fZ_max=z;
}
//Poly Vertex handlers
//Append vertex base
template<class T>
static Vertex* vert_cvt_base_(T* vtx)
{
f32 invW=vtx->xyz[2];
Vertex* cv=vdrc.verts.Append();
cv->x=vtx->xyz[0];
cv->y=vtx->xyz[1];
cv->z=invW;
update_fz(invW);
return cv;
}
#define vert_cvt_base Vertex* cv=vert_cvt_base_((TA_Vertex0*)vtx)
//Resume vertex base (for B part)
#define vert_res_base \
Vertex* cv=vdrc.verts.LastPtr();
//uv 16/32
#define vert_uv_32(u_name,v_name) \
cv->u = (vtx->u_name);\
cv->v = (vtx->v_name);
#define vert_uv_16(u_name,v_name) \
cv->u = f16(vtx->u_name);\
cv->v = f16(vtx->v_name);
#define vert_uv1_32(u_name,v_name) \
cv->u1 = (vtx->u_name);\
cv->v1 = (vtx->v_name);
#define vert_uv1_16(u_name,v_name) \
cv->u1 = f16(vtx->u_name);\
cv->v1 = f16(vtx->v_name);
//Color conversions
#define vert_packed_color_(to,src) \
{ \
u32 t=src; \
to[2] = (u8)(t);t>>=8;\
to[1] = (u8)(t);t>>=8;\
to[0] = (u8)(t);t>>=8;\
to[3] = (u8)(t); \
}
#define vert_face_base_color1(baseint) \
{ const float intensity = saturate01(vtx->baseint) * 255.f; \
cv->col1[0] = (u8)(FaceBaseColor1F[0] * intensity); \
cv->col1[1] = (u8)(FaceBaseColor1F[1] * intensity); \
cv->col1[2] = (u8)(FaceBaseColor1F[2] * intensity); \
cv->col1[3] = FaceBaseColor1[3]; }
#define vert_face_offs_color1(offsint) \
{ const float intensity = saturate01(vtx->offsint) * 255.f; \
cv->spc1[0] = (u8)(FaceOffsColor1F[0] * intensity); \
cv->spc1[1] = (u8)(FaceOffsColor1F[1] * intensity); \
cv->spc1[2] = (u8)(FaceOffsColor1F[2] * intensity); \
cv->spc1[3] = FaceOffsColor1[3]; }
#ifdef TA_COLOUR_SIMD
#define vert_float_color_(to,a,r,g,b) \
float_argb_to_satu8(to, &(a));
#else
#define vert_float_color_(to,a,r,g,b) \
to[0] = float_to_satu8(r); \
to[1] = float_to_satu8(g); \
to[2] = float_to_satu8(b); \
to[3] = float_to_satu8(a);
#endif
//Macros to make thins easier ;)
#define vert_packed_color(to,src) \
vert_packed_color_(cv->to,vtx->src);
#define vert_float_color(to,src) \
vert_float_color_(cv->to,vtx->src##A,vtx->src##R,vtx->src##G,vtx->src##B)
//Intensity handling
//Notes:
//Alpha doesn't get intensity
//Intensity is clamped before the mul, as well as on face color to work the same as the hardware. [Fixes red dog]
//The face colour and the intensity are multiplied as the floats they are
//and the product made eight bits, once. Both used to be made eight
//bits first and multiplied as a/256 fractions, which lost up to two
//levels on the way and could not reach 255: full intensity on a white
//face came out 254.
#define vert_face_base_color(baseint) \
{ const float intensity = saturate01(vtx->baseint) * 255.f; \
cv->col[0] = (u8)(FaceBaseColorF[0] * intensity); \
cv->col[1] = (u8)(FaceBaseColorF[1] * intensity); \
cv->col[2] = (u8)(FaceBaseColorF[2] * intensity); \
cv->col[3] = FaceBaseColor[3]; }
#define vert_face_offs_color(offsint) \
{ const float intensity = saturate01(vtx->offsint) * 255.f; \
cv->vtx_spc[0] = (u8)(FaceOffsColorF[0] * intensity); \
cv->vtx_spc[1] = (u8)(FaceOffsColorF[1] * intensity); \
cv->vtx_spc[2] = (u8)(FaceOffsColorF[2] * intensity); \
cv->vtx_spc[3] = FaceOffsColor[3]; }
//vert_float_color_(cv->vtx_spc,FaceOffsColor[3],FaceOffsColor[0]*satint/256,FaceOffsColor[1]*satint/256,FaceOffsColor[2]*satint/256); }
//(Non-Textured, Packed Color)
__forceinline
static void AppendPolyVertex0(TA_Vertex0* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol);
}
//(Non-Textured, Floating Color)
__forceinline
static void AppendPolyVertex1(TA_Vertex1* vtx)
{
vert_cvt_base;
vert_float_color(col,Base);
}
//(Non-Textured, Intensity)
__forceinline
static void AppendPolyVertex2(TA_Vertex2* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt);
}
//(Textured, Packed Color)
__forceinline
static void AppendPolyVertex3(TA_Vertex3* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol);
vert_packed_color(vtx_spc,OffsCol);
vert_uv_32(u,v);
}
//(Textured, Packed Color, 16bit UV)
__forceinline
static void AppendPolyVertex4(TA_Vertex4* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol);
vert_packed_color(vtx_spc,OffsCol);
vert_uv_16(u,v);
}
//(Textured, Floating Color)
__forceinline
static void AppendPolyVertex5A(TA_Vertex5A* vtx)
{
vert_cvt_base;
//Colors are on B
vert_uv_32(u,v);
}
__forceinline
static void AppendPolyVertex5B(TA_Vertex5B* vtx)
{
vert_res_base;
vert_float_color(col,Base);
vert_float_color(vtx_spc,Offs);
}
//(Textured, Floating Color, 16bit UV)
__forceinline
static void AppendPolyVertex6A(TA_Vertex6A* vtx)
{
vert_cvt_base;
//Colors are on B
vert_uv_16(u,v);
}
__forceinline
static void AppendPolyVertex6B(TA_Vertex6B* vtx)
{
vert_res_base;
vert_float_color(col,Base);
vert_float_color(vtx_spc,Offs);
}
//(Textured, Intensity)
__forceinline
static void AppendPolyVertex7(TA_Vertex7* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt);
vert_face_offs_color(OffsInt);
vert_uv_32(u,v);
}
//(Textured, Intensity, 16bit UV)
__forceinline
static void AppendPolyVertex8(TA_Vertex8* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt);
vert_face_offs_color(OffsInt);
vert_uv_16(u,v);
}
/* One vertex of a 32-byte format to @cv: what AppendPolyVertex<n>() does
* past getting the vertex from the list and looking at its depth. */
template <u32 poly_type>
__forceinline
static void vert_fast(const Ta_Dma* data, Vertex* cv)
{
switch (poly_type)
{
case 0:
{
const TA_Vertex0* vtx = &((const TA_VertexParam*)data)->vtx0;
vert_fast_base;
vert_fast_packed_color(col,BaseCol);
}
break;
case 2:
{
const TA_Vertex2* vtx = &((const TA_VertexParam*)data)->vtx2;
vert_fast_base;
vert_face_base_color(BaseInt);
}
break;
case 3:
#ifdef TA_VERT_SSE2
{
/* The format most vertices are in, and its fields lie in the
* block nearly as they do in the vertex: three loads and
* three stores. Position goes over with the word after it,
* which lands where the colours then go; the two colours
* are turned from blue, green, red, alpha to red, green,
* blue, alpha together. */
const u8* from = (const u8*)data;
const __m128i red_blue = _mm_set1_epi32(0x00ff00ff);
const __m128i xyz = _mm_loadu_si128((const __m128i*)(from + 4));
const __m128i uv = _mm_loadl_epi64((const __m128i*)(from + 16));
__m128i colours = _mm_loadl_epi64((const __m128i*)(from + 24));
const __m128i rb = _mm_and_si128(colours, red_blue);
colours = _mm_or_si128(_mm_andnot_si128(red_blue, colours),
_mm_or_si128(_mm_slli_epi32(rb, 16), _mm_srli_epi32(rb, 16)));
_mm_storeu_si128((__m128i*)cv, xyz);
_mm_storel_epi64((__m128i*)cv->col, colours);
_mm_storel_epi64((__m128i*)&cv->u, uv);
}
#else
{
const TA_Vertex3* vtx = &((const TA_VertexParam*)data)->vtx3;
vert_fast_base;
vert_fast_packed_color(col,BaseCol);
vert_fast_packed_color(vtx_spc,OffsCol);
vert_uv_32(u,v);
}
#endif
break;
case 4:
{
const TA_Vertex4* vtx = &((const TA_VertexParam*)data)->vtx4;
vert_fast_base;
vert_fast_packed_color(col,BaseCol);
vert_fast_packed_color(vtx_spc,OffsCol);
vert_uv_16(u,v);
}
break;
case 7:
{
const TA_Vertex7* vtx = &((const TA_VertexParam*)data)->vtx7;
vert_fast_base;
vert_face_base_color(BaseInt);
vert_face_offs_color(OffsInt);
vert_uv_32(u,v);
}
break;
case 8:
{
const TA_Vertex8* vtx = &((const TA_VertexParam*)data)->vtx8;
vert_fast_base;
vert_face_base_color(BaseInt);
vert_face_offs_color(OffsInt);
vert_uv_16(u,v);
}
break;
}
}
//(Non-Textured, Packed Color, with Two Volumes)
__forceinline
static void AppendPolyVertex9(TA_Vertex9* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol0);
vert_packed_color(col1, BaseCol1);
}
//(Non-Textured, Intensity, with Two Volumes)
__forceinline
static void AppendPolyVertex10(TA_Vertex10* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt0);
vert_face_base_color1(BaseInt1);
}
//(Textured, Packed Color, with Two Volumes)
__forceinline
static void AppendPolyVertex11A(TA_Vertex11A* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol0);
vert_packed_color(vtx_spc,OffsCol0);
vert_uv_32(u0,v0);
}
__forceinline
static void AppendPolyVertex11B(TA_Vertex11B* vtx)
{
vert_res_base;
vert_packed_color(col1, BaseCol1);
vert_packed_color(spc1, OffsCol1);
vert_uv1_32(u1, v1);
}
//(Textured, Packed Color, 16bit UV, with Two Volumes)
__forceinline
static void AppendPolyVertex12A(TA_Vertex12A* vtx)
{
vert_cvt_base;
vert_packed_color(col,BaseCol0);
vert_packed_color(vtx_spc,OffsCol0);
vert_uv_16(u0,v0);
}
__forceinline
static void AppendPolyVertex12B(TA_Vertex12B* vtx)
{
vert_res_base;
vert_packed_color(col1, BaseCol1);
vert_packed_color(spc1, OffsCol1);
vert_uv1_16(u1, v1);
}
//(Textured, Intensity, with Two Volumes)
__forceinline
static void AppendPolyVertex13A(TA_Vertex13A* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt0);
vert_face_offs_color(OffsInt0);
vert_uv_32(u0,v0);
}
__forceinline
static void AppendPolyVertex13B(TA_Vertex13B* vtx)
{
vert_res_base;
vert_face_base_color1(BaseInt1);
vert_face_offs_color1(OffsInt1);
vert_uv1_32(u1,v1);
}
//(Textured, Intensity, 16bit UV, with Two Volumes)
__forceinline
static void AppendPolyVertex14A(TA_Vertex14A* vtx)
{
vert_cvt_base;
vert_face_base_color(BaseInt0);
vert_face_offs_color(OffsInt0);
vert_uv_16(u0,v0);
}
__forceinline
static void AppendPolyVertex14B(TA_Vertex14B* vtx)
{
vert_res_base;
vert_face_base_color1(BaseInt1);
vert_face_offs_color1(OffsInt1);
vert_uv1_16(u1, v1);
}
//Sprites
__forceinline
static void AppendSpriteParam(TA_SpriteParam* spr)
{
//printf("Sprite\n");
PolyParam* d_pp=CurrentPP;
if (CurrentPP == NULL || CurrentPP->count != 0)
{
if (CurrentPPlist == NULL) // wldkickspw
return;
d_pp=CurrentPPlist->Append();
CurrentPP=d_pp;
}
d_pp->first = vdrc.verts.used();
d_pp->count=0;
d_pp->isp=spr->isp;
d_pp->tsp=spr->tsp;
d_pp->tcw=spr->tcw;
d_pp->pcw=spr->pcw;
d_pp->pcw.full |= PVR_PCW_SINGLE_RATE; // a sprite is drawn at a single rate
d_pp->tileclip=tileclip_val;
d_pp->texid = -1;
if (d_pp->pcw.Texture) {
d_pp->texid = renderer->GetTexture(d_pp->tsp,d_pp->tcw);
}
d_pp->tcw1.full = -1;
d_pp->tsp1.full = -1;
d_pp->texid1 = -1;
SFaceBaseColor=spr->BaseCol;
SFaceOffsColor=spr->OffsCol;
d_pp->isp.CullMode ^= 1;
}
#define append_sprite(indx) \
vert_packed_color_(cv[indx].col,SFaceBaseColor)\
vert_packed_color_(cv[indx].vtx_spc,SFaceOffsColor)
#define sprite_uv(indx,u_name,v_name) \
cv[indx].u = f16(sv->u_name);\
cv[indx].v = f16(sv->v_name);
//Sprite Vertex Handlers
__forceinline
static void AppendSpriteVertexA(TA_Sprite1A* sv)
{
/* A sprite's vertices with no polygon to belong to: its parameter
* came when no list was open and was dropped. */
if (CurrentPP == NULL)
return;
CurrentPP->count = 4;
Vertex* cv = vdrc.verts.Append(4);
//Fill static stuff
append_sprite(0);
append_sprite(1);
append_sprite(2);
append_sprite(3);
cv[2].x=sv->x0;
cv[2].y=sv->y0;
cv[2].z=sv->z0;
update_fz(sv->z0);
cv[3].x=sv->x1;
cv[3].y=sv->y1;
cv[3].z=sv->z1;
update_fz(sv->z1);
cv[1].x=sv->x2;
}
public:
// (also the GPU sort's, for the depth of a sprite's corner: see ta_gpu_sort_strips())
static void CaclulateSpritePlane(Vertex* base)
{
const Vertex& A=base[2];
const Vertex& B=base[3];
const Vertex& C=base[1];
Vertex& P=base[0];
//Vector AB = B-A;
//Vector AC = C-A;
//Vector AP = P-A;
float AC_x=C.x-A.x,AC_y=C.y-A.y,AC_z=C.z-A.z,
AB_x=B.x-A.x,AB_y=B.y-A.y,AB_z=B.z-A.z,
AP_x=P.x-A.x,AP_y=P.y-A.y;
float P_y = P.y, P_x = P.x, A_x = A.x, A_y = A.y, A_z = A.z;
float AB_v=B.v-A.v,AB_u=B.u-A.u,
AC_v=C.v-A.v,AC_u=C.u-A.u;
float /*P_v,P_u,*/A_v=A.v,A_u=A.u;
float k3 = (AC_x * AB_y - AC_y * AB_x);
if (k3 == 0)
{
//throw new Exception("WTF?!");
}
float k2 = (AP_x * AB_y - AP_y * AB_x) / k3;
float k1 = 0;
if (AB_x == 0)
{
//if (AB_y == 0)
// ;
// //throw new Exception("WTF?!");
k1 = (P_y - A_y - k2 * AC_y) / AB_y;
}
else
{
k1 = (P_x - A_x - k2 * AC_x) / AB_x;
}
P.z = A_z + k1 * AB_z + k2 * AC_z;
P.u = A_u + k1 * AB_u + k2 * AC_u;
P.v = A_v + k1 * AB_v + k2 * AC_v;
}
private:
__forceinline
static void AppendSpriteVertexB(TA_Sprite1B* sv)
{
if (CurrentPP == NULL)
return;
vert_res_base;
cv-=3;
cv[1].y=sv->y2;
cv[1].z=sv->z2;
update_fz(sv->z2);
cv[0].x=sv->x3;
cv[0].y=sv->y3;
sprite_uv(2, u0,v0);
sprite_uv(3, u1,v1);
sprite_uv(1, u2,v2);
//sprite_uv(0, u0,v2);//or sprite_uv(u2,v0); ?
CaclulateSpritePlane(cv);
update_fz(cv[0].z);
PolyParam* d_pp = CurrentPPlist->Append();
*d_pp = *CurrentPP;
CurrentPP = d_pp;
d_pp->first = vdrc.verts.used();
d_pp->count = 0;
}
// Modifier Volumes Vertex handlers
static void EndModVol()
{
List<ModifierVolumeParam> *list = NULL;
if (CurrentList == ListType_Opaque_Modifier_Volume)
list = &vdrc.global_param_mvo;
else if (CurrentList == ListType_Translucent_Modifier_Volume)
list = &vdrc.global_param_mvo_tr;
else
return;
if (list->used() > 0)
{
ModifierVolumeParam *p = list->LastPtr();
p->count = vdrc.modtrig.used() - p->first;
if (p->count == 0)
list->PopLast();
}
}
//Mod Volume Vertex handlers
static void StartModVol(TA_ModVolParam* param)
{
EndModVol();
ModifierVolumeParam *p = NULL;
if (CurrentList == ListType_Opaque_Modifier_Volume)
p = vdrc.global_param_mvo.Append();
else if (CurrentList == ListType_Translucent_Modifier_Volume)
p = vdrc.global_param_mvo_tr.Append();
else
return;
p->isp.full = param->isp.full;
p->isp.VolumeLast = param->pcw.Volume != 0;
p->first = vdrc.modtrig.used();
p->tileclip = tileclip_val;
}
__forceinline
static void AppendModVolVertexA(TA_ModVolA* mvv)
{
if (CurrentList != ListType_Opaque_Modifier_Volume && CurrentList != ListType_Translucent_Modifier_Volume)
return;
lmr=vdrc.modtrig.Append();
lmr->x0=mvv->x0;
lmr->y0=mvv->y0;
lmr->z0=mvv->z0;
//update_fz(mvv->z0);
lmr->x1=mvv->x1;
lmr->y1=mvv->y1;
lmr->z1=mvv->z1;
//update_fz(mvv->z1);
lmr->x2=mvv->x2;
}
__forceinline
static void AppendModVolVertexB(TA_ModVolB* mvv)
{
if (CurrentList != ListType_Opaque_Modifier_Volume && CurrentList != ListType_Translucent_Modifier_Volume)
return;
lmr->y2=mvv->y2;
lmr->z2=mvv->z2;
//update_fz(mvv->z2);
}
static void VDECInit()
{
vd_rc.Clear();
ta_strip_ok_clear(&vd_rc.strip_ok[0]);
ta_strip_ok_clear(&vd_rc.strip_ok[1]);
ta_strip_ok_clear(&vd_rc.strip_ok[2]);
//allocate storage for BG poly
vd_rc.global_param_op.Append();
vd_rc.verts.Append(4);
}
};
const u32 *FifoSplitter::ta_type_lut;
#define TA_POLY_DATA_LUT(flat) { \
ta_poly_data<0,SZ32,flat>, ta_poly_data<1,SZ32,flat>, ta_poly_data<2,SZ32,flat>, \
ta_poly_data<3,SZ32,flat>, ta_poly_data<4,SZ32,flat>, ta_poly_data<5,SZ64,flat>, \
ta_poly_data<6,SZ64,flat>, ta_poly_data<7,SZ32,flat>, ta_poly_data<8,SZ32,flat>, \
ta_poly_data<9,SZ32,flat>, ta_poly_data<10,SZ32,flat>, ta_poly_data<11,SZ64,flat>, \
ta_poly_data<12,SZ64,flat>, ta_poly_data<13,SZ64,flat>, ta_poly_data<14,SZ64,flat> }
TaListFP* const FifoSplitter::poly_data_luts[2][15] = { TA_POLY_DATA_LUT(false), TA_POLY_DATA_LUT(true) };
#undef TA_POLY_DATA_LUT
TaListFP* const* FifoSplitter::poly_data_lut = FifoSplitter::poly_data_luts[0];
TaTypeLut::TaTypeLut()
{
for (int i = 0; i < 256; i++)
{
PCW pcw;
pcw.obj_ctrl = i;
u32 rv = FifoSplitter::poly_data_type_id(pcw);
u32 type = FifoSplitter::poly_header_type_size(pcw);
if (rv == 0xFFFFFFFF || type == 0xFFDDEEAA)
{
// no such type: see where the table is read
table[i] = 0xFFFFFFFF;
continue;
}
if (type & 0x80)
rv |= SZ64 << 30;
else
rv |= SZ32 << 30;
rv |= (type & 0x7F) << 8;
table[i] = rv;
}
}
static void getRegionSettings(int pass_number, RenderPass *pass);
static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax);
FifoSplitter TAFifo0;
int ta_parse_cnt = 0;
//
// Create the vertex index, eliminating invalid vertices and merging strips when possible.
//
#include <stddef.h>
/* (the words poly_same_state() takes two at a time are side by side) */
typedef char ta_poly_layout[(offsetof(PolyParam, tcw) == offsetof(PolyParam, tsp) + 4
&& offsetof(PolyParam, isp) == offsetof(PolyParam, pcw) + 4
&& offsetof(PolyParam, tcw1) == offsetof(PolyParam, tsp1) + 4) ? 1 : -1];
/* Whether two polygons are drawn in the same way, so that their strips can
* be joined into one: the same control, depth, shading and texture words,
* the same clipping rectangle and mode, and the same second shading and
* texture words (those of a polygon under two volumes; all ones on any
* other). Two words are compared at a time and there is one test for all
* of them. The single-rate bit is left out of the control word's
* comparison: a draw group's is its strips' together (flat_groups()). */
static INLINE bool poly_same_state(const PolyParam *a, const PolyParam *b)
{
static const u32 ctl_mask_words[2] = { ~PVR_PCW_SINGLE_RATE, ~0u }; // (pcw, then isp)
u64 a_tex, b_tex, a_ctl, b_ctl, a_tex1, b_tex1, ctl_mask;
memcpy(&a_tex, &a->tsp, sizeof(a_tex));
memcpy(&b_tex, &b->tsp, sizeof(b_tex));
memcpy(&a_ctl, &a->pcw, sizeof(a_ctl));
memcpy(&b_ctl, &b->pcw, sizeof(b_ctl));
memcpy(&a_tex1, &a->tsp1, sizeof(a_tex1));
memcpy(&b_tex1, &b->tsp1, sizeof(b_tex1));
memcpy(&ctl_mask, ctl_mask_words, sizeof(ctl_mask));
return ((a_tex ^ b_tex) | ((a_ctl ^ b_ctl) & ctl_mask) | (a_tex1 ^ b_tex1)
| (u64)(a->tileclip ^ b->tileclip)) == 0;
}
/* Where flat strips are looked for: a draw group - polygons one after
* another in a render pass's list drawn the same way (poly_same_state()),
* which make_index() joins and the GPU's decode draws as one - is drawn at
* a single rate only if every strip of it is flat (or a sprite, or the
* background), and then all of its strips have the bit; strips with no
* vertices count for nothing. The groups are the GPU's decode's (see
* ta_gpu_prepare()), so that a frame is drawn at the same rates whichever
* of the two made it; the translucent strips, which are drawn one by one,
* too. One pass over the polygon parameters, none over the vertices. */
static void flat_groups(const List<PolyParam> *polys, int first, int end)
{
PolyParam *const pp = polys->head();
int i = first;
while (i < end)
{
if (pp[i].count == 0)
{
i++;
continue;
}
u32 bit = PVR_PCW_SINGLE_RATE;
int j = i, last = i;
for (; j < end; j++)
{
if (pp[j].count == 0)
continue;
if (!poly_same_state(&pp[last], &pp[j]))
break;
bit &= pp[j].pcw.full;
last = j;
}
for (int k = i; k < j; k++)
if (pp[k].count != 0)
pp[k].pcw.full = (pp[k].pcw.full & ~PVR_PCW_SINGLE_RATE) | bit;
i = j;
}
}
static void make_index(const List<PolyParam> *polys, int first, int end, bool merge, rend_context* ctx)
{
const Vertex *vertices = ctx->verts.head();
/* (the strips the vertex loop found drawable whole: ta_strip_ok) */
const ta_strip_ok_t *ok = polys == &ctx->global_param_op ? &ctx->strip_ok[0]
: polys == &ctx->global_param_pt ? &ctx->strip_ok[1]
: polys == &ctx->global_param_tr ? &ctx->strip_ok[2] : NULL;
/* Where the next index goes and where the list ends are kept for the
* whole of it, and given back to the list at the end (ta_index.h) */
ta_index_out_t o;
o.base = ctx->idx.head();
o.out = ctx->idx.cur();
o.end = o.out + ctx->idx.avail();
o.list = &ctx->idx.l;
PolyParam *last_poly = NULL;
const PolyParam *end_poly = &polys->head()[end];
for (PolyParam *poly = &polys->head()[first]; poly != end_poly; poly++)
{
u32 first_index;
int dupe = 0;
if (merge
&& last_poly != NULL
&& last_poly->count != 0 // (one that came to no indices has no last of them to repeat)
&& poly_same_state(poly, last_poly))
{
ta_index_put(&o, o.base[last_poly->first + last_poly->count - 1]);
dupe = 1;
first_index = last_poly->first;
}
else
{
last_poly = poly;
first_index = (u32)(o.out - o.base);
}
ta_index_strip(&o, &vertices[poly->first], sizeof(Vertex), poly->first, poly->count, dupe, first_index,
ok != NULL && ta_strip_ok_get(ok, (u32)(poly - polys->head())));
if (last_poly == poly)
{
poly->first = first_index;
poly->count = (u32)(o.out - o.base) - first_index;
}
else
{
last_poly->count = (u32)(o.out - o.base) - last_poly->first;
poly->count = 0;
}
}
ctx->idx.set_cur(o.out);
}
static void fix_texture_bleeding(const List<PolyParam> *list)
{
const PolyParam *pp_end = list->LastPtr(0);
const u32 *idx_base = vd_rc.idx.head();
Vertex *vtx_base = vd_rc.verts.head();
for (const PolyParam *pp = list->head(); pp != pp_end; pp++)
{
if (!pp->pcw.Texture || pp->count < 3)
continue;
// Find polygons that are facing the camera (constant z)
// and only use 0 and 1 for U and V (some tolerance around 1 for SA2)
// then apply a half-pixel correction on U and V.
const u32 first = idx_base[pp->first];
const u32 last = idx_base[pp->first + pp->count - 1];
bool need_fixing = true;
float z = 0.f;
for (u32 idx = first; idx <= last && need_fixing; idx++)
{
Vertex& vtx = vtx_base[idx];
if (vtx.u != 0.f && (vtx.u <= 0.995f || vtx.u > 1.f))
need_fixing = false;
else if (vtx.v != 0.f && (vtx.v <= 0.995f || vtx.v > 1.f))
need_fixing = false;
else if (idx == first)
z = vtx.z;
else if (z != vtx.z)
need_fixing = false;
}
if (!need_fixing)
continue;
u32 tex_width = 8 << pp->tsp.TexU;
u32 tex_height = 8 << pp->tsp.TexV;
for (u32 idx = first; idx <= last; idx++)
{
Vertex& vtx = vtx_base[idx];
if (vtx.u > 0.995f)
vtx.u = 1.f;
vtx.u = (0.5f + vtx.u * (tex_width - 1)) / tex_width;
if (vtx.v > 0.995f)
vtx.v = 1.f;
vtx.v = (0.5f + vtx.v * (tex_height - 1)) / tex_height;
}
}
}
static bool ta_parse_vdrc_full(TA_context* ctx)
{
bool rv=false;
vd_ctx = ctx;
vd_rc = vd_ctx->rend;
ta_parse_cnt++;
if (ctx->rend.isRTT || 0 == (ta_parse_cnt % ( settings.pvr.ta_skip + 1)))
{
TAFifo0.vdec_init();
bool empty_context = true;
int op_poly_count = 0;
int pt_poly_count = 0;
int tr_poly_count = 0;
PolyParam *bgpp = vd_rc.global_param_op.head();
if (bgpp->pcw.Texture)
{
bgpp->texid = renderer->GetTexture(bgpp->tsp, bgpp->tcw);
empty_context = false;
}
/* the render passes this render draws: all of them, or those its
* region array points to (ta_select_passes()) */
u32 first = ctx->rend.pass_first, last = ctx->rend.pass_last;
if (last > ctx->tad.render_pass_count)
last = ctx->tad.render_pass_count;
if (first > last)
first = 0;
for (u32 pass = first; pass <= last; pass++)
{
ctx->MarkRend(pass);
vd_rc.proc_start = ctx->rend.proc_start;
vd_rc.proc_end = ctx->rend.proc_end;
Ta_Dma* ta_data = (Ta_Dma*)vd_rc.proc_start;
Ta_Dma* ta_data_end = (Ta_Dma*)vd_rc.proc_end - 1;
while (ta_data <= ta_data_end)
ta_data =TaCmd(ta_data,ta_data_end);
if (ctx->rend.Overrun)
break;
/* Opaque polygons of the first render pass are not blended,
* whatever their blend instructions say: they are drawn as
* "source x 1, destination x 0". It is only in the passes that
* follow, a list continuation, that an opaque polygon's
* instructions are gone by. Upstream flycast sets them here in
* the same way, and the renderers go by what they find. */
if (pass == first)
{
PolyParam *pp = vd_rc.global_param_op.head();
for (int i = vd_rc.global_param_op.used(); i > 0; i--, pp++)
{
pp->tsp.SrcInstr = 1;
pp->tsp.DstInstr = 0;
}
}
bool empty_pass = vd_rc.global_param_op.used() == (pass == first ? 0 : (int)vd_rc.render_passes.LastPtr()->op_count)
&& vd_rc.global_param_pt.used() == (pass == first ? 0 : (int)vd_rc.render_passes.LastPtr()->pt_count)
&& vd_rc.global_param_tr.used() == (pass == first ? 0 : (int)vd_rc.render_passes.LastPtr()->tr_count);
empty_context = empty_context && empty_pass;
if (pass == first || !empty_pass)
{
RenderPass *render_pass = vd_rc.render_passes.Append();
render_pass->op_count = vd_rc.global_param_op.used();
if (flat_strips_wanted)
{
flat_groups(&vd_rc.global_param_op, op_poly_count, vd_rc.global_param_op.used());
flat_groups(&vd_rc.global_param_pt, pt_poly_count, vd_rc.global_param_pt.used());
flat_groups(&vd_rc.global_param_tr, tr_poly_count, vd_rc.global_param_tr.used());
}
make_index(&vd_rc.global_param_op, op_poly_count,
render_pass->op_count, true, &vd_rc);
op_poly_count = render_pass->op_count;
render_pass->mvo_count = vd_rc.global_param_mvo.used();
render_pass->pt_count = vd_rc.global_param_pt.used();
make_index(&vd_rc.global_param_pt, pt_poly_count,
render_pass->pt_count, true, &vd_rc);
pt_poly_count = render_pass->pt_count;
render_pass->tr_count = vd_rc.global_param_tr.used();
make_index(&vd_rc.global_param_tr, tr_poly_count,
render_pass->tr_count, false, &vd_rc);
tr_poly_count = render_pass->tr_count;
render_pass->mvo_tr_count = vd_rc.global_param_mvo_tr.used();
// (the region array's entries are this render's passes, from its first)
getRegionSettings(pass - first, render_pass);
}
}
rv = !empty_context;
}
bool overrun = ctx->rend.Overrun;
if (overrun)
WARN_LOG(PVR, "ERROR: TA context overrun\n");
else if (screen_height > 480)
{
fix_texture_bleeding(&vd_rc.global_param_op);
fix_texture_bleeding(&vd_rc.global_param_pt);
fix_texture_bleeding(&vd_rc.global_param_tr);
}
if (rv && !overrun)
{
u32 xmin, xmax, ymin, ymax;
getRegionTileClipping(xmin, xmax, ymin, ymax);
vd_rc.fb_X_CLIP.min = ta_max(vd_rc.fb_X_CLIP.min, xmin);
vd_rc.fb_X_CLIP.max = ta_min(vd_rc.fb_X_CLIP.max, xmax + 31);
vd_rc.fb_Y_CLIP.min = ta_max(vd_rc.fb_Y_CLIP.min, ymin);
vd_rc.fb_Y_CLIP.max = ta_min(vd_rc.fb_Y_CLIP.max, ymax + 31);
}
if (rv && !overrun && modvol_clip_wanted)
{
struct { const ModTriangle *t; ModTriangle operator()(u32 i) const { return t[i]; } void restart() {} } get = { vd_rc.modtrig.head() };
mv_clip_build(vd_rc, vd_rc.modtrig.used(), get);
}
vd_ctx->rend = vd_rc;
vd_ctx = 0;
ctx->rend.Overrun = overrun;
return rv && !overrun;
}
/* The header pass check (at the end of the file) */
static bool ta_walk_check_frame(TA_context *ctx);
static bool ta_walk_checking;
bool ta_parse_vdrc(TA_context* ctx)
{
if (ta_walk_checking)
return ta_walk_check_frame(ctx);
return ta_parse_vdrc_full(ctx);
}
/* For the check: the lists of the frame strip by strip, as the parse above
* makes them and gives each render pass's make_index() - which then turns
* them into ranges of indices, and joins some - kept in the order of the
* passes (ta_walk_snap_full()). The parse's own steps, but for the indices,
* the texture bleeding fix and the clipping rectangle; it leaves the frame
* for the parse to make again (from the same data, the same). */
static void ta_walk_snap_full(u32 op_from, u32 pt_from, u32 tr_from);
static void ta_walk_full_strips(TA_context* ctx)
{
const bool overrun = ctx->rend.Overrun;
vd_ctx = ctx;
vd_rc = vd_ctx->rend;
TAFifo0.vdec_init();
u32 op_poly_count = 0, pt_poly_count = 0, tr_poly_count = 0;
PolyParam *bgpp = vd_rc.global_param_op.head();
if (bgpp->pcw.Texture)
bgpp->texid = renderer->GetTexture(bgpp->tsp, bgpp->tcw);
for (u32 pass = 0; pass <= ctx->tad.render_pass_count; pass++)
{
ctx->MarkRend(pass);
Ta_Dma* ta_data = (Ta_Dma*)ctx->rend.proc_start;
Ta_Dma* ta_data_end = (Ta_Dma*)ctx->rend.proc_end - 1;
while (ta_data <= ta_data_end)
ta_data = TaCmd(ta_data, ta_data_end);
if (ctx->rend.Overrun)
break;
if (pass == 0)
{
PolyParam *pp = vd_rc.global_param_op.head();
for (int i = vd_rc.global_param_op.used(); i > 0; i--, pp++)
{
pp->tsp.SrcInstr = 1;
pp->tsp.DstInstr = 0;
}
}
bool empty_pass = vd_rc.global_param_op.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->op_count)
&& vd_rc.global_param_pt.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->pt_count)
&& vd_rc.global_param_tr.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->tr_count);
if (pass == 0 || !empty_pass)
{
RenderPass *render_pass = vd_rc.render_passes.Append();
ta_walk_snap_full(op_poly_count, pt_poly_count, tr_poly_count);
render_pass->op_count = op_poly_count = vd_rc.global_param_op.used();
render_pass->pt_count = pt_poly_count = vd_rc.global_param_pt.used();
render_pass->tr_count = tr_poly_count = vd_rc.global_param_tr.used();
}
}
vd_ctx = 0;
ctx->rend.Overrun = overrun;
}
//decode a vertex in the native pvr format
//used for bg poly
static void decode_pvr_vertex(u32 base,u32 ptr,Vertex* cv)
{
//ISP
//TSP
//TCW
ISP_TSP isp;
isp.full=vri(base);
//XYZ
//UV
//Base Col
//Offset Col
//XYZ are _always_ there :)
cv->x=vrf(ptr);
ptr+=4;
cv->y=vrf(ptr);
ptr+=4;
cv->z=vrf(ptr);
ptr+=4;
if (isp.Texture)
{ //Do texture , if any
if (isp.UV_16b)
{
u32 uv=vri(ptr);
cv->u = f16((u16)uv);
cv->v = f16((u16)(uv >> 16));
ptr+=4;
}
else
{
cv->u=vrf(ptr);
ptr+=4;
cv->v=vrf(ptr);
ptr+=4;
}
}
//Color
u32 col = vri(ptr);
ptr += 4;
vert_packed_color_(cv->col,col);
if (isp.Offset)
{
//Intensity color (can be missing too ;p)
u32 col=vri(ptr);
ptr+=4;
vert_packed_color_(cv->vtx_spc, col);
}
}
void vtxdec_init(void)
{
}
static OnLoad ol_vtxdec(&vtxdec_init);
void FillBGP(TA_context* ctx)
{
//Render pre-code
//--BG poly
u32 param_base=PARAM_BASE & 0xF00000;
PolyParam* bgpp=ctx->rend.global_param_op.head();
Vertex* cv=ctx->rend.verts.head();
bool PSVM=FPU_SHAD_SCALE.intensity_shadow!=0; //double parameters for volumes
//Get the strip base
u32 strip_base=(param_base + ISP_BACKGND_T.tag_address*4)&0x7FFFFF; //this is *not* VRAM_MASK on purpose.It fixes naomi bios and quite a few naomi games
//i have *no* idea why that happens, they manage to set the render target over there as well
//and that area is *not* written by the games (they instead write the params on 000000 instead of 800000)
//could be a h/w bug ? param_base is 400000 and tag is 100000*4
//Calculate the vertex size
//Update: Looks like I was handling the bank interleave wrong for 16 megs ram, could that be it?
u32 strip_vs=3 + ISP_BACKGND_T.skip;
u32 strip_vert_num=ISP_BACKGND_T.tag_offset;
if (PSVM && ISP_BACKGND_T.shadow)
{
strip_vs+=ISP_BACKGND_T.skip;//2x the size needed :p
}
strip_vs*=4;
//Get vertex ptr
u32 vertex_ptr=strip_vert_num*strip_vs+strip_base +3*4;
//now , all the info is ready :p
bgpp->texid = -1;
bgpp->isp.full=vri(strip_base);
bgpp->tsp.full=vri(strip_base+4);
bgpp->tcw.full=vri(strip_base+8);
bgpp->tcw1.full = -1;
bgpp->tsp1.full = -1;
bgpp->texid1 = -1;
bgpp->count=4;
bgpp->first=0;
bgpp->tileclip=0;//disabled ! HA ~
bgpp->isp.DepthMode=7;// -> this makes things AWFULLY slow .. sometimes
bgpp->isp.CullMode=0;// -> so that its not culled, or somehow else hidden !
//Set some pcw bits .. I should really get rid of pcw ..
bgpp->pcw.UV_16bit=bgpp->isp.UV_16b;
bgpp->pcw.Gouraud=bgpp->isp.Gouraud;
bgpp->pcw.Offset=bgpp->isp.Offset;
bgpp->pcw.Texture = bgpp->isp.Texture;
bgpp->pcw.Shadow = ISP_BACKGND_T.shadow;
bgpp->pcw.full |= PVR_PCW_SINGLE_RATE; // so is the background
float scale_x= (SCALER_CTL.hscale) ? 2.f:1.f; //if AA hack the hacked pos value hacks
for (int i=0;i<3;i++)
{
decode_pvr_vertex(strip_base,vertex_ptr,&cv[i]);
vertex_ptr+=strip_vs;
}
f32 bg_depth = ISP_BACKGND_D.f;
reinterpret_cast<u32&>(bg_depth) &= 0xFFFFFFF0; // ISP_BACKGND_D has only 28 bits
/* And a little further away than it says. The chip does not tell depths
* this close apart, and what a game draws at the background's own depth
* shows; here it lost to the background by a rounding. Upstream's
* figure, and its list: the sky in Xtreme Sports, Blue Stinger's (JP)
* intro, and the videos of many Windows CE games, which were black. */
bg_depth = ta_max(bg_depth - 1e-6f, 1e-11f);
/* The plane through the three vertices, over the whole screen: see
* bg_plane.h. What is drawn is wider than the screen by 256 pixels on
* either side, as before. The four corners are written over the three
* vertices, so what is needed of those is taken first, and no more
* than is needed: the texture coordinates, and the colours only where
* they vary - without Gouraud shading the colour is the third vertex's
* everywhere, and the fourth corner is all that has to be given it. */
{
bg_plane plane;
if (bg_plane_init(&plane, cv[0].x, cv[0].y, cv[1].x, cv[1].y, cv[2].x, cv[2].y))
{
const float u[3] = { cv[0].u, cv[1].u, cv[2].u };
const float v[3] = { cv[0].v, cv[1].v, cv[2].v };
const bool gouraud = bgpp->pcw.Gouraud != 0;
u8 col[3][4], spc[3][4];
if (gouraud)
for (int i = 0; i < 3; i++)
{
memcpy(col[i], cv[i].col, 4);
memcpy(spc[i], cv[i].vtx_spc, 4);
}
else
{
memcpy(cv[3].col, cv[2].col, 4);
memcpy(cv[3].vtx_spc, cv[2].vtx_spc, 4);
}
for (int i = 0; i < 4; i++)
{
const float x = (i & 1) ? 896.f : -256.f;
const float y = (i & 2) ? 480.f : 0.f;
cv[i].x = x * scale_x;
cv[i].y = y;
cv[i].z = bg_depth;
cv[i].u = bg_plane_at(&plane, u[0], u[1], u[2], x, y);
cv[i].v = bg_plane_at(&plane, v[0], v[1], v[2], x, y);
if (gouraud)
for (int c = 0; c < 4; c++)
{
cv[i].col[c] = bg_plane_colour(&plane, col[0][c], col[1][c], col[2][c], x, y);
cv[i].vtx_spc[c] = bg_plane_colour(&plane, spc[0][c], spc[1][c], spc[2][c], x, y);
}
}
return;
}
}
// the three are not a plane: the picture stretched over the screen, as it always was
f32 min_u = ta_min(cv[0].u, ta_min(cv[1].u, cv[2].u));
f32 max_u = ta_max(cv[0].u, ta_max(cv[1].u, cv[2].u));
if (max_u == 0.f)
max_u = 1.f;
const f32 diff_u = (max_u - min_u) * 0.4f;
max_u += diff_u;
min_u -= diff_u;
const f32 min_v = ta_min(cv[0].v, ta_min(cv[1].v, cv[2].v));
f32 max_v = ta_max(cv[0].v, ta_max(cv[1].v, cv[2].v));
if (max_v == 0.f)
max_v = 1.f;
cv[0].x = -256.f * scale_x;
cv[0].y = 0.f;
cv[0].z=bg_depth;
cv[0].u = min_u;
cv[0].v = min_v;
cv[1].x = 896.f * scale_x;
cv[1].y = 0.f;
cv[1].z=bg_depth;
cv[1].u = max_u;
cv[1].v = min_v;
cv[2].x = -256.f * scale_x;
cv[2].y = 480.f;
cv[2].z=bg_depth;
cv[2].u = min_u;
cv[2].v = max_v;
cv[3]=cv[2];
cv[3].x = 896.f * scale_x;
cv[3].y = 480.f;
cv[3].u = max_u;
cv[3].v = max_v;
}
/* @table: the entries are also kept in the frame's table of tiles */
template <bool table>
__forceinline
static void walkRegionArray(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
{
xmin = 20;
xmax = 0;
ymin = 15;
ymax = 0;
u32 addr = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
int tile_size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((vri(addr + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
addr += tile_size;
/* The array ends at the entry marked last. A game that starts a render
* without having written one leaves no such entry, and vri() wraps
* around video memory, so stop once all of it has been walked: the
* render thread must not spin on what the guest left in VRAM. */
RegionArrayTile tile;
u32 walked = 0;
u32 entries = 0, entry = 0;
do {
tile.full = vri(addr);
if (table)
{
entry = pvr_region_entry(tile.full, entry, entries == 0);
if (entries < REGION_TILES_MAX)
vd_rc.region_tiles[entries] = entry;
entries++;
}
xmin = ta_min(xmin, tile.X);
xmax = ta_max(xmax, tile.X);
ymin = ta_min(ymin, tile.Y);
ymax = ta_max(ymax, tile.Y);
if (type1_tile && tile.PreSort)
// Windows CE weirdness
tile_size = 6 * 4;
addr += tile_size;
walked += tile_size;
} while (!tile.LastRegion && walked < VRAM_SIZE);
if (table)
vd_rc.region_tile_count = entries;
xmin *= 32;
xmax *= 32;
ymin *= 32;
ymax *= 32;
}
/* The tiles the region array lists, as a rectangle of pixels; and the
* frame's table of them, when it is wanted */
static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax)
{
if (region_table_wanted)
walkRegionArray<true>(xmin, xmax, ymin, ymax);
else
walkRegionArray<false>(xmin, xmax, ymin, ymax);
}
// Where the region array entry of a render pass is
__forceinline
static u32 getRegionTileAddr(int pass_number)
{
u32 addr = REGION_BASE;
const bool type1_tile = ((FPU_PARAM_CFG >> 21) & 1) == 0;
int tile_size = (type1_tile ? 5 : 6) * 4;
bool empty_first_region = true;
for (int i = type1_tile ? 4 : 5; i > 0; i--)
if ((vri(addr + i * 4) & 0x80000000) == 0)
{
empty_first_region = false;
break;
}
if (empty_first_region)
addr += tile_size;
RegionArrayTile tile;
tile.full = vri(addr);
if (type1_tile && tile.PreSort)
// Windows CE weirdness
tile_size = 6 * 4;
return addr + pass_number * tile_size;
}
__forceinline
static void getRegionSettings(int pass_number, RenderPass *pass)
{
const u32 addr = getRegionTileAddr(pass_number);
RegionArrayTile tile;
tile.full = vri(addr);
if (((FPU_PARAM_CFG >> 21) & 1) == 0)
// Type 1 region header type
pass->autosort = (ISP_FEED_CFG & 1) == 0;
else
// Type 2
pass->autosort = !tile.PreSort;
pass->z_clear = !tile.NoZClear;
/* The region's opaque and translucent modifier volume lists can be the
* same list: the translucent volumes are then the opaque ones (Crazy
* Taxi, Daytona USA). Two empty ones are not that. */
const u32 op_mv = vri(addr + 8);
pass->mv_op_tr_shared = op_mv == vri(addr + 16) && (op_mv & 0x80000000) == 0;
}
/* A game can start two renders of one frame's data: Virtua Fighter 4 (a
* NAOMI 2 game) sends the scene, continues the lists with the scene as the
* water reflects it at another object list address, and then draws the
* first to the screen and the second to a texture, with a region array for
* each that points at the lists of one. Each render draws the render
* passes its region array's entries point to: the pass of an entry is the
* one whose lists start nearest below where its lists are. The entries of
* a tile are the render's passes in order; a frame whose passes do not
* come out that way, one pass at a time from some first one, is drawn
* whole, as every frame was. */
void ta_select_passes(TA_context* ctx)
{
const tad_context &tad = ctx->tad;
rend_context &rc = ctx->rend;
const u32 passes = tad.render_pass_count + 1;
rc.pass_first = 0;
rc.pass_last = (u8)tad.render_pass_count;
rc.keep = false;
if (passes == 1 || rc.isRenderFramebuffer)
return;
/* (a frame from a save state was begun before it: where its first
* pass's lists went is not known, and is below the others') */
for (u32 p = 1; p < passes; p++)
if (tad.ol_base[p] == TAD_OL_UNKNOWN)
return;
// the entries of the first tile: how many there are
const u32 base = getRegionTileAddr(0);
const u32 tile_size = getRegionTileAddr(1) - base;
RegionArrayTile tile, t;
tile.full = vri(base);
u32 entries = 0;
do {
t.full = vri(base + entries * tile_size);
if (t.X != tile.X || t.Y != tile.Y)
break;
entries++;
} while (!t.LastRegion && entries <= passes);
if (entries == 0 || entries >= passes)
return;
/* Each entry's lists, in the first tile that has one for it (a tile can
* have nothing in some pass): opaque, its modifier volumes,
* translucent, theirs, punch-through */
const u32 lists = tile_size / 4 - 1;
u32 first = 0;
for (u32 e = 0; e < entries; e++)
{
u32 ptr = 0x80000000u;
for (u32 n = 0; n < 64 && (ptr & 0x80000000u); n++)
{
const u32 at = base + n * entries * tile_size;
tile.full = vri(at);
t.full = vri(at + e * tile_size);
if (t.X != tile.X || t.Y != tile.Y)
break;
for (u32 l = 1; l <= lists && (ptr & 0x80000000u); l++)
ptr = vri(at + e * tile_size + l * 4);
// (the tile's last entry ends the array)
t.full = vri(at + (entries - 1) * tile_size);
if (t.LastRegion)
break;
}
if (ptr & 0x80000000u)
return;
ptr &= 0xFFFFFCu;
u32 pass = passes;
for (u32 p = 0; p < passes; p++)
if (tad.ol_base[p] != TAD_OL_UNKNOWN && tad.ol_base[p] <= ptr
&& (pass == passes || tad.ol_base[p] >= tad.ol_base[pass]))
pass = p;
if (pass == passes && tad.ol_base[0] == TAD_OL_UNKNOWN)
pass = 0;
if (pass == passes)
return;
// (two passes started at that address: which it is cannot be told)
for (u32 p = 0; p < passes; p++)
if (p != pass && tad.ol_base[p] == tad.ol_base[pass])
return;
if (e == 0)
first = pass;
else if (pass != first + e)
return;
}
rc.pass_first = (u8)first;
rc.pass_last = (u8)(first + entries - 1);
rc.keep = true;
}
/* ---- The header pass: a frame from its control words alone ----
*
* The CPU side of GPU-side TA processing. As the blocks come in, the
* control-word walk (ta.cpp, ta_set_walk()) notes where each block that is
* not a vertex is (TA_context::walk). From that table and the parameters
* themselves, this pass makes what the full parse above makes of the
* frame but for the vertices: the polygon parameters with their texture
* lookups, the modifier volumes, the render passes and their region
* settings, the clipping rectangle, and the draw groups (the polygons
* make_index() may join). Each parameter is a header here, with the
* blocks of its vertices, how big they are and in which format, and the
* place its first vertex goes. A vertex is never read: the vertices
* between two entries are counted from where the entries are.
*
* What it cannot know without the vertices is where strips end - the
* full parse makes a polygon parameter of each strip - and what
* make_index() keeps of them. ta_walk_check_frame() reads the vertices'
* end-of-strip bits to cut its headers into strips the full parse's way,
* and compares the two. */
#include <chrono>
#define TA_WALK_SPRITE 15 // a header's format: a sprite
#define TA_WALK_NO_GROUP 0xFFFFFFFFu // a header's group: it has no vertices
#define TA_WALK_BG 0xFFFFFFFFu // the header of a strip: the background's
#define TA_WALK_MODVOL 0x80000000u // a run's header: a modifier volume's
#define TA_WALK_UNENDED 0xFFFFFFFFu // a modifier volume's count: its end did not come
struct TaWalkHeader
{
PolyParam pp; // first: its first vertex; count: its vertices, all strips together
u32 block; // where it is in the frame's data, in 32-byte blocks
u32 group; // its draw group
u8 list; // ListType_Opaque, _Punch_Through or _Translucent
u8 format; // its vertices' format, 0-14, or TA_WALK_SPRITE
u8 vblocks; // blocks a vertex takes, 1 or 2
u8 pass; // its render pass
};
// Vertex blocks one after the other, all of one header
struct TaWalkRun
{
u32 block; // the first
u32 blocks;
u32 vertex; // the first vertex they make (a modifier volume's: triangle)
u32 header; // whose: a header, or TA_WALK_MODVOL | a modifier volume
};
/* Polygon parameters as each render pass gives them to make_index(), in
* the order of the passes: opaque, punch-through, translucent */
struct TaWalkStrips
{
PolyParam *pp[3];
u32 *header[3]; // (the header pass's) the header each is a strip of
u32 count[3];
u32 room[3];
};
/* What a header's vertices are coloured with besides their own words, for
* the GPU decode: the face colours of the intensity formats as they are
* once its parameter is in (FaceBaseColor, FaceOffsColor and their
* second-volume twins), and a sprite's two colours */
struct TaWalkFace
{
u32 col[4]; // base, offset, base 1, offset 1: red, green, blue, alpha bytes
u32 f[12]; // ...and as the clamped floats' bits, red, green, blue each
u32 sprite[2]; // a sprite's base and offset colours, as sent
};
struct TaWalkFrame
{
// in: what the frame had before it was parsed
PolyParam bg_in;
FB_X_CLIP_type x_clip_in;
FB_Y_CLIP_type y_clip_in;
TaWalkHeader *header;
u32 headers, header_room;
TaWalkRun *run;
u32 runs, run_room;
ModifierVolumeParam *mvo[2]; // opaque, translucent
u32 mvos[2], mvo_room;
RenderPass pass[10];
u32 passes;
PolyParam bg;
u32 verts, modtrig;
FB_X_CLIP_type x_clip;
FB_Y_CLIP_type y_clip;
bool rv;
/* For the GPU decode (gpu): each header's colours (face, room for
* header_room), and whether the frame is one it does not do as the
* full parse does (gpu_bad): a list open at the end of a render pass,
* or the data ending in the middle of something */
bool gpu;
bool gpu_bad;
TaWalkFace *face;
// the check's: the lists strip by strip, as the full parse has them
PolyParam *list[3];
u32 *list_header[3];
u32 list_room[3];
TaWalkStrips snap;
};
/* @strips: the vertices' end-of-strip bits are read, and the lists made
* strip by strip as the full parse makes them (for the check); otherwise
* a vertex is never looked at. The code is the full parse's, ta_main()
* and its helpers, with each vertex counted where it would be decoded. */
template <bool strips>
struct TaWalker
{
enum { NONE, POLY, SPRITE, MODVOL }; // what vertices are, as VertexDataFP has it
enum { WHOLE, HALF_PARAM, HALF_VERTEX }; // the next block is the second half of...
TaWalkFrame &f;
const u8 *root;
const u32 *entry;
u32 entries, ei;
const u32 *type_lut;
u32 list; // the list that is open (CurrentList), ListType_None for none
int pplist; // where polygon parameters go (CurrentPPlist): 0-2, -1 nowhere
bool has_pp; // there is one being filled (CurrentPP)
bool counted; // ...and its count is set: a sprite between its halves
u32 pp; // (strips) its place in its list
u32 hdr; // its header
u32 kind, vblocks, half;
u32 tileclip;
u32 data_blocks; // blocks the frame's buffer has
u32 used[3]; // polygon parameters in each list: strips (strips), or headers
u32 indexed[3]; // how many of them earlier render passes took
u32 pass_header; // the first header since the last render pass
bool pass_made; // (headers) this pass made or dropped a polygon parameter, or made vertices
u32 next_group;
bool fail;
PolyParam spare;
TaWalkFace fc; // (gpu) the colours of the moment
TaWalker(TaWalkFrame &frame) : f(frame) {}
static int pp_list(u32 l)
{
return l == ListType_Opaque ? 0 : l == ListType_Punch_Through ? 1 : l == ListType_Translucent ? 2 : -1;
}
bool eos(u32 block) const
{
return ((const PCW *)(root + (size_t)block * 32))->EndOfStrip != 0;
}
// (strips) CurrentPPlist->Append()
PolyParam *append()
{
const u32 l = (u32)pplist;
if (used[l] == f.list_room[l])
{
fail = true;
return &spare;
}
pp = used[l]++;
f.list_header[l][pp] = hdr;
return &f.list[l][pp];
}
TaWalkHeader *new_header(u32 block, u32 format, u32 vb)
{
if (f.headers == f.header_room)
{
fail = true;
return NULL;
}
hdr = f.headers++;
TaWalkHeader *h = &f.header[hdr];
memset(h, 0, sizeof(*h));
h->block = block;
h->group = TA_WALK_NO_GROUP;
h->list = (u8)(pplist == 0 ? ListType_Opaque : pplist == 1 ? ListType_Punch_Through : ListType_Translucent);
h->format = (u8)format;
h->vblocks = (u8)vb;
h->pp.first = f.verts;
return h;
}
// glob_param_bdc() and AppendSpriteParam(): which polygon parameter it is
void start_pp(const PolyParam &p)
{
if (strips)
{
PolyParam *d = (!has_pp || counted) ? append() : &f.list[pplist][pp];
*d = p;
f.list_header[pplist][pp] = hdr;
}
else
used[pplist]++;
pass_made = true;
has_pp = true;
counted = false;
}
/* poly_float_color(): face colour set @set from the four floats alpha,
* red, green, blue at @w */
void face_set(u32 set, const u32 *w)
{
float c[4];
memcpy(c, w, sizeof(c));
fc.col[set] = (u32)float_to_satu8(c[1]) | ((u32)float_to_satu8(c[2]) << 8)
| ((u32)float_to_satu8(c[3]) << 16) | ((u32)float_to_satu8(c[0]) << 24);
for (int i = 0; i < 3; i++)
{
const float v = saturate01(c[1 + i]);
memcpy(&fc.f[set * 3 + i], &v, sizeof(v));
}
}
/* The face colours of parameter @ppid at @block (AppendPolyParam1(),
* 2B, 4B), whose second half, if it has one, is the next block
* whatever comes between (ta_main() takes it as that) */
void face_param(u32 block, u32 ppid, u32 room)
{
const u32 *w = (const u32 *)(root + (size_t)block * 32);
if (ppid == 1)
face_set(0, w + 4);
else if (ppid == 2 || ppid == 4)
{
if (block + 1 >= room)
{
f.gpu_bad = true;
return;
}
face_set(0, w + 8);
face_set(ppid == 2 ? 1 : 2, w + 12);
}
}
void poly_param(const Ta_Dma *d, u32 block, u32 ppid, u32 format, u32 vb)
{
if (pplist < 0)
{
// (there is always a list here: see ta_main())
fail = true;
return;
}
TaWalkHeader *h = new_header(block, format, vb);
if (h == NULL)
return;
const TA_PolyParam0 *pp0 = (const TA_PolyParam0 *)d;
PolyParam &p = h->pp;
p.isp = pp0->isp;
p.tsp = pp0->tsp;
p.tcw = pp0->tcw;
p.pcw = pp0->pcw;
p.tileclip = tileclip;
p.texid = -1;
if (p.pcw.Texture)
p.texid = renderer->GetTexture(p.tsp, p.tcw);
p.tsp1.full = -1;
p.tcw1.full = -1;
p.texid1 = -1;
if (ppid == 3 || ppid == 4)
{
// (TA_PolyParam4A has them in the same place)
const TA_PolyParam3 *pp3 = (const TA_PolyParam3 *)d;
p.tsp1.full = pp3->tsp1.full;
p.tcw1.full = pp3->tcw1.full;
if (pp3->pcw.Texture)
p.texid1 = renderer->GetTexture(pp3->tsp1, pp3->tcw1, 1);
}
start_pp(p);
if (f.gpu)
{
face_param(block, ppid, data_blocks);
f.face[hdr] = fc;
}
}
void sprite_param(const Ta_Dma *d, u32 block)
{
if ((!has_pp || counted) && pplist < 0)
return; // (wldkickspw: dropped, and its vertices with it)
TaWalkHeader *h = new_header(block, TA_WALK_SPRITE, 2);
if (h == NULL)
return;
const TA_SpriteParam *spr = (const TA_SpriteParam *)d;
PolyParam &p = h->pp;
p.isp = spr->isp;
p.tsp = spr->tsp;
p.tcw = spr->tcw;
p.pcw = spr->pcw;
p.pcw.full |= PVR_PCW_SINGLE_RATE;
p.tileclip = tileclip;
p.texid = -1;
if (p.pcw.Texture)
p.texid = renderer->GetTexture(p.tsp, p.tcw);
p.tcw1.full = -1;
p.tsp1.full = -1;
p.texid1 = -1;
p.isp.CullMode ^= 1;
start_pp(p);
if (f.gpu)
{
fc.sprite[0] = spr->BaseCol;
fc.sprite[1] = spr->OffsCol;
f.face[hdr] = fc;
}
}
// (strips) EndPolyStrip()
void strip_end()
{
PolyParam &c = f.list[pplist][pp];
c.count = f.verts - c.first;
if (c.count > 0)
{
const PolyParam copy = c;
PolyParam *d = append();
*d = copy;
d->first = f.verts;
d->count = 0;
}
}
// AppendSpriteVertexB()'s new polygon parameter
void sprite_b()
{
if (strips)
{
const PolyParam copy = f.list[pplist][pp];
PolyParam *d = append();
*d = copy;
d->first = f.verts;
d->count = 0;
}
counted = false;
}
void end_modvol()
{
const int m = list == ListType_Opaque_Modifier_Volume ? 0 : list == ListType_Translucent_Modifier_Volume ? 1 : -1;
if (m < 0 || f.mvos[m] == 0)
return;
ModifierVolumeParam *p = &f.mvo[m][f.mvos[m] - 1];
p->count = f.modtrig - p->first;
if (p->count == 0)
f.mvos[m]--;
}
void start_modvol(const Ta_Dma *d)
{
end_modvol();
const int m = list == ListType_Opaque_Modifier_Volume ? 0 : list == ListType_Translucent_Modifier_Volume ? 1 : -1;
if (m < 0)
return;
if (f.mvos[m] == f.mvo_room)
{
fail = true;
return;
}
const TA_ModVolParam *param = (const TA_ModVolParam *)d;
ModifierVolumeParam *p = &f.mvo[m][f.mvos[m]++];
p->isp.full = param->isp.full;
p->isp.VolumeLast = param->pcw.Volume != 0;
p->first = f.modtrig;
p->count = TA_WALK_UNENDED;
p->tileclip = tileclip;
}
void end_list()
{
if (has_pp && !counted)
{
// (PopLast())
if (strips)
used[pplist]--;
pass_made = true;
}
has_pp = false;
pplist = -1;
if (list == ListType_Opaque_Modifier_Volume || list == ListType_Translucent_Modifier_Volume)
end_modvol();
}
void list_start(u32 l)
{
list = l;
if (pp_list(l) >= 0)
pplist = pp_list(l);
has_pp = false;
}
void add_run(u32 block, u32 blocks, u32 vertex, u32 header)
{
if (f.runs == f.run_room)
{
fail = true;
return;
}
TaWalkRun *r = &f.run[f.runs++];
r->block = block;
r->blocks = blocks;
r->vertex = vertex;
r->header = header;
}
/* The vertex blocks from @pos up to the next entry @nb, in a render
* pass that ends at @end. A vertex of 64 bytes that the entry cuts in
* two takes the entry for its second half, as ta_main() does. */
u32 vertices(u32 pos, u32 nb, u32 end)
{
const u32 n = nb - pos;
u32 next = nb, a, blocks = n;
if (kind == NONE)
return nb; // NullVertexData()
if (kind == POLY && vblocks == 1)
{
add_run(pos, n, f.verts, hdr);
if (strips)
for (u32 b = pos; b < nb; b++)
{
f.verts++;
if (eos(b))
strip_end();
}
else
f.verts += n;
f.header[hdr].pp.count += n;
pass_made = true;
/* (gpu) A parameter where a strip has not ended: the full parse
* takes it for a vertex, the decode for what it is */
if (f.gpu && nb < end && !eos(nb - 1))
f.gpu_bad = true;
return nb;
}
const u32 halves = (n + 1) / 2; // first halves
const bool cut = (n & 1) != 0; // the last of them has its second half at nb
if (cut)
{
if (nb < end)
{
if (ei < entries && entry[ei] == nb)
ei++;
next = nb + 1;
blocks++;
}
else
half = HALF_VERTEX;
}
if (kind == MODVOL)
{
const int m = list == ListType_Opaque_Modifier_Volume ? 0 : 1;
add_run(pos, blocks, f.modtrig, TA_WALK_MODVOL | (f.mvos[m] ? f.mvos[m] - 1 : 0));
f.modtrig += halves;
return next;
}
if (kind == SPRITE)
{
if (!has_pp)
return next; // AppendSpriteVertexA() and B(): nothing
add_run(pos, blocks, f.verts, hdr);
for (a = 0; a < halves; a++)
{
if (strips)
f.list[pplist][pp].count = 4;
counted = true;
f.verts += 4;
f.header[hdr].pp.count += 4;
if (a + 1 < halves || half != HALF_VERTEX)
sprite_b();
}
pass_made = true;
return next;
}
// 64-byte polygon vertices
if (f.gpu && nb < end && !eos(cut ? nb - 1 : nb - 2))
f.gpu_bad = true; // (as above)
add_run(pos, blocks, f.verts, hdr);
if (strips)
for (a = 0; a < halves; a++)
{
f.verts++;
if (eos(pos + a * 2))
strip_end();
}
else
f.verts += halves;
f.header[hdr].pp.count += halves;
pass_made = true;
return next;
}
void set_tile_clip(u32 xmin, u32 ymin, u32 xmax, u32 ymax)
{
tileclip = (tileclip & 0xF0000000) | xmin | (xmax << 6) | (ymin << 12) | (ymax << 17);
}
void tile_clip_mode(u32 mode)
{
tileclip = (tileclip & ~0xF0000000) | (mode << 28);
}
// The parameter at @pos, an entry
u32 param(u32 pos, u32 end)
{
const Ta_Dma *d = (const Ta_Dma *)(root + (size_t)pos * 32);
u32 uid, format, vb;
ei++;
switch (d->pcw.ParaType)
{
case ParamType_End_Of_List:
if (list == ListType_None)
list = d->pcw.ListType;
else
end_list();
list = ListType_None;
kind = NONE;
return pos + 1;
case ParamType_User_Tile_Clip:
set_tile_clip(d->data_32[3] & 63, d->data_32[4] & 31, d->data_32[5] & 63, d->data_32[6] & 31);
return pos + 1;
case ParamType_Polygon_or_Modifier_Volume:
tile_clip_mode(d->pcw.User_Clip);
if (list == ListType_None && d->pcw.ListType > ListType_Punch_Through)
return pos + 1;
if (list == ListType_None)
list_start(d->pcw.ListType);
if (IsModVolList(list))
{
start_modvol(d);
kind = MODVOL;
vblocks = 2;
return pos + 1;
}
uid = type_lut[d->pcw.obj_ctrl];
if (uid == 0xFFFFFFFF)
return pos + 1;
format = (u8)uid;
vb = format == 5 || format == 6 || (format >= 11 && format <= 14) ? 2 : 1;
kind = POLY;
vblocks = vb;
poly_param(d, pos, (u8)(uid >> 8), format, vb);
if ((uid >> 30) == SZ32)
return pos + 1;
if (pos + 1 < end)
{
if (ei < entries && entry[ei] == pos + 1)
ei++;
return pos + 2;
}
half = HALF_PARAM;
return pos + 1;
case ParamType_Sprite:
tile_clip_mode(d->pcw.User_Clip);
if (list == ListType_None && d->pcw.ListType > ListType_Punch_Through)
return pos + 1;
if (list == ListType_None)
list_start(d->pcw.ListType);
kind = SPRITE;
vblocks = 2;
sprite_param(d, pos);
return pos + 1;
case ParamType_Vertex_Parameter:
// (the walk never notes one: the table is not this data's)
fail = true;
return end;
default:
// Object_List_Set, and the two there are none of
return pos + 1;
}
}
// ta_parse_vdrc()'s work for a render pass that ends here
void pass_end(u32 pass, bool &empty_context)
{
u32 i, l;
/* A list still open: the full parse has the polygon parameter it
* is filling in the pass's lists, with no vertices, and makes
* indices for it (make_index() links it to the strip before);
* and a modifier volume's count is not set. The GPU decode does
* not do such a frame. */
if (list != ListType_None)
f.gpu_bad = true;
if (pass == 0)
{
// the first pass's opaque polygons are not blended
f.bg.tsp.SrcInstr = 1;
f.bg.tsp.DstInstr = 0;
for (i = 0; i < f.headers; i++)
if (f.header[i].list == ListType_Opaque)
{
f.header[i].pp.tsp.SrcInstr = 1;
f.header[i].pp.tsp.DstInstr = 0;
}
if (strips)
for (i = 0; i < used[0]; i++)
{
f.list[0][i].tsp.SrcInstr = 1;
f.list[0][i].tsp.DstInstr = 0;
}
}
bool empty_pass;
if (pass == 0)
empty_pass = used[0] == 0 && used[1] == 0 && used[2] == 0;
else if (strips)
{
const RenderPass &last = f.pass[f.passes - 1];
empty_pass = used[0] == last.op_count && used[1] == last.pt_count && used[2] == last.tr_count;
}
else
empty_pass = !pass_made;
pass_made = false;
empty_context = empty_context && empty_pass;
if (pass != 0 && empty_pass)
return;
RenderPass &r = f.pass[f.passes++];
r.op_count = used[0];
r.mvo_count = f.mvos[0];
r.pt_count = used[1];
r.tr_count = used[2];
r.mvo_tr_count = f.mvos[1];
getRegionSettings(pass, &r);
if (strips)
for (l = 0; l < 3; l++)
{
TaWalkStrips &s = f.snap;
for (i = indexed[l]; i < used[l] && s.count[l] < s.room[l]; i++, s.count[l]++)
{
s.pp[l][s.count[l]] = f.list[l][i];
s.header[l][s.count[l]] = f.list_header[l][i];
}
indexed[l] = used[l];
}
/* The draw groups: polygons one after another in a list, drawn the
* same way (poly_same_state()), are what make_index() joins; within
* a pass, and the translucent ones never. The background is the
* first opaque polygon of the first. */
const PolyParam *last[2] = { f.passes == 1 ? &f.bg : NULL, NULL };
u32 last_group[2] = { 0, 0 };
for (i = pass_header; i < f.headers; i++)
{
TaWalkHeader &h = f.header[i];
h.pass = (u8)(f.passes - 1);
if (h.pp.count == 0)
continue;
l = (u32)pp_list(h.list);
if (l == 2)
h.group = next_group++;
else
{
if (last[l] != NULL && poly_same_state(last[l], &h.pp))
h.group = last_group[l];
else
h.group = next_group++;
last[l] = &h.pp;
last_group[l] = h.group;
}
}
pass_header = f.headers;
}
bool run(const TA_context *ctx, u32 count)
{
const tad_context &tad = ctx->tad;
bool empty_context = true;
root = tad.thd_root;
entry = ctx->walk;
entries = count;
type_lut = TaTypeLut::instance().table;
list = ListType_None;
pplist = -1;
has_pp = counted = false;
pp = hdr = 0;
kind = NONE;
vblocks = 1;
half = WHOLE;
tileclip = 0;
set_tile_clip(0, 0, 39, 14);
tile_clip_mode(0);
indexed[0] = indexed[1] = indexed[2] = 0;
pass_header = 0;
pass_made = false;
next_group = 1;
fail = false;
data_blocks = ctx->data_size / 32;
f.gpu_bad = false;
if (f.gpu)
{
// vdec_init()'s
for (int i = 0; i < 4; i++)
fc.col[i] = 0xFFFFFFFFu;
for (int i = 0; i < 12; i++)
fc.f[i] = 0x3F800000u;
fc.sprite[0] = fc.sprite[1] = 0;
}
f.headers = f.runs = 0;
f.mvos[0] = f.mvos[1] = 0;
f.passes = 0;
f.verts = 4;
f.modtrig = 0;
f.bg = f.bg_in;
if (f.bg.pcw.Texture)
{
f.bg.texid = renderer->GetTexture(f.bg.tsp, f.bg.tcw);
empty_context = false;
}
used[0] = 1;
used[1] = used[2] = 0;
if (strips)
{
f.list[0][0] = f.bg;
f.list_header[0][0] = TA_WALK_BG;
f.snap.count[0] = f.snap.count[1] = f.snap.count[2] = 0;
}
for (u32 pass = 0; pass <= tad.render_pass_count && !fail; pass++)
{
const u8 *from = pass == 0 ? tad.thd_root : tad.render_passes[pass - 1];
const u8 *to = pass == tad.render_pass_count ? tad.End() : tad.render_passes[pass];
const u32 start = (u32)(from - root) / 32;
const u32 end = to > from ? (u32)(to - root) / 32 : start;
u32 pos = start;
u32 lo = 0, hi = entries;
// the first entry from here on
while (lo < hi)
{
const u32 mid = (lo + hi) / 2;
if (entry[mid] < start)
lo = mid + 1;
else
hi = mid;
}
ei = lo;
if (half != WHOLE && pos < end)
{
// the second half of what the last pass ended in
if (half == HALF_VERTEX && kind == SPRITE && has_pp)
sprite_b();
if (ei < entries && entry[ei] == pos)
ei++;
pos++;
half = WHOLE;
}
while (pos < end && !fail)
{
const u32 nb = ei < entries && entry[ei] < end ? entry[ei] : end;
pos = nb > pos ? vertices(pos, nb, end) : param(pos, end);
}
if (!fail)
pass_end(pass, empty_context);
}
if (fail)
return false;
// (the data ends in the middle of a parameter or vertex)
if (half != WHOLE)
f.gpu_bad = true;
f.rv = !empty_context;
f.x_clip = f.x_clip_in;
f.y_clip = f.y_clip_in;
if (f.rv)
{
u32 xmin, xmax, ymin, ymax;
walkRegionArray<false>(xmin, xmax, ymin, ymax);
f.x_clip.min = ta_max(f.x_clip.min, xmin);
f.x_clip.max = ta_min(f.x_clip.max, xmax + 31);
f.y_clip.min = ta_max(f.y_clip.min, ymin);
f.y_clip.max = ta_min(f.y_clip.max, ymax + 31);
}
return true;
}
};
/* [0] the header pass's, [1] the check's, strip by strip; what the full
* parse gave make_index(). Made by ta_walk_init() (when the check is turned
* on), for frames of up to walk_data_size, and never grown. */
static TaWalkFrame walk_frame[2];
static TaWalkStrips walk_full;
static u32 walk_data_size;
void ta_walk_term(void)
{
for (int i = 0; i < 2; i++)
{
TaWalkFrame &f = walk_frame[i];
free(f.header);
free(f.run);
free(f.mvo[0]);
free(f.mvo[1]);
free(f.face);
for (int l = 0; l < 3; l++)
{
free(f.list[l]);
free(f.list_header[l]);
free(f.snap.pp[l]);
free(f.snap.header[l]);
}
memset(&f, 0, sizeof(f));
}
for (int l = 0; l < 3; l++)
free(walk_full.pp[l]);
memset(&walk_full, 0, sizeof(walk_full));
walk_data_size = 0;
}
bool ta_walk_init(void)
{
const u32 size = ta_data_size();
const u32 scale = size / TA_DATA_SIZE;
/* (the room of the lists in TA_context::Alloc()) */
const u32 list_room[3] = { 16384 * scale, 5120 * scale, (STRIPS_AS_PPARAMS ? 10240 : 8192) * scale };
bool ok = true;
if (walk_data_size >= size)
return true;
ta_walk_term();
for (int i = 0; i < 2; i++)
{
TaWalkFrame &f = walk_frame[i];
f.header_room = TA_WALK_MAX * scale;
f.run_room = TA_WALK_MAX * scale + 16;
f.mvo_room = 4096 * scale;
f.header = (TaWalkHeader *)malloc(f.header_room * sizeof(TaWalkHeader));
f.run = (TaWalkRun *)malloc(f.run_room * sizeof(TaWalkRun));
f.mvo[0] = (ModifierVolumeParam *)malloc(f.mvo_room * sizeof(ModifierVolumeParam));
f.mvo[1] = (ModifierVolumeParam *)malloc(f.mvo_room * sizeof(ModifierVolumeParam));
ok = ok && f.header && f.run && f.mvo[0] && f.mvo[1];
if (i == 0)
{
f.face = (TaWalkFace *)malloc(f.header_room * sizeof(TaWalkFace));
ok = ok && f.face;
}
if (i == 1)
for (int l = 0; l < 3; l++)
{
f.list_room[l] = f.snap.room[l] = list_room[l];
f.list[l] = (PolyParam *)malloc(list_room[l] * sizeof(PolyParam));
f.list_header[l] = (u32 *)malloc(list_room[l] * sizeof(u32));
f.snap.pp[l] = (PolyParam *)malloc(list_room[l] * sizeof(PolyParam));
f.snap.header[l] = (u32 *)malloc(list_room[l] * sizeof(u32));
ok = ok && f.list[l] && f.list_header[l] && f.snap.pp[l] && f.snap.header[l];
}
}
for (int l = 0; l < 3; l++)
{
walk_full.room[l] = list_room[l];
walk_full.pp[l] = (PolyParam *)malloc(list_room[l] * sizeof(PolyParam));
ok = ok && walk_full.pp[l];
}
if (!ok)
{
ta_walk_term();
WARN_LOG(PVR, "TA header pass: no memory for it\n");
return false;
}
/* (touched now, so that no frame takes the page faults of their first
* use) */
for (int i = 0; i < 2; i++)
{
TaWalkFrame &f = walk_frame[i];
memset(f.header, 0, f.header_room * sizeof(TaWalkHeader));
memset(f.run, 0, f.run_room * sizeof(TaWalkRun));
memset(f.mvo[0], 0, f.mvo_room * sizeof(ModifierVolumeParam));
memset(f.mvo[1], 0, f.mvo_room * sizeof(ModifierVolumeParam));
if (f.face)
memset(f.face, 0, f.header_room * sizeof(TaWalkFace));
for (int l = 0; i == 1 && l < 3; l++)
{
memset(f.list[l], 0, f.list_room[l] * sizeof(PolyParam));
memset(f.list_header[l], 0, f.list_room[l] * sizeof(u32));
memset(f.snap.pp[l], 0, f.snap.room[l] * sizeof(PolyParam));
memset(f.snap.header[l], 0, f.snap.room[l] * sizeof(u32));
}
}
for (int l = 0; l < 3; l++)
memset(walk_full.pp[l], 0, walk_full.room[l] * sizeof(PolyParam));
walk_data_size = size;
return true;
}
/* The frame's table, if it is to be trusted: how many entries */
static bool ta_walk_table(const TA_context *ctx, u32 *count)
{
const tad_context &tad = ctx->tad;
u32 n = ctx->walk_count;
if (ctx->data_size > walk_data_size)
return false;
// (a frame with no data has nothing in its table, walked or not)
if (tad.End() == tad.thd_root)
{
*count = 0;
return true;
}
if (n == TA_WALK_INVALID || ctx->walk == NULL)
return false;
/* The lists were started again and the new data has nothing but
* vertices so far (all the rest would have been noted): the table is
* still the old data's. */
if (ctx->walk_restart && tad.thd_data != tad.thd_root)
n = 0;
if (n != 0 && ctx->walk[n - 1] >= (u32)(tad.End() - tad.thd_root) / 32)
return false;
*count = n;
return true;
}
template <bool strips>
static bool ta_walk_run(TA_context *ctx, TaWalkFrame &f, u32 count)
{
TaWalker<strips> w(f);
return w.run(ctx, count);
}
bool ta_walk_parse(TA_context *ctx)
{
TaWalkFrame &f = walk_frame[0];
u32 count;
if (!ta_walk_table(ctx, &count))
return false;
f.bg_in = *ctx->rend.global_param_op.head();
f.x_clip_in = ctx->rend.fb_X_CLIP;
f.y_clip_in = ctx->rend.fb_Y_CLIP;
return ta_walk_run<false>(ctx, f, count);
}
/* ---- The GPU decode's tables (rend/ta_gpu.h) ----
*
* The header pass, and what it found written as the GPU decode reads it:
* the headers in the order make_index() goes through them (render pass,
* then list, then as they came), each with its vertices' place in that
* order, its format, its draw group's command slot, the words that say
* whether two are drawn the same way, and the colours its vertices take
* from it; the runs of vertex blocks; the modifier volume runs; the
* segments (a list of a render pass); and the background's four vertices,
* which FillBGP() made on this side. Nothing here reads a vertex. */
static bool ta_gpu_on;
static retro_atomic_int_t ta_gpu_want = RETRO_ATOMIC_INT_INITIALIZER(0);
void ta_set_gpu_wanted(bool on)
{
retro_atomic_store_release_int(&ta_gpu_want, on ? 1 : 0);
}
bool ta_gpu_wanted(void)
{
return retro_atomic_load_acquire_int(&ta_gpu_want) != 0;
}
void ta_gpu_sizes(TaGpuSizes *sz)
{
const u32 scale = ta_data_size() / TA_DATA_SIZE;
// (what TA_context::Alloc() gives each list, and the header pass's room)
sz->verts = 4 * 1024 * 1024 * scale / sizeof(Vertex);
sz->idx = 120 * 1024 * scale;
sz->list[0] = 16384 * scale;
sz->list[1] = 5120 * scale;
sz->list[2] = (STRIPS_AS_PPARAMS ? 10240 : 8192) * scale;
sz->modtrig = 16384 * scale;
sz->headers = TA_WALK_MAX * scale + 1;
sz->runs = TA_WALK_MAX * scale + 16;
sz->table_words = TAG_T_WORDS + sz->runs * TAG_R_WORDS + sz->headers * TAG_H_WORDS
+ sz->runs * TAG_M_WORDS + TAG_MAX_SEG * TAG_G_WORDS + 4 * TAG_V_WORDS
+ TAG_SH_ENTRY + sz->headers;
}
bool ta_gpu_processing(void)
{
return ta_gpu_on;
}
/* Whether any render pass the frame's data can have is sorted (the region
* array's settings, which getRegionSettings() reads for each pass the
* parse makes): a few words of video memory */
static bool ta_gpu_any_autosort(const TA_context *ctx)
{
RenderPass r;
for (u32 pass = 0; pass <= ctx->tad.render_pass_count; pass++)
{
getRegionSettings(pass, &r);
if (r.autosort)
return true;
}
return false;
}
/* ---- TA_GPU_SORT: the translucent triangles of the sorted render passes ----
*
* The per-triangle renderer draws a sorted pass's translucent triangles in
* the order GenSortedTo() gives them, one draw for each run of them drawn the
* same way, and only the CPU can choose the pipeline and texture of a draw.
* So the order is worked out here, from the frame's own blocks: of each
* translucent vertex of a sorted pass, only its position (whether it is an
* infinite vertex, ta_index.c's, and its depth) and its end-of-strip
* bit are read, and a sprite's fourth corner is solved as the parse solves
* it. Each strip's indices are what make_index() gives it (the translucent
* lists are never joined), its triangles are GenSortedTo()'s, with the
* winding of the renderer's provoking vertex (the first, Vulkan's, with
* TA_GPU_PROVOKE; else the last, OpenGL's), and their depths are each the
* least of its three vertices'. A stable radix sort of those depths as
* integers that order as the floats do (tag_fkey(); no vertex that makes
* an index has a depth that is not a number) gives std::stable_sort()'s
* order; the draws are made as GenSortedTo() makes them. The vertices,
* colours, indices and the rest are the GPU's: the indices written here
* name the GPU decode's vertices, by their number. Nothing
* is allocated: the room, made by ta_gpu_sort_reserve(), is for as many
* triangles as a frame can have indices. */
static struct TaGpuSortState
{
u32 room; // triangles
u32 *tri; // 4 words a triangle: its three indices, its header
u64 *keys[2]; // depth key << 32 | triangle
SortTrigDrawParam *run;
u32 pass_first[TAG_MAX_PASS], pass_count[TAG_MAX_PASS]; // each pass's triangles (0: not sorted)
u8 pass_buf[TAG_MAX_PASS]; // ...and which of keys[] has them sorted
u32 pass_run[TAG_MAX_PASS], pass_runs[TAG_MAX_PASS];
u32 tris;
u64 ns;
bool first_provoking; // (TA_GPU_PROVOKE) GenSortedTo()'s firstProvoking
/* Each sorted pass's depth prepass (rend/sorter.h): the triangles of
* its polygons that can hide what is behind them, by polygon and the
* polygons by what they are drawn with (porder: triangle numbers, ptris
* of them; their indices go after the sorted ones), and its draws */
u32 *porder;
u32 ptris;
SortTrigDrawParam *prun;
u32 pass_prun[TAG_MAX_PASS], pass_pruns[TAG_MAX_PASS];
u32 hroom; // headers the next three have room for
u32 *hcount; // a header's triangles, then where they go (all 0 between frames)
u32 *htouched;
std::pair<PrepassKey, u32> *hkey;
/* The prepass's checks (TAG_K_PREPASS): each strip of the sorted
* passes, as TaGpuSortStrip made it - its first vertex, its vertices
* and its first triangle - (sroom of them; past that, no checks this
* frame), each triangle's place among the prepass's, and the checks:
* the strips in the prepass whose alpha is their vertices', for the GPU
* to look at them (TAG_PC_WORDS each) */
u32 *strip;
u32 strips, sroom;
bool strips_full;
u32 *tpos;
u32 *check;
u32 checks;
} gsort;
bool ta_gpu_sort_reserve(bool on)
{
TaGpuSortState &g = gsort;
TaGpuSizes sz;
if (on)
{
ta_gpu_sizes(&sz);
if (g.room >= sz.idx)
return true;
}
free(g.tri);
free(g.keys[0]);
free(g.keys[1]);
free(g.run);
free(g.porder);
free(g.prun);
free(g.hcount);
free(g.htouched);
free(g.hkey);
free(g.strip);
free(g.tpos);
free(g.check);
memset(&g, 0, sizeof(g));
if (!on)
return true;
/* (a frame's indices are at most its room, and a triangle takes an
* index or more) */
const u32 room = sz.idx;
g.tri = (u32 *)malloc((size_t)room * 4 * sizeof(u32));
g.keys[0] = (u64 *)malloc((size_t)room * sizeof(u64));
g.keys[1] = (u64 *)malloc((size_t)room * sizeof(u64));
g.run = (SortTrigDrawParam *)malloc((size_t)room * sizeof(SortTrigDrawParam));
const u32 hroom = sz.headers;
g.porder = (u32 *)malloc((size_t)room * sizeof(u32));
g.prun = (SortTrigDrawParam *)malloc((size_t)hroom * sizeof(SortTrigDrawParam));
g.hcount = (u32 *)calloc(hroom, sizeof(u32));
g.htouched = (u32 *)malloc((size_t)hroom * sizeof(u32));
g.hkey = (std::pair<PrepassKey, u32> *)malloc((size_t)hroom * sizeof(std::pair<PrepassKey, u32>));
// (a translucent strip is a polygon parameter of its own: as many as the list has room for)
const u32 sroom = sz.list[2];
g.strip = (u32 *)malloc((size_t)sroom * 3 * sizeof(u32));
g.tpos = (u32 *)malloc((size_t)room * sizeof(u32));
g.check = (u32 *)malloc((size_t)sroom * TAG_PC_WORDS * sizeof(u32));
if (g.tri == NULL || g.keys[0] == NULL || g.keys[1] == NULL || g.run == NULL
|| g.porder == NULL || g.prun == NULL || g.hcount == NULL || g.htouched == NULL || g.hkey == NULL
|| g.strip == NULL || g.tpos == NULL || g.check == NULL)
{
ta_gpu_sort_reserve(false);
WARN_LOG(PVR, "TA processing: no memory for sorting translucent triangles\n");
return false;
}
// (touched now, so that no frame takes the page faults of their first use)
memset(g.tri, 0, (size_t)room * 4 * sizeof(u32));
memset(g.keys[0], 0, (size_t)room * sizeof(u64));
memset(g.keys[1], 0, (size_t)room * sizeof(u64));
memset(g.run, 0, (size_t)room * sizeof(SortTrigDrawParam));
memset(g.porder, 0, (size_t)room * sizeof(u32));
memset(g.prun, 0, (size_t)hroom * sizeof(SortTrigDrawParam));
memset(g.htouched, 0, (size_t)hroom * sizeof(u32));
memset((void *)g.hkey, 0, (size_t)hroom * sizeof(std::pair<PrepassKey, u32>));
memset(g.strip, 0, (size_t)sroom * 3 * sizeof(u32));
memset(g.tpos, 0, (size_t)room * sizeof(u32));
memset(g.check, 0, (size_t)sroom * TAG_PC_WORDS * sizeof(u32));
g.sroom = sroom;
g.hroom = hroom;
g.room = room;
return true;
}
const SortTrigDrawParam *ta_gpu_sorted(u32 pass, u32 *count)
{
if (pass >= TAG_MAX_PASS || gsort.pass_runs[pass] == 0)
{
*count = 0;
return NULL;
}
*count = gsort.pass_runs[pass];
return gsort.run + gsort.pass_run[pass];
}
const SortTrigDrawParam *ta_gpu_prepass(u32 pass, u32 *count)
{
if (pass >= TAG_MAX_PASS || gsort.pass_pruns[pass] == 0)
{
*count = 0;
return NULL;
}
*count = gsort.pass_pruns[pass];
return gsort.prun + gsort.pass_prun[pass];
}
u32 ta_gpu_sorted_indices(void)
{
return (gsort.tris + gsort.ptris) * 3;
}
u32 ta_gpu_prepass_checks(void)
{
return gsort.checks;
}
u32 ta_gpu_sorted_words(void)
{
return ta_gpu_sorted_indices() + (gsort.checks != 0 ? 1 + gsort.checks * TAG_PC_WORDS : 0);
}
void ta_gpu_sorted_write(u32 *dst)
{
const TaGpuSortState &g = gsort;
for (u32 p = 0; p < TAG_MAX_PASS; p++)
{
const u64 *k = g.keys[g.pass_buf[p]] + g.pass_first[p];
for (u32 n = g.pass_count[p]; n != 0; n--, k++, dst += 3)
{
const u32 *t = &g.tri[(u32)*k * 4];
dst[0] = t[0];
dst[1] = t[1];
dst[2] = t[2];
}
}
for (u32 i = 0; i < g.ptris; i++, dst += 3)
{
const u32 *t = &g.tri[g.porder[i] * 4];
dst[0] = t[0];
dst[1] = t[1];
dst[2] = t[2];
}
// (the prepass's checks: TAG_T_PREP)
if (g.checks != 0)
{
dst[0] = g.checks;
memcpy(dst + 1, g.check, (size_t)g.checks * TAG_PC_WORDS * sizeof(u32));
}
}
u64 ta_gpu_sorted_ns(void)
{
return gsort.ns;
}
/* make_index() of one strip at a time, an index at a time (no strip is
* joined to another in a translucent list), with each index's depth key,
* and GenSortedTo()'s triangles of the indices as they come */
struct TaGpuSortStrip
{
TaGpuSortState &g;
u32 hdr; // the strip's header
u32 count; // its indices so far
int last_good; // the last kept vertex's place, -1 for none yet
u32 last_good_key;
bool dupe; // the next kept vertex goes in twice (a link)
bool link; // infinite vertices have come since the last kept one
u32 i; // the vertex's place in the strip
u32 h_place[2], h_key[2]; // the two indices before
bool full; // more triangles than the room (cannot be: see ta_gpu_sort_reserve())
TaGpuSortStrip(TaGpuSortState &state) : g(state), full(false) {}
void start(u32 header)
{
hdr = header;
count = 0;
last_good = -1;
dupe = false;
link = false;
i = 0;
}
// (the prepass's checks) the strip's vertex @place: its first makes its record
void note(u32 place)
{
if (i == 0)
{
if (g.strips == g.sroom)
{
g.strips_full = true;
return;
}
u32 *s = &g.strip[g.strips++ * 3];
s[0] = place;
s[1] = 0;
s[2] = g.tris;
}
if (!g.strips_full)
g.strip[(g.strips - 1) * 3 + 1] = place - g.strip[(g.strips - 1) * 3] + 1;
}
void put(u32 place, u32 key)
{
if (count >= 2)
{
const u32 t = g.tris;
if (t == g.room)
full = true;
else
{
/* GenSortedTo(), firstProvoking: every other triangle starts
* with the vertex that holds its colour; without it, the
* first two of every other one change places */
u32 *d = &g.tri[t * 4];
if (((count - 2) & 1) && g.first_provoking)
{
d[0] = h_place[0];
d[1] = place;
d[2] = h_place[1];
}
else if ((count - 2) & 1)
{
d[0] = h_place[1];
d[1] = h_place[0];
d[2] = place;
}
else
{
d[0] = h_place[0];
d[1] = h_place[1];
d[2] = place;
}
d[3] = hdr;
u32 key3 = h_key[0] < h_key[1] ? h_key[0] : h_key[1];
if (key < key3)
key3 = key;
g.keys[0][t] = ((u64)key3 << 32) | t;
g.tris = t + 1;
}
}
h_place[0] = h_place[1];
h_key[0] = h_key[1];
h_place[1] = place;
h_key[1] = key;
count++;
}
// the strip's next vertex, number @place (make_index()'s loop, its look ahead for the next kept vertex turned round)
void vertex(u32 place, u32 x, u32 y, u32 z)
{
note(place);
if (tag_inf(x, y, z) != 0)
link = true;
else
{
if (link && last_good >= 0)
{
put((u32)last_good, last_good_key);
dupe = true;
}
link = false;
const u32 key = tag_fkey(z);
last_good = (int)place;
last_good_key = key;
if (dupe)
{
put(place, key);
dupe = false;
}
if ((i ^ count) & 1)
put(place, key);
put(place, key);
}
i++;
}
};
/* The translucent strips of the sorted render passes, from their blocks,
* in the order the parse makes them: the runs as they came, and each
* header's strips in its runs */
static bool ta_gpu_sort_strips(const TaWalkFrame &f, const u8 *root, const bool *sorted)
{
TaGpuSortState &g = gsort;
TaGpuSortStrip s(g);
u32 cur_pass = TAG_MAX_PASS;
u32 open_hdr = TA_WALK_NO_GROUP;
for (u32 i = 0; i < f.runs; i++)
{
const TaWalkRun &run = f.run[i];
if (run.header & TA_WALK_MODVOL)
continue;
const TaWalkHeader &h = f.header[run.header];
if (h.list != ListType_Translucent || !sorted[h.pass])
continue;
if (h.pass != cur_pass)
{
cur_pass = h.pass;
g.pass_first[cur_pass] = g.tris;
}
// (its vertices: up to the next run of vertices, or the frame's last)
u32 j = i + 1, end = f.verts;
while (j < f.runs && (f.run[j].header & TA_WALK_MODVOL))
j++;
if (j < f.runs)
end = f.run[j].vertex;
const u32 n = end - run.vertex;
// (the GPU decode's vertex buffer has them in the order they came, as the parse's)
const u32 place = run.vertex;
if (open_hdr != run.header)
{
// (a strip does not go on from one header to the next: the header pass leaves such a frame to the parse)
s.start(run.header);
open_hdr = run.header;
}
if (h.format == TA_WALK_SPRITE)
{
for (u32 k = 0; k + 4 <= n; k += 4)
{
const u32 *w = (const u32 *)(root + (size_t)(run.block + 2 * (k >> 2)) * 32);
/* the corners as AppendSpriteVertexA() and B() put them, and
* the first's depth as the parse works it out */
Vertex c[4];
memset(c, 0, sizeof(c));
memcpy(&c[2].x, &w[1], 12);
memcpy(&c[3].x, &w[4], 12);
memcpy(&c[1].x, &w[7], 12);
memcpy(&c[0].x, &w[10], 8);
FifoSplitter::CaclulateSpritePlane(c);
u32 z0;
memcpy(&z0, &c[0].z, 4);
s.start(run.header);
s.vertex(place + k, w[10], w[11], z0);
s.vertex(place + k + 1, w[7], w[8], w[9]);
s.vertex(place + k + 2, w[1], w[2], w[3]);
s.vertex(place + k + 3, w[4], w[5], w[6]);
}
open_hdr = TA_WALK_NO_GROUP;
}
else
{
const u32 vb = h.vblocks;
const u32 *w = (const u32 *)(root + (size_t)run.block * 32);
for (u32 k = 0; k < n; k++, w += vb * 8)
{
s.vertex(place + k, w[1], w[2], w[3]);
if (((const PCW *)w)->EndOfStrip)
s.start(run.header);
}
}
g.pass_count[cur_pass] = g.tris - g.pass_first[cur_pass];
}
return !s.full;
}
/* A stable sort of @n keys from @k[0] by their top 32 bits, a digit of 11,
* 11 and 10 bits at a time, from the lowest; a digit all of them share
* takes no pass. Which of the two has them in the end. (A few hundred are
* sorted whole, the triangle's number in the low half breaking ties: the
* same order, without the passes' fixed cost.) */
static u32 ta_gpu_sort_keys(u64 *k[2], u32 first, u32 n)
{
static u32 hist[3][2048];
static const u32 shift[3] = { 32, 43, 54 };
u32 at = 0;
if (n <= 256)
{
std::sort(k[0] + first, k[0] + first + n);
return 0;
}
memset(hist, 0, sizeof(hist));
const u64 *a = k[0] + first;
for (u32 i = 0; i < n; i++)
{
const u64 v = a[i];
hist[0][(v >> 32) & 2047]++;
hist[1][(v >> 43) & 2047]++;
hist[2][(v >> 54) & 1023]++;
}
for (u32 d = 0; d < 3; d++)
{
const u64 *src = k[at] + first;
if (hist[d][(src[0] >> shift[d]) & 2047] == n)
continue;
u32 sum = 0;
for (u32 b = 0; b < 2048; b++)
{
const u32 c = hist[d][b];
hist[d][b] = sum;
sum += c;
}
u64 *dst = k[at ^ 1] + first;
for (u32 i = 0; i < n; i++)
{
const u64 v = src[i];
dst[hist[d][(v >> shift[d]) & 2047]++] = v;
}
at ^= 1;
}
return at;
}
/* The prepass's checks (TAG_K_PREPASS): each strip with triangles in the
* prepass whose alpha is its vertices' (PrepassAlphaFromVertices()), for
* the GPU to look at the vertices of and to leave out if none has an alpha
* of 255, as GenPrepassTo() does. A strip's triangles there are one after
* the other (its header's are, in the order they were made). */
static void ta_gpu_prepass_make_checks(const TaWalkFrame &f)
{
TaGpuSortState &g = gsort;
g.checks = 0;
if (g.strips_full)
return;
for (u32 k = 0; k < g.strips; k++)
{
const u32 *s = &g.strip[k * 3];
const u32 tend = k + 1 < g.strips ? g.strip[(k + 1) * 3 + 2] : g.tris;
u32 first = TAG_NONE, n = 0;
for (u32 t = s[2]; t < tend; t++)
{
const u32 *d = &g.tri[t * 4];
if (d[0] == d[1] || d[1] == d[2] || d[0] == d[2])
continue;
const u32 at = g.tpos[t];
if (at == TAG_NONE)
break; // (not in the prepass: none of the strip's is)
if (first == TAG_NONE)
first = at;
else if (at != first + n)
{
first = TAG_NONE; // (cannot be: no check, it is drawn)
break;
}
n++;
}
if (first == TAG_NONE || n == 0 || !PrepassAlphaFromVertices(&f.header[g.tri[s[2] * 4 + 3]].pp))
continue;
u32 *c = &g.check[g.checks++ * TAG_PC_WORDS];
c[TAG_PC_V] = s[0];
c[TAG_PC_NV] = s[1];
c[TAG_PC_T] = g.tris + first;
c[TAG_PC_NT] = n;
}
/* (in the sorted indices' room, which is six words a triangle a frame
* can have: three a triangle sorted and drawn in the prepass take all
* of it only where no strip is - see ta_gpu_sort_reserve()) */
if (ta_gpu_sorted_words() > g.room * 6)
g.checks = 0;
}
/* The depth prepass of each sorted pass, as GenPrepassTo() makes it but for
* the vertices, which are the GPU's: the polygons whose alpha is their
* vertices' are drawn, and the GPU makes nothing of those of them none of
* whose vertices has an alpha of 255 (ta_gpu_prepass_checks()). The
* triangles are the sorted ones, before they were sorted. */
static void ta_gpu_prepass_frame(const TaWalkFrame &f)
{
TaGpuSortState &g = gsort;
const u32 none = ~0u;
u32 npr = 0;
g.ptris = 0;
memset(g.pass_pruns, 0, sizeof(g.pass_pruns));
// (no triangle in the prepass yet: TAG_NONE)
memset(g.tpos, 0xFF, (size_t)g.tris * sizeof(u32));
if (f.headers > g.hroom)
return;
for (u32 p = 0; p < f.passes && p < TAG_MAX_PASS; p++)
{
g.pass_prun[p] = npr;
const u32 n = g.pass_count[p];
const bool last = p == f.passes - 1;
if (n == 0 || !PrepassWanted(last))
continue;
const u32 t0 = g.pass_first[p];
// the polygons that can hide, and their triangles' counts
u32 nh = 0, nt = 0;
for (u32 t = t0; t < t0 + n; t++)
{
const u32 *d = &g.tri[t * 4];
if (d[0] == d[1] || d[1] == d[2] || d[0] == d[2])
continue;
u32 &c = g.hcount[d[3]];
if (c == 0)
{
g.htouched[nt++] = d[3];
const PolyParam *pp = &f.header[d[3]].pp;
if (!PrepassCandidate(pp, last))
{
c = none;
continue;
}
g.hkey[nh++] = std::make_pair(PrepassMakeKey(pp, 0), d[3]);
}
else if (c == none)
continue;
c++;
}
std::sort(g.hkey, g.hkey + nh,
[](const std::pair<PrepassKey, u32>& a, const std::pair<PrepassKey, u32>& b) {
return a.first < b.first || (a.first == b.first && a.second < b.second);
});
// where each one's triangles go, and the draws
u32 at = g.ptris;
for (u32 k = 0; k < nh; k++)
{
const u32 h = g.hkey[k].second;
const u32 c = g.hcount[h];
g.hcount[h] = at;
if (k == 0 || !(g.hkey[k].first == g.hkey[k - 1].first))
{
SortTrigDrawParam &r = g.prun[npr++];
r.ppid = &f.header[h].pp;
r.first = (g.tris + at) * 3;
r.count = 0;
}
g.prun[npr - 1].count += c * 3;
at += c;
}
for (u32 t = t0; t < t0 + n; t++)
{
const u32 *d = &g.tri[t * 4];
if (d[0] == d[1] || d[1] == d[2] || d[0] == d[2])
continue;
u32 &o = g.hcount[d[3]];
if (o != none)
{
g.tpos[t] = o;
g.porder[o++] = t;
}
}
for (u32 k = 0; k < nt; k++)
g.hcount[g.htouched[k]] = 0;
g.ptris = at;
g.pass_pruns[p] = npr - g.pass_prun[p];
}
}
/* The sorted passes of the frame the header pass made (before its lists
* are made into draw groups), with @seg_v each segment's vertices: false
* if they do not fit (which cannot be) */
static bool ta_gpu_sort_frame(TaWalkFrame &f, const u8 *root)
{
TaGpuSortState &g = gsort;
bool sorted[TAG_MAX_PASS];
u32 p;
const std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
g.tris = 0;
g.ptris = 0;
g.strips = 0;
g.strips_full = false;
g.checks = 0;
memset(g.pass_pruns, 0, sizeof(g.pass_pruns));
memset(g.pass_first, 0, sizeof(g.pass_first));
memset(g.pass_count, 0, sizeof(g.pass_count));
memset(g.pass_buf, 0, sizeof(g.pass_buf));
bool any = false;
for (p = 0; p < TAG_MAX_PASS; p++)
{
sorted[p] = p < f.passes && f.pass[p].autosort;
any = any || sorted[p];
}
if (!any)
return true;
if (!ta_gpu_sort_strips(f, root, sorted))
return false;
// each pass: sorted, and its draws made as GenSortedTo() makes them
u32 nrun = 0;
for (p = 0; p < f.passes; p++)
{
const u32 n = g.pass_count[p];
g.pass_run[p] = nrun;
if (n == 0)
continue;
g.pass_buf[p] = (u8)ta_gpu_sort_keys(g.keys, g.pass_first[p], n);
const u64 *k = g.keys[g.pass_buf[p]] + g.pass_first[p];
u32 cur = g.tri[(u32)k[0] * 4 + 3];
u32 start = 0;
for (u32 j = 1; j <= n; j++)
{
u32 h = 0;
if (j < n)
{
h = g.tri[(u32)k[j] * 4 + 3];
// (another strip of the same polygon, or one drawn the same way: the same draw)
if (h == cur || SortSameDraw(&f.header[h].pp, &f.header[cur].pp))
continue;
}
SortTrigDrawParam &r = g.run[nrun++];
r.ppid = &f.header[cur].pp;
r.first = (g.pass_first[p] + start) * 3;
r.count = (j - start) * 3;
cur = h;
start = j;
}
g.pass_runs[p] = nrun - g.pass_run[p];
}
ta_gpu_prepass_frame(f);
ta_gpu_prepass_make_checks(f);
g.ns = (u64)std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - t0).count();
return true;
}
/* TA_GPU_SHADOWS: what each render pass's opaque modifier volumes cover,
* from the triangles' blocks, as ModVolBounds() has it from the parse's */
static void ta_gpu_shadow_areas(const TaWalkFrame &f, const u8 *root, f32 (*area)[4])
{
u32 r = 0, prev = 0;
for (u32 p = 0; p < f.passes; p++)
{
ScreenBounds b = { 1e30f, 1e30f, -1e30f, -1e30f };
for (u32 m = prev; m < f.pass[p].mvo_count; m++)
{
const ModifierVolumeParam &mp = f.mvo[0][m];
for (u32 t = mp.first; t < mp.first + mp.count; t++)
{
// (the runs of both lists' volumes' triangles, which are numbered together, in their order)
while (r < f.runs && (!(f.run[r].header & TA_WALK_MODVOL) || f.run[r].vertex + (f.run[r].blocks + 1) / 2 <= t))
r++;
if (r == f.runs)
break;
const u32 *w = (const u32 *)(root + (size_t)(f.run[r].block + 2 * (t - f.run[r].vertex)) * 32);
float c[9];
memcpy(c, &w[1], sizeof(c));
b.add(c[0], c[1]);
b.add(c[3], c[4]);
b.add(c[6], c[7]);
}
}
prev = f.pass[p].mvo_count;
area[p][0] = b.min_x;
area[p][1] = b.min_y;
area[p][2] = b.max_x;
area[p][3] = b.max_y;
}
}
/* The raster renderers' modifier volume triangles (rend/modvol_clip.h),
* from the triangles' blocks: each one's nine words after its first
* block's control word, as the decode takes them (tag_modtrig_word()) */
static void ta_gpu_modvol_clip(const TaWalkFrame &f, const u8 *root, rend_context &rc)
{
struct Get
{
const TaWalkFrame &f;
const u8 *root;
u32 r;
void restart() { r = 0; }
ModTriangle operator()(u32 t)
{
// (the runs of both lists' volumes' triangles, which are numbered together, in their order)
if (r >= f.runs || f.run[r].vertex > t)
r = 0;
while (r < f.runs && (!(f.run[r].header & TA_WALK_MODVOL) || f.run[r].vertex + (f.run[r].blocks + 1) / 2 <= t))
r++;
ModTriangle tri;
if (r == f.runs)
{
memset(&tri, 0, sizeof(tri));
return tri;
}
const u32 *w = (const u32 *)(root + (size_t)(f.run[r].block + 2 * (t - f.run[r].vertex)) * 32);
memcpy(&tri, &w[1], sizeof(tri));
return tri;
}
} get = { f, root, 0 };
mv_clip_build(rc, f.modtrig, get);
}
/* Where flat strips are looked for (SSAA), each header's strips: whether
* all of them are flat, in its pp's single-rate bit, as the full parse
* finds it from its vertices. From the frame's own blocks, as the sort
* reads them: of each vertex only its depth word and its end-of-strip
* bit, the AND and the OR of the depths' bits over a strip
* (PVR_FLAT_RATE_BIT()). A sprite's bit is set already, and the
* background's. The draw groups are then given their strips' bits
* together, as flat_groups() gives the parse's. */
static u64 gflat_ns; // the last frame's (ta_gpu_flat_ns())
u64 ta_gpu_flat_ns(void)
{
return gflat_ns;
}
static void ta_gpu_flat_headers(TaWalkFrame &f, const u8 *root)
{
const std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
u32 open_hdr = TA_WALK_NO_GROUP;
u32 z_and = PVR_FLAT_AND_INIT, z_or = PVR_FLAT_OR_INIT;
for (u32 i = 0; i < f.headers; i++)
if (f.header[i].format != TA_WALK_SPRITE)
f.header[i].pp.pcw.full |= PVR_PCW_SINGLE_RATE;
for (u32 i = 0; i < f.runs; i++)
{
const TaWalkRun &run = f.run[i];
if (run.header & TA_WALK_MODVOL)
continue;
TaWalkHeader &h = f.header[run.header];
if (h.format == TA_WALK_SPRITE)
continue;
// (its vertices: up to the next run of vertices, or the frame's last)
u32 j = i + 1, end = f.verts;
while (j < f.runs && (f.run[j].header & TA_WALK_MODVOL))
j++;
if (j < f.runs)
end = f.run[j].vertex;
const u32 n = end - run.vertex;
if (open_hdr != run.header)
{
// (a strip does not go on from one header to the next: the header pass leaves such a frame to the parse)
open_hdr = run.header;
z_and = PVR_FLAT_AND_INIT;
z_or = PVR_FLAT_OR_INIT;
}
const u32 stride = h.vblocks * 8;
const u32 *w = (const u32 *)(root + (size_t)run.block * 32);
for (u32 k = 0; k < n; k++, w += stride)
{
z_and &= w[3];
z_or |= w[3];
if (((const PCW *)w)->EndOfStrip)
{
h.pp.pcw.full &= ~PVR_PCW_SINGLE_RATE | PVR_FLAT_RATE_BIT(z_and, z_or);
z_and = PVR_FLAT_AND_INIT;
z_or = PVR_FLAT_OR_INIT;
}
}
}
gflat_ns = (u64)std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - t0).count();
}
bool ta_gpu_prepare(TA_context *ctx, const TaGpuLayout *layout, u32 *table, u32 table_room, TaGpuFrame *out, u32 flags)
{
TaWalkFrame &f = walk_frame[0];
u32 count, i, l;
const bool draw = (flags & TA_GPU_DRAW) != 0;
out->why = TA_GPU_WHY_TABLE;
out->shadows = 0;
// (nothing sorted, until this frame is)
gsort.tris = 0;
gsort.ptris = 0;
gsort.ns = 0;
memset(gsort.pass_count, 0, sizeof(gsort.pass_count));
memset(gsort.pass_runs, 0, sizeof(gsort.pass_runs));
memset(gsort.pass_pruns, 0, sizeof(gsort.pass_pruns));
if (!ta_gpu_on || !ta_walk_table(ctx, &count))
return false;
/* A render of some of the frame's render passes (ta_select_passes())
* has the full parse, which makes those alone */
out->why = TA_GPU_WHY_PARSE;
if (!ctx->AllPasses())
return false;
if (draw)
{
/* What only the full parse makes: a frame it passes over (frame
* skipping), whose bookkeeping it keeps; the flat strips' and the
* region array's table, which are made from the vertices */
out->why = TA_GPU_WHY_PARSE;
if (!(ctx->rend.isRTT || 0 == ((ta_parse_cnt + 1) % (settings.pvr.ta_skip + 1)))
|| region_table_wanted)
return false;
/* A renderer that sorts translucent polygons on the CPU sorts them
* from the CPU's vertices: it has the full parse for a frame that
* may have a sorted pass */
out->why = TA_GPU_WHY_SORT;
if (((flags & TA_GPU_NO_SORT) || ((flags & TA_GPU_SORT) && gsort.room == 0)) && ta_gpu_any_autosort(ctx))
return false;
}
out->why = TA_GPU_WHY_WALK;
f.bg_in = *ctx->rend.global_param_op.head();
f.x_clip_in = ctx->rend.fb_X_CLIP;
f.y_clip_in = ctx->rend.fb_Y_CLIP;
f.gpu = true;
const bool ok = ta_walk_run<false>(ctx, f, count);
f.gpu = false;
if (!ok || f.gpu_bad || f.passes > TAG_MAX_PASS)
return false;
TaGpuSizes sz;
ta_gpu_sizes(&sz);
/* What the full parse overruns with is left to it: it drops the frame
* then, as it always has */
if (f.verts > (u32)ctx->rend.verts.capacity() || f.verts > sz.verts
|| f.modtrig > (u32)ctx->rend.modtrig.capacity() || f.modtrig > sz.modtrig)
return false;
// the headers with vertices, by segment, and the vertices of each
const u32 nseg = f.passes * 3;
u32 seg_hdr[TAG_MAX_SEG], seg_v[TAG_MAX_SEG], seg_last[TAG_MAX_SEG], seg_slot[TAG_MAX_SEG];
memset(seg_hdr, 0, sizeof(seg_hdr));
memset(seg_v, 0, sizeof(seg_v));
seg_hdr[0] = 1;
seg_v[0] = 4;
u32 nhdr = 1, nruns = 0, nmvruns = 0;
for (i = 0; i < f.headers; i++)
{
const TaWalkHeader &h = f.header[i];
if (h.pp.count == 0)
continue;
const u32 seg = h.pass * 3 + (u32)TaWalker<false>::pp_list(h.list);
seg_hdr[seg]++;
seg_v[seg] += h.pp.count;
nhdr++;
}
for (i = 0; i < f.runs; i++)
if (f.run[i].header & TA_WALK_MODVOL)
nmvruns++;
else
nruns++;
const u32 off_run = TAG_T_WORDS;
const u32 off_hdr = off_run + nruns * TAG_R_WORDS;
const u32 off_mvrun = off_hdr + nhdr * TAG_H_WORDS;
const u32 off_seg = off_mvrun + nmvruns * TAG_M_WORDS;
const u32 off_bg = off_seg + nseg * TAG_G_WORDS;
const u32 off_shd = off_bg + 4 * TAG_V_WORDS;
const bool shadows = draw && (flags & TA_GPU_SHADOWS) != 0;
// (an entry for each draw group at most, which is a header at most)
const u32 words = off_shd + (shadows ? TAG_SH_ENTRY + nhdr : 0);
if (words > table_room || nhdr > sz.headers)
return false;
rend_context &rc = ctx->rend;
if (draw)
{
/* The full parse drops a frame that runs out of room for its
* indices or polygon parameters, and the screen keeps the last
* picture; the decode knows that only on the GPU, which can then
* leave out the draws but not keep the picture. So the frames that
* might run out are left to the full parse, by what can be known
* here: a list has one more polygon parameter than strips while it
* is being filled (and the opaque list the background's), and a
* strip has a vertex at least - or, where that is too many, as many
* strips as the vertices' end-of-strip bits say, which is one word
* read a vertex, and every fourth corner of a sprite; make_index()
* puts at most four indices a vertex - one for the vertex, one for
* the parity, and the two that link a strip, or a run of infinite
* vertices, to what came before. */
out->why = TA_GPU_WHY_ROOM;
u32 lv[3] = { 0, 0, 0 };
for (i = 0; i < f.headers; i++)
if (f.header[i].pp.count != 0)
lv[TaWalker<false>::pp_list(f.header[i].list)] += f.header[i].pp.count;
lv[0]++; // (the background)
const u32 lroom[3] = { (u32)rc.global_param_op.capacity(), (u32)rc.global_param_pt.capacity(), (u32)rc.global_param_tr.capacity() };
if ((u64)f.verts * 4 > (u64)rc.idx.capacity()
|| f.mvos[0] > (u32)rc.global_param_mvo.capacity() || f.mvos[1] > (u32)rc.global_param_mvo_tr.capacity())
return false;
if ((u64)lv[0] + 1 > lroom[0] || (u64)lv[1] + 1 > lroom[1] || (u64)lv[2] + 1 > lroom[2])
{
u32 ls[3] = { 1, 0, 0 };
const u8 *root = ctx->tad.thd_root;
for (i = 0; i < f.runs; i++)
{
const TaWalkRun &run = f.run[i];
if (run.header & TA_WALK_MODVOL)
continue;
u32 j = i + 1, end = f.verts;
while (j < f.runs && (f.run[j].header & TA_WALK_MODVOL))
j++;
if (j < f.runs)
end = f.run[j].vertex;
const TaWalkHeader &h = f.header[run.header];
const u32 n = end - run.vertex;
l = (u32)TaWalker<false>::pp_list(h.list);
if (h.format == TA_WALK_SPRITE)
ls[l] += n / 4;
else
for (u32 k = 0; k < n; k++)
ls[l] += ((const PCW *)(root + (size_t)(run.block + k * h.vblocks) * 32))->EndOfStrip;
}
if ((u64)ls[0] + 1 > lroom[0] || (u64)ls[1] + 1 > lroom[1] || (u64)ls[2] + 1 > lroom[2])
return false;
}
/* The sorted passes' translucent triangles, in the order the
* per-triangle renderer draws them */
out->why = TA_GPU_WHY_SORT;
gsort.first_provoking = (flags & TA_GPU_PROVOKE) != 0;
if ((flags & TA_GPU_SORT) && !ta_gpu_sort_frame(f, ctx->tad.thd_root))
return false;
if (flat_strips_wanted)
ta_gpu_flat_headers(f, ctx->tad.thd_root);
/* Drawn from here on: the lists hold a polygon parameter for each
* draw group (see TA_GPU_DRAW) */
rc.global_param_op.Clear();
rc.global_param_pt.Clear();
rc.global_param_tr.Clear();
}
out->why = TA_GPU_WHY_NONE;
// where each segment's headers and vertices start, in index order
u32 seg_next_h[TAG_MAX_SEG], seg_next_v[TAG_MAX_SEG];
{
u32 h = 0, v = 0;
for (i = 0; i < nseg; i++)
{
u32 *g = &table[off_seg + i * TAG_G_WORDS];
seg_next_h[i] = h;
seg_next_v[i] = v;
g[TAG_G_VSTART] = v;
g[TAG_G_VEND] = v + seg_v[i];
g[2] = g[3] = 0;
h += seg_hdr[i];
v += seg_v[i];
seg_last[i] = TAG_NONE;
seg_slot[i] = TAG_NONE;
}
}
/* A header's record. Its slot: a command for each draw group of the
* opaque and punch-through lists. The translucent strips are each their
* own polygon parameter, with a record of their own; for drawing, the
* translucent headers one after another in a segment that are drawn
* the same way (poly_same_state()) are a draw group too, whose records
* the GPU copies to a place of the group's in the translucent draw
* records: a word for how many there are, then room for one a vertex,
* which is the most strips the group can have (TAG_H_TRB, TAG_H_TRV).
* With TA_GPU_DRAW each draw group is a polygon parameter of the lists
* here, in the lists' order, which is the parse's. */
u32 nslots = 0, ntrd = 0;
const PolyParam *seg_tr[TAG_MAX_SEG];
u32 seg_trb[TAG_MAX_SEG], seg_trv[TAG_MAX_SEG], seg_groups[TAG_MAX_SEG];
for (i = 0; i < nseg; i++)
{
seg_tr[i] = NULL;
seg_trb[i] = seg_trv[i] = TAG_NONE;
seg_groups[i] = 0;
}
List<PolyParam> *lists[3] = { &rc.global_param_op, &rc.global_param_pt, &rc.global_param_tr };
auto put_header = [&](u32 id, const PolyParam &pp, u32 first, u32 cnt, u32 vstart, u32 fmt, u32 vb,
u32 list, u32 seg, u32 group, const TaWalkFace *face) {
u32 *w = &table[off_hdr + id * TAG_H_WORDS];
u32 slot = TAG_NONE;
bool fresh;
if (list != 2)
{
fresh = seg_last[seg] != group;
if (fresh)
{
seg_last[seg] = group;
seg_slot[seg] = nslots++;
}
slot = seg_slot[seg];
}
else
{
fresh = seg_tr[seg] == NULL || !poly_same_state(seg_tr[seg], &pp);
if (fresh)
{
seg_tr[seg] = &pp;
seg_trb[seg] = ntrd++;
seg_trv[seg] = vstart;
}
ntrd += cnt;
}
if (draw)
{
// first: the command's slot, or the group's place in the translucent draw records; count: its vertices
if (fresh)
{
PolyParam *d = lists[list]->Append();
*d = pp;
d->first = list != 2 ? slot : seg_trb[seg];
d->count = cnt;
seg_groups[seg]++;
}
else
lists[list]->LastPtr()->count += cnt;
// (a group is drawn at a single rate where all of its strips are flat: see ta_gpu_flat_headers())
if (!fresh && flat_strips_wanted)
lists[list]->LastPtr()->pcw.full &= pp.pcw.full | ~PVR_PCW_SINGLE_RATE;
}
w[TAG_H_VFIRST] = first;
w[TAG_H_VCOUNT] = cnt;
w[TAG_H_VSTART] = vstart;
w[TAG_H_INFO] = TAG_INFO(fmt, vb, list, seg, pp.pcw.Texture);
w[TAG_H_SLOT] = slot;
w[TAG_H_STATE + TAG_S_TSP] = pp.tsp.full;
w[TAG_H_STATE + TAG_S_TCW] = pp.tcw.full;
w[TAG_H_STATE + TAG_S_PCW] = pp.pcw.full & ~PVR_PCW_SINGLE_RATE;
w[TAG_H_STATE + TAG_S_ISP] = pp.isp.full;
w[TAG_H_STATE + TAG_S_TSP1] = pp.tsp1.full;
w[TAG_H_STATE + TAG_S_TCW1] = pp.tcw1.full;
w[TAG_H_STATE + TAG_S_CLIP] = pp.tileclip;
if (face != NULL)
{
memcpy(&w[TAG_H_FCOL], face->col, sizeof(face->col));
memcpy(&w[TAG_H_FF], face->f, sizeof(face->f));
memcpy(&w[TAG_H_SCOL], face->sprite, sizeof(face->sprite));
}
else
memset(&w[TAG_H_FCOL], 0, (TAG_H_TRB - TAG_H_FCOL) * sizeof(u32));
w[TAG_H_TRB] = list == 2 ? seg_trb[seg] : TAG_NONE;
w[TAG_H_TRV] = list == 2 ? seg_trv[seg] : TAG_NONE;
return id;
};
// the background, first of the first pass's opaque list (its group is 0)
put_header(seg_next_h[0]++, f.bg, 0, 4, 0, TAG_FMT_BG, 1, 0, 0, 0, NULL);
seg_next_v[0] += 4;
/* the others, as they came (which is their order within a segment);
* the runs are given the headers' new numbers through the old ones'
* group words, which are not needed after this */
for (i = 0; i < f.headers; i++)
{
TaWalkHeader &h = f.header[i];
if (h.pp.count == 0)
continue;
l = (u32)TaWalker<false>::pp_list(h.list);
const u32 seg = h.pass * 3 + l;
const u32 id = put_header(seg_next_h[seg]++, h.pp, h.pp.first, h.pp.count, seg_next_v[seg],
h.format, h.vblocks, l, seg, h.group, &f.face[i]);
seg_next_v[seg] += h.pp.count;
h.group = id;
}
{
u32 r = 0, m = 0;
for (i = 0; i < f.runs; i++)
{
const TaWalkRun &run = f.run[i];
if (run.header & TA_WALK_MODVOL)
{
u32 *w = &table[off_mvrun + m++ * TAG_M_WORDS];
w[TAG_M_BLOCK] = run.block;
w[TAG_M_FIRST] = run.vertex;
w[TAG_M_COUNT] = (run.blocks + 1) / 2;
w[3] = 0;
}
else
{
u32 *w = &table[off_run + r++ * TAG_R_WORDS];
w[TAG_R_BLOCK] = run.block;
w[TAG_R_VERTEX] = run.vertex;
w[TAG_R_COUNT] = 0;
w[TAG_R_HDR] = f.header[run.header].group;
}
}
}
memcpy(&table[off_bg], ctx->rend.verts.head(), 4 * sizeof(Vertex));
u32 *t = table;
t[TAG_T_NVERTS] = f.verts;
t[TAG_T_NRUNS] = nruns;
t[TAG_T_NHDR] = nhdr;
t[TAG_T_NMVRUN] = nmvruns;
t[TAG_T_NMOD] = f.modtrig;
t[TAG_T_NSEG] = nseg;
t[TAG_T_NSLOT] = nslots;
t[TAG_T_FLAGS] = (screen_height > 480 ? TAG_FLAG_BLEED : 0) | ((flags & TA_GPU_PROVOKE) ? TAG_FLAG_PROVOKE : 0)
| ((flags & TA_GPU_INSTANCE) ? TAG_FLAG_INSTANCE : 0);
t[TAG_T_ROOM_IDX] = (u32)ctx->rend.idx.capacity();
t[TAG_T_ROOM_OP] = (u32)ctx->rend.global_param_op.capacity();
t[TAG_T_ROOM_PT] = (u32)ctx->rend.global_param_pt.capacity();
t[TAG_T_ROOM_TR] = (u32)ctx->rend.global_param_tr.capacity();
t[TAG_T_OFF_RUN] = off_run;
t[TAG_T_OFF_HDR] = off_hdr;
t[TAG_T_OFF_MVRUN] = off_mvrun;
t[TAG_T_OFF_SEG] = off_seg;
t[TAG_T_OFF_BG] = off_bg;
memcpy(&t[TAG_T_TA_BASE], layout->words, sizeof(layout->words));
t[TAG_T_NPASS] = f.passes;
t[TAG_T_ROOM_VTX] = sz.verts;
t[TAG_T_OFF_SHD] = off_shd;
t[TAG_T_NSHD] = 0;
t[TAG_T_SHD_CMD] = sz.headers;
t[TAG_T_PREP] = (flags & TA_GPU_SORT) && gsort.checks != 0 ? ta_gpu_sorted_indices() : TAG_NONE;
out->table_words = words;
out->verts = f.verts;
out->modtrig = f.modtrig;
out->headers = nhdr;
out->slots = nslots;
out->segments = nseg;
out->passes = f.passes;
memcpy(out->pass, f.pass, sizeof(out->pass));
out->x_clip = f.x_clip;
out->y_clip = f.y_clip;
out->rv = f.rv;
out->trd = ntrd;
if (draw)
{
/* The rest of what the full parse leaves for the renderer: the
* render passes, with each list's draw groups as its count, the
* modifier volumes, the clipping rectangle. The vertices, indices
* and modifier volume triangles are the GPU's; fZ_max too, which
* the Vulkan renderers do not use. */
rc.render_passes.Clear();
u32 groups[3] = { 0, 0, 0 };
for (u32 p = 0; p < f.passes; p++)
{
RenderPass *rp = rc.render_passes.Append();
*rp = f.pass[p];
for (l = 0; l < 3; l++)
groups[l] += seg_groups[p * 3 + l];
rp->op_count = groups[0];
rp->pt_count = groups[1];
rp->tr_count = groups[2];
}
rc.global_param_mvo.Clear();
rc.global_param_mvo_tr.Clear();
if (f.mvos[0] != 0)
memcpy(rc.global_param_mvo.Append(f.mvos[0]), f.mvo[0], f.mvos[0] * sizeof(ModifierVolumeParam));
if (f.mvos[1] != 0)
memcpy(rc.global_param_mvo_tr.Append(f.mvos[1]), f.mvo[1], f.mvos[1] * sizeof(ModifierVolumeParam));
rc.fb_X_CLIP = f.x_clip;
rc.fb_Y_CLIP = f.y_clip;
rc.gpu = true;
rc.gpu_modtrig = f.modtrig;
if (modvol_clip_wanted)
ta_gpu_modvol_clip(f, ctx->tad.thd_root, rc);
ta_parse_cnt++;
/* The draw groups the per-triangle renderer draws again for
* shadows (Drawer::DrawShadowed()), each with its render pass's
* volumes' bounds, for the GPU to say which reach into them */
if (shadows)
{
u32 *sh = &table[off_shd];
u32 nsh = 0, prev[2] = { 0, 0 };
ta_gpu_shadow_areas(f, ctx->tad.thd_root, rc.gpu_shadow_area);
for (u32 p = 0; p < f.passes; p++)
{
memcpy(&sh[TAG_SH_AREA + p * 4], rc.gpu_shadow_area[p], 4 * sizeof(u32));
const ScreenBounds area = { rc.gpu_shadow_area[p][0], rc.gpu_shadow_area[p][1], rc.gpu_shadow_area[p][2], rc.gpu_shadow_area[p][3] };
const RenderPass &rp = rc.render_passes.head()[p];
const u32 end[2] = { rp.op_count, rp.pt_count };
for (l = 0; l < 2; l++)
{
for (i = prev[l]; i < end[l] && !area.empty(); i++)
{
const PolyParam &pp = lists[l]->head()[i];
if (CanDrawShadowed(l == 0 ? ListType_Opaque : ListType_Punch_Through, &pp))
sh[TAG_SH_ENTRY + nsh++] = TAG_SH(pp.first, p);
}
prev[l] = end[l];
}
}
t[TAG_T_NSHD] = nsh;
out->shadows = nsh;
}
}
return true;
}
void ta_set_gpu_processing(bool on)
{
if (on == ta_gpu_on && (!on || walk_data_size >= ta_data_size()))
return;
if (on && !ta_walk_init())
on = false;
ta_gpu_on = on;
if (!on)
ta_gpu_sort_reserve(false);
if (!on && !ta_walk_checking)
ta_walk_term();
ta_set_walk(on || ta_walk_checking);
}
/* ---- The check: the header pass beside the full parse, every frame ---- */
typedef std::chrono::steady_clock walk_clock;
static u64 walk_ns(walk_clock::time_point a, walk_clock::time_point b)
{
return (u64)std::chrono::duration_cast<std::chrono::nanoseconds>(b - a).count();
}
// Times, with a histogram of 256 ns steps (up to 16.7 ms) for the 99th percentile
#define WALK_HIST 65536
struct TaWalkTimes
{
u64 sum;
u64 max;
u32 n;
u32 hist[WALK_HIST];
void add(u64 ns)
{
u64 b = ns / 256;
sum += ns;
if (ns > max)
max = ns;
hist[b < WALK_HIST ? b : WALK_HIST - 1]++;
n++;
}
double p99_us() const
{
u32 over = 0, b = WALK_HIST;
while (b > 0 && over * 100 < n)
over += hist[--b];
return b * 0.256;
}
};
static struct TaWalkStats
{
u32 checked, same, differ, no_table, full_overrun, no_room, skipped;
u64 headers, entries, verts;
TaWalkTimes full, pass;
} *walk_stats;
// (ta_walk_full_strips(): where make_index() of a render pass would be)
static void ta_walk_snap_full(u32 op_from, u32 pt_from, u32 tr_from)
{
const List<PolyParam> *lists[3] = { &vd_rc.global_param_op, &vd_rc.global_param_pt, &vd_rc.global_param_tr };
const u32 from[3] = { op_from, pt_from, tr_from };
for (int l = 0; l < 3; l++)
{
const u32 used = (u32)lists[l]->used();
for (u32 i = from[l]; i < used && walk_full.count[l] < walk_full.room[l]; i++)
walk_full.pp[l][walk_full.count[l]++] = lists[l]->head()[i];
}
}
/* Whether two polygon parameters are the same, but for the depth the
* renderer works out (zvZ), and the single-rate bit of strips where flat
* strips are looked for (the full parse sets it from the vertices) */
static bool walk_pp_same(const PolyParam &a, const PolyParam &b)
{
const u32 pcw_mask = flat_strips_wanted ? ~PVR_PCW_SINGLE_RATE : ~0u;
return a.first == b.first && a.count == b.count && a.texid == b.texid
&& a.tsp.full == b.tsp.full && a.tcw.full == b.tcw.full
&& ((a.pcw.full ^ b.pcw.full) & pcw_mask) == 0 && a.isp.full == b.isp.full
&& a.tileclip == b.tileclip && a.tsp1.full == b.tsp1.full
&& a.tcw1.full == b.tcw1.full && a.texid1 == b.texid1;
}
/* What differs between the full parse (vd_rc as it left it, walk_full)
* and the check's frame @s and the header pass's @h: NULL for nothing */
static const char *ta_walk_compare(const TaWalkFrame &s, const TaWalkFrame &h, bool rv, char *why, size_t room)
{
static const char *const names[3] = { "opaque", "punch-through", "translucent" };
u32 l, i;
if (rv != s.rv)
return "whether there is anything to draw";
if ((u32)vd_rc.verts.used() != s.verts)
{
snprintf(why, room, "vertices: %d, %u", vd_rc.verts.used(), s.verts);
return why;
}
if ((u32)vd_rc.modtrig.used() != s.modtrig)
{
snprintf(why, room, "modifier volume triangles: %d, %u", vd_rc.modtrig.used(), s.modtrig);
return why;
}
if ((u32)vd_rc.render_passes.used() != s.passes)
{
snprintf(why, room, "render passes: %d, %u", vd_rc.render_passes.used(), s.passes);
return why;
}
for (i = 0; i < s.passes; i++)
{
const RenderPass &a = vd_rc.render_passes.head()[i];
const RenderPass &b = s.pass[i];
if (a.autosort != b.autosort || a.z_clear != b.z_clear || a.mv_op_tr_shared != b.mv_op_tr_shared
|| a.op_count != b.op_count || a.mvo_count != b.mvo_count || a.pt_count != b.pt_count
|| a.tr_count != b.tr_count || a.mvo_tr_count != b.mvo_tr_count)
{
snprintf(why, room, "render pass %u: op %u/%u pt %u/%u tr %u/%u mvo %u/%u mvo_tr %u/%u",
i, a.op_count, b.op_count, a.pt_count, b.pt_count, a.tr_count, b.tr_count,
a.mvo_count, b.mvo_count, a.mvo_tr_count, b.mvo_tr_count);
return why;
}
}
for (l = 0; l < 3; l++)
{
if (walk_full.count[l] != s.snap.count[l])
{
snprintf(why, room, "%s polygons: %u, %u", names[l], walk_full.count[l], s.snap.count[l]);
return why;
}
for (i = 0; i < s.snap.count[l]; i++)
if (!walk_pp_same(walk_full.pp[l][i], s.snap.pp[l][i]))
{
const PolyParam &a = walk_full.pp[l][i], &b = s.snap.pp[l][i];
snprintf(why, room, "%s polygon %u (header %d): first %u/%u count %u/%u pcw %08x/%08x isp %08x/%08x tsp %08x/%08x tcw %08x/%08x clip %08x/%08x",
names[l], i, (int)s.snap.header[l][i], a.first, b.first, a.count, b.count,
a.pcw.full, b.pcw.full, a.isp.full, b.isp.full, a.tsp.full, b.tsp.full,
a.tcw.full, b.tcw.full, a.tileclip, b.tileclip);
return why;
}
}
/* Each strip is drawn as its header says (the header pass's own
* polygon parameters, which have no strips) */
for (l = 0; l < 3; l++)
for (i = 0; i < s.snap.count[l]; i++)
{
const u32 hi = s.snap.header[l][i];
PolyParam a = s.snap.pp[l][i];
PolyParam b = hi == TA_WALK_BG ? s.bg : s.header[hi].pp;
a.first = b.first = 0;
a.count = b.count = 0;
if (!walk_pp_same(a, b))
{
snprintf(why, room, "%s polygon %u and its header %d", names[l], i, (int)hi);
return why;
}
}
const List<ModifierVolumeParam> *mv[2] = { &vd_rc.global_param_mvo, &vd_rc.global_param_mvo_tr };
for (l = 0; l < 2; l++)
{
if ((u32)mv[l]->used() != s.mvos[l])
{
snprintf(why, room, "modifier volumes (%u): %d, %u", l, mv[l]->used(), s.mvos[l]);
return why;
}
for (i = 0; i < s.mvos[l]; i++)
{
const ModifierVolumeParam &a = mv[l]->head()[i], &b = s.mvo[l][i];
if (a.first != b.first || (b.count != TA_WALK_UNENDED && a.count != b.count)
|| a.isp.full != b.isp.full || a.tileclip != b.tileclip)
{
snprintf(why, room, "modifier volume %u (%u)", i, l);
return why;
}
}
}
if (rv && (vd_rc.fb_X_CLIP.full != s.x_clip.full || vd_rc.fb_Y_CLIP.full != s.y_clip.full))
return "clipping rectangle";
/* The draw groups: each strip with the one before in the same pass
* and list is joined by make_index()'s rule exactly when their headers
* are of a group (the translucent ones are never joined) */
for (l = 0; l < 2; l++)
{
u32 at = 0;
for (u32 p = 0; p < s.passes; p++)
{
const int to = (int)(l == 0 ? s.pass[p].op_count : s.pass[p].pt_count);
const int from = p == 0 ? 0 : (int)(l == 0 ? s.pass[p - 1].op_count : s.pass[p - 1].pt_count);
const u32 n = to > from ? (u32)(to - from) : 0;
for (i = 1; i < n && at + i < s.snap.count[l]; i++)
{
const u32 ha = s.snap.header[l][at + i - 1], hb = s.snap.header[l][at + i];
const u32 ga = ha == TA_WALK_BG ? 0 : s.header[ha].group;
const u32 gb = hb == TA_WALK_BG ? 0 : s.header[hb].group;
const bool joined = poly_same_state(&s.snap.pp[l][at + i - 1], &s.snap.pp[l][at + i]);
if (s.snap.pp[l][at + i - 1].count == 0 || s.snap.pp[l][at + i].count == 0)
continue; // (no vertices: in no group)
if (joined != (ga == gb))
{
snprintf(why, room, "draw groups, %s polygon %u", names[l], at + i);
return why;
}
}
at += n;
}
}
/* And the header pass by itself, which never saw a vertex, made the
* same headers, runs, volumes and passes as the check's */
if (h.headers != s.headers || memcmp(h.header, s.header, h.headers * sizeof(TaWalkHeader)) != 0)
return "the header pass's headers";
if (h.runs != s.runs || memcmp(h.run, s.run, h.runs * sizeof(TaWalkRun)) != 0)
return "the header pass's vertex runs";
if (h.passes != s.passes || h.verts != s.verts || h.modtrig != s.modtrig || h.rv != s.rv
|| h.mvos[0] != s.mvos[0] || h.mvos[1] != s.mvos[1])
return "the header pass's totals";
for (i = 0; i < h.passes; i++)
if (h.pass[i].autosort != s.pass[i].autosort || h.pass[i].z_clear != s.pass[i].z_clear
|| h.pass[i].mv_op_tr_shared != s.pass[i].mv_op_tr_shared
|| h.pass[i].mvo_count != s.pass[i].mvo_count || h.pass[i].mvo_tr_count != s.pass[i].mvo_tr_count)
return "the header pass's render passes";
if (h.rv && (h.x_clip.full != s.x_clip.full || h.y_clip.full != s.y_clip.full))
return "the header pass's clipping rectangle";
return NULL;
}
static void ta_walk_log(void)
{
const TaWalkStats &st = *walk_stats;
const double nf = st.full.n ? (double)st.full.n : 1.0;
const double np = st.pass.n ? (double)st.pass.n : 1.0;
NOTICE_LOG(PVR, "TA header pass check: %u frames compared, %u the same, %u different; not compared: %u without a table, %u overrun, %u out of room, %u skipped\n",
st.checked, st.same, st.differ, st.no_table, st.full_overrun, st.no_room, st.skipped);
NOTICE_LOG(PVR, "TA header pass check: full parse mean %.1f us, p99 %.1f us, max %.1f us; header pass mean %.1f us, p99 %.1f us, max %.1f us; per frame %.0f headers, %.0f entries, %.0f vertices\n",
st.full.sum / nf / 1000.0, st.full.p99_us(), st.full.max / 1000.0,
st.pass.sum / np / 1000.0, st.pass.p99_us(), st.pass.max / 1000.0,
st.headers / np, st.entries / np, st.verts / np);
}
// (a frame has been through the check, compared or not)
static void ta_walk_tick(void)
{
const TaWalkStats &st = *walk_stats;
const u32 frames = st.checked + st.no_table + st.full_overrun + st.no_room + st.skipped;
if (frames == 30 || frames % 300 == 0)
ta_walk_log();
}
static bool ta_walk_check_frame(TA_context *ctx)
{
TaWalkStats &st = *walk_stats;
TaWalkFrame &h = walk_frame[0], &s = walk_frame[1];
u32 count;
/* Frames the full parse passes over (frame skipping) and frames with no
* table to trust are parsed as ever */
if (!(ctx->rend.isRTT || 0 == ((ta_parse_cnt + 1) % (settings.pvr.ta_skip + 1))))
{
st.skipped++;
ta_walk_tick();
return ta_parse_vdrc_full(ctx);
}
if (!ta_walk_table(ctx, &count))
{
st.no_table++;
ta_walk_tick();
return ta_parse_vdrc_full(ctx);
}
// (a render of some of its render passes: ta_gpu_prepare() leaves it to the parse too)
if (!ctx->AllPasses())
{
st.skipped++;
ta_walk_tick();
return ta_parse_vdrc_full(ctx);
}
h.bg_in = s.bg_in = *ctx->rend.global_param_op.head();
h.x_clip_in = s.x_clip_in = ctx->rend.fb_X_CLIP;
h.y_clip_in = s.y_clip_in = ctx->rend.fb_Y_CLIP;
/* The lists strip by strip first: what textures there are to convert
* are then converted, and neither of the two timed finds any */
walk_full.count[0] = walk_full.count[1] = walk_full.count[2] = 0;
ta_walk_full_strips(ctx);
walk_clock::time_point t0 = walk_clock::now();
const bool hp_ok = ta_walk_run<false>(ctx, h, count);
walk_clock::time_point t1 = walk_clock::now();
const bool rv = ta_parse_vdrc_full(ctx);
walk_clock::time_point t2 = walk_clock::now();
if (ctx->rend.Overrun)
{
st.full_overrun++;
ta_walk_tick();
return rv;
}
if (!hp_ok || !ta_walk_run<true>(ctx, s, count))
{
st.no_room++;
ta_walk_tick();
return rv;
}
st.full.add(walk_ns(t1, t2));
st.pass.add(walk_ns(t0, t1));
st.headers += h.headers;
st.entries += count;
st.verts += h.verts;
st.checked++;
char why[256];
const char *differ = ta_walk_compare(s, h, rv, why, sizeof(why));
if (differ == NULL)
st.same++;
else if (st.differ++ < 20)
WARN_LOG(PVR, "TA header pass check: frame %u differs from the full parse: %s\n", st.checked, differ);
ta_walk_tick();
return rv;
}
void ta_set_walk_check(bool on)
{
if (on == ta_walk_checking && (!on || walk_data_size >= ta_data_size()))
return;
if (on)
{
if (!ta_walk_init())
return;
if (walk_stats == NULL)
walk_stats = (TaWalkStats *)calloc(1, sizeof(TaWalkStats));
if (walk_stats == NULL)
return;
}
else
{
if (walk_stats != NULL && walk_stats->checked != 0)
ta_walk_log();
// (the GPU decode's header pass works in the same memory)
if (!ta_gpu_on)
ta_walk_term();
free(walk_stats);
walk_stats = NULL;
}
ta_walk_checking = on;
ta_set_walk(on || ta_gpu_on);
}