Fishcu/authentic gbc (#582)

* First working version

* Add overbright feature; Clean up code

* Consistent precision

* Rename included files; Separate out vert shader as utility function
This commit is contained in:
fishcu 2024-04-29 20:48:41 +02:00 committed by GitHub
parent 67a1ee78e0
commit 0eb12c628d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 170 additions and 0 deletions

View file

@ -0,0 +1,5 @@
shaders = 1
shader0 = shaders/authentic_gbc/authentic_gbc.slang
filter_linear0 = false
scale_type0 = viewport

View file

@ -0,0 +1,105 @@
#version 450
/*
Authentic GBC v1.0 by fishku
Copyright (C) 2024
Public domain license (CC0)
Attempts to render GBC subpixels authentically.
Reference photos:
- https://gbcc.dev/technology/subpixels.jpg
Inspired by:
-
https://www.reddit.com/r/AnaloguePocket/comments/1azaxgd/ive_made_some_improvements_to_my_analogue_pocket/
Changelog:
v1.0: Initial release.
*/
#include "parameters.inc"
#include "shared.inc"
layout(push_constant) uniform Push {
vec4 SourceSize;
vec4 OutputSize;
float AUTH_GBC_BRIG;
}
param;
layout(std140, set = 0, binding = 0) uniform UBO { mat4 MVP; }
global;
#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec4 px_rect;
layout(location = 1) out vec2 tx_coord;
layout(location = 2) out vec2 tx_to_px;
layout(location = 3) out vec2 subpx_size;
layout(location = 4) out vec2 notch_size;
layout(location = 5) out float subpx_orig_y;
void main() {
gl_Position = global.MVP * Position;
auth_gbc_vert_shader(param.SourceSize.xy, param.OutputSize.xy,
param.AUTH_GBC_BRIG, TexCoord, px_rect, tx_coord,
tx_to_px, subpx_size, notch_size, subpx_orig_y);
}
#pragma stage fragment
layout(location = 0) in vec4 px_rect;
layout(location = 1) in vec2 tx_coord;
layout(location = 2) in vec2 tx_to_px;
layout(location = 3) in vec2 subpx_size;
layout(location = 4) in vec2 notch_size;
layout(location = 5) in float subpx_orig_y;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
void main() {
#if 0
// Debug
vec3 res = pixel_color(px_rect, vec2(500.0), vec2(100.0));
res += pixel_color(px_rect, vec2(500.0), vec2(600.0, 100.0));
res += pixel_color(px_rect, vec2(500.0), vec2(100.0, 600.0));
res += pixel_color(px_rect, vec2(500.0), vec2(600.0));
FragColor = vec4(res, 1.0);
#else
// Figure out 4 nearest texels in source texture
vec2 tx_coord_i;
const vec2 tx_coord_f = modf(tx_coord, tx_coord_i);
const vec2 tx_coord_off = step(vec2(0.5), tx_coord_f) * 2.0 - 1.0;
const vec2 tx_origins[] = {
tx_coord_i, tx_coord_i + vec2(tx_coord_off.x, 0.0),
tx_coord_i + vec2(0.0, tx_coord_off.y), tx_coord_i + tx_coord_off};
// Sample.
// Apply square for fast "gamma correction".
vec3 samples[] = {
texture(Source, (tx_origins[0] + 0.5) * param.SourceSize.zw).rgb,
texture(Source, (tx_origins[1] + 0.5) * param.SourceSize.zw).rgb,
texture(Source, (tx_origins[2] + 0.5) * param.SourceSize.zw).rgb,
texture(Source, (tx_origins[3] + 0.5) * param.SourceSize.zw).rgb};
samples[0] *= samples[0];
samples[1] *= samples[1];
samples[2] *= samples[2];
samples[3] *= samples[3];
// Apply shader.
const vec3 res =
samples[0] * pixel_color(px_rect, tx_to_px, tx_origins[0] * tx_to_px,
subpx_orig_y, subpx_size, notch_size) +
samples[1] * pixel_color(px_rect, tx_to_px, tx_origins[1] * tx_to_px,
subpx_orig_y, subpx_size, notch_size) +
samples[2] * pixel_color(px_rect, tx_to_px, tx_origins[2] * tx_to_px,
subpx_orig_y, subpx_size, notch_size) +
samples[3] * pixel_color(px_rect, tx_to_px, tx_origins[3] * tx_to_px,
subpx_orig_y, subpx_size, notch_size);
// Apply sqrt for fast "gamma correction".
FragColor = vec4(sqrt(res), 1.0);
#endif
}

View file

@ -0,0 +1,6 @@
// See the main shader file for copyright and other information.
// clang-format off
#pragma parameter AUTH_GBC_SETTINGS "=== Authentic GBC v1.0 settings ===" 0.0 0.0 1.0 1.0
#pragma parameter AUTH_GBC_BRIG "Overbrighten" 0.0 -0.1 1.0 0.05
// clang-format on

View file

@ -0,0 +1,54 @@
// See the main shader file for copyright and other information.
// As determined by counting pixels on a photo.
const vec2 subpx_ratio = vec2(0.296, 0.910);
const vec2 notch_ratio = vec2(0.115, 0.166);
float rect_coverage(vec4 px_rect, vec4 rect) {
const vec2 bl = max(rect.xy, px_rect.xy);
const vec2 tr = min(rect.zw, px_rect.zw);
const vec2 coverage = max(tr - bl, 0.0);
return coverage.x * coverage.y;
}
float subpx_coverage(vec4 px_rect, vec2 subpx_orig, vec2 subpx_size,
vec2 notch_size) {
return rect_coverage(px_rect, vec4(subpx_orig, subpx_orig + subpx_size)) -
rect_coverage(
px_rect,
vec4(subpx_orig.x, subpx_orig.y + subpx_size.y - notch_size.y,
subpx_orig.x + notch_size.x, subpx_orig.y + subpx_size.y));
}
vec3 pixel_color(vec4 px_rect, vec2 px_size, vec2 px_orig, float subpx_orig_y,
vec2 subpx_size, vec2 notch_size) {
return vec3(
subpx_coverage(
px_rect,
px_orig + vec2(px_size.x / 6.0 - subpx_size.x * 0.5, subpx_orig_y),
subpx_size, notch_size),
subpx_coverage(
px_rect,
px_orig + vec2(px_size.x / 2.0 - subpx_size.x * 0.5, subpx_orig_y),
subpx_size, notch_size),
subpx_coverage(
px_rect,
px_orig +
vec2(5.0 * px_size.x / 6.0 - subpx_size.x * 0.5, subpx_orig_y),
subpx_size, notch_size));
}
// Precomputes all variables needed in the frag shader.
void auth_gbc_vert_shader(vec2 source_size, vec2 output_size,
float overbrighten, vec2 TexCoord, inout vec4 px_rect,
inout vec2 tx_coord, inout vec2 tx_to_px,
inout vec2 subpx_size, inout vec2 notch_size,
inout float subpx_orig_y) {
px_rect = vec4(TexCoord * output_size - 0.5, TexCoord * output_size + 0.5);
tx_coord = TexCoord * source_size;
tx_to_px = output_size / source_size;
subpx_size =
tx_to_px * mix(subpx_ratio, vec2(2.0 / 3.0, 1.0), overbrighten);
notch_size = tx_to_px * mix(notch_ratio, vec2(0.0), overbrighten);
subpx_orig_y = (tx_to_px.y - subpx_size.y) * 0.5;
}