gfx_display: count what ends each quad batch and show it on the statistics overlay

Every flush point now records why the gathered quads had to go out -
text, texture change, blend group, scissor, a non-gathering draw, a
full batch, or an explicit flush from outside - along with quads,
strips, largest strip, and text calls/bytes per menu frame. Counts are
plain increments on the drawing thread and are published atomically
once per menu frame for the overlay, which adds a MENU UI block while
the menu is up.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-14 13:11:51 +02:00
1 parent b26625232f
commit 02a1b2c23f
5 files changed
+145 -14

No files matched your search

+67 -12
View File
@@ -335,6 +335,17 @@ float gfx_display_get_dpi_scale(
return adjusted_scale;
}
static void gfx_display_flush_impl(gfx_display_t *p_disp);
/* Sends what is gathered and records why it had to go */
static void gfx_display_flush_as(gfx_display_t *p_disp,
enum gfx_display_flush_reason reason)
{
if (p_disp && p_disp->batch_quads)
p_disp->stats.v[GFX_DISPLAY_STAT_FLUSH + reason]++;
gfx_display_flush_impl(p_disp);
}
/* Begin scissoring operation */
void gfx_display_scissor_begin(
gfx_display_t *p_disp,
@@ -345,7 +356,7 @@ void gfx_display_scissor_begin(
{
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
/* What is gathered goes out before this draws */
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_as(disp_get_ptr(), GFX_DISPLAY_FLUSH_SCISSOR);
if (dispctx && dispctx->scissor_begin)
{
if (y < 0)
@@ -417,10 +428,12 @@ static void gfx_display_draw_text_internal(
float scale, bool shadows_enable, float shadow_offset,
bool draw_outside)
{
size_t _len;
struct font_params params;
gfx_display_t *p_disp = disp_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
/* What is gathered goes out before this draws */
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_TEXT);
/* NULL text is a no-op: ozone_draw_footer and similar menu code can
* legitimately reach here with text==NULL for unset/optional fields,
@@ -459,9 +472,13 @@ static void gfx_display_draw_text_internal(
params.drop_alpha = GFX_SHADOW_ALPHA;
}
_len = strlen(text);
p_disp->stats.v[GFX_DISPLAY_STAT_TEXT_CALLS]++;
p_disp->stats.v[GFX_DISPLAY_STAT_TEXT_BYTES] += (unsigned)_len;
if (video_st->poke && video_st->poke->set_osd_msg)
video_st->poke->set_osd_msg(video_st->data,
text, strlen(text), &params, (void*)font);
text, _len, &params, (void*)font);
}
void gfx_display_draw_text(
@@ -572,9 +589,9 @@ static bool gfx_display_batch_add(gfx_display_t *p_disp,
|| p_disp->batch_userdata != userdata
|| p_disp->batch_video_width != video_width
|| p_disp->batch_video_height != video_height))
gfx_display_flush_batch(p_disp);
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_TEXTURE);
if (p_disp->batch_quads >= GFX_DISPLAY_BATCH_QUADS)
gfx_display_flush_batch(p_disp);
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_CAPACITY);
if (!p_disp->batch_mem)
{
@@ -633,6 +650,7 @@ static bool gfx_display_batch_add(gfx_display_t *p_disp,
p_disp->batch_first_h = ph;
}
p_disp->batch_quads++;
p_disp->stats.v[GFX_DISPLAY_STAT_QUADS]++;
p_disp->batch_texture = texture;
p_disp->batch_userdata = userdata;
p_disp->batch_video_width = video_width;
@@ -644,7 +662,7 @@ static bool gfx_display_batch_add(gfx_display_t *p_disp,
* batch. Called before anything else draws, so that what was gathered
* lands under what comes after it, and at the end of a frame so that
* nothing is still waiting when the frame is over. */
void gfx_display_flush_batch(gfx_display_t *p_disp)
static void gfx_display_flush_impl(gfx_display_t *p_disp)
{
gfx_display_ctx_driver_t *dispctx;
gfx_display_ctx_draw_t draw;
@@ -653,6 +671,9 @@ void gfx_display_flush_batch(gfx_display_t *p_disp)
if (!p_disp || !p_disp->batch_quads)
return;
dispctx = p_disp->dispctx;
p_disp->stats.v[GFX_DISPLAY_STAT_BATCHES]++;
if (p_disp->batch_quads > p_disp->stats.v[GFX_DISPLAY_STAT_BATCH_MAX])
p_disp->stats.v[GFX_DISPLAY_STAT_BATCH_MAX] = p_disp->batch_quads;
coords.lut_tex_coord = NULL;
if (p_disp->batch_quads == 1)
{
@@ -699,13 +720,42 @@ void gfx_display_flush_batch(gfx_display_t *p_disp)
}
}
void gfx_display_flush_batch(gfx_display_t *p_disp)
{
if (p_disp && p_disp->batch_quads)
p_disp->stats.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_EXPLICIT]++;
gfx_display_flush_impl(p_disp);
}
void gfx_display_stats_latch(gfx_display_t *p_disp)
{
unsigned i;
if (!p_disp)
return;
for (i = 0; i < GFX_DISPLAY_STAT_LAST; i++)
{
retro_atomic_store_relaxed_int(&p_disp->stats_pub[i],
(int)p_disp->stats.v[i]);
p_disp->stats.v[i] = 0;
}
}
void gfx_display_stats_get(gfx_display_stats_t *out)
{
unsigned i;
gfx_display_t *p_disp = disp_get_ptr();
for (i = 0; i < GFX_DISPLAY_STAT_LAST; i++)
out->v[i] = (unsigned)retro_atomic_load_relaxed_int(
&p_disp->stats_pub[i]);
}
void gfx_display_blend_begin(gfx_display_ctx_driver_t *dispctx,
void *userdata)
{
gfx_display_t *p_disp = disp_get_ptr();
/* What was gathered outside this group goes out under the state it
* was gathered under */
gfx_display_flush_batch(p_disp);
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_BLEND);
if (dispctx && dispctx->blend_begin)
{
dispctx->blend_begin(userdata);
@@ -718,7 +768,7 @@ void gfx_display_blend_end(gfx_display_ctx_driver_t *dispctx,
{
gfx_display_t *p_disp = disp_get_ptr();
/* And what was gathered inside it goes out while it is still on */
gfx_display_flush_batch(p_disp);
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_BLEND);
if (dispctx && dispctx->blend_end)
{
dispctx->blend_end(userdata);
@@ -735,7 +785,7 @@ void gfx_display_draw(gfx_display_ctx_driver_t *dispctx,
gfx_display_ctx_draw_t *draw, void *userdata,
unsigned video_width, unsigned video_height)
{
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_as(disp_get_ptr(), GFX_DISPLAY_FLUSH_DRAW);
if (dispctx && dispctx->draw && draw)
dispctx->draw(draw, userdata, video_width, video_height);
}
@@ -792,7 +842,7 @@ void gfx_display_draw_quad(
draw.x, draw.y, draw.width, draw.height))
return;
gfx_display_flush_batch(p_disp);
gfx_display_flush_as(p_disp, GFX_DISPLAY_FLUSH_DRAW);
if (dispctx->blend_begin)
dispctx->blend_begin(data);
if (dispctx->draw)
@@ -841,7 +891,7 @@ void gfx_display_draw_texture_slice(
};
/* What is gathered goes out before this draws */
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_as(disp_get_ptr(), GFX_DISPLAY_FLUSH_DRAW);
/* Early-out: guard against division by zero from
* zero display dimensions or zero texture dimensions */
@@ -1074,7 +1124,7 @@ void gfx_display_draw_cursor(
struct video_coords coords;
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
/* What is gathered goes out before this draws */
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_as(disp_get_ptr(), GFX_DISPLAY_FLUSH_DRAW);
if (!dispctx)
return;
@@ -1447,6 +1497,11 @@ void gfx_display_init(void)
else
p_disp->flags &= ~GFX_DISP_FLAG_HAS_WINDOWED;
p_dispca->allocated = 0;
{
unsigned i;
for (i = 0; i < GFX_DISPLAY_STAT_LAST; i++)
retro_atomic_int_init(&p_disp->stats_pub[i], 0);
}
}
bool gfx_display_init_first_driver(gfx_display_t *p_disp,
+45
View File
@@ -27,6 +27,7 @@
#include <retro_common_api.h>
#include <formats/image.h>
#include <gfx/math/matrix_4x4.h>
#include <retro_atomic.h>
#include "../retroarch.h"
#include "../gfx/font_driver.h"
@@ -187,6 +188,35 @@ typedef struct gfx_display_ctx_powerstate
bool charging;
} gfx_display_ctx_powerstate_t;
/* Why a gathered batch of quads had to go out. */
enum gfx_display_flush_reason
{
GFX_DISPLAY_FLUSH_TEXT = 0, /* text drawn */
GFX_DISPLAY_FLUSH_TEXTURE, /* quad with another texture or frame */
GFX_DISPLAY_FLUSH_BLEND, /* blend group begun or ended */
GFX_DISPLAY_FLUSH_SCISSOR, /* scissor begun or ended */
GFX_DISPLAY_FLUSH_DRAW, /* a draw that does not gather */
GFX_DISPLAY_FLUSH_CAPACITY, /* batch full */
GFX_DISPLAY_FLUSH_EXPLICIT, /* gfx_display_flush_batch() from outside */
GFX_DISPLAY_FLUSH_LAST
};
enum gfx_display_stat
{
GFX_DISPLAY_STAT_QUADS = 0, /* quads gathered */
GFX_DISPLAY_STAT_BATCHES, /* strips sent out */
GFX_DISPLAY_STAT_BATCH_MAX, /* quads in the largest strip */
GFX_DISPLAY_STAT_TEXT_CALLS, /* strings handed to the font driver */
GFX_DISPLAY_STAT_TEXT_BYTES, /* bytes of text in them */
GFX_DISPLAY_STAT_FLUSH, /* one per enum gfx_display_flush_reason */
GFX_DISPLAY_STAT_LAST = GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_LAST
};
typedef struct gfx_display_stats
{
unsigned v[GFX_DISPLAY_STAT_LAST];
} gfx_display_stats_t;
struct gfx_display
{
gfx_display_ctx_driver_t *dispctx;
@@ -234,6 +264,14 @@ struct gfx_display
bool blend_on;
uint8_t flags;
/* What the batch did during the menu frame being drawn, counted
* where it happens on the drawing thread and published as a whole
* by gfx_display_stats_latch() once the frame is over. The
* statistics overlay reads the published copy from the main
* thread, so that copy is atomic and the live one is not. */
gfx_display_stats_t stats;
retro_atomic_int_t stats_pub[GFX_DISPLAY_STAT_LAST];
};
void gfx_display_free(void);
@@ -306,6 +344,13 @@ void gfx_display_draw_bg(
* that were asked for before it. */
void gfx_display_flush_batch(gfx_display_t *p_disp);
/* Publishes the counts of the menu frame just drawn and starts the
* next; called by the drawing thread once the menu frame is over. */
void gfx_display_stats_latch(gfx_display_t *p_disp);
/* The counts of the last published menu frame, safe from any thread. */
void gfx_display_stats_get(gfx_display_stats_t *out);
/* Blending, counted, so that what is gathered knows whether it is
* inside a group that has already turned blending on. Every caller
* goes through these rather than the driver's own. */
+29
View File
@@ -6406,6 +6406,35 @@ void video_driver_frame(const void *data, unsigned width,
video_st->frame_count,
video_st->frame_drop_count);
#ifdef HAVE_MENU
if (menu_is_alive)
{
/* What the menu's quad batching did in the last menu
* frame drawn: how many strips its quads went out in,
* and what ended each strip. */
gfx_display_stats_t ui;
gfx_display_stats_get(&ui);
__len += snprintf(video_st->stat_text + __len, sizeof(video_st->stat_text) - __len,
"MENU UI\n"
" Quads: %5u in %u strips (max %u)\n"
" Ended by text %u tex %u blend %u sciss %u draw %u full %u end %u\n"
" Text: %5u calls, %u bytes\n"
,
ui.v[GFX_DISPLAY_STAT_QUADS],
ui.v[GFX_DISPLAY_STAT_BATCHES],
ui.v[GFX_DISPLAY_STAT_BATCH_MAX],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_TEXT],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_TEXTURE],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_BLEND],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_SCISSOR],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_DRAW],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_CAPACITY],
ui.v[GFX_DISPLAY_STAT_FLUSH + GFX_DISPLAY_FLUSH_EXPLICIT],
ui.v[GFX_DISPLAY_STAT_TEXT_CALLS],
ui.v[GFX_DISPLAY_STAT_TEXT_BYTES]);
}
#endif
/* Split from the block above: a single concatenated format
* literal exceeded the 509-byte minimum ISO C90 guarantees
* (-Werror=overlength-strings in the C89 lane). */
+1 -1
View File
@@ -1181,7 +1181,7 @@ typedef struct
retro_atomic_int_t output_size_packed;
/* Where the statistics overlay's text is built, for the frame
* descriptor to point at (video_frame_info_t::stat_text) */
char stat_text[1024];
char stat_text[1280];
#ifdef HAVE_OVERLAY
/* The active overlay's viewport override, published by the main
* thread whenever the active overlay changes
+3 -1
View File
@@ -4548,10 +4548,12 @@ void menu_driver_frame(bool menu_is_alive, video_frame_info_t *video_info)
struct menu_state *menu_st = &menu_driver_state;
if (menu_is_alive && menu_st->driver_ctx->frame)
{
gfx_display_t *p_disp = disp_get_ptr();
menu_st->driver_ctx->frame(menu_st->userdata, video_info);
/* Nothing the menu gathered may still be waiting when the frame
* it belongs to is over */
gfx_display_flush_batch(disp_get_ptr());
gfx_display_flush_batch(p_disp);
gfx_display_stats_latch(p_disp);
}
}