From 5ecb8456624642e18328450864a6a9cbd015a7ee Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Thu, 26 Mar 2020 20:54:18 +0200 Subject: [PATCH] Add accurate frame blending option --- Cocoa/AppDelegate.m | 2 ++ Cocoa/Document.m | 21 +++++++++-------- Cocoa/GBGLShader.h | 3 ++- Cocoa/GBGLShader.m | 10 ++++---- Cocoa/GBOpenGLView.m | 5 ++-- Cocoa/GBPreferencesWindow.h | 1 + Cocoa/GBPreferencesWindow.m | 22 ++++++++++++++++-- Cocoa/GBView.h | 10 +++++++- Cocoa/GBView.m | 21 +++++++++++++---- Cocoa/GBViewMetal.m | 20 ++++++++-------- Cocoa/Preferences.xib | 44 +++++++++++++++++++++++++++++------ Core/display.c | 10 ++++++++ Core/display.h | 1 + Core/gb.h | 1 + SDL/gui.c | 43 ++++++++++++++++++++++++++++------ SDL/gui.h | 2 +- SDL/main.c | 2 +- SDL/shader.c | 7 +++--- SDL/shader.h | 13 +++++++++-- Shaders/MasterShader.fsh | 46 +++++++++++++++++++++++++++++++------ Shaders/MasterShader.metal | 43 ++++++++++++++++++++++++++++++---- 21 files changed, 258 insertions(+), 69 deletions(-) diff --git a/Cocoa/AppDelegate.m b/Cocoa/AppDelegate.m index bbaa3ae..b941eb4 100644 --- a/Cocoa/AppDelegate.m +++ b/Cocoa/AppDelegate.m @@ -1,5 +1,6 @@ #import "AppDelegate.h" #include "GBButtons.h" +#include "GBView.h" #include #import @@ -36,6 +37,7 @@ @"GBColorCorrection": @(GB_COLOR_CORRECTION_EMULATE_HARDWARE), @"GBHighpassFilter": @(GB_HIGHPASS_REMOVE_DC_OFFSET), @"GBRewindLength": @(10), + @"GBFrameBlendingMode": @([defaults boolForKey:@"DisableFrameBlending"]? GB_FRAME_BLENDING_MODE_DISABLED : GB_FRAME_BLENDING_MODE_ACCURATE), @"GBDMGModel": @(GB_MODEL_DMG_B), @"GBCGBModel": @(GB_MODEL_CGB_E), diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 8264fb3..b835233 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -492,7 +492,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) self.consoleOutput.textContainerInset = NSMakeSize(4, 4); [self.view becomeFirstResponder]; - self.view.shouldBlendFrameWithPrevious = ![[NSUserDefaults standardUserDefaults] boolForKey:@"DisableFrameBlending"]; + self.view.frameBlendingMode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; CGRect window_frame = self.mainWindow.frame; window_frame.size.width = MAX([[NSUserDefaults standardUserDefaults] integerForKey:@"LastWindowWidth"], window_frame.size.width); @@ -521,6 +521,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) name:@"GBColorCorrectionChanged" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateFrameBlendingMode) + name:@"GBFrameBlendingModeChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePalette) name:@"GBColorPaletteChanged" @@ -677,12 +682,6 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) [[NSUserDefaults standardUserDefaults] setBool:!self.audioClient.isPlaying forKey:@"Mute"]; } -- (IBAction)toggleBlend:(id)sender -{ - self.view.shouldBlendFrameWithPrevious ^= YES; - [[NSUserDefaults standardUserDefaults] setBool:!self.view.shouldBlendFrameWithPrevious forKey:@"DisableFrameBlending"]; -} - - (BOOL)validateUserInterfaceItem:(id)anItem { if([anItem action] == @selector(mute:)) { @@ -695,9 +694,6 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) else if ([anItem action] == @selector(reset:) && anItem.tag != MODEL_NONE) { [(NSMenuItem*)anItem setState:anItem.tag == current_model]; } - else if ([anItem action] == @selector(toggleBlend:)) { - [(NSMenuItem*)anItem setState:self.view.shouldBlendFrameWithPrevious]; - } else if ([anItem action] == @selector(interrupt:)) { if (![[NSUserDefaults standardUserDefaults] boolForKey:@"DeveloperMode"]) { return false; @@ -1617,6 +1613,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample) } } +- (void) updateFrameBlendingMode +{ + self.view.frameBlendingMode = (GB_frame_blending_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; +} + - (void) updateRewindLength { [self performAtomicBlock:^{ diff --git a/Cocoa/GBGLShader.h b/Cocoa/GBGLShader.h index 1a12617..8e46f93 100644 --- a/Cocoa/GBGLShader.h +++ b/Cocoa/GBGLShader.h @@ -1,6 +1,7 @@ #import +#import "GBView.h" @interface GBGLShader : NSObject - (instancetype)initWithName:(NSString *) shaderName; -- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale; +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode: (GB_frame_blending_mode_t)blendingMode; @end diff --git a/Cocoa/GBGLShader.m b/Cocoa/GBGLShader.m index fe636f8..d57f43d 100644 --- a/Cocoa/GBGLShader.m +++ b/Cocoa/GBGLShader.m @@ -21,7 +21,7 @@ void main(void) {\n\ GLuint resolution_uniform; GLuint texture_uniform; GLuint previous_texture_uniform; - GLuint mix_previous_uniform; + GLuint frame_blending_mode_uniform; GLuint position_attribute; GLuint texture; @@ -70,7 +70,7 @@ void main(void) {\n\ glBindTexture(GL_TEXTURE_2D, 0); previous_texture_uniform = glGetUniformLocation(program, "previous_image"); - mix_previous_uniform = glGetUniformLocation(program, "mix_previous"); + frame_blending_mode_uniform = glGetUniformLocation(program, "frame_blending_mode"); // Configure OpenGL [self configureOpenGL]; @@ -79,7 +79,7 @@ void main(void) {\n\ return self; } -- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale +- (void) renderBitmap: (void *)bitmap previous:(void*) previous sized:(NSSize)srcSize inSize:(NSSize)dstSize scale: (double) scale withBlendingMode:(GB_frame_blending_mode_t)blendingMode { glUseProgram(program); glUniform2f(resolution_uniform, dstSize.width * scale, dstSize.height * scale); @@ -87,8 +87,8 @@ void main(void) {\n\ glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap); glUniform1i(texture_uniform, 0); - glUniform1i(mix_previous_uniform, previous != NULL); - if (previous) { + glUniform1i(frame_blending_mode_uniform, blendingMode); + if (blendingMode) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, previous_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, srcSize.width, srcSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, previous); diff --git a/Cocoa/GBOpenGLView.m b/Cocoa/GBOpenGLView.m index 67a9f8d..fd845e1 100644 --- a/Cocoa/GBOpenGLView.m +++ b/Cocoa/GBOpenGLView.m @@ -14,10 +14,11 @@ glViewport(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); [self.shader renderBitmap:gbview.currentBuffer - previous:gbview.shouldBlendFrameWithPrevious? gbview.previousBuffer : NULL + previous:gbview.frameBlendingMode? gbview.previousBuffer : NULL sized:NSMakeSize(GB_get_screen_width(gbview.gb), GB_get_screen_height(gbview.gb)) inSize:self.bounds.size - scale:scale]; + scale:scale + withBlendingMode:gbview.frameBlendingMode]; glFlush(); } diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 2d6b0fc..27b5aa5 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -7,6 +7,7 @@ @property (strong) IBOutlet NSButton *aspectRatioCheckbox; @property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton; @property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton; +@property (strong) IBOutlet NSPopUpButton *frameBlendingModePopupButton; @property (strong) IBOutlet NSPopUpButton *colorPalettePopupButton; @property (strong) IBOutlet NSPopUpButton *displayBorderPopupButton; @property (strong) IBOutlet NSPopUpButton *rewindPopupButton; diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index 0303771..1b5aa4f 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -14,6 +14,7 @@ NSPopUpButton *_graphicsFilterPopupButton; NSPopUpButton *_highpassFilterPopupButton; NSPopUpButton *_colorCorrectionPopupButton; + NSPopUpButton *_frameBlendingModePopupButton; NSPopUpButton *_colorPalettePopupButton; NSPopUpButton *_displayBorderPopupButton; NSPopUpButton *_rewindPopupButton; @@ -87,6 +88,18 @@ return _colorCorrectionPopupButton; } +- (void)setFrameBlendingModePopupButton:(NSPopUpButton *)frameBlendingModePopupButton +{ + _frameBlendingModePopupButton = frameBlendingModePopupButton; + NSInteger mode = [[NSUserDefaults standardUserDefaults] integerForKey:@"GBFrameBlendingMode"]; + [_frameBlendingModePopupButton selectItemAtIndex:mode]; +} + +- (NSPopUpButton *)frameBlendingModePopupButton +{ + return _frameBlendingModePopupButton; +} + - (void)setColorPalettePopupButton:(NSPopUpButton *)colorPalettePopupButton { _colorPalettePopupButton = colorPalettePopupButton; @@ -223,7 +236,14 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBColorCorrection"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorCorrectionChanged" object:nil]; +} +- (IBAction)franeBlendingModeChanged:(id)sender +{ + [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) + forKey:@"GBFrameBlendingMode"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GBFrameBlendingModeChanged" object:nil]; + } - (IBAction)colorPaletteChanged:(id)sender @@ -231,7 +251,6 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender indexOfSelectedItem]) forKey:@"GBColorPalette"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBColorPaletteChanged" object:nil]; - } - (IBAction)displayBorderChanged:(id)sender @@ -239,7 +258,6 @@ [[NSUserDefaults standardUserDefaults] setObject:@([sender selectedItem].tag) forKey:@"GBBorderMode"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"GBBorderModeChanged" object:nil]; - } - (IBAction)rewindLengthChanged:(id)sender diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index f4c5e44..474e3c7 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -2,11 +2,19 @@ #include #import "GBJoystickListener.h" +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_t; + @interface GBView : NSView - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; -@property (nonatomic) BOOL shouldBlendFrameWithPrevious; +@property (nonatomic) GB_frame_blending_mode_t frameBlendingMode; @property (getter=isMouseHidingEnabled) BOOL mouseHidingEnabled; @property bool isRewinding; @property NSView *internalView; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index e3026fd..0267344 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -19,6 +19,7 @@ bool underclockKeyDown; double clockMultiplier; NSEventModifierFlags previousModifiers; + GB_frame_blending_mode_t _frameBlendingMode; } + (instancetype)alloc @@ -43,8 +44,7 @@ } - (void) _init -{ - _shouldBlendFrameWithPrevious = 1; +{ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ratioKeepingChanged) name:@"GBAspectChanged" object:nil]; tracking_area = [ [NSTrackingArea alloc] initWithRect:(NSRect){} options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect @@ -79,15 +79,26 @@ [self setFrame:self.superview.frame]; } -- (void) setShouldBlendFrameWithPrevious:(BOOL)shouldBlendFrameWithPrevious +- (void) setFrameBlendingMode:(GB_frame_blending_mode_t)frameBlendingMode { - _shouldBlendFrameWithPrevious = shouldBlendFrameWithPrevious; + _frameBlendingMode = frameBlendingMode; [self setNeedsDisplay:YES]; } + +- (GB_frame_blending_mode_t)frameBlendingMode +{ + if (_frameBlendingMode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (GB_is_sgb(_gb)) { + return GB_FRAME_BLENDING_MODE_SIMPLE; + } + return GB_is_odd_frame(_gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + return _frameBlendingMode; +} - (unsigned char) numberOfBuffers { - return _shouldBlendFrameWithPrevious? 3 : 2; + return _frameBlendingMode? 3 : 2; } - (void)dealloc diff --git a/Cocoa/GBViewMetal.m b/Cocoa/GBViewMetal.m index fde4b7e..093ed2a 100644 --- a/Cocoa/GBViewMetal.m +++ b/Cocoa/GBViewMetal.m @@ -15,7 +15,7 @@ static const vector_float2 rect[] = id vertices; id pipeline_state; id command_queue; - id mix_previous_buffer; + id frame_blending_mode_buffer; id output_resolution_buffer; vector_float2 output_resolution; } @@ -23,7 +23,7 @@ static const vector_float2 rect[] = + (bool)isSupported { if (MTLCopyAllDevices) { - return [MTLCopyAllDevices() count]; + return false; //[MTLCopyAllDevices() count]; } return false; } @@ -56,10 +56,10 @@ static const vector_float2 rect[] = length:sizeof(rect) options:MTLResourceStorageModeShared]; - static const bool default_mix_value = false; - mix_previous_buffer = [device newBufferWithBytes:&default_mix_value - length:sizeof(default_mix_value) - options:MTLResourceStorageModeShared]; + static const GB_frame_blending_mode_t default_blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; + frame_blending_mode_buffer = [device newBufferWithBytes:&default_blending_mode + length:sizeof(default_blending_mode) + options:MTLResourceStorageModeShared]; output_resolution_buffer = [device newBufferWithBytes:&output_resolution length:sizeof(output_resolution) @@ -147,7 +147,7 @@ static const vector_float2 rect[] = mipmapLevel:0 withBytes:[self currentBuffer] bytesPerRow:texture.width * 4]; - if ([self shouldBlendFrameWithPrevious]) { + if ([self frameBlendingMode]) { [previous_texture replaceRegion:region mipmapLevel:0 withBytes:[self previousBuffer] @@ -157,9 +157,9 @@ static const vector_float2 rect[] = MTLRenderPassDescriptor *render_pass_descriptor = view.currentRenderPassDescriptor; id command_buffer = [command_queue commandBuffer]; - if(render_pass_descriptor != nil) + if (render_pass_descriptor != nil) { - *(bool *)[mix_previous_buffer contents] = [self shouldBlendFrameWithPrevious]; + *(GB_frame_blending_mode_t *)[frame_blending_mode_buffer contents] = [self frameBlendingMode]; *(vector_float2 *)[output_resolution_buffer contents] = output_resolution; id render_encoder = @@ -176,7 +176,7 @@ static const vector_float2 rect[] = offset:0 atIndex:0]; - [render_encoder setFragmentBuffer:mix_previous_buffer + [render_encoder setFragmentBuffer:frame_blending_mode_buffer offset:0 atIndex:0]; diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 7eb0587..eeb1cf2 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -69,6 +69,7 @@ + @@ -80,11 +81,11 @@ - + - + @@ -93,7 +94,7 @@ - + @@ -130,7 +131,7 @@ - + @@ -139,7 +140,7 @@ - + @@ -160,6 +161,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -231,7 +261,7 @@ - + @@ -460,7 +490,7 @@ - + diff --git a/Core/display.c b/Core/display.c index 0ed973e..a7dda7d 100644 --- a/Core/display.c +++ b/Core/display.c @@ -797,6 +797,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) return; } + gb->is_odd_frame = false; + if (!GB_is_cgb(gb)) { GB_SLEEP(gb, display, 23, 1); } @@ -1228,6 +1230,7 @@ abort_fetching_object: } else { if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; display_vblank(gb); } gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; @@ -1236,6 +1239,7 @@ abort_fetching_object: else { gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) { + gb->is_odd_frame ^= true; display_vblank(gb); } } @@ -1465,3 +1469,9 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } return count; } + + +bool GB_is_odd_frame(GB_gameboy_t *gb) +{ + return gb->is_odd_frame; +} diff --git a/Core/display.h b/Core/display.h index 4c37f99..5bdeba8 100644 --- a/Core/display.h +++ b/Core/display.h @@ -58,4 +58,5 @@ void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color, bool for_border); void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode); +bool GB_is_odd_frame(GB_gameboy_t *gb); #endif /* display_h */ diff --git a/Core/gb.h b/Core/gb.h index 88e2991..b4ef658 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -524,6 +524,7 @@ struct GB_gameboy_internal_s { bool wy_triggered; uint8_t window_tile_x; uint8_t lcd_x; // The LCD can go out of sync since the push signal is skipped in some cases. + bool is_odd_frame; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/SDL/gui.c b/SDL/gui.c index b6b0f03..e34028d 100644 --- a/SDL/gui.c +++ b/SDL/gui.c @@ -46,9 +46,22 @@ void render_texture(void *pixels, void *previous) } glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); + GB_frame_blending_mode_t mode = configuration.blending_mode; + if (!previous) { + mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else if (mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + if (GB_is_sgb(&gb)) { + mode = GB_FRAME_BLENDING_MODE_SIMPLE; + } + else { + mode = GB_is_odd_frame(&gb)? GB_FRAME_BLENDING_MODE_ACCURATE_ODD : GB_FRAME_BLENDING_MODE_ACCURATE_EVEN; + } + } render_bitmap_with_shader(&shader, _pixels, previous, GB_get_screen_width(&gb), GB_get_screen_height(&gb), - rect.x, rect.y, rect.w, rect.h); + rect.x, rect.y, rect.w, rect.h, + mode); SDL_GL_SwapWindow(window); } } @@ -91,7 +104,7 @@ 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, + .blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE, .rewind_length = 60 * 2, .model = MODEL_CGB }; @@ -600,23 +613,39 @@ const char *current_filter_name(unsigned index) return shaders[i].display_name; } -static void toggle_blend_frames(unsigned index) +static void cycle_blending_mode(unsigned index) { - configuration.blend_frames ^= true; + if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_ACCURATE) { + configuration.blending_mode = GB_FRAME_BLENDING_MODE_DISABLED; + } + else { + configuration.blending_mode++; + } } -const char *blend_frames_string(unsigned index) +static void cycle_blending_mode_backwards(unsigned index) { - return configuration.blend_frames? "Enabled" : "Disabled"; + if (configuration.blending_mode == GB_FRAME_BLENDING_MODE_DISABLED) { + configuration.blending_mode = GB_FRAME_BLENDING_MODE_ACCURATE; + } + else { + configuration.blending_mode--; + } +} + +const char *blending_mode_string(unsigned index) +{ + return (const char *[]){"Disabled", "Simple", "Accurate"} + [configuration.blending_mode]; } 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}, + {"Frame Blending:", cycle_blending_mode, blending_mode_string, cycle_blending_mode_backwards}, {"Mono Palette:", cycle_palette, current_palette, cycle_palette_backwards}, {"Display Border:", cycle_border_mode, current_border_mode, cycle_border_mode_backwards}, - {"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames}, {"Back", return_to_root_menu}, {NULL,} }; diff --git a/SDL/gui.h b/SDL/gui.h index 41e9bf2..8ef2a68 100644 --- a/SDL/gui.h +++ b/SDL/gui.h @@ -70,7 +70,7 @@ typedef struct { SDL_Scancode keys[9]; GB_color_correction_mode_t color_correction_mode; enum scaling_mode scaling_mode; - bool blend_frames; + uint8_t blending_mode; GB_highpass_mode_t highpass_mode; diff --git a/SDL/main.c b/SDL/main.c index 06159c9..c2b5549 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -372,7 +372,7 @@ static void vblank(GB_gameboy_t *gb) clock_mutliplier += 1.0/16; GB_set_clock_multiplier(gb, clock_mutliplier); } - if (configuration.blend_frames) { + if (configuration.blending_mode) { render_texture(active_pixel_buffer, previous_pixel_buffer); uint32_t *temp = active_pixel_buffer; active_pixel_buffer = previous_pixel_buffer; diff --git a/SDL/shader.c b/SDL/shader.c index 37e5be7..250046b 100644 --- a/SDL/shader.c +++ b/SDL/shader.c @@ -130,7 +130,7 @@ bool init_shader_with_name(shader_t *shader, const char *name) glBindTexture(GL_TEXTURE_2D, 0); shader->previous_texture_uniform = glGetUniformLocation(shader->program, "previous_image"); - shader->mix_previous_uniform = glGetUniformLocation(shader->program, "mix_previous"); + shader->blending_mode_uniform = glGetUniformLocation(shader->program, "frame_blending_mode"); // Program @@ -164,7 +164,8 @@ 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) + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode) { glUseProgram(shader->program); glUniform2f(shader->origin_uniform, x, y); @@ -173,7 +174,7 @@ void render_bitmap_with_shader(shader_t *shader, void *bitmap, void *previous, 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); + glUniform1i(shader->blending_mode_uniform, previous? blending_mode : GB_FRAME_BLENDING_MODE_DISABLED); if (previous) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, shader->previous_texture); diff --git a/SDL/shader.h b/SDL/shader.h index 3a1c304..149958d 100644 --- a/SDL/shader.h +++ b/SDL/shader.h @@ -8,7 +8,7 @@ typedef struct shader_s { GLuint origin_uniform; GLuint texture_uniform; GLuint previous_texture_uniform; - GLuint mix_previous_uniform; + GLuint blending_mode_uniform; GLuint position_attribute; GLuint texture; @@ -16,10 +16,19 @@ typedef struct shader_s { GLuint program; } shader_t; +typedef enum { + GB_FRAME_BLENDING_MODE_DISABLED, + GB_FRAME_BLENDING_MODE_SIMPLE, + GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_EVEN = GB_FRAME_BLENDING_MODE_ACCURATE, + GB_FRAME_BLENDING_MODE_ACCURATE_ODD, +} GB_frame_blending_mode_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 source_width, unsigned source_height, - unsigned x, unsigned y, unsigned w, unsigned h); + unsigned x, unsigned y, unsigned w, unsigned h, + GB_frame_blending_mode_t blending_mode); void free_shader(struct shader_s *shader); #endif /* shader_h */ diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index cd569c2..729ab5f 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -1,7 +1,7 @@ #version 150 uniform sampler2D image; uniform sampler2D previous_image; -uniform bool mix_previous; +uniform int frame_blending_mode; uniform vec2 output_resolution; uniform vec2 origin; @@ -15,6 +15,15 @@ out vec4 frag_color; #line 1 {filter} + +#define BLEND_BIAS (1.0/3.0) + +#define DISABLED 0 +#define SIMPLE 1 +#define ACCURATE 2 +#define ACCURATE_EVEN ACCURATE +#define ACCURATE_ODD 3 + void main() { vec2 position = gl_FragCoord.xy - origin; @@ -22,11 +31,34 @@ void main() position.y = 1 - position.y; vec2 input_resolution = textureSize(image, 0); - 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); + float ratio; + switch (frame_blending_mode) { + default: + case DISABLED: + frag_color = scale(image, position, input_resolution, output_resolution); + return; + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if ((int(position.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; } + + frag_color = mix(scale(image, position, input_resolution, output_resolution), + scale(previous_image, position, input_resolution, output_resolution), ratio); + } diff --git a/Shaders/MasterShader.metal b/Shaders/MasterShader.metal index 4cae3ae..ee8dec9 100644 --- a/Shaders/MasterShader.metal +++ b/Shaders/MasterShader.metal @@ -42,19 +42,52 @@ static inline float4 texture(texture2d texture, float2 pos) #line 1 {filter} +#define BLEND_BIAS (1.0/3.0) + +enum frame_blending_mode { + DISABLED, + SIMPLE, + ACCURATE, + ACCURATE_EVEN = ACCURATE, + ACCURATE_ODD, +}; + fragment float4 fragment_shader(rasterizer_data in [[stage_in]], texture2d image [[ texture(0) ]], texture2d previous_image [[ texture(1) ]], - constant bool *mix_previous [[ buffer(0) ]], + constant enum frame_blending_mode *frame_blending_mode [[ buffer(0) ]], constant float2 *output_resolution [[ buffer(1) ]]) { float2 input_resolution = float2(image.get_width(), image.get_height()); in.texcoords.y = 1 - in.texcoords.y; - if (*mix_previous) { - return mix(scale(image, in.texcoords, input_resolution, *output_resolution), - scale(previous_image, in.texcoords, input_resolution, *output_resolution), 0.5); + float ratio; + switch (*frame_blending_mode) { + default: + case DISABLED: + return scale(image, in.texcoords, input_resolution, *output_resolution); + case SIMPLE: + ratio = 0.5; + break; + case ACCURATE_EVEN: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = BLEND_BIAS; + } + else { + ratio = 1 - BLEND_BIAS; + } + break; + case ACCURATE_ODD: + if (((int)(in.texcoords.y * input_resolution.y) & 1) == 0) { + ratio = 1 - BLEND_BIAS; + } + else { + ratio = BLEND_BIAS; + } + break; } - return scale(image, in.texcoords, input_resolution, *output_resolution); + + return mix(scale(image, in.texcoords, input_resolution, *output_resolution), + scale(previous_image, in.texcoords, input_resolution, *output_resolution), ratio); }