diff --git a/include/app.h b/include/app.h index 188fe53c..f3e521c6 100644 --- a/include/app.h +++ b/include/app.h @@ -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; @@ -209,6 +211,7 @@ struct app { enum aspect_ratio aspect_ratio; bool vsync; bool color_correction; + bool lcd; } video; struct { diff --git a/include/gui/gui.h b/include/gui/gui.h index 095decf4..42869703 100644 --- a/include/gui/gui.h +++ b/include/gui/gui.h @@ -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; diff --git a/source/gui/config.c b/source/gui/config.c index aca579d4..8c5224c3 100644 --- a/source/gui/config.c +++ b/source/gui/config.c @@ -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)); @@ -227,6 +231,7 @@ gui_config_save( "aspect_ratio": %d, "vsync": %B, "color_correction": %B, + "lcd": %B, "texture_filter": %d }, @@ -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 diff --git a/source/gui/meson.build b/source/gui/meson.build index f5d4c1fe..cd01710d 100644 --- a/source/gui/meson.build +++ b/source/gui/meson.build @@ -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', diff --git a/source/gui/sdl/video.c b/source/gui/sdl/video.c index 0fbbbdf5..85e08e84 100644 --- a/source/gui/sdl/video.c +++ b/source/gui/sdl/video.c @@ -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); @@ -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, @@ -218,9 +222,9 @@ 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); @@ -228,8 +232,26 @@ gui_sdl_video_rebuild_pipeline( 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, @@ -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); @@ -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 diff --git a/source/gui/shaders/frag-lcd.c b/source/gui/shaders/frag-lcd.c new file mode 100644 index 00000000..86e00c06 --- /dev/null +++ b/source/gui/shaders/frag-lcd.c @@ -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; + } +); diff --git a/source/gui/windows/game.c b/source/gui/windows/game.c index 72e2a82e..57addc98 100644 --- a/source/gui/windows/game.c +++ b/source/gui/windows/game.c @@ -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: { @@ -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}, diff --git a/source/gui/windows/menubar.c b/source/gui/windows/menubar.c index b08fd49f..c0b0bda2 100644 --- a/source/gui/windows/menubar.c +++ b/source/gui/windows/menubar.c @@ -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;