webm: emit native 10-bit XRGB2101010 output when the frontend accepts it

Completes the 10-bit chain end to end: with the XRGB2101010 ABI and the
four native-10-bit drivers in place, the webm core can now hand the
frontend a 10-bit frame instead of always narrowing to 8.

retro_load_game tries SET_PIXEL_FORMAT(XRGB2101010) first and falls back
to XRGB8888. When 10-bit is accepted and the VP9 stream is profile 2
(10-bit), a new rwebm_video_blit_i420_10bit emits packed XRGB2101010; an
8-bit stream (VP8, or VP9 profile 0) is blitted to XRGB8888 as before and
then widened 8 -> 10 in place so the buffer matches the announced format.

Colour correctness: the 10-bit blit produces the SAME SDR-encoded colour
as the existing _hbd path - PQ/HDR10 is still hable-tone-mapped, BT.2020
-> 709 gamut, sRGB transfer - only emitted at 10-bit precision via a new
10-bit sRGB output LUT, so gradients band less. It deliberately does NOT
pass PQ through: the drivers' HDR pipeline inverse-tone-maps an SDR
source up to the display's HDR range, so it expects SDR-encoded input;
feeding it PQ would be wrong. This was the subtle trap here - an earlier
draft that skipped the transfer to 'preserve HDR' would have fed
PQ-encoded values into an SDR-expecting pipeline. Verified the 10-bit
output narrowed back to 8-bit matches the _hbd path within +/-1 (rounding
only) across random SDR and PQ input, confirming identical colourspace at
higher precision, and that the 8 -> 10 widen is reversible and opaque.

No behaviour change for frontends that reject XRGB2101010 (the vast
majority until the driver stages propagate): they get XRGB8888 exactly as
before. The new blit is unguarded (pure RGB packing, no VP9 dependency);
the file builds with and without HAVE_RVP9, C89 and aarch64 clean.

With the driver stages, a 10-bit VP9/HDR10 webm now reaches a native
10-bit surface without the 8-bit narrowing round-trip that motivated the
whole effort.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-07-18 05:45:37 +02:00
1 parent 5f72b6f427
commit 2f9cebe8bc
3 files changed
+157 -9

No files matched your search

+61 -8
View File
@@ -109,6 +109,7 @@ typedef struct
int aeof;
int64_t vpts_ns; /* pts of the last presented frame */
#endif
int pix10; /* frontend accepted XRGB2101010 */
} webm_player_t;
static webm_player_t webm_player;
@@ -146,6 +147,31 @@ static INLINE uint32_t webm_yuv_px(int y, int u, int v, const int16_t *k)
| (uint32_t)b;
}
/* Widen an already-blitted XRGB8888 buffer to XRGB2101010 in place (each
* channel 8 -> 10 bits via << 2). Used when the frontend was told the format
* is XRGB2101010 but the decoded stream turned out to be 8-bit (VP8, or VP9
* profile 0), so the presented buffer matches the announced format. */
static void webm_expand_8888_to_2101010(uint32_t *dst, unsigned stride,
unsigned w, unsigned h)
{
unsigned yy, xx;
for (yy = 0; yy < h; yy++)
{
uint32_t *dr = dst + (size_t)yy * stride;
for (xx = 0; xx < w; xx++)
{
uint32_t px = dr[xx];
uint32_t r8 = (px >> 16) & 0xff;
uint32_t g8 = (px >> 8) & 0xff;
uint32_t b8 = px & 0xff;
dr[xx] = ((r8 << 2) << 20)
| ((g8 << 2) << 10)
| (b8 << 2)
| 0xC0000000u;
}
}
}
static void webm_blit_i420(uint32_t *dst, unsigned w, unsigned h,
const uint8_t *y, int ys, const uint8_t *u, const uint8_t *v, int uvs,
unsigned matrix)
@@ -220,19 +246,30 @@ static int webm_present_vp9(webm_player_t *p, int show)
if (p->vp9->hd.bit_depth == 10)
{
const rwebm_track *ct = rwebm_get_track(p->webm, p->vtrack);
rwebm_video_blit_i420_hbd(p->fb, p->width, w, h,
(const uint16_t*)fb->y, p->vp9->ys,
(const uint16_t*)fb->u, (const uint16_t*)fb->v, p->vp9->uvs,
ct ? ct->matrix_coefficients : 0,
ct ? ct->transfer_characteristics : 0,
ct ? ct->colour_range : 0,
ct ? ct->max_cll : 0, 0);
if (p->pix10)
rwebm_video_blit_i420_10bit(p->fb, p->width, w, h,
(const uint16_t*)fb->y, p->vp9->ys,
(const uint16_t*)fb->u, (const uint16_t*)fb->v, p->vp9->uvs,
ct ? ct->matrix_coefficients : 0,
ct ? ct->transfer_characteristics : 0,
ct ? ct->colour_range : 0,
ct ? ct->max_cll : 0);
else
rwebm_video_blit_i420_hbd(p->fb, p->width, w, h,
(const uint16_t*)fb->y, p->vp9->ys,
(const uint16_t*)fb->u, (const uint16_t*)fb->v, p->vp9->uvs,
ct ? ct->matrix_coefficients : 0,
ct ? ct->transfer_characteristics : 0,
ct ? ct->colour_range : 0,
ct ? ct->max_cll : 0, 0);
}
else
{
const rwebm_track *ct = rwebm_get_track(p->webm, p->vtrack);
webm_blit_i420(p->fb, w, h, fb->y, p->vp9->ys, fb->u, fb->v,
p->vp9->uvs, ct ? ct->matrix_coefficients : 0);
if (p->pix10)
webm_expand_8888_to_2101010(p->fb, p->width, w, h);
}
WEBM_CORE_PREFIX(video_cb)(p->fb, w, h, p->width * sizeof(uint32_t));
return 1;
@@ -316,6 +353,9 @@ static int webm_decode_packet(webm_player_t *p, const rwebm_packet *pkt)
const rwebm_track *ct = rwebm_get_track(p->webm, p->vtrack);
webm_blit_i420(p->fb, (unsigned)w, (unsigned)h, y, ys, u, v, uvs,
ct ? ct->matrix_coefficients : 0);
if (p->pix10)
webm_expand_8888_to_2101010(p->fb, p->width,
(unsigned)w, (unsigned)h);
}
WEBM_CORE_PREFIX(video_cb)(p->fb, (unsigned)w, (unsigned)h,
p->width * sizeof(uint32_t));
@@ -560,6 +600,8 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
{
webm_player_t *p = &webm_player;
enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888;
enum retro_pixel_format fmt10 = RETRO_PIXEL_FORMAT_XRGB2101010;
int want10 = 0;
const rwebm_track *vt = NULL;
int64_t len = 0;
int i, nvpkts;
@@ -567,7 +609,17 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
if (!info || !info->path)
return false;
if (!WEBM_CORE_PREFIX(environ_cb)(
/* Prefer native 10-bit output: if the frontend accepts XRGB2101010 we emit
* 10-bit (still SDR-encoded - the frontend HDR path inverse-tone-maps that
* to the display) so 10-bit sources band less; otherwise fall back to
* XRGB8888. The frontend accepts XRGB2101010 even when it will
* down-convert, so this is best-effort - the precision only survives on a
* driver with native 10-bit support. */
if (WEBM_CORE_PREFIX(environ_cb)(
RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt10))
want10 = 1;
else if (!WEBM_CORE_PREFIX(environ_cb)(
RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt))
{
if (WEBM_CORE_PREFIX(log_cb))
@@ -577,6 +629,7 @@ bool WEBM_CORE_PREFIX(retro_load_game)(const struct retro_game_info *info)
}
memset(p, 0, sizeof(*p));
p->pix10 = want10;
if (!filestream_read_file(info->path, (void**)&p->file_buf, &len)
|| len <= 0)
+83 -1
View File
@@ -331,6 +331,7 @@ static void rwebm_video_blit_i420(uint32_t *dst, unsigned dst_stride,
/* ------------------------------------------------------------------ */
static uint16_t rwebm_hbd_pq_lut[1024]; /* PQ' -> tone-mapped linear */
static uint8_t rwebm_hbd_out_lut[8193]; /* linear -> sRGB' 8-bit */
static uint16_t rwebm_hbd_out_lut10[8193];/* linear -> sRGB' 10-bit */
static int rwebm_hbd_lut_ready;
static unsigned rwebm_hbd_lut_peak; /* nits the PQ LUT was built for */
@@ -380,7 +381,8 @@ static void rwebm_hbd_init_luts(unsigned peak_cll)
double lin = i / 8192.0;
double s = lin <= 0.0031308
? 12.92 * lin : 1.055 * pow(lin, 1.0 / 2.4) - 0.055;
rwebm_hbd_out_lut[i] = (uint8_t)(s * 255.0 + 0.5);
rwebm_hbd_out_lut[i] = (uint8_t)(s * 255.0 + 0.5);
rwebm_hbd_out_lut10[i] = (uint16_t)(s * 1023.0 + 0.5);
}
rwebm_hbd_lut_ready = 1;
rwebm_hbd_lut_peak = peak_cll;
@@ -474,6 +476,86 @@ void rwebm_video_blit_i420_hbd(uint32_t *dst, unsigned dst_stride,
}
}
/* ------------------------------------------------------------------ */
/* Native 10-bit I420 -> XRGB2101010 (packed 2-10-10-10, native endian) */
/* Produces the SAME SDR-encoded colour as the _hbd path - PQ/HDR10 is */
/* tone-mapped (hable), BT.2020 -> 709 gamut, sRGB transfer - but keeps */
/* 10-bit precision instead of narrowing to 8, so gradients are smoother */
/* with less banding. The frontend's HDR pipeline expects an SDR-encoded */
/* source (it inverse-tone-maps SDR -> HDR for the display), so this */
/* deliberately does NOT pass PQ through; it only raises the source bit */
/* depth. Used when the frontend accepts RETRO_PIXEL_FORMAT_XRGB2101010. */
/* ------------------------------------------------------------------ */
void rwebm_video_blit_i420_10bit(uint32_t *dst, unsigned dst_stride,
unsigned w, unsigned h, const uint16_t *y, int ys,
const uint16_t *u, const uint16_t *v, int uvs,
unsigned matrix, unsigned transfer, unsigned range,
unsigned max_cll)
{
int re, gd, ge, bd;
int is_pq = (transfer == 16);
int full = (range == 2);
unsigned j, i;
if (!rwebm_hbd_lut_ready || (is_pq && max_cll != rwebm_hbd_lut_peak))
rwebm_hbd_init_luts(max_cll);
rwebm_hbd_matrix(matrix ? matrix : (is_pq ? 9u : 1u), &re, &gd, &ge, &bd);
for (j = 0; j < h; j++)
{
const uint16_t *yr = y + (size_t)j * ys;
const uint16_t *ur = u + (size_t)(j >> 1) * uvs;
const uint16_t *vr = v + (size_t)(j >> 1) * uvs;
uint32_t *dr = dst + (size_t)j * dst_stride;
for (i = 0; i < w; i++)
{
int c, d, e, r, g, b;
if (full)
{
c = ((int)yr[i] * 877) >> 10;
d = (((int)ur[i >> 1] - 512) * 897) >> 10;
e = (((int)vr[i >> 1] - 512) * 897) >> 10;
}
else
{
c = (int)yr[i] - 64;
d = (int)ur[i >> 1] - 512;
e = (int)vr[i >> 1] - 512;
}
r = (298 * c + re * e + 128) >> 8;
g = (298 * c - gd * d - ge * e + 128) >> 8;
b = (298 * c + bd * d + 128) >> 8;
if (r < 0) r = 0; else if (r > 1023) r = 1023;
if (g < 0) g = 0; else if (g > 1023) g = 1023;
if (b < 0) b = 0; else if (b > 1023) b = 1023;
if (is_pq)
{
/* tone-mapped linear, then 2020 -> 709 gamut, then sRGB,
* emitted at 10-bit precision. */
int lr = rwebm_hbd_pq_lut[r];
int lg = rwebm_hbd_pq_lut[g];
int lb = rwebm_hbd_pq_lut[b];
int xr = (6801 * lr - 2407 * lg - 298 * lb) >> 12;
int xg = ( -510 * lr + 4640 * lg - 34 * lb) >> 12;
int xb = ( -75 * lr - 412 * lg + 4583 * lb) >> 12;
if (xr < 0) xr = 0; else if (xr > 65535) xr = 65535;
if (xg < 0) xg = 0; else if (xg > 65535) xg = 65535;
if (xb < 0) xb = 0; else if (xb > 65535) xb = 65535;
r = rwebm_hbd_out_lut10[xr >> 3];
g = rwebm_hbd_out_lut10[xg >> 3];
b = rwebm_hbd_out_lut10[xb >> 3];
}
/* non-PQ: r/g/b are already 10-bit SDR-encoded, keep as-is */
/* Pack XRGB2101010: bits [29:20]=R [19:10]=G [9:0]=B; top 2
* bits set so the value reads opaque under an A2 interpretation. */
dr[i] = ((uint32_t)r << 20)
| ((uint32_t)g << 10)
| (uint32_t)b
| 0xC0000000u;
}
}
}
/* ------------------------------------------------------------------ */
/* VP9 superframe index (parsed from the trailing marker byte). */
/* Returns the number of sub-frames and their sizes; 1 = whole chunk. */
@@ -58,6 +58,19 @@ void rwebm_video_blit_i420_hbd(uint32_t *dst, unsigned dst_stride,
unsigned matrix, unsigned transfer, unsigned range,
unsigned max_cll, int abgr);
/* Native 10-bit variant: same SDR-encoded colour as the _hbd path (PQ/HDR10
* tone-mapped, BT.2020 -> 709 gamut, sRGB transfer) but emitted at 10-bit
* precision into packed XRGB2101010 (bits [29:20]=R [19:10]=G [9:0]=B, native
* endian) instead of narrowing to 8 bits, so gradients band less. Used when
* the frontend accepts RETRO_PIXEL_FORMAT_XRGB2101010. The frontend HDR path
* expects an SDR-encoded source, so this raises bit depth only and does not
* pass PQ through. */
void rwebm_video_blit_i420_10bit(uint32_t *dst, unsigned dst_stride,
unsigned w, unsigned h, const uint16_t *y, int ys,
const uint16_t *u, const uint16_t *v, int uvs,
unsigned matrix, unsigned transfer, unsigned range,
unsigned max_cll);
bool rwebm_video_set_buf_ptr(rwebm_video_t *webm, void *data, size_t len);
/* Decodes the first displayed frame of the first supported video track