mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-07-24 01:30:31 +00:00
Decode the monitor EDID to get HDR metadata on X11
This commit is contained in:
@@ -449,45 +449,74 @@ static bool SetXRandRModeInfo(Display *display, XRRScreenResources *res, RRCrtc
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size_t namelen, RROutput output, const unsigned long widthmm, const unsigned long heightmm)
|
||||
static MonitorInfo *GetMonitorInfo(Display *dpy, int screen, RROutput output, const char *name)
|
||||
{
|
||||
// See if we can get the EDID data for the real monitor name
|
||||
int inches;
|
||||
int nprop;
|
||||
Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop);
|
||||
int i;
|
||||
MonitorInfo *info = NULL;
|
||||
int real_format;
|
||||
Atom real_type;
|
||||
unsigned long items_read = 0, items_left = 0;
|
||||
unsigned char *propdata = NULL;
|
||||
int status;
|
||||
|
||||
for (i = 0; i < nprop; ++i) {
|
||||
unsigned char *prop;
|
||||
int actual_format;
|
||||
unsigned long nitems, bytes_after;
|
||||
Atom actual_type;
|
||||
|
||||
if (props[i] == EDID) {
|
||||
if (X11_XRRGetOutputProperty(dpy, output, props[i], 0, 100, False,
|
||||
False, AnyPropertyType, &actual_type,
|
||||
&actual_format, &nitems, &bytes_after,
|
||||
&prop) == Success) {
|
||||
MonitorInfo *info = decode_edid(prop);
|
||||
if (info) {
|
||||
#ifdef X11MODES_DEBUG
|
||||
printf("Found EDID data for %s\n", name);
|
||||
dump_monitor_info(info);
|
||||
#endif
|
||||
SDL_strlcpy(name, info->dsc_product_name, namelen);
|
||||
SDL_free(info);
|
||||
if (!info) {
|
||||
Atom GAMESCOPE_DISPLAY_EDID_PATH = X11_XInternAtom(dpy, "GAMESCOPE_DISPLAY_EDID_PATH", False);
|
||||
if (GAMESCOPE_DISPLAY_EDID_PATH != None) {
|
||||
status = X11_XGetWindowProperty(dpy, RootWindow(dpy, screen),
|
||||
GAMESCOPE_DISPLAY_EDID_PATH, 0L, 1024L, False, AnyPropertyType,
|
||||
&real_type, &real_format, &items_read, &items_left, &propdata);
|
||||
if (status == Success && propdata) {
|
||||
size_t size = 0;
|
||||
void *data = SDL_LoadFile((char *)propdata, &size);
|
||||
if (data) {
|
||||
info = decode_edid((uchar *)data, size);
|
||||
SDL_free(data);
|
||||
}
|
||||
X11_XFree(prop);
|
||||
}
|
||||
break;
|
||||
if (propdata) {
|
||||
X11_XFree(propdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (props) {
|
||||
X11_XFree(props);
|
||||
if (!info) {
|
||||
Atom EDID = X11_XInternAtom(dpy, "EDID", False);
|
||||
if (EDID != None) {
|
||||
int nprop = 0;
|
||||
Atom *props = X11_XRRListOutputProperties(dpy, output, &nprop);
|
||||
for (int i = 0; i < nprop; ++i) {
|
||||
if (props[i] == EDID) {
|
||||
status = X11_XRRGetOutputProperty(dpy, output, props[i], 0L, 128L, False,
|
||||
False, AnyPropertyType, &real_type,
|
||||
&real_format, &items_read, &items_left, &propdata);
|
||||
if (status == Success && propdata) {
|
||||
info = decode_edid(propdata, items_read);
|
||||
}
|
||||
if (propdata) {
|
||||
X11_XFree(propdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f);
|
||||
#ifdef X11MODES_DEBUG
|
||||
if (info) {
|
||||
SDL_Log("Found EDID data for %s", name);
|
||||
dump_monitor_info(info);
|
||||
}
|
||||
#endif
|
||||
return info;
|
||||
}
|
||||
|
||||
static void SetXRandRDisplayName(MonitorInfo *info, char *name, size_t namelen, unsigned long widthmm, unsigned long heightmm)
|
||||
{
|
||||
if (info) {
|
||||
SDL_strlcpy(name, info->dsc_product_name, namelen);
|
||||
}
|
||||
|
||||
int inches = (int)((SDL_sqrtf(widthmm * widthmm + heightmm * heightmm) / 25.4f) + 0.5f);
|
||||
if (*name && inches) {
|
||||
const size_t len = SDL_strlen(name);
|
||||
(void)SDL_snprintf(&name[len], namelen - len, " %d\"", inches);
|
||||
@@ -500,7 +529,6 @@ static void SetXRandRDisplayName(Display *dpy, Atom EDID, char *name, const size
|
||||
|
||||
static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int screen, RROutput outputid, XRRScreenResources *res, SDL_VideoDisplay *display, char *display_name, size_t display_name_size)
|
||||
{
|
||||
Atom EDID = X11_XInternAtom(dpy, "EDID", False);
|
||||
XRROutputInfo *output_info;
|
||||
int display_x, display_y;
|
||||
unsigned long display_mm_width, display_mm_height;
|
||||
@@ -593,8 +621,10 @@ static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int
|
||||
displaydata->xrandr_output = outputid;
|
||||
SDL_strlcpy(displaydata->connector_name, display_name, sizeof(displaydata->connector_name));
|
||||
|
||||
MonitorInfo *info = GetMonitorInfo(dpy, screen, outputid, display_name);
|
||||
|
||||
SetXRandRModeInfo(dpy, res, output_crtc, modeID, &mode);
|
||||
SetXRandRDisplayName(dpy, EDID, display_name, display_name_size, outputid, display_mm_width, display_mm_height);
|
||||
SetXRandRDisplayName(info, display_name, display_name_size, display_mm_width, display_mm_height);
|
||||
|
||||
SDL_zero(*display);
|
||||
if (*display_name) {
|
||||
@@ -604,6 +634,20 @@ static bool X11_FillXRandRDisplayInfo(SDL_VideoDevice *_this, Display *dpy, int
|
||||
display->content_scale = X11_GetGlobalContentScaleForDevice(_this);
|
||||
display->internal = displaydata;
|
||||
|
||||
if (info) {
|
||||
if (info->max_luminance > 0.0) {
|
||||
/* ITU-R BT.2408-7 (Sept 2023) has the reference PQ white level at 203 nits,
|
||||
* while older Dolby documentation claims a reference level of 100 nits.
|
||||
*
|
||||
* Use 203 nits for now.
|
||||
*/
|
||||
display->HDR.HDR_headroom = (float)info->max_luminance / 203.0f;
|
||||
display->HDR.SDR_white_level = 1.0f;
|
||||
}
|
||||
|
||||
SDL_free(info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -518,10 +518,54 @@ decode_check_sum (const uchar *edid,
|
||||
info->checksum = check;
|
||||
}
|
||||
|
||||
MonitorInfo *
|
||||
decode_edid (const uchar *edid)
|
||||
static void
|
||||
decode_HDR_metadata_block (const uchar *data, int length, MonitorInfo *info)
|
||||
{
|
||||
MonitorInfo *info = SDL_calloc (1, sizeof (MonitorInfo));
|
||||
if (length >= 3)
|
||||
info->max_luminance = 50.0 * SDL_pow(2, data[2] / 32.0);
|
||||
|
||||
if (length >= 4)
|
||||
info->max_frame_average_luminance = 50.0 * SDL_pow(2, data[3] / 32.0);
|
||||
|
||||
if (length >= 5)
|
||||
info->min_luminance = 50.0 * SDL_pow(2, data[4] / 32.0);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
decode_cta_block (const uchar *edid,
|
||||
MonitorInfo *info)
|
||||
{
|
||||
int offset = 4;
|
||||
while (offset < 128) {
|
||||
int length = edid[offset] & 0x1f;
|
||||
int type = edid[offset] >> 5;
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
if (type == 7) {
|
||||
type <<= 8;
|
||||
type |= edid[offset + 1];
|
||||
}
|
||||
if (type == 0x706) {
|
||||
decode_HDR_metadata_block (&edid[offset + 2], length - 1, info);
|
||||
}
|
||||
offset += (1 + length);
|
||||
}
|
||||
}
|
||||
|
||||
MonitorInfo *
|
||||
decode_edid (const uchar *edid, size_t size)
|
||||
{
|
||||
const int EDID_PAGE_SIZE = 128;
|
||||
int num_blocks = (size / EDID_PAGE_SIZE);
|
||||
MonitorInfo *info;
|
||||
|
||||
if ((size % EDID_PAGE_SIZE) != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info = SDL_calloc (1, sizeof (MonitorInfo));
|
||||
|
||||
decode_check_sum (edid, info);
|
||||
|
||||
@@ -533,10 +577,21 @@ decode_edid (const uchar *edid)
|
||||
!decode_established_timings (edid, info) ||
|
||||
!decode_standard_timings (edid, info) ||
|
||||
!decode_descriptors (edid, info)) {
|
||||
SDL_free(info);
|
||||
return NULL;
|
||||
SDL_free(info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (num_blocks > 1) {
|
||||
for (int i = 1; i < num_blocks; ++i) {
|
||||
int offset = i * EDID_PAGE_SIZE;
|
||||
uchar extension = edid[offset];
|
||||
if (extension == 0x02) {
|
||||
// CTA-861 Extension Block
|
||||
decode_cta_block (&edid[offset], info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,10 @@ struct MonitorInfo
|
||||
double white_x;
|
||||
double white_y;
|
||||
|
||||
double min_luminance;
|
||||
double max_luminance;
|
||||
double max_frame_average_luminance;
|
||||
|
||||
Timing established[24]; // Terminated by 0x0x0
|
||||
Timing standard[8];
|
||||
|
||||
@@ -185,7 +189,7 @@ struct MonitorInfo
|
||||
char dsc_string[14]; // Unspecified ASCII data
|
||||
};
|
||||
|
||||
MonitorInfo *decode_edid(const uchar *data);
|
||||
MonitorInfo *decode_edid(const uchar *data, size_t size);
|
||||
void dump_monitor_info(MonitorInfo *info);
|
||||
char *make_display_name(const char *output_name,
|
||||
const MonitorInfo *info);
|
||||
|
||||
Reference in New Issue
Block a user