ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
525 lines
30 KiB
C
525 lines
30 KiB
C
// retroDE_ps2 — ps2_feeder (Ch339): native HPS userspace command producer for the GS feeder.
|
|
//
|
|
// Encodes structured drawing commands (triangle / native rectangle / flat or per-vertex color / Z)
|
|
// into the PROVEN staging format and streams them to the FPGA over the existing HPS bridge via
|
|
// /dev/mem + mmap — the SAME register protocol the docs/hardware/ps2_feeder_*.sh anchors use. The
|
|
// RTL and bridge protocol are UNCHANGED; this is purely a host-side encoder + streamer.
|
|
//
|
|
// The staging word layout and every GS-register packing here are a byte-exact port of the golden
|
|
// encoder in sim/data/top_psmct32_raster_demo/bake.py. `--dump <scene>` emits the 256 staging words
|
|
// so the encoder can be diffed against the golden feeder_*.mem fixtures (byte-equivalence gate).
|
|
//
|
|
// Build: gcc -O2 -Wall -o ps2_feeder ps2_feeder.c
|
|
// Usage:
|
|
// ps2_feeder --list list built-in named scenes
|
|
// ps2_feeder --dump <scene> print the 256 staging words (no board access)
|
|
// ps2_feeder --dump-file <file> parse a scene file, print the first scene's staging words
|
|
// ps2_feeder [opts] <scene>... stream each named scene to the board (submit/go/wait each)
|
|
// ps2_feeder [opts] -f <file> stream each scene in a text scene file
|
|
// Options:
|
|
// --base 0xADDR bridge base (default 0x40000000) --dry-run encode+validate, no board access
|
|
//
|
|
// Scene file grammar (one scene per `go`/EOF; '#' comments; whitespace-separated):
|
|
// tri x0 y0 x1 y1 x2 y2 z r g b flat triangle
|
|
// trig x0 y0 r0 g0 b0 x1 y1 r1 g1 b1 x2 y2 r2 g2 b2 z gouraud (per-vertex) triangle
|
|
// tritile T z r g b flat triangle filling grid tile T (0..15)
|
|
// rect T z r g b native rectangle in grid tile T
|
|
// go submit the accumulated scene; start next
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/mman.h>
|
|
|
|
// ---- profile constants (must match the GS_FEEDER_DEMO build) ----
|
|
#define FEEDER_STG_WORDS 256
|
|
#define FIFO_DEPTH 8
|
|
#define CAP_FBW 1
|
|
#define CAP_TBP1 96
|
|
#define TMP_TBW 1
|
|
static const int TMP_UV[3][2] = {{0,0},{3,0},{0,3}};
|
|
|
|
// ---- bridge register byte offsets from base (mirror docs/hardware/ps2_feeder_*.sh) ----
|
|
#define OFF_STATUS 0x0D8 // R: bit0 = feeder ready ; W: reset staging write address
|
|
#define OFF_LO 0x0DC // W: low 32 bits of the next staging word ; R: current staging address
|
|
#define OFF_HI 0x0E4 // W: high 32 bits (commits {hi,lo}, auto-increments addr) ; R: records emitted
|
|
#define OFF_GO 0x0E8 // W: bit0 = trigger feeder ; R: fifo wait cycles
|
|
|
|
// ======================== GS register / vertex packers (port of bake.py) ===================
|
|
static uint64_t frame_1_psmct32(uint32_t fbw){ return (uint64_t)(fbw & 0x3F) << 16; }
|
|
static uint64_t alpha_pack(uint32_t a,uint32_t b,uint32_t c,uint32_t d,uint32_t fix){
|
|
return (uint64_t)(a&3) | ((uint64_t)(b&3)<<2) | ((uint64_t)(c&3)<<4)
|
|
| ((uint64_t)(d&3)<<6) | ((uint64_t)(fix&0xFF)<<32);
|
|
}
|
|
static uint64_t test1_geq(void){ return (1ull<<16) | (2ull<<17); }
|
|
static uint64_t zbuf1_pack(uint32_t zbp,uint32_t zmsk,uint32_t psm){
|
|
return (uint64_t)(zbp&0x1FF) | ((uint64_t)(psm&0xF)<<24) | (uint64_t)(zmsk&1);
|
|
}
|
|
static uint64_t tex0_pack(uint32_t tbp0,uint32_t tbw,uint32_t psm,uint32_t tw,uint32_t th,uint32_t tfx){
|
|
return (uint64_t)(tbp0&0x3FFF) | ((uint64_t)(tbw&0x3F)<<14) | ((uint64_t)(psm&0x3F)<<20)
|
|
| ((uint64_t)(tw&0xF)<<26) | ((uint64_t)(th&0xF)<<30) | ((uint64_t)(tfx&3)<<35);
|
|
}
|
|
static uint64_t prim_tri_tme_abe(void){ return 3 | (1<<4) | (1<<6); } // TRI+TME+ABE (legacy scenes)
|
|
static uint64_t prim_tri_tme(void){ return 3 | (1<<4); } // Ch342 TRI+TME, ABE=0 (cube)
|
|
static uint64_t prim_sprite_tme_abe(void){ return 6 | (1<<4) | (1<<6); } // Ch345a SPRITE+TME+ABE (textured-alpha)
|
|
static uint64_t rgbaq_data(uint8_t r,uint8_t g,uint8_t b){
|
|
return ((uint64_t)0xFF<<24) | ((uint64_t)b<<16) | ((uint64_t)g<<8) | r; // a = 0xFF
|
|
}
|
|
static uint64_t uv_data(int ui,int vi){
|
|
return (uint64_t)(((ui<<4)&0x3FFF)) | ((uint64_t)(((vi<<4)&0x3FFF))<<14);
|
|
}
|
|
static uint64_t xyz2_dataz(int x,int y,uint32_t z){
|
|
return ((uint64_t)(x&0xFFF)<<4) | ((uint64_t)(y&0xFFF)<<20) | ((uint64_t)z<<32);
|
|
}
|
|
// Ch342 — perspective ST/Q packers matching the Ch301 gs_stub contract (24-bit fixed-point, FRAC=12).
|
|
// ST register: S_fp in [23:0], T_fp in [55:32]. RGBAQ register: Q_fp in [55:32] (NOT IEEE float).
|
|
static uint64_t st_data(uint32_t s_fp,uint32_t t_fp){
|
|
return (uint64_t)(s_fp & 0xFFFFFF) | ((uint64_t)(t_fp & 0xFFFFFF) << 32);
|
|
}
|
|
static uint64_t rgbaq_persp(uint8_t r,uint8_t g,uint8_t b,uint32_t q_fp){
|
|
return ((uint64_t)0xFF<<24) | ((uint64_t)b<<16) | ((uint64_t)g<<8) | r | ((uint64_t)(q_fp & 0xFFFFFF) << 32);
|
|
}
|
|
|
|
// ======================== scene model ===================
|
|
#define MAX_TRI 64
|
|
#define MAX_RECT 64
|
|
#define MAX_SPRITE 64
|
|
typedef struct { int x[3], y[3]; uint8_t r[3], g[3], b[3]; int u[3], v[3];
|
|
uint32_t s_fp[3], t_fp[3], q_fp[3]; uint32_t z; } tri_t; // Ch342 fixed-point ST/Q
|
|
typedef struct { int tile; uint8_t r, g, b; uint32_t z; } rect_t;
|
|
// Ch345a — textured + source-over alpha SPRITE record (the Ch344-proven subset): screen rect (x0,y0)-(x1,y1),
|
|
// per-corner affine UV, flat MODULATE tint. As (source alpha) comes from the TEXEL (TCC=1), not the tint.
|
|
typedef struct { int x0,y0,x1,y1; int u0,v0,u1,v1; uint8_t r,g,b; } sprite_t;
|
|
typedef struct {
|
|
tri_t tri[MAX_TRI]; int ntri; rect_t rect[MAX_RECT]; int nrect; const char *name;
|
|
sprite_t sprite[MAX_SPRITE]; int nsprite; // Ch345a
|
|
// Ch341 — optional scene-level texture binding. textured=0 keeps the proven unity+MODULATE header
|
|
// and TMP_UV (byte-exact with all prior scenes). textured=1 (set by the `tex0` command) overrides
|
|
// the scene TEX0 and lets `tritex` carry per-vertex real UV.
|
|
int textured; uint32_t tex_tbp, tex_tbw, tex_tw, tex_th, tex_tfx;
|
|
// Ch342 — perspective format: word0[32]=1, per-vertex RGBAQ/ST/XYZ2 (fixed-point S/T/Q). Requires
|
|
// `textured`; rects are rejected (the host fails closed so a mixed scene is never silently dropped).
|
|
int perspective;
|
|
// Ch345a — SPRITE format: word0[33]=1, each prim = SPRITE record (2 verts x RGBAQ/UV/XYZ2). Requires
|
|
// `textured`; mutually exclusive with tris/rects/perspective (fail closed).
|
|
int sprite_mode;
|
|
} scene_t;
|
|
|
|
static void scene_reset(scene_t *s, const char *name){ s->ntri=0; s->nrect=0; s->nsprite=0; s->name=name; s->textured=0; s->perspective=0; s->sprite_mode=0; }
|
|
static int scene_empty(const scene_t *s){ return s->ntri==0 && s->nrect==0 && s->nsprite==0; }
|
|
|
|
// tri_in_tile(t): the half-tile triangle bake.py draws for grid tile t (0..15, row*4+col).
|
|
static void tri_in_tile(int t, int vx[3], int vy[3]){
|
|
int ox=(t%4)*16, oy=(t/4)*16;
|
|
vx[0]=ox+1; vy[0]=oy+1;
|
|
vx[1]=ox+14; vy[1]=oy+1;
|
|
vx[2]=ox+1; vy[2]=oy+14;
|
|
}
|
|
|
|
// returns 0 ok, -1 on capacity/range error (message printed)
|
|
static int add_tri_gouraud(scene_t *s, const int vx[3], const int vy[3],
|
|
const uint8_t rr[3], const uint8_t gg[3], const uint8_t bb[3], uint32_t z){
|
|
if (s->ntri >= MAX_TRI){ fprintf(stderr,"error: too many triangles (max %d)\n", MAX_TRI); return -1; }
|
|
for (int i=0;i<3;i++){
|
|
if (vx[i]<0||vx[i]>4095||vy[i]<0||vy[i]>4095){
|
|
fprintf(stderr,"error: vertex (%d,%d) out of 12-bit range [0..4095]\n", vx[i], vy[i]); return -1; }
|
|
}
|
|
tri_t *t=&s->tri[s->ntri++];
|
|
for (int i=0;i<3;i++){ t->x[i]=vx[i]; t->y[i]=vy[i]; t->r[i]=rr[i]; t->g[i]=gg[i]; t->b[i]=bb[i];
|
|
t->u[i]=TMP_UV[i][0]; t->v[i]=TMP_UV[i][1]; } // default UV (byte-exact w/ prior scenes)
|
|
t->z=z;
|
|
return 0;
|
|
}
|
|
// Ch341 — textured triangle: per-vertex real UV (texel coords) + a flat MODULATE color. Requires the
|
|
// scene `tex0` binding (caller sets s->textured). UV range [0..4095] (10.4 fixed via uv_data).
|
|
static int add_tritex(scene_t *s, const int vx[3], const int vy[3], const int uu[3], const int vv[3],
|
|
uint8_t r, uint8_t g, uint8_t b, uint32_t z){
|
|
uint8_t rr[3]={r,r,r}, gg[3]={g,g,g}, bb[3]={b,b,b};
|
|
if (add_tri_gouraud(s, vx, vy, rr, gg, bb, z)) return -1;
|
|
tri_t *t=&s->tri[s->ntri-1];
|
|
for (int i=0;i<3;i++){
|
|
if (uu[i]<0||uu[i]>4095||vv[i]<0||vv[i]>4095){
|
|
fprintf(stderr,"error: UV (%d,%d) out of range [0..4095]\n", uu[i], vv[i]); s->ntri--; return -1; }
|
|
t->u[i]=uu[i]; t->v[i]=vv[i];
|
|
}
|
|
return 0;
|
|
}
|
|
static int add_tri_flat(scene_t *s, const int vx[3], const int vy[3], uint8_t r, uint8_t g, uint8_t b, uint32_t z){
|
|
uint8_t rr[3]={r,r,r}, gg[3]={g,g,g}, bb[3]={b,b,b};
|
|
return add_tri_gouraud(s, vx, vy, rr, gg, bb, z);
|
|
}
|
|
static int add_tritile(scene_t *s, int tile, uint32_t z, uint8_t r, uint8_t g, uint8_t b){
|
|
if (tile<0||tile>15){ fprintf(stderr,"error: tile %d out of range [0..15]\n", tile); return -1; }
|
|
int vx[3], vy[3]; tri_in_tile(tile, vx, vy);
|
|
return add_tri_flat(s, vx, vy, r, g, b, z);
|
|
}
|
|
// Ch342 — perspective textured triangle: per-vertex screen (x,y) + fixed-point (S_fp,T_fp,Q_fp) + flat
|
|
// MODULATE color. Requires the scene `tex0` + `persp`. 24-bit fixed-point (FRAC=12), so S/T/Q <= 0xFFFFFF.
|
|
static int add_persptri(scene_t *s, const int vx[3], const int vy[3],
|
|
const uint32_t sf[3], const uint32_t tf[3], const uint32_t qf[3],
|
|
uint8_t r, uint8_t g, uint8_t b, uint32_t z){
|
|
uint8_t rr[3]={r,r,r}, gg[3]={g,g,g}, bb[3]={b,b,b};
|
|
if (add_tri_gouraud(s, vx, vy, rr, gg, bb, z)) return -1;
|
|
tri_t *t=&s->tri[s->ntri-1];
|
|
for (int i=0;i<3;i++){
|
|
if (sf[i]>0xFFFFFF||tf[i]>0xFFFFFF||qf[i]>0xFFFFFF){
|
|
fprintf(stderr,"error: ST/Q fixed-point out of 24-bit range\n"); s->ntri--; return -1; }
|
|
t->s_fp[i]=sf[i]; t->t_fp[i]=tf[i]; t->q_fp[i]=qf[i];
|
|
}
|
|
return 0;
|
|
}
|
|
static int add_rect(scene_t *s, int tile, uint32_t z, uint8_t r, uint8_t g, uint8_t b){
|
|
if (s->perspective){ fprintf(stderr,"error: rects not allowed in perspective mode\n"); return -1; }
|
|
if (tile<0||tile>15){ fprintf(stderr,"error: rect tile %d out of range [0..15]\n", tile); return -1; }
|
|
if (s->nrect >= MAX_RECT){ fprintf(stderr,"error: too many rects (max %d)\n", MAX_RECT); return -1; }
|
|
rect_t *q=&s->rect[s->nrect++]; q->tile=tile; q->z=z; q->r=r; q->g=g; q->b=b;
|
|
return 0;
|
|
}
|
|
// Ch345a — textured + source-over alpha SPRITE: screen rect + per-corner affine UV + flat MODULATE tint.
|
|
// Requires the scene `tex0` binding (textured); sets sprite_mode. Fails closed if mixed with tris/rects/persp.
|
|
static int add_sprite(scene_t *s, int x0, int y0, int x1, int y1,
|
|
int u0, int v0, int u1, int v1, uint8_t r, uint8_t g, uint8_t b){
|
|
if (!s->textured){ fprintf(stderr,"error: sprite requires a prior tex0 binding\n"); return -1; }
|
|
if (s->perspective || s->ntri || s->nrect){
|
|
fprintf(stderr,"error: sprite mode is exclusive with persp/tris/rects\n"); return -1; }
|
|
if (s->nsprite >= MAX_SPRITE){ fprintf(stderr,"error: too many sprites (max %d)\n", MAX_SPRITE); return -1; }
|
|
if (x0<0||x0>4095||y0<0||y0>4095||x1<0||x1>4095||y1<0||y1>4095){
|
|
fprintf(stderr,"error: sprite vertex out of 12-bit range [0..4095]\n"); return -1; }
|
|
if (u0<0||u0>4095||v0<0||v0>4095||u1<0||u1>4095||v1<0||v1>4095){
|
|
fprintf(stderr,"error: sprite UV out of range [0..4095]\n"); return -1; }
|
|
s->sprite_mode = 1;
|
|
sprite_t *q=&s->sprite[s->nsprite++];
|
|
q->x0=x0; q->y0=y0; q->x1=x1; q->y1=y1; q->u0=u0; q->v0=v0; q->u1=u1; q->v1=v1; q->r=r; q->g=g; q->b=b;
|
|
return 0;
|
|
}
|
|
|
|
// ======================== staging builder (byte-exact with bake.py builders) ===================
|
|
// Emits: word0 = {rect_count[31:16], tri_count[15:0]} (== tri_count when rect_count==0, matching the
|
|
// colored/gouraud builders), words1..6 = FRAME/ALPHA/TEST/ZBUF/TEX0(unity+MODULATE)/PRIM, then each
|
|
// triangle as 3x(RGBAQ,UV,XYZ2), then each rect as rect_record (RGBAQ, corner0 XYZ2, corner1 XYZ2).
|
|
// out[] is zero-padded to FEEDER_STG_WORDS. Returns meaningful word count, or -1 if it won't fit.
|
|
static int build_staging(const scene_t *s, uint64_t out[FEEDER_STG_WORDS]){
|
|
int need = s->sprite_mode ? (7 + s->nsprite*6) : (7 + s->ntri*9 + s->nrect*3);
|
|
if (need > FEEDER_STG_WORDS){
|
|
fprintf(stderr,"error: scene '%s' needs %d staging words > %d (FEEDER_STG_WORDS) — too large\n",
|
|
s->name?s->name:"?", need, FEEDER_STG_WORDS);
|
|
return -1;
|
|
}
|
|
if (s->perspective && (!s->textured || s->nrect)){
|
|
fprintf(stderr,"error: perspective mode requires tex0 and no rects\n"); return -1;
|
|
}
|
|
// Ch345a — SPRITE format: word0[33]=1, SPRITE+TME+ABE source-over, per-corner UV, As from texel (TCC).
|
|
if (s->sprite_mode){
|
|
if (!s->textured){ fprintf(stderr,"error: sprite mode requires tex0\n"); return -1; }
|
|
for (int i=0;i<FEEDER_STG_WORDS;i++) out[i]=0;
|
|
int n=0;
|
|
out[n++] = (uint64_t)(s->nsprite & 0xFFFF) | ((uint64_t)1 << 33);
|
|
out[n++] = frame_1_psmct32(CAP_FBW);
|
|
out[n++] = alpha_pack(0,1,0,1,0); // source-over
|
|
out[n++] = 0; // TEST_1 (ZTE=0, no depth)
|
|
out[n++] = 0; // ZBUF_1
|
|
out[n++] = tex0_pack(s->tex_tbp, s->tex_tbw, 0, s->tex_tw, s->tex_th, s->tex_tfx); // PSMCT32
|
|
out[n++] = prim_sprite_tme_abe(); // SPRITE+TME+ABE
|
|
for (int i=0;i<s->nsprite;i++){
|
|
const sprite_t *q=&s->sprite[i];
|
|
// RGBAQ A=0x80 (neutral; the vertex alpha is IGNORED — As comes from the texel, TCC=1). Matches
|
|
// the bake.py golden so test_ps2_feeder.sh can byte-check the sprite staging.
|
|
uint64_t tint = ((uint64_t)0x80<<24) | ((uint64_t)q->b<<16) | ((uint64_t)q->g<<8) | q->r;
|
|
out[n++] = tint; out[n++] = uv_data(q->u0,q->v0); out[n++] = xyz2_dataz(q->x0,q->y0,0);
|
|
out[n++] = tint; out[n++] = uv_data(q->u1,q->v1); out[n++] = xyz2_dataz(q->x1,q->y1,0);
|
|
}
|
|
return n;
|
|
}
|
|
for (int i=0;i<FEEDER_STG_WORDS;i++) out[i]=0;
|
|
int n=0;
|
|
// Ch342 — word0[32] = perspective format flag (feeder emits ST instead of UV; rects forced off).
|
|
out[n++] = ((uint64_t)(s->nrect & 0xFFFF) << 16) | (uint64_t)(s->ntri & 0xFFFF)
|
|
| (s->perspective ? ((uint64_t)1 << 32) : 0);
|
|
out[n++] = frame_1_psmct32(CAP_FBW);
|
|
out[n++] = alpha_pack(0,1,0,1,0);
|
|
out[n++] = test1_geq();
|
|
out[n++] = zbuf1_pack(2,0,0);
|
|
out[n++] = s->textured ? tex0_pack(s->tex_tbp, s->tex_tbw, 0, s->tex_tw, s->tex_th, s->tex_tfx)
|
|
: tex0_pack(CAP_TBP1, TMP_TBW, 0, 2, 2, 0); // unity texture + MODULATE (default)
|
|
out[n++] = s->perspective ? prim_tri_tme() : prim_tri_tme_abe(); // Ch342: ABE=0 -> S1 perspective path
|
|
for (int i=0;i<s->ntri;i++){
|
|
const tri_t *t=&s->tri[i];
|
|
for (int v=0; v<3; v++){
|
|
if (s->perspective){ // RGBAQ(+Q_fp) / ST(S_fp,T_fp) / XYZ2
|
|
out[n++] = rgbaq_persp(t->r[v], t->g[v], t->b[v], t->q_fp[v]);
|
|
out[n++] = st_data(t->s_fp[v], t->t_fp[v]);
|
|
} else { // legacy: RGBAQ / UV / XYZ2 (byte-exact)
|
|
out[n++] = rgbaq_data(t->r[v], t->g[v], t->b[v]);
|
|
out[n++] = uv_data(t->u[v], t->v[v]);
|
|
}
|
|
out[n++] = xyz2_dataz(t->x[v], t->y[v], t->z);
|
|
}
|
|
}
|
|
for (int i=0;i<s->nrect;i++){
|
|
const rect_t *q=&s->rect[i];
|
|
int ox=(q->tile%4)*16, oy=(q->tile/4)*16;
|
|
out[n++] = rgbaq_data(q->r, q->g, q->b);
|
|
out[n++] = xyz2_dataz(ox+1, oy+1, q->z);
|
|
out[n++] = xyz2_dataz(ox+14, oy+14, q->z);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// ======================== built-in named scenes (reproduce the Ch333-338 goldens) ===================
|
|
#define RED 0xFF,0x00,0x00
|
|
#define GREEN 0x00,0xFF,0x00
|
|
#define BLUE 0x00,0x00,0xFF
|
|
#define YELLOW 0xFF,0xFF,0x00
|
|
#define WHITE 0xFF,0xFF,0xFF
|
|
|
|
static int build_named(const char *name, scene_t *s){
|
|
scene_reset(s, name);
|
|
if (!strcmp(name,"color-tri")){ // Ch333 feeder_color_tri.mem
|
|
return add_tritile(s,0,0x5000,RED) || add_tritile(s,5,0x5100,GREEN) || add_tritile(s,10,0x5200,BLUE);
|
|
}
|
|
if (!strcmp(name,"sprite")){ // Ch345a feeder_sprite.mem — 3 textured-alpha sprites
|
|
// tex0 8x8 PSMCT32 @ TBP=64 (TW=TH=3 log2), MODULATE; white (0x80) tint = identity. As from texel.
|
|
s->textured=1; s->tex_tbp=64; s->tex_tbw=1; s->tex_tw=3; s->tex_th=3; s->tex_tfx=0;
|
|
return add_sprite(s, 8,24,24,40, 0,0,8,8, 128,128,128)
|
|
|| add_sprite(s, 26,24,42,40, 0,0,8,8, 128,128,128)
|
|
|| add_sprite(s, 44,24,60,40, 0,0,8,8, 128,128,128);
|
|
}
|
|
if (!strcmp(name,"native-rect")){ // Ch334 feeder_native_rect.mem (3 rects, 0 tris)
|
|
return add_rect(s,0,0x5000,RED) || add_rect(s,5,0x5100,GREEN) || add_rect(s,10,0x5200,BLUE);
|
|
}
|
|
if (!strcmp(name,"gouraud-tri")){ // Ch335 feeder_gouraud_tri.mem (1 gouraud tri, tile 0)
|
|
int vx[3],vy[3]; tri_in_tile(0,vx,vy);
|
|
uint8_t rr[3]={0xFF,0x00,0x00}, gg[3]={0x00,0xFF,0x00}, bb[3]={0x00,0x00,0xFF};
|
|
return add_tri_gouraud(s,vx,vy,rr,gg,bb,0x5000);
|
|
}
|
|
if (!strcmp(name,"accum")){ // Ch336 feeder_accum.mem
|
|
for (int t=0;t<14;t++) if (t<8 ? add_tritile(s,t,0x5000+t*0x100,RED)
|
|
: add_tritile(s,t,0x5000+t*0x100,BLUE)) return -1;
|
|
return 0;
|
|
}
|
|
if (!strcmp(name,"retrigger-a")){ // Ch337 feeder_scene_a.mem
|
|
for (int t=0;t<14;t++) if (add_tritile(s,t,0x5000+t*0x100,RED)) return -1;
|
|
return 0;
|
|
}
|
|
if (!strcmp(name,"retrigger-b")){ // Ch337 feeder_scene_b.mem (tiles 2..15)
|
|
for (int t=2;t<16;t++) if (add_tritile(s,t,0x5000+(t-2)*0x100,BLUE)) return -1;
|
|
return 0;
|
|
}
|
|
if (!strcmp(name,"zpersist-near") || !strcmp(name,"zpersist-far")){ // Ch338 flat overlap
|
|
int near_first = !strcmp(name,"zpersist-near");
|
|
static const int b0f[7]={0,1,2,3,4,6,7}, b1f[5]={8,9,10,11,12};
|
|
// batch0 = overlap prim + 7 fillers ; batch1 = overlap prim + 5 fillers.
|
|
if (near_first){
|
|
if (add_tritile(s,5,0x7000,RED)) return -1; // near RED (batch0)
|
|
for (int i=0;i<7;i++) if (add_tritile(s,b0f[i],0x6000,RED)) return -1;
|
|
if (add_tritile(s,5,0x5000,BLUE)) return -1; // far BLUE (batch1)
|
|
for (int i=0;i<5;i++) if (add_tritile(s,b1f[i],0x6000,BLUE)) return -1;
|
|
} else {
|
|
if (add_tritile(s,5,0x5000,BLUE)) return -1; // far BLUE (batch0)
|
|
for (int i=0;i<7;i++) if (add_tritile(s,b0f[i],0x6000,BLUE)) return -1;
|
|
if (add_tritile(s,5,0x7000,RED)) return -1; // near RED (batch1)
|
|
for (int i=0;i<5;i++) if (add_tritile(s,b1f[i],0x6000,RED)) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
if (!strcmp(name,"zpersist-grad")){ // Ch338 feeder_zpersist_grad.mem (gouraud near + flat far)
|
|
static const int b0f[7]={0,1,2,3,4,6,7}, b1f[5]={8,9,10,11,12};
|
|
int vx[3],vy[3]; tri_in_tile(5,vx,vy);
|
|
uint8_t rr[3]={0xFF,0x00,0x00}, gg[3]={0x00,0xFF,0x00}, bb[3]={0x00,0x00,0xFF};
|
|
if (add_tri_gouraud(s,vx,vy,rr,gg,bb,0x7000)) return -1; // near gradient (batch0)
|
|
for (int i=0;i<7;i++) if (add_tritile(s,b0f[i],0x6000,GREEN)) return -1;
|
|
if (add_tritile(s,5,0x5000,WHITE)) return -1; // far flat white (batch1)
|
|
for (int i=0;i<5;i++) if (add_tritile(s,b1f[i],0x6000,WHITE)) return -1;
|
|
return 0;
|
|
}
|
|
fprintf(stderr,"error: unknown scene '%s' (try --list)\n", name);
|
|
return -1;
|
|
}
|
|
static const char *named_scenes[] = {
|
|
"color-tri","native-rect","gouraud-tri","accum","retrigger-a","retrigger-b",
|
|
"zpersist-near","zpersist-far","zpersist-grad", NULL
|
|
};
|
|
|
|
// ======================== scene-file parser ===================
|
|
// Parses tokens into one or more scenes (split on `go`). Returns count, or -1 on error.
|
|
// scenes[] must hold at least max_scenes entries.
|
|
static int parse_int(const char *tok, long *out){
|
|
char *end; errno=0; long v=strtol(tok,&end,0);
|
|
if (*end || errno) return -1;
|
|
*out=v; return 0;
|
|
}
|
|
static int parse_scene_file(const char *path, scene_t *scenes, int max_scenes){
|
|
FILE *f=fopen(path,"r");
|
|
if (!f){ fprintf(stderr,"error: cannot open '%s': %s\n", path, strerror(errno)); return -1; }
|
|
int nsc=0; scene_reset(&scenes[0], path);
|
|
char line[512]; int lineno=0;
|
|
while (fgets(line,sizeof line,f)){
|
|
lineno++;
|
|
char *h=strchr(line,'#'); if (h) *h=0; // strip comment
|
|
char *tok=strtok(line," \t\r\n"); if (!tok) continue;
|
|
long a[24]; int na=0; char op[32]; // persptri needs 19 args
|
|
snprintf(op,sizeof op,"%s",tok);
|
|
for (char *t=strtok(NULL," \t\r\n"); t && na<24; t=strtok(NULL," \t\r\n")){
|
|
if (parse_int(t,&a[na])){ fprintf(stderr,"error: %s:%d bad number '%s'\n",path,lineno,t); fclose(f); return -1; }
|
|
na++;
|
|
}
|
|
scene_t *s=&scenes[nsc];
|
|
int rc=0;
|
|
if (!strcmp(op,"go")){
|
|
if (!scene_empty(s)){ if (++nsc>=max_scenes){ fprintf(stderr,"error: too many scenes\n"); fclose(f); return -1; } scene_reset(&scenes[nsc], path); }
|
|
} else if (!strcmp(op,"tri") && na==10){
|
|
int vx[3]={(int)a[0],(int)a[2],(int)a[4]}, vy[3]={(int)a[1],(int)a[3],(int)a[5]};
|
|
rc=add_tri_flat(s,vx,vy,(uint8_t)a[7],(uint8_t)a[8],(uint8_t)a[9],(uint32_t)a[6]);
|
|
} else if (!strcmp(op,"trig") && na==16){
|
|
int vx[3]={(int)a[0],(int)a[5],(int)a[10]}, vy[3]={(int)a[1],(int)a[6],(int)a[11]};
|
|
uint8_t rr[3]={(uint8_t)a[2],(uint8_t)a[7],(uint8_t)a[12]};
|
|
uint8_t gg[3]={(uint8_t)a[3],(uint8_t)a[8],(uint8_t)a[13]};
|
|
uint8_t bb[3]={(uint8_t)a[4],(uint8_t)a[9],(uint8_t)a[14]};
|
|
rc=add_tri_gouraud(s,vx,vy,rr,gg,bb,(uint32_t)a[15]);
|
|
} else if (!strcmp(op,"tritile") && na==5){
|
|
rc=add_tritile(s,(int)a[0],(uint32_t)a[1],(uint8_t)a[2],(uint8_t)a[3],(uint8_t)a[4]);
|
|
} else if (!strcmp(op,"rect") && na==5){
|
|
rc=add_rect(s,(int)a[0],(uint32_t)a[1],(uint8_t)a[2],(uint8_t)a[3],(uint8_t)a[4]);
|
|
} else if (!strcmp(op,"tex0") && na==5){ // tex0 TBP TBW TW TH TFX — scene-level texture bind
|
|
s->textured=1; s->tex_tbp=(uint32_t)a[0]; s->tex_tbw=(uint32_t)a[1];
|
|
s->tex_tw=(uint32_t)a[2]; s->tex_th=(uint32_t)a[3]; s->tex_tfx=(uint32_t)a[4];
|
|
} else if (!strcmp(op,"tritex") && na==16){ // tritex x0 y0 u0 v0 x1 y1 u1 v1 x2 y2 u2 v2 z r g b
|
|
if (!s->textured){ fprintf(stderr,"error: %s:%d tritex needs a prior tex0\n",path,lineno); fclose(f); return -1; }
|
|
int vx[3]={(int)a[0],(int)a[4],(int)a[8]}, vy[3]={(int)a[1],(int)a[5],(int)a[9]};
|
|
int uu[3]={(int)a[2],(int)a[6],(int)a[10]}, vv[3]={(int)a[3],(int)a[7],(int)a[11]};
|
|
rc=add_tritex(s,vx,vy,uu,vv,(uint8_t)a[13],(uint8_t)a[14],(uint8_t)a[15],(uint32_t)a[12]);
|
|
} else if (!strcmp(op,"persp") && na==0){ // mark scene perspective (needs prior tex0)
|
|
if (!s->textured){ fprintf(stderr,"error: %s:%d persp needs a prior tex0\n",path,lineno); fclose(f); return -1; }
|
|
s->perspective=1;
|
|
} else if (!strcmp(op,"persptri") && na==19){ // persptri x y s t q (x3) z r g b — fixed-point ST/Q
|
|
if (!s->perspective){ fprintf(stderr,"error: %s:%d persptri needs a prior persp\n",path,lineno); fclose(f); return -1; }
|
|
int vx[3]={(int)a[0],(int)a[5],(int)a[10]}, vy[3]={(int)a[1],(int)a[6],(int)a[11]};
|
|
uint32_t sf[3]={(uint32_t)a[2],(uint32_t)a[7],(uint32_t)a[12]};
|
|
uint32_t tf[3]={(uint32_t)a[3],(uint32_t)a[8],(uint32_t)a[13]};
|
|
uint32_t qf[3]={(uint32_t)a[4],(uint32_t)a[9],(uint32_t)a[14]};
|
|
rc=add_persptri(s,vx,vy,sf,tf,qf,(uint8_t)a[16],(uint8_t)a[17],(uint8_t)a[18],(uint32_t)a[15]);
|
|
} else if (!strcmp(op,"sprite") && na==11){ // Ch345a — sprite x0 y0 x1 y1 u0 v0 u1 v1 r g b
|
|
// textured + source-over alpha SPRITE (needs a prior tex0). r/g/b = MODULATE tint; As from texel.
|
|
rc=add_sprite(s,(int)a[0],(int)a[1],(int)a[2],(int)a[3],(int)a[4],(int)a[5],(int)a[6],(int)a[7],
|
|
(uint8_t)a[8],(uint8_t)a[9],(uint8_t)a[10]);
|
|
} else {
|
|
fprintf(stderr,"error: %s:%d malformed command '%s' (got %d args)\n",path,lineno,op,na); fclose(f); return -1;
|
|
}
|
|
if (rc){ fprintf(stderr," (at %s:%d)\n",path,lineno); fclose(f); return -1; }
|
|
}
|
|
fclose(f);
|
|
if (!scene_empty(&scenes[nsc])) nsc++; // trailing scene with no closing 'go'
|
|
return nsc;
|
|
}
|
|
|
|
// ======================== bridge I/O ===================
|
|
typedef struct { volatile uint8_t *base; int dry; } bridge_t;
|
|
static void wr32(bridge_t *br, int off, uint32_t v){ if (!br->dry) *(volatile uint32_t*)(br->base+off)=v; }
|
|
static uint32_t rd32(bridge_t *br, int off){ return br->dry ? 0 : *(volatile uint32_t*)(br->base+off); }
|
|
|
|
static int wait_ready(bridge_t *br){
|
|
if (br->dry) return 0;
|
|
for (int i=0;i<300000;i++){ if (rd32(br,OFF_STATUS)&1) return 0; usleep(10); }
|
|
fprintf(stderr,"error: feeder never reported ready\n"); return -1;
|
|
}
|
|
|
|
// Stream one scene: reset addr, write {lo,hi} per word, GO, wait completion. Prints diagnostics.
|
|
static int stream_scene(bridge_t *br, const scene_t *s){
|
|
uint64_t w[FEEDER_STG_WORDS];
|
|
int n = build_staging(s, w);
|
|
if (n < 0) return -1;
|
|
int exp_prims = s->ntri + s->nrect*2; // feeder expands each rect -> 2 triangles
|
|
int exp_batches = (exp_prims + FIFO_DEPTH - 1) / FIFO_DEPTH;
|
|
printf("[scene %-14s] tris=%d rects=%d -> staged_words=%d expand_prims=%d batches~=%d\n",
|
|
s->name?s->name:"?", s->ntri, s->nrect, n, exp_prims, exp_batches);
|
|
if (wait_ready(br)) return -1;
|
|
wr32(br, OFF_STATUS, 0); // reset staging write address
|
|
for (int i=0;i<n;i++){ wr32(br, OFF_LO, (uint32_t)(w[i]&0xFFFFFFFF)); wr32(br, OFF_HI, (uint32_t)(w[i]>>32)); }
|
|
uint32_t addr = rd32(br, OFF_LO);
|
|
if (!br->dry && (int)addr != n){ fprintf(stderr,"error: bridge addr=%u after streaming %d words\n", addr, n); return -1; }
|
|
if (wait_ready(br)) return -1; // staging accepted
|
|
wr32(br, OFF_GO, 1); // trigger
|
|
if (wait_ready(br)) return -1; // Ch337: ready only after the WHOLE scene drained
|
|
uint32_t records = rd32(br, OFF_HI), waits = rd32(br, OFF_GO);
|
|
printf(" staged_addr=%u records=%u waits=%u completion=ready%s\n",
|
|
addr, records, waits, br->dry?" (dry-run, no hw readback)":"");
|
|
if (!br->dry && (int)records != exp_prims)
|
|
fprintf(stderr," warn: hw records=%u != host-expected %d (rect expansion / format?)\n", records, exp_prims);
|
|
return 0;
|
|
}
|
|
|
|
// ======================== main ===================
|
|
static void dump_words(const uint64_t w[FEEDER_STG_WORDS]){
|
|
for (int i=0;i<FEEDER_STG_WORDS;i++) printf("%016llx\n", (unsigned long long)w[i]);
|
|
}
|
|
static void usage(void){
|
|
fprintf(stderr,
|
|
"usage: ps2_feeder [--base 0xADDR] [--dry-run] <scene>...\n"
|
|
" ps2_feeder [--base 0xADDR] [--dry-run] -f <scenefile>\n"
|
|
" ps2_feeder --dump <scene> | --dump-file <scenefile> | --list\n");
|
|
}
|
|
|
|
int main(int argc, char **argv){
|
|
unsigned long base = 0x40000000UL;
|
|
int dry = 0;
|
|
const char *dump = NULL, *dumpfile = NULL, *scenefile = NULL;
|
|
const char *names[64]; int nnames=0;
|
|
|
|
for (int i=1;i<argc;i++){
|
|
if (!strcmp(argv[i],"--list")){ for (const char**p=named_scenes;*p;p++) printf("%s\n",*p); return 0; }
|
|
else if (!strcmp(argv[i],"--base") && i+1<argc) base=strtoul(argv[++i],NULL,0);
|
|
else if (!strcmp(argv[i],"--dry-run")) dry=1;
|
|
else if (!strcmp(argv[i],"--dump") && i+1<argc) dump=argv[++i];
|
|
else if (!strcmp(argv[i],"--dump-file") && i+1<argc) dumpfile=argv[++i];
|
|
else if (!strcmp(argv[i],"-f") && i+1<argc) scenefile=argv[++i];
|
|
else if (argv[i][0]=='-'){ usage(); return 2; }
|
|
else if (nnames<64) names[nnames++]=argv[i];
|
|
}
|
|
|
|
// ---- dump modes (no board access) ----
|
|
if (dump){
|
|
scene_t s; if (build_named(dump,&s)) return 1;
|
|
uint64_t w[FEEDER_STG_WORDS]; if (build_staging(&s,w)<0) return 1;
|
|
dump_words(w); return 0;
|
|
}
|
|
if (dumpfile){
|
|
scene_t scenes[16]; int nsc=parse_scene_file(dumpfile,scenes,16); if (nsc<0) return 1;
|
|
if (nsc==0){ fprintf(stderr,"error: no scenes in '%s'\n",dumpfile); return 1; }
|
|
uint64_t w[FEEDER_STG_WORDS]; if (build_staging(&scenes[0],w)<0) return 1;
|
|
dump_words(w); return 0;
|
|
}
|
|
|
|
// ---- build the work list (named scenes or a scene file) ----
|
|
scene_t scenes[64]; int nsc=0;
|
|
if (scenefile){ nsc=parse_scene_file(scenefile,scenes,64); if (nsc<0) return 1; }
|
|
for (int i=0;i<nnames;i++){ if (nsc>=64){ fprintf(stderr,"too many scenes\n"); return 1; } if (build_named(names[i],&scenes[nsc])) return 1; nsc++; }
|
|
if (nsc==0){ usage(); return 2; }
|
|
// validate every scene encodes before touching hardware
|
|
for (int i=0;i<nsc;i++){ uint64_t w[FEEDER_STG_WORDS]; if (build_staging(&scenes[i],w)<0) return 1; }
|
|
|
|
// ---- open the bridge (unless dry-run) ----
|
|
bridge_t br = { .base=NULL, .dry=dry };
|
|
int fd=-1; void *map=NULL;
|
|
if (!dry){
|
|
fd=open("/dev/mem", O_RDWR|O_SYNC);
|
|
if (fd<0){ fprintf(stderr,"error: open /dev/mem: %s\n", strerror(errno)); return 1; }
|
|
map=mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)base);
|
|
if (map==MAP_FAILED){ fprintf(stderr,"error: mmap 0x%lx: %s\n", base, strerror(errno)); close(fd); return 1; }
|
|
br.base=(volatile uint8_t*)map;
|
|
printf("ps2_feeder: bridge @ 0x%lx, %d scene(s)\n", base, nsc);
|
|
} else {
|
|
printf("ps2_feeder: DRY-RUN (encode+validate only), %d scene(s)\n", nsc);
|
|
}
|
|
|
|
int rc=0;
|
|
for (int i=0;i<nsc;i++){ if (stream_scene(&br,&scenes[i])){ rc=1; break; } }
|
|
|
|
if (!dry){ munmap(map,0x1000); close(fd); }
|
|
printf("ps2_feeder: %s\n", rc?"FAILED":"done");
|
|
return rc;
|
|
}
|