1/* ScummVM - Graphic Adventure Engine 2 * 3 * ScummVM is the legal property of its developers, whose names 4 * are too numerous to list here. Please refer to the COPYRIGHT 5 * file distributed with this source distribution. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * 21 */ 22 23// Disable symbol overrides so that we can use system headers. 24#define FORBIDDEN_SYMBOL_ALLOW_ALL 25 26#include "backends/platform/ios7/ios7_video.h" 27 28#include "backends/platform/ios7/ios7_app_delegate.h" 29 30static int g_needsScreenUpdate = 0; 31 32#if 0 33static long g_lastTick = 0; 34static int g_frames = 0; 35#endif 36 37void printError(const char *error_message) { 38 NSString *messageString = [NSString stringWithUTF8String:error_message]; 39 NSLog(@"%@", messageString); 40 fprintf(stderr, "%s\n", error_message); 41} 42 43#define printOpenGLError() printOglError(__FILE__, __LINE__) 44 45void printOglError(const char *file, int line) { 46 GLenum glErr = glGetError(); 47 while (glErr != GL_NO_ERROR) { 48 Common::String error = Common::String::format("glError: %u (%s: %d)", glErr, file, line); 49 printError(error.c_str()); 50 glErr = glGetError(); 51 } 52} 53 54bool iOS7_isBigDevice() { 55 return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad; 56} 57 58static inline void execute_on_main_thread(void (^block)(void)) { 59 if ([NSThread currentThread] == [NSThread mainThread]) { 60 block(); 61 } 62 else { 63 dispatch_sync(dispatch_get_main_queue(), block); 64 } 65} 66 67void iOS7_updateScreen() { 68 //printf("Mouse: (%i, %i)\n", mouseX, mouseY); 69 if (!g_needsScreenUpdate) { 70 g_needsScreenUpdate = 1; 71 execute_on_main_thread(^{ 72 [[iOS7AppDelegate iPhoneView] updateSurface]; 73 }); 74 } 75} 76 77bool iOS7_fetchEvent(InternalEvent *event) { 78 __block bool fetched; 79 execute_on_main_thread(^{ 80 fetched = [[iOS7AppDelegate iPhoneView] fetchEvent:event]; 81 }); 82 return fetched; 83} 84 85uint getSizeNextPOT(uint size) { 86 if ((size & (size - 1)) || !size) { 87 int log = 0; 88 89 while (size >>= 1) 90 ++log; 91 92 size = (2 << log); 93 } 94 95 return size; 96} 97 98@implementation iPhoneView 99 100+ (Class)layerClass { 101 return [CAEAGLLayer class]; 102} 103 104- (VideoContext *)getVideoContext { 105 return &_videoContext; 106} 107 108- (void)createContext { 109 CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; 110 111 eaglLayer.opaque = YES; 112 eaglLayer.drawableProperties = @{ 113 kEAGLDrawablePropertyRetainedBacking: @NO, 114 kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGB565 115 }; 116 117 _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; 118 119 // In case creating the OpenGL ES context failed, we will error out here. 120 if (_context == nil) { 121 printError("Could not create OpenGL ES context."); 122 abort(); 123 } 124 125 if ([EAGLContext setCurrentContext:_context]) { 126 // glEnableClientState(GL_TEXTURE_COORD_ARRAY); printOpenGLError(); 127 // glEnableClientState(GL_VERTEX_ARRAY); printOpenGLError(); 128 [self setupOpenGL]; 129 } 130} 131 132- (void)setupOpenGL { 133 [self setupFramebuffer]; 134 [self createOverlaySurface]; 135 [self compileShaders]; 136 [self setupVBOs]; 137 [self setupTextures]; 138 139 [self finishGLSetup]; 140} 141 142- (void)finishGLSetup { 143 glViewport(0, 0, _renderBufferWidth, _renderBufferHeight); printOpenGLError(); 144 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); printOpenGLError(); 145 146 glUniform2f(_screenSizeSlot, _renderBufferWidth, _renderBufferHeight); 147 148 glEnable(GL_BLEND); 149 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 150} 151 152- (void)freeOpenGL { 153 [self deleteTextures]; 154 [self deleteVBOs]; 155 [self deleteShaders]; 156 [self deleteFramebuffer]; 157} 158 159- (void)rebuildFrameBuffer { 160 [self deleteFramebuffer]; 161 [self setupFramebuffer]; 162 [self finishGLSetup]; 163} 164 165- (void)setupFramebuffer { 166 glGenRenderbuffers(1, &_viewRenderbuffer); 167 printOpenGLError(); 168 glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer); 169 printOpenGLError(); 170 [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id <EAGLDrawable>) self.layer]; 171 172 glGenFramebuffers(1, &_viewFramebuffer); 173 printOpenGLError(); 174 glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer); 175 printOpenGLError(); 176 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _viewRenderbuffer); 177 printOpenGLError(); 178 179 // Retrieve the render buffer size. This *should* match the frame size, 180 // i.e. g_fullWidth and g_fullHeight. 181 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_renderBufferWidth); 182 printOpenGLError(); 183 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_renderBufferHeight); 184 printOpenGLError(); 185 186 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 187 NSLog(@"Failed to make complete framebuffer object %x.", glCheckFramebufferStatus(GL_FRAMEBUFFER)); 188 return; 189 } 190} 191 192- (void)createOverlaySurface { 193 uint overlayWidth = (uint) MAX(_renderBufferWidth, _renderBufferHeight); 194 uint overlayHeight = (uint) MIN(_renderBufferWidth, _renderBufferHeight); 195 196 _videoContext.overlayWidth = overlayWidth; 197 _videoContext.overlayHeight = overlayHeight; 198 199 uint overlayTextureWidthPOT = getSizeNextPOT(overlayWidth); 200 uint overlayTextureHeightPOT = getSizeNextPOT(overlayHeight); 201 202 // Since the overlay size won't change the whole run, we can 203 // precalculate the texture coordinates for the overlay texture here 204 // and just use it later on. 205 GLfloat u = _videoContext.overlayWidth / (GLfloat) overlayTextureWidthPOT; 206 GLfloat v = _videoContext.overlayHeight / (GLfloat) overlayTextureHeightPOT; 207 _overlayCoords[0].x = 0; _overlayCoords[0].y = 0; _overlayCoords[0].u = 0; _overlayCoords[0].v = 0; 208 _overlayCoords[1].x = 0; _overlayCoords[1].y = 0; _overlayCoords[1].u = u; _overlayCoords[1].v = 0; 209 _overlayCoords[2].x = 0; _overlayCoords[2].y = 0; _overlayCoords[2].u = 0; _overlayCoords[2].v = v; 210 _overlayCoords[3].x = 0; _overlayCoords[3].y = 0; _overlayCoords[3].u = u; _overlayCoords[3].v = v; 211 212 _videoContext.overlayTexture.create((uint16) overlayTextureWidthPOT, (uint16) overlayTextureHeightPOT, Graphics::PixelFormat(2, 5, 5, 5, 1, 11, 6, 1, 0)); 213} 214 215- (void)deleteFramebuffer { 216 glDeleteRenderbuffers(1, &_viewRenderbuffer); 217 glDeleteFramebuffers(1, &_viewFramebuffer); 218} 219 220- (void)setupVBOs { 221 glGenBuffers(1, &_vertexBuffer); 222 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); 223} 224 225- (void)deleteVBOs { 226 glDeleteBuffers(1, &_vertexBuffer); 227} 228 229- (GLuint)compileShader:(const char*)shaderPrg withType:(GLenum)shaderType { 230 GLuint shaderHandle = glCreateShader(shaderType); 231 232 int shaderPrgLength = strlen(shaderPrg); 233 glShaderSource(shaderHandle, 1, &shaderPrg, &shaderPrgLength); 234 235 glCompileShader(shaderHandle); 236 237 GLint compileSuccess; 238 glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); 239 if (compileSuccess == GL_FALSE) { 240 GLchar messages[256]; 241 glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); 242 printError(messages); 243 abort(); 244 } 245 246 return shaderHandle; 247} 248 249- (void)compileShaders { 250 const char *vertexPrg = 251 "uniform vec2 ScreenSize;" 252 "uniform float ShakeX;" 253 "uniform float ShakeY;" 254 "" 255 "attribute vec2 Position;" 256 "attribute vec2 TexCoord;" 257 "" 258 "varying vec4 DestColor;" 259 "varying vec2 o_TexCoord;" 260 "" 261 "void main(void) {" 262 " DestColor = vec4(Position.x, Position.y, 0, 1);" 263 " o_TexCoord = TexCoord;" 264 " gl_Position = vec4(((Position.x + ShakeX) / ScreenSize.x) * 2.0 - 1.0, (1.0 - (Position.y + ShakeY) / ScreenSize.y) * 2.0 - 1.0, 0, 1);" 265 "}"; 266 267 const char *fragmentPrg = 268 "uniform sampler2D Texture;" 269 "" 270 "varying lowp vec4 DestColor;" 271 "varying lowp vec2 o_TexCoord;" 272 "" 273 "void main(void) {" 274 " gl_FragColor = texture2D(Texture, o_TexCoord);" 275 "}"; 276 277 _vertexShader = [self compileShader:vertexPrg withType:GL_VERTEX_SHADER]; 278 _fragmentShader = [self compileShader:fragmentPrg withType:GL_FRAGMENT_SHADER]; 279 280 GLuint programHandle = glCreateProgram(); 281 glAttachShader(programHandle, _vertexShader); 282 glAttachShader(programHandle, _fragmentShader); 283 glLinkProgram(programHandle); 284 285 GLint linkSuccess; 286 glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); 287 if (linkSuccess == GL_FALSE) { 288 printOpenGLError(); 289 abort(); 290 } 291 292 glUseProgram(programHandle); 293 294 _screenSizeSlot = (GLuint) glGetUniformLocation(programHandle, "ScreenSize"); 295 _textureSlot = (GLuint) glGetUniformLocation(programHandle, "Texture"); 296 _shakeXSlot = (GLuint) glGetUniformLocation(programHandle, "ShakeX"); 297 _shakeYSlot = (GLuint) glGetUniformLocation(programHandle, "ShakeY"); 298 299 _positionSlot = (GLuint) glGetAttribLocation(programHandle, "Position"); 300 _textureCoordSlot = (GLuint) glGetAttribLocation(programHandle, "TexCoord"); 301 302 glEnableVertexAttribArray(_positionSlot); 303 glEnableVertexAttribArray(_textureCoordSlot); 304 305 glUniform1i(_textureSlot, 0); printOpenGLError(); 306} 307 308- (void)deleteShaders { 309 glDeleteShader(_vertexShader); 310 glDeleteShader(_fragmentShader); 311} 312 313- (void)setupTextures { 314 glGenTextures(1, &_screenTexture); printOpenGLError(); 315 glGenTextures(1, &_overlayTexture); printOpenGLError(); 316 glGenTextures(1, &_mouseCursorTexture); printOpenGLError(); 317 318 [self setGraphicsMode]; 319} 320 321- (void)deleteTextures { 322 if (_screenTexture) { 323 glDeleteTextures(1, &_screenTexture); printOpenGLError(); 324 _screenTexture = 0; 325 } 326 if (_overlayTexture) { 327 glDeleteTextures(1, &_overlayTexture); printOpenGLError(); 328 _overlayTexture = 0; 329 } 330 if (_mouseCursorTexture) { 331 glDeleteTextures(1, &_mouseCursorTexture); printOpenGLError(); 332 _mouseCursorTexture = 0; 333 } 334} 335 336- (void)setupGestureRecognizers { 337 UIPinchGestureRecognizer *pinchKeyboard = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(keyboardPinch:)]; 338 339 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeRight:)]; 340 swipeRight.direction = UISwipeGestureRecognizerDirectionRight; 341 swipeRight.numberOfTouchesRequired = 2; 342 swipeRight.delaysTouchesBegan = NO; 343 swipeRight.delaysTouchesEnded = NO; 344 345 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeLeft:)]; 346 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft; 347 swipeLeft.numberOfTouchesRequired = 2; 348 swipeLeft.delaysTouchesBegan = NO; 349 swipeLeft.delaysTouchesEnded = NO; 350 351 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeUp:)]; 352 swipeUp.direction = UISwipeGestureRecognizerDirectionUp; 353 swipeUp.numberOfTouchesRequired = 2; 354 swipeUp.delaysTouchesBegan = NO; 355 swipeUp.delaysTouchesEnded = NO; 356 357 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeDown:)]; 358 swipeDown.direction = UISwipeGestureRecognizerDirectionDown; 359 swipeDown.numberOfTouchesRequired = 2; 360 swipeDown.delaysTouchesBegan = NO; 361 swipeDown.delaysTouchesEnded = NO; 362 363 UISwipeGestureRecognizer *swipeRight3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeRight:)]; 364 swipeRight3.direction = UISwipeGestureRecognizerDirectionRight; 365 swipeRight3.numberOfTouchesRequired = 3; 366 swipeRight3.delaysTouchesBegan = NO; 367 swipeRight3.delaysTouchesEnded = NO; 368 369 UISwipeGestureRecognizer *swipeLeft3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeLeft:)]; 370 swipeLeft3.direction = UISwipeGestureRecognizerDirectionLeft; 371 swipeLeft3.numberOfTouchesRequired = 3; 372 swipeLeft3.delaysTouchesBegan = NO; 373 swipeLeft3.delaysTouchesEnded = NO; 374 375 UISwipeGestureRecognizer *swipeUp3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeUp:)]; 376 swipeUp3.direction = UISwipeGestureRecognizerDirectionUp; 377 swipeUp3.numberOfTouchesRequired = 3; 378 swipeUp3.delaysTouchesBegan = NO; 379 swipeUp3.delaysTouchesEnded = NO; 380 381 UISwipeGestureRecognizer *swipeDown3 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(threeFingersSwipeDown:)]; 382 swipeDown3.direction = UISwipeGestureRecognizerDirectionDown; 383 swipeDown3.numberOfTouchesRequired = 3; 384 swipeDown3.delaysTouchesBegan = NO; 385 swipeDown3.delaysTouchesEnded = NO; 386 387 UITapGestureRecognizer *doubleTapTwoFingers = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersDoubleTap:)]; 388 doubleTapTwoFingers.numberOfTapsRequired = 2; 389 doubleTapTwoFingers.numberOfTouchesRequired = 2; 390 doubleTapTwoFingers.delaysTouchesBegan = NO; 391 doubleTapTwoFingers.delaysTouchesEnded = NO; 392 393 [self addGestureRecognizer:pinchKeyboard]; 394 [self addGestureRecognizer:swipeRight]; 395 [self addGestureRecognizer:swipeLeft]; 396 [self addGestureRecognizer:swipeUp]; 397 [self addGestureRecognizer:swipeDown]; 398 [self addGestureRecognizer:swipeRight3]; 399 [self addGestureRecognizer:swipeLeft3]; 400 [self addGestureRecognizer:swipeUp3]; 401 [self addGestureRecognizer:swipeDown3]; 402 [self addGestureRecognizer:doubleTapTwoFingers]; 403 404 [pinchKeyboard release]; 405 [swipeRight release]; 406 [swipeLeft release]; 407 [swipeUp release]; 408 [swipeDown release]; 409 [swipeRight3 release]; 410 [swipeLeft3 release]; 411 [swipeUp3 release]; 412 [swipeDown3 release]; 413 [doubleTapTwoFingers release]; 414} 415 416- (id)initWithFrame:(struct CGRect)frame { 417 self = [super initWithFrame: frame]; 418 419 _backgroundSaveStateTask = UIBackgroundTaskInvalid; 420 421 [self setupGestureRecognizers]; 422 423 [self setContentScaleFactor:[[UIScreen mainScreen] scale]]; 424 425 _keyboardView = nil; 426 _keyboardVisible = NO; 427 _screenTexture = 0; 428 _overlayTexture = 0; 429 _mouseCursorTexture = 0; 430 431 _scaledShakeXOffset = 0; 432 _scaledShakeYOffset = 0; 433 434 _firstTouch = NULL; 435 _secondTouch = NULL; 436 437 _eventLock = [[NSLock alloc] init]; 438 439 memset(_gameScreenCoords, 0, sizeof(GLVertex) * 4); 440 memset(_overlayCoords, 0, sizeof(GLVertex) * 4); 441 memset(_mouseCoords, 0, sizeof(GLVertex) * 4); 442 443 // Initialize the OpenGL ES context 444 [self createContext]; 445 446 return self; 447} 448 449- (void)dealloc { 450 [_keyboardView release]; 451 452 _videoContext.screenTexture.free(); 453 _videoContext.overlayTexture.free(); 454 _videoContext.mouseTexture.free(); 455 456 [_eventLock release]; 457 [super dealloc]; 458} 459 460- (void)setFilterModeForTexture:(GLuint)tex { 461 if (!tex) 462 return; 463 464 glActiveTexture(GL_TEXTURE0); 465 glBindTexture(GL_TEXTURE_2D, tex); printOpenGLError(); 466 467 GLint filter = _videoContext.filtering ? GL_LINEAR : GL_NEAREST; 468 469 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); printOpenGLError(); 470 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); printOpenGLError(); 471 // We use GL_CLAMP_TO_EDGE here to avoid artifacts when linear filtering 472 // is used. If we would not use this for example the cursor in Loom would 473 // have a line/border artifact on the right side of the covered rect. 474 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); printOpenGLError(); 475 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); printOpenGLError(); 476} 477 478- (void)setGraphicsMode { 479 [self setFilterModeForTexture:_screenTexture]; 480 [self setFilterModeForTexture:_overlayTexture]; 481 [self setFilterModeForTexture:_mouseCursorTexture]; 482} 483 484- (void)updateSurface { 485 if (!g_needsScreenUpdate) { 486 return; 487 } 488 g_needsScreenUpdate = 0; 489 490 glClear(GL_COLOR_BUFFER_BIT); printOpenGLError(); 491 492 [self updateMainSurface]; 493 494 if (_videoContext.overlayVisible) 495 [self updateOverlaySurface]; 496 497 if (_videoContext.mouseIsVisible) 498 [self updateMouseSurface]; 499 500 [_context presentRenderbuffer:GL_RENDERBUFFER]; 501 glFinish(); 502} 503 504- (void)notifyMouseMove { 505 const GLint mouseX = (GLint)(_videoContext.mouseX * _mouseScaleX) - _mouseHotspotX; 506 const GLint mouseY = (GLint)(_videoContext.mouseY * _mouseScaleY) - _mouseHotspotY; 507 508 _mouseCoords[0].x = _mouseCoords[2].x = mouseX; 509 _mouseCoords[0].y = _mouseCoords[1].y = mouseY; 510 _mouseCoords[1].x = _mouseCoords[3].x = mouseX + _mouseWidth; 511 _mouseCoords[2].y = _mouseCoords[3].y = mouseY + _mouseHeight; 512} 513 514- (void)updateMouseCursorScaling { 515 CGRect *rect; 516 int maxWidth, maxHeight; 517 518 if (!_videoContext.overlayVisible) { 519 rect = &_gameScreenRect; 520 maxWidth = _videoContext.screenWidth; 521 maxHeight = _videoContext.screenHeight; 522 } else { 523 rect = &_overlayRect; 524 maxWidth = _videoContext.overlayWidth; 525 maxHeight = _videoContext.overlayHeight; 526 } 527 528 if (!maxWidth || !maxHeight) { 529 printf("WARNING: updateMouseCursorScaling called when screen was not ready (%d)!\n", _videoContext.overlayVisible); 530 return; 531 } 532 533 _mouseScaleX = CGRectGetWidth(*rect) / (GLfloat)maxWidth; 534 _mouseScaleY = CGRectGetHeight(*rect) / (GLfloat)maxHeight; 535 536 _mouseWidth = (GLint)(_videoContext.mouseWidth * _mouseScaleX); 537 _mouseHeight = (GLint)(_videoContext.mouseHeight * _mouseScaleY); 538 539 _mouseHotspotX = (GLint)(_videoContext.mouseHotspotX * _mouseScaleX); 540 _mouseHotspotY = (GLint)(_videoContext.mouseHotspotY * _mouseScaleY); 541 542 // We subtract the screen offset to the hotspot here to simplify the 543 // screen offset handling in the mouse code. Note the subtraction here 544 // makes sure that the offset actually gets added to the mouse position, 545 // since the hotspot offset is substracted from the position. 546 _mouseHotspotX -= (GLint)CGRectGetMinX(*rect); 547 _mouseHotspotY -= (GLint)CGRectGetMinY(*rect); 548 549 // FIXME: For now we also adapt the mouse position here. In reality we 550 // would be better off to also adjust the event position when switching 551 // from overlay to game screen or vica versa. 552 [self notifyMouseMove]; 553} 554 555- (void)updateMouseCursor { 556 [self updateMouseCursorScaling]; 557 558 _mouseCoords[1].u = _mouseCoords[3].u = (_videoContext.mouseWidth - 1) / (GLfloat)_videoContext.mouseTexture.w; 559 _mouseCoords[2].v = _mouseCoords[3].v = (_videoContext.mouseHeight - 1) / (GLfloat)_videoContext.mouseTexture.h; 560 561 [self setFilterModeForTexture:_mouseCursorTexture]; 562 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _videoContext.mouseTexture.w, _videoContext.mouseTexture.h, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _videoContext.mouseTexture.getPixels()); printOpenGLError(); 563} 564 565- (void)updateMainSurface { 566 glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _gameScreenCoords, GL_STATIC_DRAW); 567 glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); 568 glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); 569 570 [self setFilterModeForTexture:_screenTexture]; 571 572 // Unfortunately we have to update the whole texture every frame, since glTexSubImage2D is actually slower in all cases 573 // due to the iPhone internals having to convert the whole texture back from its internal format when used. 574 // In the future we could use several tiled textures instead. 575 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _videoContext.screenTexture.w, _videoContext.screenTexture.h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, _videoContext.screenTexture.getPixels()); printOpenGLError(); 576 577 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); 578} 579 580- (void)updateOverlaySurface { 581 glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _overlayCoords, GL_STATIC_DRAW); 582 glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); 583 glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); 584 585 [self setFilterModeForTexture:_overlayTexture]; 586 587 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _videoContext.overlayTexture.w, _videoContext.overlayTexture.h, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _videoContext.overlayTexture.getPixels()); printOpenGLError(); 588 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); 589} 590 591- (void)updateMouseSurface { 592 glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _mouseCoords, GL_STATIC_DRAW); 593 glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0); 594 glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2)); 595 596 glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError(); 597 598 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); 599} 600 601- (void)createScreenTexture { 602 const uint screenTexWidth = getSizeNextPOT(_videoContext.screenWidth); 603 const uint screenTexHeight = getSizeNextPOT(_videoContext.screenHeight); 604 605 _gameScreenCoords[1].u = _gameScreenCoords[3].u = _videoContext.screenWidth / (GLfloat)screenTexWidth; 606 _gameScreenCoords[2].v = _gameScreenCoords[3].v = _videoContext.screenHeight / (GLfloat)screenTexHeight; 607 608 _videoContext.screenTexture.create((uint16) screenTexWidth, (uint16) screenTexHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); 609} 610 611- (void)initSurface { 612 if (_context) { 613 [self rebuildFrameBuffer]; 614 } 615 616 BOOL isLandscape = (self.bounds.size.width > self.bounds.size.height); // UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]); 617 618 int screenWidth, screenHeight; 619 if (isLandscape) { 620 screenWidth = MAX(_renderBufferWidth, _renderBufferHeight); 621 screenHeight = MIN(_renderBufferWidth, _renderBufferHeight); 622 } 623 else { 624 screenWidth = MIN(_renderBufferWidth, _renderBufferHeight); 625 screenHeight = MAX(_renderBufferWidth, _renderBufferHeight); 626 } 627 628 if (_keyboardView == nil) { 629 _keyboardView = [[SoftKeyboard alloc] initWithFrame:CGRectZero]; 630 [_keyboardView setInputDelegate:self]; 631 [self addSubview:[_keyboardView inputView]]; 632 [self addSubview: _keyboardView]; 633 [self showKeyboard]; 634 } 635 636 glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer); printOpenGLError(); 637 638 [self clearColorBuffer]; 639 640 GLfloat adjustedWidth = _videoContext.screenWidth; 641 GLfloat adjustedHeight = _videoContext.screenHeight; 642 if (_videoContext.asprectRatioCorrection) { 643 if (_videoContext.screenWidth == 320 && _videoContext.screenHeight == 200) 644 adjustedHeight = 240; 645 else if (_videoContext.screenWidth == 640 && _videoContext.screenHeight == 400) 646 adjustedHeight = 480; 647 } 648 649 float overlayPortraitRatio; 650 651 if (isLandscape) { 652 GLfloat gameScreenRatio = adjustedWidth / adjustedHeight; 653 GLfloat screenRatio = (GLfloat)screenWidth / (GLfloat)screenHeight; 654 655 // These are the width/height according to the portrait layout! 656 int rectWidth, rectHeight; 657 int xOffset, yOffset; 658 659 if (gameScreenRatio < screenRatio) { 660 // When the game screen ratio is less than the screen ratio 661 // we need to scale the width, since the game screen was higher 662 // compared to the width than our output screen is. 663 rectWidth = (int)(screenHeight * gameScreenRatio); 664 rectHeight = screenHeight; 665 xOffset = (screenWidth - rectWidth) / 2; 666 yOffset = 0; 667 } else { 668 // When the game screen ratio is bigger than the screen ratio 669 // we need to scale the height, since the game screen was wider 670 // compared to the height than our output screen is. 671 rectWidth = screenWidth; 672 rectHeight = (int)(screenWidth / gameScreenRatio); 673 xOffset = 0; 674 yOffset = (screenHeight - rectHeight) / 2; 675 } 676 677 //printf("Rect: %i, %i, %i, %i\n", xOffset, yOffset, rectWidth, rectHeight); 678 _gameScreenRect = CGRectMake(xOffset, yOffset, rectWidth, rectHeight); 679 overlayPortraitRatio = 1.0f; 680 } else { 681 GLfloat ratio = adjustedHeight / adjustedWidth; 682 int height = (int)(screenWidth * ratio); 683 //printf("Making rect (%u, %u)\n", screenWidth, height); 684 685 _gameScreenRect = CGRectMake(0, 0, screenWidth, height); 686 687 overlayPortraitRatio = (_videoContext.overlayHeight * ratio) / _videoContext.overlayWidth; 688 } 689 _overlayRect = CGRectMake(0, 0, screenWidth, screenHeight * overlayPortraitRatio); 690 691 _gameScreenCoords[0].x = _gameScreenCoords[2].x = CGRectGetMinX(_gameScreenRect); 692 _gameScreenCoords[0].y = _gameScreenCoords[1].y = CGRectGetMinY(_gameScreenRect); 693 _gameScreenCoords[1].x = _gameScreenCoords[3].x = CGRectGetMaxX(_gameScreenRect); 694 _gameScreenCoords[2].y = _gameScreenCoords[3].y = CGRectGetMaxY(_gameScreenRect); 695 696 _overlayCoords[1].x = _overlayCoords[3].x = CGRectGetMaxX(_overlayRect); 697 _overlayCoords[2].y = _overlayCoords[3].y = CGRectGetMaxY(_overlayRect); 698 699 [self setViewTransformation]; 700 [self updateMouseCursorScaling]; 701 [self adjustViewFrameForSafeArea]; 702} 703 704#ifndef __has_builtin 705#define __has_builtin(x) 0 706#endif 707 708-(void)adjustViewFrameForSafeArea { 709 // The code below does not quite compile with SDKs older than 11.0. 710 // warning: instance method '-safeAreaInsets' not found (return type defaults to 'id') 711 // error: no viable conversion from 'id' to 'UIEdgeInsets' 712 // So for now disable this code when compiled with an older SDK, which means it is only 713 // available when running on iOS 11+ if it has been compiled on iOS 11+ 714#ifdef __IPHONE_11_0 715#if __has_builtin(__builtin_available) 716 if ( @available(iOS 11,*) ) { 717#else 718 if ( [[[UIApplication sharedApplication] keyWindow] respondsToSelector:@selector(safeAreaInsets)] ) { 719#endif 720 CGRect screenSize = [[UIScreen mainScreen] bounds]; 721 UIEdgeInsets inset = [[[UIApplication sharedApplication] keyWindow] safeAreaInsets]; 722 UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 723 CGRect newFrame = screenSize; 724 if ( orientation == UIInterfaceOrientationPortrait ) { 725 newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y + inset.top, screenSize.size.width, screenSize.size.height - inset.top); 726 } else if ( orientation == UIInterfaceOrientationLandscapeLeft ) { 727 newFrame = CGRectMake(screenSize.origin.x, screenSize.origin.y, screenSize.size.width - inset.right, screenSize.size.height); 728 } else if ( orientation == UIInterfaceOrientationLandscapeRight ) { 729 newFrame = CGRectMake(screenSize.origin.x + inset.left, screenSize.origin.y, screenSize.size.width - inset.left, screenSize.size.height); 730 } 731 self.frame = newFrame; 732 } 733#endif 734} 735 736- (void)setViewTransformation { 737 // Scale the shake offset according to the overlay size. We need this to 738 // adjust the overlay mouse click coordinates when an offset is set. 739 _scaledShakeXOffset = (int)(_videoContext.shakeXOffset / (GLfloat)_videoContext.screenWidth * CGRectGetWidth(_overlayRect)); 740 _scaledShakeYOffset = (int)(_videoContext.shakeYOffset / (GLfloat)_videoContext.screenHeight * CGRectGetHeight(_overlayRect)); 741 742 glUniform1f(_shakeXSlot, _scaledShakeXOffset); 743 glUniform1f(_shakeYSlot, _scaledShakeYOffset); 744} 745 746- (void)clearColorBuffer { 747 // The color buffer is triple-buffered, so we clear it multiple times right away to avid doing any glClears later. 748 int clearCount = 5; 749 while (clearCount-- > 0) { 750 glClear(GL_COLOR_BUFFER_BIT); printOpenGLError(); 751 [_context presentRenderbuffer:GL_RENDERBUFFER]; 752 glFinish(); 753 } 754} 755 756- (void)addEvent:(InternalEvent)event { 757 [_eventLock lock]; 758 _events.push_back(event); 759 [_eventLock unlock]; 760} 761 762- (bool)fetchEvent:(InternalEvent *)event { 763 [_eventLock lock]; 764 if (_events.empty()) { 765 [_eventLock unlock]; 766 return false; 767 } 768 769 *event = *_events.begin(); 770 _events.pop_front(); 771 [_eventLock unlock]; 772 return true; 773} 774 775- (bool)getMouseCoords:(CGPoint)point eventX:(int *)x eventY:(int *)y { 776 // We scale the input according to our scale factor to get actual screen 777 // coordinates. 778 point.x *= self.contentScaleFactor; 779 point.y *= self.contentScaleFactor; 780 781 CGRect *area; 782 int width, height, offsetX, offsetY; 783 if (_videoContext.overlayVisible) { 784 area = &_overlayRect; 785 width = _videoContext.overlayWidth; 786 height = _videoContext.overlayHeight; 787 offsetX = _scaledShakeXOffset; 788 offsetY = _scaledShakeYOffset; 789 } else { 790 area = &_gameScreenRect; 791 width = _videoContext.screenWidth; 792 height = _videoContext.screenHeight; 793 offsetX = _videoContext.shakeXOffset; 794 offsetY = _videoContext.shakeYOffset; 795 } 796 797 point.x = (point.x - CGRectGetMinX(*area)) / CGRectGetWidth(*area); 798 point.y = (point.y - CGRectGetMinY(*area)) / CGRectGetHeight(*area); 799 800 *x = (int)(point.x * width + offsetX); 801 // offsetY describes the translation of the screen in the upward direction, 802 // thus we need to add it here. 803 *y = (int)(point.y * height + offsetY); 804 805 if (!iOS7_touchpadModeEnabled()) { 806 // Clip coordinates 807 if (*x < 0 || *x > width || *y < 0 || *y > height) 808 return false; 809 } 810 811 return true; 812} 813 814- (void)deviceOrientationChanged:(UIDeviceOrientation)orientation { 815 [self addEvent:InternalEvent(kInputOrientationChanged, orientation, 0)]; 816 817 BOOL isLandscape = (self.bounds.size.width > self.bounds.size.height); 818 if (isLandscape) { 819 [self hideKeyboard]; 820 } else { 821 [self showKeyboard]; 822 } 823} 824 825- (void)showKeyboard { 826 [_keyboardView showKeyboard]; 827 _keyboardVisible = YES; 828} 829 830- (void)hideKeyboard { 831 [_keyboardView hideKeyboard]; 832 _keyboardVisible = NO; 833} 834 835- (BOOL)isKeyboardShown { 836 return _keyboardVisible; 837} 838 839- (UITouch *)secondTouchOtherTouchThan:(UITouch *)touch in:(NSSet *)set { 840 NSArray *all = [set allObjects]; 841 for (UITouch *t in all) { 842 if (t != touch) { 843 return t; 844 } 845 } 846 return nil; 847} 848 849- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 850 int x, y; 851 852 NSSet *allTouches = [event allTouches]; 853 if (allTouches.count == 1) { 854 _firstTouch = [allTouches anyObject]; 855 CGPoint point = [_firstTouch locationInView:self]; 856 if (![self getMouseCoords:point eventX:&x eventY:&y]) 857 return; 858 859 [self addEvent:InternalEvent(kInputMouseDown, x, y)]; 860 } 861 else if (allTouches.count == 2) { 862 _secondTouch = [self secondTouchOtherTouchThan:_firstTouch in:allTouches]; 863 if (_secondTouch) { 864 CGPoint point = [_secondTouch locationInView:self]; 865 if (![self getMouseCoords:point eventX:&x eventY:&y]) 866 return; 867 868 [self addEvent:InternalEvent(kInputMouseSecondDown, x, y)]; 869 } 870 } 871} 872 873- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { 874 int x, y; 875 876 NSSet *allTouches = [event allTouches]; 877 for (UITouch *touch in allTouches) { 878 if (touch == _firstTouch) { 879 CGPoint point = [touch locationInView:self]; 880 if (![self getMouseCoords:point eventX:&x eventY:&y]) 881 return; 882 883 [self addEvent:InternalEvent(kInputMouseDragged, x, y)]; 884 } else if (touch == _secondTouch) { 885 CGPoint point = [touch locationInView:self]; 886 if (![self getMouseCoords:point eventX:&x eventY:&y]) 887 return; 888 889 [self addEvent:InternalEvent(kInputMouseSecondDragged, x, y)]; 890 } 891 } 892} 893 894- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 895 int x, y; 896 897 NSSet *allTouches = [event allTouches]; 898 if (allTouches.count == 1) { 899 UITouch *touch = [allTouches anyObject]; 900 CGPoint point = [touch locationInView:self]; 901 if (![self getMouseCoords:point eventX:&x eventY:&y]) { 902 return; 903 } 904 905 [self addEvent:InternalEvent(kInputMouseUp, x, y)]; 906 } 907 else if (allTouches.count == 2) { 908 UITouch *touch = [[allTouches allObjects] objectAtIndex:1]; 909 CGPoint point = [touch locationInView:self]; 910 if (![self getMouseCoords:point eventX:&x eventY:&y]) 911 return; 912 913 [self addEvent:InternalEvent(kInputMouseSecondUp, x, y)]; 914 } 915 _firstTouch = nil; 916 _secondTouch = nil; 917} 918 919- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { 920 _firstTouch = nil; 921 _secondTouch = nil; 922} 923 924- (void)keyboardPinch:(UIPinchGestureRecognizer *)recognizer { 925 if ([recognizer scale] < 0.8) 926 [self showKeyboard]; 927 else if ([recognizer scale] > 1.25) 928 [self hideKeyboard]; 929} 930 931- (void)twoFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer { 932 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 2)]; 933} 934 935- (void)twoFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer { 936 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 2)]; 937} 938 939- (void)twoFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer { 940 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 2)]; 941} 942 943- (void)twoFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer { 944 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 2)]; 945} 946 947- (void)threeFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer { 948 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 3)]; 949} 950 951- (void)threeFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer { 952 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 3)]; 953} 954 955- (void)threeFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer { 956 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 3)]; 957} 958 959- (void)threeFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer { 960 [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 3)]; 961} 962 963- (void)twoFingersDoubleTap:(UITapGestureRecognizer *)recognizer { 964 [self addEvent:InternalEvent(kInputTap, kUIViewTapDouble, 2)]; 965} 966 967- (void)handleKeyPress:(unichar)c { 968 if (c == '`') { 969 [self addEvent:InternalEvent(kInputKeyPressed, '\E', 0)]; 970 } else { 971 [self addEvent:InternalEvent(kInputKeyPressed, c, 0)]; 972 } 973} 974 975- (void)handleMainMenuKey { 976 [self addEvent:InternalEvent(kInputMainMenu, 0, 0)]; 977} 978 979- (void)applicationSuspend { 980 [self addEvent:InternalEvent(kInputApplicationSuspended, 0, 0)]; 981} 982 983- (void)applicationResume { 984 [self addEvent:InternalEvent(kInputApplicationResumed, 0, 0)]; 985} 986 987- (void)saveApplicationState { 988 [self addEvent:InternalEvent(kInputApplicationSaveState, 0, 0)]; 989} 990 991- (void)clearApplicationState { 992 [self addEvent:InternalEvent(kInputApplicationClearState, 0, 0)]; 993} 994 995- (void)restoreApplicationState { 996 [self addEvent:InternalEvent(kInputApplicationRestoreState, 0, 0)]; 997} 998 999- (void) beginBackgroundSaveStateTask { 1000 if (_backgroundSaveStateTask == UIBackgroundTaskInvalid) { 1001 _backgroundSaveStateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ 1002 [self endBackgroundSaveStateTask]; 1003 }]; 1004 } 1005} 1006 1007- (void) endBackgroundSaveStateTask { 1008 if (_backgroundSaveStateTask != UIBackgroundTaskInvalid) { 1009 [[UIApplication sharedApplication] endBackgroundTask: _backgroundSaveStateTask]; 1010 _backgroundSaveStateTask = UIBackgroundTaskInvalid; 1011 } 1012} 1013 1014@end 1015