Sony Megatron Colour Video Monitor

Very nice! I was gonna try my hand at it today, but it seems like you saved me the trouble. Works very well, to be sure.

Are you sure about this? I was able to blend it just fine by using Mixed NTSC Phase, with resolution scaling set to 0.75:

Only problem is, at least with the SNES9X core, it gets mighty blurry when you go into a screen that uses the regular 256x224 resolution. I don’t know if that’s correct. I certainly don’t remember the game looking sharper or blurrier when entering certain screens back in the day. Perhaps there’s something to be said for having the game always output in 512x224 like the old bsnes-accuracy core would.

One other thing: might it be possible to remove the Remove Scanline Spike parameter? I don’t think it does anything here outside the context of guest’s original preset, and in fact I seem to remember it being placed elsewhere in guest’s other presets, but it’s placed in the filtering options here for some reason, so to clean it up just a bit further, it might make sense to get rid of it entirely for this preset.

2 Likes

So just created a pull request for V5.6 of the Sony Megatron.

The major addition is that of the phosphor set primaries that @Azurfel pointed out were missing. The reason why this change has seemingly leap frogged other changes is because it was dead simple to do and an important addition. The other change I made was with regards to @cyber comment about trying to do something about the fact HDR needs both peak and paper white luminance set. Essentially all I’ve managed to do is jiggle around the order of the settings so that they are first.

I was planning on adding rotation of the scanlines and masks but this requires a bit of work that I’ve not found time for yet BUT I will do it.

Thanks again for all the support!

3 Likes

@GPDP1 Just tried your above patch using @guest.r NTSC simulation and it is an incredible signal simulation - really well done Guest. I’m definitely thrown back to my 1980’s bedroom on my RF CRT. I need to play some Spectrum games now (of course the best computer ever made - what more could you want than a rubber keyboard built in - actually scratch that - its the best thing humanity has ever made - we peaked)

2 Likes

Here’s the PVM 1910 SDR Megatron preset modified to use the 600 TVL mask (for more brightness) and 6500K color temperature (personal preference), combined with the NTSC preset modified to minimize artifacts and maximize sharpness while still fully blending the pseudo-hires dithering:

2 Likes

I can’t reproduce any problems with transparency on kirby’s dreamland 3 using snes9x or bsnes with the ntsc-adaptive that’s in the repo.

2 Likes

This is what i get with freshly updated shaders regardless of core:

ntsc-adaptive

scale_type_x0 = "absolute"
scale_x0 = "1024"

Turns out i was using outdated shaders tho, so a more current version of crt-guest-advanced-ntsc-pass1a.slang:

#version 450

/*
   CRT - Guest - NTSC - Pass1 (Decoupled)
   
   Copyright (C) 2018-2023 guest(r) - [email protected]

   Incorporates many good ideas and suggestions from Dr. Venom.
   I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float SIGMA_HOR;
	float HSHARPNESS;
	float S_SHARP;
	float HARNG;
	float HSHARP; 
	float spike;
	float internal_res;
	float MAXS;
} params;

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;


#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0

#pragma parameter HSHARPNESS "          Horizontal Filter Range" 1.5 1.0 8.0 0.05
#define HSHARPNESS params.HSHARPNESS

#pragma parameter SIGMA_HOR "          Horizontal Blur Sigma" 0.90 0.1 7.0 0.05
#define SIGMA_HOR params.SIGMA_HOR

#pragma parameter S_SHARP "          Substractive Sharpness" 0.9 0.0 2.0 0.10
#define S_SHARP params.S_SHARP

#pragma parameter HSHARP "          Sharpness Definition" 1.2 0.0 2.0 0.10
#define HSHARP params.HSHARP

#pragma parameter MAXS "          Maximum Sharpness" 0.15 0.0 0.30 0.01
#define MAXS params.MAXS 

#pragma parameter HARNG "          Substractive Sharpness Ringing" 0.4 0.0 4.0 0.10
#define HARNG params.HARNG 

#pragma parameter spike "          Scanline Spike Removal" 1.0 0.0 2.0 0.10
#define spike params.spike

#define COMPAT_TEXTURE(c,d) texture(c,d)
#define TEX0 vTexCoord

#define OutputSize params.OutputSize
#define gl_FragCoord (vTexCoord * OutputSize.xy)

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord * 1.00001;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D NtscPass;

float invsqrsigma = 1.0/(2.0*SIGMA_HOR*SIGMA_HOR);

float gaussian(float x)
{
	return exp(-x*x*invsqrsigma);
}

void main()
{	
	vec2 prescalex = vec2(textureSize(NtscPass, 0)) / params.OriginalSize.xy;

	vec4 SourceSize = params.OriginalSize * vec4(2.0*prescalex.x, prescalex.y, 0.5/prescalex.x, 1.0/prescalex.y);

	float f = fract(SourceSize.x * vTexCoord.x);
	f = 0.5 - f;
	vec2 tex = floor(SourceSize.xy * vTexCoord)*SourceSize.zw + 0.5*SourceSize.zw;
	vec3 color = 0.0.xxx;
	float scolor = 0.0;
	vec2 dx  = vec2(SourceSize.z, 0.0);

	float w = 0.0;
	float swsum = 0.0;
	float wsum = 0.0;
	vec3 pixel;

	float hsharpness = HSHARPNESS;
	vec3 cmax = 0.0.xxx;
	vec3 cmin = 1.0.xxx;
	float sharp = gaussian(hsharpness) * S_SHARP;
	float maxsharp = MAXS;
	float FPR = hsharpness;
	float fpx = 0.0;
	float sp = 0.0;
	float sw = 0.0;

	float ts = 0.025;
	vec3 luma = vec3(0.2126, 0.7152, 0.0722); 

	float LOOPSIZE = ceil(2.0*FPR);
	float CLAMPSIZE = round(2.0*LOOPSIZE/3.0);
	
	float n = -LOOPSIZE;
	
	do
	{
		pixel  = COMPAT_TEXTURE(NtscPass, tex + n*dx).rgb;
		sp = max(max(pixel.r,pixel.g),pixel.b);
		
		w = gaussian(n+f) - sharp;
		fpx = abs(n+f-sign(n)*FPR)/FPR;
		if (abs(n) <= CLAMPSIZE) { cmax = max(cmax, pixel); cmin = min(cmin, pixel); }
		if (w < 0.0) w = clamp(w, mix(-maxsharp, 0.0, pow(clamp(fpx,0.0,1.0), HSHARP)), 0.0);
	
		color = color + w * pixel;
		wsum  = wsum + w;

		sw = max(w, 0.0) * (dot(pixel,luma) + ts); 
		scolor = scolor + sw * sp;
		swsum = swsum + sw;
		
		n = n + 1.0;
			
	} while (n <= LOOPSIZE);

	color = color / wsum;
	scolor = scolor / swsum;
	
	color = clamp(mix(clamp(color, cmin, cmax), color, HARNG), 0.0, 1.0); 
	
	scolor = clamp(mix(max(max(color.r, color.g),color.b), scolor, spike), 0.0, 1.0);
	
	FragColor = vec4(color, scolor);
}

or with scanline spike commented out as @GPDP1 suggested

#version 450

/*
   CRT - Guest - NTSC - Pass1 (Decoupled)
   
   Copyright (C) 2018-2023 guest(r) - [email protected]

   Incorporates many good ideas and suggestions from Dr. Venom.
   I would also like give thanks to many Libretro forums members for continuous feedback, suggestions and caring about the shader.
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   
*/

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
	float SIGMA_HOR;
	float HSHARPNESS;
	float S_SHARP;
	float HARNG;
	float HSHARP; 
// 	float spike;
	float internal_res;
	float MAXS;
} params;

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;


#pragma parameter bogus_filtering "[ FILTERING OPTIONS ]: " 0.0 0.0 1.0 1.0

#pragma parameter HSHARPNESS "          Horizontal Filter Range" 1.5 1.0 8.0 0.05
#define HSHARPNESS params.HSHARPNESS

#pragma parameter SIGMA_HOR "          Horizontal Blur Sigma" 0.90 0.1 7.0 0.05
#define SIGMA_HOR params.SIGMA_HOR

#pragma parameter S_SHARP "          Substractive Sharpness" 0.9 0.0 2.0 0.10
#define S_SHARP params.S_SHARP

#pragma parameter HSHARP "          Sharpness Definition" 1.2 0.0 2.0 0.10
#define HSHARP params.HSHARP

#pragma parameter MAXS "          Maximum Sharpness" 0.15 0.0 0.30 0.01
#define MAXS params.MAXS 

#pragma parameter HARNG "          Substractive Sharpness Ringing" 0.4 0.0 4.0 0.10
#define HARNG params.HARNG 

// #pragma parameter spike "          Scanline Spike Removal" 1.0 0.0 2.0 0.10
// #define spike params.spike

#define COMPAT_TEXTURE(c,d) texture(c,d)
#define TEX0 vTexCoord

#define OutputSize params.OutputSize
#define gl_FragCoord (vTexCoord * OutputSize.xy)

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord * 1.00001;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D NtscPass;

float invsqrsigma = 1.0/(2.0*SIGMA_HOR*SIGMA_HOR);

float gaussian(float x)
{
	return exp(-x*x*invsqrsigma);
}

void main()
{	
	vec2 prescalex = vec2(textureSize(NtscPass, 0)) / params.OriginalSize.xy;

	vec4 SourceSize = params.OriginalSize * vec4(2.0*prescalex.x, prescalex.y, 0.5/prescalex.x, 1.0/prescalex.y);

	float f = fract(SourceSize.x * vTexCoord.x);
	f = 0.5 - f;
	vec2 tex = floor(SourceSize.xy * vTexCoord)*SourceSize.zw + 0.5*SourceSize.zw;
	vec3 color = 0.0.xxx;
	float scolor = 0.0;
	vec2 dx  = vec2(SourceSize.z, 0.0);

	float w = 0.0;
	float swsum = 0.0;
	float wsum = 0.0;
	vec3 pixel;

	float hsharpness = HSHARPNESS;
	vec3 cmax = 0.0.xxx;
	vec3 cmin = 1.0.xxx;
	float sharp = gaussian(hsharpness) * S_SHARP;
	float maxsharp = MAXS;
	float FPR = hsharpness;
	float fpx = 0.0;
	float sp = 0.0;
	float sw = 0.0;

	float ts = 0.025;
	vec3 luma = vec3(0.2126, 0.7152, 0.0722); 

	float LOOPSIZE = ceil(2.0*FPR);
	float CLAMPSIZE = round(2.0*LOOPSIZE/3.0);
	
	float n = -LOOPSIZE;
	
	do
	{
		pixel  = COMPAT_TEXTURE(NtscPass, tex + n*dx).rgb;
		sp = max(max(pixel.r,pixel.g),pixel.b);
		
		w = gaussian(n+f) - sharp;
		fpx = abs(n+f-sign(n)*FPR)/FPR;
		if (abs(n) <= CLAMPSIZE) { cmax = max(cmax, pixel); cmin = min(cmin, pixel); }
		if (w < 0.0) w = clamp(w, mix(-maxsharp, 0.0, pow(clamp(fpx,0.0,1.0), HSHARP)), 0.0);
	
		color = color + w * pixel;
		wsum  = wsum + w;

		sw = max(w, 0.0) * (dot(pixel,luma) + ts); 
		scolor = scolor + sw * sp;
		swsum = swsum + sw;
		
		n = n + 1.0;
			
	} while (n <= LOOPSIZE);

	color = color / wsum;
	scolor = scolor / swsum;
	
	color = clamp(mix(clamp(color, cmin, cmax), color, HARNG), 0.0, 1.0); 
	
// 	scolor = clamp(mix(max(max(color.r, color.g),color.b), scolor, spike), 0.0, 1.0);
	
	FragColor = vec4(color, scolor);
}
2 Likes

This is par for the course when dropping the NTSC Scale so low for the purposes of perfect pseudo transparency blending.

First try increasing the Chroma/Blending Scale. After that the simple but effective solution to this is to adjust one or more of the sharpness settings until things become legible again.

Amazingly, the transparency effects should remain intact.

As a matter of fact I wouldn’t be surprised if CRT’s used analog circuitry to sharpen up the signal. I remember adjusting a screw on some old AOC monitors to do just that and many sets had sharpness controls.

I think Toshiba sets had some sharpening circuitry as well.

Try it and see.

1 Like

Oh, believe me, I do all of those things (see my last pic). Again, though, the issue is the screens that use pseudo-hires for transparency effects look just fine, but the ones that don’t look a lot blurrier, since they’re running at a lower horizontal resolution. Again, unless someone with access to real hardware and a CRT can confirm otherwise, I don’t think this is what’s supposed to happen.

2 Likes

Well this is something that I have noticed as well. It’s very easy to see this in Turbo Duo (PC-Engine) games. 2 Phase at the same Resolution Scale settings is always blurrier than 3 phase. So with multi-res games you end up with the phase switching but you have to use the tuning for whichever phase type you had set when you did your tuning.

This is something I was going to bring up at some point because I’ve heard people saying that PC-Engine uses 2 Phase NTSC but with the NTSC Adaptive algorithm this system uses 2 and 3 phase NTSC in many games.

I think the solution to this would be to have independent Resolution Scale (and Chroma/Blending Scale) controls for each phase type. What do you think about this @guest.r, @hunterk et al?

In the interim you can just use the discrete Phase settings. So for Turbo Duo (PCE) Phase Type 2, Merge Fields 1. For SNES and NES Phase Type 3, Merge Fields 1. For Sega Genesis, Phase Type 2, Merge Fields 0.

1 Like

AFAIK, which phase to use is entirely dependent on the resolution, which is why ntsc-adaptive exists. That is, for cores/games that change resolution mid-game, so you would have to keep swapping between 3-phase and 2-phase presets any time it changes.

2 Likes

Fair enough but does this resolution dependent algorithm mimic the behaviour of actual consoles or was it just a convenient but rudimentary solution to the problem of how to be able to have just one NTSC Shader/Preset to be able to work for different Phase Types instead of requiring 2 separate shaders?

The current behaviour leaves a little to be desired due to the huge change in blur at the same Resolution Scale settings (because there is only a single, united Resolution Scale setting for both phase types).

Having 2 sets of Resolution Scale and Chroma/Blending Scale controls would allow us to address this inconsistency so we can set whatever Resolution Scale we want for 2 Phase and we can set whatever Resolution Scale we want for 3 Phase so that if the phase changes mid game the level of blur between the different phases would be whatever we desire.

Even if the current behaviour is the most accurate compared to real hardware, it shouldn’t hurt to give users a bit more leeway to fine tune things however they desire.

The alternative to this is that the weighting or the offset could be adjusted internally (or manually) in the final resolution scale used so that if the Resolution Scale is set to 1 for example, if a user (or the Auto algorithm) switches between both Phase types mid game, the effective resolution and level of blur as well as the Chroma/Blending Scale will be consistent for both Phase Types.

Currently, a Resolution Scale settings of 1.0 blends basically almost all types of dither patterns across Sega Genesis, Turbo Duo and possibly many other consoles’ games once the Phase Type is set to 2 Phase Mode. However, this doesn’t blend SNES pseudo transparency in HiRes mode using 3 Phase Mode. Instead an NTSC Scale of 0.75 needs to be used to achieve the same level of blending as 1.0 in 2 Phase Mode.

This creates the problem in multi-res games which would trigger a Phase Type change as resolution changes because we only have a single NTSC Scale setting to govern both 2 Phase and 3 Phase and we just lowered it to 0.75.

0.75 might still leave things sharp and in focus enough for HiRes mode to remain comfortably playable but when the Resolution changes and things switch to low res mode and 2 Phase, that 0.75 setting might make things a bit inconsistent, blurry and somewhat jarring.

I’m unaware of ever noticing this type of behaviour on real hardware. My suggestions above are all aimed at addressing this.

1 Like

Oh wow - sorry I missed this earlier. Ill have to give your release a whirl. Sounds great!

1 Like

It’s just the way the filters were calculated. It’s the resolution scale options that are not really a thing IRL, so I was reluctant to add them into ntsc-adaptive in the first place. If we need to add in another hack on top of that hack, I’d rather make it automatic (e.g., if(width > 400) scale *= 0.75;) than add in 4 more settings for something that doesn’t have any reflection in actual hardware.

2 Likes

@MajorPainTheCactus Do you have the old versions of Megatron archived somewhere? I’m trying to track down when something stopped working for my setup.

1 Like

you can access the repo’s history for the ‘hdr’ directory here: https://github.com/libretro/slang-shaders/commits/master/hdr and you can click on one of the buttons next to a revision to browse the repository’s state at that time.

3 Likes

So it’s possible that i am somehow missing something settings-wise, but it appears that the implementation of the rec. 601 transfer function in 4.2 is noticeably brightening dark greys relative to other dark colors.

The intro to Super Ghouls 'n Ghosts provides an excellent example:

4.1

4.1 resized

4.2+ (image taken using 5.6)

4.2+ resized

At least on my system, no combination of peak luminescence, paper white, brightness, gamma, and/or inverse gamma cutoff settings seem to result in the very dark grey of the mountainside actually being darker that those violet gradients.

1 Like

Wait. I just realized: Is a Rec 601 transfer function into linear space even (automatically) correct there? Aren’t most (if not all) extant cores outputting an sRGB image? Shouldn’t the transfer to linear be from non-piecewise sRGB/pure 2.2 gamma, with the transformation into Rec 601 gamma performed in linear space? (Or possibly into linear from user’s choice of pure 2.2, pure 2.4, or piecewise sRGB for maximum flexibility?)

For that matter, would transposing the colorimetry in linear space give better results as well?

1 Like

@hunterk, @guest.r, I deleted my last post because I finally updated to the latest version of CRT-Guest-Advanced-NTSC from the CRT-Guest-Advanced thread and so far it feels like a complete overhaul has been done.

So much seems to have changed that I don’t think any previous assessments I’ve made based on older versions, including the one in the repo would still be valid.

I also need to update my Sony Megatron Color Video Monitor Presets to use the version of the NTSC shader that includes the Filtering Section.

3 Likes

Ill try and repro this - it might be good to try reproing it in the 240p test suite to get to the bottom of where its hoing wrong and how. There are colour and grey ramps in there so it should be easy to see whats going wrong in the ramps.

If you used ‘-1.0 custom’ settings, then the new versions should work out-of-the-box. The most notable change is that presets (svideo, composite, rf) are removed since you must only set artifacting and fringing to the desired value for the same effect.

2 Likes