mirror of
https://github.com/italicsjenga/slang-shaders.git
synced 2024-11-30 11:21:32 +11:00
295 lines
13 KiB
Plaintext
Executable file
295 lines
13 KiB
Plaintext
Executable file
#version 450
|
|
|
|
layout(push_constant) uniform Push
|
|
{
|
|
vec4 SourceSize;
|
|
vec4 OutputSize;
|
|
vec4 MASK_RESIZESize;
|
|
vec4 ORIG_LINEARIZEDSize;
|
|
vec4 VERTICAL_SCANLINESSize;
|
|
vec4 BLOOM_APPROXSize;
|
|
vec4 HALATION_BLURSize;
|
|
} registers;
|
|
|
|
#include "params.inc"
|
|
|
|
///////////////////////////// GPL LICENSE NOTICE /////////////////////////////
|
|
|
|
// crt-royale: A full-featured CRT shader, with cheese.
|
|
// Copyright (C) 2014 TroggleMonkey <trogglemonkey@gmx.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 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
|
|
|
|
|
|
///////////////////////////// SETTINGS MANAGEMENT ////////////////////////////
|
|
|
|
#include "../user-settings.h"
|
|
#include "derived-settings-and-constants.h"
|
|
#include "bind-shader-params.h"
|
|
|
|
|
|
////////////////////////////////// INCLUDES //////////////////////////////////
|
|
|
|
#include "scanline-functions.h"
|
|
#include "phosphor-mask-resizing.h"
|
|
#include "bloom-functions.h"//"bloom-functions.h"
|
|
#include "../../../../include/gamma-management.h"
|
|
|
|
/////////////////////////////////// HELPERS //////////////////////////////////
|
|
|
|
vec4 tex2Dtiled_mask_linearize(const sampler2D tex,
|
|
const vec2 tex_uv)
|
|
{
|
|
// If we're manually tiling a texture, anisotropic filtering can get
|
|
// confused. One workaround is to just select the lowest mip level:
|
|
#ifdef PHOSPHOR_MASK_MANUALLY_RESIZE
|
|
#ifdef ANISOTROPIC_TILING_COMPAT_TEX2DLOD
|
|
// TODO: Use tex2Dlod_linearize with a calculated mip level.
|
|
return tex2Dlod_linearize(tex, vec4(tex_uv, 0.0, 0.0));
|
|
#else
|
|
#ifdef ANISOTROPIC_TILING_COMPAT_TEX2DBIAS
|
|
return tex2Dbias_linearize(tex, float4(tex_uv, 0.0, -16.0));
|
|
#else
|
|
return tex2D_linearize(tex, tex_uv);
|
|
#endif
|
|
#endif
|
|
#else
|
|
return tex2D_linearize(tex, tex_uv);
|
|
#endif
|
|
}
|
|
|
|
#pragma stage vertex
|
|
layout(location = 0) in vec4 Position;
|
|
layout(location = 1) in vec2 TexCoord;
|
|
layout(location = 0) out vec2 video_uv;
|
|
layout(location = 1) out vec2 scanline_tex_uv;
|
|
layout(location = 2) out vec2 blur3x3_tex_uv;
|
|
layout(location = 3) out vec2 halation_tex_uv;
|
|
layout(location = 4) out vec2 scanline_texture_size_inv;
|
|
layout(location = 5) out vec4 mask_tile_start_uv_and_size;
|
|
layout(location = 6) out vec2 mask_tiles_per_screen;
|
|
|
|
void main()
|
|
{
|
|
gl_Position = params.MVP * Position;
|
|
|
|
// Our various input textures use different coords.
|
|
video_uv = TexCoord;
|
|
scanline_texture_size_inv =
|
|
registers.VERTICAL_SCANLINESSize.zw;
|
|
scanline_tex_uv = video_uv;// * registers.VERTICAL_SCANLINESSize.xy *
|
|
scanline_texture_size_inv;
|
|
blur3x3_tex_uv = video_uv;// * registers.BLOOM_APPROXSize.xy *
|
|
registers.BLOOM_APPROXSize.zw;
|
|
halation_tex_uv = video_uv;// * registers.HALATION_BLURSize.xy *
|
|
registers.HALATION_BLURSize.zw;
|
|
|
|
// Get a consistent name for the final mask texture size. Sample mode 0
|
|
// uses the manually resized mask, but ignore it if we never resized.
|
|
#ifdef PHOSPHOR_MASK_MANUALLY_RESIZE
|
|
const float mask_sample_mode = params.mask_sample_mode_desired;//get_mask_sample_mode();
|
|
vec2 mask_resize_texture_size = registers.MASK_RESIZESize.xy;
|
|
if(mask_sample_mode > 0.5) mask_resize_texture_size = mask_texture_large_size;
|
|
vec2 mask_resize_video_size = registers.MASK_RESIZESize.xy;
|
|
if(mask_sample_mode > 0.5) mask_resize_video_size = mask_texture_large_size;
|
|
#else
|
|
const vec2 mask_resize_texture_size = mask_texture_large_size;
|
|
const vec2 mask_resize_video_size = mask_texture_large_size;
|
|
#endif
|
|
// mask_tiles_per_screen = vec2(1280.0, 480.0);
|
|
|
|
// Compute mask tile dimensions, starting points, etc.:
|
|
mask_tile_start_uv_and_size = get_mask_sampling_parameters(
|
|
mask_resize_texture_size, mask_resize_video_size, registers.OutputSize.xy,
|
|
mask_tiles_per_screen);
|
|
}
|
|
|
|
#pragma stage fragment
|
|
#pragma format R8G8B8A8_SRGB
|
|
layout(location = 0) in vec2 video_uv;
|
|
layout(location = 1) in vec2 scanline_tex_uv;
|
|
layout(location = 2) in vec2 blur3x3_tex_uv;
|
|
layout(location = 3) in vec2 halation_tex_uv;
|
|
layout(location = 4) in vec2 scanline_texture_size_inv;
|
|
layout(location = 5) in vec4 mask_tile_start_uv_and_size;
|
|
layout(location = 6) in vec2 mask_tiles_per_screen;
|
|
layout(location = 0) out vec4 FragColor;
|
|
layout(set = 0, binding = 2) uniform sampler2D Source;
|
|
layout(set = 0, binding = 3) uniform sampler2D mask_grille_texture_large;
|
|
layout(set = 0, binding = 4) uniform sampler2D mask_slot_texture_large;
|
|
layout(set = 0, binding = 5) uniform sampler2D mask_shadow_texture_large;
|
|
layout(set = 0, binding = 6) uniform sampler2D VERTICAL_SCANLINES;
|
|
layout(set = 0, binding = 7) uniform sampler2D BLOOM_APPROX;
|
|
layout(set = 0, binding = 8) uniform sampler2D HALATION_BLUR;
|
|
#ifdef PHOSPHOR_MASK_MANUALLY_RESIZE
|
|
layout(set = 0, binding = 9) uniform sampler2D MASK_RESIZE;
|
|
#endif
|
|
|
|
void main()
|
|
{
|
|
// This pass: Sample (misconverged?) scanlines to the final horizontal
|
|
// resolution, apply halation (bouncing electrons), and apply the phosphor
|
|
// mask. Fake a bloom if requested. Unless we fake a bloom, the output
|
|
// will be dim from the scanline auto-dim, mask dimming, and low gamma.
|
|
|
|
// Horizontally sample the current row (a vertically interpolated scanline)
|
|
// and account for horizontal convergence offsets, given in units of texels.
|
|
const vec3 scanline_color_dim = sample_rgb_scanline_horizontal(
|
|
VERTICAL_SCANLINES, scanline_tex_uv,
|
|
registers.VERTICAL_SCANLINESSize.xy, scanline_texture_size_inv);
|
|
const float auto_dim_factor = levels_autodim_temp;
|
|
|
|
// Sample the phosphor mask:
|
|
const vec2 tile_uv_wrap = video_uv * mask_tiles_per_screen;
|
|
const vec2 mask_tex_uv = convert_phosphor_tile_uv_wrap_to_tex_uv(
|
|
tile_uv_wrap, mask_tile_start_uv_and_size);
|
|
vec3 phosphor_mask_sample;
|
|
#ifdef PHOSPHOR_MASK_MANUALLY_RESIZE
|
|
bool sample_orig_luts = true;
|
|
if (params.mask_sample_mode_desired < 0.5) sample_orig_luts = false;
|
|
#else
|
|
const bool sample_orig_luts = true;
|
|
#endif
|
|
|
|
if(sample_orig_luts == true)
|
|
{
|
|
// If mask_type is static, this branch will be resolved statically.
|
|
if(params.mask_type < 0.5)
|
|
{
|
|
phosphor_mask_sample = tex2D_linearize(
|
|
mask_grille_texture_large, mask_tex_uv).rgb;
|
|
}
|
|
else if(params.mask_type < 1.5)
|
|
{
|
|
phosphor_mask_sample = tex2D_linearize(
|
|
mask_slot_texture_large, mask_tex_uv).rgb;
|
|
}
|
|
else
|
|
{
|
|
phosphor_mask_sample = tex2D_linearize(
|
|
mask_shadow_texture_large, mask_tex_uv).rgb;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Sample the resized mask, and avoid tiling artifacts:
|
|
phosphor_mask_sample = tex2Dtiled_mask_linearize(
|
|
MASK_RESIZE, mask_tex_uv).rgb;
|
|
}
|
|
|
|
// Sample the halation texture (auto-dim to match the scanlines), and
|
|
// account for both horizontal and vertical convergence offsets, given
|
|
// in units of texels horizontally and same-field scanlines vertically:
|
|
const vec3 halation_color = tex2D_linearize(
|
|
HALATION_BLUR, halation_tex_uv).rgb;
|
|
|
|
// Apply halation: Halation models electrons flying around under the glass
|
|
// and hitting the wrong phosphors (of any color). It desaturates, so
|
|
// average the halation electrons to a scalar. Reduce the local scanline
|
|
// intensity accordingly to conserve energy.
|
|
const vec3 halation_intensity_dim =
|
|
vec3(dot(halation_color, vec3(auto_dim_factor/3.0)));
|
|
const vec3 electron_intensity_dim = mix(scanline_color_dim,
|
|
halation_intensity_dim, params.halation_weight);
|
|
|
|
// Apply the phosphor mask:
|
|
const vec3 phosphor_emission_dim = electron_intensity_dim *
|
|
phosphor_mask_sample;
|
|
// #define PHOSPHOR_BLOOM_FAKE // TODO/FIXME: something seems wrong with the non-FAKE path
|
|
#ifdef PHOSPHOR_BLOOM_FAKE
|
|
// The BLOOM_APPROX pass approximates a blurred version of a masked
|
|
// and scanlined image. It's usually used to compute the brightpass,
|
|
// but we can also use it to fake the bloom stage entirely. Caveats:
|
|
// 1.) A fake bloom is conceptually different, since we're mixing in a
|
|
// fully blurred low-res image, and the biggest implication are:
|
|
// 2.) If mask_amplify is incorrect, results deteriorate more quickly.
|
|
// 3.) The inaccurate blurring hurts quality in high-contrast areas.
|
|
// 4.) The bloom_underestimate_levels parameter seems less sensitive.
|
|
// Reverse the auto-dimming and amplify to compensate for mask dimming:
|
|
#define PHOSPHOR_BLOOM_FAKE_WITH_SIMPLE_BLEND
|
|
#ifdef PHOSPHOR_BLOOM_FAKE_WITH_SIMPLE_BLEND
|
|
const float blur_contrast = 1.05;
|
|
#else
|
|
const float blur_contrast = 1.0;
|
|
#endif
|
|
const float mask_amplify = get_mask_amplify();
|
|
const float undim_factor = 1.0/auto_dim_factor;
|
|
const vec3 phosphor_emission =
|
|
phosphor_emission_dim * undim_factor * mask_amplify;
|
|
// Get a phosphor blur estimate, accounting for convergence offsets:
|
|
const vec3 electron_intensity = electron_intensity_dim * undim_factor;
|
|
const vec3 phosphor_blur_approx_soft = tex2D_linearize(
|
|
BLOOM_APPROX, blur3x3_tex_uv).rgb;
|
|
const vec3 phosphor_blur_approx = mix(phosphor_blur_approx_soft,
|
|
electron_intensity, 0.1) * blur_contrast;
|
|
// We could blend between phosphor_emission and phosphor_blur_approx,
|
|
// solving for the minimum blend_ratio that avoids clipping past 1.0:
|
|
// 1.0 >= total_intensity
|
|
// 1.0 >= phosphor_emission * (1.0 - blend_ratio) +
|
|
// phosphor_blur_approx * blend_ratio
|
|
// blend_ratio = (phosphor_emission - 1.0)/
|
|
// (phosphor_emission - phosphor_blur_approx);
|
|
// However, this blurs far more than necessary, because it aims for
|
|
// full brightness, not minimal blurring. To fix it, base blend_ratio
|
|
// on a max area intensity only so it varies more smoothly:
|
|
const vec3 phosphor_blur_underestimate =
|
|
phosphor_blur_approx * params.bloom_underestimate_levels;
|
|
const vec3 area_max_underestimate =
|
|
phosphor_blur_underestimate * mask_amplify;
|
|
#ifdef PHOSPHOR_BLOOM_FAKE_WITH_SIMPLE_BLEND
|
|
const vec3 blend_ratio_temp =
|
|
(area_max_underestimate - vec3(1.0)) /
|
|
(area_max_underestimate - phosphor_blur_underestimate);
|
|
#else
|
|
// Try doing it like an area-based brightpass. This is nearly
|
|
// identical, but it's worth toying with the code in case I ever
|
|
// find a way to make it look more like a real bloom. (I've had
|
|
// some promising textures from combining an area-based blend ratio
|
|
// for the phosphor blur and a more brightpass-like blend-ratio for
|
|
// the phosphor emission, but I haven't found a way to make the
|
|
// brightness correct across the whole color range, especially with
|
|
// different bloom_underestimate_levels values.)
|
|
const float desired_triad_size = mix(params.mask_triad_size_desired,
|
|
registers.OutputSize.x/params.mask_num_triads_desired,
|
|
params.mask_specify_num_triads);
|
|
const float bloom_sigma = get_min_sigma_to_blur_triad(
|
|
desired_triad_size, bloom_diff_thresh);
|
|
const float center_weight = get_center_weight(bloom_sigma);
|
|
const vec3 max_area_contribution_approx =
|
|
max(vec3(0.0), phosphor_blur_approx -
|
|
center_weight * phosphor_emission);
|
|
const vec3 area_contrib_underestimate =
|
|
params.bloom_underestimate_levels * max_area_contribution_approx;
|
|
const vec3 blend_ratio_temp =
|
|
((vec3(1.0) - area_contrib_underestimate) /
|
|
area_max_underestimate - vec3(1.0)) / (center_weight - 1.0);
|
|
#endif
|
|
// Clamp blend_ratio in case it's out-of-range, but be SUPER careful:
|
|
// min/max/clamp are BIZARRELY broken with lerp (optimization bug?),
|
|
// and this redundant sequence avoids bugs, at least on nVidia cards:
|
|
const vec3 blend_ratio_clamped = max(clamp(blend_ratio_temp, 0.0, 1.0), 0.0);
|
|
const vec3 blend_ratio = mix(blend_ratio_clamped, vec3(1.0), params.bloom_excess);
|
|
// Blend the blurred and unblurred images:
|
|
const vec3 phosphor_emission_unclipped =
|
|
mix(phosphor_emission, phosphor_blur_approx, blend_ratio);
|
|
// Simulate refractive diffusion by reusing the halation sample.
|
|
const vec3 pixel_color = mix(phosphor_emission_unclipped,
|
|
halation_color, params.diffusion_weight);
|
|
#else
|
|
const vec3 pixel_color = phosphor_emission_dim;
|
|
#endif
|
|
|
|
FragColor = encode_output(vec4(pixel_color, 1.0));
|
|
}
|