Feature: HDMI color controls via tranceiver CSC (#715)

* Addition of HDMI color controls (brightness, contrast, saturation, hue, gain, offset) that can be tweaked via MiSTer.ini (examples in-file). These controls are used to construct a matrix for the HDMI chip's color space converter. Due to the nature of this change, HDMI limited and YCbCr options have been reworked and combined into the color matrix routine (hdmi_config_set_csc)

* Change cc_ prefix to video_, as per suggestion and inline with existing video options.
This commit is contained in:
Sam Hardeman
2022-12-21 14:38:47 +09:00
committed by GitHub
parent caac5f52b2
commit 3214046c50
5 changed files with 369 additions and 27 deletions

270
video.cpp
View File

@@ -17,6 +17,7 @@
#include "spi.h"
#include "cfg.h"
#include "file_io.h"
#include "mat4x4.h"
#include "menu.h"
#include "video.h"
#include "input.h"
@@ -1059,6 +1060,246 @@ static void hdmi_config_set_spare(bool val)
}
}
static void hdmi_config_set_csc()
{
// default color conversion matrices
// for the original hexadecimal versions please refer
// to the ADV7513 programming guide section 4.3.7
float ypbpr_coeffs[] = {
0.42944335937f, 1.64038085938f, 1.93017578125f, 0.49389648437f,
0.25683593750f, 0.50415039062f, 0.09790039062f, 0.06250f,
1.85498046875f, 1.71557617188f, 0.42944335937f, 0.49389648437f,
0.0f, 0.0f, 0.0f, 1.0f
};
// no transformation, so use identity matrix
float hdmi_full_coeffs[] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
float hdmi_limited_1_coeffs[] = {
0.8583984375f, 0.0f, 0.0f, 0.06250f,
0.0f, 0.8583984375f, 0.0f, 0.06250f,
0.0f, 0.0f, 0.8583984375f, 0.06250f,
0.0f, 0.0f, 0.0f, 1.0f
};
float hdmi_limited_2_coeffs[] = {
0.93701171875f, 0.0f, 0.0f, 0.06250f,
0.0f, 0.93701171875f, 0.0f, 0.06250f,
0.0f, 0.0f, 0.93701171875f, 0.06250f,
0.0f, 0.0f, 0.0f, 1.0f
};
const float pi = float(M_PI);
// select the base CSC
int ypbpr = cfg.ypbpr && cfg.direct_video;
int hdmi_limited_1 = cfg.hdmi_limited & 1;
int hdmi_limited_2 = cfg.hdmi_limited & 2;
mat4x4 coeffs = hdmi_full_coeffs;
if (ypbpr)
coeffs = ypbpr_coeffs;
else if (hdmi_limited_1)
coeffs = hdmi_limited_1_coeffs;
else if (hdmi_limited_2)
coeffs = hdmi_limited_2_coeffs;
else
coeffs = hdmi_full_coeffs;
mat4x4 csc(coeffs);
// apply color controls
float brightness = (((cfg.video_brightness/100.0f) - 0.5f)); // [-0.5 .. 0.5]
float contrast = ((cfg.video_contrast/100.0f) - 0.5f) * 2.0f + 1.0f; // [0 .. 2]
float saturation = ((cfg.video_saturation/100.0f)); // [0 .. 1]
float hue = (cfg.video_hue * pi / 180.0f);
char* gain_offset = cfg.video_gain_offset;
// we have to parse these
float gain_red = 1;
float gain_green = 1;
float gain_blue = 1;
float off_red = 0;
float off_green = 0;
float off_blue = 0;
size_t target = 0;
float* targets[6] = { &gain_red, &off_red, &gain_green, &off_green, &gain_blue, &off_blue };
for (size_t i = 0; i < strlen(gain_offset) && target < 6; i++)
{
// skip whitespace
if (gain_offset[i] == ' ' || gain_offset[i] == ',')
continue;
int numRead = 0;
int match = sscanf(gain_offset + i, "%f%n", targets[target], &numRead);
i += numRead > 0 ? numRead - 1 : 0;
if (match == 1)
target++;
}
// first apply hue matrix, because it does not touch luminance
float cos_hue = cos(hue);
float sin_hue = sin(hue);
float lr = 0.213f;
float lg = 0.715f;
float lb = 0.072f;
float ca = 0.143f;
float cb = 0.140f;
float cc = 0.283f;
mat4x4 mat_hue;
mat_hue.setIdentity();
mat_hue.m11 = lr+cos_hue*(1-lr)+sin_hue*(-lr);
mat_hue.m12 = lg+cos_hue*(-lg) +sin_hue*(-lg);
mat_hue.m13 = lb+cos_hue*(-lb) +sin_hue*(1-lb);
mat_hue.m21 = lr+cos_hue*(-lr) +sin_hue*(ca);
mat_hue.m22 = lg+cos_hue*(1-lg)+sin_hue*(cb);
mat_hue.m23 = lb+cos_hue*(-lb) +sin_hue*(cc);
mat_hue.m31 = lr+cos_hue*(-lr) +sin_hue*(-(1-lr));
mat_hue.m32 = lg+cos_hue*(-lg) +sin_hue*(lg);
mat_hue.m33 = lb+cos_hue*(1-lb)+sin_hue*(lb);
csc = csc * mat_hue;
// now saturation
float s = saturation;
float sr = ( 1.0f - s ) * .3086f;
float sg = ( 1.0f - s ) * .6094f;
float sb = ( 1.0f - s ) * .0920f;
float mat_saturation[] = {
sr + s, sg, sb, 0,
sr, sg + s, sb, 0,
sr, sg, sb + s, 0,
0, 0, 0, 1.0f
};
csc = csc * mat4x4(mat_saturation);
// now brightness and contrast
float b = brightness;
float c = contrast;
float t = (1.0f - c) / 2.0f;
float mat_brightness_contrast[] = {
c, 0, 0, (t+b),
0, c, 0, (t+b),
0, 0, c, (t+b),
0, 0, 0, 1.0f
};
csc = csc * mat4x4(mat_brightness_contrast);
// gain and offset
float rg = gain_red;
float ro = off_red;
float gg = gain_green;
float go = off_green;
float bg = gain_blue;
float bo = off_blue;
float mat_gain_off[] = {
rg, 0, 0, ro,
0, gg, 0, go,
0, 0, bg, bo,
0, 0, 0, 1.0f
};
csc = csc * mat4x4(mat_gain_off);
// final compression
csc.compress(2.0f);
// finally, apply a fixed multiplier to get it in
// correct range for ADV7513 chip
const int16_t csc_int16[12] = {
int16_t(csc.comp[0] * 2048.0f),
int16_t(csc.comp[1] * 2048.0f),
int16_t(csc.comp[2] * 2048.0f),
int16_t(csc.comp[3] * 2048.0f),
int16_t(csc.comp[4] * 2048.0f),
int16_t(csc.comp[5] * 2048.0f),
int16_t(csc.comp[6] * 2048.0f),
int16_t(csc.comp[7] * 2048.0f),
int16_t(csc.comp[8] * 2048.0f),
int16_t(csc.comp[9] * 2048.0f),
int16_t(csc.comp[10] * 2048.0f),
int16_t(csc.comp[11] * 2048.0f),
};
// Clamps to reinforce limited if necessary
// 0x100 = 16/256 * 4096 (12-bit mul)
// 0xEB0 = 235/256 * 4096
// 0xFFF = 4095 (12-bit max)
uint16_t clipMin = (hdmi_limited_1 || hdmi_limited_2) ? 0x100 : 0x000;
uint16_t clipMax = hdmi_limited_1 ? 0xEB0 : 0xFFF;
// pass to HDMI, use 0xA0 to set a mode of [-2 .. 2] per ADV7513 programming guide
uint8_t csc_data[] = {
0x18, (uint8_t)(0b10100000 | (( csc_int16[0] >> 8) & 0b00011111)), // csc Coefficients, Channel A
0x19, (uint8_t)(csc_int16[0] & 0xff),
0x1A, (uint8_t)(csc_int16[1] >> 8),
0x1B, (uint8_t)(csc_int16[1] & 0xff),
0x1C, (uint8_t)(csc_int16[2] >> 8),
0x1D, (uint8_t)(csc_int16[2] & 0xff),
0x1E, (uint8_t)(csc_int16[3] >> 8),
0x1F, (uint8_t)(csc_int16[3] & 0xff),
0x20, (uint8_t)(csc_int16[4] >> 8), // csc Coefficients, Channel B
0x21, (uint8_t)(csc_int16[4] & 0xff),
0x22, (uint8_t)(csc_int16[5] >> 8),
0x23, (uint8_t)(csc_int16[5] & 0xff),
0x24, (uint8_t)(csc_int16[6] >> 8),
0x25, (uint8_t)(csc_int16[6] & 0xff),
0x26, (uint8_t)(csc_int16[7] >> 8),
0x27, (uint8_t)(csc_int16[7] & 0xff),
0x28, (uint8_t)(csc_int16[8] >> 8), // csc Coefficients, Channel C
0x29, (uint8_t)(csc_int16[8] & 0xff),
0x2A, (uint8_t)(csc_int16[9] >> 8),
0x2B, (uint8_t)(csc_int16[9] & 0xff),
0x2C, (uint8_t)(csc_int16[10] >> 8),
0x2D, (uint8_t)(csc_int16[10] & 0xff),
0x2E, (uint8_t)(csc_int16[11] >> 8),
0x2F, (uint8_t)(csc_int16[11] & 0xff),
0xC0, (uint8_t)(clipMin >> 8), // HDMI limited clamps
0xC1, (uint8_t)(clipMin & 0xff),
0xC2, (uint8_t)(clipMax >> 8),
0xC3, (uint8_t)(clipMax & 0xff)
};
int fd = i2c_open(0x39, 0);
if (fd >= 0)
{
for (uint i = 0; i < sizeof(csc_data); i += 2)
{
int res = i2c_smbus_write_byte_data(fd, csc_data[i], csc_data[i + 1]);
if (res < 0) printf("i2c: write error (%02X %02X): %d\n", csc_data[i], csc_data[i + 1], res);
}
i2c_close(fd);
}
else
{
printf("*** ADV7513 not found on i2c bus! HDMI won't be available!\n");
}
}
static void hdmi_config_init()
{
int ypbpr = cfg.ypbpr && cfg.direct_video;
@@ -1100,33 +1341,6 @@ static void hdmi_config_init()
0x17, 0b01100010, // Aspect ratio 16:9 [1]=1, 4:3 [1]=0, invert sync polarity
0x18, (uint8_t)(ypbpr ? 0x86 : (cfg.hdmi_limited & 1) ? 0x8D : (cfg.hdmi_limited & 2) ? 0x8E : 0x00), // CSC Scaling Factors and Coefficients for RGB Full->Limited.
0x19, (uint8_t)(ypbpr ? 0xDF : (cfg.hdmi_limited & 1) ? 0xBC : 0xFE), // Taken from table in ADV7513 Programming Guide.
0x1A, (uint8_t)(ypbpr ? 0x1A : 0x00), // CSC Channel A.
0x1B, (uint8_t)(ypbpr ? 0x3F : 0x00),
0x1C, (uint8_t)(ypbpr ? 0x1E : 0x00),
0x1D, (uint8_t)(ypbpr ? 0xE2 : 0x00),
0x1E, (uint8_t)(ypbpr ? 0x07 : 0x01),
0x1F, (uint8_t)(ypbpr ? 0xE7 : 0x00),
0x20, (uint8_t)(ypbpr ? 0x04 : 0x00), // CSC Channel B.
0x21, (uint8_t)(ypbpr ? 0x1C : 0x00),
0x22, (uint8_t)(ypbpr ? 0x08 : (cfg.hdmi_limited & 1) ? 0x0D : 0x0E),
0x23, (uint8_t)(ypbpr ? 0x11 : (cfg.hdmi_limited & 1) ? 0xBC : 0xFE),
0x24, (uint8_t)(ypbpr ? 0x01 : 0x00),
0x25, (uint8_t)(ypbpr ? 0x91 : 0x00),
0x26, (uint8_t)(ypbpr ? 0x01 : 0x01),
0x27, 0x00,
0x28, (uint8_t)(ypbpr ? 0x1D : 0x00), // CSC Channel C.
0x29, (uint8_t)(ypbpr ? 0xAE : 0x00),
0x2A, (uint8_t)(ypbpr ? 0x1B : 0x00),
0x2B, (uint8_t)(ypbpr ? 0x73 : 0x00),
0x2C, (uint8_t)(ypbpr ? 0x06 : (cfg.hdmi_limited & 1) ? 0x0D : 0x0E),
0x2D, (uint8_t)(ypbpr ? 0xDF : (cfg.hdmi_limited & 1) ? 0xBC : 0xFE),
0x2E, (uint8_t)(ypbpr ? 0x07 : 0x01),
0x2F, (uint8_t)(ypbpr ? 0xE7 : 0x00),
0x3B, 0x0, // Automatic pixel repetition and VIC detection
@@ -1241,6 +1455,8 @@ static void hdmi_config_init()
{
printf("*** ADV7513 not found on i2c bus! HDMI won't be available!\n");
}
hdmi_config_set_csc();
}
static uint8_t last_sync_invert = 0xff;