update to upstream v0.10
This commit is contained in:
parent
1d96a10acb
commit
1272405f16
@ -504,7 +504,7 @@ Palettes:
|
||||
dw $7FFF, $1BEF, $6180, $0000
|
||||
; Sameboy "Exclusives"
|
||||
dw $7FFF, $7FEA, $7D5F, $0000 ; CGA 1
|
||||
dw $1B77, $0AD2, $25E9, $1545 ; DMG LCD
|
||||
dw $4778, $3290, $1D87, $0861 ; DMG LCD
|
||||
|
||||
KeyCombinationPalettes
|
||||
db 1 ; Right
|
||||
|
@ -1171,7 +1171,12 @@ static void printImage(GB_gameboy_t *gb, uint32_t *image, uint8_t height,
|
||||
else if (tableView == self.spritesTableView) {
|
||||
switch (columnIndex) {
|
||||
case 0:
|
||||
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image length:64 * 4] width:8 height:oamHeight scale:16.0/oamHeight];
|
||||
return [Document imageFromData:[NSData dataWithBytesNoCopy:oamInfo[row].image
|
||||
length:64 * 4
|
||||
freeWhenDone:NO]
|
||||
width:8
|
||||
height:oamHeight
|
||||
scale:16.0/oamHeight];
|
||||
case 1:
|
||||
return @((int)oamInfo[row].x - 8);
|
||||
case 2:
|
||||
|
8
Cocoa/GBJoystickListener.h
Normal file
8
Cocoa/GBJoystickListener.h
Normal file
@ -0,0 +1,8 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@protocol GBJoystickListener <NSObject>
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state;
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value;
|
||||
|
||||
@end
|
@ -1,9 +1,12 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "GBJoystickListener.h"
|
||||
|
||||
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource>
|
||||
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, GBJoystickListener>
|
||||
@property IBOutlet NSTableView *controlsTableView;
|
||||
@property IBOutlet NSPopUpButton *graphicsFilterPopupButton;
|
||||
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
|
||||
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
||||
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
||||
@property (strong) IBOutlet NSButton *configureJoypadButton;
|
||||
@property (strong) IBOutlet NSButton *skipButton;
|
||||
@end
|
||||
|
@ -7,6 +7,9 @@
|
||||
{
|
||||
bool is_button_being_modified;
|
||||
NSInteger button_being_modified;
|
||||
signed joystick_configuration_state;
|
||||
NSString *joystick_being_configured;
|
||||
signed last_axis;
|
||||
|
||||
NSPopUpButton *_graphicsFilterPopupButton;
|
||||
NSPopUpButton *_highpassFilterPopupButton;
|
||||
@ -36,6 +39,15 @@
|
||||
return filters;
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
joystick_configuration_state = -1;
|
||||
[self.configureJoypadButton setEnabled:YES];
|
||||
[self.skipButton setEnabled:NO];
|
||||
[self.configureJoypadButton setTitle:@"Configure Joypad"];
|
||||
[super close];
|
||||
}
|
||||
|
||||
- (NSPopUpButton *)graphicsFilterPopupButton
|
||||
{
|
||||
return _graphicsFilterPopupButton;
|
||||
@ -149,6 +161,108 @@
|
||||
|
||||
}
|
||||
|
||||
- (IBAction) configureJoypad:(id)sender
|
||||
{
|
||||
[self.configureJoypadButton setEnabled:NO];
|
||||
[self.skipButton setEnabled:YES];
|
||||
joystick_being_configured = nil;
|
||||
[self advanceConfigurationStateMachine];
|
||||
last_axis = -1;
|
||||
}
|
||||
|
||||
- (IBAction) skipButton:(id)sender
|
||||
{
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (void) advanceConfigurationStateMachine
|
||||
{
|
||||
joystick_configuration_state++;
|
||||
if (joystick_configuration_state < GBButtonCount) {
|
||||
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
|
||||
}
|
||||
else if (joystick_configuration_state == GBButtonCount) {
|
||||
[self.configureJoypadButton setTitle:@"Move the Analog Stick"];
|
||||
}
|
||||
else {
|
||||
joystick_configuration_state = -1;
|
||||
[self.configureJoypadButton setEnabled:YES];
|
||||
[self.skipButton setEnabled:NO];
|
||||
[self.configureJoypadButton setTitle:@"Configure Joypad"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
|
||||
{
|
||||
if (!state) return;
|
||||
if (joystick_configuration_state == -1) return;
|
||||
if (joystick_configuration_state == GBButtonCount) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = joystick_name;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:joystick_name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
|
||||
|
||||
if (!all_mappings) {
|
||||
all_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
|
||||
|
||||
if (!mapping) {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
mapping[GBButtonNames[joystick_configuration_state]] = @(button);
|
||||
|
||||
all_mappings[joystick_name] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
|
||||
{
|
||||
if (abs(value) < 0x4000) return;
|
||||
if (joystick_configuration_state != GBButtonCount) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = joystick_name;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:joystick_name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_axis == -1) {
|
||||
last_axis = axis;
|
||||
return;
|
||||
}
|
||||
|
||||
if (axis == last_axis) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
|
||||
|
||||
if (!all_mappings) {
|
||||
all_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
|
||||
|
||||
if (!mapping) {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
mapping[@"XAxis"] = @(MIN(axis, last_axis));
|
||||
mapping[@"YAxis"] = @(MAX(axis, last_axis));
|
||||
|
||||
all_mappings[joystick_name] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (NSButton *)aspectRatioCheckbox
|
||||
{
|
||||
return _aspectRatioCheckbox;
|
||||
|
@ -1,8 +1,9 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import "GBJoystickListener.h"
|
||||
#import "GBShader.h"
|
||||
|
||||
@interface GBView : NSOpenGLView
|
||||
@interface GBView<GBJoystickListener> : NSOpenGLView
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
@property GB_gameboy_t *gb;
|
||||
|
@ -11,6 +11,7 @@
|
||||
BOOL mouse_hidden;
|
||||
NSTrackingArea *tracking_area;
|
||||
BOOL _mouseHidingEnabled;
|
||||
bool enableAnalog;
|
||||
}
|
||||
|
||||
- (void) awakeFromNib
|
||||
@ -213,6 +214,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
|
||||
{
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
|
||||
|
||||
for (GBButton i = 0; i < GBButtonCount; i++) {
|
||||
NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]];
|
||||
if (mapped_button && [mapped_button integerValue] == button) {
|
||||
switch (i) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, state, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (i < GB_KEY_A) {
|
||||
enableAnalog = false;
|
||||
}
|
||||
GB_set_key_state(_gb, (GB_key_t)i, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
|
||||
{
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
|
||||
NSNumber *x_axis = [mapping objectForKey:@"XAxis"];
|
||||
NSNumber *y_axis = [mapping objectForKey:@"YAxis"];
|
||||
|
||||
if (value > 0x4000 || value < -0x4000) {
|
||||
enableAnalog = true;
|
||||
}
|
||||
if (!enableAnalog) return;
|
||||
|
||||
if (x_axis && [x_axis integerValue] == axis) {
|
||||
GB_set_key_state(_gb, GB_KEY_LEFT, value < -0x4000);
|
||||
GB_set_key_state(_gb, GB_KEY_RIGHT, value > 0x4000);
|
||||
}
|
||||
else if (y_axis && [y_axis integerValue] == axis) {
|
||||
GB_set_key_state(_gb, GB_KEY_UP, value < -0x4000);
|
||||
GB_set_key_state(_gb, GB_KEY_DOWN, value > 0x4000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
@ -259,4 +305,5 @@
|
||||
{
|
||||
return _mouseHidingEnabled;
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13196" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13196"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13529"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@ -17,14 +17,14 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="292" height="426"/>
|
||||
<rect key="contentRect" x="196" y="240" width="292" height="459"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="426"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="459"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T91-rh-rRp">
|
||||
<rect key="frame" x="18" y="389" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="422" 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"/>
|
||||
@ -33,7 +33,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6pP-kK-EEC">
|
||||
<rect key="frame" x="30" y="357" width="245" height="26"/>
|
||||
<rect key="frame" x="30" y="390" width="245" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="I1w-05-lGl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -52,7 +52,7 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem title="HQ2x" id="gxB-Uj-wp2"/>
|
||||
<menuItem title="OmniScale (Beta, any factor)" id="z6q-Jp-Z8R"/>
|
||||
<menuItem title="OmniScale (Any factor)" id="z6q-Jp-Z8R"/>
|
||||
<menuItem title="OmniScale Legacy" id="doe-kf-quG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
@ -67,7 +67,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Wc3-2K-6CD">
|
||||
<rect key="frame" x="18" y="335" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="368" 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"/>
|
||||
@ -76,7 +76,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VEz-N4-uP6">
|
||||
<rect key="frame" x="30" y="303" width="245" height="26"/>
|
||||
<rect key="frame" x="30" y="336" width="245" 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"/>
|
||||
@ -97,7 +97,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T69-6N-dhT">
|
||||
<rect key="frame" x="30" y="223" width="245" height="26"/>
|
||||
<rect key="frame" x="30" y="256" width="245" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Disabled (Keep DC Offset)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Fgo-0S-zUG" id="om2-Bn-43B">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -117,7 +117,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfj-tg-7OP">
|
||||
<rect key="frame" x="18" y="278" width="256" height="18"/>
|
||||
<rect key="frame" x="18" y="311" width="256" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Keep Aspect Ratio" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lsj-rC-Eo6">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
@ -128,7 +128,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
|
||||
<rect key="frame" x="18" y="201" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="234" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Button configuration:" id="YqW-Ds-VIC">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -137,7 +137,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="WU3-oV-KHO">
|
||||
<rect key="frame" x="18" y="255" width="256" height="17"/>
|
||||
<rect key="frame" x="18" y="288" width="256" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="High-pass Filter:" id="YLF-RL-b2D">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -146,7 +146,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
|
||||
<rect key="frame" x="20" y="20" width="252" height="173"/>
|
||||
<rect key="frame" x="20" y="53" width="252" height="173"/>
|
||||
<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="250" height="171"/>
|
||||
@ -203,15 +203,39 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
|
||||
<rect key="frame" x="205" y="10" width="67" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
|
||||
<rect key="frame" x="20" y="10" width="188" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Configure Joypad" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="aspectRatioCheckbox" destination="Vfj-tg-7OP" id="Yw0-xS-DBr"/>
|
||||
<outlet property="colorCorrectionPopupButton" destination="VEz-N4-uP6" id="EO2-Vt-JFJ"/>
|
||||
<outlet property="configureJoypadButton" destination="Qa7-Z7-yfO" id="RaX-P3-oCX"/>
|
||||
<outlet property="controlsTableView" destination="UDd-IJ-fxX" id="a1D-Md-yXv"/>
|
||||
<outlet property="delegate" destination="-2" id="ASc-vN-Zbq"/>
|
||||
<outlet property="graphicsFilterPopupButton" destination="6pP-kK-EEC" id="LS7-HY-kHC"/>
|
||||
<outlet property="highpassFilterPopupButton" destination="T69-6N-dhT" id="0p6-4m-hb1"/>
|
||||
<outlet property="skipButton" destination="d2I-jU-sLb" id="udX-8K-0sK"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="179" y="498"/>
|
||||
</window>
|
||||
|
684
Cocoa/joypad.m
Executable file
684
Cocoa/joypad.m
Executable file
@ -0,0 +1,684 @@
|
||||
/*
|
||||
Joypad support is based on a stripped-down version of SDL's Darwin implementation
|
||||
of the Joystick API, under the following license:
|
||||
*/
|
||||
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
#include "GBJoystickListener.h"
|
||||
|
||||
typedef signed SDL_JoystickID;
|
||||
typedef struct _SDL_Joystick SDL_Joystick;
|
||||
|
||||
typedef struct _SDL_JoystickAxisInfo
|
||||
{
|
||||
int16_t initial_value; /* Initial axis state */
|
||||
int16_t value; /* Current axis state */
|
||||
int16_t zero; /* Zero point on the axis (-32768 for triggers) */
|
||||
bool has_initial_value; /* Whether we've seen a value on the axis yet */
|
||||
bool sent_initial_value; /* Whether we've sent the initial axis value */
|
||||
} SDL_JoystickAxisInfo;
|
||||
|
||||
struct _SDL_Joystick
|
||||
{
|
||||
SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */
|
||||
char *name; /* Joystick name - system dependent */
|
||||
|
||||
int naxes; /* Number of axis controls on the joystick */
|
||||
SDL_JoystickAxisInfo *axes;
|
||||
|
||||
int nbuttons; /* Number of buttons on the joystick */
|
||||
uint8_t *buttons; /* Current button states */
|
||||
|
||||
struct joystick_hwdata *hwdata; /* Driver dependent information */
|
||||
|
||||
int ref_count; /* Reference count for multiple opens */
|
||||
|
||||
bool is_game_controller;
|
||||
bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */
|
||||
struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[16];
|
||||
} SDL_JoystickGUID;
|
||||
|
||||
struct recElement
|
||||
{
|
||||
IOHIDElementRef elementRef;
|
||||
IOHIDElementCookie cookie;
|
||||
uint32_t usagePage, usage; /* HID usage */
|
||||
SInt32 min; /* reported min value possible */
|
||||
SInt32 max; /* reported max value possible */
|
||||
|
||||
/* runtime variables used for auto-calibration */
|
||||
SInt32 minReport; /* min returned value */
|
||||
SInt32 maxReport; /* max returned value */
|
||||
|
||||
struct recElement *pNext; /* next element in list */
|
||||
};
|
||||
typedef struct recElement recElement;
|
||||
|
||||
struct joystick_hwdata
|
||||
{
|
||||
IOHIDDeviceRef deviceRef; /* HIDManager device handle */
|
||||
io_service_t ffservice; /* Interface for force feedback, 0 = no ff */
|
||||
|
||||
char product[256]; /* name of product */
|
||||
uint32_t usage; /* usage page from IOUSBHID Parser.h which defines general usage */
|
||||
uint32_t usagePage; /* usage within above page from IOUSBHID Parser.h which defines specific usage */
|
||||
|
||||
int axes; /* number of axis (calculated, not reported by device) */
|
||||
int buttons; /* number of buttons (calculated, not reported by device) */
|
||||
int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */
|
||||
|
||||
recElement *firstAxis;
|
||||
recElement *firstButton;
|
||||
|
||||
bool removed;
|
||||
|
||||
int instance_id;
|
||||
SDL_JoystickGUID guid;
|
||||
|
||||
SDL_Joystick joystick;
|
||||
};
|
||||
typedef struct joystick_hwdata recDevice;
|
||||
|
||||
/* The base object of the HID Manager API */
|
||||
static IOHIDManagerRef hidman = NULL;
|
||||
|
||||
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
|
||||
static int s_joystick_instance_id = -1;
|
||||
|
||||
#define SDL_JOYSTICK_AXIS_MAX 32767
|
||||
|
||||
void SDL_PrivateJoystickAxis(SDL_Joystick * joystick, uint8_t axis, int16_t value)
|
||||
{
|
||||
/* Make sure we're not getting garbage or duplicate events */
|
||||
if (axis >= joystick->naxes) {
|
||||
return;
|
||||
}
|
||||
if (!joystick->axes[axis].has_initial_value) {
|
||||
joystick->axes[axis].initial_value = value;
|
||||
joystick->axes[axis].value = value;
|
||||
joystick->axes[axis].zero = value;
|
||||
joystick->axes[axis].has_initial_value = true;
|
||||
}
|
||||
if (value == joystick->axes[axis].value) {
|
||||
return;
|
||||
}
|
||||
if (!joystick->axes[axis].sent_initial_value) {
|
||||
/* Make sure we don't send motion until there's real activity on this axis */
|
||||
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 controller needed 96 */
|
||||
if (abs(value - joystick->axes[axis].value) <= MAX_ALLOWED_JITTER) {
|
||||
return;
|
||||
}
|
||||
joystick->axes[axis].sent_initial_value = true;
|
||||
joystick->axes[axis].value = value; /* Just so we pass the check above */
|
||||
SDL_PrivateJoystickAxis(joystick, axis, joystick->axes[axis].initial_value);
|
||||
}
|
||||
|
||||
/* Update internal joystick state */
|
||||
joystick->axes[axis].value = value;
|
||||
|
||||
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:@selector(joystick:axis:movedTo:)]) {
|
||||
[responder joystick:@(joystick->name) axis:axis movedTo:value];
|
||||
break;
|
||||
}
|
||||
responder = (typeof(responder)) [responder nextResponder];
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t state)
|
||||
{
|
||||
|
||||
/* Make sure we're not getting garbage or duplicate events */
|
||||
if (button >= joystick->nbuttons) {
|
||||
return;
|
||||
}
|
||||
if (state == joystick->buttons[button]) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update internal joystick state */
|
||||
joystick->buttons[button] = state;
|
||||
|
||||
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
|
||||
[responder joystick:@(joystick->name) button:button changedState:state];
|
||||
break;
|
||||
}
|
||||
responder = (typeof(responder)) [responder nextResponder];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
FreeElementList(recElement *pElement)
|
||||
{
|
||||
while (pElement) {
|
||||
recElement *pElementNext = pElement->pNext;
|
||||
free(pElement);
|
||||
pElement = pElementNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static recDevice *
|
||||
FreeDevice(recDevice *removeDevice)
|
||||
{
|
||||
recDevice *pDeviceNext = NULL;
|
||||
if (removeDevice) {
|
||||
if (removeDevice->deviceRef) {
|
||||
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
removeDevice->deviceRef = NULL;
|
||||
}
|
||||
|
||||
/* free element lists */
|
||||
FreeElementList(removeDevice->firstAxis);
|
||||
FreeElementList(removeDevice->firstButton);
|
||||
|
||||
free(removeDevice);
|
||||
}
|
||||
return pDeviceNext;
|
||||
}
|
||||
|
||||
static SInt32
|
||||
GetHIDElementState(recDevice *pDevice, recElement *pElement)
|
||||
{
|
||||
SInt32 value = 0;
|
||||
|
||||
if (pDevice && pElement) {
|
||||
IOHIDValueRef valueRef;
|
||||
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
|
||||
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
|
||||
|
||||
/* record min and max for auto calibration */
|
||||
if (value < pElement->minReport) {
|
||||
pElement->minReport = value;
|
||||
}
|
||||
if (value > pElement->maxReport) {
|
||||
pElement->maxReport = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static SInt32
|
||||
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
|
||||
{
|
||||
const float deviceScale = max - min;
|
||||
const float readScale = pElement->maxReport - pElement->minReport;
|
||||
const SInt32 value = GetHIDElementState(pDevice, pElement);
|
||||
if (readScale == 0) {
|
||||
return value; /* no scaling at all */
|
||||
}
|
||||
return ((value - pElement->minReport) * deviceScale / readScale) + min;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
|
||||
{
|
||||
recDevice *device = (recDevice *) ctx;
|
||||
device->removed = true;
|
||||
device->deviceRef = NULL; // deviceRef was invalidated due to the remove
|
||||
FreeDevice(device);
|
||||
}
|
||||
|
||||
|
||||
static void AddHIDElement(const void *value, void *parameter);
|
||||
|
||||
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
|
||||
static void
|
||||
AddHIDElements(CFArrayRef array, recDevice *pDevice)
|
||||
{
|
||||
const CFRange range = { 0, CFArrayGetCount(array) };
|
||||
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
|
||||
}
|
||||
|
||||
static bool
|
||||
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
|
||||
while (listitem) {
|
||||
if (listitem->cookie == cookie) {
|
||||
return true;
|
||||
}
|
||||
listitem = listitem->pNext;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See if we care about this HID element, and if so, note it in our recDevice. */
|
||||
static void
|
||||
AddHIDElement(const void *value, void *parameter)
|
||||
{
|
||||
recDevice *pDevice = (recDevice *) parameter;
|
||||
IOHIDElementRef refElement = (IOHIDElementRef) value;
|
||||
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
|
||||
|
||||
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
|
||||
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
|
||||
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
|
||||
const uint32_t usage = IOHIDElementGetUsage(refElement);
|
||||
recElement *element = NULL;
|
||||
recElement **headElement = NULL;
|
||||
|
||||
/* look at types of interest */
|
||||
switch (IOHIDElementGetType(refElement)) {
|
||||
case kIOHIDElementTypeInput_Misc:
|
||||
case kIOHIDElementTypeInput_Button:
|
||||
case kIOHIDElementTypeInput_Axis: {
|
||||
switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
|
||||
case kHIDPage_GenericDesktop:
|
||||
switch (usage) {
|
||||
case kHIDUsage_GD_X:
|
||||
case kHIDUsage_GD_Y:
|
||||
case kHIDUsage_GD_Z:
|
||||
case kHIDUsage_GD_Rx:
|
||||
case kHIDUsage_GD_Ry:
|
||||
case kHIDUsage_GD_Rz:
|
||||
case kHIDUsage_GD_Slider:
|
||||
case kHIDUsage_GD_Dial:
|
||||
case kHIDUsage_GD_Wheel:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->axes++;
|
||||
headElement = &(pDevice->firstAxis);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDUsage_GD_DPadUp:
|
||||
case kHIDUsage_GD_DPadDown:
|
||||
case kHIDUsage_GD_DPadRight:
|
||||
case kHIDUsage_GD_DPadLeft:
|
||||
case kHIDUsage_GD_Start:
|
||||
case kHIDUsage_GD_Select:
|
||||
case kHIDUsage_GD_SystemMainMenu:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->buttons++;
|
||||
headElement = &(pDevice->firstButton);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDPage_Simulation:
|
||||
switch (usage) {
|
||||
case kHIDUsage_Sim_Rudder:
|
||||
case kHIDUsage_Sim_Throttle:
|
||||
case kHIDUsage_Sim_Accelerator:
|
||||
case kHIDUsage_Sim_Brake:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->axes++;
|
||||
headElement = &(pDevice->firstAxis);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDPage_Button:
|
||||
case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->buttons++;
|
||||
headElement = &(pDevice->firstButton);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kIOHIDElementTypeCollection: {
|
||||
CFArrayRef array = IOHIDElementGetChildren(refElement);
|
||||
if (array) {
|
||||
AddHIDElements(array, pDevice);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (element && headElement) { /* add to list */
|
||||
recElement *elementPrevious = NULL;
|
||||
recElement *elementCurrent = *headElement;
|
||||
while (elementCurrent && usage >= elementCurrent->usage) {
|
||||
elementPrevious = elementCurrent;
|
||||
elementCurrent = elementCurrent->pNext;
|
||||
}
|
||||
if (elementPrevious) {
|
||||
elementPrevious->pNext = element;
|
||||
} else {
|
||||
*headElement = element;
|
||||
}
|
||||
|
||||
element->elementRef = refElement;
|
||||
element->usagePage = usagePage;
|
||||
element->usage = usage;
|
||||
element->pNext = elementCurrent;
|
||||
|
||||
element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
|
||||
element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
|
||||
element->cookie = IOHIDElementGetCookie(refElement);
|
||||
|
||||
pDevice->elements++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
|
||||
{
|
||||
const uint16_t BUS_USB = 0x03;
|
||||
const uint16_t BUS_BLUETOOTH = 0x05;
|
||||
int32_t vendor = 0;
|
||||
int32_t product = 0;
|
||||
int32_t version = 0;
|
||||
CFTypeRef refCF = NULL;
|
||||
CFArrayRef array = NULL;
|
||||
uint16_t *guid16 = (uint16_t *)pDevice->guid.data;
|
||||
|
||||
/* get usage page and usage */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
|
||||
}
|
||||
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
|
||||
return false; /* Filter device list to non-keyboard/mouse stuff */
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
|
||||
}
|
||||
|
||||
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
|
||||
pDevice->usage != kHIDUsage_GD_GamePad &&
|
||||
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
|
||||
return false; /* Filter device list to non-keyboard/mouse stuff */
|
||||
}
|
||||
|
||||
pDevice->deviceRef = hidDevice;
|
||||
|
||||
/* get device name */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
|
||||
if (!refCF) {
|
||||
/* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
|
||||
}
|
||||
if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
|
||||
strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
|
||||
}
|
||||
|
||||
memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
|
||||
|
||||
if (vendor && product) {
|
||||
*guid16++ = BUS_USB;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = vendor;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = product;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = version;
|
||||
*guid16++ = 0;
|
||||
} else {
|
||||
*guid16++ = BUS_BLUETOOTH;
|
||||
*guid16++ = 0;
|
||||
strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
|
||||
}
|
||||
|
||||
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
|
||||
if (array) {
|
||||
AddHIDElements(array, pDevice);
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
|
||||
{
|
||||
recDevice *device = joystick->hwdata;
|
||||
recElement *element;
|
||||
SInt32 value;
|
||||
int i;
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->removed) { /* device was unplugged; ignore it. */
|
||||
if (joystick->hwdata) {
|
||||
joystick->force_recentering = true;
|
||||
joystick->hwdata = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
element = device->firstAxis;
|
||||
i = 0;
|
||||
while (element) {
|
||||
value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
|
||||
SDL_PrivateJoystickAxis(joystick, i, value);
|
||||
element = element->pNext;
|
||||
++i;
|
||||
}
|
||||
|
||||
element = device->firstButton;
|
||||
i = 0;
|
||||
while (element) {
|
||||
value = GetHIDElementState(device, element);
|
||||
if (value > 1) { /* handle pressure-sensitive buttons */
|
||||
value = 1;
|
||||
}
|
||||
SDL_PrivateJoystickButton(joystick, i, value);
|
||||
element = element->pNext;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
static void JoystickInputCallback(
|
||||
SDL_Joystick * joystick,
|
||||
IOReturn result,
|
||||
void * _Nullable sender,
|
||||
IOHIDReportType type,
|
||||
uint32_t reportID,
|
||||
uint8_t * report,
|
||||
CFIndex reportLength)
|
||||
{
|
||||
SDL_SYS_JoystickUpdate(joystick);
|
||||
}
|
||||
|
||||
static void
|
||||
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
|
||||
{
|
||||
recDevice *device;
|
||||
io_service_t ioservice;
|
||||
|
||||
if (res != kIOReturnSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
device = (recDevice *) calloc(1, sizeof(recDevice));
|
||||
|
||||
if (!device) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
|
||||
free(device);
|
||||
return; /* not a device we care about, probably. */
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = &device->joystick;
|
||||
|
||||
joystick->instance_id = device->instance_id;
|
||||
joystick->hwdata = device;
|
||||
joystick->name = device->product;
|
||||
|
||||
joystick->naxes = device->axes;
|
||||
joystick->nbuttons = device->buttons;
|
||||
|
||||
if (joystick->naxes > 0) {
|
||||
joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo));
|
||||
}
|
||||
if (joystick->nbuttons > 0) {
|
||||
joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1);
|
||||
}
|
||||
|
||||
/* Get notified when this device is disconnected. */
|
||||
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
|
||||
static uint8_t junk[80];
|
||||
IOHIDDeviceRegisterInputReportCallback(ioHIDDeviceObject, junk, sizeof(junk), (IOHIDReportCallback) JoystickInputCallback, joystick);
|
||||
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
||||
/* Allocate an instance ID for this device */
|
||||
device->instance_id = ++s_joystick_instance_id;
|
||||
|
||||
/* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
|
||||
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
|
||||
}
|
||||
|
||||
static bool
|
||||
ConfigHIDManager(CFArrayRef matchingArray)
|
||||
{
|
||||
CFRunLoopRef runloop = CFRunLoopGetCurrent();
|
||||
|
||||
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
|
||||
IOHIDManagerScheduleWithRunLoop(hidman, runloop, kCFRunLoopDefaultMode);
|
||||
|
||||
return true; /* good to go. */
|
||||
}
|
||||
|
||||
|
||||
static CFDictionaryRef
|
||||
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
|
||||
{
|
||||
CFDictionaryRef retval = NULL;
|
||||
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
|
||||
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
|
||||
const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
|
||||
const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
|
||||
|
||||
if (pageNumRef && usageNumRef) {
|
||||
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
}
|
||||
|
||||
if (pageNumRef) {
|
||||
CFRelease(pageNumRef);
|
||||
}
|
||||
if (usageNumRef) {
|
||||
CFRelease(usageNumRef);
|
||||
}
|
||||
|
||||
if (!retval) {
|
||||
*okay = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateHIDManager(void)
|
||||
{
|
||||
bool retval = false;
|
||||
int okay = 1;
|
||||
const void *vals[] = {
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
|
||||
};
|
||||
const size_t numElements = sizeof(vals) / sizeof(vals[0]);
|
||||
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < numElements; i++) {
|
||||
if (vals[i]) {
|
||||
CFRelease((CFTypeRef) vals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (array) {
|
||||
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||
if (hidman != NULL) {
|
||||
retval = ConfigHIDManager(array);
|
||||
}
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
void __attribute__((constructor)) SDL_SYS_JoystickInit(void)
|
||||
{
|
||||
if (!CreateHIDManager()) {
|
||||
fprintf(stderr, "Joystick: Couldn't initialize HID Manager");
|
||||
}
|
||||
}
|
@ -94,6 +94,7 @@ static void render(GB_gameboy_t *gb)
|
||||
{left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate,
|
||||
right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate};
|
||||
|
||||
case GB_HIGHPASS_MAX:;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ typedef enum {
|
||||
GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset
|
||||
GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware
|
||||
GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform
|
||||
GB_HIGHPASS_MAX
|
||||
} GB_highpass_mode_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -279,7 +279,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
|
||||
|
||||
void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index)
|
||||
{
|
||||
if (!gb->rgb_encode_callback) return;
|
||||
if (!gb->rgb_encode_callback || !gb->is_cgb) return;
|
||||
uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data;
|
||||
uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
|
||||
|
||||
|
@ -5,11 +5,10 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/time.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "gb.h"
|
||||
|
||||
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
|
||||
|
@ -303,6 +303,11 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 32; i++) {
|
||||
GB_palette_changed(gb, false, i * 2);
|
||||
GB_palette_changed(gb, true, i * 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
21
Makefile
21
Makefile
@ -17,7 +17,8 @@ ifeq ($(MAKECMDGOALS),)
|
||||
MAKECMDGOALS := $(DEFAULT)
|
||||
endif
|
||||
|
||||
VERSION := 0.9
|
||||
VERSION := 0.10
|
||||
export VERSION
|
||||
CONF ?= debug
|
||||
|
||||
BIN := build/bin
|
||||
@ -43,10 +44,11 @@ endif
|
||||
# Set compilation and linkage flags based on target, platform and configuration
|
||||
|
||||
CFLAGS += -Werror -Wall -std=gnu11 -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
|
||||
SDL_LDFLAGS := -lSDL2 -lopengl32
|
||||
else
|
||||
LDFLAGS += -lc -lm
|
||||
endif
|
||||
@ -56,7 +58,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 +90,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/drop.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 libretro
|
||||
@ -173,7 +175,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \
|
||||
|
||||
$(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
|
||||
-@$(MKDIR) -p $(dir $@)
|
||||
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia
|
||||
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit
|
||||
ifeq ($(CONF), release)
|
||||
strip $@
|
||||
endif
|
||||
@ -262,14 +264,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/drop.bmp: SDL/drop.bmp
|
||||
$(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
|
||||
|
@ -1,6 +1,6 @@
|
||||
# SameBoy
|
||||
|
||||
SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for OS X, an incomplete experimental SDL frontend for other operating systems, and a libretro core. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/).
|
||||
SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for OS X, an experimental SDL frontend for other operating systems, and a libretro core. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/).
|
||||
|
||||
## Features
|
||||
Features common to both Cocoa and SDL versions:
|
||||
@ -20,12 +20,12 @@ Features common to both Cocoa and SDL versions:
|
||||
* Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos
|
||||
* Extermely high accuracy
|
||||
* Real time clock emulation
|
||||
* Retina/High DPI display support, allowing a wider range of scaling factors without artifacts
|
||||
* Optional frame blending (Requires OpenGL 3.2 or later)
|
||||
* Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x; Requires OpenGL 3.2 or later)
|
||||
|
||||
Features currently supported only with the Cocoa version:
|
||||
* Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars
|
||||
* Retina display support, allowing a wider range of scaling factors without artifacts
|
||||
* Optional frame blending
|
||||
* Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x)
|
||||
* GameBoy Camera support
|
||||
|
||||
[Read more](https://sameboy.github.io/features/).
|
||||
|
BIN
SDL/background.bmp
Normal file
BIN
SDL/background.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
1038
SDL/font.c
Normal file
1038
SDL/font.c
Normal file
File diff suppressed because it is too large
Load Diff
16
SDL/font.h
Normal file
16
SDL/font.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef font_h
|
||||
#define font_h
|
||||
|
||||
#include <stdint.h>
|
||||
extern uint8_t font[];
|
||||
extern const uint8_t font_max;
|
||||
#define GLYPH_HEIGHT 8
|
||||
#define GLYPH_WIDTH 6
|
||||
#define LEFT_ARROW_STRING "\x86"
|
||||
#define RIGHT_ARROW_STRING "\x7f"
|
||||
#define SELECTION_STRING RIGHT_ARROW_STRING
|
||||
#define CTRL_STRING "\x80\x81\x82"
|
||||
#define SHIFT_STRING "\x83"
|
||||
#define CMD_STRING "\x84\x85"
|
||||
#endif /* font_h */
|
||||
|
903
SDL/gui.c
Normal file
903
SDL/gui.c
Normal file
@ -0,0 +1,903 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdbool.h>
|
||||
#include "utils.h"
|
||||
#include "gui.h"
|
||||
#include "font.h"
|
||||
|
||||
static const SDL_Color gui_palette[4] = {{8, 24, 16,}, {57, 97, 57,}, {132, 165, 99}, {198, 222, 140}};
|
||||
static uint32_t gui_palette_native[4];
|
||||
|
||||
SDL_Window *window = NULL;
|
||||
SDL_Renderer *renderer = NULL;
|
||||
SDL_Texture *texture = NULL;
|
||||
SDL_PixelFormat *pixel_format = NULL;
|
||||
enum pending_command pending_command;
|
||||
unsigned command_parameter;
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define MODIFIER_NAME " " CMD_STRING
|
||||
#else
|
||||
#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 =
|
||||
{
|
||||
.keys = { SDL_SCANCODE_RIGHT,
|
||||
SDL_SCANCODE_LEFT,
|
||||
SDL_SCANCODE_UP,
|
||||
SDL_SCANCODE_DOWN,
|
||||
SDL_SCANCODE_X,
|
||||
SDL_SCANCODE_Z,
|
||||
SDL_SCANCODE_BACKSPACE,
|
||||
SDL_SCANCODE_RETURN,
|
||||
SDL_SCANCODE_SPACE
|
||||
},
|
||||
.color_correction_mode = GB_COLOR_CORRECTION_EMULATE_HARDWARE,
|
||||
.highpass_mode = GB_HIGHPASS_ACCURATE,
|
||||
.scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR,
|
||||
.blend_frames = true
|
||||
};
|
||||
|
||||
|
||||
static const char *help[] ={
|
||||
"Drop a GB or GBC ROM\n"
|
||||
"file to play.\n"
|
||||
"\n"
|
||||
"Keyboard Shortcuts:\n"
|
||||
" Open Menu: Escape\n"
|
||||
" Reset: " MODIFIER_NAME "+R\n"
|
||||
" Pause: " MODIFIER_NAME "+P\n"
|
||||
" Toggle DMG/CGB: " MODIFIER_NAME "+T\n"
|
||||
" Save state: " MODIFIER_NAME "+(0-9)\n"
|
||||
" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n"
|
||||
#ifdef __APPLE__
|
||||
" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n"
|
||||
#else
|
||||
" Mute/Unmute: " MODIFIER_NAME "+M\n"
|
||||
#endif
|
||||
" Break Debugger: " CTRL_STRING "+C"
|
||||
};
|
||||
|
||||
void update_viewport(void)
|
||||
{
|
||||
int 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;
|
||||
|
||||
if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) {
|
||||
x_factor = (int)(x_factor);
|
||||
y_factor = (int)(y_factor);
|
||||
}
|
||||
|
||||
if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
|
||||
if (x_factor > y_factor) {
|
||||
x_factor = y_factor;
|
||||
}
|
||||
else {
|
||||
y_factor = x_factor;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned new_width = x_factor * 160;
|
||||
unsigned new_height = y_factor * 144;
|
||||
|
||||
rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2,
|
||||
new_width, new_height};
|
||||
|
||||
if (renderer) {
|
||||
SDL_RenderSetViewport(renderer, &rect);
|
||||
}
|
||||
else {
|
||||
glViewport(rect.x, rect.y, rect.w, rect.h);
|
||||
}
|
||||
}
|
||||
|
||||
/* Does NOT check for bounds! */
|
||||
static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color)
|
||||
{
|
||||
if (ch < ' ' || ch > font_max) {
|
||||
ch = '?';
|
||||
}
|
||||
|
||||
uint8_t *data = &font[(ch - ' ') * GLYPH_WIDTH * GLYPH_HEIGHT];
|
||||
|
||||
for (unsigned y = GLYPH_HEIGHT; y--;) {
|
||||
for (unsigned x = GLYPH_WIDTH; x--;) {
|
||||
if (*(data++)) {
|
||||
(*buffer) = color;
|
||||
}
|
||||
buffer++;
|
||||
}
|
||||
buffer += 160 - GLYPH_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color)
|
||||
{
|
||||
unsigned orig_x = x;
|
||||
while (*string) {
|
||||
if (*string == '\n') {
|
||||
x = orig_x;
|
||||
y += GLYPH_HEIGHT + 4;
|
||||
string++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x > 160 - GLYPH_WIDTH || y == 0 || y > 144 - GLYPH_HEIGHT) {
|
||||
break;
|
||||
}
|
||||
|
||||
draw_char(&buffer[x + 160 * y], *string, color);
|
||||
x += GLYPH_WIDTH;
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border)
|
||||
{
|
||||
draw_unbordered_text(buffer, x - 1, y, string, border);
|
||||
draw_unbordered_text(buffer, x + 1, y, string, border);
|
||||
draw_unbordered_text(buffer, x, y - 1, string, border);
|
||||
draw_unbordered_text(buffer, x, y + 1, string, border);
|
||||
draw_unbordered_text(buffer, x, y, string, color);
|
||||
}
|
||||
|
||||
enum decoration {
|
||||
DECORATION_NONE,
|
||||
DECORATION_SELECTION,
|
||||
DECORATION_ARROWS,
|
||||
};
|
||||
|
||||
static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration)
|
||||
{
|
||||
unsigned x = 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2;
|
||||
draw_text(buffer, x, y, string, color, border);
|
||||
switch (decoration) {
|
||||
case DECORATION_SELECTION:
|
||||
draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border);
|
||||
break;
|
||||
case DECORATION_ARROWS:
|
||||
draw_text(buffer, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border);
|
||||
draw_text(buffer, 160 - x, y, RIGHT_ARROW_STRING, color, border);
|
||||
break;
|
||||
|
||||
case DECORATION_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct menu_item {
|
||||
const char *string;
|
||||
void (*handler)(unsigned);
|
||||
const char *(*value_getter)(unsigned);
|
||||
void (*backwards_handler)(unsigned);
|
||||
};
|
||||
static const struct menu_item *current_menu = NULL;
|
||||
static const struct menu_item *root_menu = NULL;
|
||||
static unsigned current_selection = 0;
|
||||
|
||||
static enum {
|
||||
SHOWING_DROP_MESSAGE,
|
||||
SHOWING_MENU,
|
||||
SHOWING_HELP,
|
||||
WAITING_FOR_KEY,
|
||||
WAITING_FOR_JBUTTON,
|
||||
} gui_state;
|
||||
|
||||
unsigned auto_detect_progress = 0;
|
||||
unsigned auto_detect_inputs[3];
|
||||
|
||||
static void item_exit(unsigned index)
|
||||
{
|
||||
pending_command = GB_SDL_QUIT_COMMAND;
|
||||
}
|
||||
|
||||
static unsigned current_help_page = 0;
|
||||
static void item_help(unsigned index)
|
||||
{
|
||||
current_help_page = 0;
|
||||
gui_state = SHOWING_HELP;
|
||||
}
|
||||
|
||||
static void enter_graphics_menu(unsigned index);
|
||||
static void enter_controls_menu(unsigned index);
|
||||
static void enter_joypad_menu(unsigned index);
|
||||
static void enter_audio_menu(unsigned index);
|
||||
|
||||
static const struct menu_item paused_menu[] = {
|
||||
{"Resume", NULL},
|
||||
{"Graphic Options", enter_graphics_menu},
|
||||
{"Audio Options", enter_audio_menu},
|
||||
{"Keyboard", enter_controls_menu},
|
||||
{"Joypad", enter_joypad_menu},
|
||||
{"Help", item_help},
|
||||
{"Exit", item_exit},
|
||||
{NULL,}
|
||||
};
|
||||
|
||||
static const struct menu_item *const nonpaused_menu = &paused_menu[1];
|
||||
|
||||
const char *current_scaling_mode(unsigned index)
|
||||
{
|
||||
return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"}
|
||||
[configuration.scaling_mode];
|
||||
}
|
||||
|
||||
const char *current_color_correction_mode(unsigned index)
|
||||
{
|
||||
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}
|
||||
[configuration.color_correction_mode];
|
||||
}
|
||||
|
||||
void cycle_scaling(unsigned index)
|
||||
{
|
||||
configuration.scaling_mode++;
|
||||
if (configuration.scaling_mode == GB_SDL_SCALING_MAX) {
|
||||
configuration.scaling_mode = 0;
|
||||
}
|
||||
update_viewport();
|
||||
render_texture(NULL, NULL);
|
||||
}
|
||||
|
||||
void cycle_scaling_backwards(unsigned index)
|
||||
{
|
||||
if (configuration.scaling_mode == 0) {
|
||||
configuration.scaling_mode = GB_SDL_SCALING_MAX - 1;
|
||||
}
|
||||
else {
|
||||
configuration.scaling_mode--;
|
||||
}
|
||||
update_viewport();
|
||||
render_texture(NULL, NULL);
|
||||
}
|
||||
|
||||
static void cycle_color_correction(unsigned index)
|
||||
{
|
||||
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
|
||||
configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED;
|
||||
}
|
||||
else {
|
||||
configuration.color_correction_mode++;
|
||||
}
|
||||
}
|
||||
|
||||
static void cycle_color_correction_backwards(unsigned index)
|
||||
{
|
||||
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) {
|
||||
configuration.color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS;
|
||||
}
|
||||
else {
|
||||
configuration.color_correction_mode--;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
current_menu = root_menu;
|
||||
current_selection = 0;
|
||||
}
|
||||
|
||||
static void toggle_blend_frames(unsigned index)
|
||||
{
|
||||
configuration.blend_frames ^= true;
|
||||
}
|
||||
|
||||
const char *blend_frames_string(unsigned index)
|
||||
{
|
||||
return configuration.blend_frames? "Enabled" : "Disabled";
|
||||
}
|
||||
|
||||
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},
|
||||
{"Blend Frames:", toggle_blend_frames, blend_frames_string, toggle_blend_frames},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
};
|
||||
|
||||
static void enter_graphics_menu(unsigned index)
|
||||
{
|
||||
current_menu = graphics_menu;
|
||||
current_selection = 0;
|
||||
}
|
||||
|
||||
const char *highpass_filter_string(unsigned index)
|
||||
{
|
||||
return (const char *[]){"None (Keep DC Offset)", "Accurate", "Preserve Waveform"}
|
||||
[configuration.highpass_mode];
|
||||
}
|
||||
|
||||
void cycle_highpass_filter(unsigned index)
|
||||
{
|
||||
configuration.highpass_mode++;
|
||||
if (configuration.highpass_mode == GB_HIGHPASS_MAX) {
|
||||
configuration.highpass_mode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void cycle_highpass_filter_backwards(unsigned index)
|
||||
{
|
||||
if (configuration.highpass_mode == 0) {
|
||||
configuration.highpass_mode = GB_HIGHPASS_MAX - 1;
|
||||
}
|
||||
else {
|
||||
configuration.highpass_mode--;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct menu_item audio_menu[] = {
|
||||
{"Highpass Filter:", cycle_highpass_filter, highpass_filter_string, cycle_highpass_filter_backwards},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
};
|
||||
|
||||
static void enter_audio_menu(unsigned index)
|
||||
{
|
||||
current_menu = audio_menu;
|
||||
current_selection = 0;
|
||||
}
|
||||
static const char *key_name(unsigned index)
|
||||
{
|
||||
return SDL_GetScancodeName(configuration.keys[index]);
|
||||
}
|
||||
|
||||
static void modify_key(unsigned index)
|
||||
{
|
||||
gui_state = WAITING_FOR_KEY;
|
||||
}
|
||||
|
||||
static const struct menu_item controls_menu[] = {
|
||||
{"Right:", modify_key, key_name,},
|
||||
{"Left:", modify_key, key_name,},
|
||||
{"Up:", modify_key, key_name,},
|
||||
{"Down:", modify_key, key_name,},
|
||||
{"A:", modify_key, key_name,},
|
||||
{"B:", modify_key, key_name,},
|
||||
{"Select:", modify_key, key_name,},
|
||||
{"Start:", modify_key, key_name,},
|
||||
{"Turbo:", modify_key, key_name,},
|
||||
{"Back", return_to_root_menu},
|
||||
{NULL,}
|
||||
};
|
||||
|
||||
static void enter_controls_menu(unsigned index)
|
||||
{
|
||||
current_menu = controls_menu;
|
||||
current_selection = 0;
|
||||
}
|
||||
|
||||
static unsigned joypad_index = 0;
|
||||
SDL_Joystick *joystick = NULL;
|
||||
SDL_GameController *controller = NULL;
|
||||
const char *current_joypad_name(unsigned index)
|
||||
{
|
||||
static char name[23] = {0,};
|
||||
const char *orig_name = joystick? SDL_JoystickName(joystick) : NULL;
|
||||
if (!orig_name) return "Not Found";
|
||||
unsigned i = 0;
|
||||
|
||||
// SDL returns a name with repeated and trailing spaces
|
||||
while (*orig_name && i < sizeof(name) - 2) {
|
||||
if (orig_name[0] != ' ' || orig_name[1] != ' ') {
|
||||
name[i++] = *orig_name;
|
||||
}
|
||||
orig_name++;
|
||||
}
|
||||
if (i && name[i - 1] == ' ') {
|
||||
i--;
|
||||
}
|
||||
name[i] = 0;
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static void cycle_joypads(unsigned index)
|
||||
{
|
||||
joypad_index++;
|
||||
if (joypad_index >= SDL_NumJoysticks()) {
|
||||
joypad_index = 0;
|
||||
}
|
||||
if (controller) {
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = NULL;
|
||||
}
|
||||
else if (joystick) {
|
||||
SDL_JoystickClose(joystick);
|
||||
joystick = NULL;
|
||||
}
|
||||
if ((controller = SDL_GameControllerOpen(joypad_index))){
|
||||
joystick = SDL_GameControllerGetJoystick(controller);
|
||||
}
|
||||
else {
|
||||
joystick = SDL_JoystickOpen(joypad_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void cycle_joypads_backwards(unsigned index)
|
||||
{
|
||||
joypad_index++;
|
||||
if (joypad_index >= SDL_NumJoysticks()) {
|
||||
joypad_index = SDL_NumJoysticks() - 1;
|
||||
}
|
||||
if (controller) {
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = NULL;
|
||||
}
|
||||
else if (joystick) {
|
||||
SDL_JoystickClose(joystick);
|
||||
joystick = NULL;
|
||||
}
|
||||
if ((controller = SDL_GameControllerOpen(joypad_index))){
|
||||
joystick = SDL_GameControllerGetJoystick(controller);
|
||||
}
|
||||
else {
|
||||
joystick = SDL_JoystickOpen(joypad_index);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned fix_joypad_axis(unsigned axis)
|
||||
{
|
||||
if (controller) {
|
||||
/* Convert to the mapping used by generic Xbox-style controllers */
|
||||
for (SDL_GameControllerAxis i = 0; i < SDL_CONTROLLER_AXIS_MAX; i++) {
|
||||
if (SDL_GameControllerGetBindForAxis(controller, i).value.axis == axis) {
|
||||
if (i == SDL_CONTROLLER_AXIS_LEFTX || i == SDL_CONTROLLER_AXIS_RIGHTX) return 0;
|
||||
if (i == SDL_CONTROLLER_AXIS_LEFTY || i == SDL_CONTROLLER_AXIS_RIGHTY) return 1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (configuration.div_joystick) {
|
||||
axis >>= 1;
|
||||
}
|
||||
|
||||
return axis & 1;
|
||||
}
|
||||
|
||||
unsigned fix_joypad_button(unsigned button)
|
||||
{
|
||||
if (controller) {
|
||||
/* Convert to the mapping used by generic Xbox-style controllers */
|
||||
for (SDL_GameControllerButton i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++) {
|
||||
if (SDL_GameControllerGetBindForButton(controller, i).value.button == button) {
|
||||
if (i == SDL_CONTROLLER_BUTTON_START) {
|
||||
return 9;
|
||||
}
|
||||
if (i == 9) {
|
||||
return SDL_CONTROLLER_BUTTON_START;
|
||||
}
|
||||
|
||||
if (i == SDL_CONTROLLER_BUTTON_BACK) {
|
||||
return 8;
|
||||
}
|
||||
if (i == 8) {
|
||||
return SDL_CONTROLLER_BUTTON_BACK;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
if (configuration.div_joystick) {
|
||||
button >>= 1;
|
||||
}
|
||||
|
||||
if (button < 4) {
|
||||
if (configuration.swap_joysticks_bits_1_and_2) {
|
||||
button = (int[]){0, 2, 1, 3}[button];
|
||||
}
|
||||
|
||||
if (configuration.flip_joystick_bit_1) {
|
||||
button ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
static void detect_joypad_layout(unsigned index)
|
||||
{
|
||||
gui_state = WAITING_FOR_JBUTTON;
|
||||
auto_detect_progress = 0;
|
||||
}
|
||||
|
||||
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,}
|
||||
};
|
||||
|
||||
static void enter_joypad_menu(unsigned index)
|
||||
{
|
||||
current_menu = joypad_menu;
|
||||
current_selection = 0;
|
||||
}
|
||||
|
||||
extern void set_filename(const char *new_filename, bool new_should_free);
|
||||
void run_gui(bool is_running)
|
||||
{
|
||||
if (joystick && !SDL_NumJoysticks()) {
|
||||
if (controller) {
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = NULL;
|
||||
joystick = NULL;
|
||||
}
|
||||
else {
|
||||
SDL_JoystickClose(joystick);
|
||||
joystick = NULL;
|
||||
}
|
||||
}
|
||||
else if (!joystick && SDL_NumJoysticks()) {
|
||||
if ((controller = SDL_GameControllerOpen(0))){
|
||||
joystick = SDL_GameControllerGetJoystick(controller);
|
||||
}
|
||||
else {
|
||||
joystick = SDL_JoystickOpen(0);
|
||||
}
|
||||
}
|
||||
/* Draw the background screen */
|
||||
static SDL_Surface *converted_background = NULL;
|
||||
if (!converted_background) {
|
||||
SDL_Surface *background = SDL_LoadBMP(executable_relative_path("background.bmp"));
|
||||
SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4);
|
||||
converted_background = SDL_ConvertSurface(background, pixel_format, 0);
|
||||
SDL_LockSurface(converted_background);
|
||||
SDL_FreeSurface(background);
|
||||
|
||||
for (unsigned i = 4; i--; ) {
|
||||
gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t pixels[160 * 144];
|
||||
SDL_Event event = {0,};
|
||||
gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE;
|
||||
bool should_render = true;
|
||||
current_menu = root_menu = is_running? paused_menu : nonpaused_menu;
|
||||
current_selection = 0;
|
||||
do {
|
||||
/* Convert Joypad events (We only generate down events) */
|
||||
if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) {
|
||||
switch (event.type) {
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.jbutton.button = fix_joypad_button(event.jbutton.button);
|
||||
if (event.jbutton.button < 4) {
|
||||
event.key.keysym.scancode = (event.jbutton.button & 1) ? SDL_SCANCODE_RETURN : SDL_SCANCODE_ESCAPE;
|
||||
}
|
||||
else if (event.jbutton.button == 8 || event.jbutton.button == 9) {
|
||||
event.key.keysym.scancode = SDL_SCANCODE_ESCAPE;
|
||||
}
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) event.key.keysym.scancode = SDL_SCANCODE_UP;
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) event.key.keysym.scancode = SDL_SCANCODE_DOWN;
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) event.key.keysym.scancode = SDL_SCANCODE_LEFT;
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) event.key.keysym.scancode = SDL_SCANCODE_RIGHT;
|
||||
break;
|
||||
|
||||
case SDL_JOYAXISMOTION: {
|
||||
static bool axis_active[2] = {false, false};
|
||||
event.jaxis.axis = fix_joypad_axis(event.jaxis.axis);
|
||||
if (event.jaxis.axis == 1) {
|
||||
if (event.jaxis.value > 0x4000) {
|
||||
if (!axis_active[1]) {
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.key.keysym.scancode = SDL_SCANCODE_DOWN;
|
||||
}
|
||||
axis_active[1] = true;
|
||||
}
|
||||
else if (event.jaxis.value < -0x4000) {
|
||||
if (!axis_active[0]) {
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.key.keysym.scancode = SDL_SCANCODE_UP;
|
||||
}
|
||||
axis_active[1] = true;
|
||||
}
|
||||
else {
|
||||
axis_active[1] = false;
|
||||
}
|
||||
}
|
||||
else if (event.jaxis.axis == 0) {
|
||||
if (event.jaxis.value > 0x4000) {
|
||||
if (!axis_active[0]) {
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.key.keysym.scancode = SDL_SCANCODE_RIGHT;
|
||||
}
|
||||
axis_active[0] = true;
|
||||
}
|
||||
else if (event.jaxis.value < -0x4000) {
|
||||
if (!axis_active[0]) {
|
||||
event.type = SDL_KEYDOWN;
|
||||
event.key.keysym.scancode = SDL_SCANCODE_LEFT;
|
||||
}
|
||||
axis_active[0] = true;
|
||||
}
|
||||
else {
|
||||
axis_active[0] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (event.type) {
|
||||
case SDL_QUIT: {
|
||||
if (!is_running) {
|
||||
exit(0);
|
||||
}
|
||||
else {
|
||||
pending_command = GB_SDL_QUIT_COMMAND;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
case SDL_WINDOWEVENT: {
|
||||
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
update_viewport();
|
||||
render_texture(NULL, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_DROPFILE: {
|
||||
set_filename(event.drop.file, true);
|
||||
pending_command = GB_SDL_NEW_FILE_COMMAND;
|
||||
return;
|
||||
}
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
{
|
||||
if (gui_state == WAITING_FOR_JBUTTON) {
|
||||
should_render = true;
|
||||
auto_detect_inputs[auto_detect_progress++] = event.jbutton.button;
|
||||
if (auto_detect_progress == 3) {
|
||||
gui_state = SHOWING_MENU;
|
||||
|
||||
configuration.div_joystick =
|
||||
((auto_detect_inputs[0] | auto_detect_inputs[1] | auto_detect_inputs[2]) & 1) == 0 &&
|
||||
auto_detect_inputs[0] > 9;
|
||||
|
||||
if (configuration.div_joystick) {
|
||||
auto_detect_inputs[0] >>= 1;
|
||||
auto_detect_inputs[1] >>= 1;
|
||||
auto_detect_inputs[2] >>= 1;
|
||||
}
|
||||
|
||||
configuration.swap_joysticks_bits_1_and_2 =
|
||||
(auto_detect_inputs[1] & 1) == (auto_detect_inputs[2] & 1);
|
||||
|
||||
if (configuration.swap_joysticks_bits_1_and_2) {
|
||||
auto_detect_inputs[1] = (int[]){0, 2, 1, 3}[auto_detect_inputs[1]];
|
||||
auto_detect_inputs[2] = (int[]){0, 2, 1, 3}[auto_detect_inputs[2]];
|
||||
}
|
||||
|
||||
configuration.flip_joystick_bit_1 = auto_detect_inputs[2] & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
if (is_running) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (gui_state == SHOWING_DROP_MESSAGE) {
|
||||
gui_state = SHOWING_MENU;
|
||||
}
|
||||
else if (gui_state == SHOWING_MENU) {
|
||||
gui_state = SHOWING_DROP_MESSAGE;
|
||||
}
|
||||
current_selection = 0;
|
||||
current_menu = root_menu;
|
||||
should_render = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (gui_state == SHOWING_MENU) {
|
||||
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) {
|
||||
current_selection++;
|
||||
should_render = true;
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) {
|
||||
current_selection--;
|
||||
should_render = true;
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) {
|
||||
if (current_menu[current_selection].handler) {
|
||||
current_menu[current_selection].handler(current_selection);
|
||||
if (pending_command) {
|
||||
if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) {
|
||||
exit(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
should_render = true;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) {
|
||||
current_menu[current_selection].handler(current_selection);
|
||||
should_render = true;
|
||||
}
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) {
|
||||
current_menu[current_selection].backwards_handler(current_selection);
|
||||
should_render = true;
|
||||
}
|
||||
}
|
||||
else if (gui_state == SHOWING_HELP) {
|
||||
current_help_page++;
|
||||
if (current_help_page == sizeof(help) / sizeof(help[0])) {
|
||||
gui_state = SHOWING_MENU;
|
||||
}
|
||||
should_render = true;
|
||||
}
|
||||
else if (gui_state == WAITING_FOR_KEY) {
|
||||
configuration.keys[current_selection] = event.key.keysym.scancode;
|
||||
gui_state = SHOWING_MENU;
|
||||
should_render = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (should_render) {
|
||||
should_render = false;
|
||||
memcpy(pixels, converted_background->pixels, sizeof(pixels));
|
||||
|
||||
switch (gui_state) {
|
||||
case SHOWING_DROP_MESSAGE:
|
||||
draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false);
|
||||
draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false);
|
||||
break;
|
||||
case SHOWING_MENU:
|
||||
draw_text_centered(pixels, 8, "SameBoy", gui_palette_native[3], gui_palette_native[0], false);
|
||||
unsigned i = 0, y = 24;
|
||||
for (const struct menu_item *item = current_menu; item->string; item++, i++) {
|
||||
if (item->value_getter && !item->backwards_handler) {
|
||||
char line[25];
|
||||
snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i));
|
||||
draw_text_centered(pixels, y, line, gui_palette_native[3], gui_palette_native[0],
|
||||
i == current_selection ? DECORATION_SELECTION : DECORATION_NONE);
|
||||
y += 12;
|
||||
|
||||
}
|
||||
else {
|
||||
draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0],
|
||||
i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE);
|
||||
y += 12;
|
||||
if (item->value_getter) {
|
||||
draw_text_centered(pixels, y, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
|
||||
i == current_selection ? DECORATION_ARROWS : DECORATION_NONE);
|
||||
y += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SHOWING_HELP:
|
||||
draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]);
|
||||
break;
|
||||
case WAITING_FOR_KEY:
|
||||
draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE);
|
||||
break;
|
||||
case WAITING_FOR_JBUTTON:
|
||||
draw_text_centered(pixels, 68, (const char *[])
|
||||
{
|
||||
"Press button for Start",
|
||||
"Press button for A",
|
||||
"Press button for B",
|
||||
} [auto_detect_progress],
|
||||
gui_palette_native[3], gui_palette_native[0], DECORATION_NONE);
|
||||
break;
|
||||
}
|
||||
|
||||
render_texture(pixels, NULL);
|
||||
}
|
||||
} while (SDL_WaitEvent(&event));
|
||||
}
|
57
SDL/gui.h
Normal file
57
SDL/gui.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef gui_h
|
||||
#define gui_h
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <Core/gb.h>
|
||||
#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,
|
||||
GB_SDL_SCALING_KEEP_RATIO,
|
||||
GB_SDL_SCALING_INTEGER_FACTOR,
|
||||
GB_SDL_SCALING_MAX,
|
||||
};
|
||||
|
||||
|
||||
enum pending_command {
|
||||
GB_SDL_NO_COMMAND,
|
||||
GB_SDL_SAVE_STATE_COMMAND,
|
||||
GB_SDL_LOAD_STATE_COMMAND,
|
||||
GB_SDL_RESET_COMMAND,
|
||||
GB_SDL_NEW_FILE_COMMAND,
|
||||
GB_SDL_TOGGLE_MODEL_COMMAND,
|
||||
GB_SDL_QUIT_COMMAND,
|
||||
};
|
||||
|
||||
extern enum pending_command pending_command;
|
||||
extern unsigned command_parameter;
|
||||
typedef struct {
|
||||
SDL_Scancode keys[9];
|
||||
GB_color_correction_mode_t color_correction_mode;
|
||||
enum scaling_mode scaling_mode;
|
||||
bool blend_frames;
|
||||
|
||||
GB_highpass_mode_t highpass_mode;
|
||||
|
||||
bool div_joystick;
|
||||
bool flip_joystick_bit_1;
|
||||
bool swap_joysticks_bits_1_and_2;
|
||||
|
||||
char filter[32];
|
||||
} configuration_t;
|
||||
|
||||
extern configuration_t configuration;
|
||||
|
||||
void update_viewport(void);
|
||||
void run_gui(bool is_running);
|
||||
unsigned fix_joypad_button(unsigned button);
|
||||
unsigned fix_joypad_axis(unsigned axis);
|
||||
void render_texture(void *pixels, void *previous);
|
||||
|
||||
#endif
|
389
SDL/main.c
389
SDL/main.c
@ -2,8 +2,9 @@
|
||||
#include <signal.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <Core/gb.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "gui.h"
|
||||
#include "shader.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#define AUDIO_FREQUENCY 96000
|
||||
@ -12,50 +13,26 @@
|
||||
#define AUDIO_FREQUENCY 44100
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define MODIFIER_NAME "Cmd"
|
||||
#else
|
||||
#define MODIFIER_NAME "Ctrl"
|
||||
#endif
|
||||
|
||||
static const char help[] =
|
||||
"Drop a GB or GBC ROM file to play.\n"
|
||||
"\n"
|
||||
"Controls:\n"
|
||||
" D-Pad: Arrow Keys\n"
|
||||
" A: X\n"
|
||||
" B: Z\n"
|
||||
" Start: Enter\n"
|
||||
" Select: Backspace\n"
|
||||
"\n"
|
||||
"Keyboard Shortcuts: \n"
|
||||
" Restart: " MODIFIER_NAME "+R\n"
|
||||
" Pause: " MODIFIER_NAME "+P\n"
|
||||
" Turbo: Space\n"
|
||||
#ifdef __APPLE__
|
||||
" Mute/Unmute: " MODIFIER_NAME "+Shift+M\n"
|
||||
#else
|
||||
" Mute/Unmute: " MODIFIER_NAME "+M\n"
|
||||
#endif
|
||||
" Save state: " MODIFIER_NAME "+Number (0-9)\n"
|
||||
" Load state: " MODIFIER_NAME "+Shift+Number (0-9)\n"
|
||||
" Cycle between DMG/CGB emulation: " MODIFIER_NAME "+T\n"
|
||||
" Cycle scaling modes: Tab"
|
||||
;
|
||||
|
||||
GB_gameboy_t gb;
|
||||
static bool dmg = false;
|
||||
static bool paused = false;
|
||||
static uint32_t pixels[160*144];
|
||||
static uint32_t pixel_buffer_1[160*144], pixel_buffer_2[160*144];
|
||||
static uint32_t *active_pixel_buffer = pixel_buffer_1, *previous_pixel_buffer = pixel_buffer_2;
|
||||
|
||||
|
||||
static char *filename = NULL;
|
||||
static bool should_free_filename = false;
|
||||
static char *battery_save_path_ptr;
|
||||
|
||||
static SDL_Window *window = NULL;
|
||||
static SDL_Renderer *renderer = NULL;
|
||||
static SDL_Texture *texture = NULL;
|
||||
static SDL_PixelFormat *pixel_format = NULL;
|
||||
void set_filename(const char *new_filename, bool new_should_free)
|
||||
{
|
||||
if (filename && should_free_filename) {
|
||||
SDL_free(filename);
|
||||
}
|
||||
filename = (char *) new_filename;
|
||||
should_free_filename = new_should_free;
|
||||
}
|
||||
|
||||
static SDL_AudioSpec want_aspec, have_aspec;
|
||||
|
||||
static char *captured_log = NULL;
|
||||
@ -97,62 +74,6 @@ static const char *end_capturing_logs(bool show_popup, bool should_exit)
|
||||
return captured_log;
|
||||
}
|
||||
|
||||
static enum {
|
||||
GB_SDL_NO_COMMAND,
|
||||
GB_SDL_SAVE_STATE_COMMAND,
|
||||
GB_SDL_LOAD_STATE_COMMAND,
|
||||
GB_SDL_RESET_COMMAND,
|
||||
GB_SDL_NEW_FILE_COMMAND,
|
||||
GB_SDL_TOGGLE_MODEL_COMMAND,
|
||||
} pending_command;
|
||||
|
||||
static enum {
|
||||
GB_SDL_SCALING_ENTIRE_WINDOW,
|
||||
GB_SDL_SCALING_KEEP_RATIO,
|
||||
GB_SDL_SCALING_INTEGER_FACTOR,
|
||||
GB_SDL_SCALING_MAX,
|
||||
} scaling_mode = GB_SDL_SCALING_INTEGER_FACTOR;
|
||||
static unsigned command_parameter;
|
||||
|
||||
static void update_viewport(void)
|
||||
{
|
||||
int win_width, win_height;
|
||||
SDL_GetWindowSize(window, &win_width, &win_height);
|
||||
double x_factor = win_width / 160.0;
|
||||
double y_factor = win_height / 144.0;
|
||||
|
||||
if (scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) {
|
||||
x_factor = (int)(x_factor);
|
||||
y_factor = (int)(y_factor);
|
||||
}
|
||||
|
||||
if (scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
|
||||
if (x_factor > y_factor) {
|
||||
x_factor = y_factor;
|
||||
}
|
||||
else {
|
||||
y_factor = x_factor;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
new_width, new_height};
|
||||
SDL_RenderSetViewport(renderer, &rect);
|
||||
}
|
||||
|
||||
static void cycle_scaling(void)
|
||||
{
|
||||
scaling_mode++;
|
||||
scaling_mode %= GB_SDL_SCALING_MAX;
|
||||
update_viewport();
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
static void handle_events(GB_gameboy_t *gb)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@ -165,15 +86,11 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
{
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
GB_save_battery(gb, battery_save_path_ptr);
|
||||
exit(0);
|
||||
pending_command = GB_SDL_QUIT_COMMAND;
|
||||
break;
|
||||
|
||||
case SDL_DROPFILE: {
|
||||
if (should_free_filename) {
|
||||
SDL_free(filename);
|
||||
}
|
||||
filename = event.drop.file;
|
||||
should_free_filename = true;
|
||||
set_filename(event.drop.file, true);
|
||||
pending_command = GB_SDL_NEW_FILE_COMMAND;
|
||||
break;
|
||||
}
|
||||
@ -182,36 +99,105 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
update_viewport();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_JOYBUTTONUP:
|
||||
case SDL_JOYBUTTONDOWN:
|
||||
event.jbutton.button = fix_joypad_button(event.jbutton.button);
|
||||
if (event.jbutton.button < 4) {
|
||||
GB_set_key_state(gb, (event.jbutton.button & 1) ? GB_KEY_A : GB_KEY_B,
|
||||
event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == 8) {
|
||||
GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == 9) {
|
||||
GB_set_key_state(gb, GB_KEY_START, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
|
||||
GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
|
||||
GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
|
||||
GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
|
||||
GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_JOYBUTTONDOWN);
|
||||
}
|
||||
else if (event.jbutton.button & 1) {
|
||||
GB_set_turbo_mode(gb, event.type == SDL_JOYBUTTONDOWN, false);
|
||||
}
|
||||
|
||||
else {
|
||||
bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING;
|
||||
if (audio_playing) {
|
||||
SDL_PauseAudio(true);
|
||||
}
|
||||
run_gui(true);
|
||||
if (audio_playing) {
|
||||
SDL_PauseAudio(false);
|
||||
}
|
||||
GB_set_color_correction_mode(gb, configuration.color_correction_mode);
|
||||
GB_set_highpass_filter_mode(gb, configuration.highpass_mode);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_JOYAXISMOTION:
|
||||
event.jaxis.axis = fix_joypad_axis(event.jaxis.axis);
|
||||
if (event.jaxis.axis == 1) {
|
||||
GB_set_key_state(gb, GB_KEY_DOWN, event.jaxis.value > 0x4000);
|
||||
GB_set_key_state(gb, GB_KEY_UP, event.jaxis.value < -0x4000);
|
||||
}
|
||||
else if (event.jaxis.axis == 0) {
|
||||
GB_set_key_state(gb, GB_KEY_RIGHT, event.jaxis.value > 0x4000);
|
||||
GB_set_key_state(gb, GB_KEY_LEFT, event.jaxis.value < -0x4000);
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_c:
|
||||
switch (event.key.keysym.scancode) {
|
||||
case SDL_SCANCODE_ESCAPE: {
|
||||
bool audio_playing = SDL_GetAudioStatus() == SDL_AUDIO_PLAYING;
|
||||
if (audio_playing) {
|
||||
SDL_PauseAudio(true);
|
||||
}
|
||||
run_gui(true);
|
||||
if (audio_playing) {
|
||||
SDL_PauseAudio(false);
|
||||
}
|
||||
GB_set_color_correction_mode(gb, configuration.color_correction_mode);
|
||||
GB_set_highpass_filter_mode(gb, configuration.highpass_mode);
|
||||
break;
|
||||
}
|
||||
case SDL_SCANCODE_C:
|
||||
if (event.type == SDL_KEYDOWN && (event.key.keysym.mod & KMOD_CTRL)) {
|
||||
GB_debugger_break(gb);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_r:
|
||||
case SDL_SCANCODE_R:
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
pending_command = GB_SDL_RESET_COMMAND;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_t:
|
||||
case SDL_SCANCODE_T:
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
pending_command = GB_SDL_TOGGLE_MODEL_COMMAND;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_p:
|
||||
case SDL_SCANCODE_P:
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
paused = !paused;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_m:
|
||||
case SDL_SCANCODE_M:
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
#ifdef __APPLE__
|
||||
// Can't override CMD+M (Minimize) in SDL
|
||||
@ -223,27 +209,11 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
}
|
||||
break;
|
||||
|
||||
case SDLK_TAB:
|
||||
cycle_scaling();
|
||||
break;
|
||||
#ifndef __APPLE__
|
||||
case SDLK_F1:
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window);
|
||||
break;
|
||||
#else
|
||||
case SDLK_SLASH:
|
||||
if (!(event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) {
|
||||
break;
|
||||
}
|
||||
case SDLK_QUESTION:
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window);
|
||||
#endif
|
||||
|
||||
default:
|
||||
/* Save states */
|
||||
if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) {
|
||||
if (event.key.keysym.scancode >= SDL_SCANCODE_0 && event.key.keysym.scancode <= SDL_SCANCODE_9) {
|
||||
if (event.key.keysym.mod & MODIFIER) {
|
||||
command_parameter = event.key.keysym.sym - SDLK_0;
|
||||
command_parameter = event.key.keysym.scancode - SDL_SCANCODE_0;
|
||||
|
||||
if (event.key.keysym.mod & KMOD_SHIFT) {
|
||||
pending_command = GB_SDL_LOAD_STATE_COMMAND;
|
||||
@ -256,34 +226,15 @@ static void handle_events(GB_gameboy_t *gb)
|
||||
break;
|
||||
}
|
||||
case SDL_KEYUP: // Fallthrough
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_RIGHT:
|
||||
GB_set_key_state(gb, GB_KEY_RIGHT, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
GB_set_key_state(gb, GB_KEY_LEFT, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_UP:
|
||||
GB_set_key_state(gb, GB_KEY_UP, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
GB_set_key_state(gb, GB_KEY_DOWN, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_x:
|
||||
GB_set_key_state(gb, GB_KEY_A, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_z:
|
||||
GB_set_key_state(gb, GB_KEY_B, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_BACKSPACE:
|
||||
GB_set_key_state(gb, GB_KEY_SELECT, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
GB_set_key_state(gb, GB_KEY_START, event.type == SDL_KEYDOWN);
|
||||
break;
|
||||
case SDLK_SPACE:
|
||||
GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false);
|
||||
break;
|
||||
if (event.key.keysym.scancode == configuration.keys[8]) {
|
||||
GB_set_turbo_mode(gb, event.type == SDL_KEYDOWN, false);
|
||||
}
|
||||
else {
|
||||
for (unsigned i = 0; i < GB_KEY_MAX; i++) {
|
||||
if (event.key.keysym.scancode == configuration.keys[i]) {
|
||||
GB_set_key_state(gb, i, event.type == SDL_KEYDOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -294,10 +245,16 @@ 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);
|
||||
if (configuration.blend_frames) {
|
||||
render_texture(active_pixel_buffer, previous_pixel_buffer);
|
||||
uint32_t *temp = active_pixel_buffer;
|
||||
active_pixel_buffer = previous_pixel_buffer;
|
||||
previous_pixel_buffer = temp;
|
||||
GB_set_pixels_output(gb, active_pixel_buffer);
|
||||
}
|
||||
else {
|
||||
render_texture(active_pixel_buffer, NULL);
|
||||
}
|
||||
handle_events(gb);
|
||||
}
|
||||
|
||||
@ -309,8 +266,10 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
||||
|
||||
static void debugger_interrupt(int ignore)
|
||||
{
|
||||
if (!GB_is_inited(&gb)) return;
|
||||
/* ^C twice to exit */
|
||||
if (GB_debugger_is_stopped(&gb)) {
|
||||
GB_save_battery(&gb, battery_save_path_ptr);
|
||||
exit(0);
|
||||
}
|
||||
GB_debugger_break(&gb);
|
||||
@ -361,12 +320,17 @@ static bool handle_pending_command(void)
|
||||
case GB_SDL_TOGGLE_MODEL_COMMAND:
|
||||
dmg = !dmg;
|
||||
return true;
|
||||
|
||||
case GB_SDL_QUIT_COMMAND:
|
||||
GB_save_battery(&gb, battery_save_path_ptr);
|
||||
exit(0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void run(void)
|
||||
{
|
||||
pending_command = GB_SDL_NO_COMMAND;
|
||||
restart:
|
||||
if (GB_is_inited(&gb)) {
|
||||
GB_switch_model_and_reset(&gb, !dmg);
|
||||
@ -380,9 +344,11 @@ restart:
|
||||
}
|
||||
|
||||
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
||||
GB_set_pixels_output(&gb, pixels);
|
||||
GB_set_pixels_output(&gb, active_pixel_buffer);
|
||||
GB_set_rgb_encode_callback(&gb, rgb_encode);
|
||||
GB_set_sample_rate(&gb, have_aspec.freq);
|
||||
GB_set_color_correction_mode(&gb, configuration.color_correction_mode);
|
||||
GB_set_highpass_filter_mode(&gb, configuration.highpass_mode);
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
@ -433,6 +399,17 @@ restart:
|
||||
}
|
||||
}
|
||||
|
||||
static char prefs_path[1024] = {0, };
|
||||
|
||||
static void save_configuration(void)
|
||||
{
|
||||
FILE *prefs_file = fopen(prefs_path, "wb");
|
||||
if (prefs_file) {
|
||||
fwrite(&configuration, 1, sizeof(configuration), prefs_file);
|
||||
fclose(prefs_file);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#define str(x) #x
|
||||
@ -464,14 +441,34 @@ usage:
|
||||
|
||||
SDL_Init( SDL_INIT_EVERYTHING );
|
||||
|
||||
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);
|
||||
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
SDL_SetWindowMinimumSize(window, 160, 144);
|
||||
renderer = SDL_CreateRenderer(window, -1, 0);
|
||||
|
||||
texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144);
|
||||
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
|
||||
|
||||
GLint major = 0, minor = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
||||
|
||||
if (major * 0x100 + minor < 0x302) {
|
||||
SDL_GL_DeleteContext(gl_context);
|
||||
gl_context = NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window));
|
||||
|
||||
/* Configure Audio */
|
||||
memset(&want_aspec, 0, sizeof(want_aspec));
|
||||
@ -490,60 +487,30 @@ usage:
|
||||
SDL_OpenAudio(&want_aspec, &have_aspec);
|
||||
|
||||
/* Start Audio */
|
||||
SDL_PauseAudio(false);
|
||||
|
||||
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
|
||||
|
||||
if (filename == NULL) {
|
||||
/* Draw the "Drop file" screen */
|
||||
SDL_Surface *drop_backround = SDL_LoadBMP(executable_relative_path("drop.bmp"));
|
||||
SDL_Surface *drop_converted = SDL_ConvertSurface(drop_backround, pixel_format, 0);
|
||||
SDL_LockSurface(drop_converted);
|
||||
SDL_UpdateTexture(texture, NULL, drop_converted->pixels, drop_converted->pitch);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_FreeSurface(drop_converted);
|
||||
SDL_FreeSurface(drop_backround);
|
||||
SDL_Event event;
|
||||
while (SDL_WaitEvent(&event))
|
||||
{
|
||||
switch (event.type) {
|
||||
case SDL_QUIT: {
|
||||
exit(0);
|
||||
}
|
||||
case SDL_WINDOWEVENT: {
|
||||
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
update_viewport();
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_DROPFILE: {
|
||||
filename = event.drop.file;
|
||||
should_free_filename = true;
|
||||
goto start;
|
||||
}
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_TAB) {
|
||||
cycle_scaling();
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
else if (event.key.keysym.sym == SDLK_F1) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window);
|
||||
}
|
||||
#else
|
||||
else if (event.key.keysym.sym == SDLK_QUESTION || (event.key.keysym.sym && (event.key.keysym.mod & KMOD_SHIFT))) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Help", help, window);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
char *prefs_dir = SDL_GetPrefPath("", "SameBoy");
|
||||
snprintf(prefs_path, sizeof(prefs_path) - 1, "%sprefs.bin", prefs_dir);
|
||||
SDL_free(prefs_dir);
|
||||
|
||||
FILE *prefs_file = fopen(prefs_path, "rb");
|
||||
if (prefs_file) {
|
||||
fread(&configuration, 1, sizeof(configuration), prefs_file);
|
||||
fclose(prefs_file);
|
||||
}
|
||||
start:
|
||||
|
||||
atexit(save_configuration);
|
||||
|
||||
if (!init_shader_with_name(&shader, configuration.filter)) {
|
||||
init_shader_with_name(&shader, "NearestNeighbor");
|
||||
}
|
||||
update_viewport();
|
||||
|
||||
if (filename == NULL) {
|
||||
run_gui(false);
|
||||
}
|
||||
SDL_PauseAudio(false);
|
||||
run(); // Never returns
|
||||
return 0;
|
||||
}
|
||||
|
34
SDL/opengl_compat.c
Normal file
34
SDL/opengl_compat.c
Normal file
@ -0,0 +1,34 @@
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
|
||||
#ifndef __APPLE__
|
||||
#define GL_COMPAT_NAME(func) gl_compat_##func
|
||||
#define GL_COMPAT_VAR(func) typeof(func) *GL_COMPAT_NAME(func)
|
||||
|
||||
GL_COMPAT_VAR(glCreateShader);
|
||||
GL_COMPAT_VAR(glGetAttribLocation);
|
||||
GL_COMPAT_VAR(glGetUniformLocation);
|
||||
GL_COMPAT_VAR(glUseProgram);
|
||||
GL_COMPAT_VAR(glGenVertexArrays);
|
||||
GL_COMPAT_VAR(glBindVertexArray);
|
||||
GL_COMPAT_VAR(glGenBuffers);
|
||||
GL_COMPAT_VAR(glBindBuffer);
|
||||
GL_COMPAT_VAR(glBufferData);
|
||||
GL_COMPAT_VAR(glEnableVertexAttribArray);
|
||||
GL_COMPAT_VAR(glVertexAttribPointer);
|
||||
GL_COMPAT_VAR(glCreateProgram);
|
||||
GL_COMPAT_VAR(glAttachShader);
|
||||
GL_COMPAT_VAR(glLinkProgram);
|
||||
GL_COMPAT_VAR(glGetProgramiv);
|
||||
GL_COMPAT_VAR(glGetProgramInfoLog);
|
||||
GL_COMPAT_VAR(glDeleteShader);
|
||||
GL_COMPAT_VAR(glUniform2f);
|
||||
GL_COMPAT_VAR(glActiveTexture);
|
||||
GL_COMPAT_VAR(glUniform1i);
|
||||
GL_COMPAT_VAR(glBindFragDataLocation);
|
||||
GL_COMPAT_VAR(glDeleteProgram);
|
||||
GL_COMPAT_VAR(glShaderSource);
|
||||
GL_COMPAT_VAR(glCompileShader);
|
||||
GL_COMPAT_VAR(glGetShaderiv);
|
||||
GL_COMPAT_VAR(glGetShaderInfoLog);
|
||||
#endif
|
45
SDL/opengl_compat.h
Normal file
45
SDL/opengl_compat.h
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef opengl_compat_h
|
||||
#define opengl_compat_h
|
||||
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
|
||||
#ifndef __APPLE__
|
||||
#define GL_COMPAT_NAME(func) gl_compat_##func
|
||||
|
||||
#define GL_COMPAT_WRAPPER(func) \
|
||||
({ extern typeof(func) *GL_COMPAT_NAME(func); \
|
||||
if(!GL_COMPAT_NAME(func)) GL_COMPAT_NAME(func) = SDL_GL_GetProcAddress(#func); \
|
||||
GL_COMPAT_NAME(func); \
|
||||
})
|
||||
|
||||
#define glCreateShader GL_COMPAT_WRAPPER(glCreateShader)
|
||||
#define glGetAttribLocation GL_COMPAT_WRAPPER(glGetAttribLocation)
|
||||
#define glGetUniformLocation GL_COMPAT_WRAPPER(glGetUniformLocation)
|
||||
#define glUseProgram GL_COMPAT_WRAPPER(glUseProgram)
|
||||
#define glGenVertexArrays GL_COMPAT_WRAPPER(glGenVertexArrays)
|
||||
#define glBindVertexArray GL_COMPAT_WRAPPER(glBindVertexArray)
|
||||
#define glGenBuffers GL_COMPAT_WRAPPER(glGenBuffers)
|
||||
#define glBindBuffer GL_COMPAT_WRAPPER(glBindBuffer)
|
||||
#define glBufferData GL_COMPAT_WRAPPER(glBufferData)
|
||||
#define glEnableVertexAttribArray GL_COMPAT_WRAPPER(glEnableVertexAttribArray)
|
||||
#define glVertexAttribPointer GL_COMPAT_WRAPPER(glVertexAttribPointer)
|
||||
#define glCreateProgram GL_COMPAT_WRAPPER(glCreateProgram)
|
||||
#define glAttachShader GL_COMPAT_WRAPPER(glAttachShader)
|
||||
#define glLinkProgram GL_COMPAT_WRAPPER(glLinkProgram)
|
||||
#define glGetProgramiv GL_COMPAT_WRAPPER(glGetProgramiv)
|
||||
#define glGetProgramInfoLog GL_COMPAT_WRAPPER(glGetProgramInfoLog)
|
||||
#define glDeleteShader GL_COMPAT_WRAPPER(glDeleteShader)
|
||||
#define glUniform2f GL_COMPAT_WRAPPER(glUniform2f)
|
||||
#define glActiveTexture GL_COMPAT_WRAPPER(glActiveTexture)
|
||||
#define glUniform1i GL_COMPAT_WRAPPER(glUniform1i)
|
||||
#define glBindFragDataLocation GL_COMPAT_WRAPPER(glBindFragDataLocation)
|
||||
#define glDeleteProgram GL_COMPAT_WRAPPER(glDeleteProgram)
|
||||
#define glShaderSource GL_COMPAT_WRAPPER(glShaderSource)
|
||||
#define glCompileShader GL_COMPAT_WRAPPER(glCompileShader)
|
||||
#define glGetShaderiv GL_COMPAT_WRAPPER(glGetShaderiv)
|
||||
#define glGetShaderInfoLog GL_COMPAT_WRAPPER(glGetShaderInfoLog)
|
||||
#endif
|
||||
|
||||
#endif /* opengl_compat_h */
|
199
SDL/shader.c
Normal file
199
SDL/shader.c
Normal file
@ -0,0 +1,199 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "shader.h"
|
||||
#include "utils.h"
|
||||
|
||||
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)
|
||||
{
|
||||
GLint major = 0, minor = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
||||
|
||||
if (major * 0x100 + minor < 0x302) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static char master_shader_code[0x801] = {0,};
|
||||
static char shader_code[0x10001] = {0,};
|
||||
static char final_shader_code[0x10801] = {0,};
|
||||
static signed long 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)
|
||||
{
|
||||
GLint major = 0, minor = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
||||
|
||||
if (major * 0x100 + minor < 0x302) {
|
||||
return;
|
||||
}
|
||||
|
||||
glDeleteProgram(shader->program);
|
||||
glDeleteTextures(1, &shader->texture);
|
||||
glDeleteTextures(1, &shader->previous_texture);
|
||||
|
||||
}
|
23
SDL/shader.h
Normal file
23
SDL/shader.h
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef shader_h
|
||||
#define shader_h
|
||||
#include "opengl_compat.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
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 */
|
54
SDL/utils.c
54
SDL/utils.c
@ -1,62 +1,24 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "utils.h"
|
||||
|
||||
const char *executable_folder(void)
|
||||
{
|
||||
static char path[1024] = {0,};
|
||||
if (path[0]) {
|
||||
return path;
|
||||
}
|
||||
/* Ugly unportable code! :( */
|
||||
#ifdef __APPLE__
|
||||
unsigned int length = sizeof(path) - 1;
|
||||
_NSGetExecutablePath(&path[0], &length);
|
||||
#else
|
||||
#ifdef __linux__
|
||||
ssize_t __attribute__((unused)) length = readlink("/proc/self/exe", &path[0], sizeof(path) - 1);
|
||||
assert (length != -1);
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
HMODULE hModule = GetModuleHandle(NULL);
|
||||
GetModuleFileName(hModule, path, sizeof(path) - 1);
|
||||
#else
|
||||
/* No OS-specific way, assume running from CWD */
|
||||
getcwd(&path[0], sizeof(path) - 1);
|
||||
return path;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
size_t pos = strlen(path);
|
||||
while (pos) {
|
||||
pos--;
|
||||
#ifdef _WIN32
|
||||
if (path[pos] == '\\') {
|
||||
#else
|
||||
if (path[pos] == '/') {
|
||||
#endif
|
||||
path[pos] = 0;
|
||||
break;
|
||||
static const char *ret = NULL;
|
||||
if (!ret) {
|
||||
ret = SDL_GetBasePath();
|
||||
if (!ret) {
|
||||
ret = "./";
|
||||
}
|
||||
}
|
||||
return path;
|
||||
return ret;
|
||||
}
|
||||
|
||||
char *executable_relative_path(const char *filename)
|
||||
{
|
||||
static char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/%s", executable_folder(), filename);
|
||||
snprintf(path, sizeof(path), "%s%s", executable_folder(), filename);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
1
Windows/inttypes.h
Executable file
1
Windows/inttypes.h
Executable file
@ -0,0 +1 @@
|
||||
#include <stdint.h>
|
@ -14,7 +14,7 @@
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#define SAMEBOY_CORE_VERSION "0.9"
|
||||
#define SAMEBOY_CORE_VERSION "0.10"
|
||||
|
||||
#include <Core/gb.h>
|
||||
#include "libretro.h"
|
||||
|
Loading…
Reference in New Issue
Block a user