mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-07 04:25:58 -10:00
SPG: keep the beam from one read of SPG_STATUS to the next
A game waiting on the beam reads SPG_STATUS hundreds of times a line, and each read found the next line due and divided twice to place the beam. spg_beam() now keeps what it found - the line, the pending event's end, the cycles that bound the line - and answers a read on the same line, aimed by the same event, with a subtraction. Aiming the event, a timing change or a state loaded starts it afresh. The headless disc runs 3000 frames in 11.5 s, from 17.8 s. tools/pvr/spg_beam_test.sh makes every read twice, kept and afresh, straight and with every frame run ahead, and fails where they differ.
This commit is contained in:
1 parent
b60d97627a
commit
1ed251bfec
6 files changed
+170
-6
No files matched your search
@@ -9,7 +9,9 @@ name: Headless
|
||||
# And a strip's indices, the parse's C89 kernel, against the loop it
|
||||
# replaced (tools/pvr/ta_index_test.sh); and the parse timed on the heavy
|
||||
# disc, the strips its vertex loop found drawable whole making the indices
|
||||
# a look at their vertices does (tools/pvr/ta_parse_bench.sh).
|
||||
# a look at their vertices does (tools/pvr/ta_parse_bench.sh). And the beam
|
||||
# each read of SPG_STATUS sees, kept from the read before on its line,
|
||||
# against the beam found afresh (tools/pvr/spg_beam_test.sh).
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -53,3 +55,5 @@ jobs:
|
||||
run: NEON=required sh tools/pvr/ta_index_test.sh
|
||||
- name: The parse timed on the heavy disc, the strips known good making the indices a look at them does
|
||||
run: sh tools/pvr/ta_parse_bench.sh
|
||||
- name: The beam kept from read to read of SPG_STATUS against the beam found afresh, straight and run ahead
|
||||
run: sh tools/pvr/spg_beam_test.sh
|
||||
+57
-5
@@ -40,6 +40,33 @@ u32 sh4_sched_remaining(int id);
|
||||
int render_end_schid;
|
||||
int vblank_schid;
|
||||
|
||||
/* What spg_beam() last found, for the reads of SPG_STATUS a game makes
|
||||
* while the beam is still on the same line - hundreds a line, as it waits
|
||||
* for one: the line, the pending event's end it was aimed by, and
|
||||
* Line_Num's generation; the cycle the event's line starts at, the line's
|
||||
* own, and the cycles the event still has while the beam is before the
|
||||
* next line (more than beam_stay) or still at the line's start (beam_span
|
||||
* or more). Anything else that moves the answer - a register written, a
|
||||
* gun, the run's window - aims the event again, and its end is another. */
|
||||
extern std::vector<sched_list> sch_list;
|
||||
static u32 beam_line = ~0u;
|
||||
static int beam_end;
|
||||
static u32 beam_gen, line_gen = 1;
|
||||
static u64 beam_target_at, beam_line_at;
|
||||
static u32 beam_span, beam_stay;
|
||||
|
||||
void spg_beam_forget()
|
||||
{
|
||||
beam_line = ~0u;
|
||||
}
|
||||
|
||||
/* The pending event aimed again: whatever spg_beam() kept is from before. */
|
||||
static void spg_aim(int cycles)
|
||||
{
|
||||
line_gen++;
|
||||
sh4_sched_request(vblank_schid, cycles);
|
||||
}
|
||||
|
||||
static u32 lightgun_line = 0xffff;
|
||||
static u32 lightgun_hpos;
|
||||
static bool maple_int_pending;
|
||||
@@ -142,7 +169,7 @@ static void frame_window_close()
|
||||
}
|
||||
const u64 beam = spg_beam();
|
||||
frame_window = false;
|
||||
sh4_sched_request(vblank_schid, (int)(line_start(spg_next_line()) - beam));
|
||||
spg_aim((int)(line_start(spg_next_line()) - beam));
|
||||
}
|
||||
|
||||
static void frame_end()
|
||||
@@ -222,6 +249,8 @@ void CalculateSync(bool reschedule)
|
||||
|
||||
Line_Num = (u64)SH4_MAIN_CLOCK * (u64)(SPG_LOAD.hcount + 1);
|
||||
Line_Den = pixel_clock;
|
||||
// (spg_beam()'s lines are another's now)
|
||||
line_gen++;
|
||||
|
||||
float scale_x = 1.f, scale_y = 1.f;
|
||||
|
||||
@@ -253,7 +282,7 @@ void CalculateSync(bool reschedule)
|
||||
if (reschedule)
|
||||
{
|
||||
pvr_cur_scanline = 0;
|
||||
sh4_sched_request(vblank_schid, (int)line_start(spg_next_line()));
|
||||
spg_aim((int)line_start(spg_next_line()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,14 +464,27 @@ static void spg_run_lines(u32 lines)
|
||||
* event would have been aimed at one of them. */
|
||||
static u64 spg_beam()
|
||||
{
|
||||
const u32 remaining = sh4_sched_remaining(vblank_schid);
|
||||
// on the line it was on, by the same event: no division, no line looked for
|
||||
if (beam_line == pvr_cur_scanline && beam_gen == line_gen && remaining != (u32)-1
|
||||
&& beam_end == sch_list[vblank_schid].end)
|
||||
{
|
||||
if (remaining >= beam_span)
|
||||
return beam_line_at;
|
||||
if (remaining > beam_stay)
|
||||
return beam_target_at - remaining;
|
||||
}
|
||||
|
||||
const u32 target = spg_next_line();
|
||||
const u64 target_at = line_start(target);
|
||||
const u64 line_at = line_start(pvr_cur_scanline);
|
||||
const u32 remaining = sh4_sched_remaining(vblank_schid);
|
||||
|
||||
// nothing pending, or the event is due: the beam is where it was put last
|
||||
if (remaining == (u32)-1 || (u64)remaining >= target_at - line_at)
|
||||
{
|
||||
beam_line = ~0u;
|
||||
return line_at;
|
||||
}
|
||||
|
||||
const u64 beam = target_at - remaining;
|
||||
u32 line = (u32)(beam * Line_Den / Line_Num);
|
||||
@@ -454,6 +496,14 @@ static u64 spg_beam()
|
||||
line = target - 1;
|
||||
if (line > pvr_cur_scanline)
|
||||
spg_run_lines(line - pvr_cur_scanline);
|
||||
// (the line it is on now, and the next line's start, for the reads to come)
|
||||
beam_line = pvr_cur_scanline;
|
||||
beam_end = sch_list[vblank_schid].end;
|
||||
beam_gen = line_gen;
|
||||
beam_target_at = target_at;
|
||||
beam_line_at = line_start(pvr_cur_scanline);
|
||||
beam_span = (u32)(target_at - beam_line_at);
|
||||
beam_stay = pvr_cur_scanline + 1 < target ? (u32)(target_at - line_start(pvr_cur_scanline + 1)) : 0;
|
||||
return beam;
|
||||
}
|
||||
|
||||
@@ -478,7 +528,7 @@ void spg_write_timing(u32 addr, u32 data)
|
||||
const u64 beam = spg_beam();
|
||||
|
||||
PvrReg(addr, u32) = data;
|
||||
sh4_sched_request(vblank_schid, (int)(line_start(spg_next_line()) - beam));
|
||||
spg_aim((int)(line_start(spg_next_line()) - beam));
|
||||
}
|
||||
|
||||
//called from sh4 context , should update pvr/ta state and everything else
|
||||
@@ -527,7 +577,7 @@ void read_lightgun_position(int x, int y)
|
||||
lightgun_flip ^= 1;
|
||||
}
|
||||
if (pending)
|
||||
sh4_sched_request(vblank_schid, (int)(line_start(spg_next_line()) - beam));
|
||||
spg_aim((int)(line_start(spg_next_line()) - beam));
|
||||
}
|
||||
|
||||
// The game cancelled the gun's occupation of the bus: its transfer ends in its own time
|
||||
@@ -567,6 +617,7 @@ void spg_gun_unserialize(void **data, unsigned int *total_size)
|
||||
|
||||
/* A line is at most 479 down from the line the picture starts on, which
|
||||
* is a 10-bit register; the place goes into ten bits of a register. */
|
||||
spg_beam_forget();
|
||||
lightgun_line = line > 479 + 1023 ? 0xffff : line;
|
||||
lightgun_hpos = hpos & 0x3FF;
|
||||
maple_int_pending = (flags & 1) != 0;
|
||||
@@ -582,6 +633,7 @@ void spg_gun_before_v21(void)
|
||||
{
|
||||
extern int maple_sched;
|
||||
|
||||
spg_beam_forget();
|
||||
lightgun_line = 0xffff;
|
||||
lightgun_hpos = 0;
|
||||
lightgun_flip = 0;
|
||||
|
||||
@@ -12,6 +12,8 @@ void read_lightgun_position(int x, int y);
|
||||
void spg_gun_occupy_cancel();
|
||||
// Before a read of SPG_STATUS: brings its scanline up to where the beam is
|
||||
void spg_sync();
|
||||
// The beam's state has been put in place from elsewhere (a save state)
|
||||
void spg_beam_forget();
|
||||
// A write to SPG_HBLANK_INT, SPG_VBLANK_INT or SPG_VBLANK
|
||||
void spg_write_timing(u32 addr, u32 data);
|
||||
// Where a run (a retro_run's frame) ends, spg.cpp: a run starts (dc_run())...
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "hw/gdrom/gdromv3.h"
|
||||
#include "hw/maple/maple_devs.h"
|
||||
#include "hw/maple/maple_cfg.h"
|
||||
#include "hw/pvr/spg.h"
|
||||
#include "hw/pvr/Renderer_if.h"
|
||||
#include "hw/pvr/ta_structs.h"
|
||||
#include "hw/sh4/sh4_interrupts.h"
|
||||
@@ -1440,6 +1441,7 @@ bool dc_unserialize(void **data, unsigned int *total_size, size_t actual_data_si
|
||||
LIBRETRO_US(dummy_int) ; // the tag: what the device registered with, not state
|
||||
LIBRETRO_US(sch_list[vblank_schid].start) ;
|
||||
LIBRETRO_US(sch_list[vblank_schid].end) ;
|
||||
spg_beam_forget();
|
||||
|
||||
if (version < V9)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/* The beam a read of SPG_STATUS sees, as spg_beam() keeps it from one read
|
||||
* to the next on the same line, against the beam found afresh. Linked into
|
||||
* one program with the core's objects and tools/threads/headless.c, with
|
||||
* spg_sync() wrapped (spg_beam_test.sh): each read is made as the core
|
||||
* makes it, and made again once spg_beam_forget() has thrown away what it
|
||||
* kept. The second finds the beam from nothing; were the first's line
|
||||
* short of it, the second would move it on, and SPG_STATUS would read
|
||||
* otherwise. At the end: "spg_beam_test: N reads, M lines, K different". */
|
||||
#include "types.h"
|
||||
#include "hw/pvr/pvr_regs.h"
|
||||
#include "hw/pvr/spg.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern "C" void __real__Z8spg_syncv(void);
|
||||
extern "C" void *__real_dlopen(const char *file, int mode);
|
||||
extern u32 pvr_cur_scanline;
|
||||
|
||||
/* headless.c opens the core; "self" is the program, the core linked in */
|
||||
extern "C" void *__wrap_dlopen(const char *file, int mode)
|
||||
{
|
||||
return __real_dlopen(file != NULL && strcmp(file, "self") == 0 ? NULL : file, mode);
|
||||
}
|
||||
|
||||
static unsigned long long reads, lines, different;
|
||||
static u32 last_line = ~0u;
|
||||
|
||||
extern "C" void __wrap__Z8spg_syncv(void)
|
||||
{
|
||||
u32 status, line;
|
||||
|
||||
__real__Z8spg_syncv();
|
||||
status = SPG_STATUS.full;
|
||||
line = pvr_cur_scanline;
|
||||
reads++;
|
||||
if (line != last_line)
|
||||
lines++;
|
||||
last_line = line;
|
||||
|
||||
spg_beam_forget();
|
||||
__real__Z8spg_syncv();
|
||||
if (SPG_STATUS.full != status || pvr_cur_scanline != line)
|
||||
{
|
||||
if (different++ < 8)
|
||||
fprintf(stderr, "spg_beam_test: read %llu: line %u status %08x kept, %u %08x afresh\n",
|
||||
reads, line, status, pvr_cur_scanline, SPG_STATUS.full);
|
||||
}
|
||||
}
|
||||
|
||||
/* (headless.c leaves through _exit(), past the destructors) */
|
||||
extern "C" void __real__exit(int status);
|
||||
extern "C" void __wrap__exit(int status)
|
||||
{
|
||||
printf("spg_beam_test: %llu reads, %llu lines, %llu different\n", reads, lines, different);
|
||||
fflush(stdout);
|
||||
__real__exit(status);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# The beam SPG_STATUS reads give, kept from read to read on a line by
|
||||
# spg_beam(), against the beam found afresh at each (spg_beam_test.cpp):
|
||||
# the test disc run on tools/threads/headless.c, straight and then with
|
||||
# every frame run ahead (a state saved and loaded each frame). The core's
|
||||
# own objects are linked in, so the core has to be built for this machine
|
||||
# first (make platform=unix).
|
||||
#
|
||||
# sh tools/pvr/spg_beam_test.sh [FRAMES]
|
||||
#
|
||||
# KEEP=1: the program is kept, in $TMPDIR/spg_beam_test.<pid>.
|
||||
# Fails where a read's beam differs, or too few reads were made to tell.
|
||||
set -e
|
||||
cd "$(dirname "$0")/../.."
|
||||
FRAMES=${1:-600}
|
||||
WORK=${TMPDIR:-/tmp}/spg_beam_test.$$
|
||||
mkdir -p "$WORK"
|
||||
[ -n "${KEEP:-}" ] || trap 'rm -rf "$WORK"' EXIT
|
||||
[ -f core/hw/pvr/spg.o ] || { echo "build the core first: core/hw/pvr/spg.o is not there" >&2; exit 1; }
|
||||
|
||||
CORE_FLAGS=$(make -n platform=unix -W core/hw/pvr/spg.cpp core/hw/pvr/spg.o 2>/dev/null | grep -m1 'core/hw/pvr/spg.cpp' \
|
||||
| sed -e 's/^g++ //' -e 's/ -c / /' -e 's/ -MMD -MP .*$//' -e 's/-DGIT_VERSION=[^ ]* [^ ]*//')
|
||||
OBJS=$(make -n platform=unix -W core/hw/pvr/spg.cpp 2>/dev/null | tail -1 | tr ' ' '\n' | grep '\.o$' | grep -v 'glslang.js.o')
|
||||
${CC:-cc} -O1 -c -o "$WORK/headless.o" tools/threads/headless.c
|
||||
${CXX:-g++} $CORE_FLAGS -w -c -o "$WORK/test.o" tools/pvr/spg_beam_test.cpp
|
||||
${CXX:-g++} -fopenmp -rdynamic -o "$WORK/spg_beam_test" "$WORK/headless.o" "$WORK/test.o" $OBJS \
|
||||
-Wl,--wrap=dlopen -Wl,--wrap=_exit -Wl,--wrap=_Z8spg_syncv -lGL -lrt -lpthread -lm -ldl
|
||||
# an OpenGL that does nothing, for the core's renderer (headless.sh's)
|
||||
nm -D --undefined-only "$WORK/spg_beam_test" | awk '{print $NF}' | sed 's/@.*//' | grep '^gl' \
|
||||
| python3 tools/threads/headless_gl.py > "$WORK/nogl.c" || true
|
||||
${CC:-cc} -O1 -shared -fPIC -Wl,-soname,libGLESv2.so.2 -o "$WORK/libGLESv2.so.2" "$WORK/nogl.c"
|
||||
python3 tools/threads/live_disc.py "$WORK/test.gdi"
|
||||
|
||||
fail=0
|
||||
for ahead in "" 10; do
|
||||
rm -rf "$WORK/dir"; mkdir -p "$WORK/dir"
|
||||
HEADLESS_AHEAD=$ahead LD_PRELOAD="$WORK/libGLESv2.so.2" HEADLESS_DIR="$WORK/dir" \
|
||||
"$WORK/spg_beam_test" self "$WORK/test.gdi" "$FRAMES" reicast_threaded_rendering=disabled \
|
||||
> "$WORK/out" 2> "$WORK/err" || true
|
||||
r=$(grep '^spg_beam_test:' "$WORK/out") || { echo "spg_beam_test: no result"; tail -5 "$WORK/err"; exit 1; }
|
||||
echo "${ahead:+run ahead from frame $ahead: }$r"
|
||||
grep 'spg_beam_test: read' "$WORK/err" || true
|
||||
# many reads to a line (a wait on the beam), and every one the same
|
||||
echo "$r" | awk '{ if ($6 != 0 || $2 < 100000 || $2 < 4 * $4) exit 1 }' || fail=1
|
||||
done
|
||||
[ $fail = 0 ] || { echo "spg_beam_test: FAIL: a read's beam kept from the last differs from the beam found afresh, or too few reads"; exit 1; }
|
||||
echo "spg_beam_test: pass"
|
||||
Reference in new issue
Block a user