GameBoy Camera support in Cocoa
This commit is contained in:
parent
479a64dca6
commit
b50b38c78a
109
Cocoa/Document.m
109
Cocoa/Document.m
@ -1,3 +1,4 @@
|
|||||||
|
#include <AVFoundation/AVFoundation.h>
|
||||||
#include <CoreAudio/CoreAudio.h>
|
#include <CoreAudio/CoreAudio.h>
|
||||||
#include "GBAudioClient.h"
|
#include "GBAudioClient.h"
|
||||||
#include "Document.h"
|
#include "Document.h"
|
||||||
@ -5,6 +6,7 @@
|
|||||||
#include "gb.h"
|
#include "gb.h"
|
||||||
#include "debugger.h"
|
#include "debugger.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "camera.h"
|
||||||
#include "HexFiend/HexFiend.h"
|
#include "HexFiend/HexFiend.h"
|
||||||
#include "GBMemoryByteArray.h"
|
#include "GBMemoryByteArray.h"
|
||||||
|
|
||||||
@ -23,6 +25,11 @@
|
|||||||
|
|
||||||
NSString *lastConsoleInput;
|
NSString *lastConsoleInput;
|
||||||
HFLineCountingRepresenter *lineRep;
|
HFLineCountingRepresenter *lineRep;
|
||||||
|
|
||||||
|
CVImageBufferRef cameraImage;
|
||||||
|
AVCaptureSession *cameraSession;
|
||||||
|
AVCaptureConnection *cameraConnection;
|
||||||
|
AVCaptureStillImageOutput *cameraOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property GBAudioClient *audioClient;
|
@property GBAudioClient *audioClient;
|
||||||
@ -30,6 +37,8 @@
|
|||||||
- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes;
|
- (void) log: (const char *) log withAttributes: (GB_log_attributes) attributes;
|
||||||
- (const char *) getDebuggerInput;
|
- (const char *) getDebuggerInput;
|
||||||
- (const char *) getAsyncDebuggerInput;
|
- (const char *) getAsyncDebuggerInput;
|
||||||
|
- (void) cameraRequestUpdate;
|
||||||
|
- (uint8_t) cameraGetPixelAtX:(uint8_t)x andY:(uint8_t)y;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void vblank(GB_gameboy_t *gb)
|
static void vblank(GB_gameboy_t *gb)
|
||||||
@ -62,6 +71,18 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
|||||||
return (r << 0) | (g << 8) | (b << 16);
|
return (r << 0) | (g << 8) | (b << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cameraRequestUpdate(GB_gameboy_t *gb)
|
||||||
|
{
|
||||||
|
Document *self = (__bridge Document *)(gb->user_data);
|
||||||
|
[self cameraRequestUpdate];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t cameraGetPixel(GB_gameboy_t *gb, uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
Document *self = (__bridge Document *)(gb->user_data);
|
||||||
|
return [self cameraGetPixelAtX:x andY:y];
|
||||||
|
}
|
||||||
|
|
||||||
@implementation Document
|
@implementation Document
|
||||||
{
|
{
|
||||||
GB_gameboy_t gb;
|
GB_gameboy_t gb;
|
||||||
@ -91,23 +112,26 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
|||||||
{
|
{
|
||||||
GB_init(&gb);
|
GB_init(&gb);
|
||||||
GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]);
|
GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"dmg_boot" ofType:@"bin"] UTF8String]);
|
||||||
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
[self initCommon];
|
||||||
GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
|
|
||||||
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
|
|
||||||
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
|
|
||||||
GB_set_rgb_encode_callback(&gb, rgbEncode);
|
|
||||||
gb.user_data = (__bridge void *)(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) initCGB
|
- (void) initCGB
|
||||||
{
|
{
|
||||||
GB_init_cgb(&gb);
|
GB_init_cgb(&gb);
|
||||||
GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]);
|
GB_load_boot_rom(&gb, [[[NSBundle mainBundle] pathForResource:@"cgb_boot" ofType:@"bin"] UTF8String]);
|
||||||
|
[self initCommon];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) initCommon
|
||||||
|
{
|
||||||
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
GB_set_vblank_callback(&gb, (GB_vblank_callback_t) vblank);
|
||||||
GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
|
GB_set_log_callback(&gb, (GB_log_callback_t) consoleLog);
|
||||||
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
|
GB_set_input_callback(&gb, (GB_input_callback_t) consoleInput);
|
||||||
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
|
GB_set_async_input_callback(&gb, (GB_input_callback_t) asyncConsoleInput);
|
||||||
GB_set_rgb_encode_callback(&gb, rgbEncode);
|
GB_set_rgb_encode_callback(&gb, rgbEncode);
|
||||||
|
GB_set_camera_get_pixel_callback(&gb, cameraGetPixel);
|
||||||
|
GB_set_camera_update_request_callback(&gb, cameraRequestUpdate);
|
||||||
gb.user_data = (__bridge void *)(self);
|
gb.user_data = (__bridge void *)(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +246,9 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
|||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
GB_free(&gb);
|
GB_free(&gb);
|
||||||
|
if (cameraImage) {
|
||||||
|
CVBufferRelease(cameraImage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
|
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
|
||||||
@ -680,4 +707,74 @@ static uint32_t rgbEncode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)cameraRequestUpdate
|
||||||
|
{
|
||||||
|
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
@try {
|
||||||
|
if (!cameraSession) {
|
||||||
|
NSError *error;
|
||||||
|
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
|
||||||
|
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice: device error: &error];
|
||||||
|
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions([[[device formats] firstObject] formatDescription]);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
GB_camera_updated(&gb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraOutput = [[AVCaptureStillImageOutput alloc] init];
|
||||||
|
/* Greyscale is not widely supported, so we use YUV, whose first element is the brightness. */
|
||||||
|
[cameraOutput setOutputSettings: @{(id)kCVPixelBufferPixelFormatTypeKey: @(kYUVSPixelFormat),
|
||||||
|
(id)kCVPixelBufferWidthKey: @(MAX(128, 112 * dimensions.width / dimensions.height)),
|
||||||
|
(id)kCVPixelBufferHeightKey: @(MAX(112, 128 * dimensions.height / dimensions.width)),}];
|
||||||
|
|
||||||
|
|
||||||
|
cameraSession = [AVCaptureSession new];
|
||||||
|
cameraSession.sessionPreset = AVCaptureSessionPresetPhoto;
|
||||||
|
|
||||||
|
[cameraSession addInput: input];
|
||||||
|
[cameraSession addOutput: cameraOutput];
|
||||||
|
/* ARC will stop the session when the window is closed. */
|
||||||
|
[cameraSession startRunning];
|
||||||
|
cameraConnection = [cameraOutput connectionWithMediaType: AVMediaTypeVideo];
|
||||||
|
}
|
||||||
|
|
||||||
|
[cameraOutput captureStillImageAsynchronouslyFromConnection: cameraConnection completionHandler: ^(CMSampleBufferRef sampleBuffer, NSError *error) {
|
||||||
|
if (error) {
|
||||||
|
GB_camera_updated(&gb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (cameraImage) {
|
||||||
|
CVBufferRelease(cameraImage);
|
||||||
|
cameraImage = NULL;
|
||||||
|
}
|
||||||
|
cameraImage = CVBufferRetain(CMSampleBufferGetImageBuffer(sampleBuffer));
|
||||||
|
/* We only need the actual buffer, no need to ever unlock it. */
|
||||||
|
CVPixelBufferLockBaseAddress(cameraImage, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
GB_camera_updated(&gb);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
@catch (NSException *exception) {
|
||||||
|
/* I have not tested camera support on many devices, so we catch exceptions just in case. */
|
||||||
|
GB_camera_updated(&gb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- (uint8_t)cameraGetPixelAtX:(uint8_t)x andY:(uint8_t) y
|
||||||
|
{
|
||||||
|
if (!cameraImage) {
|
||||||
|
return rand();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(cameraImage);
|
||||||
|
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(cameraImage);
|
||||||
|
uint8_t offsetX = (CVPixelBufferGetWidth(cameraImage) - 128) / 2;
|
||||||
|
uint8_t offsetY = (CVPixelBufferGetHeight(cameraImage) - 112) / 2;
|
||||||
|
uint8_t ret = baseAddress[(x + offsetX) * 2 + (y + offsetY) * bytesPerRow];
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
@end
|
@end
|
2
Makefile
2
Makefile
@ -161,7 +161,7 @@ $(BIN)/Sameboy.app: $(BIN)/Sameboy.app/Contents/MacOS/Sameboy \
|
|||||||
|
|
||||||
$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
|
$(BIN)/Sameboy.app/Contents/MacOS/Sameboy: $(CORE_OBJECTS) $(COCOA_OBJECTS)
|
||||||
-@$(MKDIR) -p $(dir $@)
|
-@$(MKDIR) -p $(dir $@)
|
||||||
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit
|
$(CC) $^ -o $@ $(LDFLAGS) -framework OpenGL -framework AudioUnit -framework AVFoundation -framework CoreVideo -framework CoreMedia
|
||||||
ifeq ($(CONF), release)
|
ifeq ($(CONF), release)
|
||||||
strip $@
|
strip $@
|
||||||
endif
|
endif
|
||||||
|
@ -26,6 +26,7 @@ Features currently supported only with the Cocoa version:
|
|||||||
* Retina display support, allowing a wider range of scaling factors without artifacts
|
* Retina display support, allowing a wider range of scaling factors without artifacts
|
||||||
* Optional frame blending
|
* Optional frame blending
|
||||||
* Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x)
|
* Several [scaling algorithms](SCALING.md) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x)
|
||||||
|
* GameBoy Camera support
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html).
|
SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as most of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) acceptance tests. SameBoy should work with most games and demos, please report any broken ROM. The latest results for SameBoy's automatic tester are available [here](http://htmlpreview.github.io/?https://github.com/LIJI32/SameBoy/blob/automation_results/results.html).
|
||||||
|
Loading…
Reference in New Issue
Block a user