From c2432b73485a6906a62f7c2f540f8c29ece3c88a Mon Sep 17 00:00:00 2001 From: Maximilian Mader Date: Tue, 11 Jun 2019 04:21:32 +0200 Subject: [PATCH] Work with WebGL 1.0 and WebGL 2.0 --- Shaders/CRT.fsh | 98 ++++++------- Shaders/HQ2x.fsh | 14 +- Shaders/LCD.fsh | 42 +++--- Shaders/OmniScale.fsh | 62 ++++----- Shaders/WasmMasterShader.fsh | 40 ++++++ wasm/Makefile | 7 +- wasm/main.c | 131 +++++++++++++----- wasm/main.h | 8 ++ wasm/opengl_compat.c | 1 + wasm/opengl_compat.h | 7 + wasm/shader.c | 261 +++++++++++++++++++++++++++++++++++ wasm/shader.h | 29 ++++ 12 files changed, 557 insertions(+), 143 deletions(-) create mode 100644 Shaders/WasmMasterShader.fsh create mode 100644 wasm/opengl_compat.c create mode 100644 wasm/opengl_compat.h create mode 100644 wasm/shader.c create mode 100644 wasm/shader.h diff --git a/Shaders/CRT.fsh b/Shaders/CRT.fsh index 8684451..3ccb383 100644 --- a/Shaders/CRT.fsh +++ b/Shaders/CRT.fsh @@ -7,36 +7,36 @@ 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_curve = cos(position.x - 0.5) * CURVENESS + (1.0 - CURVENESS); float y_multiplier = 8.0 / 7.0 / y_curve; position.y *= y_multiplier; - position.y -= (y_multiplier - 1) / 2; + position.y -= (y_multiplier - 1.0) / 2.0; 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; + float x_curve = cos(position.y - 0.5) * CURVENESS + (1.0 - CURVENESS); + float x_multiplier = 1.0 / 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); + position.x -= (x_multiplier - 1.0) / 2.0; + if (position.x < 0.0) return vec4(0.0, 0.0, 0.0, 0.0); + if (position.x > 1.0) return vec4(0.0, 0.0, 0.0, 0.0); /* Setting up common vars */ vec2 pos = fract(position * input_resolution); - vec2 sub_pos = fract(position * input_resolution * 6); + vec2 sub_pos = fract(position * input_resolution * 6.0); 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)); + vec4 left = texture(image, position - vec2(1.0 / input_resolution.x, 0.0)); + vec4 right = texture(image, position + vec2(1.0 / input_resolution.x, 0.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); + center = mix(center, texture(image, position + vec2(0.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); + center = mix(center, texture(image, position + vec2(0.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); } @@ -44,10 +44,10 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou /* Scanlines */ float scanline_multiplier; if (pos.y < 0.5) { - scanline_multiplier = (pos.y * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + scanline_multiplier = (pos.y * 2.0) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); } else { - scanline_multiplier = ((1 - pos.y) * 2) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + scanline_multiplier = ((1.0 - pos.y) * 2.0) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); } center *= scanline_multiplier; @@ -55,7 +55,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou right *= scanline_multiplier; /* Vertical seperator for shadow masks */ - bool odd = bool(int((position * input_resolution).x) & 1); + bool odd = bool(int((position * input_resolution).x) >= 1); if (odd) { pos.y += 0.5; pos.y = fract(pos.y); @@ -63,15 +63,15 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou 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); + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - 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); + float gradient_position = (1.0 - pos.y) * 3.0; + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); } /* Blur the edges of the separators of adjacent columns */ @@ -82,26 +82,26 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou 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); + gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (pos.x) * 6.0); } else { - gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (1.0 - 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); + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); } else if (pos.y > 2.0 / 3.0) { - float gradient_position = (1 - pos.y) * 3.0; + float gradient_position = (1.0 - pos.y) * 3.0; if (pos.x < 0.5) { - gradient_position = 1 - (1 - gradient_position) * (1 - (pos.x) * 6.0); + gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (pos.x) * 6.0); } else { - gradient_position = 1 - (1 - gradient_position) * (1 - (1 - pos.x) * 6.0); + gradient_position = 1.0 - (1.0 - gradient_position) * (1.0 - (1.0 - 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); + center *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + left *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); + right *= gradient_position * VERTICAL_BORDER_DEPTH + (1.0 - VERTICAL_BORDER_DEPTH); } } @@ -113,48 +113,48 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou 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), + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1.0), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0), 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), + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0), 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), + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1.0), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1.0), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1.0), sub_pos.x); } /* Anti alias the curve */ vec2 pixel_position = position * output_resolution; - if (pixel_position.x < 1) { + if (pixel_position.x < 1.0) { ret *= pixel_position.x; } - else if (pixel_position.x > output_resolution.x - 1) { + else if (pixel_position.x > output_resolution.x - 1.0) { ret *= output_resolution.x - pixel_position.x; } - if (pixel_position.y < 1) { + if (pixel_position.y < 1.0) { ret *= pixel_position.y; } - else if (pixel_position.y > output_resolution.y - 1) { + else if (pixel_position.y > output_resolution.y - 1.0) { ret *= output_resolution.y - pixel_position.y; } diff --git a/Shaders/HQ2x.fsh b/Shaders/HQ2x.fsh index 3871db9..50947b9 100644 --- a/Shaders/HQ2x.fsh +++ b/Shaders/HQ2x.fsh @@ -2,7 +2,7 @@ /* The colorspace used by the HQnx filters is not really YUV, despite the algorithm description claims it is. It is also not normalized. Therefore, we shall call the colorspace used by HQnx "HQ Colorspace" to avoid confusion. */ -STATIC vec3 rgb_to_hq_colospace(vec4 rgb) +STATIC vec3 rgb_to_hq_colorspace(vec4 rgb) { return vec3( 0.250 * rgb.r + 0.250 * rgb.g + 0.250 * rgb.b, 0.250 * rgb.r - 0.000 * rgb.g - 0.250 * rgb.b, @@ -11,7 +11,7 @@ STATIC vec3 rgb_to_hq_colospace(vec4 rgb) STATIC bool is_different(vec4 a, vec4 b) { - vec3 diff = abs(rgb_to_hq_colospace(a) - rgb_to_hq_colospace(b)); + vec3 diff = abs(rgb_to_hq_colorspace(a) - rgb_to_hq_colorspace(b)); return diff.x > 0.188 || diff.y > 0.027 || diff.z > 0.031; } @@ -43,13 +43,13 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, position + vec2( 0, -o.y)); + vec4 w1 = texture(image, position + vec2( 0.0, -o.y)); vec4 w2 = texture(image, position + vec2( o.x, -o.y)); - vec4 w3 = texture(image, position + vec2( -o.x, 0)); - vec4 w4 = texture(image, position + vec2( 0, 0)); - vec4 w5 = texture(image, position + vec2( o.x, 0)); + vec4 w3 = texture(image, position + vec2( -o.x, 0.0)); + vec4 w4 = texture(image, position + vec2( 0.0, 0.0)); + vec4 w5 = texture(image, position + vec2( o.x, 0.0)); vec4 w6 = texture(image, position + vec2( -o.x, o.y)); - vec4 w7 = texture(image, position + vec2( 0, o.y)); + vec4 w7 = texture(image, position + vec2( 0.0, o.y)); vec4 w8 = texture(image, position + vec2( o.x, o.y)); int pattern = 0; diff --git a/Shaders/LCD.fsh b/Shaders/LCD.fsh index d20a7c9..7714919 100644 --- a/Shaders/LCD.fsh +++ b/Shaders/LCD.fsh @@ -5,27 +5,27 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 output_resolution) { vec2 pos = fract(position * input_resolution); - vec2 sub_pos = fract(position * input_resolution * 6); + vec2 sub_pos = fract(position * input_resolution * 6.0); 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)); 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); + center = mix(center, texture(image, position + vec2(0.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); - center *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - left *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - right *= sub_pos.y * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + center *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); + left *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); + right *= sub_pos.y * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); } 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); + center = mix(center, texture(image, position + vec2(0.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); - center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); - right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1 - SCANLINE_DEPTH); + center *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); + left *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); + right *= (1.0 - sub_pos.y) * SCANLINE_DEPTH + (1.0 - SCANLINE_DEPTH); } @@ -34,33 +34,33 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou 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), + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_HIGH * left.b, 1.0), + vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0), 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), + ret = mix(vec4(COLOR_HIGH * center.r, COLOR_LOW * center.g, COLOR_LOW * left.b, 1.0), + vec4(COLOR_HIGH * center.r, COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0), 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), + ret = mix(vec4(COLOR_HIGH * center.r , COLOR_HIGH * center.g, COLOR_LOW * midleft.b, 1.0), + vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g, COLOR_LOW * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * midright.r, COLOR_HIGH * center.g , COLOR_LOW * center.b, 1.0), + vec4(COLOR_LOW * right.r , COLOR_HIGH * center.g, COLOR_HIGH * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * right.r, COLOR_HIGH * center.g , COLOR_HIGH * center.b, 1.0), + vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0), 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), + ret = mix(vec4(COLOR_LOW * right.r, COLOR_LOW * midright.g, COLOR_HIGH * center.b, 1.0), + vec4(COLOR_HIGH * right.r, COLOR_LOW * right.g , COLOR_HIGH * center.b, 1.0), sub_pos.x); } diff --git a/Shaders/OmniScale.fsh b/Shaders/OmniScale.fsh index bb2b7d6..0e8e019 100644 --- a/Shaders/OmniScale.fsh +++ b/Shaders/OmniScale.fsh @@ -2,9 +2,9 @@ - The actual output calculating was completely redesigned as resolution independent graphic generator. This allows scaling to any factor. - HQnx approximations that were good enough for a 2x/3x/4x factor were refined, creating smoother gradients. - - "Quarters" can be interpolated in more ways than in the HQnx filters - - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels - per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. + - "Quarters" can be interpolated in more ways than in the HQnx filters + - If a pattern does not provide enough information to determine the suitable scaling interpolation, up to 16 pixels + per quarter are sampled (in contrast to the usual 9) in order to determine the best interpolation. */ /* We use the same colorspace as the HQ algorithms. */ @@ -28,7 +28,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou { // o = offset, the width of a pixel vec2 o = 1.0 / input_resolution; - + /* We always calculate the top left quarter. If we need a different quarter, we flip our co-ordinates */ // p = the position within a pixel [0...1] @@ -44,13 +44,13 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou } vec4 w0 = texture(image, position + vec2( -o.x, -o.y)); - vec4 w1 = texture(image, position + vec2( 0, -o.y)); + vec4 w1 = texture(image, position + vec2( 0.0, -o.y)); vec4 w2 = texture(image, position + vec2( o.x, -o.y)); - vec4 w3 = texture(image, position + vec2( -o.x, 0)); - vec4 w4 = texture(image, position + vec2( 0, 0)); - vec4 w5 = texture(image, position + vec2( o.x, 0)); + vec4 w3 = texture(image, position + vec2( -o.x, 0.0)); + vec4 w4 = texture(image, position + vec2( 0.0, 0.0)); + vec4 w5 = texture(image, position + vec2( o.x, 0.0)); vec4 w6 = texture(image, position + vec2( -o.x, o.y)); - vec4 w7 = texture(image, position + vec2( 0, o.y)); + vec4 w7 = texture(image, position + vec2( 0.0, o.y)); vec4 w8 = texture(image, position + vec2( o.x, o.y)); int pattern = 0; @@ -81,7 +81,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (P(0x2f,0x2f)) { float dist = length(p - vec2(0.5)); float pixel_size = length(1.0 / (output_resolution / input_resolution)); - if (dist < 0.5 - pixel_size / 2) { + if (dist < 0.5 - pixel_size / 2.0) { return w4; } vec4 r; @@ -92,40 +92,40 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); } - if (dist > 0.5 + pixel_size / 2) { + if (dist > 0.5 + pixel_size / 2.0) { return r; } - return mix(w4, r, (dist - 0.5 + pixel_size / 2) / pixel_size); + return mix(w4, r, (dist - 0.5 + pixel_size / 2.0) / pixel_size); } if (P(0xbf,0x37) || P(0xdb,0x13)) { float dist = p.x - 2.0 * p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); - if (dist > pixel_size / 2) { + if (dist > pixel_size / 2.0) { return w1; } vec4 r = mix(w3, w4, p.x + 0.5); - if (dist < -pixel_size / 2) { + if (dist < -pixel_size / 2.0) { return r; } - return mix(r, w1, (dist + pixel_size / 2) / pixel_size); + return mix(r, w1, (dist + pixel_size / 2.0) / pixel_size); } if (P(0xdb,0x49) || P(0xef,0x6d)) { float dist = p.y - 2.0 * p.x; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); - if (p.y - 2.0 * p.x > pixel_size / 2) { + if (p.y - 2.0 * p.x > pixel_size / 2.0) { return w3; } vec4 r = mix(w1, w4, p.x + 0.5); - if (dist < -pixel_size / 2) { + if (dist < -pixel_size / 2.0) { return r; } - return mix(r, w3, (dist + pixel_size / 2) / pixel_size); + return mix(r, w3, (dist + pixel_size / 2.0) / pixel_size); } if (P(0xbf,0x8f) || P(0x7e,0x0e)) { float dist = p.x + 2.0 * p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); - if (dist > 1.0 + pixel_size / 2) { + if (dist > 1.0 + pixel_size / 2.0) { return w4; } @@ -137,11 +137,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); } - if (dist < 1.0 - pixel_size / 2) { + if (dist < 1.0 - pixel_size / 2.0) { return r; } - return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size); } @@ -149,7 +149,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou float dist = p.y + 2.0 * p.x; float pixel_size = length(1.0 / (output_resolution / input_resolution)) * sqrt(5.0); - if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2) { + if (p.y + 2.0 * p.x > 1.0 + pixel_size / 2.0) { return w4; } @@ -162,11 +162,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); } - if (dist < 1.0 - pixel_size / 2) { + if (dist < 1.0 - pixel_size / 2.0) { return r; } - return mix(r, w4, (dist + pixel_size / 2 - 1.0) / pixel_size); + return mix(r, w4, (dist + pixel_size / 2.0 - 1.0) / pixel_size); } if (P(0x1b,0x03) || P(0x4f,0x43) || P(0x8b,0x83) || P(0x6b,0x43)) @@ -185,7 +185,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou float dist = p.x + p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)); - if (dist > 0.5 + pixel_size / 2) { + if (dist > 0.5 + pixel_size / 2.0) { return w4; } @@ -197,11 +197,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou r = mix(mix(w1 * 0.375 + w0 * 0.25 + w3 * 0.375, w3, p.y * 2.0), w1, p.x * 2.0); } - if (dist < 0.5 - pixel_size / 2) { + if (dist < 0.5 - pixel_size / 2.0) { return r; } - return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size); } if (P(0x0b,0x01)) @@ -213,7 +213,7 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou float dist = p.x + p.y; float pixel_size = length(1.0 / (output_resolution / input_resolution)); - if (dist > 0.5 + pixel_size / 2) + if (dist > 0.5 + pixel_size / 2.) return w4; /* We need more samples to "solve" this diagonal */ @@ -241,11 +241,11 @@ STATIC vec4 scale(sampler2D image, vec2 position, vec2 input_resolution, vec2 ou if (diagonal_bias <= 0) { vec4 r = mix(w1, w3, p.y - p.x + 0.5); - if (dist < 0.5 - pixel_size / 2) { + if (dist < 0.5 - pixel_size / 2.0) { return r; } - return mix(r, w4, (dist + pixel_size / 2 - 0.5) / pixel_size); + return mix(r, w4, (dist + pixel_size / 2.0 - 0.5) / pixel_size); } - + return w4; } diff --git a/Shaders/WasmMasterShader.fsh b/Shaders/WasmMasterShader.fsh new file mode 100644 index 0000000..5d9adb9 --- /dev/null +++ b/Shaders/WasmMasterShader.fsh @@ -0,0 +1,40 @@ +precision mediump float; + +uniform sampler2D image; +uniform sampler2D previous_image; +uniform bool mix_previous; + +uniform vec2 input_resolution; +uniform vec2 output_resolution; +uniform vec2 origin; + +#define equal(x, y) ((x) == (y)) +#define inequal(x, y) ((x) != (y)) +#define STATIC + +#if VERSION >= 0x300 +#define FRAG_COLOR fragColor + out vec4 FRAG_COLOR; +#else +#define FRAG_COLOR gl_FragColor + vec4 texture(sampler2D s, vec2 c) { return texture2D(s, c); } + vec4 texture(sampler2D s, vec2 c, float b) { return texture2D(s, c, b); } +#endif + +#line 1 +{filter} + +void main() +{ + vec2 position = gl_FragCoord.xy - origin; + position /= output_resolution; + position.y = 1.0 - position.y; + + if (mix_previous) { + FRAG_COLOR = mix(scale(image, position, input_resolution, output_resolution), + scale(previous_image, position, input_resolution, output_resolution), 0.5); + } + else { + FRAG_COLOR = scale(image, position, input_resolution, output_resolution); + } +} diff --git a/wasm/Makefile b/wasm/Makefile index 3c7843a..7131388 100644 --- a/wasm/Makefile +++ b/wasm/Makefile @@ -58,7 +58,8 @@ endif CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES # CFLAGS += -DGB_INTERNAL=1 # get access to internal APIs CFLAGS += -I$(CORE_DIR) -CFLAGS += -s WASM=1 -s USE_SDL=2 --preload-file $(BOOTROMS_DIR)@/BootROMs -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap', 'getValue', 'AsciiToString', 'FS']" +CFLAGS += -s WASM=1 -s USE_SDL=2 --preload-file $(BOOTROMS_DIR)@/BootROMs --preload-file $(CORE_DIR)/Shaders@/Shaders -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap', 'getValue', 'AsciiToString', 'FS']" +CFLAGS += -s USE_WEBGL2=1 # CFLAGS += -Wcast-align -Wover-aligned -s SAFE_HEAP=1 -s WARN_UNALIGNED=1 WASM_LDFLAGS := @@ -95,7 +96,9 @@ WASM_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(WASM_SOURCES)) WEB_SOURCES := $(shell ls ressources/.) WEB_OBJECTS := $(patsubst %,$(BIN)/ressources/%,$(WEB_SOURCES)) -wasm: bootroms $(BIN)/index.html $(WEB_OBJECTS) +SHADERS := $(shell ls $(CORE_DIR)/Shaders/*.fsh) + +wasm: bootroms $(BIN)/index.html $(WEB_OBJECTS) $(SHADERS) all: wasm # Automatic dependency generation diff --git a/wasm/main.c b/wasm/main.c index 3a04613..5ff8974 100644 --- a/wasm/main.c +++ b/wasm/main.c @@ -1,7 +1,6 @@ #include -#include -#include #include +#include #include #include @@ -9,6 +8,7 @@ #include #include "main.h" #include "utils.h" +#include "shader.h" GB_gameboy_t gb; @@ -19,12 +19,36 @@ SDL_Texture *texture; SDL_PixelFormat *pixel_format; SDL_AudioDeviceID device_id; +shader_t shader; + +static SDL_Rect rect; +static unsigned factor; static SDL_AudioSpec want_aspec, have_aspec; static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224]; static uint32_t *active_pixel_buffer = pixel_buffer_1; static uint32_t *previous_pixel_buffer = pixel_buffer_2; static char *battery_save_path_ptr; +struct shader_name { + const char *file_name; + const char *display_name; +} shaders[] = +{ + {"NearestNeighbor", "Nearest Neighbor"}, + {"Bilinear", "Bilinear"}, + {"SmoothBilinear", "Smooth Bilinear"}, + {"LCD", "LCD Display"}, + {"CRT", "CRT Display"}, + {"Scale2x", "Scale2x"}, + {"Scale4x", "Scale4x"}, + {"AAScale2x", "Anti-aliased Scale2x"}, + {"AAScale4x", "Anti-aliased Scale4x"}, + // {"HQ2x", "HQ2x"}, // requires OpenGL ES 1.30 features + // {"OmniScale", "OmniScale"}, // requires OpenGL ES 1.30 features + {"OmniScaleLegacy", "OmniScale Legacy"}, + {"AAOmniScaleLegacy", "AA OmniScale Legacy"}, +}; + configuration_t configuration = { .keys = { @@ -62,9 +86,11 @@ configuration_t configuration = }, .color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE, .highpass_mode = GB_HIGHPASS_ACCURATE, + .scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR, .blend_frames = true, .rewind_length = 60 * 2, - .model = MODEL_CGB + .model = MODEL_CGB, + .filter = "OmniScale", }; // Use this function instead of GB_save_battery() @@ -102,6 +128,46 @@ static void audio_callback(void *gb, Uint8 *stream, int len) } } +void update_viewport(void) +{ + int win_width, win_height; + SDL_GL_GetDrawableSize(window, &win_width, &win_height); + int logical_width, logical_height; + SDL_GetWindowSize(window, &logical_width, &logical_height); + factor = win_width / logical_width; + + double x_factor = win_width / (double) GB_get_screen_width(&gb); + double y_factor = win_height / (double) GB_get_screen_height(&gb); + + if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) { + x_factor = (int)(x_factor); + y_factor = (int)(y_factor); + } + + if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) { + if (x_factor > y_factor) { + x_factor = y_factor; + } + else { + y_factor = x_factor; + } + } + + unsigned new_width = x_factor * GB_get_screen_width(&gb); + unsigned new_height = y_factor * GB_get_screen_height(&gb); + + rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) / 2, + new_width, new_height}; + + if (renderer) { + SDL_RenderSetViewport(renderer, &rect); + } + else { + glViewport(rect.x, rect.y, rect.w, rect.h); + } +} + + void render_texture(void *pixels, void *previous) { if (renderer) { @@ -112,7 +178,7 @@ void render_texture(void *pixels, void *previous) SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); } - /*else { + else { static void *_pixels = NULL; if (pixels) { _pixels = pixels; @@ -123,7 +189,7 @@ void render_texture(void *pixels, void *previous) GB_get_screen_width(&gb), GB_get_screen_height(&gb), rect.x, rect.y, rect.w, rect.h); SDL_GL_SwapWindow(window); - }*/ + } } static void handle_events(GB_gameboy_t *gb) { @@ -243,8 +309,8 @@ int EMSCRIPTEN_KEEPALIVE init() { "SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - VIDEO_WIDTH, - VIDEO_HEIGHT, + VIDEO_WIDTH * 4, + VIDEO_HEIGHT * 4, SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI ); @@ -256,37 +322,31 @@ int EMSCRIPTEN_KEEPALIVE init() { SDL_SetWindowMinimumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT); SDL_SetWindowMaximumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT); - renderer = SDL_CreateRenderer( - window, - -1, - SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC - ); + // Try to get a GLES 3.0 context + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); - if (!renderer) { - fprintf(stderr, "SDL_CreateRenderer Error: %s\n", SDL_GetError()); - return EXIT_FAILURE; + if (gl_context == NULL) { + // Try to get a GLES 2.0 context + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + gl_context = SDL_GL_CreateContext(window); } - screen = SDL_CreateRGBSurface( - 0, - VIDEO_WIDTH, - VIDEO_HEIGHT, - 32, - 0, 0, 0, 0 - ); - - if (!screen) { - SDL_Log("SDL_CreateRGBSurface() failed: %s", SDL_GetError()); - exit(1); + if (gl_context == NULL) { + fprintf(stderr, "Using software renderer!\n"); + renderer = SDL_CreateRenderer(window, -1, 0); + texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144); + pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window)); } + else { + fprintf(stderr, "Using OpenGL renderer!\n"); + pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); - pixel_format = screen->format; - - texture = SDL_CreateTextureFromSurface(renderer, screen); - - if (!texture) { - fprintf(stderr, "SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError()); - return EXIT_FAILURE; + fprintf(stderr, "GLES: %s\n", glGetString(GL_VERSION)); + fprintf(stderr, "GLSL: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + fprintf(stderr, "Parsed GL version: %hu\n", get_gl_version()); } unsigned audio_sample_rate = query_sample_rate_of_audiocontexts(); @@ -355,6 +415,11 @@ int EMSCRIPTEN_KEEPALIVE init() { init_gb(); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + update_viewport(); + SDL_PauseAudioDevice(device_id, 0); return EXIT_SUCCESS; diff --git a/wasm/main.h b/wasm/main.h index a8807a3..e68f283 100644 --- a/wasm/main.h +++ b/wasm/main.h @@ -34,9 +34,17 @@ typedef enum { JOYPAD_AXISES_MAX } joypad_axis_t; +enum scaling_mode { + GB_SDL_SCALING_ENTIRE_WINDOW, + GB_SDL_SCALING_KEEP_RATIO, + GB_SDL_SCALING_INTEGER_FACTOR, + GB_SDL_SCALING_MAX, +}; + typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; + enum scaling_mode scaling_mode; bool blend_frames; GB_highpass_mode_t highpass_mode; diff --git a/wasm/opengl_compat.c b/wasm/opengl_compat.c new file mode 100644 index 0000000..a83fa29 --- /dev/null +++ b/wasm/opengl_compat.c @@ -0,0 +1 @@ +#include "opengl_compat.h" diff --git a/wasm/opengl_compat.h b/wasm/opengl_compat.h new file mode 100644 index 0000000..cce7fec --- /dev/null +++ b/wasm/opengl_compat.h @@ -0,0 +1,7 @@ +#ifndef opengl_compat_h +#define opengl_compat_h + +#define GL_GLEXT_PROTOTYPES 1 +#include + +#endif /* opengl_compat_h */ diff --git a/wasm/shader.c b/wasm/shader.c new file mode 100644 index 0000000..d4842df --- /dev/null +++ b/wasm/shader.c @@ -0,0 +1,261 @@ +#include +#include +#include +#include "shader.h" +#include "utils.h" + +static const char *vertex_shader_100 = "\ +#version 100 \n\ +attribute vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +static const char *vertex_shader_300 = "\ +#version 300 es\n\ +in vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +uint16_t get_gl_version() { + GLint major = 0, minor = 0; + +#if defined(GL_MAJOR_VERSION) && defined(GL_MINOR_VERSION) + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); +#else + char *version = (char *) glGetString(GL_VERSION); + + int res = sscanf(version, "OpenGL ES %d.%d", &major, &minor); + + if (res != 2) { + // Maybe the OpenGL ES prefixed was missing + res = sscanf(version, "%d.%d", &major, &minor); + } + + if (res != 2) { + major = 0; + minor = 0; + } +#endif + + return (uint16_t)(major * 0x100 + minor); +} + +static GLuint create_shader(const char *source, GLenum type) +{ + // Create the shader object + GLuint shader = glCreateShader(type); + // Load the shader source + glShaderSource(shader, 1, &source, 0); + // Compile the shader + glCompileShader(shader); + // Check for errors + GLint status = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Shader Error: %s", messages); + } + return shader; +} + +static GLuint create_program(const char *vsh, const char *fsh) +{ + // Build shaders + GLuint vertex_shader = create_shader(vsh, GL_VERTEX_SHADER); + GLuint fragment_shader = create_shader(fsh, GL_FRAGMENT_SHADER); + + // Create program + GLuint program = glCreateProgram(); + + fprintf(stderr, "Creating program...\n"); + + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + + fprintf(stderr, "Linking program...\n"); + + // Link program + glLinkProgram(program); + + fprintf(stderr, "Checking for errors...\n"); + + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) { + GLint info_len = 0; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_len); + + if (info_len > 1) { + char* info_log = (char*)malloc(sizeof(char) * info_len); + + glGetProgramInfoLog(program, info_len, NULL, info_log); + printf("Error linking program:\n%s\n", info_log); + + free(info_log); + } + + glDeleteProgram(program); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +bool init_shader_with_name(shader_t *shader, const char *name) +{ + uint16_t gl_version = get_gl_version(); + + static char master_shader_code[0x801] = {0,}; + static char shader_code[0x10001] = {0,}; + static char final_shader_code[0x10801] = {0,}; + static signed long filter_token_location = 0; + + if (!master_shader_code[0]) { + size_t header_len = 0; + + if (gl_version >= 0x300) { + char* header = "#version 300 es\n#define VERSION 0x300\n"; + header_len = strlen(header); + + strcpy(master_shader_code, header); + } + else { + char* header = "#version 100\n#define VERSION 0x100\n"; + header_len = strlen(header); + + strcpy(master_shader_code, header); + } + + FILE *master_shader_f = fopen(resource_path("Shaders/WasmMasterShader.fsh"), "r"); + if (!master_shader_f) return false; + fread(master_shader_code + header_len, 1, sizeof(master_shader_code) - 1, master_shader_f); + fclose(master_shader_f); + filter_token_location = strstr(master_shader_code, "{filter}") - master_shader_code; + if (filter_token_location < 0) { + master_shader_code[0] = 0; + return false; + } + } + + char shader_path[1024]; + sprintf(shader_path, "Shaders/%s.fsh", name); + + FILE *shader_f = fopen(resource_path(shader_path), "r"); + if (!shader_f) return false; + memset(shader_code, 0, sizeof(shader_code)); + fread(shader_code, 1, sizeof(shader_code) - 1, shader_f); + fclose(shader_f); + + memset(final_shader_code, 0, sizeof(final_shader_code)); + memcpy(final_shader_code, master_shader_code, filter_token_location); + strcpy(final_shader_code + filter_token_location, shader_code); + strcat(final_shader_code + filter_token_location, + master_shader_code + filter_token_location + sizeof("{filter}") - 1); + + fprintf(stderr, "Shader code:\n%s\n", final_shader_code); + + if (gl_version >= 0x300) { + shader->program = create_program(vertex_shader_300, final_shader_code); + } + else { + shader->program = create_program(vertex_shader_100, final_shader_code); + } + + // Attributes + shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); + // Uniforms + shader->input_resolution_uniform = glGetUniformLocation(shader->program, "input_resolution"); + shader->resolution_uniform = glGetUniformLocation(shader->program, "output_resolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "origin"); + + glGenTextures(1, &shader->texture); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->texture_uniform = glGetUniformLocation(shader->program, "image"); + + glGenTextures(1, &shader->previous_texture); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); + + shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous"); + + // Program + + glUseProgram(shader->program); + + GLuint vao; + if (gl_version >= 0x300) { + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + } + + GLuint vbo; + glGenBuffers(1, &vbo); + + // Attributes + + static GLfloat const quad[16] = { + -1.f, -1.f, 0, 1, + -1.f, +1.f, 0, 1, + +1.f, -1.f, 0, 1, + +1.f, +1.f, 0, 1, + }; + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW); + glEnableVertexAttribArray(shader->position_attribute); + glVertexAttribPointer(shader->position_attribute, 4, GL_FLOAT, GL_FALSE, 0, 0); + + return true; +} + +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h) +{ + glUseProgram(shader->program); + glUniform2f(shader->origin_uniform, x, y); + glUniform2f(shader->input_resolution_uniform, source_width, source_height); + glUniform2f(shader->resolution_uniform, w, h); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); + glUniform1i(shader->texture_uniform, 0); + glUniform1i(shader->mix_previous_uniform, previous != NULL); + if (previous) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->previous_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, source_width, source_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(shader->previous_texture_uniform, 1); + } + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +void free_shader(shader_t *shader) +{ + glDeleteProgram(shader->program); + glDeleteTextures(1, &shader->texture); + glDeleteTextures(1, &shader->previous_texture); +} diff --git a/wasm/shader.h b/wasm/shader.h new file mode 100644 index 0000000..050e967 --- /dev/null +++ b/wasm/shader.h @@ -0,0 +1,29 @@ +#ifndef shader_h +#define shader_h + +#include "opengl_compat.h" +#include + +typedef struct shader_s { + GLuint input_resolution_uniform; + GLuint resolution_uniform; + GLuint origin_uniform; + GLuint texture_uniform; + GLuint previous_texture_uniform; + GLuint mix_previous_uniform; + + GLuint position_attribute; + GLuint texture; + GLuint previous_texture; + GLuint program; +} shader_t; + +uint16_t get_gl_version(); + +bool init_shader_with_name(shader_t *shader, const char *name); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, + unsigned source_width, unsigned source_height, + unsigned x, unsigned y, unsigned w, unsigned h); +void free_shader(struct shader_s *shader); + +#endif /* shader_h */