Add accurate frame blending option

This commit is contained in:
Lior Halphon 2020-03-26 20:54:18 +02:00
parent e94e7cc501
commit 5ecb845662
21 changed files with 258 additions and 69 deletions

View File

@ -1,5 +1,6 @@
#import "AppDelegate.h"
#include "GBButtons.h"
#include "GBView.h"
#include <Core/gb.h>
#import <Carbon/Carbon.h>
@ -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),

View File

@ -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<NSValidatedUserInterfaceItem>)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:^{

View File

@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#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

View File

@ -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);

View File

@ -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();
}

View File

@ -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;

View File

@ -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,6 +236,13 @@
[[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];
}
@ -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

View File

@ -2,11 +2,19 @@
#include <Core/gb.h>
#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<GBJoystickListener> : 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;

View File

@ -19,6 +19,7 @@
bool underclockKeyDown;
double clockMultiplier;
NSEventModifierFlags previousModifiers;
GB_frame_blending_mode_t _frameBlendingMode;
}
+ (instancetype)alloc
@ -44,7 +45,6 @@
- (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

View File

@ -15,7 +15,7 @@ static const vector_float2 rect[] =
id<MTLBuffer> vertices;
id<MTLRenderPipelineState> pipeline_state;
id<MTLCommandQueue> command_queue;
id<MTLBuffer> mix_previous_buffer;
id<MTLBuffer> frame_blending_mode_buffer;
id<MTLBuffer> 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<MTLCommandBuffer> 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<MTLRenderCommandEncoder> 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];

View File

@ -69,6 +69,7 @@
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
<outlet property="displayBorderPopupButton" destination="R9D-FV-bpd" id="VfO-b4-gqH"/>
<outlet property="dmgPopupButton" destination="LFw-Uk-cPR" id="KDw-i2-k4u"/>
<outlet property="frameBlendingModePopupButton" destination="lxk-db-Sxv" id="wzt-uo-TE6"/>
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
<outlet property="playerListButton" destination="gWx-7h-0xq" id="zo6-82-JId"/>
@ -80,11 +81,11 @@
<point key="canvasLocation" x="183" y="354"/>
</window>
<customView id="sRK-wO-K6R">
<rect key="frame" x="0.0" y="0.0" width="292" height="267"/>
<rect key="frame" x="0.0" y="0.0" width="292" height="323"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
<rect key="frame" x="18" y="230" width="256" height="17"/>
<rect key="frame" x="18" y="286" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Graphics filter:" id="pXg-WY-8Q5">
<font key="font" metaFont="system"/>
@ -93,7 +94,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
<rect key="frame" x="30" y="198" width="234" height="26"/>
<rect key="frame" x="30" y="254" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Nearest neighbor (Pixelated)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="neN-eo-LA7" id="I1w-05-lGl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -130,7 +131,7 @@
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
<rect key="frame" x="18" y="176" width="256" height="17"/>
<rect key="frame" x="18" y="232" width="256" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Color correction:" id="5Si-hz-EK3">
<font key="font" metaFont="system"/>
@ -139,7 +140,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
<rect key="frame" x="30" y="144" width="234" height="26"/>
<rect key="frame" x="30" y="200" width="234" height="26"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="D2J-wV-1vu" id="fNJ-Fi-yOm">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
@ -160,6 +161,35 @@
<action selector="colorCorrectionChanged:" target="QvC-M9-y7g" id="Oq4-B5-nO6"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MLC-Rx-FgO">
<rect key="frame" x="20" y="178" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Frame blending" id="UCa-EO-tzh">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lxk-db-Sxv">
<rect key="frame" x="32" y="149" width="229" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="push" title="Disabled" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="iHP-Yz-fiH" id="aQ6-HN-7Aj">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Q9L-qo-kF4">
<items>
<menuItem title="Disabled" state="on" id="iHP-Yz-fiH">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Simple" id="Hxy-jw-x6E"/>
<menuItem title="Accurate" id="Aaq-uy-Csa"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="franeBlendingModeChanged:" target="QvC-M9-y7g" id="kE1-pm-MIp"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8fG-zm-hpr">
<rect key="frame" x="18" y="122" width="252" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
@ -231,7 +261,7 @@
</connections>
</button>
</subviews>
<point key="canvasLocation" x="-176" y="639.5"/>
<point key="canvasLocation" x="-176" y="667.5"/>
</customView>
<customView id="ymk-46-SX7">
<rect key="frame" x="0.0" y="0.0" width="292" height="320"/>
@ -460,7 +490,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
<rect key="frame" x="1" y="1" width="238" height="209"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" appearanceType="vibrantLight" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" id="UDd-IJ-fxX">
<rect key="frame" x="0.0" y="0.0" width="238" height="209"/>

View File

@ -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;
}

View File

@ -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 */

View File

@ -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 */

View File

@ -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,}
};

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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 */

View File

@ -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);
}

View File

@ -42,19 +42,52 @@ static inline float4 texture(texture2d<half> 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<half> image [[ texture(0) ]],
texture2d<half> 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);
}