font: keep the cells frames still on the GPU draw from

A glyph cell looked up in the current frame is not handed to another
codepoint until the frame is over. Where the CPU writes cells into
memory the GPU samples - the RSX's atlas in texture memory, the
textures GXM and GX2 copy cells into - the frames before may still be
drawing from them, and a cell reused for a new glyph shows up in their
place. A consumer now says how many earlier frames that can be
(font_atlas.inflight): the RSX one, GXM as many as swaps may be
pending, GX2 the frame swapped last, and a cell any of them looked up
waits until they are over. The default, zero, is the rule as it was.

font_frame_test fills the cache in one frame with two in flight: the
next two frames take none of its cells, and the third finds every cell
free and the first frame's glyph where it was.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-04 17:33:48 +01:00
1 parent 61c298b7fb
commit 688cc53eda
6 files changed
+76 -12

No files matched your search

+3
View File
@@ -745,6 +745,9 @@ static void* gx2_font_init(void* data, const char* font_path,
font->atlas = font->font_driver->get_atlas(font->font_data);
/* The atlas may grow, kept modest for the 32 MB of MEM1 */
font->atlas->max_dims = VIDEO_SCALE_PACK(1024, 1024);
/* Cells are copied into the texture the GPU samples, and the frame
* swapped last may still be drawing from it */
font->atlas->inflight = 1;
if (!gx2_font_make_texture(font))
{
font->font_driver->free(font->font_data);
+3
View File
@@ -1756,6 +1756,9 @@ static void *gxm_font_init(void *data,
/* The atlas may grow; the texture follows it */
atlas->max_dims = VIDEO_SCALE_PACK(2048, 2048);
/* Cells are copied into the texture the GPU samples, and as many
* frames as swaps may be pending can still be drawing from it */
atlas->inflight = DISPLAY_MAX_PENDING_SWAPS;
if (!(font->texture = gxm_font_make_texture(atlas)))
goto error;
+3
View File
@@ -830,6 +830,9 @@ static void *rsx_font_init(void *data,
/* The atlas may grow; the texture follows it */
font->atlas->max_dims = VIDEO_SCALE_PACK(2048, 2048);
/* Its cells are written where the RSX samples them, and the frame
* before may still be drawing: one per buffer the flip waits on */
font->atlas->inflight = RSX_MAX_BUFFERS - 1;
font->tex_dims = VIDEO_SCALE_PACK(font->atlas->width,
font->atlas->height);
/* The atlas kept in texture memory, or copied into it */
+21 -12
View File
@@ -449,7 +449,7 @@ typedef struct font_cache
unsigned cand_count;
unsigned cand_next;
unsigned cand_frame;
/* The frame a search found every cell looked up in, when full_set:
/* The frame a search found every cell busy in, when full_set:
* no cell is free again before the next one */
unsigned full_frame;
bool full_set;
@@ -640,9 +640,10 @@ static void *font_cache_fallback_face(font_cache_t *c, int id,
/* Advanced once a video frame by font_driver_frame_begin(). A cell
* looked up in the current frame may already have glyphs queued from
* it, so it is not handed to another codepoint until the frame is over:
* reusing it would draw the new glyph in the earlier one's place. 0
* until the first frame, which leaves plain LRU in place for a cache
* used outside the video loop. It is only ever compared, never used
* reusing it would draw the new glyph in the earlier one's place. The
* same holds for the atlas's inflight frames before it, which the GPU
* may still be drawing. 0 until the first frame, which leaves plain LRU
* in place for a cache used outside the video loop. It is only ever compared, never used
* to publish anything, so it is read relaxed: a plain load on every
* backend, where the glyph lookup reads it once a glyph. */
static retro_atomic_int_t font_frame_epoch;
@@ -652,8 +653,14 @@ void font_driver_frame_begin(void)
retro_atomic_fetch_add_int(&font_frame_epoch, 1);
}
/* Searches every cell for the least recently used one not looked up
* in @frame, keeping the oldest few in c->cand for the misses after */
/* Whether @s was looked up in @frame or in one of the atlas's inflight
* frames before it; 0 for a cell never looked up in a frame */
#define FONT_CACHE_BUSY(c, s, frame) \
( (frame) && (s)->last_frame \
&& (unsigned)((frame) - (s)->last_frame) <= (c)->atlas.inflight)
/* Searches every cell for the least recently used one not busy in
* @frame, keeping the oldest few in c->cand for the misses after */
static font_cache_slot_t *font_cache_search(font_cache_t *c, unsigned frame)
{
unsigned i, b, k;
@@ -667,7 +674,7 @@ static font_cache_slot_t *font_cache_search(font_cache_t *c, unsigned frame)
{
/* Unsigned subtraction handles the counter wrapping */
unsigned age = c->usage_counter - blk[i].last_used;
if (frame && blk[i].last_frame == frame)
if (FONT_CACHE_BUSY(c, &blk[i], frame))
continue;
if ( n == FONT_CACHE_CANDIDATES
&& age <= c->usage_counter - c->cand_used[n - 1])
@@ -690,8 +697,8 @@ static font_cache_slot_t *font_cache_search(font_cache_t *c, unsigned frame)
return n ? c->cand[0] : NULL;
}
/* The least recently used cell not looked up in this frame, taken out
* of the hash; NULL when every cell is in use this frame */
/* The least recently used cell not busy in this frame, taken out of
* the hash; NULL when every cell is */
static font_cache_slot_t *font_cache_take_slot(font_cache_t *c)
{
unsigned frame = (unsigned)retro_atomic_load_relaxed_int(
@@ -708,7 +715,7 @@ static font_cache_slot_t *font_cache_take_slot(font_cache_t *c)
{
font_cache_slot_t *s = c->cand[c->cand_next];
unsigned used = c->cand_used[c->cand_next++];
if (s->last_used == used && !(frame && s->last_frame == frame))
if (s->last_used == used && !FONT_CACHE_BUSY(c, s, frame))
{
victim = s;
break;
@@ -910,9 +917,11 @@ VIDEO_NOINLINE static const struct font_glyph *font_cache_miss(
if (!rast->render_glyph(face, code, gi, dst, c->atlas.width,
c->cell_dims, c->atlas.format, &slot->glyph))
{
/* Nothing to draw: the cell goes back as the oldest there is */
/* Nothing to draw: the cell goes back as the oldest there is,
* free again at once - no frame has drawn from it since it was
* taken */
slot->last_used = c->usage_counter - 0x80000000u;
slot->last_frame--;
slot->last_frame = 0;
c->cand_count = 0;
return NULL;
}
+7
View File
@@ -541,6 +541,13 @@ struct font_atlas
* keeps the atlas at its first size. Cells only ever get added: a
* glyph's offsets never change. */
unsigned max_dims;
/* Set by the consumer whose GPU may still be drawing earlier frames
* from memory the CPU writes glyphs into - a texture sampled in
* place, or one the CPU copies cells into: how many frames before
* the current one may still be reading it. A cell looked up in any
* of them is not given to another codepoint. Zero, the default:
* only the current frame's cells are kept. */
unsigned inflight;
enum font_atlas_format format;
bool dirty;
};
@@ -125,6 +125,45 @@ int main(void)
drv->free(h);
/* A consumer whose GPU may still draw the two frames before: the
* cells those looked up are not given to other codepoints until both
* are over, and a glyph drawn three frames back is still where it
* was */
if (!font_renderer_create_default(&drv, &h, dejavu, 16,
FONT_ATLAS_FORMAT_A8))
return 1;
{
struct font_atlas *atlas = drv->get_atlas(h);
const struct font_glyph *g;
unsigned pos, i, missing = 0;
atlas->inflight = 2;
/* The glyphs drawn as the font was made count as looked up in
* the frame it was made in: past them */
for (i = 0; i < 3; i++)
font_driver_frame_begin();
g = drv->get_glyph(h, 0x0100);
pos = g ? g->atlas_pos : 0;
for (i = 1; i < 256; i++)
if (!drv->get_glyph(h, 0x0100 + i))
missing++;
CHECK(g && missing == 0, "inflight: a frame fills every cell");
font_driver_frame_begin();
undrawn = frame(drv, h, 0x0400, &moved);
CHECK(undrawn == 299, "inflight: the frame after takes none of its cells");
font_driver_frame_begin();
undrawn = frame(drv, h, 0x0400, &moved);
CHECK(undrawn == 299, "inflight: nor the one after that");
font_driver_frame_begin();
g = drv->get_glyph(h, 0x0100);
CHECK(g && g->atlas_pos == pos, "inflight: its glyph is where it was");
undrawn = frame(drv, h, 0x0400, &moved);
CHECK(undrawn == 300 - 255,
"inflight: three frames on, every cell but the one in use");
printf(" inflight 2: %u of 300 undrawn once the frames are over\n",
undrawn);
}
drv->free(h);
/* A consumer that can take a bigger texture: the frame after one
* that ran out finds the atlas grown, every earlier glyph where it
* was, and draws all it asks for */