Add accurate frame blending option
This commit is contained in:
parent
e94e7cc501
commit
5ecb845662
@ -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),
|
||||
|
@ -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:^{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
43
SDL/gui.c
43
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,}
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
13
SDL/shader.h
13
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 */
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user