// // 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 #import #import #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 #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 #include #include 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