update to upstream v0.10

This commit is contained in:
hunterk 2017-12-30 14:33:52 -06:00
parent 1d96a10acb
commit 1272405f16
30 changed files with 3447 additions and 295 deletions

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -93,7 +93,8 @@ static void render(GB_gameboy_t *gb)
gb->apu_output.highpass_diff = (GB_double_sample_t)
{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:;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

1038
SDL/font.c Normal file

File diff suppressed because it is too large Load Diff

16
SDL/font.h Normal file
View 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
View 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
View 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

View File

@ -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;
@ -432,7 +398,18 @@ restart:
pending_command = GB_SDL_NO_COMMAND;
}
}
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,15 +441,35 @@ usage:
SDL_Init( SDL_INIT_EVERYTHING );
window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
SDL_SetWindowMinimumSize(window, 160, 144);
renderer = SDL_CreateRenderer(window, -1, 0);
texture = SDL_CreateTexture(renderer, SDL_GetWindowPixelFormat(window), SDL_TEXTUREACCESS_STREAMING, 160, 144);
pixel_format = SDL_AllocFormat(SDL_GetWindowPixelFormat(window));
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
window = SDL_CreateWindow("SameBoy v" xstr(VERSION), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
160 * 2, 144 * 2, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_SetWindowMinimumSize(window, 160, 144);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
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);
}
/* Configure Audio */
memset(&want_aspec, 0, sizeof(want_aspec));
want_aspec.freq = AUDIO_FREQUENCY;
@ -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
View 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
View 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
View 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
View 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 */

View File

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

View File

@ -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
View File

@ -0,0 +1 @@
#include <stdint.h>

View File

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