From 96c592851ab06a91d1c7d5effa51d7c6cc843f9b Mon Sep 17 00:00:00 2001 From: hunterk Date: Fri, 3 May 2019 12:31:31 -0500 Subject: [PATCH] update crt-guest-dr-venom --- crt/crt-guest-dr-venom-fast.slangp | 40 ++ crt/crt-guest-dr-venom.slangp | 61 ++- crt/shaders/guest/README | 365 ++++++++++++++ crt/shaders/guest/afterglow.slang | 59 ++- crt/shaders/guest/avg-lum.slang | 72 ++- crt/shaders/guest/avg-lum0.slang | 63 +++ crt/shaders/guest/blur_horiz.slang | 24 +- crt/shaders/guest/blur_vert.slang | 25 +- crt/shaders/guest/color-profiles.slang | 160 +++++++ crt/shaders/guest/crt-guest-dr-venom.slang | 449 +++++++++++------- crt/shaders/guest/d65-d50.slang | 55 ++- .../guest/fast/crt-guest-dr-venom-pass1.slang | 126 +++++ .../guest/fast/crt-guest-dr-venom-pass2.slang | 328 +++++++++++++ .../guest/fast/linearize-multipass.slang | 39 ++ crt/shaders/guest/fast/smoothing.slang | 84 ++++ crt/shaders/guest/linearize.slang | 12 +- crt/shaders/guest/lut/README | 1 + crt/shaders/guest/lut/lut.slang | 104 ++++ crt/shaders/guest/lut/other1.png | Bin 0 -> 12811 bytes crt/shaders/guest/lut/sony_trinitron1.png | Bin 0 -> 72062 bytes crt/shaders/guest/lut/sony_trinitron2.png | Bin 0 -> 20266 bytes .../crt-guest-dr-venom-ntsc-composite.slangp | 80 ++++ 22 files changed, 1874 insertions(+), 273 deletions(-) create mode 100644 crt/crt-guest-dr-venom-fast.slangp create mode 100644 crt/shaders/guest/README create mode 100644 crt/shaders/guest/avg-lum0.slang create mode 100644 crt/shaders/guest/color-profiles.slang create mode 100644 crt/shaders/guest/fast/crt-guest-dr-venom-pass1.slang create mode 100644 crt/shaders/guest/fast/crt-guest-dr-venom-pass2.slang create mode 100644 crt/shaders/guest/fast/linearize-multipass.slang create mode 100644 crt/shaders/guest/fast/smoothing.slang create mode 100644 crt/shaders/guest/lut/README create mode 100644 crt/shaders/guest/lut/lut.slang create mode 100644 crt/shaders/guest/lut/other1.png create mode 100644 crt/shaders/guest/lut/sony_trinitron1.png create mode 100644 crt/shaders/guest/lut/sony_trinitron2.png create mode 100644 presets/crt-guest-dr-venom-ntsc-composite.slangp diff --git a/crt/crt-guest-dr-venom-fast.slangp b/crt/crt-guest-dr-venom-fast.slangp new file mode 100644 index 0000000..2f2293e --- /dev/null +++ b/crt/crt-guest-dr-venom-fast.slangp @@ -0,0 +1,40 @@ +shaders = 5 + +shader0 = shaders/guest/lut/lut.slang +filter_linear0 = false +scale_type0 = source +scale0 = 1.0 + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3" +SamplerLUT1 = shaders/guest/lut/sony_trinitron1.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/lut/sony_trinitron2.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/lut/other1.png +SamplerLUT3_linear = true + +shader1 = shaders/guest/fast/smoothing.slang +filter_linear1 = false +scale_type1 = source +scale1 = 1.0 +alias1 = SmoothPass + +shader2 = shaders/guest/fast/linearize-multipass.slang +filter_linear2 = false +scale_type2 = source +scale2 = 1.0 +float_framebuffer2 = true + +shader3 = shaders/guest/fast/crt-guest-dr-venom-pass1.slang +filter_linear3 = false +scale_type_x3 = viewport +scale_x3 = 1.0 +scale_type_y3 = source +scale_y3 = 1.0 +float_framebuffer3 = true + +shader4 = shaders/guest/fast/crt-guest-dr-venom-pass2.slang +filter_linear4 = false +scale_type4 = viewport +scale_x4 = 1.0 +scale_y4 = 1.0 diff --git a/crt/crt-guest-dr-venom.slangp b/crt/crt-guest-dr-venom.slangp index 7aaeb21..f89d232 100644 --- a/crt/crt-guest-dr-venom.slangp +++ b/crt/crt-guest-dr-venom.slangp @@ -1,43 +1,68 @@ -shaders = 7 +shaders = 10 -shader0 = shaders/guest/afterglow.slang +shader0 = shaders/guest/lut/lut.slang filter_linear0 = false scale_type0 = source scale0 = 1.0 -shader1 = shaders/guest/d65-d50.slang +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3" +SamplerLUT1 = shaders/guest/lut/sony_trinitron1.png +SamplerLUT1_linear = true +SamplerLUT2 = shaders/guest/lut/sony_trinitron2.png +SamplerLUT2_linear = true +SamplerLUT3 = shaders/guest/lut/other1.png +SamplerLUT3_linear = true + +shader1 = shaders/guest/color-profiles.slang filter_linear1 = false scale_type1 = source scale1 = 1.0 -alias1 = temp_pass -shader2 = shaders/guest/avg-lum.slang +shader2 = shaders/guest/d65-d50.slang filter_linear2 = false scale_type2 = source scale2 = 1.0 -mipmap_input2 = true -alias2 = lum_pass +alias2 = WhitePointPass -shader3 = shaders/guest/linearize.slang +shader3 = shaders/guest/afterglow.slang filter_linear3 = false scale_type3 = source scale3 = 1.0 -float_framebuffer3 = true -alias3 = linearize_pass +alias3 = AfterglowPass -shader4 = shaders/guest/blur_horiz.slang +shader4 = shaders/guest/avg-lum0.slang filter_linear4 = false scale_type4 = source scale4 = 1.0 -float_framebuffer4 = true -shader5 = shaders/guest/blur_vert.slang +shader5 = shaders/guest/avg-lum.slang filter_linear5 = false scale_type5 = source scale5 = 1.0 -float_framebuffer5 = true +mipmap_input5 = true +alias5 = AvgLumPass -shader6 = shaders/guest/crt-guest-dr-venom.slang -filter_linear6 = true -scale_type6 = viewport -scale6 = 1.0 \ No newline at end of file +shader6 = shaders/guest/linearize.slang +filter_linear6 = false +scale_type6 = source +scale6 = 1.0 +float_framebuffer6 = true +alias6 = LinearizePass + +shader7 = shaders/guest/blur_horiz.slang +filter_linear7 = false +scale_type7 = source +scale7 = 1.0 +float_framebuffer7 = true + +shader8 = shaders/guest/blur_vert.slang +filter_linear8 = false +scale_type8 = source +scale8 = 1.0 +float_framebuffer8 = true + +shader9 = shaders/guest/crt-guest-dr-venom.slang +filter_linear9 = true +scale_type9 = viewport +scale_x9 = 1.0 +scale_y9 = 1.0 diff --git a/crt/shaders/guest/README b/crt/shaders/guest/README new file mode 100644 index 0000000..0532296 --- /dev/null +++ b/crt/shaders/guest/README @@ -0,0 +1,365 @@ + ____ ____ _____ ____ _ ____ __ __ + / ___| _ \_ _| / ___|_ _ ___ ___| |_ | _ \ _ _\ \ / /__ _ __ ___ _ __ ___ + | | | |_) || |_____| | _| | | |/ _ \/ __| __|____| | | | '__\ \ / / _ \ '_ \ / _ \| '_ ` _ \ + | |___| _ < | |_____| |_| | |_| | __/\__ \ ||_____| |_| | |_ \ V / __/ | | | (_) | | | | | | + \____|_| \_\|_| \____|\__,_|\___||___/\__| |____/|_(_) \_/ \___|_| |_|\___/|_| |_| |_| + + CRT - Guest - Dr.Venom + + Copyright (C) 2018-2019 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr.Venom. + + +This guide: Rev 1, April 28th 2019 + + +---------------- +# Introduction # +---------------- + +This shader mimics the look of Cathode Ray Tubes (CRTs) on modern LCD monitors. +Its main goal is to be accurate out of the box, and keep plenty of customization options for the CRT purists to tinker with. That's you if you're reading this :) + +Since the range of shader parameters can be a bit overwhelming we've created this little readme to explain some of the options. + +If you have questions, please don't hesitate to reach out to us! + +Most of the stuff in this guide has been discussed on English Amiga Board here: http://eab.abime.net/showthread.php?t=95969&page=2 + + + +-------------- +# Afterglow # +-------------- + +The RGB phosphors in CRTs have a decay time when going from fully lit to off. When going from bright lit status to off the luminance level instantly falls to about 10%, but then tend to linger at that level for a bit. This is what we call "afterglow". This afterglow of phosphors causes slightly visible trails when bright objects are moving fast on a dark background. + +For an example of this "CRT phosphor afterglowing" see this video on youtube: https://youtu.be/N72uiXFgrh0 + +If you look at the UFO flying by from second 13 to 18 you can clearly see the trail in motion. In the video it looks slightly exaggerated because of the way cameras work. + +The shader allows to control the strength of afterglow per Red, Green, Blue "phospors". Generally the blue phosphor has the least afterglow, so you may want to have red and green a bit stronger than blue. + +Afterglow switch ON/OFF // Turn afterglow feature ON/OFF +Afterglow Red (more is more) // controls the initial brightness of the afterglow on the red phosphor. Higher values makes red more visible in the afterglow. Default is 0.07 +Persistence Red (more is less) // Controls the decay time, i.e. if red should fade away quickly from its initial brightness or slowly. A higher value makes the red phosphor fade away more quickly. Default value is 0.05. +Afterglow Green +Persistence Green +Afterglow Blue +Persistence Blue +Afterglow saturation // Determines the saturation of the RGB afterglow. Generally afterglow has very low saturation so it defaults to a low value of 0.1. Higher values give more saturation. + +------------- +# TATE mode # +------------- + +Yes we do in weird lingo :) In short TATE means "vertical" orientation for arcade monitors. The term ”Tate” is apparently a shortened form of the Japanese verb “tateru,” which means “to stand.” Pronounced “tah-teh,” though the common mispronunciation of “tayte” has gained semi-acceptance. Also commonly spelled with all capital letters (“TATE”), though it is not an acronym. + +In the shader when you set TATE to 1 both the scanlines and mask orientation will rotate by 90 degrees to accommodate to Arcade games that are in vertical orientation, like 1942 shooters and the like. Mostly useful for when running MAME vertical games. + +------------------------- +# Smart Integer Scaling # +------------------------- + +When the video scaling is set to full screen, it may happen that the scanlines appear slightly uneven. To remedy this you can enable this option. It will scale vertically to the nearest suitable integer scale factor. The "smart" part is that it will take into account "overscan" as it appeared on TVs, so it may scale the image slightly larger than the screen size to keep things full screen while also enabling integer scale factor. When it makes use of this overscan up to about 10% of the image may fall outside of the visible area. If the nearest integer larger scale factor would go over this this bound, it chooses the nearest lower integer scale, which will come at the cost of some black bars around the image. Just try and learn :). + +Values are: +0 = off +1 = smart integer scale vertically +2 = smart integer scale vertically and keep aspect ratio + +---------------- +# Raster Bloom # +---------------- + +Raster bloom is a feature that occurs on some CRTs, where the image will slightly expand on brighter images. This may give some extra "pop" to short flashing bright images, like big explosions and such. Almost all CRTs are experiencing raster bloom to some extent, but how much and whether it's really visible depends largely on the CRT model and how much it has aged / been maintained. + +The following two youtube videos show examples of raster blooming. + +The first movie shows metal slug on a CRT TV. If you go to second 75 and look at the bottom right where it says "Credit 04", you'll see how this text gets pushed slightly to the outside of the bezel when the screen gets bright and it coming back in on darker screens. This is sort of the default case where bloom sizes up the image by 1 to 2%. + +https://youtu.be/_K-kTSUaekk + +The next one shows an older TV where the voltage regulation has clearly been diminished. From 1:15 in this youtube: + +https://youtu.be/zbGYwPwf-zA + +The guy is turning the brightness of the monitor up and down. You can see how the white raster expands quite a bit when the brightness on the monitor is turned up and then shrinks back when it's lowered. This is what we call the 10% blooming case. I.e. it has a defect and needs servicing, opposed to the 1 - 2% normal bloom on properly calibrated sets. + +The following parameters control Raster Bloom in the shader: + +Raster bloom % // This determines the raster bloom scale factor. A value of 2 to 3% will result in realistic raster bloom. Larger values will exaggerate the effect, or you're really into mimicking a faulty CRT :). +R. Bloom Overscan Mode // This setting determines whether a fully bright image may push the image outside of the screen / bezel or not. Possible settings: + +(This assumes that automatic full screen scaling is enabled in Retroarch video options.) +0 = Raster bloom is always within the boundaries of the visible screen +1 = holds the middle between option 0 and 2 :) +2 = Raster bloom pushes part of the image outside of the visible area on bright screens. + +Setting 1 and 2 more or less mimic the behavior as seen in the metal slug x video mentioned above. + +------------------------- +# Saturation adjustment # +------------------------- + +This controls how saturated the image is. Higher values give more saturation. This may be useful with emulated systems like e.g. SNES, which tend to have a more saturated image than default. + +-------------------------- +# Gamma In and Gamma Out # +-------------------------- + +Setting Gamma In ("input gamma") to 2.4 affects the following: + +- horizontal interpolation is done in gamma space, where brighter colors spread a bit more over darker ones. Should match interpolation of CRT's. +- scanlines are applied differently +- masks are better distributed over the spectrum + +Generally it's best to keep this value at the default of 2.4 as it mostly concerns a shader internal conversion step where this default value results in desired behavior + + +Gamma Out: + +Of course we have to switch back to the normal color space, so there is the Gamma Out out functionality. In some CRT shaders it's about 10% lower compared with the input gamma. Different input/output gamma values affect saturation and brightness. + +Since there is an option to use neutral input/output gamma (1.0), you can observe the difference within the shader. + +I think most importantly is that this part of the shader functions as intended and can be tweaked to personal preference. + +I think the most catchy part here is that CRT's have this 2.2-2.25 gamma and I defaulted 2.4. It roots in sRGB a bit and I got used to it. You can set output gamma (often referred to as CRT gamma) to 2.25 np. + +From the CRT color research (at the end of this section) it was found that the researched CRTs had a Gamma of 2.25. So to have the shader color profiles displayed properly one has to set the Gamma Out to 2.25. + + +---------------- +# Bright boost # +---------------- + +This setting makes the image brighter. Making the image brighter is useful / necessary when scanlines are enabled, as scanline simulation reduces brightness of the image. + +Good values for "bright boost" are between 1.1 and 1.3 depending on preference. Note that there's a tradeoff, higher values for brightboost make the image brighter, but can cause clipping of colors, making the image become more "flat". On lower color systems, like 8-bit, this clipping may be seen as soon as you go over 1.2. It's best to experiment a bit to see what suits one's own taste. + +------------- +# Scanlines # +------------- + +One of the most distinguishable features of CRTs when run in progressive mode are visible scanlines. I.e. the image is characterized by distinguishable brighter lines (the "scanlines"), and darker/black in-between lines. Sometime people refer to these darker lines when they say scanlines, but the bottom line is the same ;-) + +Since good scanline simulation is one of the most determining aspects of good CRT simulation, there are no less than 5 parameters that control this feature: + +Scanline Type // 0 = normal scanlines; 1 = more intense scanline type ; 2 = more aggressive / accentuated scanline type. Default = 0 +Scanline beamshape // The scanline beamshape "low" and "high" parameters define the look of the scanlines. With these two settings the scanlines can be made thinner or thicker, less or more rounded at the edges and degrees between them. In particular the scanline beamshape low value defines the scanline shape near the middle and the beamshape high value defines the scanline shape near the edges. For example a setting of 5.0,15.0 creates a stronger but more flat like looking scanline. The default values work very well, but you may want to experiment depending on your screen resolution and preference. +Scanline dark // On a real CRT darker scanlines are thinner than bright scanlines. This setting determines by how much. Raising the value makes them thinner. Default is 1.35. +Scanline bright // On a real CRT bright scanlines are thicker than darker scanlines. This setting determines how much thicker. Lowering the values will make them thicker. Default is 1.10. +Increased bright scanline beam // This accentuates the brighter parts or pixels within a single scanline. On a real CRT bright pixels within a scanline will appear thicker. This setting controls by how much. Default is 0.65. + +--------------------------- +# Sharpness and smoothing # +--------------------------- + +The following parameters determine the smoothness versus sharpness balance of the image. + +A real CRT has the peculiar but very nice characteristic that single pixels are smooth, while the overall image is sharp. This opposed to modern LCDs, where both individual pixels and the total image are very sharp. To recreate the soft "pillow shape" phosphor dot characteristics while keeping overall image sharp there 4 parameters that control this balance. + +Horizontal sharpness // This setting determines the overall image sharpness mostly. Higher values create a sharper image. Default is 5.25. +Substractive sharpness // This is a nice "hack" that may be used in combination with "horizontal sharpness". Higher values give more sharpness to pixels and mask. Default is 0.05. +Horizontal Smoothing // This is some candy that blends pixels more that are close in color tint to each other. Gives a nice touch to especially high color systems that have many grades of color (like playstation). +Smart Smoothing Threshold // This works in cooperation with "Horizontal Smoothing". It sets the threshold for how far apart "same" color tints must be for horizontal smoothing to apply. + +------------- +# Curvature # +------------- + +The physical properties of (earlier) shadow mask CRTs make them to have a slightly curved screen. For some of these CRTs the image may appear slightly curved as well, although that largely depends on make and model and how well the set is calibrated. The following two parameters allow for the curvature to be configured in the vertical and horizontal direction. + +CurvatureX // default is OFF. In case you like curvature, a recommended value is 0.03. +CurvatureY // Default is OFF. In case you like curvature, a recommended value is 0.04. + +-------- +# Glow # +-------- + +Phosphors typically create a small surrounding glow on objects. This is especially noticeable when bright objects are shown on a dark background, and even more so when watching a CRT in a dim environment. Whether this glow exists because the light emission gets scattered slightly in the front glass of the tube or something else we would like to know :). + +In the shader the glow strength, radius and grade (fall-off) can be configured. + +Glow Strength // Determines the overall strength of the glow . Default is 0.02. +H. Glow Radius // Determines the radius of the glow in horizontal direction. Higher values create a bigger radius. Default is 4.0. +Horizontal Glow Grade // Determines the grade/ fall-off / fade of the glow. Higher values make the glow fade out more quickly. +V. Glow Radius // Same as horizontal parameter but for vertical. +Vertical Glow Grade // Same as horizontal parameter but for vertical. + +-------- +# Mask # +-------- + +Together with scanlines the second most distinguishable feature of CRTs is the very subtle pattern, also called "mask", apparent in images displayed on a CRT. The type of pattern largely depends on the technology used, shadow mask (dotmask and slotmask) versus aperture grille (trinitron). All these types of masks can be simulated with the shader. + +CRT Mask: 0:CGWG, 1-4:Lottes, 5-6:'Trinitron', 7: slotmask (see below) // The shader allows to set 7 different types of masks + +0 = CGCW - a very light generic mask pattern +1 - 4 = Lottes masks, 4 different types of masks that simulate a shadow mask more or less. +5 - 6 = Trinitron. These are very effective mask types, that closely resemble the appearance of Trinitron's aperture grill. "5" is a finer version, mostly for use on 1080p, "6" is a more coarse version, mostly for use on 4K resolution. +7 = slotmask, to be used in conjunction with the additional slotmask parameters. + +Mask types 1 to 6 strength can be set with these two parameters: + +Lottes maskDark // lower values generally make the mask appear more strongly +Lottes maskLight // higher values generally make the mask appear more strongly + +When using "7" slotmask, below parameters need to be set: + +CRT Mask Size (2.0 is nice in 4k) // set to 1 for 1080p, 2 for 4K resolution +Slot Mask Strength // Overall strength of the slot mask. Good values are around 0.5 +Slot Mask Width // Determines the horizontal size of the slotmask pattern. Use 2 or 3 for 1080p, 4-6 for 4K resolution. +Slot Mask Height: 2x1 or 4x1" // Determines the vertical size of the slotmask pattern. Use 1 for 1080p, or 2 for 4K resolution. + +Finally there's a parameter that influences the look of mask 5 - 7. +Mask 5&6 cutoff // This determines how soon black appears between pixels for colors that are close to the R, G, or B primaries. This is suitable for both Trinitron and Slotmask. Default is 0.2. Higher values create a quicker cutoff to black between these pixels. + +----------------------- +# Color Temperature % # +----------------------- + +Each CRT monitor has a "whitepoint" setting, which influences how warm or cold the colors will look. The whitepoint setting differed quite a bit between different CRT models. Some had a 5000K (D50) whitepoint, i.e. "warm" colors, and some had a 9300K whitepoint,i.e. very "cold" colors. Most will sit somewhere in between, with today's standard being 6500K (D65). Note that a warmer setting tends the greys noticeably to a more yellowish brown, while the colder setting moves it to a more blueish grey. + +At setting 0 the "color temperature %" is equivalent to the D65 whitepoint, let's say "neutral". Lower it to move towards the more warm 5000K point, or raise it to move it more towards the 9300K cold point. + +------------------- +# PVM Like Colors # +------------------- + + PVM Like Colors // This is a bit of candy that tries to simulate some of the color aberrations that appear with Sony PVM and BVM monitors. + + ------------------- +# LUT Colors # +------------------- + +The shader has two ways to simulating "CRT Colors". LUT colors and CRT Color Profiles. LUT colors use lookup tables to transform the sRGB color profile to something more close to CRT colors. These LUTs come from varying sources, and the accuracy regarding CRT color simulation is a bit of an unknown, but at the least they provide some nice alternative color schemes which you may like. + + +--------------------- --------------- +# CRT Color Profile # & # Color Space # +--------------------- --------------- + +"CRT colors" changes the default colors to something more close to what the RGB Phosphors in CRTs produce. + +These profiles are about subtle changes, but if you were used to the display of CRTs you may remember the display having more vivid greens, softer yellows and red, etc. It all depends on the type of CRT you were looking at, but admittedly CRT colors are different from the default sRGB color gamut that is prevalent in today's LCD screens. + +The CRT colors are based on research of the CIE chromaticity coordinates of the most common phosphors used back in the day. Therefore we ended up with 3 "specs" which are EBU standard phosphors, P22-RGB phosphors and the SMPTE-C standard. These three profiles can be selected under "CRT colors" as number 1, 2 and 3. + +Then there are two additional "calibrated" profiles that actually quite closely match a Philips based CRT monitor and a Trinitron monitor. They are profile number 4 and 5. + +Some more information on these profiles can be read below. + +The main drawback currently is that Phosphor color primaries are partly outside the sRGB spectrum, such that for these profiles a "Wide Color Gamut" monitor is needed / recommended. This is what the "Color space" option is for. If you happen to own a monitor that is able to display DCI-P3 color gamut, then set this option to "1". Option "2" is for AdobeRGB and "3" for Rec. 2020. DCI-P3 is verified to be quite accurate. + + +In conclusion: + +The good news is that we've got "CRT colors" largely covered now with the correct specs and two "quite accurate" calibrated profiles. The bad news is that with a default sRGB monitor the profiles will be more or less clipped and look wrong, depending on the content. Then we have some good news, as it seems that DCI-P3 or some other wide color gamut will be part of the HDR-500+ spec (see here: https://displayhdr.org/wp-content/uploads/2019/02/DisplayHDR_SpecChart_Rows_190219.jpg ) So within a few years wide color gamut should become mainstream in monitors. The question remains whether for HDR-500+ certified monitors this wide color gamut can be enabled by the user or emulator, or whether it will be only available in HDR content encoded mode. Time will tell. + +Lastly, let's not forget we are talking about subtle differences from the default sRGB colors here. Let's just say you have to be slightly OCD on CRT tech to really appreciate the difference :-) + +For those interested below is some additional information on each of the profiles and the specific chromaticity values used. This is purely additional information, there is no need (or possibility) to do anything with these in the shader settings. + + +"CRT Colors" for CRT-Guest-Dr-Venom -- Additional information. + +3 x spec +2 x calibration + +Specifications for three standards + +1. EBU Standard Phosphors +// Amongst others used in Sony BVMs and Higher-end PVMs +// Tolerances are described in the E.B.U. standard reference document "E.B.U. standard for chromaticity tolerances for studio monitors" tech-3213-E. +// PVM-1440QM service manual quotes 0.01 as tolerance on the RGB CIE coordinates. + +xb0 = "150.000000" +xg0 = "290.000000" +xr0 = "640.000000" +yb0 = "60.000000" +yg0 = "600.000000" +yr0 = "330.000000" + +Whitepoint is D65 + +X 95,04 --> Xw0 950.4 +Y 100 --> Yw0 1000 +Z 108,88 --> Zw0 1088.8 + +2. P22 Phosphors +// These phosphors are often quoted as the "default" phosphors used in CRTs +// Also used in lower-end PVMs, see Sony PVM-20M4E 20M2E Colour Video Monitor.pdf +// These can still be bought :-) https://www.phosphor-technology.com/crt-phosphors/ -- includes CIE coordinates. + +xb0 = "148.000000" +xg0 = "310.000000" +xr0 = "647.000000" +yb0 = "62.000000" +yg0 = "594.000000" +yr0 = "343.000000" + +3. SMPTE-C +// Spec for most of America. +// I have forgone on the 1953 NTSC standard, as apart from very few early color TV's the 1953 NTSC standard was never actually used. Instead less saturated primaries were used to achieve brighter screens. +// Taken from the WIKI on NTSC (https://en.wikipedia.org/wiki/NTSC) In 1968-69 the Conrac Corp., working with RCA, defined a set of controlled phosphors for use in broadcast color picture video monitors. This specification survives today as the SMPTE "C" phosphor specification: + +xb0 = "155.000000" +xg0 = "310.000000" +xr0 = "630.000000" +yb0 = "70.000000" +yg0 = "595.000000" +yr0 = "340.000000" + +Whitepoint is D65 + +X 95,04 --> Xw0 950.4 +Y 100 --> Yw0 1000 +Z 108,88 --> Zw0 1088.8 + + +4. Calibrated profile for Philips CRT monitors. Of course an approximation, but I'm pleased with the "quite accurate" results. +// Manually calibrated and compared to real Philips based CRT monitors, running side by side with the shader on a 10-bit DCI-P3 gamut panel. This calibrated CRT profile covers amongst others Philips CM8533, Philips VS-0080, and Commodore 1084. +// Note the whitepoint is significantly different from D65. It's closer to 6100K, but clearly not on the blackbody curve. Possibly an ISO-line target, given the slight hue on the whitepoint. Other than that it could be aging / whitepoint drift. I compared four CRT monitors, one of them in very mint condition, and they all have this slight hue on the whitepoint, so I would guess this is how they came out of the factory. But then again since these things are now getting close to 30 years old, who knows? Either way the profile should be good: factory out or true to life aged CRTs... :D. +// It's important this specific whitepoint is used in the shader or the colors will not be accurate. +// Also it's important to note that this profile should be used with "Gamma Out" at 2.25 or the colors will be less accurate. + +xb0 = "154.000000" +xg0 = "300.000000" +xr0 = "635.000000" +yb0 = "60.000000" +yg0 = "620.000000" +yr0 = "339.000000" + +Whitepoint: +Xw0 = "910.000000" +Yw0 = "1000.000000" +Zw0 = "960.000000" + + +5. Calibrated profile for Sony Trinitron Monitor. +// In a similar fashion as the Philips CRT based profile, this is a manually calibrated profile for a Sony Trinitron monitor, model KX-14CP1. +// This monitor uses a Whitepoint that is close to 9300K. The Z value in the calibration process has been raised to the point where the "blue-ishness" of the white matches. To achieve further 9300K white, I guess one has to raise the hardware whitepoint of the host PC monitor... +// It's important this specific whitepoint is used in the shader or the colors will not be accurate. +// Also it's important to note that this profile should be used with "Gamma Out" at 2.25 or the colors will be less accurate. + +xb0 = "152.000000" +xg0 = "279.000000" +xr0 = "647.000000" +yb0 = "60.000000" +yg0 = "635.000000" +yr0 = "335.000000" + +Whitepoint: +Xw0 = "903.000000" +Yw0 = "1000.000000" +Zw0 = "1185.000000" + +End :) + + + + + diff --git a/crt/shaders/guest/afterglow.slang b/crt/shaders/guest/afterglow.slang index 03dc4a3..1f34b5e 100644 --- a/crt/shaders/guest/afterglow.slang +++ b/crt/shaders/guest/afterglow.slang @@ -23,19 +23,7 @@ layout(push_constant) uniform Push { - vec4 SourceSize; - vec4 OriginalSize; - vec4 OutputSize; - uint FrameCount; - float SW; - float AR; - float PR; - float AG; - float PG; - float AB; - float PB; - float sat; - float GTH; + float SW, AR, PR, AG, PG, AB, PB, sat; } params; #pragma parameter SW "Afterglow switch ON/OFF" 1.0 0.0 1.0 1.0 @@ -46,7 +34,17 @@ layout(push_constant) uniform Push #pragma parameter AB "Afterglow Blue" 0.07 0.0 1.0 0.01 #pragma parameter PB "Persistence Blue" 0.05 0.0 1.0 0.01 #pragma parameter sat "Afterglow saturation" 0.10 0.0 1.0 0.01 -#pragma parameter GTH "Afterglow threshold" 5.0 0.0 255.0 1.0 + +#define SW params.SW +#define AR params.AR +#define PR params.PR +#define AG params.AG +#define PG params.PG +#define AB params.AB +#define PB params.PB +#define sat params.sat + +#define COMPAT_TEXTURE(c,d) texture(c,d) layout(std140, set = 0, binding = 0) uniform UBO { @@ -75,30 +73,39 @@ layout(set = 0, binding = 6) uniform sampler2D OriginalHistory4; layout(set = 0, binding = 7) uniform sampler2D OriginalHistory5; layout(set = 0, binding = 8) uniform sampler2D OriginalHistory6; -#define eps 1e-4 +#define Prev1Texture OriginalHistory1 +#define Prev2Texture OriginalHistory2 +#define Prev3Texture OriginalHistory3 +#define Prev4Texture OriginalHistory4 +#define Prev5Texture OriginalHistory5 +#define Prev6Texture OriginalHistory6 + +#define TEX0 vTexCoord + +#define eps 1e-3 vec3 afterglow(float number) { - return vec3(params.AR, params.AG, params.AB)*exp2(-vec3(params.PR, params.PG, params.PB)*vec3(number*number)); + return vec3(AR, AG, AB)*exp2(-vec3(PR, PG, PB)*vec3(number*number)); } void main() { - vec3 color = texture(Source, vTexCoord.xy).rgb; - vec3 color1 = texture(OriginalHistory1, vTexCoord.xy).rgb * afterglow(1.0); - vec3 color2 = texture(OriginalHistory2, vTexCoord.xy).rgb * afterglow(2.0); - vec3 color3 = texture(OriginalHistory3, vTexCoord.xy).rgb * afterglow(3.0); - vec3 color4 = texture(OriginalHistory4, vTexCoord.xy).rgb * afterglow(4.0); - vec3 color5 = texture(OriginalHistory5, vTexCoord.xy).rgb * afterglow(5.0); - vec3 color6 = texture(OriginalHistory6, vTexCoord.xy).rgb * afterglow(6.0); + vec3 color = COMPAT_TEXTURE(Source, TEX0.xy).rgb; + vec3 color1 = COMPAT_TEXTURE(Prev1Texture, TEX0.xy).rgb * afterglow(1.0); + vec3 color2 = COMPAT_TEXTURE(Prev2Texture, TEX0.xy).rgb * afterglow(2.0); + vec3 color3 = COMPAT_TEXTURE(Prev3Texture, TEX0.xy).rgb * afterglow(3.0); + vec3 color4 = COMPAT_TEXTURE(Prev4Texture, TEX0.xy).rgb * afterglow(4.0); + vec3 color5 = COMPAT_TEXTURE(Prev5Texture, TEX0.xy).rgb * afterglow(5.0); + vec3 color6 = COMPAT_TEXTURE(Prev6Texture, TEX0.xy).rgb * afterglow(6.0); vec3 glow = color1 + color2 + color3 + color4 + color5 + color6; float l = length(glow); - glow = normalize(pow(glow + vec3(eps), vec3(params.sat)))*l; + glow = normalize(pow(glow + vec3(eps), vec3(sat)))*l; float w = 1.0; - if ((color.r + color.g + color.b) > params.GTH/255.0) w = 0.0; + if ((color.r + color.g + color.b) > 7.0/255.0) w = 0.0; - FragColor = vec4(color + params.SW*w*glow,1.0); + FragColor = vec4(color + SW*w*glow,1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/avg-lum.slang b/crt/shaders/guest/avg-lum.slang index a171543..55eb90c 100644 --- a/crt/shaders/guest/avg-lum.slang +++ b/crt/shaders/guest/avg-lum.slang @@ -1,9 +1,9 @@ #version 450 /* - Average Luminance Shader + Average Luminance Shader, Smart Smoothing Difference Shader - Copyright (C) 2018 guest(r) - guest.r@gmail.com + Copyright (C) 2018-2019 guest(r) - guest.r@gmail.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,19 +19,22 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - Thanks to HunterK for the mipmap hint. :D + Thanks to HunterK for the mipmap hint. :D */ layout(push_constant) uniform Push { vec4 SourceSize; - vec4 OriginalSize; - vec4 OutputSize; - uint FrameCount; - float grade; + float STH; } params; -#pragma parameter grade "Raster Bloom Grade" 0.65 0.10 1.0 0.05 +#pragma parameter STH "Smart Smoothing Threshold" 0.7 0.4 1.2 0.05 + +#define STH params.STH +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize +#define InputSize SourceSize +#define TEX0 vTexCoord layout(std140, set = 0, binding = 0) uniform UBO { @@ -46,32 +49,61 @@ layout(location = 0) out vec2 vTexCoord; void main() { gl_Position = global.MVP * Position; - vTexCoord = TexCoord; + vTexCoord = TexCoord * 1.0001; } #pragma stage fragment layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D WhitePointPass; + +#define PassPrev2Texture WhitePointPass + +float df (vec3 A, vec3 B) +{ + float diff = length(A-B); + float luma = clamp(length(0.5*min(A,B) + 0.25*(A+B) + 1e-8), 0.0001, 1.0); + float diff1 = diff/luma; + return 1.0 - clamp(7.0*(max(1.5*diff,diff1)-STH), 0.0, 0.9999); +} void main() { - float xtotal = floor(params.SourceSize.x/32.0); - float ytotal = floor(params.SourceSize.y/32.0); - - float ltotal = 0.0; - vec2 dx = vec2(params.SourceSize.z, 0.0)*32.0; - vec2 dy = vec2(0.0, params.SourceSize.w)*32.0; - + float xtotal = floor(InputSize.x/64.0); + float ytotal = floor(InputSize.y/64.0); + + float ltotal = 0.0; + + vec2 dx = vec2(SourceSize.z, 0.0)*64.0; + vec2 dy = vec2(0.0, SourceSize.w)*64.0; + vec2 offset = 0.25*(dx+dy); + for (float i = 0.0; i <= xtotal; i++) { for (float j = 0.0; j <= ytotal; j++) { - ltotal += length(textureLod(Source, i*dx + j*dy, 5.0).rgb); + ltotal+= max(0.25, length(textureLod(Source, i*dx + j*dy + offset, 6.0).rgb)); } - } + } + + ltotal = 0.577350269 * ltotal / ((xtotal+1.0)*(ytotal+1.0)); - ltotal = inversesqrt(3.0)*ltotal / ((xtotal+1.0)*(ytotal+1.0)); + dx = vec2(SourceSize.z, 0.0); + dy = vec2(0.0, SourceSize.w); - FragColor = vec4(pow(ltotal, params.grade)); + vec3 l1 = COMPAT_TEXTURE(PassPrev2Texture, TEX0.xy -dx).xyz; + vec3 ct = COMPAT_TEXTURE(PassPrev2Texture, TEX0.xy ).xyz; + vec3 r1 = COMPAT_TEXTURE(PassPrev2Texture, TEX0.xy +dx).xyz; + vec3 t1 = COMPAT_TEXTURE(PassPrev2Texture, TEX0.xy -dy).xyz; + vec3 b1 = COMPAT_TEXTURE(PassPrev2Texture, TEX0.xy +dy).xyz; + + float dl = df(ct, l1); + float dr = df(ct, r1); + float dt = df(ct, t1); + float db = df(ct, b1); + + float resx = dl; float resy = dr; float resz = floor(9.0*dt)/10.0 + floor(9.0*db)/100.0; + + FragColor = vec4(resx,resy,resz,pow(ltotal, 0.65)); } \ No newline at end of file diff --git a/crt/shaders/guest/avg-lum0.slang b/crt/shaders/guest/avg-lum0.slang new file mode 100644 index 0000000..f2ef96f --- /dev/null +++ b/crt/shaders/guest/avg-lum0.slang @@ -0,0 +1,63 @@ +#version 450 + +// Avg. Luminance Smoothing + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D OriginalHistory1; +layout(set = 0, binding = 3) uniform sampler2D OriginalHistory2; +layout(set = 0, binding = 4) uniform sampler2D OriginalHistory3; +layout(set = 0, binding = 5) uniform sampler2D OriginalHistory4; +layout(set = 0, binding = 6) uniform sampler2D OriginalHistory5; +layout(set = 0, binding = 7) uniform sampler2D OriginalHistory6; +layout(set = 0, binding = 8) uniform sampler2D OriginalHistory7; + +#define PrevTexture OriginalHistory1 +#define Prev1Texture OriginalHistory2 +#define Prev2Texture OriginalHistory3 +#define Prev3Texture OriginalHistory4 +#define Prev4Texture OriginalHistory5 +#define Prev5Texture OriginalHistory6 +#define Prev6Texture OriginalHistory7 + +#define TEX0 vTexCoord +#define COMPAT_TEXTURE(c,d) texture(c,d) + + +void main() +{ + vec3 color = COMPAT_TEXTURE(PrevTexture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev6Texture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev5Texture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev4Texture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev3Texture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev2Texture, TEX0.xy).rgb; + color+= COMPAT_TEXTURE(Prev1Texture, TEX0.xy).rgb; + + FragColor = vec4(color/7.0,1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/blur_horiz.slang b/crt/shaders/guest/blur_horiz.slang index 54e4eb8..53e8363 100644 --- a/crt/shaders/guest/blur_horiz.slang +++ b/crt/shaders/guest/blur_horiz.slang @@ -1,20 +1,21 @@ #version 450 -// Higher value, more centered glow. -// Lower values might need more taps. - layout(push_constant) uniform Push { vec4 SourceSize; vec4 OriginalSize; vec4 OutputSize; uint FrameCount; - float TAPSH; - float GLOW_FALLOFF_H; + float TAPSH; + float GLOW_FALLOFF_H; } params; +// Higher value, more centered glow. +// Lower values might need more taps. #pragma parameter TAPSH "H. Glow Radius" 4.0 1.0 10.0 1.0 -#pragma parameter GLOW_FALLOFF_H "Horizontal Glow Grade" 0.30 0.0 1.0 0.01 +#define TAPSH params.TAPSH +#pragma parameter GLOW_FALLOFF_H "Horizontal Glow Grade" 0.30 0.00 1.0 0.01 +#define GLOW_FALLOFF_H params.GLOW_FALLOFF_H layout(std140, set = 0, binding = 0) uniform UBO { @@ -37,19 +38,22 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -#define kernel(x) exp(-params.GLOW_FALLOFF_H * (x) * (x)) +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize + +#define kernel(x) exp(-GLOW_FALLOFF_H * (x) * (x)) void main() { vec3 col = vec3(0.0); - float dx = params.SourceSize.z; + float dx = SourceSize.z; float k_total = 0.; - for (float i = -params.TAPSH; i <= params.TAPSH; i++) + for (float i = -TAPSH; i <= TAPSH; i++) { float k = kernel(i); k_total += k; - col += k * texture(Source, vTexCoord + vec2(float(i) * dx, 0.0)).rgb; + col += k * COMPAT_TEXTURE(Source, vTexCoord + vec2(float(i) * dx, 0.0)).rgb; } FragColor = vec4(col / k_total, 1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/blur_vert.slang b/crt/shaders/guest/blur_vert.slang index 92dc74e..735de11 100644 --- a/crt/shaders/guest/blur_vert.slang +++ b/crt/shaders/guest/blur_vert.slang @@ -1,20 +1,22 @@ #version 450 -// Higher value, more centered glow. -// Lower values might need more taps. - layout(push_constant) uniform Push { vec4 SourceSize; vec4 OriginalSize; vec4 OutputSize; uint FrameCount; - float TAPSV; - float GLOW_FALLOFF_V; + float TAPSV; + float GLOW_FALLOFF_V; } params; +// Higher value, more centered glow. +// Lower values might need more taps. +// Parameter lines go here: #pragma parameter TAPSV "V. Glow Radius" 4.0 1.0 10.0 1.0 -#pragma parameter GLOW_FALLOFF_V "Vertical Glow Grade" 0.30 0.0 1.0 0.01 +#define TAPSV params.TAPSV +#pragma parameter GLOW_FALLOFF_V "Vertical Glow Grade" 0.30 0.00 1.0 0.01 +#define GLOW_FALLOFF_V params.GLOW_FALLOFF_V layout(std140, set = 0, binding = 0) uniform UBO { @@ -37,19 +39,22 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -#define kernel(x) exp(-params.GLOW_FALLOFF_V * (x) * (x)) +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define SourceSize params.SourceSize + +#define kernel(x) exp(-GLOW_FALLOFF_V * (x) * (x)) void main() { vec3 col = vec3(0.0); - float dy = params.SourceSize.w; + float dy = SourceSize.w; float k_total = 0.; - for (float i = -params.TAPSV; i <= params.TAPSV; i++) + for (float i = -TAPSV; i <= TAPSV; i++) { float k = kernel(i); k_total += k; - col += k * texture(Source, vTexCoord + vec2(0.0, float(i) * dy)).rgb; + col += k * COMPAT_TEXTURE(Source, vTexCoord + vec2(0.0, float(i) * dy)).rgb; } FragColor = vec4(col / k_total, 1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/color-profiles.slang b/crt/shaders/guest/color-profiles.slang new file mode 100644 index 0000000..aef2001 --- /dev/null +++ b/crt/shaders/guest/color-profiles.slang @@ -0,0 +1,160 @@ +#version 450 + +/* + CRT Color Profiles + + Copyright (C) 2019 guest(r) and Dr. Venom + + 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 +{ + float CP, CS; +} params; + +#pragma parameter CP "CRT Color Profile" 0.0 -1.0 5.0 1.0 +#pragma parameter CS "Color Space: sRGB, DCI, Adobe, Rec.2020" 0.0 0.0 3.0 1.0 + +#define CP params.CP +#define CS params.CS + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +const mat3 Profile0 = +mat3( + 0.412391, 0.212639, 0.019331, + 0.357584, 0.715169, 0.119195, + 0.180481, 0.072192, 0.950532 +); + +const mat3 Profile1 = +mat3( + 0.430554, 0.222004, 0.020182, + 0.341550, 0.706655, 0.129553, + 0.178352, 0.071341, 0.939322 +); + +const mat3 Profile2 = +mat3( + 0.396686, 0.210299, 0.006131, + 0.372504, 0.713766, 0.115356, + 0.181266, 0.075936, 0.967571 +); + +const mat3 Profile3 = +mat3( + 0.393521, 0.212376, 0.018739, + 0.365258, 0.701060, 0.111934, + 0.191677, 0.086564, 0.958385 +); + +const mat3 Profile4 = +mat3( + 0.392258, 0.209410, 0.016061, + 0.351135, 0.725680, 0.093636, + 0.166603, 0.064910, 0.850324 +); + +const mat3 Profile5 = +mat3( + 0.377923, 0.195679, 0.010514, + 0.317366, 0.722319, 0.097826, + 0.207738, 0.082002, 1.076960 +); + +const mat3 ToSRGB = +mat3( + 3.240970, -0.969244, 0.055630, +-1.537383, 1.875968, -0.203977, +-0.498611, 0.041555, 1.056972 +); + +const mat3 ToDCI = +mat3( + 2.725394, -0.795168, 0.041242, +-1.018003, 1.689732, 0.022647, +-0.440163, 0.022647, 1.100929 +); + +const mat3 ToAdobe = +mat3( + 2.041588, -0.969244, 0.013444, +-0.565007, 1.875968, -0.11836, +-0.344731, 0.041555, 1.015175 +); + +const mat3 ToREC = +mat3( + 1.716651, -0.666684, 0.017640, +-0.355671, 1.616481, -0.042771, +-0.253366, 0.015769, 0.942103 +); + +void main() +{ + vec3 c = COMPAT_TEXTURE(Source, TEX0.xy).rgb; + + float p; + mat3 m_out; + + if (CS == 0.0) { p = 2.4; m_out = ToSRGB; } else + if (CS == 1.0) { p = 2.6; m_out = ToDCI; } else + if (CS == 2.0) { p = 2.2; m_out = ToAdobe;} else + if (CS == 3.0) { p = 2.4; m_out = ToREC; } + + vec3 color = pow(c, vec3(p)); + + mat3 m_in = Profile0; + + if (CP == 0.0) { m_in = Profile0; } else + if (CP == 1.0) { m_in = Profile1; } else + if (CP == 2.0) { m_in = Profile2; } else + if (CP == 3.0) { m_in = Profile3; } else + if (CP == 4.0) { m_in = Profile4; } else + if (CP == 5.0) { m_in = Profile5; } + + color = m_in*color; + color = m_out*color; + + color = pow(color, vec3(1.0/p)); + + if (CP == -1.0) color = c; + + FragColor = vec4(color,1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/crt-guest-dr-venom.slang b/crt/shaders/guest/crt-guest-dr-venom.slang index a14af5a..fd30344 100644 --- a/crt/shaders/guest/crt-guest-dr-venom.slang +++ b/crt/shaders/guest/crt-guest-dr-venom.slang @@ -4,9 +4,9 @@ CRT - Guest - Dr. Venom Copyright (C) 2018-2019 guest(r) - guest.r@gmail.com - - Incorporates many good ideas and suggestions from Dr. Venom. + Incorporates many good ideas and suggestions from Dr. Venom. + 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 @@ -25,82 +25,92 @@ layout(push_constant) uniform Push { - vec4 SourceSize; - vec4 OutputSize; - float TATE; // Screen orientation - float IOS; // Smart Integer Scaling - float OS; // Do overscan - float BLOOM; // Bloom overscan percentage - float brightboost; // adjust brightness - float saturation; // 1.0 is normal saturation - float gsl; // Alternate scanlines - float scanline; // scanline param, vertical sharpness - float beam_min; // dark area beam min - wide - float beam_max; // bright area beam max - narrow - float h_sharp; // pixel sharpness - float s_sharp; // substractive sharpness - float csize; // corner size - float warpX; // Curvature X - float warpY; // Curvature Y - float glow; // Glow Strength - float shadowMask; // Mask Style - float maskDark; // Dark "Phosphor" - float maskLight; // Light "Phosphor" - float CGWG; // CGWG Mask Strength - float GTW; // Gamma tweak - float gamma_out; // output gamma + float TATE, IOS, OS, BLOOM, brightboost, gsl, scanline1, scanline2, beam_min, beam_max, beam_size, + h_sharp, s_sharp, h_smart, csize, bsize, warpX, warpY, glow, shadowMask, masksize, vertmask, + slotmask, slotwidth, double_slot, mcut, maskDark, maskLight, CGWG, GTW, gamma_out; } params; #pragma parameter TATE "TATE Mode" 0.0 0.0 1.0 1.0 -#define TATE params.TATE -#pragma parameter IOS "Smart Integer Scaling" 0.0 0.0 1.0 1.0 -#define IOS params.IOS -#pragma parameter OS "R. Bloom Overscan Mode" 2.0 0.0 2.0 1.0 -#define OS params.OS +#define TATE params.TATE // Screen orientation +#pragma parameter IOS "Smart Integer Scaling: 1.0:Y, 2.0:'X'+Y" 0.0 0.0 2.0 1.0 +#define IOS params.IOS // Smart Integer Scaling +#pragma parameter OS "R. Bloom Overscan Mode" 1.0 0.0 2.0 1.0 +#define OS params.OS // Do overscan #pragma parameter BLOOM "Raster bloom %" 0.0 0.0 20.0 1.0 -#define BLOOM params.BLOOM -#pragma parameter brightboost "Bright boost" 1.10 0.50 2.00 0.01 -#define brightboost params.brightboost -#pragma parameter saturation "Saturation adjustment" 1.0 0.1 2.0 0.05 -#define saturation params.saturation -#pragma parameter gsl "Alternate scanlines" 0.0 0.0 1.0 1.0 -#define gsl params.gsl -#pragma parameter scanline "Scanline adjust" 8.0 1.0 12.0 1.0 -#define scanline params.scanline -#pragma parameter beam_min "Scanline dark" 1.30 0.5 2.0 0.05 -#define beam_min params.beam_min -#pragma parameter beam_max "Scanline bright" 1.0 0.5 2.0 0.05 -#define beam_max params.beam_max -#pragma parameter h_sharp "Horizontal sharpness" 5.0 1.5 20.0 0.25 -#define h_sharp params.h_sharp -#pragma parameter s_sharp "Substractive sharpness" 0.0 0.0 0.20 0.01 -#define s_sharp params.s_sharp -#pragma parameter csize "Corner size" 0.0 0.0 0.05 0.01 -#define csize params.csize +#define BLOOM params.BLOOM // Bloom overscan percentage +#pragma parameter brightboost "Bright boost" 1.35 0.50 2.00 0.01 +#define brightboost params.brightboost // adjust brightness +#pragma parameter gsl "Scanline Type" 0.0 0.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines +#pragma parameter scanline1 "Scanline beam shape low" 8.0 1.0 15.0 1.0 +#define scanline1 params.scanline1 // scanline param, vertical sharpness +#pragma parameter scanline2 "Scanline beam shape high" 8.0 5.0 23.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness +#pragma parameter beam_min "Scanline dark" 1.35 0.5 2.0 0.05 +#define beam_min params.beam_min // dark area beam min - narrow +#pragma parameter beam_max "Scanline bright" 1.05 0.5 2.0 0.05 +#define beam_max params.beam_max // bright area beam max - wide +#pragma parameter beam_size "Increased bright scanline beam" 0.65 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size +#pragma parameter h_sharp "Horizontal sharpness" 5.25 1.5 20.0 0.25 +#define h_sharp params.h_sharp // pixel sharpness +#pragma parameter s_sharp "Substractive sharpness" 0.05 0.0 0.20 0.01 +#define s_sharp params.s_sharp // substractive sharpness +#pragma parameter h_smart "Smart Horizontal Smoothing" 0.0 0.0 1.0 0.1 +#define h_smart params.h_smart // smart horizontal smoothing +#pragma parameter csize "Corner size" 0.0 0.0 0.07 0.01 +#define csize params.csize // corner size +#pragma parameter bsize "Border smoothness" 600.0 100.0 600.0 25.0 +#define bsize params.bsize // border smoothness #pragma parameter warpX "CurvatureX (default 0.03)" 0.0 0.0 0.125 0.01 -#define warpX params.warpX +#define warpX params.warpX // Curvature X #pragma parameter warpY "CurvatureY (default 0.04)" 0.0 0.0 0.125 0.01 -#define warpY params.warpY -#pragma parameter glow "Glow Strength" 0.04 0.0 0.5 0.01 -#define glow params.glow -#pragma parameter shadowMask "Mask Style (0 = CGWG)" 0.0 -1.0 5.0 1.0 -#define shadowMask params.shadowMask -#pragma parameter maskDark "Lottes maskDark" 0.5 0.0 2.0 0.1 -#define maskDark params.maskDark -#pragma parameter maskLight "Lottes maskLight" 1.5 0.0 2.0 0.1 -#define maskLight params.maskLight -#pragma parameter CGWG "CGWG Mask Str." 0.4 0.0 1.0 0.05 -#define CGWG params.CGWG -#pragma parameter GTW "Gamma Tweak" 1.10 0.5 1.5 0.01 -#define GTW params.GTW -#pragma parameter gamma_out "Gamma out" 2.4 1.0 3.0 0.05 -#define gamma_out params.gamma_out +#define warpY params.warpY // Curvature Y +#pragma parameter glow "Glow Strength" 0.02 0.0 0.5 0.01 +#define glow params.glow // Glow Strength +#pragma parameter shadowMask "CRT Mask: 0:CGWG, 1-4:Lottes, 5-6:'Trinitron'" 0.0 -1.0 7.0 1.0 +#define shadowMask params.shadowMask // Mask Style +#pragma parameter masksize "CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 2.0 1.0 +#define masksize params.masksize // Mask Size +#pragma parameter vertmask "PVM Like Colors" 0.0 0.0 0.25 0.01 +#define vertmask params.vertmask // Vertical mask +#pragma parameter slotmask "Slot Mask Strength" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask // Slot Mask ON/OFF +#pragma parameter slotwidth "Slot Mask Width" 2.0 2.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width +#pragma parameter double_slot "Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height +#pragma parameter mcut "Mask 5&6 cutoff" 0.2 0.0 0.5 0.05 +#define mcut params.mcut // Mask 5&6 cutoff +#pragma parameter maskDark "Lottes maskDark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" +#pragma parameter maskLight "Lottes maskLight" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" +#pragma parameter CGWG "CGWG Mask Str." 0.3 0.0 1.0 0.05 +#define CGWG params.CGWG // CGWG Mask Strength +#pragma parameter GTW "Gamma Tweak" 1.05 0.5 1.5 0.01 +#define GTW params.GTW // Gamma tweak +#pragma parameter gamma_out "Gamma out" 2.4 1.0 3.5 0.05 +#define gamma_out params.gamma_out // output gamma + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord +#define InputSize SourceSize +#define TextureSize SourceSize layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; } global; +#define SourceSize global.SourceSize +#define OutputSize global.OutputSize +#define gl_FragCoord (vTexCoord * OutputSize.xy) + #pragma stage vertex layout(location = 0) in vec4 Position; layout(location = 1) in vec2 TexCoord; @@ -109,62 +119,71 @@ layout(location = 0) out vec2 vTexCoord; void main() { gl_Position = global.MVP * Position; - vTexCoord = TexCoord * 1.0001; + 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 Source; -layout(set = 0, binding = 3) uniform sampler2D lum_pass; -layout(set = 0, binding = 4) uniform sampler2D linearize_pass; +layout(set = 0, binding = 3) uniform sampler2D LinearizePass; +layout(set = 0, binding = 4) uniform sampler2D AvgLumPass; + +#define Texture Source +#define PassPrev3Texture LinearizePass +#define PassPrev4Texture AvgLumPass #define eps 1e-10 -float b_min = 1.0 + 7.0*(beam_min - 0.5)*0.666666666; -float b_max = 1.0 + 7.0*(beam_max - 0.5)*0.666666666; -float scn_s = 0.3 + 0.7*(scanline - 1.0)*0.090909090; - -vec3 sw(float x, vec3 color) +float st(float x, float scanline) +{ + return exp2(-scanline*x*x); +} + +vec3 sw0(vec3 x, vec3 color, float scanline) { vec3 tmp = mix(vec3(beam_min),vec3(beam_max), color); - vec3 ex = vec3(x)*tmp; + vec3 ex = x*tmp; return exp2(-scanline*ex*ex); +} + +vec3 sw1(vec3 x, vec3 color, float scanline) +{ + float mx = max(max(color.r, color.g),color.b); + x = mix (x, beam_min*x, max(x-0.4*mx,0.0)); + vec3 tmp = mix(vec3(1.2*beam_min),vec3(beam_max), color); + vec3 ex = x*tmp; + float br = clamp(0.8*beam_min - 1.0, 0.2, 0.45); + return exp2(-scanline*ex*ex)/(1.0-br+br*color); +} + +vec3 sw2(vec3 x, vec3 color, float scanline) +{ + vec3 tmp = mix(vec3(2.75*beam_min),vec3(beam_max), color); + tmp = mix(vec3(beam_max), tmp, pow(x, vec3(max(max(color.r, color.g),color.b)+0.3))); + vec3 ex = x*tmp; + return exp2(-scanline*ex*ex)/(0.6 + 0.4*color); } -vec3 sw2(float x, vec3 c) -{ - vec3 s = mix(vec3(b_min), vec3(b_max), c); - return clamp(smoothstep(vec3(0.0), vec3(scn_s), pow(vec3(x),s)), 0.0001, 1.0); -} - -// Shadow mask (mostly from PD Lottes shader). -vec3 Mask(vec2 pos) +// Shadow mask (1-4 from PD CRT Lottes shader). +vec3 Mask(vec2 pos, vec3 c) { + pos = floor(pos/masksize); vec3 mask = vec3(maskDark, maskDark, maskDark); - float mf = floor(mod(pos.x,2.0)); - float mf2 = floor(mod(pos.x + pos.y,2.0)); - float mc = 1.0 - CGWG; - float mc2 = mc * 0.7; - + // No mask if (shadowMask == -1.0) { mask = vec3(1.0); } - - // Light mask. - else if (shadowMask == 5.0) - { - if (mf2 == 0.0) { mask = vec3(1.0); } - else { mask = vec3(mc2); } - } - + // Phosphor. else if (shadowMask == 0.0) { - if (mf == 0.0) { mask.r = 1.0; mask.g = mc; mask.b = 1.0; } - else { mask.r = mc; mask.g = 1.0; mask.b = mc; } + pos.x = fract(pos.x*0.5); + float mc = 1.0 - CGWG; + if (pos.x < 0.5) { mask.r = 1.1; mask.g = mc; mask.b = 1.1; } + else { mask.r = mc; mask.g = 1.1; mask.b = mc; } } // Very compressed TV style shadow mask. @@ -183,6 +202,7 @@ vec3 Mask(vec2 pos) if (pos.x < 0.333) mask.r = maskLight; else if (pos.x < 0.666) mask.g = maskLight; else mask.b = maskLight; + mask*=line; } @@ -218,10 +238,64 @@ vec3 Mask(vec2 pos) else if (pos.x < 0.666) mask.g = maskLight; else mask.b = maskLight; } + + // Alternate mask 5 + else if (shadowMask == 5.0) + { + float mx = max(max(c.r,c.g),c.b); + vec3 maskTmp = vec3( min( 1.25*max(mx-mcut,0.0)/(1.0-mcut) ,maskDark + 0.2*(1.0-maskDark)*mx)); + float adj = 0.80*maskLight - 0.5*(0.80*maskLight - 1.0)*mx + 0.75*(1.0-mx); + mask = maskTmp; + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = adj; + mask.b = adj; + } + else mask.g = adj; + } + // Alternate mask 6 + else if (shadowMask == 6.0) + { + float mx = max(max(c.r,c.g),c.b); + vec3 maskTmp = vec3( min( 1.33*max(mx-mcut,0.0)/(1.0-mcut) ,maskDark + 0.225*(1.0-maskDark)*mx)); + float adj = 0.80*maskLight - 0.5*(0.80*maskLight - 1.0)*mx + 0.75*(1.0-mx); + mask = maskTmp; + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = adj; + else if (pos.x < 0.666) mask.g = adj; + else mask.b = adj; + } + + // Alternate mask 7 + else if (shadowMask == 7.0) + { + float mx = max(max(c.r,c.g),c.b); + float maskTmp = min(1.6*max(mx-mcut,0.0)/(1.0-mcut) ,1.0 + 0.6*(1.0-mx)); + mask = vec3(maskTmp); + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) mask = vec3(1.0 + 0.6*(1.0-mx)); + } + return mask; -} +} +float SlotMask(vec2 pos, vec3 c) +{ + if (slotmask == 0.0) return 1.0; + + float mx = pow(max(max(c.r,c.g),c.b),1.33); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask, 1.0-0.80*slotmask, mx); + float slot = 1.0 + 0.7*slotmask*(1.0-mx); + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; +} + // Distortion of scanlines, and end of screen alpha (PD Lottes Curvature) vec2 Warp(vec2 pos) { @@ -236,25 +310,19 @@ vec2 Overscan(vec2 pos, float dx, float dy){ return pos*0.5+0.5; } -float Overscan2(float pos, float dy){ - pos=pos*2.0-1.0; - pos*=dy; - return pos*0.5+0.5; -} // Borrowed from cgwg's crt-geom, under GPL float corner(vec2 coord) { + coord *= SourceSize.xy / InputSize.xy; coord = (coord - vec2(0.5)) * 1.0 + vec2(0.5); - coord = min(coord, vec2(1.0)-coord) * vec2(1.0, params.OutputSize.y*params.OutputSize.z); - vec2 cdist = vec2(max(csize,0.002)); + coord = min(coord, vec2(1.0)-coord) * vec2(1.0, OutputSize.y/OutputSize.x); + vec2 cdist = vec2(max(csize, max((1.0-smoothstep(100.0,600.0,bsize))*0.01,0.002))); coord = (cdist - min(coord,cdist)); float dist = sqrt(dot(coord,coord)); - return clamp((cdist.x-dist)*700.0,0.0, 1.0); -} - -const float sqrt3 = 1.732050807568877; + return clamp((cdist.x-dist)*bsize,0.0, 1.0); +} vec3 gamma_correct(vec3 color, vec3 tmp) { @@ -263,29 +331,27 @@ vec3 gamma_correct(vec3 color, vec3 tmp) void main() { - vec3 lum = texture(lum_pass, vec2(0.33,0.33)).xyz; + float lum = COMPAT_TEXTURE(PassPrev4Texture, vec2(0.33,0.33)).a; // Calculating texel coordinates - vec2 texcoord = vTexCoord.xy; - if (IOS == 1.0){ - vec2 ofactor = params.OutputSize.xy*params.SourceSize.zw; + vec2 texcoord = TEX0.xy; + if (IOS > 0.0){ + vec2 ofactor = OutputSize.xy/InputSize.xy; vec2 intfactor = round(ofactor); vec2 diff = ofactor/intfactor; - vec2 smartcoord; - smartcoord.x = Overscan2(vTexCoord.x, diff.x); - smartcoord.y = Overscan2(vTexCoord.y, diff.y); - texcoord = (TATE > 0.5) ? vec2(smartcoord.x, texcoord.y) : - vec2(texcoord.x, smartcoord.y); + float scan = mix(diff.y, diff.x, TATE); + texcoord = Overscan(texcoord*(SourceSize.xy/InputSize.xy), scan, scan)*(InputSize.xy/SourceSize.xy); + if (IOS == 1.0) texcoord = mix(vec2(TEX0.x, texcoord.y), vec2(texcoord.x, TEX0.y), TATE); } - float factor = 1.00 + (1.0-0.5*OS)*BLOOM/100.0 - lum.x*BLOOM/100.0; - texcoord = Overscan(texcoord, factor, factor); - vec2 pos = Warp(texcoord); - vec2 pos0 = Warp(vTexCoord.xy); + float factor = 1.00 + (1.0-0.5*OS)*BLOOM/100.0 - lum*BLOOM/100.0; + texcoord = Overscan(texcoord*(SourceSize.xy/InputSize.xy), factor, factor)*(InputSize.xy/SourceSize.xy); + vec2 pos = Warp(texcoord*(TextureSize.xy/InputSize.xy))*(InputSize.xy/TextureSize.xy); + vec2 pos0 = Warp(TEX0.xy*(TextureSize.xy/InputSize.xy))*(InputSize.xy/TextureSize.xy); - vec2 ps = params.SourceSize.zw; - vec2 OGL2Pos = pos * params.SourceSize.xy - ((TATE < 0.5) ? + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = pos * SourceSize.xy - ((TATE < 0.5) ? vec2(0.0,0.5) : vec2(0.5, 0.0)); vec2 fp = fract(OGL2Pos); vec2 dx = vec2(ps.x,0.0); @@ -310,63 +376,110 @@ void main() } bool sharp = (s_sharp > 0.0); - - float wl2 = 1.5 + fpx; wl2*=wl2; wl2 = exp2(-h_sharp*wl2); wl2 = max(wl2 - s_sharp, -wl2); - float wl1 = 0.5 + fpx; wl1*=wl1; wl1 = exp2(-h_sharp*wl1); wl1 = max(wl1 - s_sharp, -0.4*s_sharp); - float wct = 0.5 - fpx; wct*=wct; wct = exp2(-h_sharp*wct); wct = max(wct - s_sharp, s_sharp); - float wr1 = 1.5 - fpx; wr1*=wr1; wr1 = exp2(-h_sharp*wr1); wr1 = max(wr1 - s_sharp, -0.4*s_sharp); - float wr2 = 2.5 - fpx; wr2*=wr2; wr2 = exp2(-h_sharp*wr2); wr2 = max(wr2 - s_sharp, -wr2); - float wt = 1.0/(wl2+wl1+wct+wr1+wr2); - - vec3 l2 = texture(linearize_pass, pC4 -off2).xyz; - vec3 l1 = texture(linearize_pass, pC4 -offx).xyz; - vec3 ct = texture(linearize_pass, pC4 ).xyz; - vec3 r1 = texture(linearize_pass, pC4 +offx).xyz; - vec3 r2 = texture(linearize_pass, pC4 +off2).xyz; - vec3 color1 = (l2*wl2 + l1*wl1 + ct*wct + r1*wr1 + r2*wr2)*wt; + float hsharp_tl, hsharp_tr, hsharp_bl, hsharp_br, hsharp_tc, hsharp_bc; + + if (h_smart == 0.0) + { + hsharp_tl = h_sharp; hsharp_tr = h_sharp; hsharp_bl = h_sharp; hsharp_br = h_sharp; hsharp_tc = h_sharp; hsharp_bc = h_sharp; + } + else + { + // reading differences for smoothing + vec3 diffs_top = COMPAT_TEXTURE(PassPrev4Texture, pC4 ).xyz; + vec3 diffs_bot = COMPAT_TEXTURE(PassPrev4Texture, pC4 + offy).xyz; + + if(TATE > 0.5) + { + diffs_top.x = floor(10.0*diffs_top.z)*0.11111; diffs_top.y = fract(10.0*diffs_top.z)*1.11111; + diffs_bot.x = floor(10.0*diffs_bot.z)*0.11111; diffs_bot.y = fract(10.0*diffs_bot.z)*1.11111; + } + + float ls = mix (4.5, 2.25, h_smart); + hsharp_tl = mix(h_sharp, ls, diffs_top.x); + hsharp_tr = mix(h_sharp, ls, diffs_top.y); + hsharp_bl = mix(h_sharp, ls, diffs_bot.x); + hsharp_br = mix(h_sharp, ls, diffs_bot.y); + hsharp_tc = hsharp_tl; + hsharp_bc = hsharp_bl; + if (fpx == 0.5) { hsharp_tc = 0.5*(hsharp_tl + hsharp_tr); hsharp_bc = 0.5*(hsharp_bl + hsharp_br); } + if (fpx > 0.5) { hsharp_tc = hsharp_tr; hsharp_bc = hsharp_bl; } + } + + float wl2 = 1.5 + fpx; wl2*=wl2; float twl2 = exp2(-hsharp_tl*wl2); twl2 = max(twl2 - s_sharp, -twl2); float bwl2 = exp2(-hsharp_bl*wl2); bwl2 = max(bwl2 - s_sharp, -bwl2); + float wl1 = 0.5 + fpx; wl1*=wl1; float twl1 = exp2(-hsharp_tl*wl1); twl1 = max(twl1 - s_sharp, -0.4*s_sharp); float bwl1 = exp2(-hsharp_bl*wl1); bwl1 = max(bwl1 - s_sharp, -0.4*s_sharp); + float wct = 0.5 - fpx; wct*=wct; float twct = exp2(-hsharp_tc*wct); twct = max(twct - s_sharp, s_sharp); float bwct = exp2(-hsharp_bc*wct); bwct = max(bwct - s_sharp, s_sharp); + float wr1 = 1.5 - fpx; wr1*=wr1; float twr1 = exp2(-hsharp_tr*wr1); twr1 = max(twr1 - s_sharp, -0.4*s_sharp); float bwr1 = exp2(-hsharp_br*wr1); bwr1 = max(bwr1 - s_sharp, -0.4*s_sharp); + float wr2 = 2.5 - fpx; wr2*=wr2; float twr2 = exp2(-hsharp_tr*wr2); twr2 = max(twr2 - s_sharp, -twr2); float bwr2 = exp2(-hsharp_br*wr2); bwr2 = max(bwr2 - s_sharp, -bwr2); + + float wtt = 1.0/(twl2+twl1+twct+twr1+twr2); + float wtb = 1.0/(bwl2+bwl1+bwct+bwr1+bwr2); + + vec3 l2 = COMPAT_TEXTURE(PassPrev3Texture, pC4 -off2).xyz; + vec3 l1 = COMPAT_TEXTURE(PassPrev3Texture, pC4 -offx).xyz; + vec3 ct = COMPAT_TEXTURE(PassPrev3Texture, pC4 ).xyz; + vec3 r1 = COMPAT_TEXTURE(PassPrev3Texture, pC4 +offx).xyz; + vec3 r2 = COMPAT_TEXTURE(PassPrev3Texture, pC4 +off2).xyz; + + vec3 color1 = (l2*twl2 + l1*twl1 + ct*twct + r1*twr1 + r2*twr2)*wtt; if (sharp) color1 = clamp(color1, min(min(l1,r1),ct), max(max(l1,r1),ct)); - l2 = texture(linearize_pass, pC4 -off2 +offy).xyz; - l1 = texture(linearize_pass, pC4 -offx +offy).xyz; - ct = texture(linearize_pass, pC4 +offy).xyz; - r1 = texture(linearize_pass, pC4 +offx +offy).xyz; - r2 = texture(linearize_pass, pC4 +off2 +offy).xyz; - - vec3 color2 = (l2*wl2 + l1*wl1 + ct*wct + r1*wr1 + r2*wr2)*wt; + l2 = COMPAT_TEXTURE(PassPrev3Texture, pC4 -off2 +offy).xyz; + l1 = COMPAT_TEXTURE(PassPrev3Texture, pC4 -offx +offy).xyz; + ct = COMPAT_TEXTURE(PassPrev3Texture, pC4 +offy).xyz; + r1 = COMPAT_TEXTURE(PassPrev3Texture, pC4 +offx +offy).xyz; + r2 = COMPAT_TEXTURE(PassPrev3Texture, pC4 +off2 +offy).xyz; + + vec3 color2 = (l2*bwl2 + l1*bwl1 + ct*bwct + r1*bwr1 + r2*bwr2)*wtb; if (sharp) color2 = clamp(color2, min(min(l1,r1),ct), max(max(l1,r1),ct)); // calculating scanlines float f = (TATE < 0.5) ? fp.y : fp.x; - - vec3 w1 = sw(f,color1); - vec3 w2 = sw(1.0-f,color2); - - if (gsl == 1.0) { w1 = sw2(1.0-f,color1); w2 = sw2(f,color2);} - + + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f, shape1); + float wt2 = st(1.0-f, shape2); + vec3 color0 = color1*wt1 + color2*wt2; + vec3 ctmp = color0/(wt1+wt2); + vec3 tmp = pow(ctmp, vec3(1.0/gamma_out)); + + vec3 w1,w2 = vec3(0.0); + + vec3 cref1 = mix(ctmp, color1, beam_size); + vec3 cref2 = mix(ctmp, color2, beam_size); + + vec3 shift = vec3(-vertmask, vertmask, -vertmask); + + vec3 f1 = clamp(vec3(f) + shift*0.5*(1.0+f), 0.0, 1.0); + vec3 f2 = clamp(vec3(1.0-f) - shift*0.5*(2.0-f), 0.0, 1.0); + + if (gsl == 0.0) { w1 = sw0(f1,cref1,shape1); w2 = sw0(f2,cref2,shape2);} else + if (gsl == 1.0) { w1 = sw1(f1,cref1,shape1); w2 = sw1(f2,cref2,shape2);} else + if (gsl == 2.0) { w1 = sw2(f1,cref1,shape1); w2 = sw2(f2,cref2,shape2);} + vec3 color = color1*w1 + color2*w2; - vec3 ctmp = color/(w1+w2); - - color = pow(color, vec3(1.0/gamma_out)); - float l = length(color); - color = normalize(pow(color + vec3(eps), vec3(saturation,saturation,saturation)))*l; - color*=brightboost; - color = gamma_correct(color,ctmp); - color = pow(color, vec3(gamma_out)); - color = min(color, 1.0); + + color*=brightboost; + color = min(color, 1.0); // Apply Mask - - color *= (TATE < 0.5) ? Mask(gl_FragCoord.xy * 1.000001) : - Mask(gl_FragCoord.yx * 1.000001); - - vec3 Bloom = texture(Source, pos).xyz; + + color *= (TATE < 0.5) ? Mask(gl_FragCoord.xy * 1.000001,tmp) : + Mask(gl_FragCoord.yx * 1.000001,tmp); + + color = min(color,1.0); + + color *= (TATE < 0.5) ? SlotMask(gl_FragCoord.xy * 1.000001,tmp) : + SlotMask(gl_FragCoord.yx * 1.000001,tmp); + + vec3 Bloom = COMPAT_TEXTURE(Texture, pos).xyz; color+=glow*Bloom; - color = min(color, 1.0); color = pow(color, vec3(1.0/gamma_out)); - FragColor = vec4(color*corner(pos0), 1.0); + FragColor = vec4(color*corner(pos0), 1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/d65-d50.slang b/crt/shaders/guest/d65-d50.slang index 5b6cdee..5463d18 100644 --- a/crt/shaders/guest/d65-d50.slang +++ b/crt/shaders/guest/d65-d50.slang @@ -6,10 +6,15 @@ layout(push_constant) uniform Push vec4 OriginalSize; vec4 OutputSize; uint FrameCount; - float WP; + float WP; } params; -#pragma parameter WP "D65 to D50 strength %" 0.0 -100.0 100.0 5.0 +#pragma parameter WP "Color Temperature %" 0.0 -100.0 100.0 5.0 + +#define WP params.WP + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define TEX0 vTexCoord layout(std140, set = 0, binding = 0) uniform UBO { @@ -32,26 +37,46 @@ layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; -const mat3 D65 = mat3 ( - 0.5767309, 0.2973769, 0.0270343, - 0.1855540, 0.6273491, 0.0706872, - 0.1881852, 0.0752741, 0.9911085); +const mat3 D65_to_XYZ = mat3 ( + 0.4306190, 0.2220379, 0.0201853, + 0.3415419, 0.7066384, 0.1295504, + 0.1783091, 0.0713236, 0.9390944); -const mat3 D50 = mat3 ( - 1.7552599, -0.5441336, 0.0063467, - -0.4836786, 1.5068789, -0.0175761, - -0.2530000, 0.0215528, 1.2256959); +const mat3 XYZ_to_D65 = mat3 ( + 3.0628971, -0.9692660, 0.0678775, + -1.3931791, 1.8760108, -0.2288548, + -0.4757517, 0.0415560, 1.0693490); + +const mat3 D50_to_XYZ = mat3 ( + 0.4552773, 0.2323025, 0.0145457, + 0.3675500, 0.7077956, 0.1049154, + 0.1413926, 0.0599019, 0.7057489); + +const mat3 XYZ_to_D50 = mat3 ( + 2.9603944, -0.9787684, 0.0844874, + -1.4678519, 1.9161415, -0.2545973, + -0.4685105, 0.0334540, 1.4216174); void main() { - vec3 color = texture(Source, vTexCoord.xy).rgb; + vec3 color = COMPAT_TEXTURE(Source, TEX0.xy).rgb; + float p = 2.4; - vec3 c65 = D65*color; - vec3 c50 = D50*c65; + color = pow(color, vec3(p)); - float m = params.WP/100.0; + vec3 warmer = D50_to_XYZ*color; + warmer = XYZ_to_D65*warmer; - color = (1.0-m)*color + m*c50; + vec3 cooler = D65_to_XYZ*color; + cooler = XYZ_to_D50*cooler; + + float m = abs(WP)/100.0; + + vec3 comp = (WP < 0.0) ? cooler : warmer; + + color = mix(color, comp, m); + + color = pow(color, vec3(1.0/p)); FragColor = vec4(color,1.0); } \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-dr-venom-pass1.slang b/crt/shaders/guest/fast/crt-guest-dr-venom-pass1.slang new file mode 100644 index 0000000..050e77d --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-dr-venom-pass1.slang @@ -0,0 +1,126 @@ +#version 450 + +/* + CRT - Guest - Dr. Venom - Pass1 + + Copyright (C) 2018-2019 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + + 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 h_sharp; + float s_sharp; + float h_smart; +} params; + +#pragma parameter h_sharp "Horizontal sharpness" 5.00 1.5 20.0 0.25 +#define h_sharp params.h_sharp +#pragma parameter s_sharp "Substractive sharpness" 0.05 0.0 0.20 0.01 +#define s_sharp params.s_sharp +#pragma parameter h_smart "Smart Horizontal Smoothing" 0.0 0.0 1.0 0.1 +#define h_smart params.h_smart +#define SourceSize params.SourceSize + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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 Source; +layout(set = 0, binding = 3) uniform sampler2D SmoothPass; + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define PassPrev2Texture SmoothPass + +void main() +{ + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = vTexCoord * SourceSize.xy; + vec2 fp = fract(OGL2Pos); + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + // Reading the texels + vec2 x2 = 2.0*dx; + vec2 y2 = 2.0*dy; + + bool sharp = (s_sharp > 0.0); + + float hsharp_tl, hsharp_tr, hsharp_tc; float s_sharpl = s_sharp; float s_sharpr = s_sharp; float s_sharpc = s_sharp; + + if (h_smart == 0.0) + { + hsharp_tl = h_sharp; hsharp_tr = h_sharp; hsharp_tc = h_sharp; + } + else + { + // reading differences for smoothing + vec2 diffs = COMPAT_TEXTURE(PassPrev2Texture, pC4).xy; + + float ls = mix (4.25, 2.25, h_smart); + hsharp_tl = mix(h_sharp, ls, diffs.x); + hsharp_tr = mix(h_sharp, ls, diffs.y); + + s_sharpl = mix(s_sharp, 0.0, diffs.x); + s_sharpr = mix(s_sharp, 0.0, diffs.y); + + hsharp_tc = hsharp_tl; + if (fp.x == 0.5) { hsharp_tc = 0.5*(hsharp_tl + hsharp_tr); s_sharpc = 0.5*(s_sharpl + s_sharpr); } + if (fp.x > 0.5) { hsharp_tc = hsharp_tr; } + } + + float wl2 = 1.5 + fp.x; wl2*=wl2; float twl2 = exp2(-hsharp_tl*wl2); twl2 = max(twl2 - s_sharpl, -twl2); + float wl1 = 0.5 + fp.x; wl1*=wl1; float twl1 = exp2(-hsharp_tl*wl1); twl1 = max(twl1 - s_sharpl, -0.4*s_sharpl); + float wct = 0.5 - fp.x; wct*=wct; float twct = exp2(-hsharp_tc*wct); twct = max(twct - s_sharpc, s_sharpc); + float wr1 = 1.5 - fp.x; wr1*=wr1; float twr1 = exp2(-hsharp_tr*wr1); twr1 = max(twr1 - s_sharpr, -0.4*s_sharpr); + float wr2 = 2.5 - fp.x; wr2*=wr2; float twr2 = exp2(-hsharp_tr*wr2); twr2 = max(twr2 - s_sharpr, -twr2); + + float wtt = 1.0/(twl2+twl1+twct+twr1+twr2); + + vec3 l2 = COMPAT_TEXTURE(Source, pC4 -x2).xyz; + vec3 l1 = COMPAT_TEXTURE(Source, pC4 -dx).xyz; + vec3 ct = COMPAT_TEXTURE(Source, pC4 ).xyz; + vec3 r1 = COMPAT_TEXTURE(Source, pC4 +dx).xyz; + vec3 r2 = COMPAT_TEXTURE(Source, pC4 +x2).xyz; + + vec3 color = (l2*twl2 + l1*twl1 + ct*twct + r1*twr1 + r2*twr2)*wtt; + if (sharp) color = clamp(color, 0.8*min(min(l1,r1),ct), 1.2*max(max(l1,r1),ct)); + + FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/crt-guest-dr-venom-pass2.slang b/crt/shaders/guest/fast/crt-guest-dr-venom-pass2.slang new file mode 100644 index 0000000..3a8b700 --- /dev/null +++ b/crt/shaders/guest/fast/crt-guest-dr-venom-pass2.slang @@ -0,0 +1,328 @@ +#version 450 + +/* + CRT - Guest - Dr. Venom - Pass2 + + Copyright (C) 2018-2019 guest(r) - guest.r@gmail.com + + Incorporates many good ideas and suggestions from Dr. Venom. + + 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 +{ + float brightboost, IOS, gsl, scanline1, scanline2, beam_min, beam_max, s_power, beam_size, shadowMask, + masksize, vertmask, slotmask, slotwidth, double_slot, mcut, maskDark, maskLight, CGWG, gamma_out; +} params; + +#pragma parameter brightboost "Bright boost" 1.30 0.50 2.00 0.01 +#define brightboost params.brightboost // adjust brightness +#pragma parameter IOS "Smart Y Integer Scaling" 0.0 0.0 1.0 1.0 +#define IOS params.IOS // smart integer scaling +#pragma parameter gsl "Scanline Type" 1.0 0.0 2.0 1.0 +#define gsl params.gsl // Alternate scanlines +#pragma parameter scanline1 "Scanline beam shape low" 8.0 1.0 15.0 1.0 +#define scanline1 params.scanline1 // scanline param, vertical sharpness +#pragma parameter scanline2 "Scanline beam shape high" 8.0 5.0 23.0 1.0 +#define scanline2 params.scanline2 // scanline param, vertical sharpness +#pragma parameter beam_min "Scanline dark" 1.25 0.5 2.0 0.05 +#define beam_min params.beam_min // dark area beam min - narrow +#pragma parameter beam_max "Scanline bright" 1.05 0.5 2.0 0.05 +#define beam_max params.beam_max // bright area beam max - wide +#pragma parameter s_power "Scanline intensity" 1.0 0.5 2.5 0.05 +#define s_power params.s_power // scanline intensity +#pragma parameter beam_size "Increased bright scanline beam" 0.65 0.0 1.0 0.05 +#define beam_size params.beam_size // increased max. beam size +#pragma parameter shadowMask "CRT Mask: 0:CGWG, 1-4:Lottes, 5-6:'Trinitron'" 5.0 -1.0 6.0 1.0 +#define shadowMask params.shadowMask // Mask Style +#pragma parameter masksize "CRT Mask Size (2.0 is nice in 4k)" 1.0 1.0 2.0 1.0 +#define masksize params.masksize // Mask Size +#pragma parameter vertmask "PVM Like Colors" 0.05 0.0 0.25 0.01 +#define vertmask params.vertmask // Vertical mask +#pragma parameter slotmask "Slot Mask Strength" 0.0 0.0 1.0 0.05 +#define slotmask params.slotmask // Slot Mask ON/OFF +#pragma parameter slotwidth "Slot Mask Width" 2.0 2.0 6.0 0.5 +#define slotwidth params.slotwidth // Slot Mask Width +#pragma parameter double_slot "Slot Mask Height: 2x1 or 4x1" 1.0 1.0 2.0 1.0 +#define double_slot params.double_slot // Slot Mask Height +#pragma parameter mcut "Mask 5&6 cutoff" 0.2 0.0 0.5 0.05 +#define mcut params.mcut // Mask 5&6 cutoff +#pragma parameter maskDark "Mask Dark" 0.5 0.0 2.0 0.05 +#define maskDark params.maskDark // Dark "Phosphor" +#pragma parameter maskLight "Mask Light" 1.5 0.0 2.0 0.05 +#define maskLight params.maskLight // Light "Phosphor" +#pragma parameter CGWG "CGWG Mask Str." 0.3 0.0 1.0 0.05 +#define CGWG params.CGWG // CGWG Mask Strength +#pragma parameter gamma_out "Gamma out" 2.4 1.0 3.5 0.05 +#define gamma_out params.gamma_out // output gamma + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; + vec4 SourceSize; + vec4 OutputSize; + uint FrameCount; +} global; + +#define SourceSize global.SourceSize +#define OutputSize global.OutputSize +#define FrameCount global.FrameCount + +#define COMPAT_TEXTURE(c,d) texture(c,d) +#define gl_FragCoord (vTexCoord.xy * OutputSize.xy) +#define InputSize SourceSize + +#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 Source; + +#define eps 1e-10 + +float st(float x, float scanline) +{ + return exp2(-scanline*x*x); +} + +vec3 sw0(vec3 x, vec3 color, float scanline) +{ + vec3 tmp = mix(vec3(beam_min),vec3(beam_max), color); + vec3 ex = x*tmp; + return exp2(-scanline*ex*ex); +} + +vec3 sw1(vec3 x, vec3 color, float scanline) +{ + float mx = max(max(color.r, color.g),color.b); + x = mix (x, beam_min*x, max(x-0.4*mx,0.0)); + vec3 tmp = mix(vec3(1.2*beam_min),vec3(beam_max), color); + vec3 ex = x*tmp; + float br = clamp(0.8*beam_min - 1.0, 0.2, 0.45); + return exp2(-scanline*ex*ex)/(1.0-br+br*color); +} + +vec3 sw2(vec3 x, vec3 color, float scanline) +{ + vec3 tmp = mix(vec3(2.75*beam_min),vec3(beam_max), color); + tmp = mix(vec3(beam_max), tmp, pow(x, vec3(max(max(color.r, color.g),color.b)+0.3))); + vec3 ex = x*tmp; + return exp2(-scanline*ex*ex)/(0.6 + 0.4*color); +} + +// Shadow mask (1-4 from PD CRT Lottes shader). +vec3 Mask(vec2 pos, vec3 c) +{ + pos = floor(pos/masksize); + vec3 mask = vec3(maskDark, maskDark, maskDark); + + + // No mask + if (shadowMask == -1.0) + { + mask = vec3(1.0); + } + + // Phosphor. + else if (shadowMask == 0.0) + { + pos.x = fract(pos.x*0.5); + float mc = 1.0 - CGWG; + if (pos.x < 0.5) { mask.r = 1.1; mask.g = mc; mask.b = 1.1; } + else { mask.r = mc; mask.g = 1.1; mask.b = mc; } + } + + // Very compressed TV style shadow mask. + else if (shadowMask == 1.0) + { + float line = maskLight; + float odd = 0.0; + + if (fract(pos.x/6.0) < 0.5) + odd = 1.0; + if (fract((pos.y + odd)/2.0) < 0.5) + line = maskDark; + + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + + mask*=line; + } + + // Aperture-grille. + else if (shadowMask == 2.0) + { + pos.x = fract(pos.x/3.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Stretched VGA style shadow mask (same as prior shaders). + else if (shadowMask == 3.0) + { + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // VGA style shadow mask. + else if (shadowMask == 4.0) + { + pos.xy = floor(pos.xy*vec2(1.0, 0.5)); + pos.x += pos.y*3.0; + pos.x = fract(pos.x/6.0); + + if (pos.x < 0.333) mask.r = maskLight; + else if (pos.x < 0.666) mask.g = maskLight; + else mask.b = maskLight; + } + + // Alternate mask 5 + else if (shadowMask == 5.0) + { + float mx = max(max(c.r,c.g),c.b); + vec3 maskTmp = vec3( min( 1.25*max(mx-mcut,0.0)/(1.0-mcut) ,maskDark + 0.2*(1.0-maskDark)*mx)); + float adj = 0.80*maskLight - 0.5*(0.80*maskLight - 1.0)*mx + 0.75*(1.0-mx); + mask = maskTmp; + pos.x = fract(pos.x/2.0); + if (pos.x < 0.5) + { mask.r = adj; + mask.b = adj; + } + else mask.g = adj; + } + + // Alternate mask 6 + else if (shadowMask == 6.0) + { + float mx = max(max(c.r,c.g),c.b); + vec3 maskTmp = vec3( min( 1.5*max(mx-mcut,0.0)/(1.0-mcut) ,maskDark + 0.225*(1.0-maskDark)*mx)); + float adj = 0.80*maskLight - 0.5*(0.80*maskLight - 1.0)*mx + 0.75*(1.0-mx); + mask = maskTmp; + pos.x = fract(pos.x/3.0); + if (pos.x < 0.333) mask.r = adj; + else if (pos.x < 0.666) mask.g = adj; + else mask.b = adj; + } + + return mask; +} + +float SlotMask(vec2 pos, vec3 c) +{ + if (slotmask == 0.0) return 1.0; + + float mx = pow(max(max(c.r,c.g),c.b),1.33); + float mlen = slotwidth*2.0; + float px = fract(pos.x/mlen); + float py = floor(fract(pos.y/(2.0*double_slot))*2.0*double_slot); + float slot_dark = mix(1.0-slotmask, 1.0-0.80*slotmask, mx); + float slot = 1.0 + 0.7*slotmask*(1.0-mx); + if (py == 0.0 && px < 0.5) slot = slot_dark; else + if (py == double_slot && px >= 0.5) slot = slot_dark; + + return slot; +} + +float Overscan2(float pos, float dy){ + pos=pos*2.0-1.0; + pos*=dy; + return pos*0.5+0.5; +} + +void main() +{ + vec2 texcoord = vTexCoord; + + if (IOS == 1.0){ + float ofactor = OutputSize.y/InputSize.y; + float intfactor = round(ofactor); + float diff = ofactor/intfactor; + texcoord.y = Overscan2(texcoord.y*(SourceSize.y/InputSize.y), diff)*(InputSize.y/SourceSize.y); + } + + vec2 ps = SourceSize.zw; + vec2 OGL2Pos = texcoord * SourceSize.xy - vec2(0.0,0.5); + vec2 fp = fract(OGL2Pos); + vec2 dx = vec2(ps.x,0.0); + vec2 dy = vec2(0.0, ps.y); + + vec2 pC4 = floor(OGL2Pos) * ps + 0.5*ps; + + vec3 color1 = COMPAT_TEXTURE(Source, pC4 ).xyz; + vec3 color2 = COMPAT_TEXTURE(Source, pC4 +dy).xyz; + + // calculating scanlines + + float f = fp.y; + float shape1 = mix(scanline1, scanline2, f); + float shape2 = mix(scanline1, scanline2, 1.0-f); + + float wt1 = st(f, shape1); + float wt2 = st(1.0-f, shape2); + vec3 color0 = color1*wt1 + color2*wt2; + vec3 ctmp = color0/(wt1+wt2); + vec3 tmp = pow(ctmp, vec3(1.0/gamma_out)); + + vec3 w1,w2 = vec3(0.0); + + vec3 cref1 = mix(ctmp, color1, beam_size); + vec3 cref2 = mix(ctmp, color2, beam_size); + + vec3 shift = vec3(-vertmask, vertmask, -vertmask); + + vec3 f1 = clamp(vec3(f) + shift*0.5*(1.0+f), 0.0, 1.0); + vec3 f2 = clamp(vec3(1.0-f) - shift*0.5*(2.0-f), 0.0, 1.0); + + if (gsl == 0.0) { w1 = sw0(f1,cref1,shape1); w2 = sw0(f2,cref2,shape2);} else + if (gsl == 1.0) { w1 = sw1(f1,cref1,shape1); w2 = sw1(f2,cref2,shape2);} else + if (gsl == 2.0) { w1 = sw2(f1,cref1,shape1); w2 = sw2(f2,cref2,shape2);} + + vec3 color = color1*pow(w1, vec3(s_power)) + color2*pow(w2, vec3(s_power)); + + color*=brightboost; + color = min(color, 1.0); + + // Apply Mask + + color *= Mask(gl_FragCoord.xy * 1.000001,tmp); + + color = min(color,1.0); + + color *= SlotMask(gl_FragCoord.xy * 1.000001,tmp); + + color = pow(color, vec3(1.0/gamma_out)); + FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/linearize-multipass.slang b/crt/shaders/guest/fast/linearize-multipass.slang new file mode 100644 index 0000000..0701b8b --- /dev/null +++ b/crt/shaders/guest/fast/linearize-multipass.slang @@ -0,0 +1,39 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float GAMMA_INPUT; +} params; + +#pragma parameter GAMMA_INPUT "Gamma Input" 2.4 0.1 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Original; + +void main() +{ + FragColor = pow(texture(Original, vTexCoord), vec4(GAMMA_INPUT)); +} \ No newline at end of file diff --git a/crt/shaders/guest/fast/smoothing.slang b/crt/shaders/guest/fast/smoothing.slang new file mode 100644 index 0000000..6222be8 --- /dev/null +++ b/crt/shaders/guest/fast/smoothing.slang @@ -0,0 +1,84 @@ +#version 450 + +/* + Smart Smoothing Difference Shader + + Copyright (C) 2019 guest(r) - guest.r@gmail.com + + 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. + +*/ + +#pragma name SmoothPass + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float STH; +} params; + +#pragma parameter STH "Smart Smoothing Threshold" 0.7 0.4 1.2 0.05 +#define STH params.STH +#define SourceSize params.SourceSize +#define COMPAT_TEXTURE(c,d) texture(c,d) + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; + +float df (vec3 A, vec3 B) +{ + float diff = length(A-B); + float luma = clamp(length(0.5*min(A,B) + 0.25*(A+B) + 1e-8), 0.0001, 1.0); + float diff1 = diff/luma; + return 1.0 - clamp(7.0*(max(1.5*diff,diff1)-STH), 0.0, 1.0); +} + +void main() +{ + vec2 dx = vec2(SourceSize.z, 0.0); + vec2 dy = vec2(0.0, SourceSize.w); + + vec3 l1 = COMPAT_TEXTURE(Source, vTexCoord.xy -dx).xyz; + vec3 ct = COMPAT_TEXTURE(Source, vTexCoord.xy ).xyz; + vec3 r1 = COMPAT_TEXTURE(Source, vTexCoord.xy +dx).xyz; + + float dl = df(ct, l1); + float dr = df(ct, r1); + + float resx = dl; float resy = dr; + + FragColor = vec4(resx,resy,1.0,1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/linearize.slang b/crt/shaders/guest/linearize.slang index 222b675..c2e49ce 100644 --- a/crt/shaders/guest/linearize.slang +++ b/crt/shaders/guest/linearize.slang @@ -2,14 +2,11 @@ layout(push_constant) uniform Push { - vec4 SourceSize; - vec4 OriginalSize; - vec4 OutputSize; - uint FrameCount; float GAMMA_INPUT; } params; #pragma parameter GAMMA_INPUT "Gamma Input" 2.4 0.1 5.0 0.05 +#define GAMMA_INPUT params.GAMMA_INPUT layout(std140, set = 0, binding = 0) uniform UBO { @@ -30,9 +27,12 @@ void main() #pragma stage fragment layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; -layout(set = 0, binding = 2) uniform sampler2D temp_pass; +layout(set = 0, binding = 2) uniform sampler2D AfterglowPass; + +#define PassPrev3Texture AfterglowPass +#define COMPAT_TEXTURE(c,d) texture(c,d) void main() { - FragColor = pow(vec4(texture(temp_pass, vTexCoord)), vec4(params.GAMMA_INPUT)); + FragColor = pow(vec4(COMPAT_TEXTURE(PassPrev3Texture, vTexCoord)), vec4(GAMMA_INPUT)); } \ No newline at end of file diff --git a/crt/shaders/guest/lut/README b/crt/shaders/guest/lut/README new file mode 100644 index 0000000..35c3fb0 --- /dev/null +++ b/crt/shaders/guest/lut/README @@ -0,0 +1 @@ +LUT's kindly provided by torridgristle. \ No newline at end of file diff --git a/crt/shaders/guest/lut/lut.slang b/crt/shaders/guest/lut/lut.slang new file mode 100644 index 0000000..78398c7 --- /dev/null +++ b/crt/shaders/guest/lut/lut.slang @@ -0,0 +1,104 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + float TNTC; +} params; + +#pragma parameter TNTC "LUT Colors" 0.0 0.0 3.0 1.0 +#define TNTC params.TNTC + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#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; +} + +#pragma stage fragment +layout(location = 0) in vec2 vTexCoord; +layout(location = 0) out vec4 FragColor; +layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D SamplerLUT1; +layout(set = 0, binding = 4) uniform sampler2D SamplerLUT2; +layout(set = 0, binding = 5) uniform sampler2D SamplerLUT3; + +#define LUT_Size 32.0 +#define COMPAT_TEXTURE(c,d) texture(c,d) + +const mat3 D65_to_XYZ = mat3 ( + 0.4306190, 0.2220379, 0.0201853, + 0.3415419, 0.7066384, 0.1295504, + 0.1783091, 0.0713236, 0.9390944); + +const mat3 XYZ_to_D50 = mat3 ( + 2.9603944, -0.9787684, 0.0844874, + -1.4678519, 1.9161415, -0.2545973, + -0.4685105, 0.0334540, 1.4216174); + + +// This shouldn't be necessary but it seems some undefined values can +// creep in and each GPU vendor handles that differently. This keeps +// all values within a safe range +vec4 mixfix(vec4 a, vec4 b, float c) +{ + return (a.z < 1.0) ? mix(a, b, c) : a; +} + +void main() +{ + vec4 imgColor = COMPAT_TEXTURE(Source, vTexCoord.xy); + float red = ( imgColor.r * (LUT_Size - 1.0) + 0.499999 ) / (LUT_Size * LUT_Size); + float green = ( imgColor.g * (LUT_Size - 1.0) + 0.499999 ) / LUT_Size; + float blue1 = (floor( imgColor.b * (LUT_Size - 1.0) ) / LUT_Size) + red; + float blue2 = (ceil( imgColor.b * (LUT_Size - 1.0) ) / LUT_Size) + red; + float mixer = clamp(max((imgColor.b - blue1) / (blue2 - blue1), 0.0), 0.0, 32.0); + vec4 color1, color2, res; + if (int(TNTC) == 1) + { + color1 = COMPAT_TEXTURE( SamplerLUT1, vec2( blue1, green )); + color2 = COMPAT_TEXTURE( SamplerLUT1, vec2( blue2, green )); + res = mixfix(color1, color2, mixer); + float mx = max(res.r,max(res.g,res.b)); + float l = mix(length(imgColor.rgb), length(res.rgb), max(mx-0.5,0.0)); + res.rgb = mix(imgColor.rgb, res.rgb, clamp(25.0*(mx-0.02),0.0,1.0)); + res.rgb = normalize(res.rgb+1e-10)*l; + vec3 cooler = D65_to_XYZ*res.rgb; + cooler = XYZ_to_D50*cooler; + res.rgb = mix(res.rgb, cooler, 0.25); + } + else if (int(TNTC) == 2) + { + color1 = COMPAT_TEXTURE( SamplerLUT2, vec2( blue1, green )); + color2 = COMPAT_TEXTURE( SamplerLUT2, vec2( blue2, green )); + res = mixfix(color1, color2, mixer); + float l = mix(length(imgColor.rgb), length(res.rgb), 0.4); + res.rgb = normalize(res.rgb + 1e-10)*l; + } + else if (int(TNTC) == 3) + { + color1 = COMPAT_TEXTURE( SamplerLUT3, vec2( blue1, green )); + color2 = COMPAT_TEXTURE( SamplerLUT3, vec2( blue2, green )); + res = mixfix(color1, color2, mixer); + res.rgb = pow(res.rgb, vec3(1.0/1.20)); + float mx = max(res.r,max(res.g,res.b)); + res.rgb = mix(imgColor.rgb, res.rgb, clamp(25.0*(mx-0.05),0.0,1.0)); + float l = length(imgColor.rgb); + res.rgb = normalize(res.rgb + 1e-10)*l; + } + + FragColor = vec4(mix(imgColor.rgb, res.rgb, min(TNTC,1.0)),1.0); +} \ No newline at end of file diff --git a/crt/shaders/guest/lut/other1.png b/crt/shaders/guest/lut/other1.png new file mode 100644 index 0000000000000000000000000000000000000000..d059c9ac8e1daaa44f5cd34d2f80844dcb024058 GIT binary patch literal 12811 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

D%{`VDZ|m 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 B{&D~S literal 0 HcmV?d00001 diff --git a/presets/crt-guest-dr-venom-ntsc-composite.slangp b/presets/crt-guest-dr-venom-ntsc-composite.slangp new file mode 100644 index 0000000..33161d1 --- /dev/null +++ b/presets/crt-guest-dr-venom-ntsc-composite.slangp @@ -0,0 +1,80 @@ +shaders = 11 + +shader0 = ../ntsc/shaders/ntsc-pass1-composite-2phase.slang +shader1 = ../ntsc/shaders/ntsc-pass2-2phase-gamma.slang + +filter_linear0 = false +filter_linear1 = false + +scale_type_x0 = source +scale_type_y0 = source +scale_x0 = 4.0 +scale_y0 = 1.0 +frame_count_mod0 = 2 +float_framebuffer0 = true + +scale_type1 = source +scale_x1 = 0.5 +scale_y1 = 1.0 + +shader2 = ../crt/shaders/guest/lut/lut.slang +filter_linear2 = false +scale_type2 = source +scale2 = 1.0 + +textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3" +SamplerLUT1 = ../crt/shaders/guest/lut/sony_trinitron1.png +SamplerLUT1_linear = true +SamplerLUT2 = ../crt/shaders/guest/lut/sony_trinitron2.png +SamplerLUT2_linear = true +SamplerLUT3 = ../crt/shaders/guest/lut/other1.png +SamplerLUT3_linear = true + +shader3 = ../crt/shaders/guest/d65-d50.slang +filter_linear3 = false +scale_type3 = source +scale3 = 1.0 +alias3 = WhitePointPass + +shader4 = ../crt/shaders/guest/afterglow.slang +filter_linear4 = false +scale_type4 = source +scale4 = 1.0 +alias4 = AfterglowPass + +shader5 = ../crt/shaders/guest/avg-lum0.slang +filter_linear5 = false +scale_type5 = source +scale5 = 1.0 + +shader6 = ../crt/shaders/guest/avg-lum.slang +filter_linear6 = false +scale_type6 = source +scale6 = 1.0 +mipmap_input6 = true +alias6 = AvgLumPass + +shader7 = ../crt/shaders/guest/linearize.slang +filter_linear7 = false +scale_type7 = source +scale7 = 1.0 +float_framebuffer7 = true +alias7 = LinearizePass + +shader8 = ../crt/shaders/guest/blur_horiz.slang +filter_linear8 = false +scale_type8 = source +scale8 = 1.0 +float_framebuffer8 = true + +shader9 = ../crt/shaders/guest/blur_vert.slang +filter_linear9 = false +scale_type9 = source +scale9 = 1.0 +float_framebuffer9 = true + +shader10 = ../crt/shaders/guest/crt-guest-dr-venom.slang +filter_linear10 = true +scale_type10 = viewport +scale_x10 = 1.0 +scale_y10 = 1.0