mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
apple: chromaticity and a CTA block for the synthesized panel EDID
Three of the rows the menu draws were empty for the Apple Silicon panel, and two of them need not have been. Chromaticity read 0.000 across the board. The display's colour profile has the primaries: CGDisplayCopyColorSpace gives the profile, its rXYZ/gXYZ/bXYZ/wtpt tags give the colorants. ICC stores those adapted to D50 with the adaptation it used in the 'chad' tag, so reporting them as-is would put the white point at D50 rather than where the panel actually sits; the adaptation is inverted first, and a profile with no 'chad' reports nothing rather than the D50 numbers. Gamma comes from the same place, and the base block now carries both. The colour data the DCP lists per timing - ColorModes' Depth, PixelEncoding, Colorimetry and EOTF, plus the panel's BLNitsCap as a peak luminance - is a colorimetry data block and HDR static metadata, so the block now has a CTA-861 extension carrying exactly those and nothing else. No video data block: a native panel timing has no VIC to name. No audio data block and no basic-audio flag: the panel makes no sound. No detailed timing in the extension either, since the base block already carries the panel's one mode. What the extension says is what the DCP said. Established and standard timings stay absent, which is correct: they are legacy VGA mode bitmaps, the panel runs one timing, and a guessed DMT list would be a claim nothing made.
This commit is contained in:
1 parent
65b920d0e7
commit
eae4aedde3
3 files changed
+289
-10
No files matched your search
@@ -47,6 +47,7 @@
|
||||
* moved to DCP) and Apple published no replacement, so the walk finds
|
||||
* nothing and the op reports -1 there. */
|
||||
#include <dlfcn.h>
|
||||
#include <math.h>
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
@@ -977,6 +978,130 @@ static bool apple_dcp_timing(CFDictionaryRef elem, video_edid_timing_t *t)
|
||||
return t->pclock != 0;
|
||||
}
|
||||
|
||||
/* s15Fixed16 as a double */
|
||||
static double apple_icc_fixed(const uint8_t *p)
|
||||
{
|
||||
int32_t v = (int32_t)(((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16)
|
||||
| ((uint32_t)p[2] << 8) | p[3]);
|
||||
return (double)v / 65536.0;
|
||||
}
|
||||
|
||||
/* One XYZType tag: signature, four reserved bytes, three s15Fixed16 */
|
||||
static bool apple_icc_xyz(const uint8_t *icc, size_t len, uint32_t off,
|
||||
uint32_t size, double xyz[3])
|
||||
{
|
||||
if (off + 20 > len || size < 20)
|
||||
return false;
|
||||
if (memcmp(icc + off, "XYZ ", 4))
|
||||
return false;
|
||||
xyz[0] = apple_icc_fixed(icc + off + 8);
|
||||
xyz[1] = apple_icc_fixed(icc + off + 12);
|
||||
xyz[2] = apple_icc_fixed(icc + off + 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool apple_icc_tag(const uint8_t *icc, size_t len, const char *sig,
|
||||
uint32_t *off, uint32_t *size)
|
||||
{
|
||||
uint32_t i, count;
|
||||
if (len < 132)
|
||||
return false;
|
||||
count = ((uint32_t)icc[128] << 24) | ((uint32_t)icc[129] << 16)
|
||||
| ((uint32_t)icc[130] << 8) | icc[131];
|
||||
if (count > 256 || 132 + count * 12 > len)
|
||||
return false;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
const uint8_t *e = icc + 132 + i * 12;
|
||||
if (memcmp(e, sig, 4))
|
||||
continue;
|
||||
*off = ((uint32_t)e[4] << 24) | ((uint32_t)e[5] << 16)
|
||||
| ((uint32_t)e[6] << 8) | e[7];
|
||||
*size = ((uint32_t)e[8] << 24) | ((uint32_t)e[9] << 16)
|
||||
| ((uint32_t)e[10] << 8) | e[11];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The primaries and white point of the display's colour profile, in
|
||||
* thousandths, as the EDID chromaticity bytes hold them.
|
||||
*
|
||||
* An ICC profile stores its colorants chromatically adapted to D50,
|
||||
* with the adaptation it used in the 'chad' tag; reporting the D50
|
||||
* numbers as the display's primaries would be wrong, so the
|
||||
* adaptation is undone first. Without a 'chad' tag there is nothing
|
||||
* to undo it with and nothing is reported. */
|
||||
static bool apple_display_chromaticity(CGDirectDisplayID display,
|
||||
unsigned *out)
|
||||
{
|
||||
CGColorSpaceRef cs = CGDisplayCopyColorSpace(display);
|
||||
CFDataRef icc = NULL;
|
||||
const uint8_t *b;
|
||||
size_t len;
|
||||
uint32_t off, size;
|
||||
double chad[9], inv[9], det;
|
||||
double xyz[4][3];
|
||||
bool ok = false;
|
||||
int i;
|
||||
static const char *tags[4] = { "rXYZ", "gXYZ", "bXYZ", "wtpt" };
|
||||
|
||||
if (!cs)
|
||||
return false;
|
||||
icc = CGColorSpaceCopyICCData(cs);
|
||||
CGColorSpaceRelease(cs);
|
||||
if (!icc)
|
||||
return false;
|
||||
b = CFDataGetBytePtr(icc);
|
||||
len = (size_t)CFDataGetLength(icc);
|
||||
|
||||
if (!apple_icc_tag(b, len, "chad", &off, &size) || size < 8 + 36
|
||||
|| off + 8 + 36 > len || memcmp(b + off, "sf32", 4))
|
||||
goto done;
|
||||
for (i = 0; i < 9; i++)
|
||||
chad[i] = apple_icc_fixed(b + off + 8 + i * 4);
|
||||
det = chad[0] * (chad[4] * chad[8] - chad[5] * chad[7])
|
||||
- chad[1] * (chad[3] * chad[8] - chad[5] * chad[6])
|
||||
+ chad[2] * (chad[3] * chad[7] - chad[4] * chad[6]);
|
||||
if (det > -1e-9 && det < 1e-9)
|
||||
goto done;
|
||||
inv[0] = (chad[4] * chad[8] - chad[5] * chad[7]) / det;
|
||||
inv[1] = (chad[2] * chad[7] - chad[1] * chad[8]) / det;
|
||||
inv[2] = (chad[1] * chad[5] - chad[2] * chad[4]) / det;
|
||||
inv[3] = (chad[5] * chad[6] - chad[3] * chad[8]) / det;
|
||||
inv[4] = (chad[0] * chad[8] - chad[2] * chad[6]) / det;
|
||||
inv[5] = (chad[2] * chad[3] - chad[0] * chad[5]) / det;
|
||||
inv[6] = (chad[3] * chad[7] - chad[4] * chad[6]) / det;
|
||||
inv[7] = (chad[1] * chad[6] - chad[0] * chad[7]) / det;
|
||||
inv[8] = (chad[0] * chad[4] - chad[1] * chad[3]) / det;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
double v[3], n[3], sum;
|
||||
if (!apple_icc_tag(b, len, tags[i], &off, &size)
|
||||
|| !apple_icc_xyz(b, len, off, size, v))
|
||||
goto done;
|
||||
n[0] = inv[0] * v[0] + inv[1] * v[1] + inv[2] * v[2];
|
||||
n[1] = inv[3] * v[0] + inv[4] * v[1] + inv[5] * v[2];
|
||||
n[2] = inv[6] * v[0] + inv[7] * v[1] + inv[8] * v[2];
|
||||
sum = n[0] + n[1] + n[2];
|
||||
if (sum < 1e-9)
|
||||
goto done;
|
||||
xyz[i][0] = n[0] / sum;
|
||||
xyz[i][1] = n[1] / sum;
|
||||
ok = true;
|
||||
}
|
||||
if (ok)
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
out[i * 2] = (unsigned)(xyz[i][0] * 1000.0 + 0.5);
|
||||
out[i * 2 + 1] = (unsigned)(xyz[i][1] * 1000.0 + 0.5);
|
||||
}
|
||||
done:
|
||||
CFRelease(icc);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int apple_dcp_synthesize_edid(CGDirectDisplayID display,
|
||||
uint8_t *out, size_t max)
|
||||
{
|
||||
@@ -1010,6 +1135,18 @@ static int apple_dcp_synthesize_edid(CGDirectDisplayID display,
|
||||
props, CFSTR("IOMFBDisplayRefresh"));
|
||||
CFDictionaryRef maxpix = (CFDictionaryRef)CFDictionaryGetValue(
|
||||
props, CFSTR("IOMFBMaxSrcPixels"));
|
||||
long nits = 0;
|
||||
|
||||
/* The peak luminance the backlight is capped at, 16.16
|
||||
* fixed point, which is what HDR static metadata codes as
|
||||
* 50 * 2^(v/32) cd/m2 */
|
||||
if (apple_dcp_num(props, "BLNitsCap", &nits) && nits > 0)
|
||||
{
|
||||
double cd = (double)nits / 65536.0;
|
||||
if (cd >= 50.0 && cd <= 10000.0)
|
||||
in.cta_hdr_max_lum =
|
||||
(uint8_t)(32.0 * (log(cd / 50.0) / log(2.0)) + 0.5);
|
||||
}
|
||||
CFIndex i, n = (timings && CFGetTypeID(timings) == CFArrayGetTypeID())
|
||||
? CFArrayGetCount(timings) : 0;
|
||||
|
||||
@@ -1024,6 +1161,45 @@ static int apple_dcp_synthesize_edid(CGDirectDisplayID display,
|
||||
continue;
|
||||
if (!apple_dcp_timing(elem, &t))
|
||||
continue;
|
||||
/* The colour modes the DCP lists for this timing: bit
|
||||
* depth, whether anything but RGB 4:4:4 is offered, the
|
||||
* colorimetry bits and the EOTFs. A panel with one SDR
|
||||
* RGB mode reports exactly that. */
|
||||
{
|
||||
CFArrayRef modes = (CFArrayRef)CFDictionaryGetValue(elem,
|
||||
CFSTR("ColorModes"));
|
||||
CFIndex m, mn = (modes
|
||||
&& CFGetTypeID(modes) == CFArrayGetTypeID())
|
||||
? CFArrayGetCount(modes) : 0;
|
||||
for (m = 0; m < mn; m++)
|
||||
{
|
||||
CFDictionaryRef cm = (CFDictionaryRef)
|
||||
CFArrayGetValueAtIndex(modes, m);
|
||||
long v = 0;
|
||||
if (!cm || CFGetTypeID(cm) != CFDictionaryGetTypeID())
|
||||
continue;
|
||||
if (apple_dcp_num(cm, "Depth", &v) && v >= 6 && v <= 16)
|
||||
in.bit_depth = (uint8_t)v;
|
||||
if (apple_dcp_num(cm, "PixelEncoding", &v))
|
||||
{
|
||||
if (v == 1)
|
||||
in.ycbcr444 = true;
|
||||
else if (v == 2)
|
||||
in.ycbcr422 = true;
|
||||
}
|
||||
if (apple_dcp_num(cm, "Colorimetry", &v) && v > 0)
|
||||
{
|
||||
in.cta_colorimetry |= (uint8_t)(v & 0xff);
|
||||
in.cta = true;
|
||||
}
|
||||
/* EOTF 0 is SDR, which is bit 0 of the HDR block */
|
||||
if (apple_dcp_num(cm, "EOTF", &v))
|
||||
{
|
||||
in.cta_hdr_eotf |= (uint8_t)(1u << (v & 3));
|
||||
in.cta = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apple_dcp_bool(elem, "IsPreferred") && in.n_timings)
|
||||
{
|
||||
in.timing[1] = in.timing[0];
|
||||
@@ -1098,8 +1274,19 @@ static int apple_dcp_synthesize_edid(CGDirectDisplayID display,
|
||||
size_mm = CGDisplayScreenSize(display);
|
||||
in.width_mm = (unsigned)(size_mm.width + 0.5);
|
||||
in.height_mm = (unsigned)(size_mm.height + 0.5);
|
||||
in.bit_depth = 8; /* the DCP's ColorModes report 8 bpc per channel */
|
||||
if (!in.bit_depth)
|
||||
in.bit_depth = 8; /* the DCP's ColorModes usually say; 8 if not */
|
||||
in.interface = 5; /* DisplayPort, which is what the DCP drives */
|
||||
{
|
||||
unsigned c[8];
|
||||
if (apple_display_chromaticity(display, c))
|
||||
{
|
||||
in.red_x = c[0]; in.red_y = c[1];
|
||||
in.green_x = c[2]; in.green_y = c[3];
|
||||
in.blue_x = c[4]; in.blue_y = c[5];
|
||||
in.white_x = c[6]; in.white_y = c[7];
|
||||
}
|
||||
}
|
||||
strlcpy(in.name, built_in ? "Built-in" : "DCP display", sizeof(in.name));
|
||||
/* The unspecified-text descriptor says where the block came from,
|
||||
* so the menu shows it and nobody mistakes it for a read block */
|
||||
|
||||
@@ -937,6 +937,13 @@ static void edid_write_text(uint8_t *d, uint8_t tag, const char *s)
|
||||
d[5 + i] = 0x20;
|
||||
}
|
||||
|
||||
/* A 10-bit fraction of 1.0, which is how the chromaticity bytes hold
|
||||
* a coordinate */
|
||||
static unsigned edid_chroma10(unsigned thousandths)
|
||||
{
|
||||
return (thousandths * 1024u + 500u) / 1000u;
|
||||
}
|
||||
|
||||
size_t modeline_edid_synthesize(const video_edid_synth_t *in,
|
||||
uint8_t *out, size_t max)
|
||||
{
|
||||
@@ -976,14 +983,37 @@ size_t modeline_edid_synthesize(const video_edid_synth_t *in,
|
||||
/* Physical size, in whole centimetres as the block stores it */
|
||||
out[21] = (uint8_t)((in->width_mm + 5) / 10);
|
||||
out[22] = (uint8_t)((in->height_mm + 5) / 10);
|
||||
out[23] = 0xff; /* gamma: undefined, since nothing reported one */
|
||||
/* RGB 4:4:4, preferred timing is the first descriptor, continuous
|
||||
* frequency. No power-management claims and no sRGB claim: the
|
||||
* source said nothing about either. */
|
||||
/* Gamma, when the profile gave one; 0xff is "undefined" */
|
||||
out[23] = (in->gamma_x100 >= 100 && in->gamma_x100 <= 355)
|
||||
? (uint8_t)(in->gamma_x100 - 100) : 0xff;
|
||||
/* Preferred timing is the first descriptor, continuous frequency,
|
||||
* plus whatever colour formats the source reported. No
|
||||
* power-management claims and no sRGB claim: the source said
|
||||
* nothing about either. */
|
||||
out[24] = 0x02 | 0x01;
|
||||
if (in->ycbcr444)
|
||||
out[24] |= 0x08;
|
||||
if (in->ycbcr422)
|
||||
out[24] |= 0x10;
|
||||
|
||||
/* Chromaticity: all zero, the encoding for "not stated". Inventing
|
||||
* primaries here would read as measured data. */
|
||||
/* Chromaticity, when the display's colour profile supplied the
|
||||
* primaries; all zero otherwise, the encoding for "not stated".
|
||||
* Inventing primaries here would read as measured data. */
|
||||
if (in->white_x || in->red_x)
|
||||
{
|
||||
unsigned c[8];
|
||||
c[0] = edid_chroma10(in->red_x); c[1] = edid_chroma10(in->red_y);
|
||||
c[2] = edid_chroma10(in->green_x); c[3] = edid_chroma10(in->green_y);
|
||||
c[4] = edid_chroma10(in->blue_x); c[5] = edid_chroma10(in->blue_y);
|
||||
c[6] = edid_chroma10(in->white_x); c[7] = edid_chroma10(in->white_y);
|
||||
/* the low two bits of each live packed in bytes 25-26 */
|
||||
out[25] = (uint8_t)(((c[0] & 3) << 6) | ((c[1] & 3) << 4)
|
||||
| ((c[2] & 3) << 2) | (c[3] & 3));
|
||||
out[26] = (uint8_t)(((c[4] & 3) << 6) | ((c[5] & 3) << 4)
|
||||
| ((c[6] & 3) << 2) | (c[7] & 3));
|
||||
for (i = 0; i < 8; i++)
|
||||
out[27 + i] = (uint8_t)(c[i] >> 2);
|
||||
}
|
||||
|
||||
/* No established or standard timings: the source reports its modes
|
||||
* as detailed timings, and a guessed DMT bitmap would be a claim
|
||||
@@ -1030,9 +1060,56 @@ size_t modeline_edid_synthesize(const video_edid_synth_t *in,
|
||||
if (in->text[0] && slot < 4)
|
||||
edid_write_text(out + 54 + slot++ * 18, 0xfe, in->text);
|
||||
|
||||
out[126] = 0;
|
||||
out[126] = in->cta ? 1 : 0;
|
||||
for (i = 0; i < 127; i++)
|
||||
checksum += out[i];
|
||||
out[127] = (uint8_t)((256 - (checksum & 0xff)) & 0xff);
|
||||
|
||||
/* A CTA-861 extension carrying only what was reported: no video
|
||||
* data block, since a native panel timing has no VIC to name, and
|
||||
* no audio data block, since a panel that cannot make a sound has
|
||||
* no descriptor to give. Revision 3, one native detailed timing,
|
||||
* no basic audio. */
|
||||
if (in->cta && max >= MODELINE_EDID_SIZE * 2)
|
||||
{
|
||||
uint8_t *e = out + MODELINE_EDID_SIZE;
|
||||
unsigned p = 4;
|
||||
memset(e, 0, MODELINE_EDID_SIZE);
|
||||
e[0] = MODELINE_EDID_EXT_CTA;
|
||||
e[1] = 3;
|
||||
/* No native detailed timings in this block: the base block
|
||||
* already carries the panel's timing and repeating it here
|
||||
* would read as a second mode. */
|
||||
e[3] = (uint8_t)((in->ycbcr444 ? 0x20 : 0)
|
||||
| (in->ycbcr422 ? 0x10 : 0));
|
||||
if (in->cta_colorimetry)
|
||||
{
|
||||
e[p++] = (uint8_t)((7 << 5) | 3); /* extended tag, 3 bytes */
|
||||
e[p++] = 0x05; /* colorimetry data block */
|
||||
e[p++] = in->cta_colorimetry;
|
||||
e[p++] = 0x00; /* no gamut metadata profiles */
|
||||
}
|
||||
if (in->cta_hdr_eotf)
|
||||
{
|
||||
unsigned len = in->cta_hdr_max_lum ? 4 : 2;
|
||||
e[p++] = (uint8_t)((7 << 5) | (len + 1));
|
||||
e[p++] = 0x06; /* HDR static metadata */
|
||||
e[p++] = in->cta_hdr_eotf;
|
||||
e[p++] = 0x01; /* static metadata type 1 */
|
||||
if (in->cta_hdr_max_lum)
|
||||
{
|
||||
e[p++] = in->cta_hdr_max_lum;
|
||||
e[p++] = 0x00; /* frame average: not reported */
|
||||
}
|
||||
}
|
||||
/* Byte 2 points past the data blocks, where detailed timings
|
||||
* would start; the zeros there say there are none */
|
||||
e[2] = (uint8_t)p;
|
||||
checksum = 0;
|
||||
for (i = 0; i < 127; i++)
|
||||
checksum += e[i];
|
||||
e[127] = (uint8_t)((256 - (checksum & 0xff)) & 0xff);
|
||||
return MODELINE_EDID_SIZE * 2;
|
||||
}
|
||||
return MODELINE_EDID_SIZE;
|
||||
}
|
||||
@@ -233,14 +233,29 @@ typedef struct video_edid_synth
|
||||
unsigned pclock_max; /* Hz, 0 when unknown */
|
||||
uint8_t bit_depth; /* bits per colour, 0 when unknown */
|
||||
uint8_t interface; /* 1 DVI 2 HDMI-a 3 HDMI-b 4 MDDI 5 DP */
|
||||
/* Chromaticity in thousandths, from the display's colour profile;
|
||||
* all zero leaves the chromaticity bytes at "not stated" */
|
||||
unsigned red_x, red_y, green_x, green_y, blue_x, blue_y;
|
||||
unsigned white_x, white_y;
|
||||
unsigned gamma_x100; /* 0 when unknown */
|
||||
bool ycbcr444, ycbcr422;
|
||||
/* A CTA-861 extension block, when the display reports colour
|
||||
* capabilities worth carrying. Nothing is invented to fill it: a
|
||||
* panel with no audio and no TV formats gets a colorimetry data
|
||||
* block and HDR static metadata, and nothing else. */
|
||||
bool cta;
|
||||
uint8_t cta_colorimetry; /* byte 2 of the colorimetry block */
|
||||
uint8_t cta_hdr_eotf; /* bit 0 SDR, 1 HDR, 2 PQ, 3 HLG */
|
||||
uint8_t cta_hdr_max_lum; /* coded, 0 when not reported */
|
||||
char name[MODELINE_EDID_TEXT_LEN];
|
||||
char text[MODELINE_EDID_TEXT_LEN];
|
||||
uint8_t n_timings; /* the first is the preferred one */
|
||||
video_edid_timing_t timing[2];
|
||||
} video_edid_synth_t;
|
||||
|
||||
/* Writes MODELINE_EDID_SIZE bytes and returns that, or 0 when max is
|
||||
* short or in carries no timing. */
|
||||
/* Writes MODELINE_EDID_SIZE bytes, or twice that when in asks for a
|
||||
* CTA-861 extension, and returns the length; 0 when max is short or
|
||||
* in carries no timing. */
|
||||
size_t modeline_edid_synthesize(const video_edid_synth_t *in,
|
||||
uint8_t *out, size_t max);
|
||||
|
||||
|
||||
Reference in new issue
Block a user