Sony Megatron Colour Video Monitor

Thanks, that’s appreciated. Possibly you could test with the 240p test suite “White & RGB Screen” test. If you press “W” on the keyboard you can cycle between White, Black, Red, Green, Blue screens.

I can clearly see unwanted subpixels light up on the Green subscreen when switching to DCI-P3 color in the shader.

Just out of interest, are you testing on a wide gamut monitor or sRGB monitor?

2 Likes

Hi @MajorPainTheCactus

I’ve got an additional more objective test related to the colour space settings of which the result seem strange: the luminousity of a pure white screen changes when cycling between the 3 colour spaces: the white screen shader output gets brighter with each change from colour space 709 to sRGB to DCI-P3

Would be great if you could do the following test:

First make the RA menu transparent such that when changing the shader colour space setting you see the result while in the menu, it makes the luminousity changes easier to spot when cycling through the colour spaces.

Go to Main Menu -> Settings -> User Interface -> Appearance -> Framebuffer Opacity and set this to a value of 0.00 (menu becomes fully transparent such that you can see the shader output when in the menu)

Start the SNES 240p test suite and go into test patterns “White & RGB Screens”, stay on the white screen.

Load the megatron 2730-sdr preset, go into the shader menu and cycle between the three colour spaces:

SDR: Display's Colour Space: r709 | sRGB | DCI-P3

You’ll see the luminousity/brightness of the 240p white screen increase with each step: r709 darkest, sRGB brighter, DCI-P3 brightest

Now since the white screen has no saturation I assume the luminousity of a full white screen should definitely not change when changing the colour space.

The only thing I could think of influencing the result is that the 709 and DCI-P3 matrix would be created with different white points, but as far as I can see they are both created with D65 white.

To be absolutely sure, Ive repeated above test on a completely different monitor (1080p sRGB), and the exact same happens, the luminousity of the 240p full white screen test increases with changing the shaders colour space settings…

Any idea what could be an explanantion or cause for this?

3 Likes

Yes so this is expected and is kind of a problem with the limited interface we have for the shader params. Each of the colour spaces, rec.601, rec 709, sRGB and rec.2020 isn’t just colour primaries it’s also a transfer function too and from optical and electrical spectrums. With rec.709 and below this a gamma curve, with rec.2020 this is a PQ function.

When you cycle through the colour systems youre also changing the gamma curve which is why we see the change in luminosity. Rec.709 is ^2.22, sRGB is ^2.4 and DCI-P3 is ^2.6. Because I can’t change values in the menu based on other values in the menu behind the scenes I add 0.2 to gamma out for dci-p3 and minus 0.18 for rec.709 onto the menu value of 2.4. Ideally we’d just change the menu values for gamma when we change colour space.

However I do see your point in that on a pure white screen we shouldn’t see this as everything would be at 1 but is everything at 1? As these masks mask out channels to red, blue and green and then we have the drop off in intensity from the center of the scan line to the outer part. I’d guess at this point you will see a change in luminosity for certain.

So based on that there maybe a good argument for changing into the output colour/gamma space before we do the main shader. I’ll try that and see where it gets us in terms of white stability.

Thanks for these tests and thoughts it’s great appreciated. I will try this out hopefully today.

3 Likes

Just thinking about this some more and although I’ll try this out (moving the gamma before the main shader) I’m not sure it’s correct. As in before the main shader we’re effectively in CRT space i.e rec.601 (I should probably change it to this from rec.709 as it currently is).

However when we come to DCI-P3 and rec.2020 these colour spaces didn’t exist back then and so are part of the conversion to your actual modern monitor from CRT. These should come after the main shader with probably the artifacts you’re seeing.

Ok you could change them before and get more luminosity stability but is that actually correct as in what a CRT scanline drop off would do with a different transfer function. Not sure.

2 Likes

I see your point.

I’m still not sure whether the artifacts I’m seeing regarding the mask output is to do with the curves alone. There’s an itch with this gamut mapping, that I’d like to at least understand a bit better.

I created a fictitious “Limited 709” space that is using desaturated 709 primaries for red and green. Mapping this one into 709 is the equivalent of the 709 to wide gamut mapping (DCI-P3/2020) but has the benefit that we keep 709 as output space, such that we can possibly rule out issues related to wide gamut modes.

The interesting finding is that the mask output gets messed up when color mapping of “limited 709 space” to regular 709 space is done, i.e. when a color gamut transform is active, while with regular 709 the mask output is a clean RBG.

Below is the patched gamma-correct.h. It’s a quick hack, but it gives the idea. Hopefully this is useful in tracking down why the mask output is so different (wrong?) when a color gamut transform is active.

Note that when you replace gamma-correct.h with this version, then the colour space parameter 709 and sRGB actually switches between “regular 709” space and “limited 709” in 709 space. Also the transform for regular 709 is done pro forma, as it’s a transform from 709toXYZ and XYZ to 709.

For me when using “limited 709” I can see the mask output being messe up when doing macro zoom on the subpixel output in the white screen of the 240p test suite. Contrary when setting regular 709 the mask output is clean RBG.

// SDR Colour output spaces

const mat3 k709_to_XYZ = mat3(
   0.412391f, 0.357584f, 0.180481f,
   0.212639f, 0.715169f, 0.072192f,
   0.019331f, 0.119195f, 0.950532f);
   
const mat3 kXYZ_to_DCIP3 = mat3 (
2.4934969119f, -0.9313836179f, -0.4027107845f,
-0.8294889696f,  1.7626640603f,  0.0236246858f,
0.0358458302f, -0.0761723893f,  0.9568845240f);

// "709limited" - fictitious 709 space with less saturated red and green primaries
// Wx=0.3127, Wy=0.329 -- D65 white
// Rx=0.50, Ry=0.33 -- less red saturation than 709/sRGB primaries
// Gx=0.30, Gy=0.50 -- less green saturation than 709/sRGB primaries
// Bx=0.15, By=0.06 -- Blue same as 709/sRGB

const mat3 k709limited_to_XYZ = mat3(
0.4182745739f,	0.4034733473f,	0.1287080058f,
0.2760612188f,	0.6724555789f,	0.0514832023f,
0.1422133551f,	0.2689822316f,	0.6778621641f);

// XYZ to 709
const mat3 kXYZ_to_k709 = mat3 (
   3.2409699419f,	-1.5373831776f,	-0.4986107603,
  -0.9692436363f,	 1.8759675015f,	 0.0415550574,
   0.0556300797f,	-0.2039769589f,	 1.0569715142);



float LinearTosRGB_1(const float channel)
{
	return (channel > 0.0031308f) ? (1.055f * pow(channel, 1.0f / HCRT_GAMMA_OUT)) - 0.055f : channel * 12.92f; 
}

vec3 LinearTosRGB(const vec3 colour)
{
	return vec3(LinearTosRGB_1(colour.r), LinearTosRGB_1(colour.g), LinearTosRGB_1(colour.b));
}

float LinearTo709_1(const float channel)
{
	return (channel >= 0.018f) ? pow(channel * 1.099f, 1.0f / (HCRT_GAMMA_OUT - 0.18f)) - 0.099f : channel * 4.5f;  // Gamma: 2.4 - 0.18 = 2.22
}

vec3 LinearTo709(const vec3 colour)
{
	return vec3(LinearTo709_1(colour.r), LinearTo709_1(colour.g), LinearTo709_1(colour.b));
}

vec3 LinearToDCIP3(const vec3 colour)
{
	return clamp(pow(colour, vec3(1.0f / (HCRT_GAMMA_OUT + 0.2f))), 0.0f, 1.0f);   // Gamma: 2.4 + 0.2 = 2.6
}

void GammaCorrect(const vec3 scanline_colour, inout vec3 gamma_out)
{
   if(HCRT_HDR < 1.0f)
   {
  if(HCRT_OUTPUT_COLOUR_SPACE == 0.0f)
  {
     const vec3 Rec709_colour = (scanline_colour * k709_to_XYZ) * kXYZ_to_k709; 
     gamma_out = LinearTo709(Rec709_colour);
  }
  else if(HCRT_OUTPUT_COLOUR_SPACE == 1.0f)
  {
     const vec3 Rec709_colour = (scanline_colour * k709limited_to_XYZ) * kXYZ_to_k709; 
     gamma_out = LinearTo709(Rec709_colour);
  }
  else
  {
     const vec3 Rec709_colour = (scanline_colour * k709limited_to_XYZ) * kXYZ_to_k709; 
     gamma_out = LinearToDCIP3(Rec709_colour);
  }
   }
   else
   {
  gamma_out = Hdr10(scanline_colour, HCRT_PAPER_WHITE_NITS, HCRT_EXPAND_GAMUT);
   }
}

Output when “Limited 709” is selected:

Output with regular 709:

2 Likes

So let’s separate out two things we’ve been talking about here:

  1. there’s an issue you’re seeing with the mask in DCI-P3
  2. there’s a change in luminosity as you change between SDR colour spaces

I think we can tick off 2) or at least is less of a priority as it makes sense. I’m much more interested in 1) for the time being as it seems less understood.

With regards to 1) I’ll try on my monitor the test we were talking about with 300TVL and pure red screen as I think you said that was the more obvious repro case.

Does that sound like a good idea?

My hunch at the moment is that this is some kind of post processing that kicks in by the monitor in wide colour gamut modes as we’re seeing similar things with other users when they use HDR and yet HDR on mine is fine.

Let me do the tests and see if I can repro the issue you’re seeing.

In the meantime itd be good to see if you can see the issues you’re seeing in a screen grab i.e with the snipping tool as this will separate out what is monitor and what is shader ie if we see the same errors in the screen grab we know the problem is in the shader and if not then the problem is at some point down stream ie gfx card, driver, port, cable or monitor.

2 Likes

I’ve been doing all my tests in the 240p test suite “White & RGB Screen”, below is from the White screen.

I’ve made a GPU screenshot using glcore and it is showing the mask, I magnified a cutout below a number of times.

The main difference between the two is when the output that I see on screen in macro mode is good (clean subpixel RGB output) then the mask snapshot is saturated like below:

mask-good

When the output I see on screen is with “messed up” subpixel layout, then the GPU screenshot show me a desaturated mask snapshot like below.

mask-wrong

The monitor output of the two snaps above correlates to the pictures I showed earlier:

2 Likes

Great stuff - can you lower the gamma out to 2.2 for me when dci-p3 is selected and do another screen grab.

I’ll take a look at the desaturated one for colour values - I’m looking for non pure magenta green values i.e they should be just red and blue with 0 green in the first channel and green with red and blue channels being 0 in the second channel.

1 Like

@rafan just tested it and I’m wrong in that it’s further down stream - there’s definitely something wrong in the shader. I’ll investigate further. Looks to be mangling the blue channel

3 Likes

Brilliant found the problem! I’d copied the XYZ_To_DCIP3 from the Kronos group without transposing it as you need to. Simply transposing the matrix solves the issues I’m seeing with DCI-P3.

I’ll submit this fix right away and see where we get to. Again thanks for preservering with this Rafan it’s greatly appreciated.

It may seem I’m ignoring people at times it’s just I’ve got very small amounts of time I can devote towards this and so have to rely on others to try things out. Many thanks again.

3 Likes

Just created a pull request for Sony Megatron 3.7 with fixed DCI-P3 colour space. Contrary to what I just said above it wasn’t the transpose issue (I have no idea how I fooled myself into thinking it was but anyway). Instead it was simply the DCI-P3 to linear incorrectly applying the gamma - it basically created a new channel because it was writing out a vector with a 1 at the end (I think anyway).

Regardless its now fixed as far as I’ve tested. Thanks again to @rafan for identifying this problem.

1 Like

Family comes first! No pressure!

I’ve been following this and I’m looking forward to redoing my OLED Mask tests to see if there’s an improvement after the fix.

Remember us OLED users have been getting similar strange colour anomalies when HDR is enabled but things look pretty clean in SDR mode.

Thanks @rafan, @GPDP1 and @MajorPainTheCactus!

Keep up the great work!

1 Like

Thanks for your kind words @Cyber

So one thing to bear in mind with this issue was that it was visible in the screen grab’s I did (and we’re also yet to confirm whether it fixes @rafan’s issue). What I’m trying to say is that I think we proved the OLED issue was with the TV rather than the shader as it wasn’t apparent in any of the screen grabs we did. Am I right in saying that? If I’m wrong in saying that we can certainly take another look at it as I’d be keen to resolve any bugs.

2 Likes

Thanks @MajorPainTheCactus for further investigations.

Unfortunately the issue with the masks in DCI-P3 is not fixed.

Below is an extended testcase that dives into the interaction between color gamut mapping and mask quality. I’m sure this will convince you there’s an issue in the color gamut mapping and interaction with the masks.

The essence is that colour gamut mapping of 709 (or any smaller gamut than DCI-P3) to a wide gamut like DCI-P3 or 2020 is not doing any “true” primary conversion, but instead is just scaling the RGB values. This is the intended way color gamut mapping works I think, but for our use or because of the current shader (mask?) implementation it is having an adverse effect on mask quality unfortunately.

In fact I’m quite sure ANY color mapping of a less saturated space into a more saturated (i.e. wide gamut) space is negatively affecting mask quality in the current shader, which would include the HDR shaders, as mapping 709 to 2020 is their default.

I’ll let the results below speak for themselves. The essence of space “A” and “B” are that they’re the same as DCI-P3, except for the green primary: they are less saturated as pictured below. That way we can single out issues by only having to focus on the green channel.

To replicate my results please add the following three color matrices to “gamma_correct.h”:

// DCI-P3
const mat3 kDCIP3_to_XYZ = mat3(  
0.4865709486f,	0.2656676932f,	0.1982172852f,
0.2289745641f,	0.6917385218f,	0.0792869141f,
0.0000000000f,	0.0451133819f,	1.0439443689f);
    
// DCI-P3 "A" -- Green primary changed to Gx=0.28, Gy=0.50
const mat3 kDCIP3A_to_XYZ = mat3(   
0.3795617857f,	0.4279562091f,	0.1429379323f,
0.1786173109f,	0.7642075162f,	0.0571751729f,
0.0000000000f,	0.3362513071f,	0.7528064436f);
 
 // DCI-P3 "B" -- Green primary changed to Gx=0.30, Gy=0.40
const mat3 kDCIP3B_to_XYZ = mat3(
0.2190998562f,	0.6475197396f,	0.0838363312f,
0.1031058147f,	0.8633596528f,	0.0335345325f,
0.0000000000f,	0.6475197396f,	0.4415380111f);

and replace the “else” for the DCI-P3 conversion by this:

  else
  {
     //const vec3 dcip3_colour = (scanline_colour * k709_to_XYZ) * kXYZ_to_DCIP3;
     //const vec3 dcip3_colour = (scanline_colour * kDCIP3_to_XYZ) * kXYZ_to_DCIP3;
     //const vec3 dcip3_colour = (scanline_colour * kDCIP3A_to_XYZ) * kXYZ_to_DCIP3;
     const vec3 dcip3_colour = (scanline_colour * kDCIP3B_to_XYZ) * kXYZ_to_DCIP3;      
     gamma_out = LinearToDCIP3(dcip3_colour);

As for the preset, I’ve used your default 2730-sdr preset and changed nothing EXCEPT for

  • colour space to DCI-P3
  • colour system to “0” (709), as the NTSC-J mapping is also causing adverse effect on mask output, although less noticable.

I’ve attached the preset with these two changes at the end.

So below are three results, first DCIP3 to DCIP3, then DCIP3A to DCIP3 and then DCIP3B to DCIP3.

DCIP3 to DCIP3ALL OK since there’s no color scaling happening

 //const vec3 dcip3_colour = (scanline_colour * k709_to_XYZ) * kXYZ_to_DCIP3;
 const vec3 dcip3_colour = (scanline_colour * kDCIP3_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3A_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3B_to_XYZ) * kXYZ_to_DCIP3;  

240p WHITE test screen

0dcip3todcip3_white

240p GREEN test screen

0dcip3todcip3_green

GPU snapshot of mask, note green is (0,255,0)

0dcip3todcip3_mask

DCIP3A to DCIP3 - ISSUE, red and blue subpixels light up left and right of the green subpixel.

 //const vec3 dcip3_colour = (scanline_colour * k709_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3_to_XYZ) * kXYZ_to_DCIP3;
 const vec3 dcip3_colour = (scanline_colour * kDCIP3A_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3B_to_XYZ) * kXYZ_to_DCIP3;  

240p WHITE test screen

0dcip3todcip3A_white

240p GREEN test screen

0dcip3todcip3A_green

GPU snapshot of mask, note green has become less saturated (142,255,56)

0dcip3todcip3A_mask_(142,255,156)

DCIP3B to DCIP3 - ISSUE gets worse, red and blue subpixels light up brighter left and right of the green subpixel.

 //const vec3 dcip3_colour = (scanline_colour * k709_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3_to_XYZ) * kXYZ_to_DCIP3;
 //const vec3 dcip3_colour = (scanline_colour * kDCIP3A_to_XYZ) * kXYZ_to_DCIP3;
 const vec3 dcip3_colour = (scanline_colour * kDCIP3B_to_XYZ) * kXYZ_to_DCIP3;  

240p WHITE test screen

0dcip3todcip3B_white

240p GREEN test screen

0dcip3todcip3B_green

GPU snapshot of mask, note green has become even less saturated (203,255,206)

0dcip3todcip3B_mask_(203,255,206)

Preset used for testing:

hcrt_hdr = "0.000000"
hcrt_colour_space = "2.000000"
hcrt_colour_system = "0.000000"
hcrt_brightness = "0.150000"
hcrt_gamma_in = "2.000000"
hcrt_red_vertical_convergence = "-0.140000"
hcrt_red_scanline_min = "0.550000"
hcrt_red_scanline_max = "0.820000"
hcrt_red_scanline_attack = "0.650000"
hcrt_green_scanline_min = "0.550000"
hcrt_green_scanline_max = "0.900000"
hcrt_green_scanline_attack = "0.130000"
hcrt_blue_scanline_min = "0.720000"
hcrt_blue_scanline_attack = "0.650000"
hcrt_red_beam_attack = "0.720000"
hcrt_green_beam_sharpness = "1.600000"
hcrt_green_beam_attack = "0.800000"
hcrt_blue_beam_sharpness = "1.900000"
hcrt_blue_beam_attack = "0.450000"
3 Likes

Hi @rafan, so I’m yet to fully digest the above but from my tests of the green screen I’m definitely seeing another issue. I see the red channel appearing in all colour systems:

I’m going to investigate where this is coming from as I wouldn’t expect it. Maybe I just need to move this all before the mask and scanlines but not sure yet.

4 Likes

Yes the red channel appearing is also related to gamut mapping.

If you change Colour System from NTSC-J to r709 then the red hue disappears.

3 Likes

Ah actually the red channel is probably right as this is a RGBX mask. The NTSC-J is applied before and shifts the image to a warmer temperature which is correct.

1 Like

I’m not sure I understand you. NTSC-J (9300K) is actually cooler than 709 (6500K), so how would going to NTSC-J shift the image to a warmer temperature?

1 Like

Yes the slightly tipsy topsy world of colour temperature got the better of me where a hotter temp of 9300 Kelvin vs a cooler 6500 Kelvin results in a bluer image than a redder one which just trips me up sometimes. The maths is correct in respect - when you select NTSC-J it is a bluer image.

So going back to the red appearing it isn’t because of the colour temp as I had suspected. I’ll investigate further where that red is coming from but now I suspect the transform from Rec.601 space to XYZ rather than from rec.709 to XYZ.

2 Likes

Ah I see, yeah so we’re talking color temp.

With regards to the matrices, I would not be surprised if that’s all fine.

Could you possibly take me shortly through how the magenta-green mask application works, how/when does a subpixel get masked and does the magenta and green in the mask need to be fully saturated to work properly?

2 Likes