mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	
		
			
	
	
		
			624 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			HLSL
		
	
	
	
	
	
		
		
			
		
	
	
			624 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			HLSL
		
	
	
	
	
	
|   | #ifndef _PHOSHOR_MASK_CALCULATIONS_H | ||
|  | #define _PHOSHOR_MASK_CALCULATIONS_H | ||
|  | 
 | ||
|  | /////////////////////////////////  MIT LICENSE  //////////////////////////////// | ||
|  | 
 | ||
|  | //  Copyright (C) 2020 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. | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  Our goal is to use arithmetic to generate the phosphor mask. | ||
|  |  *  Phosphor masks are regular patterns, so we want something periodic. | ||
|  |  *  We need to avoid integer arithmetic because it tends to cause rounding errors. | ||
|  |  *   | ||
|  |  *  For all masks, we want to approximate a pulse wave in at least one dimension. This pulse wave | ||
|  |  *  will have narrow peaks, wide troughs, and constant periodicity. | ||
|  |  *    GRILLE will have a pulse wave along the x-axis and will be constant along the y-axis. | ||
|  |  *    SLOT and SHADOW will likely have a superposition of two out-of-phase pulse waves along each axis. | ||
|  |  *      For SHADOW, the width of the peaks will vary such that they generate ellipsoids on the screen. | ||
|  |  * | ||
|  |  *  We can get a periodic function by starting with a triangle wave: T(t, f) = abs(1 - 2*frac(t * f)). | ||
|  |  *    This function gives us a triangle wave with f cycles in the domain [0, 1]. | ||
|  |  *    Note that T(0, f) = 1. | ||
|  |  * | ||
|  |  *  Then we can compose this with a sigmoid curve to squish the triangle wave into a pulse wave. | ||
|  |  *     P(s, p, q) = exp(q s - q/2) / (exp(q s - q/2) + exp(-p)) | ||
|  |  *     s(t, f, o) = T(t*f - o, 1) | ||
|  |  * | ||
|  |  *     f is the number of pulses to render along the given axis. | ||
|  |  *     o is the channel's horizontal ofset along the given axis, normalized via the quotient raw_offset / raw_triad width. | ||
|  |  *     p and q control how closely P resembles an ideal pulse wave and also how wide the peaks and troughs are. | ||
|  |  * | ||
|  |  *  The interaction between p and q is rather complicated and difficult to describe, so they're not a good pair | ||
|  |  *  of parameters for users. But we have the info necessary to solve for p in terms of q. | ||
|  |  *    We know the width of a phosphor and the width of a triad, and we know the domain and range of P. | ||
|  |  *    We can choose a coordinate (t0, y0) that will denote the edge of the phosphor. | ||
|  |  *      Note that y0 = P(t0, p, q) for some p and q. | ||
|  |  *    We let t0 = raw_phosphor_width / raw_triad_width, since we need to respect the shape of the phosphor. | ||
|  |  *    We let the user define P(t0). | ||
|  |  *      Technically, this means the user is defining the brightness of the phosphor's furthest edge. | ||
|  |  *      Visually, this looks like the user is defining the width of the phosphor. | ||
|  |  *      We'll call this the Phosphor Thickness. | ||
|  |  *   We let the user define q. | ||
|  |  *      Technically, this means the user is defining the squareness of the pulse wave. | ||
|  |  *      Visually, this looks like the user is defining the sharpness of the phosphor. | ||
|  |  *      We'll call this the Phosphor Sharpness. | ||
|  |  * | ||
|  |  *   We can solve for p in terms of q very efficiently. | ||
|  |  *     p = (ln(y0 / (1 - y0)) - q) / (0.5 - 2 t0) | ||
|  |  * | ||
|  |  *     Note that, if you work through the algebra, you get a denominator of (t0 - 0.5). | ||
|  |  *       Using (0.5 - 2 t0) actually works better. It also matches up when you try plotting P and (t0, y0). | ||
|  |  * | ||
|  |  *  For the GRILLE and SLOT masks, we can compute p once and recycle it. | ||
|  |  *  For the SHADOW mask, we can either compute p on each iteration or find a way to interpolate between min_p and max_p. | ||
|  |  * | ||
|  |  *  One might expect it'd be way better to use a clamped triangle wave rather than a sigmoid or exponentiated cosine wave.  | ||
|  |  *  As far as I can tell, this ends up being incorrect surprisingly enough. Although it's a good bit faster, | ||
|  |  *  it has terrible aliasing artifacts at small scales. The other implementations are slower, but they produce | ||
|  |  *  evenly-sized RGB phosphors for a variety of configurations even when the triad width is 3 pixels. At that | ||
|  |  *  scale, the triangle wave approach produces triads where one of the phosphors is thicker than the others. | ||
|  |  *  Taking into account the compute_mask_factor trick, the triangle wave approach would be a negligible | ||
|  |  *  performance improvement at the cost of a large drop in visual quality and user friendliness. | ||
|  |  */ | ||
|  | 
 | ||
|  | 
 | ||
|  | #include "bind-shader-params.fxh" | ||
|  | #include "scanline-functions.fxh" | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The GRILLE mask consists of an array of vertical stripes, so each channel will vary along the x-axis and will be constant | ||
|  |  *  along the y-axis. | ||
|  |  * | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Phosphors are 18 units wide with unbounded height. | ||
|  |  *    Phosphors in a triad are 2 units apart. | ||
|  |  *    Triads are 6 units apart. | ||
|  |  *    Triad centers are 64 units apart. | ||
|  |  *    The phosphors follow an RGB pattern. | ||
|  |  *    The left-most phosphor is red and offset by 3 units to the right. | ||
|  |  */ | ||
|  | static const float grille_raw_phosphor_width = 18; | ||
|  | static const float grille_raw_phosphor_gap = 2; | ||
|  | static const float grille_raw_triad_horiz_gap = 6; | ||
|  | static const float grille_raw_triad_width = 3*grille_raw_phosphor_width + 2*grille_raw_phosphor_gap + grille_raw_triad_horiz_gap; | ||
|  | 
 | ||
|  | static const float grille_raw_r_offset = (grille_raw_triad_horiz_gap + grille_raw_phosphor_width) / 2; | ||
|  | static const float grille_raw_g_offset = grille_raw_r_offset + grille_raw_phosphor_width + grille_raw_phosphor_gap; | ||
|  | static const float grille_raw_b_offset = grille_raw_g_offset + grille_raw_phosphor_width + grille_raw_phosphor_gap; | ||
|  | static const float3 grille_norm_center_offsets = float3( | ||
|  |     grille_raw_r_offset, | ||
|  |     grille_raw_g_offset, | ||
|  |     grille_raw_b_offset | ||
|  | ) / grille_raw_triad_width; | ||
|  | 
 | ||
|  | static const float grille_edge_t = grille_raw_phosphor_width / 2; | ||
|  | static const float grille_edge_norm_t = grille_edge_t / grille_raw_triad_width; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The SLOT mask consists of an array of rectangles, so each channel will vary along both the x- and y-axes. | ||
|  |  * | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Phosphors are 18 units wide and 66 units tall. | ||
|  |  *    Phosphors in a triad are 2 units apart. | ||
|  |  *    Triads are 6 units apart horizontally and 6 units apart vertically. | ||
|  |  *    Triad centers are 64 units apart horizontally and 73 units apart vertically. | ||
|  |  *    The phosphors follow an RGB pattern. | ||
|  |  *    The upper-left-most phosphor is red and offset by 3 units to the right and 3 units down. | ||
|  |  */ | ||
|  | static const float slot_raw_phosphor_width = 18; | ||
|  | static const float slot_raw_phosphor_gap = 2; | ||
|  | static const float slot_raw_triad_horiz_gap = 6; | ||
|  | static const float slot_raw_triad_width = 3*slot_raw_phosphor_width + 2*slot_raw_phosphor_gap + slot_raw_triad_horiz_gap; | ||
|  | 
 | ||
|  | static const float slot_raw_phosphor_height = 66; | ||
|  | static const float slot_raw_triad_vert_gap = 6; | ||
|  | static const float slot_raw_triad_height = slot_raw_phosphor_height + slot_raw_triad_vert_gap; | ||
|  | 
 | ||
|  | static const float slot_aspect_ratio = slot_raw_triad_height / slot_raw_triad_width; | ||
|  | 
 | ||
|  | static const float slot_raw_r_offset_x = (slot_raw_triad_horiz_gap + slot_raw_phosphor_width) / 2; | ||
|  | static const float slot_raw_g_offset_x = slot_raw_r_offset_x + slot_raw_phosphor_width + slot_raw_phosphor_gap; | ||
|  | static const float slot_raw_b_offset_x = slot_raw_g_offset_x + slot_raw_phosphor_width + slot_raw_phosphor_gap; | ||
|  | static const float3 slot_norm_center_offsets_x = float3( | ||
|  |     slot_raw_r_offset_x, | ||
|  |     slot_raw_g_offset_x, | ||
|  |     slot_raw_b_offset_x | ||
|  | ) / slot_raw_triad_width; | ||
|  | static const float3 slot_norm_center_offsets_y = float3(0.5, 0.5, 0.5); | ||
|  | 
 | ||
|  | static const float slot_edge_tx = slot_raw_phosphor_width / 2; | ||
|  | // We draw the slot mask as two sets of columns. To do that, we have to pretend the horizontal gap is the size of a whole triad. | ||
|  | //   Then we need to halve the position of the phosphor edge. | ||
|  | static const float slot_edge_norm_tx = 0.5 * slot_edge_tx / slot_raw_triad_width; | ||
|  | static const float slot_edge_ty = slot_raw_phosphor_height / 2; | ||
|  | static const float slot_edge_norm_ty = slot_edge_ty / slot_raw_triad_height; | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The SHADOW mask consists of an array of circles, so each channel will vary along both the x- and y-axes. | ||
|  |  * | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Phosphors are 21 units in diameter. | ||
|  |  *    All phosphors are 0 units apart. | ||
|  |  *    Triad centers are 63 units apart horizontally and 21 units apart vertically. | ||
|  |  *    The phosphors follow a GBR pattern on odd rows and RBG on even rows. | ||
|  |  *    The upper-left-most phosphor is green and centered on the corner of the screen. | ||
|  |  */ | ||
|  | static const float shadow_raw_phosphor_diam = 21; | ||
|  | static const float shadow_raw_phosphor_gap = 0; | ||
|  | static const float shadow_raw_triad_horiz_gap = 0; | ||
|  | static const float shadow_raw_triad_vert_gap = 0; | ||
|  | 
 | ||
|  | static const float shadow_raw_triad_width = 3*shadow_raw_phosphor_diam + 2*shadow_raw_phosphor_gap + shadow_raw_triad_horiz_gap; | ||
|  | static const float shadow_raw_triad_height = shadow_raw_phosphor_diam + shadow_raw_triad_vert_gap; | ||
|  | 
 | ||
|  | static const float shadow_aspect_ratio = shadow_raw_triad_height / shadow_raw_triad_width; | ||
|  | 
 | ||
|  | static const float shadow_raw_g_offset_x = 0; | ||
|  | static const float shadow_raw_b_offset_x = shadow_raw_g_offset_x + shadow_raw_phosphor_diam + shadow_raw_phosphor_gap; | ||
|  | static const float shadow_raw_r_offset_x = shadow_raw_b_offset_x + shadow_raw_phosphor_diam + shadow_raw_phosphor_gap; | ||
|  | static const float3 shadow_norm_center_offsets_x = float3( | ||
|  |     shadow_raw_r_offset_x, | ||
|  |     shadow_raw_g_offset_x, | ||
|  |     shadow_raw_b_offset_x | ||
|  | ) / shadow_raw_triad_width; | ||
|  | 
 | ||
|  | static const float3 shadow_norm_center_offsets_y = float3(0.0, 0.0, 0.0); | ||
|  | 
 | ||
|  | static const float shadow_edge_tx = shadow_raw_phosphor_diam / 2; | ||
|  | static const float shadow_edge_norm_tx = shadow_edge_tx / shadow_raw_triad_width; | ||
|  | static const float shadow_edge_ty = shadow_raw_phosphor_diam / 2; | ||
|  | // We draw the shadow mask as two sets of rows. To do that, we have to pretend the vertical gap is the size of a whole triad. | ||
|  | //   Then we need to halve the position of the phosphor edge. | ||
|  | static const float shadow_edge_norm_ty = 0.5 * shadow_edge_ty / shadow_raw_triad_height; | ||
|  | static const float shadow_norm_phosphor_rad = (shadow_raw_phosphor_diam/2) / shadow_raw_triad_width; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The SMALL GRILLE mask is composed of magenta and green stripes. | ||
|  |  *  Sourced from http://filthypants.blogspot.com/2020/02/crt-shader-masks.html | ||
|  |  *   | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Stripes are 32 units wide. | ||
|  |  *    Stripes in a triad are 0 units apart. | ||
|  |  *    Triads are 0 units apart horizontally. | ||
|  |  *     | ||
|  |  *  Each triad has two quads, side-by-side and aligned. | ||
|  |  *    Neighboring triads are offset vertically. | ||
|  |  *    Below is an array of 2 triads. | ||
|  |  *    x's denote magenta stripes, and o's denote green ones. | ||
|  |  * | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  * | ||
|  |  *    The phosphors follow a MG pattern. | ||
|  |  *    The left-most phosphor is magenta and offset by 16 units to the right. | ||
|  |  */ | ||
|  | 
 | ||
|  | static const float smallgrille_raw_stripe_width = 32; | ||
|  | static const float smallgrille_raw_triad_width = 2*smallgrille_raw_stripe_width; | ||
|  | 
 | ||
|  | static const float smallgrille_raw_r_offset_x = 0.5 * smallgrille_raw_stripe_width; | ||
|  | static const float smallgrille_raw_g_offset_x = smallgrille_raw_r_offset_x + smallgrille_raw_stripe_width; | ||
|  | static const float smallgrille_raw_b_offset_x = smallgrille_raw_r_offset_x; | ||
|  | static const float3 smallgrille_norm_center_offsets_x = float3( | ||
|  |     smallgrille_raw_r_offset_x, | ||
|  |     smallgrille_raw_g_offset_x, | ||
|  |     smallgrille_raw_b_offset_x | ||
|  | ) / smallgrille_raw_triad_width; | ||
|  | 
 | ||
|  | static const float smallgrille_edge_t = 0.5 * smallgrille_raw_stripe_width; | ||
|  | static const float smallgrille_edge_norm_t = smallgrille_edge_t / smallgrille_raw_triad_width; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The SMALL SLOT mask is composed of magenta and green quads. | ||
|  |  *  Sourced from http://filthypants.blogspot.com/2020/02/crt-shader-masks.html | ||
|  |  *   | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Quads are 32 units wide and 48 units tall. | ||
|  |  *    Quads in a triad are 0 units apart. | ||
|  |  *    Triads are 0 units apart horizontally and 16 units apart vertically. | ||
|  |  *     | ||
|  |  *  Each triad has two quads, side-by-side and aligned. | ||
|  |  *    Neighboring triads are offset vertically. | ||
|  |  *    Below is a 2x2 matrix of 4 triads. | ||
|  |  *    x's denote magenta quads, and o's denote green ones. | ||
|  |  * | ||
|  |  *      xxoo     | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *          xxoo | ||
|  |  *      xxoo     | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *          xxoo | ||
|  |  * | ||
|  |  *    The phosphors follow a MG pattern. | ||
|  |  *    The upper-left-most phosphor is magenta and offset by 16 units to the right and 16 units down. | ||
|  |  */ | ||
|  | 
 | ||
|  | static const float smallslot_raw_quad_width = 32; | ||
|  | static const float smallslot_raw_triad_width = 2*smallslot_raw_quad_width; | ||
|  | 
 | ||
|  | static const float smallslot_raw_quad_height = 1.5 * smallslot_raw_quad_width; | ||
|  | static const float smallslot_raw_triad_vert_gap = 0.5 * smallslot_raw_quad_width; | ||
|  | static const float smallslot_raw_triad_height = smallslot_raw_quad_height + smallslot_raw_triad_vert_gap; | ||
|  | 
 | ||
|  | static const float smallslot_aspect_ratio = smallslot_raw_triad_height / smallslot_raw_triad_width; | ||
|  | 
 | ||
|  | static const float smallslot_raw_r_offset_x = 0.5 * smallslot_raw_quad_width; | ||
|  | static const float smallslot_raw_g_offset_x = smallslot_raw_r_offset_x + smallslot_raw_quad_width; | ||
|  | static const float smallslot_raw_b_offset_x = smallslot_raw_r_offset_x; | ||
|  | static const float3 smallslot_norm_center_offsets_x = float3( | ||
|  |     smallslot_raw_r_offset_x, | ||
|  |     smallslot_raw_g_offset_x, | ||
|  |     smallslot_raw_b_offset_x | ||
|  | ) / smallslot_raw_triad_width; | ||
|  | 
 | ||
|  | static const float3 smallslot_norm_center_offsets_y1 = 0.5 * smallslot_raw_quad_height / smallslot_raw_triad_height; | ||
|  | static const float3 smallslot_norm_center_offsets_y2 = smallslot_norm_center_offsets_y1 + smallslot_raw_triad_vert_gap / smallslot_raw_triad_height; | ||
|  | 
 | ||
|  | static const float smallslot_edge_tx = 0.5 * smallslot_raw_quad_width; | ||
|  | // We draw the slot mask as two sets of columns. To do that, we have to pretend the horizontal gap is the size of a whole triad. | ||
|  | //   Then we need to halve the position of the phosphor edge. | ||
|  | static const float smallslot_edge_norm_tx = 0.5 * smallslot_edge_tx / smallslot_raw_triad_width; | ||
|  | static const float smallslot_edge_ty = smallslot_raw_quad_height / 2; | ||
|  | static const float smallslot_edge_norm_ty = smallslot_edge_ty / smallslot_raw_triad_height; | ||
|  | 
 | ||
|  | /* | ||
|  |  *  The SMALL SHADOW mask is composed of magenta and green quads. | ||
|  |  *  Sourced from http://filthypants.blogspot.com/2020/02/crt-shader-masks.html | ||
|  |  *   | ||
|  |  *  It has the following dimensions: | ||
|  |  *    Quads are 17 units wide and 17 units tall. | ||
|  |  *    Quads in a triad are 0 units apart. | ||
|  |  *    Triads are 0 units apart horizontally and 0 units apart vertically. | ||
|  |  *     | ||
|  |  *  Each triad has two quads, side-by-side and aligned. | ||
|  |  *    Neighboring triads are offset vertically. | ||
|  |  *    Below is a 2x2 matrix of 4 triads. | ||
|  |  *    x's denote magenta quads, and o's denote green ones. | ||
|  |  * | ||
|  |  *      xxooxxoo | ||
|  |  *      xxooxxoo | ||
|  |  *      ooxxooxx | ||
|  |  *      ooxxooxx | ||
|  |  * | ||
|  |  *    The phosphors follow a MG pattern. | ||
|  |  *    The upper-left-most phosphor is magenta and offset by 16 units to the right and 16 units down. | ||
|  |  */ | ||
|  | 
 | ||
|  | static const float smallshadow_raw_quad_width = 17; | ||
|  | static const float smallshadow_raw_triad_width = 2 * smallshadow_raw_quad_width; | ||
|  | 
 | ||
|  | static const float smallshadow_raw_quad_height = 17; | ||
|  | static const float smallshadow_raw_triad_height = smallshadow_raw_quad_height; | ||
|  | 
 | ||
|  | static const float smallshadow_aspect_ratio = smallshadow_raw_triad_height / smallshadow_raw_triad_width; | ||
|  | 
 | ||
|  | static const float smallshadow_raw_r_offset_x = 0.5 * smallshadow_raw_quad_width; | ||
|  | static const float smallshadow_raw_g_offset_x = smallshadow_raw_r_offset_x + smallshadow_raw_quad_width; | ||
|  | static const float smallshadow_raw_b_offset_x = smallshadow_raw_r_offset_x; | ||
|  | static const float3 smallshadow_norm_center_offsets_x = float3( | ||
|  |     smallshadow_raw_r_offset_x, | ||
|  |     smallshadow_raw_g_offset_x, | ||
|  |     smallshadow_raw_b_offset_x | ||
|  | ) / smallshadow_raw_triad_width; | ||
|  | 
 | ||
|  | static const float3 smallshadow_norm_center_offsets_y = 0.5 * smallshadow_raw_triad_height; | ||
|  | 
 | ||
|  | static const float smallshadow_edge_tx = 0.5 * smallshadow_raw_quad_width; | ||
|  | static const float smallshadow_edge_norm_tx = smallshadow_edge_tx / smallshadow_raw_triad_width; | ||
|  | static const float smallshadow_edge_ty = 0.5 * smallshadow_raw_quad_height; | ||
|  | // We draw the shadow mask as two sets of rows. To do that, we have to pretend the vertical gap is the size of a whole triad. | ||
|  | //   Then we need to halve the position of the phosphor edge. | ||
|  | static const float smallshadow_edge_norm_ty = 0.5 * smallshadow_edge_ty / smallshadow_raw_triad_height; | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | float get_selected_aspect_ratio() { | ||
|  |     float aspect_ratio; | ||
|  |     [flatten] | ||
|  |     if (mask_type == 0 || mask_type == 3) { | ||
|  |             aspect_ratio = scale_triad_height; | ||
|  |     } | ||
|  |     else if (mask_type == 1 || mask_type == 4) { | ||
|  |             aspect_ratio = scale_triad_height * slot_aspect_ratio; | ||
|  |     } | ||
|  |     else { | ||
|  |             aspect_ratio = scale_triad_height * shadow_aspect_ratio; | ||
|  |     } | ||
|  |     [flatten] | ||
|  |     switch (mask_type) { | ||
|  |         case 0: | ||
|  |             aspect_ratio = scale_triad_height; | ||
|  |             break; | ||
|  |         case 1: | ||
|  |             aspect_ratio = scale_triad_height * slot_aspect_ratio; | ||
|  |             break; | ||
|  |         case 2: | ||
|  |             aspect_ratio = scale_triad_height * shadow_aspect_ratio; | ||
|  |             break; | ||
|  |         case 3: | ||
|  |             aspect_ratio = scale_triad_height; | ||
|  |             break; | ||
|  |         case 4: | ||
|  |             aspect_ratio = scale_triad_height * smallslot_aspect_ratio; | ||
|  |             break; | ||
|  |         default: | ||
|  |             aspect_ratio = scale_triad_height * smallshadow_aspect_ratio; | ||
|  |             break; | ||
|  |     } | ||
|  | 
 | ||
|  |     return aspect_ratio; | ||
|  | } | ||
|  | 
 | ||
|  | float2 calc_triad_size() { | ||
|  |     const float aspect_ratio = get_selected_aspect_ratio(); | ||
|  | 
 | ||
|  |     [branch] | ||
|  |     if (mask_size_param == 0) { | ||
|  |         return float2(1, aspect_ratio) * mask_triad_width; | ||
|  |     } | ||
|  |     else { | ||
|  |         float triad_width = content_size.x * rcp(mask_num_triads_across); | ||
|  |         return float2(1, aspect_ratio) * triad_width; | ||
|  |     } | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | float2 calc_phosphor_viewport_frequency_factor() { | ||
|  |     const float aspect_ratio = get_selected_aspect_ratio(); | ||
|  | 
 | ||
|  |     float2 triad_size_factor; | ||
|  |     float2 num_triads_factor; | ||
|  |     [branch] | ||
|  |     if (geom_rotation_mode == 0 || geom_rotation_mode == 2) { | ||
|  |         triad_size_factor = content_size * rcp(mask_triad_width * float2(1, aspect_ratio)); | ||
|  |         num_triads_factor = mask_num_triads_across * float2(1, content_size.y * rcp(content_size.x) * rcp(aspect_ratio)); | ||
|  |     } | ||
|  |     else { | ||
|  |         triad_size_factor = content_size * rcp(mask_triad_width * float2(1, aspect_ratio)).yx; | ||
|  |         num_triads_factor = mask_num_triads_across * float2(1, content_size.y * rcp(content_size.x) * rcp(aspect_ratio)).yx; | ||
|  |     } | ||
|  | 
 | ||
|  |     return ((mask_size_param == 0) ? triad_size_factor : num_triads_factor); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  We have a pulse wave f(t0_norm, p, q) = y0 with unknown p. | ||
|  |  *  This function solves for p. | ||
|  |  */ | ||
|  | #define calculate_phosphor_p_value(t0_norm, y0, q) (log((y0) * rcp(1 - (y0))) - (q) * (0.5 - 2*(t0_norm))) | ||
|  | 
 | ||
|  | /* | ||
|  |  *  If we don't rescale the phosphor_thickness parameter, it has a logarithmic effect on the phosphor shape. | ||
|  |  *  Rescaling it makes it look closer to a linear effect. | ||
|  |  */ | ||
|  | #define linearize_phosphor_thickness_param(p) (1 - exp(-(p))) | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  Generates a grille mask with the desired resolution and sharpness. | ||
|  |  */ | ||
|  | float3 get_phosphor_intensity_grille( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 grille_pq | ||
|  | ) { | ||
|  |     float3 center_offsets = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         grille_norm_center_offsets.bgr : grille_norm_center_offsets; | ||
|  |          | ||
|  |     center_offsets += phosphor_offset_x * 0.5; | ||
|  | 
 | ||
|  |     float3 theta = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets, 1); | ||
|  |     float3 alpha = exp((theta - 0.5) * grille_pq.y); | ||
|  |     return alpha * rcp(alpha + grille_pq.x); | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* | ||
|  |  *  Generates a slot mask with the desired resolution and sharpness. | ||
|  |  */ | ||
|  | float3 get_phosphor_intensity_slot( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 slot_pq_x, | ||
|  |     const float2 slot_pq_y | ||
|  | ) { | ||
|  |     float3 center_offsets_x = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         slot_norm_center_offsets_x.bgr : slot_norm_center_offsets_x; | ||
|  |     float3 center_offsets_y = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         slot_norm_center_offsets_y.bgr : slot_norm_center_offsets_y; | ||
|  |          | ||
|  |     center_offsets_x += phosphor_offset_x * 0.5; | ||
|  |     center_offsets_y += phosphor_offset_y * 0.5; | ||
|  | 
 | ||
|  |     float3 theta_x1 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x, 0.5); | ||
|  |     float3 alpha_x1 = exp((theta_x1 - 0.5) * slot_pq_x.y); | ||
|  |     alpha_x1 *= rcp(alpha_x1 + slot_pq_x.x); | ||
|  |      | ||
|  |     float3 theta_x2 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x + 1, 0.5); | ||
|  |     float3 alpha_x2 = exp((theta_x2 - 0.5) * slot_pq_x.y); | ||
|  |     alpha_x2 *= rcp(alpha_x2 + slot_pq_x.x); | ||
|  | 
 | ||
|  |     float3 theta_y1 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y, 1); | ||
|  |     float3 alpha_y1 = exp((theta_y1 - 0.5) * slot_pq_y.y); | ||
|  |     alpha_y1 *= rcp(alpha_y1 + slot_pq_y.x); | ||
|  |      | ||
|  |     float3 theta_y2 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y + 0.5, 1); | ||
|  |     float3 alpha_y2 = exp((theta_y2 - 0.5) * slot_pq_y.y); | ||
|  |     alpha_y2 *= rcp(alpha_y2 + slot_pq_y.x); | ||
|  | 
 | ||
|  |     return alpha_x1 * alpha_y1 + alpha_x2 * alpha_y2; | ||
|  | } | ||
|  | 
 | ||
|  | /* | ||
|  |  *  Generates a shadow mask with the desired resolution and sharpness. | ||
|  |  */ | ||
|  | float3 get_phosphor_intensity_shadow( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 shadow_q | ||
|  | ) { | ||
|  |     float3 center_offsets_x = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         shadow_norm_center_offsets_x.bgr : shadow_norm_center_offsets_x; | ||
|  |     float3 center_offsets_y = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         shadow_norm_center_offsets_y.bgr : shadow_norm_center_offsets_y; | ||
|  |          | ||
|  |     center_offsets_x += phosphor_offset_x * 0.5; | ||
|  |     center_offsets_y += phosphor_offset_y * 0.5; | ||
|  | 
 | ||
|  |     const float2 thickness_scaled = linearize_phosphor_thickness_param(phosphor_thickness); | ||
|  | 
 | ||
|  |     const float3 x_adj = texcoord.x * viewport_frequency_factor.x - center_offsets_x;     | ||
|  |     const float3 y_adj = texcoord.y * viewport_frequency_factor.y - center_offsets_y; | ||
|  | 
 | ||
|  |     const float3 texcoord_x_periodic1 = shadow_norm_phosphor_rad * triangle_wave(x_adj * 3 - 0.5, 1.0); | ||
|  |     const float3 texcoord_x_periodic2 = shadow_norm_phosphor_rad * triangle_wave(x_adj * 3, 1.0); | ||
|  |     const float3 ty1 = sqrt( | ||
|  |         shadow_norm_phosphor_rad*shadow_norm_phosphor_rad - texcoord_x_periodic1*texcoord_x_periodic1 | ||
|  |     ); | ||
|  |     const float3 ty2 = sqrt( | ||
|  |         shadow_norm_phosphor_rad*shadow_norm_phosphor_rad - texcoord_x_periodic2*texcoord_x_periodic2 | ||
|  |     ); | ||
|  | 
 | ||
|  |     const float shadow_px = exp(-calculate_phosphor_p_value(shadow_edge_norm_tx, thickness_scaled.x, shadow_q.x)); | ||
|  |     const float3 shadow_py1 = exp(-calculate_phosphor_p_value(ty1 * 0.5 * rcp(shadow_aspect_ratio), thickness_scaled.y, shadow_q.y)); | ||
|  |     const float3 shadow_py2 = exp(-calculate_phosphor_p_value(ty2 * 0.5 * rcp(shadow_aspect_ratio), thickness_scaled.y, shadow_q.y)); | ||
|  | 
 | ||
|  |     float3 theta_x1 = triangle_wave(x_adj, 1); | ||
|  |     float3 alpha_x1 = exp((theta_x1 - 0.5) * shadow_q.x); | ||
|  |     alpha_x1 *= rcp(alpha_x1 + shadow_px); | ||
|  |      | ||
|  |     float3 theta_x2 = triangle_wave(x_adj + 0.5, 1); | ||
|  |     float3 alpha_x2 = exp((theta_x2 - 0.5) * shadow_q.x); | ||
|  |     alpha_x2 *= rcp(alpha_x2 + shadow_px); | ||
|  |      | ||
|  |     float3 theta_y1 = triangle_wave(y_adj, 0.5); | ||
|  |     float3 alpha_y1 = exp((theta_y1 - 0.5) * shadow_q.y); | ||
|  |     alpha_y1 *= rcp(alpha_y1 + shadow_py1); | ||
|  |      | ||
|  |     float3 theta_y2 = triangle_wave(y_adj + 1, 0.5); | ||
|  |     float3 alpha_y2 = exp((theta_y2 - 0.5) * shadow_q.y); | ||
|  |     alpha_y2 *= rcp(alpha_y2 + shadow_py2); | ||
|  | 
 | ||
|  |     return alpha_x1 * alpha_y1 + alpha_x2 * alpha_y2; | ||
|  | } | ||
|  | 
 | ||
|  | float3 get_phosphor_intensity_grille_small( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 grille_pq_x | ||
|  | ) { | ||
|  |     float3 center_offsets_x = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallgrille_norm_center_offsets_x.grg : smallgrille_norm_center_offsets_x; | ||
|  |          | ||
|  |     center_offsets_x += phosphor_offset_x * 0.5; | ||
|  |      | ||
|  |     float3 theta = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x, 1); | ||
|  |     float3 alpha = exp((theta - 0.5) * grille_pq_x.y); | ||
|  |     alpha *= rcp(alpha + grille_pq_x.x); | ||
|  | 
 | ||
|  |     // Taking a sqrt here helps hide the gaps between the pixels when the triad size is small | ||
|  |     return sqrt(alpha); | ||
|  | } | ||
|  | 
 | ||
|  | float3 get_phosphor_intensity_slot_small( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 slot_pq_x, | ||
|  |     const float2 slot_pq_y | ||
|  | ) { | ||
|  |     float3 center_offsets_x = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallslot_norm_center_offsets_x.grg : smallslot_norm_center_offsets_x; | ||
|  |     float3 center_offsets_y1 = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallslot_norm_center_offsets_y1.grg : smallslot_norm_center_offsets_y1; | ||
|  |     float3 center_offsets_y2 = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallslot_norm_center_offsets_y2.grg : smallslot_norm_center_offsets_y2; | ||
|  |          | ||
|  |     center_offsets_x += phosphor_offset_x * 0.5; | ||
|  |     center_offsets_y1 += phosphor_offset_y * 0.5; | ||
|  |     center_offsets_y2 += phosphor_offset_y * 0.5; | ||
|  | 
 | ||
|  |     float3 theta_x1 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x, 0.5); | ||
|  |     float3 alpha_x1 = exp((theta_x1 - 0.5) * slot_pq_x.y); | ||
|  |     alpha_x1 *= rcp(alpha_x1 + slot_pq_x.x); | ||
|  |      | ||
|  |     float3 theta_x2 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x + 1, 0.5); | ||
|  |     float3 alpha_x2 = exp((theta_x2 - 0.5) * slot_pq_x.y); | ||
|  |     alpha_x2 *= rcp(alpha_x2 + slot_pq_x.x); | ||
|  | 
 | ||
|  |     float3 theta_y1 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y1, 1); | ||
|  |     float3 alpha_y1 = exp((theta_y1 - 0.5) * slot_pq_y.y); | ||
|  |     alpha_y1 *= rcp(alpha_y1 + slot_pq_y.x); | ||
|  |      | ||
|  |     float3 theta_y2 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y2 + 0.5, 1); | ||
|  |     float3 alpha_y2 = exp((theta_y2 - 0.5) * slot_pq_y.y); | ||
|  |     alpha_y2 *= rcp(alpha_y2 + slot_pq_y.x); | ||
|  | 
 | ||
|  |     // Taking a sqrt here helps hide the gaps between the pixels when the triad size is small | ||
|  |     return (alpha_x1 * alpha_y1 + alpha_x2 * alpha_y2); | ||
|  | } | ||
|  | 
 | ||
|  | float3 get_phosphor_intensity_shadow_small( | ||
|  |     const float2 texcoord, | ||
|  |     const float2 viewport_frequency_factor, | ||
|  |     const float2 shadow_pq_x, | ||
|  |     const float2 shadow_pq_y | ||
|  | ) { | ||
|  |     float3 center_offsets_x = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallshadow_norm_center_offsets_x.grg : smallshadow_norm_center_offsets_x; | ||
|  |     float3 center_offsets_y = (geom_rotation_mode == 2 || geom_rotation_mode == 3) ? | ||
|  |         smallshadow_norm_center_offsets_y.grg : smallshadow_norm_center_offsets_y; | ||
|  | 
 | ||
|  |     center_offsets_x += phosphor_offset_x * 0.5; | ||
|  |     center_offsets_y += phosphor_offset_y * 0.5; | ||
|  |      | ||
|  |     float3 theta_x1 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x, 1); | ||
|  |     float3 alpha_x1 = exp((theta_x1 - 0.5) * shadow_pq_x.y); | ||
|  |     alpha_x1 *= rcp(alpha_x1 + shadow_pq_x.x); | ||
|  |      | ||
|  |     float3 theta_x2 = triangle_wave(texcoord.x * viewport_frequency_factor.x - center_offsets_x + 0.5, 1); | ||
|  |     float3 alpha_x2 = exp((theta_x2 - 0.5) * shadow_pq_x.y); | ||
|  |     alpha_x2 *= rcp(alpha_x2 + shadow_pq_x.x); | ||
|  | 
 | ||
|  |     float3 theta_y1 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y, 0.5); | ||
|  |     float3 alpha_y1 = exp((theta_y1 - 0.5) * shadow_pq_y.y); | ||
|  |     alpha_y1 *= rcp(alpha_y1 + shadow_pq_y.x); | ||
|  |      | ||
|  |     float3 theta_y2 = triangle_wave(texcoord.y * viewport_frequency_factor.y - center_offsets_y + 1, 0.5); | ||
|  |     float3 alpha_y2 = exp((theta_y2 - 0.5) * shadow_pq_y.y); | ||
|  |     alpha_y2 *= rcp(alpha_y2 + shadow_pq_y.x); | ||
|  | 
 | ||
|  |     // Taking a sqrt here helps hide the gaps between the pixels when the triad size is small | ||
|  |     return sqrt(alpha_x1 * alpha_y1 + alpha_x2 * alpha_y2); | ||
|  | } | ||
|  | 
 | ||
|  | #endif  //  _PHOSHOR_MASK_CALCULATIONS_H |