mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 01:25:51 +00:00
211 lines
7.5 KiB
HLSL
211 lines
7.5 KiB
HLSL
#ifndef _PHOSPHOR_MASK_H
|
|
#define _PHOSPHOR_MASK_H
|
|
|
|
///////////////////////////////// MIT LICENSE ////////////////////////////////
|
|
|
|
// Copyright (C) 2022 Alex Gunter
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
|
|
#include "../lib/bind-shader-params.fxh"
|
|
#include "../lib/phosphor-mask-calculations.fxh"
|
|
|
|
#include "shared-objects.fxh"
|
|
|
|
|
|
// Split into 64 segments that overlap a little bit
|
|
static const float num_segments = 64;
|
|
static const float segment_offset = 0.015625; // 1/64
|
|
static const float segment_width = 0.0234375; // 1/128
|
|
|
|
void generatePhosphorMaskVS(
|
|
in uint id : SV_VertexID,
|
|
|
|
out float4 position : SV_Position,
|
|
out float2 texcoord : TEXCOORD0,
|
|
out float2 viewport_frequency_factor: TEXCOORD1,
|
|
out float2 mask_pq_x : TEXCOORD2,
|
|
out float2 mask_pq_y : TEXCOORD3
|
|
) {
|
|
const float screen_segment_idx = frame_count % num_segments;
|
|
const float left_coord = lerp(segment_offset * screen_segment_idx, 0, overlay_active > 0);
|
|
const float right_coord = lerp(left_coord + segment_width, 1, overlay_active > 0);
|
|
const float pos_center = 2 * (left_coord + 0.5 * segment_width - 0.5);
|
|
const float pos_left = lerp(pos_center - segment_width, -1, overlay_active > 0);
|
|
const float pos_right = lerp(pos_center + segment_width, 1, overlay_active > 0);
|
|
|
|
#if _DX9_ACTIVE
|
|
texcoord.x = (id == 1 || id == 3) ? right_coord : left_coord;
|
|
texcoord.y = (id > 1) ? 1 : 0;
|
|
|
|
position.x = (id == 1 || id == 3) ? pos_right : pos_left;
|
|
position.y = (id > 1) ? -1 : 1;
|
|
position.zw = 1;
|
|
#else
|
|
texcoord.x = (id & 1) ? right_coord : left_coord;
|
|
texcoord.y = (id & 2) ? 1 : 0;
|
|
|
|
position.x = (id & 1) ? pos_right : pos_left;
|
|
position.y = (id & 2) ? -1 : 1;
|
|
position.zw = 1;
|
|
#endif
|
|
|
|
viewport_frequency_factor = calc_phosphor_viewport_frequency_factor();
|
|
|
|
// We don't alter these based on screen rotation because they're independent of screen dimensions.
|
|
float edge_norm_tx;
|
|
float edge_norm_ty;
|
|
[flatten]
|
|
switch (mask_type) {
|
|
case 0:
|
|
edge_norm_tx = grille_edge_norm_t;
|
|
break;
|
|
case 1:
|
|
edge_norm_tx = slot_edge_norm_tx;
|
|
edge_norm_ty = slot_edge_norm_ty;
|
|
break;
|
|
case 2:
|
|
edge_norm_tx = shadow_edge_norm_tx;
|
|
edge_norm_ty = shadow_edge_norm_ty;
|
|
break;
|
|
case 3:
|
|
edge_norm_tx = smallgrille_edge_norm_t;
|
|
break;
|
|
case 4:
|
|
edge_norm_tx = smallslot_edge_norm_tx;
|
|
edge_norm_ty = smallslot_edge_norm_ty;
|
|
break;
|
|
default:
|
|
edge_norm_tx = smallshadow_edge_norm_tx;
|
|
edge_norm_ty = smallshadow_edge_norm_ty;
|
|
break;
|
|
}
|
|
|
|
const float2 thickness_scaled = linearize_phosphor_thickness_param(phosphor_thickness);
|
|
const float mask_p_x = exp(-calculate_phosphor_p_value(edge_norm_tx, thickness_scaled.x, phosphor_sharpness.x));
|
|
const float mask_p_y = exp(-calculate_phosphor_p_value(edge_norm_ty, thickness_scaled.y, phosphor_sharpness.y));
|
|
mask_pq_x = float2(mask_p_x, phosphor_sharpness.x);
|
|
mask_pq_y = float2(mask_p_y, phosphor_sharpness.y);
|
|
}
|
|
|
|
void generatePhosphorMaskPS(
|
|
in float4 pos : SV_Position,
|
|
in float2 texcoord : TEXCOORD0,
|
|
in float2 viewport_frequency_factor: TEXCOORD1,
|
|
in float2 mask_pq_x : TEXCOORD2,
|
|
in float2 mask_pq_y : TEXCOORD3,
|
|
|
|
out float4 color : SV_Target
|
|
) {
|
|
[branch]
|
|
if (geom_rotation_mode == 1 || geom_rotation_mode == 3) {
|
|
texcoord = texcoord.yx;
|
|
viewport_frequency_factor = viewport_frequency_factor.yx;
|
|
}
|
|
|
|
float3 phosphor_color;
|
|
[branch]
|
|
if (mask_type == 0) {
|
|
phosphor_color = get_phosphor_intensity_grille(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
mask_pq_x
|
|
);
|
|
}
|
|
else if (mask_type == 1) {
|
|
phosphor_color = get_phosphor_intensity_slot(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
mask_pq_x,
|
|
mask_pq_y
|
|
);
|
|
}
|
|
else if (mask_type == 2) {
|
|
phosphor_color = get_phosphor_intensity_shadow(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
float2(mask_pq_x.y, mask_pq_y.y)
|
|
);
|
|
}
|
|
else if (mask_type == 3) {
|
|
phosphor_color = get_phosphor_intensity_grille_small(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
mask_pq_x
|
|
);
|
|
}
|
|
else if (mask_type == 4) {
|
|
phosphor_color = get_phosphor_intensity_slot_small(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
mask_pq_x,
|
|
mask_pq_y
|
|
);
|
|
}
|
|
else {
|
|
phosphor_color = get_phosphor_intensity_shadow_small(
|
|
texcoord,
|
|
viewport_frequency_factor,
|
|
mask_pq_x,
|
|
mask_pq_y
|
|
);
|
|
}
|
|
|
|
color = float4(phosphor_color, 1.0);
|
|
}
|
|
|
|
|
|
void applyComputedPhosphorMaskPS(
|
|
in float4 pos : SV_Position,
|
|
in float2 texcoord : TEXCOORD0,
|
|
|
|
out float4 color : SV_Target
|
|
) {
|
|
bool use_deinterlacing_tex = enable_interlacing && (
|
|
scanline_deinterlacing_mode == 2 || scanline_deinterlacing_mode == 3
|
|
);
|
|
|
|
float3 scanline_color_dim;
|
|
[branch]
|
|
if (use_deinterlacing_tex) scanline_color_dim = tex2D(samplerDeinterlace, texcoord).rgb;
|
|
else scanline_color_dim = tex2D(samplerBeamConvergence, texcoord).rgb;
|
|
|
|
const float3 phosphor_color = tex2D(samplerPhosphorMask, texcoord).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 float3 halation_color = tex2D_linearize(samplerBlurHorizontal, texcoord, get_intermediate_gamma()).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 float halation_intensity_dim_scalar = dot(halation_color, float3(1, 1, 1)) / 3.0;
|
|
const float3 halation_intensity_dim = halation_intensity_dim_scalar;
|
|
const float3 electron_intensity_dim = lerp(scanline_color_dim, halation_intensity_dim, halation_weight);
|
|
|
|
// Apply the phosphor mask:
|
|
const float3 phosphor_emission_dim = electron_intensity_dim * phosphor_color;
|
|
|
|
color = float4(phosphor_emission_dim, 1.0);
|
|
}
|
|
|
|
#endif // _PHOSPHOR_MASK_H |