mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 19:07:47 -04:00
Support source GPU flat lines for Biohazard replay
(cherry picked from commit adb633e071)
This commit is contained in:
committed by
Alexandros Mandravillis
parent
2bf27fe2c1
commit
41db3dd60d
@@ -461,6 +461,15 @@ if(BUILD_TESTING)
|
||||
target_compile_options(tas_gpu_flat_blend_${_mode} PRIVATE -${_mode} -UNDEBUG)
|
||||
target_link_libraries(tas_gpu_flat_blend_${_mode} PRIVATE m)
|
||||
add_test(NAME tas_gpu_flat_blend_${_mode} COMMAND tas_gpu_flat_blend_${_mode})
|
||||
add_executable(tas_gpu_line_${_mode}
|
||||
../runtime/tests/test_source_gpu_line.c ../runtime/src/gpu_sw_renderer.c
|
||||
../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
|
||||
target_include_directories(tas_gpu_line_${_mode} PRIVATE ../runtime/include)
|
||||
target_compile_options(tas_gpu_line_${_mode} PRIVATE -${_mode} -UNDEBUG)
|
||||
target_link_libraries(tas_gpu_line_${_mode} PRIVATE m)
|
||||
add_test(NAME tas_gpu_line_${_mode} COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_line_contracts.py
|
||||
$<TARGET_FILE:tas_gpu_line_${_mode}>)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* explicitly qualified source GPU service event; reads do not advance it.
|
||||
* Current scope: NOP/cache-clear, drawing environment, A0/C0 transfers,02 fills,80 copies, variable rectangles,
|
||||
* observed untextured/textured polygons, including general opaque flat quads,
|
||||
* and flat two-vertex lines (0x40-0x47),
|
||||
* with inclusive clipping. Vertex and drawing offset are independently signed11-bit. Their sum stays
|
||||
* within[-2048,2046]; source signed clipping and raw interpolation are separate.
|
||||
* Clipping stays inside the admitted VRAM draw area.
|
||||
@@ -63,11 +64,15 @@ static inline int source_gpu_polygon_supported(unsigned opcode) {
|
||||
static inline unsigned source_gpu_polygon_setup(unsigned opcode) {
|
||||
return opcode&4 ? (opcode&0x10 ? 450:180) : (opcode&0x10 ? 288:0);
|
||||
}
|
||||
static inline int source_gpu_line_supported(unsigned command) {
|
||||
return command>=0x40 && command<=0x47; /* Two-vertex, flat-color lines. */
|
||||
}
|
||||
static inline int source_gpu_block_supported(unsigned command) {
|
||||
return command==2 || command==0x80 || command==0x60 || command==0x62 || command==0x64 || command==0x65 || command==0x66;
|
||||
}
|
||||
static inline unsigned source_gpu_command_length(uint32_t word) {
|
||||
unsigned command=word>>24;
|
||||
if(source_gpu_line_supported(command))return 3;
|
||||
if(source_gpu_block_supported(command))return command==0x80 || command==0x64 || command==0x65 || command==0x66?4:3;
|
||||
if(source_gpu_polygon_supported(command))return 1+3*source_gpu_polygon_stride(command)-!!(command&0x10);
|
||||
return command==0xa0 || command==0xc0 ? 3u : 1u;
|
||||
@@ -150,6 +155,17 @@ static inline int source_gpu_command_block_cost(const SourceGPUCommandProjection
|
||||
return cost;
|
||||
}
|
||||
|
||||
/* gpu_line.cpp charges setup and the unclipped major-axis length. Degenerate
|
||||
* lines still draw one pixel; oversize lines pay setup without raster work. */
|
||||
static inline int source_gpu_command_line_cost(const SourceGPUCommandProjection *s,const uint32_t *words) {
|
||||
if(s->clip_y0>511 || s->clip_y1>511 ||
|
||||
((s->display_mode&0x24)==0x24 && !(s->draw_mode&0x400) && !s->field_valid))return -1;
|
||||
int dx=source_gpu_command_coord(words[2],0)-source_gpu_command_coord(words[1],0);
|
||||
int dy=source_gpu_command_coord(words[2],16)-source_gpu_command_coord(words[1],16);
|
||||
if(dx<0)dx=-dx;if(dy<0)dy=-dy;
|
||||
return 16+((dx>=1024 || dy>=512)?0:2*(dx>dy?dx:dy));
|
||||
}
|
||||
|
||||
static inline int source_gpu_command_process(SourceGPUCommandProjection *s) {
|
||||
s->dispatch.kind=SOURCE_GPU_DISPATCH_NONE;
|
||||
if(s->error) return 0;
|
||||
@@ -177,6 +193,14 @@ static inline int source_gpu_command_process(SourceGPUCommandProjection *s) {
|
||||
}
|
||||
|
||||
unsigned command=s->queue[0]>>24;
|
||||
if(source_gpu_line_supported(command)) {
|
||||
if(s->budget<0 || s->count<3)return 1;
|
||||
int cost=source_gpu_command_line_cost(s,s->queue);
|
||||
if(cost<0){s->error=SOURCE_GPU_COMMAND_UNSUPPORTED;return 0;}
|
||||
s->dispatch.kind=SOURCE_GPU_DISPATCH_COMMAND;s->dispatch.count=3;
|
||||
for(unsigned i=0;i<3;i++)s->dispatch.words[i]=source_gpu_command_pop(s);
|
||||
s->budget-=2+cost;return 1;
|
||||
}
|
||||
if(source_gpu_block_supported(command)) {
|
||||
unsigned n=source_gpu_command_length(s->queue[0]);
|
||||
if(s->budget<0 || s->count<n)return 1;
|
||||
|
||||
+5
-1
@@ -5905,6 +5905,10 @@ static int gpu_source_draw_block(const uint32_t *words) {
|
||||
block.clip_right=state.clip_x1;block.clip_bottom=state.clip_y1;
|
||||
block.x=source_gpu_sprite_origin(words[1],0,state.offset_x);
|
||||
block.y=source_gpu_sprite_origin(words[1],16,state.offset_y);
|
||||
if(source_gpu_line_supported(opcode)) {
|
||||
block.x=source_gpu_command_coord(words[1],0)+state.offset_x;
|
||||
block.y=source_gpu_command_coord(words[1],16)+state.offset_y;
|
||||
}
|
||||
block.interlace=(state.display_mode&0x24)==0x24 && !(state.draw_mode&0x400);
|
||||
block.skip_field=state.skip_field;
|
||||
if(opcode==0x80)gr_source_texture_control(2,0);
|
||||
@@ -5924,7 +5928,7 @@ static int gpu_source_dispatch(const SourceGPUCommandDispatch *event) {
|
||||
gpu_write_gp0_body(event->words[0]);
|
||||
} else if(event->kind==SOURCE_GPU_DISPATCH_COMMAND) {
|
||||
unsigned opcode=event->words[0]>>24;
|
||||
if(source_gpu_block_supported(opcode)) {
|
||||
if(source_gpu_block_supported(opcode) || source_gpu_line_supported(opcode)) {
|
||||
gp0_opcode_count[opcode]++;
|
||||
if(opcode==2)gp0_fill_count++;else if(opcode==0x80)gp0_copy_count++;else gp0_draw_count++;
|
||||
gp0_ring_record(gp0_cmd_buf,event->count);
|
||||
|
||||
@@ -404,6 +404,32 @@ int sw_draw_source_block(const SourceGPUBlock *block,int *extra_work) {
|
||||
*extra_work=0;
|
||||
if(g_hr || g_wide_cur || g_precise_valid || g_perspective_valid)return 0;
|
||||
const uint32_t *words=block->words;unsigned opcode=words[0]>>24;
|
||||
if(opcode>=0x40 && opcode<=0x47) {
|
||||
int dx=(int)((words[2]&2047u)^1024u)-(int)((words[1]&2047u)^1024u);
|
||||
int dy=(int)(((words[2]>>16)&2047u)^1024u)-(int)(((words[1]>>16)&2047u)^1024u);
|
||||
int ax=abs(dx),ay=abs(dy),length=ax>ay?ax:ay;
|
||||
if(ax>=1024 || ay>=512)return 1;
|
||||
int x=block->x,y=block->y;
|
||||
if(dx<=0 && length){x+=dx;y+=dy;dx=-dx;dy=-dy;}
|
||||
/* Source line interpolation uses 32 fractional bits, rounds the step
|
||||
* away from zero, and biases half-pixel ties by 1024 fractional units. */
|
||||
int64_t sx=(int64_t)dx*4294967296LL,sy=(int64_t)dy*4294967296LL;
|
||||
if(length) {
|
||||
sx=(sx+(sx<0?1-length:sx>0?length-1:0))/length;
|
||||
sy=(sy+(sy<0?1-length:sy>0?length-1:0))/length;
|
||||
}
|
||||
uint64_t fx=((uint64_t)(int64_t)x<<32)+2147483648ULL-1024;
|
||||
uint64_t fy=((uint64_t)(int64_t)y<<32)+2147483648ULL-(sy<0?1024:0);
|
||||
SourceTriangleColors c={0};c.dither=!!(block->draw_mode&512u);
|
||||
for(unsigned channel=0;channel<3;channel++)c.base[channel]=((words[0]>>(8*channel))&255u)*4096u;
|
||||
for(int i=0;i<=length;i++,fx+=(uint64_t)sx,fy+=(uint64_t)sy) {
|
||||
int px=(int)((fx>>32)&2047u),py=(int)((fy>>32)&2047u);
|
||||
if(px<block->clip_left || px>block->clip_right || py<block->clip_top || py>block->clip_bottom)continue;
|
||||
if(block->interlace && ((unsigned)py&1u)==block->skip_field)continue;
|
||||
source_triangle_span(&c,py,px,1,px);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if(opcode==2) {
|
||||
unsigned x0=words[1]&1008u,y0=(words[1]>>16)&511u;
|
||||
unsigned width=((words[2]&1023u)+15u)&~15u,height=(words[2]>>16)&511u;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,7 @@ int main(int argc,char **argv) {
|
||||
assert(source_gpu_command_update(&s,595)); assert(s.second_triangles==1);
|
||||
assert(source_gpu_command_update(&s,638)); assert(s.second_triangles==2 && s.budget==-45);
|
||||
assert(!source_gpu_command_update(&s,637)); assert(s.error==SOURCE_GPU_COMMAND_REVERSE_TIME);
|
||||
setup(&s); assert(!source_gpu_command_write(&s,0x40000000));
|
||||
setup(&s); assert(!source_gpu_command_write(&s,0x48000000));
|
||||
assert(s.error==SOURCE_GPU_COMMAND_UNSUPPORTED);
|
||||
setup(&s); assert(source_gpu_command_update(&s,146)); quad(&s,32);
|
||||
for(unsigned i=1;i<16;++i) assert(source_gpu_command_write(&s,0));
|
||||
|
||||
@@ -74,6 +74,6 @@ int main(int argc,char **argv) {
|
||||
const unsigned opcodes[]={0x20,0x21,0x22,0x23,0x28,0x29,0x2a,0x2b};
|
||||
for(unsigned op=0;op<8;op++)for(unsigned blend=0;blend<4;blend++)for(unsigned mask=0;mask<4;mask++)run(opcodes[op],blend,mask);
|
||||
SourceGPUCommandProjection s;source_gpu_command_cold(&s);
|
||||
check(!source_gpu_command_write(&s,0x40000000) && s.error==SOURCE_GPU_COMMAND_UNSUPPORTED,"unqualified line commands still fail closed");
|
||||
check(!source_gpu_command_write(&s,0x48000000) && s.error==SOURCE_GPU_COMMAND_UNSUPPORTED,"unqualified polyline commands still fail closed");
|
||||
printf("source_gpu_flat_blend: %u checks passed\n",checks);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/* Authored input cases are compared against the retained source oracle hashes. */
|
||||
#include "source_gpu_command_projection.h"
|
||||
#include "gpu_sw_renderer.h"
|
||||
#include "psx_sha256.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
static uint16_t vram[1024*512];
|
||||
int g_ws_bd_stretch_on,g_ws_bd_stretch_pct;
|
||||
int psx_ws_prim_in_backdrop(void){abort();}
|
||||
int main(void) {
|
||||
unsigned op,color,mode,mask,field;
|
||||
int x0,y0,x1,y1,ox,oy,clip;
|
||||
while(scanf("%u %u %d %d %d %d %d %d %u %u %u %d",&op,&color,&x0,&y0,&x1,&y1,&ox,&oy,&mode,&mask,&field,&clip)==12) {
|
||||
uint32_t words[]={op<<24|color,((uint32_t)y0&65535u)<<16|((uint32_t)x0&65535u),((uint32_t)y1&65535u)<<16|((uint32_t)x1&65535u)};
|
||||
SourceGPUCommandProjection s;source_gpu_command_cold(&s);
|
||||
s.clip_x0=s.clip_y0=clip;s.clip_x1=1023-clip;s.clip_y1=511-clip;
|
||||
s.budget=256;s.offset_x=ox;s.offset_y=oy;s.draw_mode=mode;
|
||||
s.display_mode=field?0x24:0;s.field_valid=1;s.skip_field=field==2;s.mask_bits=mask;
|
||||
assert(source_gpu_command_length(words[0])==3);
|
||||
assert(source_gpu_command_feedback_length(words[0])==1);
|
||||
for(unsigned i=0;i<2;i++){assert(source_gpu_command_write(&s,words[i]));assert(s.dispatch.kind==0);}
|
||||
assert(!source_gpu_command_ready(&s));
|
||||
assert(source_gpu_command_write(&s,words[2]));
|
||||
assert(s.dispatch.kind==SOURCE_GPU_DISPATCH_COMMAND && s.dispatch.count==3 && !s.count);
|
||||
for(unsigned i=0;i<1024*512;i++)vram[i]=(uint16_t)(i*37u+0x1234u);
|
||||
sw_renderer_init(vram);sw_set_mask_bits(mask&1,!!(mask&2));sw_set_semi_transparency(!!(op&2),(mode>>5)&3);
|
||||
SourceGPUBlock b={0};b.words=words;b.draw_mode=mode;
|
||||
b.x=source_gpu_command_coord(words[1],0)+ox;b.y=source_gpu_command_coord(words[1],16)+oy;
|
||||
b.clip_left=b.clip_top=clip;b.clip_right=1023-clip;b.clip_bottom=511-clip;
|
||||
b.interlace=field && !(mode&1024);b.skip_field=field==2;
|
||||
int extra=-1;assert(sw_draw_source_block(&b,&extra) && extra==0);
|
||||
uint8_t hash[32];psx_sha256_compute((uint8_t*)vram,sizeof(vram),hash);
|
||||
printf("%d ",256-s.budget);for(unsigned i=0;i<32;i++)printf("%02x",hash[i]);puts("");
|
||||
}
|
||||
SourceGPUCommandProjection s;source_gpu_command_cold(&s);
|
||||
s.budget=-1;s.clip_x1=1023;s.clip_y1=511;
|
||||
assert(source_gpu_command_write(&s,0x4000ff00));assert(source_gpu_command_write(&s,0x00a20054));
|
||||
assert(source_gpu_command_write(&s,0x00a20054));assert(s.count==3 && !s.dispatch.kind);
|
||||
assert(source_gpu_command_update(&s,1));assert(!s.count && s.budget==-17);
|
||||
source_gpu_command_cold(&s);assert(!source_gpu_command_write(&s,0x48000000));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Check complete VRAM hashes and timing against authored source-line fixtures."""
|
||||
import json, os, subprocess, sys
|
||||
from pathlib import Path
|
||||
fixture=json.loads(Path(__file__).with_name('source_gpu_line_fixtures.json').read_text())
|
||||
env={k:v for k,v in os.environ.items() if not k.startswith('PSX_')}
|
||||
r=subprocess.run([sys.argv[1]],input=''.join(' '.join(map(str,c['input']))+'\n' for c in fixture['cases']),capture_output=True,text=True,env=env,timeout=120)
|
||||
assert r.returncode==0,(r.returncode,r.stderr)
|
||||
rows=r.stdout.splitlines()
|
||||
assert len(rows)==len(fixture['cases'])
|
||||
for row,case in zip(rows,fixture['cases']):
|
||||
assert row==case['expected'],(case['input'],row,case['expected'])
|
||||
print(f"{len(rows)} source line timing and complete VRAM hashes PASS")
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Generate authored line fixtures using unedited Nymashock source functions.
|
||||
|
||||
Builds extracted reference functions only in the supplied private output folder.
|
||||
This is a component oracle, not an unmodified-core or full-replay qualification.
|
||||
"""
|
||||
import argparse, hashlib, json, random, subprocess
|
||||
from pathlib import Path
|
||||
|
||||
p=argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--source',type=Path,required=True)
|
||||
p.add_argument('--output',type=Path,required=True)
|
||||
a=p.parse_args();a.output.mkdir(parents=True,exist_ok=False)
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
line=(a.source/'gpu_line.cpp').read_text();common=(a.source/'gpu_common.inc').read_text()
|
||||
plot=common[common.index('template<int BlendMode, bool MaskEval_TA, bool textured>'):common.index('static INLINE uint16 ModTexel')]
|
||||
skip=common[common.index('static INLINE bool LineSkipTest'):common.index('static INLINE bool LineSkipTest')+common[common.index('static INLINE bool LineSkipTest'):].index('\n}')+2]
|
||||
body=line[line.index('struct line_fxp_coord'):line.index('MDFN_HIDE extern const CTEntry')]
|
||||
prefix='''#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "psx_sha256.h"
|
||||
using uint8=uint8_t;using uint16=uint16_t;using uint32=uint32_t;using uint64=uint64_t;
|
||||
using int32=int32_t;using int64=int64_t;
|
||||
#define INLINE inline
|
||||
static uint16 GPURAM[512][1024];
|
||||
static int ClipX0,ClipY0,ClipX1,ClipY1,OffsX,OffsY,DrawTimeAvail,dtd,dfe,MaskSetOR,MaskEvalAND;
|
||||
static unsigned DisplayMode,DisplayFB_YStart,field_ram_readout;
|
||||
static uint8 DitherLUT[4][4][256];
|
||||
struct line_point {int32 x,y;uint8 r,g,b;};
|
||||
struct PS_GPU {enum {INCMD_PLINE=1};};
|
||||
static int InCmd,InCmd_CC;static line_point InPLine_PrevPoint;
|
||||
static int sign_x_to_s32(unsigned bits,unsigned value){unsigned mask=1u<<(bits-1);return int((value&((mask<<1)-1))^mask)-int(mask);}
|
||||
'''
|
||||
dispatch='\n'.join(f'case {b}: if(mask&2)Command_DrawLine<false,false,{b},true>(words);else Command_DrawLine<false,false,{b},false>(words);break;' for b in range(-1,4))
|
||||
main='''int main(){unsigned op,color,mode,mask,field;int x0,y0,x1,y1,ox,oy,clip;
|
||||
const int matrix[4][4]={{-4,0,-3,1},{2,-2,3,-1},{-3,1,-4,0},{3,-1,2,-2}};
|
||||
for(int y=0;y<4;y++)for(int x=0;x<4;x++)for(int c=0;c<256;c++){int v=c+matrix[y][x];if(v<0)v=0;if(v>255)v=255;DitherLUT[y][x][c]=v>>3;}
|
||||
while(scanf("%u %u %d %d %d %d %d %d %u %u %u %d",&op,&color,&x0,&y0,&x1,&y1,&ox,&oy,&mode,&mask,&field,&clip)==12){
|
||||
for(unsigned i=0;i<512*1024;i++)GPURAM[i/1024][i%1024]=(uint16)(i*37u+0x1234u);
|
||||
ClipX0=ClipY0=clip;ClipX1=1023-clip;ClipY1=511-clip;OffsX=ox;OffsY=oy;
|
||||
MaskSetOR=(mask&1)?0x8000:0;MaskEvalAND=(mask&2)?0x8000:0;dtd=!!(mode&512);dfe=!!(mode&1024);
|
||||
DisplayMode=field?0x24:0;DisplayFB_YStart=0;field_ram_readout=field==2;
|
||||
uint32 words[]={op<<24|color,((uint32)y0&65535u)<<16|((uint32)x0&65535u),((uint32)y1&65535u)<<16|((uint32)x1&65535u)};
|
||||
DrawTimeAvail=254;switch((op&2)?int((mode>>5)&3):-1){DISPATCH}
|
||||
uint8 hash[32];psx_sha256_compute((uint8*)GPURAM,sizeof(GPURAM),hash);
|
||||
printf("%d ",256-DrawTimeAvail);for(unsigned i=0;i<32;i++)printf("%02x",hash[i]);puts("");}}
|
||||
'''.replace('DISPATCH',dispatch)
|
||||
cpp=a.output/'source-line.cpp';cpp.write_text(prefix+plot+skip+body+main)
|
||||
exe=a.output/'source-line.exe'
|
||||
subprocess.run(['g++','-O2','-I'+str(root/'runtime/include'),str(cpp),str(root/'runtime/src/psx_sha256.c'),'-o',str(exe)],check=True)
|
||||
rng=random.Random(20371)
|
||||
geometries=[(84,162,84,162),(0,0,0,0),(4,4,12,4),(4,4,4,12),(4,4,12,8),(12,8,4,4),(4,12,12,4),(-1,-1,8,8),(0,0,1023,511),(0,0,1024,0),(0,0,0,512),(1023,511,1023,511),(-1024,-1024,-1024,-1024)]
|
||||
cases=[]
|
||||
for i in range(256):
|
||||
xy=geometries[i%len(geometries)] if i<104 else tuple(rng.randrange(-1024,1024) for _ in range(4))
|
||||
cases.append([0x40+i%8,[0x00ff00,0xe3942b,0xffffff,0][i%4],*xy,0 if i<104 else rng.choice([-1024,-1,0,240,1023]),240 if i==0 else 0 if i<104 else rng.choice([-1024,-1,0,240,1023]),((i//8)%4)*32+(512 if i&1 else 0)+(1024 if i%7==0 else 0),(i//32)%4,(i//64)%3,0 if i%3 else 4])
|
||||
cases[0]=[0x40,0x00ff00,84,162,84,162,0,240,512,0,0,0]
|
||||
r=subprocess.run([str(exe)],input=''.join(' '.join(map(str,c))+'\n' for c in cases),capture_output=True,text=True,check=True,timeout=120)
|
||||
rows=r.stdout.splitlines();assert len(rows)==len(cases)
|
||||
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
receipt={'scope':__doc__,'source_files':[{'path':str(a.source/n),'sha256':sha(a.source/n)} for n in ['gpu_line.cpp','gpu_common.inc']], 'driver_sha256':sha(Path(__file__)),'extracted_harness_sha256':sha(cpp),'cases':[{'input':c,'expected':row} for c,row in zip(cases,rows)]}
|
||||
(a.output/'fixtures.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(a.output/'fixtures.json')
|
||||
Reference in New Issue
Block a user