Skip to content

Commit

Permalink
Add an LCD shader effect for a more retro look.
Browse files Browse the repository at this point in the history
  • Loading branch information
Arignir committed Dec 16, 2023
1 parent cab06e7 commit 1465ac6
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 47 deletions.
5 changes: 4 additions & 1 deletion include/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,14 @@ struct app {

enum texture_filter_kind texture_filter;
GLuint game_texture_in;
GLuint game_texture_out;
GLuint game_texture_a;
GLuint game_texture_b;
GLuint fbo;
GLuint vao;
GLuint vbo;

GLuint program_color_correction;
GLuint program_lcd;

GLuint active_programs[MAX_GFX_PROGRAMS];
size_t active_programs_length;
Expand All @@ -209,6 +211,7 @@ struct app {
enum aspect_ratio aspect_ratio;
bool vsync;
bool color_correction;
bool lcd;
} video;

struct {
Expand Down
3 changes: 3 additions & 0 deletions include/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ void gui_sdl_video_rebuild_pipeline(struct app *app);
/* gui/shaders/frag-color-correction.c */
extern char const *SHADER_FRAG_COLOR_CORRECTION;

/* gui/shaders/frag-lcd.c */
extern char const *SHADER_FRAG_LCD;

/* gui/shaders/vertex-common.c */
extern char const *SHADER_VERTEX_COMMON;

Expand Down
6 changes: 6 additions & 0 deletions source/gui/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ gui_config_load(
app->video.color_correction = b;
}

if (mjson_get_bool(data, data_len, "$.video.lcd", &b)) {
app->video.lcd = b;
}

if (mjson_get_number(data, data_len, "$.video.texture_filter", &d)) {
app->gfx.texture_filter = (int)d;
app->gfx.texture_filter = max(TEXTURE_FILTER_MIN, min(app->gfx.texture_filter, TEXTURE_FILTER_MAX));
Expand Down Expand Up @@ -227,6 +231,7 @@ gui_config_save(
"aspect_ratio": %d,
"vsync": %B,
"color_correction": %B,
"lcd": %B,
"texture_filter": %d
},

Expand All @@ -253,6 +258,7 @@ gui_config_save(
(int)app->video.aspect_ratio,
(int)app->video.vsync,
(int)app->video.color_correction,
(int)app->video.lcd,
(int)app->gfx.texture_filter,
(int)app->audio.mute,
app->audio.level
Expand Down
1 change: 1 addition & 0 deletions source/gui/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ libgui = static_library(
'sdl/input.c',
'sdl/video.c',
'shaders/frag-color-correction.c',
'shaders/frag-lcd.c',
'shaders/vertex-common.c',
'windows/game.c',
'windows/keybinds.c',
Expand Down
50 changes: 39 additions & 11 deletions source/gui/sdl/video.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,14 @@ gui_sdl_video_init(
ImGui_ImplSDL2_InitForOpenGL(app->sdl.window, app->gfx.gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);

/* Build all the available shaders */
app->gfx.program_color_correction = build_shader_program("color_correction", SHADER_FRAG_COLOR_CORRECTION, SHADER_VERTEX_COMMON);
app->gfx.program_lcd = build_shader_program("lcd", SHADER_FRAG_LCD, SHADER_VERTEX_COMMON);

/* Create the OpenGL objects required to build the pipeline */
glGenTextures(1, &app->gfx.game_texture_in);
glGenTextures(1, &app->gfx.game_texture_out);
app->gfx.program_color_correction = build_shader_program("color_correction", SHADER_FRAG_COLOR_CORRECTION, SHADER_VERTEX_COMMON);
glGenTextures(1, &app->gfx.game_texture_a);
glGenTextures(1, &app->gfx.game_texture_b);
glGenFramebuffers(1, &app->gfx.fbo);
glGenVertexArrays(1, &app->gfx.vao);
glGenBuffers(1, &app->gfx.vbo);
Expand Down Expand Up @@ -200,11 +204,11 @@ gui_sdl_video_rebuild_pipeline(
default: texture_filter = GL_NEAREST; break;
}

// Setup the input texture (RGBA)
// Setup the input texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_in);
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_MIN_FILTER, texture_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture_filter);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(
GL_TEXTURE_2D,
Expand All @@ -218,18 +222,36 @@ gui_sdl_video_rebuild_pipeline(
NULL
);

// Setup the output texture (RGBA)
// Setup the A texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_out);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_a);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture_filter);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
GBA_SCREEN_WIDTH,
GBA_SCREEN_HEIGHT,
GBA_SCREEN_WIDTH * 3,
GBA_SCREEN_HEIGHT * 3,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
NULL
);

// Setup the B texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_b);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texture_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texture_filter);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RGBA,
GBA_SCREEN_WIDTH * 3,
GBA_SCREEN_HEIGHT * 3,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
Expand All @@ -243,8 +265,13 @@ gui_sdl_video_rebuild_pipeline(
++app->gfx.active_programs_length;
}

if (app->video.lcd) {
app->gfx.active_programs[app->gfx.active_programs_length] = app->gfx.program_lcd;
++app->gfx.active_programs_length;
}

glBindFramebuffer(GL_FRAMEBUFFER, app->gfx.fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, app->gfx.game_texture_out, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, app->gfx.game_texture_a, 0); // TODO FIXME

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
Expand Down Expand Up @@ -381,7 +408,8 @@ gui_sdl_video_cleanup(
glDeleteVertexArrays(1, &app->gfx.vbo);
glDeleteFramebuffers(1, &app->gfx.fbo);
glDeleteTextures(1, &app->gfx.game_texture_in);
glDeleteTextures(1, &app->gfx.game_texture_out);
glDeleteTextures(1, &app->gfx.game_texture_a);
glDeleteTextures(1, &app->gfx.game_texture_b);
SDL_GL_DeleteContext(app->gfx.gl_context);

// Close the Wingowd
Expand Down
36 changes: 36 additions & 0 deletions source/gui/shaders/frag-lcd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include "gui/gui.h"

/*
** TODO
*/
char const *SHADER_FRAG_LCD = GLSL(
layout(location = 0) out vec4 frag_color;

in vec2 v_uv;

uniform sampler2D u_screen_map;

void main() {
vec4 color = texture(u_screen_map, v_uv);

vec4 lcd = vec4(0.8);

int offset_x = int(mod(gl_FragCoord.x, 3.0));
int offset_y = int(mod(gl_FragCoord.y, 3.0));

if (offset_x == 0) {
lcd.r = 1.0f;
} else if (offset_x == 1) {
lcd.g = 1.0f;
} else if (offset_x == 2) {
lcd.b = 1.0f;
}

if (offset_y == 0) {
lcd = vec4(0.8);
}

frag_color = color * lcd;
frag_color.a = 1.0f;
}
);
76 changes: 41 additions & 35 deletions source/gui/windows/game.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,57 +48,63 @@ gui_win_game(
float game_pos_y;
float game_size_x;
float game_size_y;
GLuint in_texture;
GLuint out_texture;
float tint;
size_t i;

// Adjust the tint if the game is paused
tint = app->emulation.is_running ? 1.0 : 0.1;

if (!app->gfx.active_programs_length) {
// If there's no shaders loaded, we shortcut the pipeline and simply load the game's framebuffer in the corresponding texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_in);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_out);
pthread_mutex_lock(&app->emulation.gba->shared_data.framebuffer.lock);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t *)app->emulation.gba->shared_data.framebuffer.data);
pthread_mutex_unlock(&app->emulation.gba->shared_data.framebuffer.lock);

pthread_mutex_lock(&app->emulation.gba->shared_data.framebuffer.lock);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t *)app->emulation.gba->shared_data.framebuffer.data);
pthread_mutex_unlock(&app->emulation.gba->shared_data.framebuffer.lock);
glViewport(0, 0, GBA_SCREEN_WIDTH * 3.f, GBA_SCREEN_HEIGHT * 3.f);

glBindTexture(GL_TEXTURE_2D, 0);
} else {
size_t i;
in_texture = app->gfx.game_texture_in;
out_texture = app->gfx.game_texture_in;

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, app->gfx.game_texture_in);
glBindVertexArray(app->gfx.vao);
glBindFramebuffer(GL_FRAMEBUFFER, app->gfx.fbo);

pthread_mutex_lock(&app->emulation.gba->shared_data.framebuffer.lock);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t *)app->emulation.gba->shared_data.framebuffer.data);
pthread_mutex_unlock(&app->emulation.gba->shared_data.framebuffer.lock);
for (i = 0; i < app->gfx.active_programs_length; ++i) {

glViewport(0, 0, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT);
glBindVertexArray(app->gfx.vao);
glBindFramebuffer(GL_FRAMEBUFFER, app->gfx.fbo);
// We swap the input and output texture with each shader pass
if (i == 0) {
in_texture = app->gfx.game_texture_in;
out_texture = app->gfx.game_texture_a;
} else if (i == 1) {
in_texture = app->gfx.game_texture_a;
out_texture = app->gfx.game_texture_b;
} else {
GLuint tmp;

for (i = 0; i < app->gfx.active_programs_length; ++i) {
GLuint target_texture;
tmp = in_texture;
in_texture = out_texture;
out_texture = tmp;
}

glUseProgram(app->gfx.active_programs[i]);
if (i == app->gfx.active_programs_length - 1) {
target_texture = app->gfx.game_texture_out;
} else {
target_texture = app->gfx.game_texture_in;
}
// Set the input and output texture
glBindTexture(GL_TEXTURE_2D, in_texture);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, out_texture, 0);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_texture, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// Set the shader to use
glUseProgram(app->gfx.active_programs[i]);

glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
// Draw
glDrawArrays(GL_TRIANGLES, 0, 6);
}

/* Resize the game to keep the correct aspect ratio */
glUseProgram(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);

// Calculate the game size depending on the aspect ratio
switch (app->video.aspect_ratio) {
case ASPECT_RATIO_RESIZE:
case ASPECT_RATIO_BORDERS: {
Expand Down Expand Up @@ -139,7 +145,7 @@ gui_win_game(
);

igImage(
(void *)(uintptr_t)app->gfx.game_texture_out,
(void *)(uintptr_t)out_texture,
(ImVec2){.x = game_size_x, .y = game_size_y},
(ImVec2){.x = 0, .y = 0},
(ImVec2){.x = 1, .y = 1},
Expand Down
6 changes: 6 additions & 0 deletions source/gui/windows/menubar.c
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@ gui_win_menubar_video(
gui_sdl_video_rebuild_pipeline(app);
}

/* LCD Effect */
if (igMenuItemBool("LCD Effect", NULL, app->video.lcd, true)) {
app->video.lcd ^= 1;
gui_sdl_video_rebuild_pipeline(app);
}

/* VSync */
if (igMenuItemBool("VSync", NULL, app->video.vsync, true)) {
app->video.vsync ^= 1;
Expand Down

0 comments on commit 1465ac6

Please sign in to comment.