summaryrefslogtreecommitdiff
path: root/base/fragment_shader.glsl
blob: c21b2def9d93f41902a0cc2da66d813d08342551 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Specify default precision for fragment shaders

out vec4 outcolor;
in vec2 frag_texture_coord;

uniform vec2 resolution;
uniform vec2 src_image_size;
uniform float brightness;
uniform vec4 tone_data;
uniform bool crt_emulation;
uniform sampler2D iChannel0;

vec3 CrtsFetch(vec2 uv) {
	const float bias = 0.003333333;
	return max(texture(iChannel0, uv, -16.0).rgb, vec3(bias));
}

#define CrtsRcpF1(x) (1.0 / (x))
#define CrtsSatF1(x) clamp((x), 0.0, 1.0)
const float PI2 = 6.28318530717958;
const float HALF = 0.5;

float CrtsMax3F1(float a, float b, float c) {
	return max(a, max(b, c));
}

vec3 CrtsMask(vec2 pos, float dark) {
	#ifdef CRTS_MASK_GRILLE
		vec3 m = vec3(dark);
		float x = fract(pos.x * (1.0 / 3.0));
		m.r = (x < (1.0 / 3.0)) ? 1.0 : dark;
		m.g = (x >= (1.0 / 3.0) && x < (2.0 / 3.0)) ? 1.0 : dark;
		m.b = (x >= (2.0 / 3.0)) ? 1.0 : dark;
		return m;
	#endif

	#ifdef CRTS_MASK_GRILLE_LITE
		vec3 m = vec3(1.0);
		float x = fract(pos.x * (1.0 / 3.0));
		m.r = (x < (1.0 / 3.0)) ? dark : 1.0;
		m.g = (x >= (1.0 / 3.0) && x < (2.0 / 3.0)) ? dark : 1.0;
		m.b = (x >= (2.0 / 3.0)) ? dark : 1.0;
		return m;
	#endif

	#ifdef CRTS_MASK_NONE
		return vec3(1.0);
	#endif

	#ifdef CRTS_MASK_SHADOW
		pos.x += pos.y * 3.0;
		vec3 m = vec3(dark);
		float x = fract(pos.x * (1.0 / 6.0));
		m.r = (x < (1.0 / 3.0)) ? 1.0 : dark;
		m.g = (x >= (1.0 / 3.0) && x < (2.0 / 3.0)) ? 1.0 : dark;
		m.b = (x >= (2.0 / 3.0)) ? 1.0 : dark;
		return m;
	#endif
}

vec3 CrtsFilter(vec2 ipos, vec2 inputSizeDivOutputSize, vec2 halfInputSize, vec2 rcpInputSize, vec2 rcpOutputSize, vec2 twoDivOutputSize, float inputHeight, vec2 warp, float thin, float blur, float mask, vec4 tone) {
	vec2 pos = ipos * twoDivOutputSize - vec2(1.0);
	pos *= vec2(1.0 + (pos.y * pos.y) * warp.x, 1.0 + (pos.x * pos.x) * warp.y);
	float vin = 1.0 - ((1.0 - CrtsSatF1(pos.x * pos.x)) * (1.0 - CrtsSatF1(pos.y * pos.y)));
	vin = CrtsSatF1((-vin) * inputHeight + inputHeight);
	pos = pos * halfInputSize + halfInputSize;

	float y0 = floor(pos.y - 0.5) + 0.5;
	float x0 = floor(pos.x - 1.5) + 0.5;
	vec2 p = vec2(x0 * rcpInputSize.x, y0 * rcpInputSize.y);

	vec3 colA[4], colB[4];
	for (int i = 0; i < 4; i++) {
		colA[i] = CrtsFetch(p);
		p.x += rcpInputSize.x;
	}
	p.y += rcpInputSize.y;
	for (int i = 3; i >= 0; i--) {
		p.x -= rcpInputSize.x;
		colB[i] = CrtsFetch(p);
	}

	float off = pos.y - y0;
	float scanA = cos(min(HALF, off * thin) * PI2) * HALF + HALF;
	float scanB = cos(min(HALF, (-off) * thin + thin) * PI2) * HALF + HALF;

	float off0 = pos.x - x0;
	float pix[4];
	for (int i = 0; i < 4; i++) {
		float diff = off0 - float(i);
		pix[i] = exp2(blur * diff * diff);
	}
	float pixT = CrtsRcpF1(pix[0] + pix[1] + pix[2] + pix[3]);

	#ifdef CRTS_WARP
		pixT *= vin;
	#endif

	scanA *= pixT;
	scanB *= pixT;

	vec3 color = (colA[0] * pix[0] + colA[1] * pix[1] + colA[2] * pix[2] + colA[3] * pix[3]) * scanA + (colB[0] * pix[0] + colB[1] * pix[1] + colB[2] * pix[2] + colB[3] * pix[3]) * scanB;
	color *= CrtsMask(ipos, mask);

	#ifdef CRTS_TONE
		float peak = max(1.0 / (256.0 * 65536.0), CrtsMax3F1(color.r, color.g, color.b));
		vec3 ratio = color * CrtsRcpF1(peak);
		#ifdef CRTS_CONTRAST
			peak = pow(peak, tone.x);
		#endif
		peak = peak * CrtsRcpF1(peak * tone.y + tone.z);
		#ifdef CRTS_SATURATION
			ratio = pow(ratio, vec3(tone.w));
		#endif
		return ratio * peak;
	#else
		return color;
	#endif
}

vec3 linearToSRGB(vec3 color) {
	return pow(color, vec3(1.0 / 2.2));
}

void main() {
	vec2 fragCoord = vec2(frag_texture_coord.x, 1.0 - frag_texture_coord.y);
	if (crt_emulation) {
		outcolor.rgb = CrtsFilter(
			fragCoord.xy * resolution,
			src_image_size / resolution,
			src_image_size * vec2(0.5),
			1.0 / src_image_size,
			1.0 / resolution,
			2.0 / resolution,
			src_image_size.y,
			vec2(1.0 / 24.0, 1.0 / 16.0),	// warp value
			INPUT_THIN,
			INPUT_BLUR,
			INPUT_MASK,
			tone_data
		);

		outcolor.rgb *= brightness;
		outcolor = vec4(outcolor.rgb, 1.0); // Keep original color with alpha set to 1.0

	} else {
		outcolor = texture(iChannel0, fragCoord);
	}
}