From d262dde71aee83fa320fcfcb3f629bae92c17d78 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Sat, 23 Dec 2017 17:29:42 +0200 Subject: [PATCH] Scaling filters in SDL --- Makefile | 13 ++- SDL/gui.c | 132 ++++++++++++++++++++++++---- SDL/gui.h | 5 ++ SDL/main.c | 39 ++++++--- SDL/shader.c | 184 +++++++++++++++++++++++++++++++++++++++ SDL/shader.h | 23 +++++ Shaders/MasterShader.fsh | 6 ++ 7 files changed, 370 insertions(+), 32 deletions(-) create mode 100644 SDL/shader.c create mode 100644 SDL/shader.h diff --git a/Makefile b/Makefile index fcde67e..dde767d 100755 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ endif # Set compilation and linkage flags based on target, platform and configuration CFLAGS += -Werror -Wall -std=gnu11 -ICore -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES -SDL_LDFLAGS := -lSDL2 +SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows LDFLAGS += -lmsvcrt -lSDL2main -Wl,/MANIFESTFILE:NUL @@ -56,7 +56,7 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 +SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations ifeq ($(PLATFORM),windows32) @@ -88,7 +88,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin all: cocoa sdl tester @@ -262,14 +262,21 @@ $(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin cp -f $^ $@ $(BIN)/SDL/LICENSE: LICENSE + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/SDL/registers.sym: Misc/registers.sym + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/SDL/background.bmp: SDL/background.bmp + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ +$(BIN)/SDL/Shaders: Shaders + -@$(MKDIR) -p $(dir $@) + cp -rf $^ $@ + # Boot ROMs $(BIN)/BootROMs/%.bin: BootROMs/%.asm diff --git a/SDL/gui.c b/SDL/gui.c index 1a12e28..bbee0d8 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -20,6 +20,31 @@ unsigned command_parameter; #define MODIFIER_NAME CTRL_STRING #endif +shader_t shader; +SDL_Rect rect; + +void render_texture(void *pixels, void *previous) +{ + if (renderer) { + if (pixels) { + SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); + } + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + } + else { + static void *_pixels = NULL; + if (pixels) { + _pixels = pixels; + } + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + render_bitmap_with_shader(&shader, _pixels, previous, rect.x, rect.y, rect.w, rect.h); + SDL_GL_SwapWindow(window); + } +} + configuration_t configuration = { { SDL_SCANCODE_RIGHT, @@ -59,7 +84,7 @@ static const char *help[] ={ void update_viewport(void) { int win_width, win_height; - SDL_GetWindowSize(window, &win_width, &win_height); + SDL_GL_GetDrawableSize(window, &win_width, &win_height); double x_factor = win_width / 160.0; double y_factor = win_height / 144.0; @@ -80,9 +105,15 @@ void update_viewport(void) unsigned new_width = x_factor * 160; unsigned new_height = y_factor * 144; - SDL_Rect rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, + rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2, new_width, new_height}; - SDL_RenderSetViewport(renderer, &rect); + + if (renderer) { + SDL_RenderSetViewport(renderer, &rect); + } + else { + glViewport(rect.x, rect.y, rect.w, rect.h); + } } /* Does NOT check for bounds! */ @@ -234,9 +265,7 @@ void cycle_scaling(unsigned index) configuration.scaling_mode = 0; } update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } void cycle_scaling_backwards(unsigned index) @@ -248,9 +277,7 @@ void cycle_scaling_backwards(unsigned index) configuration.scaling_mode--; } update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } static void cycle_color_correction(unsigned index) @@ -273,6 +300,82 @@ static void cycle_color_correction_backwards(unsigned index) } } +struct shader_name { + const char *file_name; + const char *display_name; +} shaders[] = +{ + {"NearestNeighbor", "Nearest Neighbor"}, + {"Bilinear", "Bilinear"}, + {"SmoothBilinear", "Smooth Bilinear"}, + {"Scale2x", "Scale2x"}, + {"Scale4x", "Scale4x"}, + {"AAScale2x", "Anti-aliased Scale2x"}, + {"AAScale4x", "Anti-aliased Scale4x"}, + {"HQ2x", "HQ2x"}, + {"OmniScale", "OmniScale"}, + {"OmniScaleLegacy", "OmniScale Legacy"}, + {"AAOmniScaleLegacy", "AA OmniScale Legacy"}, +}; + +static void cycle_filter(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + + i += 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i -= sizeof(shaders) / sizeof(shaders[0]); + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } +} + +static void cycle_filter_backwards(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + i -= 1; + if (i >= sizeof(shaders) / sizeof(shaders[0])) { + i = sizeof(shaders) / sizeof(shaders[0]) - 1; + } + + strcpy(configuration.filter, shaders[i].file_name); + free_shader(&shader); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + +} +const char *current_filter_name(unsigned index) +{ + unsigned i = 0; + for (; i < sizeof(shaders) / sizeof(shaders[0]); i++) { + if (strcmp(shaders[i].file_name, configuration.filter) == 0) { + break; + } + } + + if (i == sizeof(shaders) / sizeof(shaders[0])) { + i = 0; + } + + return shaders[i].display_name; +} static void return_to_root_menu(unsigned index) { @@ -282,6 +385,7 @@ static void return_to_root_menu(unsigned index) static const struct menu_item graphics_menu[] = { {"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards}, + {"Scaling Filter:", cycle_filter, current_filter_name, cycle_filter_backwards}, {"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards}, {"Back", return_to_root_menu}, {NULL,} @@ -399,6 +503,7 @@ static void detect_joypad_layout(unsigned index) static const struct menu_item joypad_menu[] = { {"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards}, {"Detect layout", detect_joypad_layout}, + {"Back", return_to_root_menu}, {NULL,} }; @@ -510,9 +615,7 @@ void run_gui(bool is_running) case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { update_viewport(); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(NULL, NULL); } break; } @@ -667,10 +770,7 @@ void run_gui(bool is_running) break; } - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(pixels, NULL); } } while (SDL_WaitEvent(&event)); } diff --git a/SDL/gui.h b/SDL/gui.h index 6e08c6b..5a7e94a 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -3,11 +3,13 @@ #include #include +#include "shader.h" extern SDL_Window *window; extern SDL_Renderer *renderer; extern SDL_Texture *texture; extern SDL_PixelFormat *pixel_format; +extern shader_t shader; enum scaling_mode { GB_SDL_SCALING_ENTIRE_WINDOW, @@ -37,6 +39,8 @@ typedef struct { bool div_joystick; bool flip_joystick_bit_1; bool swap_joysticks_bits_1_and_2; + + char filter[32]; } configuration_t; extern configuration_t configuration; @@ -44,5 +48,6 @@ extern configuration_t configuration; void update_viewport(void); void run_gui(bool is_running); unsigned fix_joypad_button(unsigned button); +void render_texture(void *pixels, void *previous); #endif diff --git a/SDL/main.c b/SDL/main.c index 8320b67..bac5936 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -4,6 +4,7 @@ #include "gb.h" #include "utils.h" #include "gui.h" +#include "shader.h" #ifndef _WIN32 #define AUDIO_FREQUENCY 96000 @@ -212,10 +213,7 @@ static void handle_events(GB_gameboy_t *gb) static void vblank(GB_gameboy_t *gb) { - SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t)); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + render_texture(pixels, NULL); handle_events(gb); } @@ -358,7 +356,7 @@ restart: pending_command = GB_SDL_NO_COMMAND; } } - + int main(int argc, char **argv) { #define str(x) #x @@ -390,15 +388,25 @@ usage: SDL_Init( SDL_INIT_EVERYTHING ); - window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - 160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - SDL_SetWindowMinimumSize(window, 160, 144); - 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)); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + 160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_SetWindowMinimumSize(window, 160, 144); + + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + if (gl_context == NULL) { + 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 { + pixel_format = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); + } + + /* Configure Audio */ memset(&want_aspec, 0, sizeof(want_aspec)); want_aspec.freq = AUDIO_FREQUENCY; @@ -420,6 +428,11 @@ usage: SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + if (!init_shader_with_name(&shader, configuration.filter)) { + init_shader_with_name(&shader, "NearestNeighbor"); + } + update_viewport(); + if (filename == NULL) { run_gui(false); } diff --git a/SDL/shader.c b/SDL/shader.c new file mode 100644 index 0000000..47c02d6 --- /dev/null +++ b/SDL/shader.c @@ -0,0 +1,184 @@ +#include +#include +#include "shader.h" +#include "utils.h" +#include + +static const char *vertex_shader = "\n\ +#version 150 \n\ +in vec4 aPosition;\n\ +void main(void) {\n\ +gl_Position = aPosition;\n\ +}\n\ +"; + +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(); + + // Attach shaders + glAttachShader(program, vertex_shader); + glAttachShader(program, fragment_shader); + + // Link program + glLinkProgram(program); + // Check for errors + GLint status; + glGetProgramiv(program, GL_LINK_STATUS, &status); + + if (status == GL_FALSE) { + GLchar messages[1024]; + glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); + fprintf(stderr, "GLSL Program Error: %s", messages); + } + + // Delete shaders + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return program; +} + +bool init_shader_with_name(shader_t *shader, const char *name) +{ + static char master_shader_code[0x801] = {0,}; + static char shader_code[0x10001] = {0,}; + static char final_shader_code[0x10801] = {0,}; + static ssize_t filter_token_location = 0; + + if (!master_shader_code[0]) { + FILE *master_shader_f = fopen(executable_relative_path("Shaders/MasterShader.fsh"), "r"); + if (!master_shader_f) return false; + fread(master_shader_code, 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(executable_relative_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); + + shader->program = create_program(vertex_shader, final_shader_code); + + // Attributes + shader->position_attribute = glGetAttribLocation(shader->program, "aPosition"); + // Uniforms + shader->resolution_uniform = glGetUniformLocation(shader->program, "uResolution"); + shader->origin_uniform = glGetUniformLocation(shader->program, "uOrigin"); + + 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, "previousImage"); + + shader->mix_previous_uniform = glGetUniformLocation(shader->program, "uMixPrevious"); + + // Program + + glUseProgram(shader->program); + + GLuint vao; + 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 x, unsigned y, unsigned w, unsigned h) +{ + glUseProgram(shader->program); + glUniform2f(shader->origin_uniform, x, y); + glUniform2f(shader->resolution_uniform, w, h); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 160, 144, 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, 160, 144, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); + glUniform1i(shader->previous_texture_uniform, 1); + } + glBindFragDataLocation(shader->program, 0, "frag_color"); + 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/SDL/shader.h b/SDL/shader.h new file mode 100644 index 0000000..74a9250 --- /dev/null +++ b/SDL/shader.h @@ -0,0 +1,23 @@ +#ifndef shader_h +#define shader_h +#include +#include + +typedef struct shader_s { + 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; + +bool init_shader_with_name(shader_t *shader, const char *name); +void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous,unsigned x, unsigned y, unsigned w, unsigned h); +void free_shader(struct shader_s *shader); + +#endif /* shader_h */ diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 642026e..4103056 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -4,14 +4,20 @@ uniform sampler2D previousImage; uniform bool uMixPrevious; uniform vec2 uResolution; +uniform vec2 uOrigin; const vec2 textureDimensions = vec2(160, 144); out vec4 frag_color; +vec4 modified_frag_cord; +#define gl_FragCoord modified_frag_cord #line 1 {filter} +#undef gl_FragCoord void main() { + modified_frag_cord = gl_FragCoord - vec4(uOrigin.x, uOrigin.y, 0, 0); + if (uMixPrevious) { frag_color = mix(scale(image), scale(previousImage), 0.5); }