573 lines
17 KiB
Plaintext
573 lines
17 KiB
Plaintext
//
|
|
// EmulatorViewController.m
|
|
// emulator
|
|
//
|
|
// Created by Karen Tsai (angelXwind) on 2014/3/5.
|
|
// Copyright (c) 2014 Karen Tsai (angelXwind). All rights reserved.
|
|
//
|
|
|
|
#import "EmulatorViewController.h"
|
|
|
|
#import <OpenGLES/ES3/gl.h>
|
|
#import <OpenGLES/ES3/glext.h>
|
|
#import <OpenGLES/EAGL.h>
|
|
|
|
#include "types.h"
|
|
#include "profiler/profiler.h"
|
|
#include "cfg/cfg.h"
|
|
#include "rend/TexCache.h"
|
|
#include "hw/maple/maple_devs.h"
|
|
#include "hw/maple/maple_if.h"
|
|
#import <sys/kdebug_signpost.h>
|
|
|
|
#define USE_CRT_SHADER 0
|
|
|
|
@interface EmulatorViewController () {
|
|
GLuint crtFragmentShader;
|
|
GLuint crtShaderProgram;
|
|
int crtUniform_DisplayRect;
|
|
int crtUniform_EmulatedImage;
|
|
int crtUniform_EmulatedImageSize;
|
|
int crtUniform_FinalRes;
|
|
|
|
GLuint defaultVertexShader;
|
|
}
|
|
|
|
@property (strong, nonatomic) EAGLContext *context;
|
|
@property (strong, nonatomic) GLKBaseEffect *effect;
|
|
|
|
- (void)setupGL;
|
|
- (void)tearDownGL;
|
|
- (void)emuThread;
|
|
|
|
@end
|
|
|
|
//who has time for headers
|
|
extern int screen_width,screen_height;
|
|
bool rend_single_frame();
|
|
bool gles_init();
|
|
extern "C" int reicast_main(int argc, char* argv[]);
|
|
|
|
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_time.h>
|
|
#include <pthread.h>
|
|
|
|
void move_pthread_to_realtime_scheduling_class(pthread_t pthread)
|
|
{
|
|
mach_timebase_info_data_t timebase_info;
|
|
mach_timebase_info(&timebase_info);
|
|
|
|
const uint64_t NANOS_PER_MSEC = 1000000ULL;
|
|
double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
|
|
|
|
thread_time_constraint_policy_data_t policy;
|
|
policy.period = 0;
|
|
policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work
|
|
policy.constraint = (uint32_t)(10 * clock2abs);
|
|
policy.preemptible = FALSE;
|
|
|
|
int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
|
|
THREAD_TIME_CONSTRAINT_POLICY,
|
|
(thread_policy_t)&policy,
|
|
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
|
|
if (kr != KERN_SUCCESS) {
|
|
mach_error("thread_policy_set:", kr);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void MakeCurrentThreadRealTime()
|
|
{
|
|
move_pthread_to_realtime_scheduling_class(pthread_self());
|
|
}
|
|
|
|
@implementation EmulatorViewController
|
|
|
|
-(void)emuThread
|
|
{
|
|
char *Args[3];
|
|
const char *P;
|
|
|
|
P = (const char *)[self.diskImage UTF8String];
|
|
Args[0] = "dc";
|
|
Args[1] = "-config";
|
|
Args[2] = P&&P[0]? (char *)malloc(strlen(P)+32):0;
|
|
|
|
if(Args[2])
|
|
{
|
|
strcpy(Args[2],"config:image=");
|
|
strcat(Args[2],P);
|
|
}
|
|
|
|
MakeCurrentThreadRealTime();
|
|
|
|
reicast_main(Args[2]? 3:1,Args);
|
|
}
|
|
|
|
- (void)viewDidLoad
|
|
{
|
|
[super viewDidLoad];
|
|
|
|
#if !TARGET_OS_TV
|
|
self.controllerView = [[PadViewController alloc] initWithNibName:@"PadViewController" bundle:nil];
|
|
#endif
|
|
|
|
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
|
|
|
// Not sure what this effects in our case without making 2 contexts and swapping
|
|
// [self.context setMultiThreaded:YES];
|
|
|
|
if (!self.context) {
|
|
NSLog(@"Failed to create ES context");
|
|
}
|
|
|
|
self.emuView = (EmulatorView *)self.view;
|
|
// self.emuView.opaque = YES;
|
|
self.emuView.context = self.context;
|
|
self.emuView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
|
|
|
|
// Set preferred refresh rate
|
|
[self setPreferredFramesPerSecond:60];
|
|
|
|
[self.controllerView setControlOutput:self.emuView];
|
|
|
|
self.connectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
if ( GCController.controllers.count ){
|
|
[self toggleHardwareController:YES];
|
|
}
|
|
}];
|
|
self.disconnectObserver = [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidDisconnectNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
|
|
if (GCController.controllers.count == 0) {
|
|
[self toggleHardwareController:NO];
|
|
}
|
|
}];
|
|
|
|
if ([[GCController controllers] count]) {
|
|
[self toggleHardwareController:YES];
|
|
}
|
|
|
|
#if !TARGET_OS_TV
|
|
[self addChildViewController:self.controllerView];
|
|
self.controllerView.view.frame = self.view.bounds;
|
|
self.controllerView.view.translatesAutoresizingMaskIntoConstraints = YES;
|
|
self.controllerView.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth;
|
|
[self.view addSubview:self.controllerView.view];
|
|
[self.controllerView didMoveToParentViewController:self];
|
|
#endif
|
|
|
|
self.iCadeReader = [[iCadeReaderView alloc] init];
|
|
[self.view addSubview:self.iCadeReader];
|
|
self.iCadeReader.delegate = self;
|
|
self.iCadeReader.active = YES;
|
|
|
|
[self setupGL];
|
|
|
|
if (!gles_init())
|
|
die("OPENGL FAILED");
|
|
|
|
#if USE_CRT_SHADER
|
|
[EAGLContext setCurrentContext:self.context];
|
|
defaultVertexShader = [self compileShaderResource:@"default_vertex" ofType:GL_VERTEX_SHADER];
|
|
[self setupCRTShader];
|
|
#endif
|
|
|
|
NSThread* myThread = [[NSThread alloc] initWithTarget:self
|
|
selector:@selector(emuThread)
|
|
object:nil];
|
|
[myThread start]; // Actually create the thread
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (crtShaderProgram > 0)
|
|
{
|
|
glDeleteProgram(crtShaderProgram);
|
|
}
|
|
if (crtFragmentShader > 0)
|
|
{
|
|
glDeleteShader(crtFragmentShader);
|
|
}
|
|
if (defaultVertexShader > 0)
|
|
{
|
|
glDeleteShader(defaultVertexShader);
|
|
}
|
|
|
|
[self tearDownGL];
|
|
|
|
if ([EAGLContext currentContext] == self.context) {
|
|
[EAGLContext setCurrentContext:nil];
|
|
}
|
|
}
|
|
|
|
//- (void)didReceiveMemoryWarning
|
|
//{
|
|
// [super didReceiveMemoryWarning];
|
|
//
|
|
// if ([self isViewLoaded] && ([[self view] window] == nil)) {
|
|
// self.view = nil;
|
|
//
|
|
// [self tearDownGL];
|
|
//
|
|
// if ([EAGLContext currentContext] == self.context) {
|
|
// [EAGLContext setCurrentContext:nil];
|
|
// }
|
|
// self.context = nil;
|
|
// }
|
|
//
|
|
// // Dispose of any resources that can be recreated.
|
|
//}
|
|
|
|
- (void)setupGL
|
|
{
|
|
[EAGLContext setCurrentContext:self.context];
|
|
|
|
}
|
|
|
|
- (void)tearDownGL
|
|
{
|
|
[EAGLContext setCurrentContext:self.context];
|
|
|
|
}
|
|
|
|
#pragma mark - GLKView and GLKViewController delegate methods
|
|
|
|
- (void)update
|
|
{
|
|
|
|
}
|
|
|
|
- (void)toggleHardwareController:(BOOL)useHardware {
|
|
if (useHardware) {
|
|
|
|
[self.controllerView hideController];
|
|
|
|
#if TARGET_OS_TV
|
|
for(GCController*c in GCController.controllers) {
|
|
if ((c.gamepad != nil || c.extendedGamepad != nil) && (c != _gController)) {
|
|
|
|
self.gController = c;
|
|
break;
|
|
}
|
|
}
|
|
#else
|
|
self.gController = [GCController controllers].firstObject;
|
|
#endif
|
|
// TODO: ADd multi player using gController.playerIndex and iterate all controllers
|
|
|
|
if (self.gController.extendedGamepad) {
|
|
[self.gController.extendedGamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_A);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_A);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_B);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_B);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_X);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_X);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_Y);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_Y);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.rightTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed) {
|
|
rt[0] = (255);
|
|
} else {
|
|
rt[0] = (0);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.leftTrigger setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed) {
|
|
lt[0] = (255);
|
|
} else {
|
|
lt[0] = (0);
|
|
}
|
|
}];
|
|
|
|
// Either trigger for start
|
|
[self.gController.extendedGamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_START);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_START);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
kcode[0] &= ~(DC_BTN_START);
|
|
} else {
|
|
kcode[0] |= (DC_BTN_START);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue){
|
|
if (dpad.right.isPressed) {
|
|
kcode[0] &= ~(DC_DPAD_RIGHT);
|
|
} else {
|
|
kcode[0] |= (DC_DPAD_RIGHT);
|
|
}
|
|
if (dpad.left.isPressed) {
|
|
kcode[0] &= ~(DC_DPAD_LEFT);
|
|
} else {
|
|
kcode[0] |= (DC_DPAD_LEFT);
|
|
}
|
|
if (dpad.up.isPressed) {
|
|
kcode[0] &= ~(DC_DPAD_UP);
|
|
} else {
|
|
kcode[0] |= (DC_DPAD_UP);
|
|
}
|
|
if (dpad.down.isPressed) {
|
|
kcode[0] &= ~(DC_DPAD_DOWN);
|
|
} else {
|
|
kcode[0] |= (DC_DPAD_DOWN);
|
|
}
|
|
}];
|
|
[self.gController.extendedGamepad.leftThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value){
|
|
s8 v=(s8)(value*127); //-127 ... + 127 range
|
|
|
|
NSLog(@"Joy X: %i", v);
|
|
joyx[0] = v;
|
|
}];
|
|
[self.gController.extendedGamepad.leftThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value){
|
|
s8 v=(s8)(value*127 * - 1); //-127 ... + 127 range
|
|
|
|
NSLog(@"Joy Y: %i", v);
|
|
joyy[0] = v;
|
|
}];
|
|
|
|
// TODO: Right dpad
|
|
// [self.gController.extendedGamepad.rightThumbstick.xAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value){
|
|
// s8 v=(s8)(value*127); //-127 ... + 127 range
|
|
//
|
|
// NSLog(@"Joy X: %i", v);
|
|
// joyx[0] = v;
|
|
// }];
|
|
// [self.gController.extendedGamepad.rightThumbstick.yAxis setValueChangedHandler:^(GCControllerAxisInput *axis, float value){
|
|
// s8 v=(s8)(value*127 * - 1); //-127 ... + 127 range
|
|
//
|
|
// NSLog(@"Joy Y: %i", v);
|
|
// joyy[0] = v;
|
|
// }];
|
|
}
|
|
else if (self.gController.gamepad) {
|
|
[self.gController.gamepad.buttonA setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_abxy_a];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_abxy_a];
|
|
}
|
|
}];
|
|
[self.gController.gamepad.buttonB setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_abxy_b];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_abxy_b];
|
|
}
|
|
}];
|
|
[self.gController.gamepad.buttonX setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_abxy_x];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_abxy_x];
|
|
}
|
|
}];
|
|
[self.gController.gamepad.buttonY setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed && value >= 0.1) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_abxy_y];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_abxy_y];
|
|
}
|
|
}];
|
|
[self.gController.gamepad.dpad setValueChangedHandler:^(GCControllerDirectionPad *dpad, float xValue, float yValue){
|
|
if (dpad.right.isPressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_dpad_r];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_dpad_r];
|
|
}
|
|
if (dpad.left.isPressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_dpad_l];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_dpad_l];
|
|
}
|
|
if (dpad.up.isPressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_dpad_u];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_dpad_u];
|
|
}
|
|
if (dpad.down.isPressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_dpad_d];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_dpad_d];
|
|
}
|
|
}];
|
|
|
|
[self.gController.gamepad.rightShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_rt];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_rt];
|
|
}
|
|
}];
|
|
|
|
[self.gController.gamepad.leftShoulder setValueChangedHandler:^(GCControllerButtonInput *button, float value, BOOL pressed) {
|
|
if (pressed) {
|
|
[self.emuView handleKeyDown:self.controllerView.img_lt];
|
|
} else {
|
|
[self.emuView handleKeyUp:self.controllerView.img_lt];
|
|
}
|
|
}];
|
|
|
|
//Add controller pause handler here
|
|
}
|
|
} else {
|
|
self.gController = nil;
|
|
[self.controllerView showController:self.view];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
|
|
{
|
|
// kdebug_signpost_start(10, 0, 0, 0, 0);
|
|
screen_width = view.drawableWidth;
|
|
screen_height = view.drawableHeight;
|
|
|
|
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
while(!rend_single_frame()) ;
|
|
|
|
#if USE_CRT_SHADER
|
|
glUseProgram(crtShaderProgram);
|
|
glUniform4f(crtUniform_DisplayRect, 0,0, 320, 640); // TODO: Correct sizes?
|
|
glUniform1i(crtUniform_EmulatedImage, 0);
|
|
glUniform2f(crtUniform_EmulatedImageSize, 320, 640);
|
|
float finalResWidth = view.drawableWidth;
|
|
float finalResHeight = view.drawableHeight;
|
|
glUniform2f(crtUniform_FinalRes, finalResWidth, finalResHeight);
|
|
#endif
|
|
// kdebug_signpost_end(10, 0, 0, 0, 0);
|
|
}
|
|
|
|
/*** CRT Shader borrowed from BrainDX for Provenance app ***/
|
|
- (void)setupCRTShader
|
|
{
|
|
crtFragmentShader = [self compileShaderResource:@"crt_fragment" ofType:GL_FRAGMENT_SHADER];
|
|
crtShaderProgram = [self linkVertexShader:defaultVertexShader withFragmentShader:crtFragmentShader];
|
|
crtUniform_DisplayRect = glGetUniformLocation(crtShaderProgram, "DisplayRect");
|
|
crtUniform_EmulatedImage = glGetUniformLocation(crtShaderProgram, "EmulatedImage");
|
|
crtUniform_EmulatedImageSize = glGetUniformLocation(crtShaderProgram, "EmulatedImageSize");
|
|
crtUniform_FinalRes = glGetUniformLocation(crtShaderProgram, "FinalRes");
|
|
}
|
|
|
|
- (GLuint)compileShaderResource:(NSString*)shaderResourceName ofType:(GLenum)shaderType
|
|
{
|
|
NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderResourceName ofType:@"glsl"];
|
|
if ( shaderPath == NULL )
|
|
{
|
|
NSLog(@"No shader path");
|
|
return 0;
|
|
}
|
|
|
|
NSString* shaderSource = [NSString stringWithContentsOfFile:shaderPath encoding:NSASCIIStringEncoding error:nil];
|
|
if ( shaderSource == NULL )
|
|
{
|
|
NSLog(@"No shader source");
|
|
return 0;
|
|
}
|
|
|
|
const char* shaderSourceCString = [shaderSource cStringUsingEncoding:NSASCIIStringEncoding];
|
|
if ( shaderSourceCString == NULL )
|
|
{
|
|
NSLog(@"Nil shaderSourceCString");
|
|
return 0;
|
|
}
|
|
|
|
GLuint shader = glCreateShader( shaderType );
|
|
if ( shader == 0 )
|
|
{
|
|
NSLog(@"Nil shader");
|
|
return 0;
|
|
}
|
|
|
|
glShaderSource( shader, 1, &shaderSourceCString, NULL );
|
|
glCompileShader( shader );
|
|
|
|
GLint compiled;
|
|
glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled );
|
|
if ( compiled == 0 )
|
|
{
|
|
GLint infoLogLength = 0;
|
|
glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &infoLogLength );
|
|
if ( infoLogLength > 1 )
|
|
{
|
|
char* infoLog = (char*)malloc( infoLogLength );
|
|
glGetShaderInfoLog( shader, infoLogLength, NULL, infoLog );
|
|
printf( "Error compiling shader: %s", infoLog );
|
|
free( infoLog );
|
|
}
|
|
|
|
NSLog(@"Compile fail");
|
|
|
|
glDeleteShader( shader );
|
|
return 0;
|
|
}
|
|
|
|
NSLog(@"Shader success %@", shaderResourceName);
|
|
|
|
return shader;
|
|
}
|
|
|
|
- (GLuint)linkVertexShader:(GLuint)vertexShader withFragmentShader:(GLuint)fragmentShader
|
|
{
|
|
GLuint shaderProgram = glCreateProgram();
|
|
if ( shaderProgram == 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
glAttachShader( shaderProgram, vertexShader );
|
|
glAttachShader( shaderProgram, fragmentShader );
|
|
|
|
glBindAttribLocation( shaderProgram, GLKVertexAttribPosition, "vPosition" );
|
|
glBindAttribLocation( shaderProgram, GLKVertexAttribTexCoord0, "vTexCoord" );
|
|
|
|
glLinkProgram( shaderProgram );
|
|
|
|
GLint linkStatus;
|
|
glGetProgramiv( shaderProgram, GL_LINK_STATUS, &linkStatus );
|
|
if ( linkStatus == 0 )
|
|
{
|
|
GLint infoLogLength = 0;
|
|
glGetProgramiv( shaderProgram, GL_INFO_LOG_LENGTH, &infoLogLength );
|
|
if ( infoLogLength > 1 )
|
|
{
|
|
char* infoLog = (char*)malloc( infoLogLength );
|
|
glGetProgramInfoLog( shaderProgram, infoLogLength, NULL, infoLog );
|
|
printf( "Error linking program: %s", infoLog );
|
|
free( infoLog );
|
|
}
|
|
|
|
glDeleteProgram( shaderProgram );
|
|
return 0;
|
|
}
|
|
|
|
return shaderProgram;
|
|
}
|
|
|
|
@end
|