diff --git a/BootROMs/cgb_boot.asm b/BootROMs/cgb_boot.asm index c22aab93..92f06713 100644 --- a/BootROMs/cgb_boot.asm +++ b/BootROMs/cgb_boot.asm @@ -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 diff --git a/Cocoa/Document.m b/Cocoa/Document.m index 89e31452..e5fb9ae1 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -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: diff --git a/Cocoa/GBJoystickListener.h b/Cocoa/GBJoystickListener.h new file mode 100644 index 00000000..690fde97 --- /dev/null +++ b/Cocoa/GBJoystickListener.h @@ -0,0 +1,8 @@ +#import + +@protocol GBJoystickListener + +- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state; +- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value; + +@end diff --git a/Cocoa/GBPreferencesWindow.h b/Cocoa/GBPreferencesWindow.h index 52d26e05..88cd9265 100644 --- a/Cocoa/GBPreferencesWindow.h +++ b/Cocoa/GBPreferencesWindow.h @@ -1,9 +1,12 @@ #import +#import "GBJoystickListener.h" -@interface GBPreferencesWindow : NSWindow +@interface GBPreferencesWindow : NSWindow @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 diff --git a/Cocoa/GBPreferencesWindow.m b/Cocoa/GBPreferencesWindow.m index af88ab34..e85d2e1b 100644 --- a/Cocoa/GBPreferencesWindow.m +++ b/Cocoa/GBPreferencesWindow.m @@ -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; diff --git a/Cocoa/GBView.h b/Cocoa/GBView.h index a1d4f552..813f8c03 100644 --- a/Cocoa/GBView.h +++ b/Cocoa/GBView.h @@ -1,8 +1,9 @@ #import #include +#import "GBJoystickListener.h" #import "GBShader.h" -@interface GBView : NSOpenGLView +@interface GBView : NSOpenGLView - (void) flip; - (uint32_t *) pixels; @property GB_gameboy_t *gb; diff --git a/Cocoa/GBView.m b/Cocoa/GBView.m index 2e907ede..dfcc2f20 100644 --- a/Cocoa/GBView.m +++ b/Cocoa/GBView.m @@ -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 diff --git a/Cocoa/Preferences.xib b/Cocoa/Preferences.xib index 6b625616..57c42dbd 100644 --- a/Cocoa/Preferences.xib +++ b/Cocoa/Preferences.xib @@ -1,8 +1,8 @@ - + - + @@ -17,14 +17,14 @@ - + - + - + @@ -33,7 +33,7 @@ - + @@ -52,7 +52,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -76,7 +76,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -137,7 +137,7 @@ - + @@ -146,7 +146,7 @@ - + @@ -203,15 +203,39 @@ + + + + diff --git a/Cocoa/joypad.m b/Cocoa/joypad.m new file mode 100755 index 00000000..bca40973 --- /dev/null +++ b/Cocoa/joypad.m @@ -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 + + 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 +#include +#include +#include +#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 *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 *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"); + } +} diff --git a/Core/apu.c b/Core/apu.c index 5ba19117..c74bb6da 100755 --- a/Core/apu.c +++ b/Core/apu.c @@ -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:; } } diff --git a/Core/apu.h b/Core/apu.h index 3bca4561..bace09a3 100644 --- a/Core/apu.h +++ b/Core/apu.h @@ -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 { diff --git a/Core/display.c b/Core/display.c index 4466e34c..f9533bab 100755 --- a/Core/display.c +++ b/Core/display.c @@ -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); diff --git a/Core/gb.c b/Core/gb.c index c612231f..bf41891a 100755 --- a/Core/gb.c +++ b/Core/gb.c @@ -5,11 +5,10 @@ #include #include #include -#include #ifndef _WIN32 #include -#endif #include +#endif #include "gb.h" void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) diff --git a/Core/save_state.c b/Core/save_state.c index 24604c12..933b4178 100644 --- a/Core/save_state.c +++ b/Core/save_state.c @@ -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; } diff --git a/Makefile b/Makefile index f306305b..2969a3e8 100755 --- a/Makefile +++ b/Makefile @@ -17,7 +17,8 @@ ifeq ($(MAKECMDGOALS),) MAKECMDGOALS := $(DEFAULT) endif -VERSION := 0.9 +VERSION := 0.10 +export VERSION CONF ?= debug BIN := build/bin @@ -43,10 +44,11 @@ endif # Set compilation and linkage flags based on target, platform and configuration CFLAGS += -Werror -Wall -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES -SDL_LDFLAGS := -lSDL2 +SDL_LDFLAGS := -lSDL2 -lGL ifeq ($(PLATFORM),windows32) CFLAGS += -IWindows LDFLAGS += -lmsvcrt -lSDL2main -Wl,/MANIFESTFILE:NUL +SDL_LDFLAGS := -lSDL2 -lopengl32 else LDFLAGS += -lc -lm endif @@ -56,7 +58,7 @@ SYSROOT := $(shell xcodebuild -sdk macosx -version Path 2> /dev/null) CFLAGS += -F/Library/Frameworks OCFLAGS += -x objective-c -fobjc-arc -Wno-deprecated-declarations -isysroot $(SYSROOT) -mmacosx-version-min=10.9 LDFLAGS += -framework AppKit -framework PreferencePanes -framework Carbon -framework QuartzCore -SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 +SDL_LDFLAGS := -F/Library/Frameworks -framework SDL2 -framework OpenGL endif CFLAGS += -Wno-deprecated-declarations ifeq ($(PLATFORM),windows32) @@ -88,7 +90,7 @@ endif cocoa: $(BIN)/SameBoy.app quicklook: $(BIN)/SameBoy.qlgenerator -sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/drop.bmp +sdl: $(SDL_TARGET) $(BIN)/SDL/dmg_boot.bin $(BIN)/SDL/cgb_boot.bin $(BIN)/SDL/LICENSE $(BIN)/SDL/registers.sym $(BIN)/SDL/background.bmp $(BIN)/SDL/Shaders bootroms: $(BIN)/BootROMs/cgb_boot.bin $(BIN)/BootROMs/dmg_boot.bin tester: $(TESTER_TARGET) $(BIN)/tester/dmg_boot.bin $(BIN)/tester/cgb_boot.bin all: cocoa sdl tester libretro @@ -173,7 +175,7 @@ $(BIN)/SameBoy.app: $(BIN)/SameBoy.app/Contents/MacOS/SameBoy \ $(BIN)/SameBoy.app/Contents/MacOS/SameBoy: $(CORE_OBJECTS) $(COCOA_OBJECTS) -@$(MKDIR) -p $(dir $@) - $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia + $(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia -framework IOKit ifeq ($(CONF), release) strip $@ endif @@ -262,14 +264,21 @@ $(BIN)/SameBoy.app/Contents/Resources/%.bin: $(BOOTROMS_DIR)/%.bin cp -f $^ $@ $(BIN)/SDL/LICENSE: LICENSE + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ $(BIN)/SDL/registers.sym: Misc/registers.sym + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ -$(BIN)/SDL/drop.bmp: SDL/drop.bmp +$(BIN)/SDL/background.bmp: SDL/background.bmp + -@$(MKDIR) -p $(dir $@) cp -f $^ $@ +$(BIN)/SDL/Shaders: Shaders + -@$(MKDIR) -p $(dir $@) + cp -rf $^ $@ + # Boot ROMs $(BIN)/BootROMs/%.bin: BootROMs/%.asm diff --git a/README.md b/README.md index 2e049e45..e80a92db 100644 --- a/README.md +++ b/README.md @@ -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/). diff --git a/SDL/background.bmp b/SDL/background.bmp new file mode 100644 index 00000000..0f6192d6 Binary files /dev/null and b/SDL/background.bmp differ diff --git a/SDL/font.c b/SDL/font.c new file mode 100644 index 00000000..93f3fa94 --- /dev/null +++ b/SDL/font.c @@ -0,0 +1,1038 @@ +#include "font.h" + +#define _ 0 +#define X 1 + +uint8_t font[] = { + /* */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* ! */ + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* " */ + X, X, _, X, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* # */ + _, X, _, X, _, _, + _, X, _, X, _, _, + X, X, X, X, X, _, + _, X, _, X, _, _, + X, X, X, X, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, + + /* $ */ + _, _, X, _, _, _, + _, X, X, X, _, _, + X, _, X, _, X, _, + _, X, X, _, _, _, + _, _, X, X, _, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + _, _, X, _, _, _, + + /* % */ + _, _, _, _, _, _, + X, X, _, _, _, X, + X, X, _, _, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, X, X, + X, _, _, _, X, X, + _, _, _, _, _, _, + + /* & */ + _, X, X, _, _, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + _, _, _, _, _, _, + + /* ' */ + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* ( */ + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + + /* ) */ + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + + /* * */ + _, _, _, _, _, _, + _, _, X, _, _, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + X, _, X, _, X, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* + */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /*, */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + + /* - */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* . */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* / */ + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + + /* 0 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 1 */ + _, _, X, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* 2 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, X, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* 3 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 4 */ + _, _, _, X, _, _, + _, _, X, X, _, _, + _, X, _, X, _, _, + X, _, _, X, _, _, + X, X, X, X, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, + + /* 5 */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 6 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 7 */ + X, X, X, X, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* 8 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* 9 */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, X, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* : */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* ; */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + + /* < */ + _, _, _, _, _, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + X, X, _, _, _, _, + X, X, _, _, _, _, + _, _, X, X, _, _, + _, _, _, _, X, _, + _, _, _, _, _, _, + + /* = */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* > */ + _, _, _, _, _, _, + X, _, _, _, _, _, + _, X, X, _, _, _, + _, _, _, X, X, _, + _, _, _, X, X, _, + _, X, X, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, + + /* ? */ + _, X, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, X, _, + _, _, X, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* @ */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, + + /* A */ + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* B */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* C */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* D */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* E */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* F */ + X, X, X, X, X, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, + + /* G */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* H */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* I */ + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* J */ + _, _, X, X, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* K */ + X, _, _, _, X, _, + X, _, _, X, _, _, + X, _, X, _, _, _, + X, X, _, _, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* L */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* M */ + X, _, _, _, X, _, + X, X, _, X, X, _, + X, X, _, X, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* N */ + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, _, X, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* O */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* P */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + _, _, _, _, _, _, + + /* Q */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + _, X, X, X, _, _, + _, _, _, _, X, X, + + /* R */ + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + X, _, X, _, _, _, + X, _, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* S */ + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* T */ + X, X, X, X, X, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* U */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* V */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* W */ + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, X, _, X, X, _, + X, X, _, X, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* X */ + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* Y */ + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* Z */ + X, X, X, X, X, _, + _, _, _, _, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + X, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* [ */ + _, X, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, X, X, _, _, + + /* \ */ + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, _, X, _, + _, _, _, _, X, _, + + /* ] */ + _, X, X, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, X, X, X, _, _, + + /* ^ */ + _, _, X, _, _, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* _ */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, X, + + /* ` */ + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* a */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, _, _, _, + _, _, _, X, _, _, + _, X, X, X, _, _, + X, _, _, X, _, _, + _, X, X, _, X, _, + _, _, _, _, _, _, + + /* b */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* c */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* d */ + _, _, _, _, X, _, + _, _, _, _, X, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* e */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* f */ + _, _, X, X, _, _, + _, X, _, _, _, _, + X, X, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + /* g */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, X, _, _, + _, X, X, _, _, _, + _, _, _, X, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + + /* h */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* i */ + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* j */ + _, _, X, _, _, _, + _, _, _, _, _, _, + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, X, _, _, _, _, + + /* k */ + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, X, _, _, + X, X, _, _, _, _, + X, _, X, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* l */ + X, X, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, X, _, + _, _, _, _, _, _, + + /* m */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, _, _, _, _, _, + + /* n */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, X, X, _, _, + X, X, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* o */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, X, X, _, _, + _, _, _, _, _, _, + + /* p */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, X, _, _, X, _, + X, _, X, X, _, _, + X, _, _, _, _, _, + + /* q */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, + + /* r */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, _, X, _, _, + _, X, X, _, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + /* s */ + _, _, _, _, _, _, + _, _, _, _, _, _, + _, X, X, X, X, _, + X, _, _, _, _, _, + _, X, X, X, _, _, + _, _, _, _, X, _, + X, X, X, X, _, _, + _, _, _, _, _, _, + + /* t */ + _, _, _, _, _, _, + _, X, _, _, _, _, + X, X, X, X, X, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, X, X, _, + _, _, _, _, _, _, + + /* u */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, _, _, + + /* v */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, _, _, _, _, _, + + /* w */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, X, _, X, _, + X, _, X, _, X, _, + _, X, _, X, _, _, + _, X, _, X, _, _, + _, _, _, _, _, _, + + /* x */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + _, X, _, X, _, _, + _, _, X, _, _, _, + _, X, _, X, _, _, + X, _, _, _, X, _, + _, _, _, _, _, _, + + /* y */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, _, _, _, X, _, + X, _, _, _, X, _, + X, _, _, X, X, _, + _, X, X, _, X, _, + _, _, _, _, X, _, + _, X, X, X, _, _, + + /* z */ + _, _, _, _, _, _, + _, _, _, _, _, _, + X, X, X, X, X, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + X, X, X, X, X, _, + _, _, _, _, _, _, + + /* { */ + _, _, X, X, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, X, _, _, _, _, + _, X, _, _, _, _, + _, _, X, X, _, _, + + /* | */ + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + + /* } */ + _, X, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + _, _, _, X, _, _, + _, _, _, X, _, _, + _, X, X, _, _, _, + + /* ~ */ + _, _, _, _, _, X, + _, _, _, _, _, _, + _, _, X, _, _, X, + _, X, _, X, _, X, + _, X, _, _, X, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, _, + + /* Custom characters */ + /* Selection */ + _, X, _, _, _, _, + _, X, X, _, _, _, + _, X, X, X, _, _, + _, X, X, X, X, _, + _, X, X, X, _, _, + _, X, X, _, _, _, + _, X, _, _, _, _, + _, _, _, _, _, _, + + + /* CTRL symbol */ + _, X, X, _, _, X, + X, _, _, X, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, _, _, _, + X, _, _, X, _, _, + _, X, X, _, _, _, + _, _, _, _, _, _, + + X, X, _, X, X, X, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, _, _, X, X, X, + X, _, _, X, _, X, + X, _, _, X, _, _, + _, _, _, _, _, _, + + _, _, X, _, _, _, + X, _, X, _, _, _, + X, _, X, _, _, _, + X, _, X, _, _, _, + _, _, X, _, _, _, + _, _, X, _, _, _, + X, _, X, X, X, X, + _, _, _, _, _, _, + + /* Shift symbol */ + _, _, X, X, _, _, + _, X, X, X, X, _, + X, X, X, X, X, X, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, X, X, X, X, _, + _, _, _, _, _, _, + + /* Cmd symbol */ + + _, _, _, _, _, X, + _, _, _, _, X, _, + _, _, _, _, _, X, + _, _, _, _, _, _, + _, _, _, _, _, _, + _, _, _, _, _, X, + _, _, _, _, X, _, + _, _, _, _, _, X, + + _, _, _, _, X, _, + X, _, _, X, _, X, + X, X, X, X, X, _, + X, _, _, X, _, _, + X, _, _, X, _, _, + X, X, X, X, X, _, + X, _, _, X, _, X, + _, _, _, _, X, _, + + /* Left Arrow */ + _, _, _, _, X, _, + _, _, _, X, X, _, + _, _, X, X, X, _, + _, X, X, X, X, _, + _, _, X, X, X, _, + _, _, _, X, X, _, + _, _, _, _, X, _, + _, _, _, _, _, _, +}; + +const uint8_t font_max = sizeof(font) / GLYPH_HEIGHT / GLYPH_WIDTH + ' '; diff --git a/SDL/font.h b/SDL/font.h new file mode 100644 index 00000000..21753a8d --- /dev/null +++ b/SDL/font.h @@ -0,0 +1,16 @@ +#ifndef font_h +#define font_h + +#include +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 */ + diff --git a/SDL/gui.c b/SDL/gui.c new file mode 100644 index 00000000..ba2cb735 --- /dev/null +++ b/SDL/gui.c @@ -0,0 +1,903 @@ +#include +#include +#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)); +} diff --git a/SDL/gui.h b/SDL/gui.h new file mode 100644 index 00000000..b68d0d1b --- /dev/null +++ b/SDL/gui.h @@ -0,0 +1,57 @@ +#ifndef gui_h +#define gui_h + +#include +#include +#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 diff --git a/SDL/main.c b/SDL/main.c index 5bad3c1f..7db2df6c 100755 --- a/SDL/main.c +++ b/SDL/main.c @@ -2,8 +2,9 @@ #include #include #include - #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; } diff --git a/SDL/opengl_compat.c b/SDL/opengl_compat.c new file mode 100644 index 00000000..aed2a769 --- /dev/null +++ b/SDL/opengl_compat.c @@ -0,0 +1,34 @@ +#define GL_GLEXT_PROTOTYPES +#include + +#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 diff --git a/SDL/opengl_compat.h b/SDL/opengl_compat.h new file mode 100644 index 00000000..9fd1ca92 --- /dev/null +++ b/SDL/opengl_compat.h @@ -0,0 +1,45 @@ +#ifndef opengl_compat_h +#define opengl_compat_h + +#define GL_GLEXT_PROTOTYPES +#include +#include + +#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 */ diff --git a/SDL/shader.c b/SDL/shader.c new file mode 100644 index 00000000..a34866e7 --- /dev/null +++ b/SDL/shader.c @@ -0,0 +1,199 @@ +#include +#include +#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); + +} diff --git a/SDL/shader.h b/SDL/shader.h new file mode 100644 index 00000000..20baf765 --- /dev/null +++ b/SDL/shader.h @@ -0,0 +1,23 @@ +#ifndef shader_h +#define shader_h +#include "opengl_compat.h" +#include + +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 */ diff --git a/SDL/utils.c b/SDL/utils.c index d14beed1..539e3518 100644 --- a/SDL/utils.c +++ b/SDL/utils.c @@ -1,62 +1,24 @@ +#include #include #include -#ifdef __APPLE__ -#include -#endif -#ifdef _WIN32 -#include -#include -#endif -#ifdef __linux__ -#include -#include -#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; } diff --git a/Shaders/MasterShader.fsh b/Shaders/MasterShader.fsh index 642026ee..41030565 100644 --- a/Shaders/MasterShader.fsh +++ b/Shaders/MasterShader.fsh @@ -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); } diff --git a/Windows/inttypes.h b/Windows/inttypes.h new file mode 100755 index 00000000..9a6118bd --- /dev/null +++ b/Windows/inttypes.h @@ -0,0 +1 @@ +#include diff --git a/libretro/libretro.c b/libretro/libretro.c index 7320a7ac..3fcf77d8 100644 --- a/libretro/libretro.c +++ b/libretro/libretro.c @@ -14,7 +14,7 @@ #define snprintf _snprintf #endif -#define SAMEBOY_CORE_VERSION "0.9" +#define SAMEBOY_CORE_VERSION "0.10" #include #include "libretro.h"