mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-07 04:25:58 -10:00
ta: many blocks at once are scanned, not stepped; the NAOMI 2's are not copied
The tile accelerator's input: each 32-byte block is copied into the
frame's buffer and a state machine moved on by its control word.
Many at once (a DMA transfer; the NAOMI 2's geometry processor).
ta_vtx_data() called the one-block code for each: where the buffer is,
whether there is room and the state, read from memory and written back
every time. It now keeps them in registers across a run, and a run of
vertices in a 32-byte format - most of what there ever is - changes
nothing but where the next block goes, so it is found by looking at one
byte of each.
No copy for the geometry processor. It made its blocks in a buffer of
its own and the tile accelerator copied them: 2.8 MB for a Virtua
Fighter 4 fight frame. The host now offers it room where the next
blocks would be put (elan_host_ta_room(), from ta_vtx_room()); blocks
made there are handed over as before, and ta_vtx_data(), finding them
already in place, only moves the state on. The chip asks each time it
has nothing waiting - other things write to the tile accelerator in
between - and uses its own buffer when there is no room to give. When
its buffer fills in the middle of a strip, the vertices it keeps back
are already where the next room begins, and are not moved either.
One block at a time (a store queue: what most Dreamcast games do) is
one comparison where there were two tests, and the same speed: what
bounds it is the state being read, looked up and written for each
block. Reaching the variables it uses directly, not through the
library's table of addresses, took it from 38 instructions to 34 and
made it 40% slower in the benchmark on this machine; that was left out.
Per frame, on one core of a cloud machine, the same two frames (Virtua
Fighter 4, 88,143 blocks; Dead or Alive 2, 9,175):
before after
VF4, copied 258 us 215-230 us
VF4, made in place 258 us 92 us (what the game now gets)
DOA2, copied 20 us 6 us
a block a call 2.3-3.0 ns a block, before and after
The buffer and the state come out the same from all three ways (checked
against the data in the benchmark), and the chip sends the same: its
stand-alone test runs every scene again with room given by the host -
the 400 made up at random have the checksum they had, on all six
builds.
This commit is contained in:
1 parent
4f9a0f15cb
commit
524f31262d
6 files changed
+211
-28
No files matched your search
+25
-3
@@ -137,9 +137,21 @@ static unsigned count_in, count_out;
|
||||
/* (The tile accelerator takes its blocks from addresses that are
|
||||
* multiples of 32, as the SH4's store queues are.) */
|
||||
static uint32_t out_space[OUT_BLOCKS * 8 + 8];
|
||||
static uint32_t *out;
|
||||
static uint32_t *out_own; /* in out_space */
|
||||
static uint32_t *out; /* where blocks are being made */
|
||||
static unsigned out_count;
|
||||
|
||||
/* Where the next blocks are made: in the tile accelerator's own memory
|
||||
* when the host has room there for a buffer's worth - they are then taken
|
||||
* as they lie, with no copy - and in this chip's buffer when it has not.
|
||||
* Asked every time there is nothing waiting: other things send to the
|
||||
* tile accelerator in between, and its place moves. */
|
||||
static void out_begin(void)
|
||||
{
|
||||
uint32_t *room = elan_host_ta_room(OUT_BLOCKS);
|
||||
out = room ? room : out_own;
|
||||
}
|
||||
|
||||
static void out_flush(void)
|
||||
{
|
||||
if (out_count)
|
||||
@@ -155,6 +167,8 @@ static INLINE uint32_t *out_alloc(unsigned blocks)
|
||||
uint32_t *p;
|
||||
if (out_count + blocks > OUT_BLOCKS)
|
||||
out_flush();
|
||||
if (!out_count)
|
||||
out_begin();
|
||||
p = out + out_count * 8;
|
||||
out_count += blocks;
|
||||
return p;
|
||||
@@ -856,15 +870,22 @@ static void strip_vertex(const elan_vtx_t *v)
|
||||
* there are three; the rest goes. */
|
||||
unsigned keep = strip_total >= 3 ? 1 : strip_held;
|
||||
unsigned blocks = keep * strip_blocks;
|
||||
const uint32_t *kept;
|
||||
if (keep > strip_held)
|
||||
keep = strip_held, blocks = keep * strip_blocks;
|
||||
out_count -= blocks;
|
||||
kept = out + out_count * 8;
|
||||
if (out_count)
|
||||
elan_host_ta(out, out_count);
|
||||
memmove(out, out + out_count * 8, blocks * 32);
|
||||
/* (made in the tile accelerator's memory, what is kept is already
|
||||
* where the next room begins, and nothing moves) */
|
||||
out_begin();
|
||||
memmove(out, kept, blocks * 32);
|
||||
out_count = blocks;
|
||||
strip_held = keep;
|
||||
}
|
||||
else if (!out_count)
|
||||
out_begin();
|
||||
p = out + out_count * 8;
|
||||
out_count += strip_blocks;
|
||||
strip_total++;
|
||||
@@ -2021,7 +2042,8 @@ void elan_dma_done(void)
|
||||
void elan_init(uint8_t *memory)
|
||||
{
|
||||
ram = memory;
|
||||
out = (uint32_t *)(((size_t)out_space + 31) & ~(size_t)31);
|
||||
out_own = (uint32_t *)(((size_t)out_space + 31) & ~(size_t)31);
|
||||
out = out_own;
|
||||
}
|
||||
|
||||
void elan_reset(void)
|
||||
|
||||
@@ -76,6 +76,12 @@ void elan_counters(unsigned *in, unsigned *out);
|
||||
|
||||
/* @count 32-byte blocks for the tile accelerator. */
|
||||
void elan_host_ta(const uint32_t *blocks, unsigned count);
|
||||
|
||||
/* Room for @blocks blocks where the tile accelerator would put the next
|
||||
* ones itself, or NULL if it has none to give. Blocks made there are
|
||||
* handed to elan_host_ta() like any others - which sees where they are
|
||||
* and does not copy them. Good until the next thing is sent. */
|
||||
uint32_t *elan_host_ta_room(unsigned blocks);
|
||||
/* The list the tile accelerator has open (0-4), or -1. */
|
||||
int elan_host_ta_list(void);
|
||||
/* The tile accelerator is waiting for the second half of a 64-byte
|
||||
|
||||
@@ -97,6 +97,11 @@ extern "C" void elan_host_ta(const uint32_t *blocks, unsigned count)
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" uint32_t *elan_host_ta_room(unsigned blocks)
|
||||
{
|
||||
return (uint32_t *)ta_vtx_room(blocks);
|
||||
}
|
||||
|
||||
extern "C" int elan_host_ta_list(void)
|
||||
{
|
||||
return ta_vtx_list();
|
||||
|
||||
+140
-23
@@ -284,8 +284,10 @@ bool ta_vtx_half(void)
|
||||
return ta_cur_state >= TAS_PLHV32;
|
||||
}
|
||||
|
||||
static INLINE
|
||||
void DYNACALL ta_thd_data32_i(void* data)
|
||||
/* A block the careful way: with no list begun, at the very start of a
|
||||
* frame's data, or with the buffer full. */
|
||||
static NOINLINE
|
||||
void DYNACALL ta_data32_slow(void* data)
|
||||
{
|
||||
if (ta_ctx == NULL)
|
||||
{
|
||||
@@ -306,7 +308,8 @@ void DYNACALL ta_thd_data32_i(void* data)
|
||||
PCW pcw = *(PCW*)data;
|
||||
|
||||
/* Copy the TA data */
|
||||
*dst = *src;
|
||||
if (dst != src)
|
||||
*dst = *src;
|
||||
|
||||
ta_tad.thd_data+=32;
|
||||
|
||||
@@ -321,39 +324,153 @@ void DYNACALL ta_thd_data32_i(void* data)
|
||||
ta_handle_cmd(trans);
|
||||
}
|
||||
|
||||
/* A block from a store queue. With data already in the buffer and room
|
||||
* for more - one comparison says both - it is copied and the state moved
|
||||
* on here; anything else is ta_data32_slow()'s. */
|
||||
void DYNACALL ta_vtx_data32(void* data)
|
||||
{
|
||||
ta_thd_data32_i(data);
|
||||
TA_context *ctx = ta_ctx;
|
||||
u8 *to = ta_tad.thd_data;
|
||||
|
||||
if (ctx == NULL || (u32)(to - ta_tad.thd_root) - 32 >= ctx->data_size - 32)
|
||||
{
|
||||
ta_data32_slow(data);
|
||||
return;
|
||||
}
|
||||
|
||||
PCW pcw = *(PCW*)data;
|
||||
|
||||
*(simd256_t*)to = *(simd256_t*)data;
|
||||
ta_tad.thd_data = to + 32;
|
||||
|
||||
u32 trans = ta_fsm[(ta_cur_state<<8) | (pcw.ParaType<<5) | (pcw.obj_ctrl>>2)%32];
|
||||
ta_cur_state = (ta_state)trans;
|
||||
if (trans & 0xF0)
|
||||
ta_handle_cmd(trans);
|
||||
}
|
||||
|
||||
/* Where the next block will be put, if @blocks more can be: something that
|
||||
* makes blocks can make them there, and they are then taken by
|
||||
* ta_vtx_data() without being copied. NULL: they cannot, or not there. */
|
||||
u8 *ta_vtx_room(u32 blocks)
|
||||
{
|
||||
TA_context *ctx = ta_ctx;
|
||||
u32 used;
|
||||
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
used = (u32)(ta_tad.thd_data - ta_tad.thd_root);
|
||||
/* (nothing yet this frame: what the buffer had last is counted, as
|
||||
* ta_data32_slow() counts it) */
|
||||
if (used == 0 && (u32)(ta_tad.thd_old_data - ta_tad.thd_root) >= ctx->data_size)
|
||||
return NULL;
|
||||
if (used > ctx->data_size || blocks > (ctx->data_size - used) / 32)
|
||||
return NULL;
|
||||
return ta_tad.thd_data;
|
||||
}
|
||||
|
||||
/* Many blocks at once: a DMA transfer, or the NAOMI 2's geometry processor.
|
||||
*
|
||||
* Where to put the next one and the state are kept in registers across a
|
||||
* run of them, and a run of vertices in a 32-byte format - most of what
|
||||
* there ever is - changes neither the state nor anything else, so it is
|
||||
* found by looking at one byte of each. Blocks made in the buffer itself
|
||||
* (ta_vtx_room()) are not copied at all. */
|
||||
void ta_vtx_data(u32* data, u32 size)
|
||||
{
|
||||
while(size>4)
|
||||
while (size > 0)
|
||||
{
|
||||
ta_thd_data32_i(data);
|
||||
TA_context *ctx = ta_ctx;
|
||||
u8 *to = ta_tad.thd_data;
|
||||
u8 *root = ta_tad.thd_root;
|
||||
u32 used = (u32)(to - root);
|
||||
u32 count;
|
||||
u32 state;
|
||||
const u8 *from, *stop;
|
||||
bool handled = false;
|
||||
|
||||
data+=8;
|
||||
size--;
|
||||
if (ctx == NULL || used >= ctx->data_size
|
||||
|| (used == 0 && (u32)(ta_tad.thd_old_data - root) >= ctx->data_size))
|
||||
{
|
||||
ta_data32_slow(data);
|
||||
data += 8;
|
||||
size--;
|
||||
continue;
|
||||
}
|
||||
|
||||
ta_thd_data32_i(data);
|
||||
count = (ctx->data_size - used) / 32;
|
||||
if (count > size)
|
||||
count = size;
|
||||
state = ta_cur_state;
|
||||
from = (const u8 *)data;
|
||||
stop = from + count * 32;
|
||||
|
||||
data+=8;
|
||||
size--;
|
||||
if (from == to)
|
||||
{
|
||||
/* made in place */
|
||||
while (from != stop)
|
||||
{
|
||||
PCW pcw;
|
||||
u32 trans;
|
||||
|
||||
ta_thd_data32_i(data);
|
||||
data+=8;
|
||||
size--;
|
||||
if (state == TAS_PLV32)
|
||||
{
|
||||
while (from != stop && ((const PCW *)from)->ParaType == ParamType_Vertex_Parameter)
|
||||
from += 32;
|
||||
if (from == stop)
|
||||
break;
|
||||
}
|
||||
pcw = *(const PCW *)from;
|
||||
trans = ta_fsm[(state<<8) | (pcw.ParaType<<5) | (pcw.obj_ctrl>>2)%32];
|
||||
from += 32;
|
||||
state = trans;
|
||||
if (trans & 0xF0)
|
||||
{
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
to = (u8 *)from;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (from != stop)
|
||||
{
|
||||
PCW pcw;
|
||||
u32 trans;
|
||||
|
||||
ta_thd_data32_i(data);
|
||||
data+=8;
|
||||
size--;
|
||||
}
|
||||
if (state == TAS_PLV32)
|
||||
{
|
||||
while (from != stop && ((const PCW *)from)->ParaType == ParamType_Vertex_Parameter)
|
||||
{
|
||||
*(simd256_t *)to = *(const simd256_t *)from;
|
||||
to += 32;
|
||||
from += 32;
|
||||
}
|
||||
if (from == stop)
|
||||
break;
|
||||
}
|
||||
pcw = *(const PCW *)from;
|
||||
trans = ta_fsm[(state<<8) | (pcw.ParaType<<5) | (pcw.obj_ctrl>>2)%32];
|
||||
*(simd256_t *)to = *(const simd256_t *)from;
|
||||
to += 32;
|
||||
from += 32;
|
||||
state = trans;
|
||||
if (trans & 0xF0)
|
||||
{
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while(size>0)
|
||||
{
|
||||
ta_thd_data32_i(data);
|
||||
ta_tad.thd_data = to;
|
||||
ta_cur_state = (ta_state)state;
|
||||
if (handled)
|
||||
ta_handle_cmd(state);
|
||||
|
||||
data+=8;
|
||||
size--;
|
||||
count = (u32)(from - (const u8 *)data) / 32;
|
||||
data += count * 8;
|
||||
size -= count;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ void ta_vtx_SoftReset();
|
||||
|
||||
void DYNACALL ta_vtx_data32(void* data);
|
||||
void ta_vtx_data(u32* data, u32 size);
|
||||
u8 *ta_vtx_room(u32 blocks);
|
||||
// The list that is open (0-4), or -1
|
||||
int ta_vtx_list(void);
|
||||
// The second half of a 64-byte parameter is what comes next
|
||||
|
||||
+34
-2
@@ -32,13 +32,32 @@ static struct { uint32_t vram, src, size; int from_eram; } dma[8];
|
||||
static unsigned dma_count;
|
||||
static bool dma_refuse;
|
||||
|
||||
/* Set: the chip is given room in ta[] itself to make its blocks in, as the
|
||||
* core gives it room in the tile accelerator's buffer. What arrives has to
|
||||
* be the same either way. */
|
||||
static int give_room;
|
||||
static unsigned made_in_place;
|
||||
|
||||
uint32_t *elan_host_ta_room(unsigned blocks)
|
||||
{
|
||||
if (!give_room || ta_count + blocks > TA_MAX)
|
||||
return NULL;
|
||||
return ta[ta_count];
|
||||
}
|
||||
|
||||
void elan_host_ta(const uint32_t *blocks, unsigned count)
|
||||
{
|
||||
for (; count; count--, blocks += 8)
|
||||
{
|
||||
uint32_t pcw = blocks[0];
|
||||
if (ta_count < TA_MAX)
|
||||
memcpy(ta[ta_count++], blocks, 32);
|
||||
{
|
||||
if (blocks == ta[ta_count])
|
||||
made_in_place++;
|
||||
else
|
||||
memcpy(ta[ta_count], blocks, 32);
|
||||
ta_count++;
|
||||
}
|
||||
if (ta_half)
|
||||
{
|
||||
ta_half = 0;
|
||||
@@ -813,6 +832,7 @@ static void test_random_scenes(void)
|
||||
{
|
||||
uint32_t h = 2166136261u;
|
||||
unsigned scene;
|
||||
unsigned in_place_before = made_in_place;
|
||||
|
||||
for (scene = 0; scene < 400; scene++)
|
||||
{
|
||||
@@ -837,7 +857,8 @@ static void test_random_scenes(void)
|
||||
run(from);
|
||||
CHECK(hash_ta(2166136261u) == once);
|
||||
}
|
||||
printf("random scenes: %08x\n", h);
|
||||
printf("random scenes: %08x%s\n", h, give_room ? " (made in the host's buffer)" : "");
|
||||
CHECK(give_room ? made_in_place > in_place_before : made_in_place == in_place_before);
|
||||
/* What they have come to on every build so far - x86-64 and 64-bit
|
||||
* ARM, four at a time and one by one, gcc at every optimisation level
|
||||
* (built as run.sh builds it, with -ffp-contract=off: a compiler that
|
||||
@@ -866,6 +887,17 @@ int main(void)
|
||||
test_deep_links();
|
||||
test_state();
|
||||
test_random_scenes();
|
||||
/* and all of it again with the blocks made where the host takes them from */
|
||||
give_room = 1;
|
||||
test_strip();
|
||||
test_lighting();
|
||||
test_clipping();
|
||||
test_fan();
|
||||
test_passthrough();
|
||||
test_bad_lists();
|
||||
test_deep_links();
|
||||
test_random_scenes();
|
||||
give_room = 0;
|
||||
|
||||
free(ram);
|
||||
if (failures)
|
||||
|
||||
Reference in new issue
Block a user