mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
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:
1 parent
5f72b6f427
commit
2f9cebe8bc
3 files changed
+157
-9
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user