From 23f6675eed81354857f3d05838948d94bb81a8ff Mon Sep 17 00:00:00 2001 From: bjornstahl Date: Sat, 16 Sep 2023 03:18:28 +0200 Subject: [PATCH] (core/lua/shmif) wire up image_metadata This should provide most of the missing pieces for ensuring both a path from a shmif consumer signalling HDR capability, annotating frame with metadata and having that persist across the stack. What is mainly missing for testing this for real is accessing the metadata from Lua space (only really needed if you manually need to composite / tone-map different metadata- sourced images in the same pipeline. The rest would be to simply add the properties to the atomic modeset when a vstore with metadata set is mapped to an output. --- CHANGELOG.md | 1 + doc/image_metadata.lua | 33 +++++++++++++++++ doc/rendertarget_reconfigure.lua | 39 ++++++++++++++------ src/engine/arcan_frameserver.c | 21 +++++++++++ src/engine/arcan_lua.c | 63 +++++++++++++++++++++++++++++++- src/platform/egl-dri/video.c | 25 +++++++------ src/platform/platform_types.h | 14 +++++++ src/shmif/arcan_shmif_sub.c | 6 +-- src/shmif/arcan_shmif_sub.h | 41 ++++++--------------- 9 files changed, 186 insertions(+), 57 deletions(-) create mode 100644 doc/image_metadata.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index a532d75b4..f4271afd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * open\_nonblock can now adopt an existing iostream into a target vid * input\_remap\_translation overloaded form for serializing backing keymap * clockreq no longer forwarded for frameserver event handler + * image\_metadata added for annotating a vid used when streaming/sharing/scanout HDR contents ## Core * respect border attribute in text rasteriser diff --git a/doc/image_metadata.lua b/doc/image_metadata.lua new file mode 100644 index 000000000..f432e867d --- /dev/null +++ b/doc/image_metadata.lua @@ -0,0 +1,33 @@ +-- image_metadata +-- @short: Set / update image contents metadata +-- @inargs: vid:tgt, string:model=drmv1, numtbl[8]:coords, number:mmin, number:mmax, number:cll, number:fll, string:eotf +-- @outargs: bool:ok +-- @longdescr: +-- HDR rendering, computer vision, compression and similar processes all can +-- benefit about added information about what the pixels represents or other +-- kind of contextual detail such as motion vectors. +-- +-- This function is used to attach/update/define a metadata model for the +-- target vid. The currently support model is "drmv1" used for HDR scanout +-- if the *tgt* is mapped to a display supporting it, the metadata will be +-- be forwarded to that screen accordingly. +-- +-- The arguments for *model=drmv1* are 8 coordinates for red, green, blue +-- chromacities and whitepoint. These coordinates will be clamped to range from +-- 0 to 1.3107. +-- +-- *mmin* sets the master minimum luminance, in cd/m2 from 1 to 65535. +-- *mmax* sets the master max luminance, in cd/m2 from 1 to 65535. +-- *cll* sets the max content light level and *fll* the frame average light +-- level, both in cd/m2 from 1 to 65535. +-- +-- The *eotf* (electro-optical transfer function) should be one out of 'sdr' +-- (sRGB), 'hdr' (linear), 'pq', 'pq-inv' or 'bt709'. +-- +-- @group: image +-- @cfunction: imagemetadata +-- @flags: debugbuild +function main() +#ifdef MAIN +#endif +end diff --git a/doc/rendertarget_reconfigure.lua b/doc/rendertarget_reconfigure.lua index 2efc6e64e..4cc642fad 100644 --- a/doc/rendertarget_reconfigure.lua +++ b/doc/rendertarget_reconfigure.lua @@ -1,17 +1,34 @@ -- rendertarget_reconfigure --- @short: Change the output density on a rendertarget +-- @short: Change the output density or colour properties of a rendertarget -- @inargs: vid:rtgt, float:hppcm, float:vppcm +-- @inargs: vid:rtgt, tbl:metadata -- @outargs: --- @longdescr: Vectorized assets that are sized in physical units --- e.g. pt (1/72th of an inch) like with render_text, the engine needs --- needs knowledge of the target output before drawing. By default, this is --- some display-dependent initial size, accessible through the global --- constants HPPCM and VPPCM, or 38.4 when those cannot be found. When --- targeting a display, locally or remote, that has a different density --- it is typically advised to update the rendertarget pipeline that gets --- mapped to that output using this function. Whenever an asset gets created --- or attached to a rendertarget, it will rerastered to match the density --- of the rendertarget. +-- @longdescr: +-- Two major properties of a rendertarget are the intended output density +-- and the targetted colour space. +-- +-- For vectorized assets that are sized in physical units e.g. pt (1/72th of an +-- inch) like with render_text, the engine needs knowledge of the target output +-- before drawing. By default, this is some display-dependent initial size, +-- accessible through the global constants HPPCM and VPPCM, or 38.4 when those +-- cannot be found. When targeting a display, locally or remote, that has a +-- different density it is typically advised to update the rendertarget +-- pipeline that gets mapped to that output using this function. Whenever an +-- asset gets created or attached to a rendertarget, it will be rerastered to +-- match the density of the rendertarget. +-- +-- For HDR composition and rendering, you typically need to provide further +-- metadata about light levels and so on. This can be set through the metadata +-- table which accepts the following fields: {encoding, whitepoint, levels, +-- lumarange, primaries}. +-- +-- @tblent: string:encoding, "itu-r", "bt.601", "bt.709", "bt.2020", "YCBCr" +-- @tblent: table:whitepoint[2], x/y coordinates ranging from 0..1, 0..1 +-- @tblent: table:primaries[6], x/y coordinates of red, green, blue primaries +-- @tblent: number:max_frame_average, in nits, caps at 65535. +-- @tblent: number:max_content_level, in nits, caps at 65535. +-- @tblent: table:mastering_levels[2], in nits, caps at 65535. +-- -- @group: targetcontrol -- @cfunction: renderreconf -- @external: yes diff --git a/src/engine/arcan_frameserver.c b/src/engine/arcan_frameserver.c index 09ad7cc4f..2ec602393 100644 --- a/src/engine/arcan_frameserver.c +++ b/src/engine/arcan_frameserver.c @@ -269,6 +269,27 @@ static bool push_buffer(arcan_frameserver* src, vready = (vready <= 0 || vready > src->vbuf_cnt) ? 0 : vready - 1; shmif_pixel* buf = src->vbufs[vready]; +/* If the HDR subprotocol is enabled, verify and translate into store metadata + * - explicitly map the metadata format. There is only the one to chose from + * right now, but it wouldn't be surprising if that changes. */ + if (src->desc.aext.hdr){ + struct arcan_shmif_hdr fc = *src->desc.aext.hdr; + store->hdr.model = 1; + store->hdr.drm = (struct drm_hdr_meta){ + .eotf = fc.drm.eotf, + .rx = fc.drm.rx, + .ry = fc.drm.ry, + .gx = fc.drm.gx, + .gy = fc.drm.gy, + .bx = fc.drm.bx, + .by = fc.drm.by, + .wpx = fc.drm.wpx, + .wpy = fc.drm.wpy, + .cll = fc.drm.cll_max, + .fll = fc.drm.fll_max + }; + } + /* Need to do this check here as-well as in the regular frameserver tick * control because the backing store might have changed somehwere else. */ if (src->desc.width != store->w || src->desc.height != store->h || diff --git a/src/engine/arcan_lua.c b/src/engine/arcan_lua.c index 6f25f46e7..2d6f62afb 100644 --- a/src/engine/arcan_lua.c +++ b/src/engine/arcan_lua.c @@ -4553,9 +4553,8 @@ bool arcan_lua_pushevent(lua_State* ctx, arcan_event* ev) } else if (ev->ext.netstate.state == 0) tblbool(ctx, "lost", true, top); - else { + else tblbool(ctx, "bad", true, top); - } if (ev->ext.netstate.type & 1) tblbool(ctx, "source", true, top); @@ -9739,6 +9738,65 @@ enum arcan_ffunc_rv arcan_lua_proctarget FFUNC_HEAD return 0; } +static int imagemetadata(lua_State* ctx) +{ + LUA_TRACE("image_metadata"); + arcan_vobject* vobj; + luaL_checkvid(ctx, 1, &vobj); + if (vobj->vstore->txmapped != TXSTATE_TEX2D){ + lua_pushboolean(ctx, false); + LUA_ETRACE("image_metadata", "storage_type mismatch", 1); + } + + const char* model = luaL_checkstring(ctx, 2); + if (strcmp(model, "drmv1") != 0){ + lua_pushboolean(ctx, false); + LUA_ETRACE("image_metadata", "metadata model missing", 1); + } + + if (lua_type(ctx, 3) != LUA_TTABLE){ + lua_pushboolean(ctx, false); + arcan_fatal("image_metadata(, , >tbl< ) expected table"); + LUA_ETRACE("image_metadata", "expected table for coordinates", 1); + } + + int ncords = lua_rawlen(ctx, 3); + if (ncords < 8){ + arcan_fatal("image_metadata(), wrong coordinate set"); + lua_pushboolean(ctx, false); + LUA_ETRACE("image_metadata", "expected 10 coordinates (rgb,w)", 1); + } + + float coords[8]; + for (size_t i = 0; i < 8; i++){ + lua_rawgeti(ctx, 3, i+1); + coords[i] = lua_tonumber(ctx, -1); + lua_pop(ctx, 1); + } + + struct drm_hdr_meta meta = { + .rx = coords[0], + .ry = coords[1], + .gx = coords[2], + .gy = coords[3], + .bx = coords[4], + .by = coords[5], + .wpx = coords[6], + .wpy = coords[7] + }; + + meta.master_min = luaL_checknumber(ctx, 4); + meta.master_max = luaL_checknumber(ctx, 5); + meta.cll = luaL_checknumber(ctx, 6); + meta.fll = luaL_checknumber(ctx, 7); + + vobj->vstore->hdr.model = 1; + vobj->vstore->hdr.drm = meta; + + lua_pushboolean(ctx, true); + LUA_ETRACE("image_metadata", NULL, 1); +} + static int imagestorage(lua_State* ctx) { LUA_TRACE("image_access_storage"); @@ -12024,6 +12082,7 @@ static const luaL_Reg imgfuns[] = { {"image_state", imagestate }, {"image_access_storage", imagestorage }, {"image_resize_storage", imageresizestorage }, +{"image_metadata", imagemetadata }, {"image_sharestorage", sharestorage }, {"image_matchstorage", matchstorage }, {"cursor_setstorage", cursorstorage }, diff --git a/src/platform/egl-dri/video.c b/src/platform/egl-dri/video.c index 374f5246c..11dcf38a9 100644 --- a/src/platform/egl-dri/video.c +++ b/src/platform/egl-dri/video.c @@ -830,7 +830,7 @@ static int setup_buffers_gbm(struct dispout* d) if (!got_config){ debug_print("no matching gbm-format <-> visual " - "<-> egl config for fmt: %d", (int)gbm_formats[i]); + "<-> egl config for fmt: %s", fmt_lbls[i]); continue; } @@ -1100,14 +1100,11 @@ bool platform_video_set_mode(platform_display_id disp, } */ -/* - * setup / allocate a new set of buffers that match the new mode - */ - if (!realloc_buffers(d)) + if (!realloc_buffers(d)){ return false; + } d->state = DISP_MAPPED; - return true; } @@ -3917,8 +3914,10 @@ ssize_t platform_video_map_display_layer(arcan_vobj_id id, arcan_video_display.default_txcos), sizeof(float) * 8 ); - debug_print("map_display(%d->%d) ok @%zu*%zu+%zu,%zu, hint: %d", - (int) id, (int) disp, (size_t) d->dispw, (size_t) d->disph, + debug_print("map_display(%d:%s->%d) ok @%zu*%zu+%zu,%zu, hint: %d", + (int) id, + vobj->tracetag ? vobj->tracetag : "(notag)", + (int) disp, (size_t) d->dispw, (size_t) d->disph, (size_t) d->dispx, (size_t) d->dispy, (int) hint); d->frame_cookie = 0; @@ -4075,7 +4074,9 @@ static enum display_update_state draw_display(struct dispout* d) struct rendertarget* newtgt = arcan_vint_findrt(vobj); if (newtgt){ size_t nd = agp_rendertarget_dirty(newtgt->art, NULL); - verbose_print("(%d) draw display, dirty regions: %zu", nd); + verbose_print( + "(%d:%s) draw display, dirty regions: %zu", + (int) d->id, vobj->tracetag ? vobj->tracetag : "(untagged)", nd); if (nd || newtgt->frame_cookie != d->frame_cookie){ agp_rendertarget_dirty_reset(newtgt->art, NULL); } @@ -4124,8 +4125,10 @@ static enum display_update_state draw_display(struct dispout* d) agp_rendertarget_clear(); agp_blendstate(BLEND_NONE); agp_draw_vobj(0, 0, d->dispw, d->disph, d->txcos, NULL); - verbose_print("(%d) draw, shader: %d, %zu*%zu", - (int)d->id, (int)shid, (size_t)d->dispw, (size_t)d->disph); + verbose_print("(%d:%s) draw, shader: %d, %zu*%zu", + (int)d->id, + vobj->tracetag ? vobj->tracetag : "(notag)", + (int)shid, (size_t)d->dispw, (size_t)d->disph); } /* * another rough corner case, if we have a store that is not world ID but diff --git a/src/platform/platform_types.h b/src/platform/platform_types.h index ff7b2a350..3d6bb77d6 100644 --- a/src/platform/platform_types.h +++ b/src/platform/platform_types.h @@ -232,6 +232,15 @@ struct agp_region { size_t x1, y1, x2, y2; }; +struct drm_hdr_meta { + int eotf; + uint16_t rx, ry, gx, gy, bx, by; + uint16_t wpx, wpy; + uint16_t master_min, master_max; + uint16_t cll; + uint16_t fll; +}; + struct agp_vstore; struct agp_vstore { size_t refcount; @@ -294,6 +303,11 @@ struct agp_vstore { size_t w, h; uint8_t bpp, txmapped, txu, txv, scale, imageproc, filtermode; + + struct { + int model; + struct drm_hdr_meta drm; + } hdr; }; /* Built in Shader Vertex Attributes */ diff --git a/src/shmif/arcan_shmif_sub.c b/src/shmif/arcan_shmif_sub.c index 21414db4e..67b286a1b 100644 --- a/src/shmif/arcan_shmif_sub.c +++ b/src/shmif/arcan_shmif_sub.c @@ -26,7 +26,7 @@ union shmif_ext_substruct arcan_shmif_substruct( sub.vr = (struct arcan_shmif_vr*)(base + aofs->ofs_vr); if (aofs->sz_hdr) - sub.hdr = (struct arcan_shmif_hdr16f*)(base + aofs->ofs_hdr); + sub.hdr = (struct arcan_shmif_hdr*)(base + aofs->ofs_hdr); if (aofs->sz_vector) sub.vector = (struct arcan_shmif_vector*)(base + aofs->ofs_vector); @@ -40,8 +40,8 @@ union shmif_ext_substruct arcan_shmif_substruct( bool arcan_shmifsub_getramp( struct arcan_shmif_cont* cont, size_t ind, struct ramp_block* out) { - struct arcan_shmif_ramp* hdr = arcan_shmif_substruct( - cont, SHMIF_META_CM).cramp; + struct arcan_shmif_ramp* hdr = + arcan_shmif_substruct(cont, SHMIF_META_CM).cramp; if (!hdr || hdr->magic != ARCAN_SHMIF_RAMPMAGIC || ind > (hdr->n_blocks >> 1)) return false; diff --git a/src/shmif/arcan_shmif_sub.h b/src/shmif/arcan_shmif_sub.h index 8f17c49f7..6a75fff8c 100644 --- a/src/shmif/arcan_shmif_sub.h +++ b/src/shmif/arcan_shmif_sub.h @@ -67,14 +67,14 @@ static inline uint16_t subp_checksum(const uint8_t* const buf, size_t len) */ struct arcan_shmif_vr; struct arcan_shmif_ramp; -struct arcan_shmif_hdr16f; +struct arcan_shmif_hdr; struct arcan_shmif_vector; struct arcan_shmif_venc; union shmif_ext_substruct { struct arcan_shmif_vr* vr; struct arcan_shmif_ramp* cramp; - struct arcan_shmif_hdr16f* hdr; + struct arcan_shmif_hdr* hdr; struct arcan_shmif_vector* vector; struct arcan_shmif_venc* venc; }; @@ -107,28 +107,6 @@ struct arcan_shmif_ofstbl { }; }; -/* - * Practically speaking these will only be used witin libdrm like settings, - * thus the fields practically match libdrm expected metadata. For other - * applications - */ -struct arcan_shmif_hdr_metadata { - bool valid; - int eotf; - - struct { - float white[2]; - float red[2]; - float green[2]; - float blue[2]; - } primaries; - - uint16_t max_disp_luma; - uint16_t min_disp_luma; - uint16_t max_cll; - uint16_t max_fall; -}; - /* HDR is something of a misnomer here, it can also refer to SDR contents with * higher precision (e.g. 10-bit). In that case the SDR eotf mode is specified. * The values here match what libdrm metadata takes. */ @@ -140,13 +118,16 @@ enum shmif_hdr_eotf { }; struct arcan_shmif_hdr { - int format; /* 0 = fp32 rgba, 1 = fp16 rgba, 2 = 10-bit rgba in 1010102 */ + uint8_t model; /* match eotf */ -/* PRODUCER SET */ - struct arcan_shmif_hdr_metadata source; - -/* CONSUMER SET - updated on DISPLAYHINT, if known / applicable */ - struct arcan_shmif_hdr_metadata sink; + struct { + int eotf; + uint16_t rx, ry, gx, gy, bx, by; + uint16_t wpx, wpy; + uint16_t master_min, master_max; + uint16_t cll_max; + uint16_t fll_max; + } drm; }; /* verified during _signal, framesize <= w * h * sizeof(shmif_pixel) */