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:
270
video.cpp
270
video.cpp
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user