diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 73406d6..97f37d7 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -29,6 +29,7 @@ @"Bilinear", @"SmoothBilinear", @"LCD", + @"CRT", @"Scale2x", @"Scale4x", @"AAScale2x", diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 747a682..f712a24 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -46,6 +46,7 @@ + diff --git a/SDL/gui.c b/SDL/gui.c index ed9f357..4de76f5 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -427,6 +427,7 @@ struct shader_name { {"Bilinear", "Bilinear"}, {"SmoothBilinear", "Smooth Bilinear"}, {"LCD", "LCD Display"}, + {"CRT", "CRT Display"}, {"Scale2x", "Scale2x"}, {"Scale4x", "Scale4x"}, {"AAScale2x", "Anti-aliased Scale2x"}, diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh new file mode 100644 index 0000000..cbb1528 --- /dev/null +++ b/Shaders/CRT.fsh @@ -0,0 +1,163 @@ +#define COLOR_LOW 0.7 +#define COLOR_HIGH 1.0 +#define VERTICAL_BORDER_DEPTH 0.6 +#define SCANLINE_DEPTH 0.3 +#define CURVENESS 0.3 + +STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) +{ + /* Curve and pixel ratio */ + float y_curve = cos(position.x - 0.5) * CURVENESS + (1 - CURVENESS); + float y_multiplier = 8.0 / 7.0 / y_curve; + position.y *= y_multiplier; + position.y -= (y_multiplier - 1) / 2; + if (position.y < 0.0) return vec4(0,0,0,0); + if (position.y > 1.0) return vec4(0,0,0,0); + + float x_curve = cos(position.y - 0.5) * CURVENESS + (1 - CURVENESS); + float x_multiplier = 1/x_curve; + position.x *= x_multiplier; + position.x -= (x_multiplier - 1) / 2; + if (position.x < 0.0) return vec4(0,0,0,0); + if (position.x > 1.0) return vec4(0,0,0,0); + + /* Setting up common vars */ + vec2 pos = fract(position * input_resolution); + vec2 sub_pos = fract(position * input_resolution * 6); + + vec4 center = texture(image, position); + vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0)); + vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0)); + + /* Vertical blurring */ + if (pos.y < 1.0 / 6.0) { + center = mix(center, texture(image, position + vec2(0, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, -1.0 / input_resolution.y)), 0.5 - sub_pos.y / 2.0); + } + else if (pos.y > 5.0 / 6.0) { + center = mix(center, texture(image, position + vec2(0, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + left = mix(left, texture(image, position + vec2(-1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + right = mix(right, texture(image, position + vec2( 1.0 / input_resolution.x, 1.0 / input_resolution.y)), sub_pos.y / 2.0); + } + + /* Scanlines */ + float scanline_multiplier; + if (pos.y < 0.5) { + scanline_multiplier = (pos.y * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + else { + scanline_multiplier = ((1 - pos.y) * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + } + + center *= scanline_multiplier; + left *= scanline_multiplier; + right *= scanline_multiplier; + + /* Vertical seperator for shadow masks */ + bool odd = (int)(position * input_resolution).x & 1; + if (odd) { + pos.y += 0.5; + pos.y = fract(pos.y); + } + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + + /* Blur the edges of the separators of adjacent columns */ + if (pos.x < 1.0 / 6.0 || pos.x > 5.0 / 6.0) { + pos.y += 0.5; + pos.y = fract(pos.y); + + if (pos.y < 1.0 / 3.0) { + float gradient_position = pos.y * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + else if (pos.y > 2.0 / 3.0) { + float gradient_position = (1 - pos.y) * 3.0; + if (pos.x < 0.5) { + gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + } + else { + gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + } + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1 - VERTICAL_BORDER_DEPTH); + } + } + + + /* Subpixel blurring, like LCD filter*/ + + vec4 midleft = mix(left, center, 0.5); + vec4 midright = mix(right, center, 0.5); + + vec4 ret; + if (pos.x < 1.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + sub_pos.x); + } + else if (pos.x < 2.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + sub_pos.x); + } + else if (pos.x < 3.0 / 6.0) { + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1), + sub_pos.x); + } + else if (pos.x < 4.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else if (pos.x < 5.0 / 6.0) { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + sub_pos.x); + } + else { + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1), + sub_pos.x); + } + + /* Anti alias the curve */ + vec2 pixel_position = position * output_resolution; + if (pixel_position.x < 1) { + ret *= pixel_position.x; + } + else if (pixel_position.x > output_resolution.x - 1) { + ret *= output_resolution.x - pixel_position.x; + } + if (pixel_position.y < 1) { + ret *= pixel_position.y; + } + else if (pixel_position.y > output_resolution.y - 1) { + ret *= output_resolution.y - pixel_position.y; + } + + + return ret; +}