aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/ios7/ios7_video.mm
diff options
context:
space:
mode:
Diffstat (limited to 'backends/platform/ios7/ios7_video.mm')
-rw-r--r--backends/platform/ios7/ios7_video.mm1002
1 files changed, 1002 insertions, 0 deletions
diff --git a/backends/platform/ios7/ios7_video.mm b/backends/platform/ios7/ios7_video.mm
new file mode 100644
index 0000000000..2cfc3e05f7
--- /dev/null
+++ b/backends/platform/ios7/ios7_video.mm
@@ -0,0 +1,1002 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "ios7_video.h"
+
+#include "graphics/colormasks.h"
+#include "common/system.h"
+#import "iOS7AppDelegate.h"
+
+static int g_needsScreenUpdate = 0;
+
+#if 0
+static long g_lastTick = 0;
+static int g_frames = 0;
+#endif
+
+#define printOpenGLError() printOglError(__FILE__, __LINE__)
+
+int printOglError(const char *file, int line) {
+ int retCode = 0;
+
+ // returns 1 if an OpenGL error occurred, 0 otherwise.
+ GLenum glErr = glGetError();
+ while (glErr != GL_NO_ERROR) {
+ fprintf(stderr, "glError: %u (%s: %d)\n", glErr, file, line);
+ retCode = 1;
+ glErr = glGetError();
+ }
+ return retCode;
+}
+
+bool iOS7_isBigDevice() {
+ return UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;
+}
+
+void iOS7_updateScreen() {
+ //printf("Mouse: (%i, %i)\n", mouseX, mouseY);
+ if (!g_needsScreenUpdate) {
+ g_needsScreenUpdate = 1;
+ [[iOS7AppDelegate iPhoneView] performSelectorOnMainThread:@selector(updateSurface) withObject:nil waitUntilDone: NO];
+ }
+}
+
+bool iOS7_fetchEvent(InternalEvent *event) {
+ return [[iOS7AppDelegate iPhoneView] fetchEvent:event];
+}
+
+uint getSizeNextPOT(uint size) {
+ if ((size & (size - 1)) || !size) {
+ int log = 0;
+
+ while (size >>= 1)
+ ++log;
+
+ size = (2 << log);
+ }
+
+ return size;
+}
+
+@implementation iPhoneView
+
++ (Class)layerClass {
+ return [CAEAGLLayer class];
+}
+
+- (VideoContext *)getVideoContext {
+ return &_videoContext;
+}
+
+- (void)createContext {
+ CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
+
+ eaglLayer.opaque = YES;
+ eaglLayer.drawableProperties = @{
+ kEAGLDrawablePropertyRetainedBacking: @NO,
+ kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGB565
+ };
+
+ _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+
+ // In case creating the OpenGL ES context failed, we will error out here.
+ if (_context == nil) {
+ fprintf(stderr, "Could not create OpenGL ES context\n");
+ exit(-1);
+ }
+
+ if ([EAGLContext setCurrentContext:_context]) {
+ // glEnableClientState(GL_TEXTURE_COORD_ARRAY); printOpenGLError();
+ // glEnableClientState(GL_VERTEX_ARRAY); printOpenGLError();
+ [self setupOpenGL];
+ }
+}
+
+- (void)setupOpenGL {
+ [self setupFramebuffer];
+ [self createOverlaySurface];
+ [self compileShaders];
+ [self setupVBOs];
+ [self setupTextures];
+
+ [self finishGLSetup];
+}
+
+- (void)finishGLSetup {
+ glViewport(0, 0, _renderBufferWidth, _renderBufferHeight); printOpenGLError();
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f); printOpenGLError();
+
+ glUniform2f(_screenSizeSlot, _renderBufferWidth, _renderBufferHeight);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+- (void)freeOpenGL {
+ [self deleteTextures];
+ [self deleteVBOs];
+ [self deleteShaders];
+ [self deleteFramebuffer];
+}
+
+- (void)rebuildFrameBuffer {
+ [self deleteFramebuffer];
+ [self setupFramebuffer];
+ [self finishGLSetup];
+}
+
+- (void)setupFramebuffer {
+ glGenRenderbuffers(1, &_viewRenderbuffer);
+ printOpenGLError();
+ glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer);
+ printOpenGLError();
+ [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id <EAGLDrawable>) self.layer];
+
+ glGenFramebuffers(1, &_viewFramebuffer);
+ printOpenGLError();
+ glBindFramebuffer(GL_FRAMEBUFFER, _viewFramebuffer);
+ printOpenGLError();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _viewRenderbuffer);
+ printOpenGLError();
+
+ // Retrieve the render buffer size. This *should* match the frame size,
+ // i.e. g_fullWidth and g_fullHeight.
+ glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_renderBufferWidth);
+ printOpenGLError();
+ glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_renderBufferHeight);
+ printOpenGLError();
+
+ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ NSLog(@"Failed to make complete framebuffer object %x.", glCheckFramebufferStatus(GL_FRAMEBUFFER));
+ return;
+ }
+}
+
+- (void)createOverlaySurface {
+ uint overlayWidth = (uint) MAX(_renderBufferWidth, _renderBufferHeight);
+ uint overlayHeight = (uint) MIN(_renderBufferWidth, _renderBufferHeight);
+
+ if (iOS7_isBigDevice()) {
+ // On really big displays, like the iPad Pro, we scale the interface down
+ // so that the controls are not too small..
+ while (overlayHeight > 1024) {
+ overlayWidth /= 2;
+ overlayHeight /= 2;
+ }
+ }
+ else {
+ // On small devices, we force the user interface to use the small theme
+ while (overlayHeight > 480) {
+ overlayWidth /= 2;
+ overlayHeight /= 2;
+ }
+ }
+
+ _videoContext.overlayWidth = overlayWidth;
+ _videoContext.overlayHeight = overlayHeight;
+
+ uint overlayTextureWidthPOT = getSizeNextPOT(overlayWidth);
+ uint overlayTextureHeightPOT = getSizeNextPOT(overlayHeight);
+
+ // Since the overlay size won't change the whole run, we can
+ // precalculate the texture coordinates for the overlay texture here
+ // and just use it later on.
+ GLfloat u = _videoContext.overlayWidth / (GLfloat) overlayTextureWidthPOT;
+ GLfloat v = _videoContext.overlayHeight / (GLfloat) overlayTextureHeightPOT;
+ _overlayCoords[0].x = 0; _overlayCoords[0].y = 0; _overlayCoords[0].u = 0; _overlayCoords[0].v = 0;
+ _overlayCoords[1].x = 0; _overlayCoords[1].y = 0; _overlayCoords[1].u = u; _overlayCoords[1].v = 0;
+ _overlayCoords[2].x = 0; _overlayCoords[2].y = 0; _overlayCoords[2].u = 0; _overlayCoords[2].v = v;
+ _overlayCoords[3].x = 0; _overlayCoords[3].y = 0; _overlayCoords[3].u = u; _overlayCoords[3].v = v;
+
+ _videoContext.overlayTexture.create((uint16) overlayTextureWidthPOT, (uint16) overlayTextureHeightPOT, Graphics::createPixelFormat<5551>());
+}
+
+- (void)deleteFramebuffer {
+ glDeleteRenderbuffers(1, &_viewRenderbuffer);
+ glDeleteFramebuffers(1, &_viewFramebuffer);
+}
+
+- (void)setupVBOs {
+ glGenBuffers(1, &_vertexBuffer);
+ glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
+}
+
+- (void)deleteVBOs {
+ glDeleteBuffers(1, &_vertexBuffer);
+}
+
+- (GLuint)compileShader:(const char*)shaderPrg withType:(GLenum)shaderType {
+ GLuint shaderHandle = glCreateShader(shaderType);
+
+ int shaderPrgLength = strlen(shaderPrg);
+ glShaderSource(shaderHandle, 1, &shaderPrg, &shaderPrgLength);
+
+ glCompileShader(shaderHandle);
+
+ GLint compileSuccess;
+ glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
+ if (compileSuccess == GL_FALSE) {
+ GLchar messages[256];
+ glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
+ NSString *messageString = [NSString stringWithUTF8String:messages];
+ NSLog(@"%@", messageString);
+ exit(1);
+ }
+
+ return shaderHandle;
+}
+
+- (void)compileShaders {
+ const char *vertexPrg =
+ "uniform vec2 ScreenSize;"
+ "uniform float Shake;"
+ ""
+ "attribute vec2 Position;"
+ "attribute vec2 TexCoord;"
+ ""
+ "varying vec4 DestColor;"
+ "varying vec2 o_TexCoord;"
+ ""
+ "void main(void) {"
+ " DestColor = vec4(Position.x, Position.y, 0, 1);"
+ " o_TexCoord = TexCoord;"
+ " gl_Position = vec4((Position.x / ScreenSize.x) * 2.0 - 1.0, (1.0 - (Position.y + Shake) / ScreenSize.y) * 2.0 - 1.0, 0, 1);"
+ "}";
+
+ const char *fragmentPrg =
+ "uniform sampler2D Texture;"
+ ""
+ "varying lowp vec4 DestColor;"
+ "varying lowp vec2 o_TexCoord;"
+ ""
+ "void main(void) {"
+ " gl_FragColor = texture2D(Texture, o_TexCoord);"
+ "}";
+
+ _vertexShader = [self compileShader:vertexPrg withType:GL_VERTEX_SHADER];
+ _fragmentShader = [self compileShader:fragmentPrg withType:GL_FRAGMENT_SHADER];
+
+ GLuint programHandle = glCreateProgram();
+ glAttachShader(programHandle, _vertexShader);
+ glAttachShader(programHandle, _fragmentShader);
+ glLinkProgram(programHandle);
+
+ GLint linkSuccess;
+ glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
+ if (linkSuccess == GL_FALSE) {
+ printOpenGLError();
+ exit(1);
+ }
+
+ glUseProgram(programHandle);
+
+ _screenSizeSlot = (GLuint) glGetUniformLocation(programHandle, "ScreenSize");
+ _textureSlot = (GLuint) glGetUniformLocation(programHandle, "Texture");
+ _shakeSlot = (GLuint) glGetUniformLocation(programHandle, "Shake");
+
+ _positionSlot = (GLuint) glGetAttribLocation(programHandle, "Position");
+ _textureCoordSlot = (GLuint) glGetAttribLocation(programHandle, "TexCoord");
+
+ glEnableVertexAttribArray(_positionSlot);
+ glEnableVertexAttribArray(_textureCoordSlot);
+
+ glUniform1i(_textureSlot, 0); printOpenGLError();
+}
+
+- (void)deleteShaders {
+ glDeleteShader(_vertexShader);
+ glDeleteShader(_fragmentShader);
+}
+
+- (void)setupTextures {
+ glGenTextures(1, &_screenTexture); printOpenGLError();
+ glGenTextures(1, &_overlayTexture); printOpenGLError();
+ glGenTextures(1, &_mouseCursorTexture); printOpenGLError();
+
+ [self setGraphicsMode];
+}
+
+- (void)deleteTextures {
+ if (_screenTexture) {
+ glDeleteTextures(1, &_screenTexture); printOpenGLError();
+ _screenTexture = 0;
+ }
+ if (_overlayTexture) {
+ glDeleteTextures(1, &_overlayTexture); printOpenGLError();
+ _overlayTexture = 0;
+ }
+ if (_mouseCursorTexture) {
+ glDeleteTextures(1, &_mouseCursorTexture); printOpenGLError();
+ _mouseCursorTexture = 0;
+ }
+}
+
+- (void)setupGestureRecognizers {
+ UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeRight:)];
+ swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
+ swipeRight.numberOfTouchesRequired = 2;
+ swipeRight.delaysTouchesBegan = NO;
+ swipeRight.delaysTouchesEnded = NO;
+
+ UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeLeft:)];
+ swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
+ swipeLeft.numberOfTouchesRequired = 2;
+ swipeLeft.delaysTouchesBegan = NO;
+ swipeLeft.delaysTouchesEnded = NO;
+
+ UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeUp:)];
+ swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
+ swipeUp.numberOfTouchesRequired = 2;
+ swipeUp.delaysTouchesBegan = NO;
+ swipeUp.delaysTouchesEnded = NO;
+
+ UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersSwipeDown:)];
+ swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
+ swipeDown.numberOfTouchesRequired = 2;
+ swipeDown.delaysTouchesBegan = NO;
+ swipeDown.delaysTouchesEnded = NO;
+
+ UITapGestureRecognizer *doubleTapTwoFingers = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(twoFingersDoubleTap:)];
+ doubleTapTwoFingers.numberOfTapsRequired = 2;
+ doubleTapTwoFingers.numberOfTouchesRequired = 2;
+ doubleTapTwoFingers.delaysTouchesBegan = NO;
+ doubleTapTwoFingers.delaysTouchesEnded = NO;
+
+ [self addGestureRecognizer:swipeRight];
+ [self addGestureRecognizer:swipeLeft];
+ [self addGestureRecognizer:swipeUp];
+ [self addGestureRecognizer:swipeDown];
+ [self addGestureRecognizer:doubleTapTwoFingers];
+
+ [swipeRight release];
+ [swipeLeft release];
+ [swipeUp release];
+ [swipeDown release];
+ [doubleTapTwoFingers release];
+}
+
+- (id)initWithFrame:(struct CGRect)frame {
+ self = [super initWithFrame: frame];
+
+#if defined(USE_SCALERS) || defined(USE_HQ_SCALERS)
+ InitScalers(565);
+#endif
+
+ [self setupGestureRecognizers];
+
+ [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
+
+#ifdef ENABLE_IOS7_SCALERS
+ _scalerMemorySrc = NULL;
+ _scalerMemoryDst = NULL;
+ _scalerMemorySrcSize = 0;
+ _scalerMemoryDstSize = 0;
+ _scaler = NULL;
+ _scalerScale = 1;
+#endif
+
+ _keyboardView = nil;
+ _screenTexture = 0;
+ _overlayTexture = 0;
+ _mouseCursorTexture = 0;
+
+ _scaledShakeOffsetY = 0;
+
+ _firstTouch = NULL;
+ _secondTouch = NULL;
+
+ _eventLock = [[NSLock alloc] init];
+
+ memset(_gameScreenCoords, 0, sizeof(GLVertex) * 4);
+ memset(_overlayCoords, 0, sizeof(GLVertex) * 4);
+ memset(_mouseCoords, 0, sizeof(GLVertex) * 4);
+
+ // Initialize the OpenGL ES context
+ [self createContext];
+
+ return self;
+}
+
+- (void)dealloc {
+ [_keyboardView release];
+
+ _videoContext.screenTexture.free();
+ _videoContext.overlayTexture.free();
+ _videoContext.mouseTexture.free();
+
+#ifdef ENABLE_IOS7_SCALERS
+ free(_scalerMemorySrc);
+ free(_scalerMemoryDst);
+#endif
+
+ [_eventLock release];
+ [super dealloc];
+}
+
+- (void)setFilterModeForTexture:(GLuint)tex {
+ if (!tex)
+ return;
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex); printOpenGLError();
+
+ GLint filter = GL_LINEAR;
+
+ switch (_videoContext.graphicsMode) {
+ case kGraphicsModeNone:
+ filter = GL_NEAREST;
+ break;
+
+ case kGraphicsModeLinear:
+ case kGraphicsMode2xSaI:
+ case kGraphicsModeSuper2xSaI:
+ case kGraphicsModeSuperEagle:
+ case kGraphicsModeAdvMame2x:
+ case kGraphicsModeAdvMame3x:
+ case kGraphicsModeHQ2x:
+ case kGraphicsModeHQ3x:
+ case kGraphicsModeTV2x:
+ case kGraphicsModeDotMatrix:
+ filter = GL_LINEAR;
+ break;
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); printOpenGLError();
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); printOpenGLError();
+ // We use GL_CLAMP_TO_EDGE here to avoid artifacts when linear filtering
+ // is used. If we would not use this for example the cursor in Loom would
+ // have a line/border artifact on the right side of the covered rect.
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); printOpenGLError();
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); printOpenGLError();
+}
+
+#ifdef ENABLE_IOS7_SCALERS
+- (void)setScaler {
+ ScalerProc *scaler = NULL;
+ int scalerScale = 1;
+
+ switch (_videoContext.graphicsMode) {
+ case kGraphicsModeLinear:
+ break;
+
+ case kGraphicsModeNone:
+ break;
+#ifdef USE_SCALERS
+ case kGraphicsMode2xSaI:
+ scaler = _2xSaI;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeSuper2xSaI:
+ scaler = Super2xSaI;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeSuperEagle:
+ scaler = SuperEagle;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeAdvMame2x:
+ scaler = AdvMame2x;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeAdvMame3x:
+ scaler = AdvMame3x;
+ scalerScale = 3;
+ break;
+
+#ifdef USE_HQ_SCALERS
+ case kGraphicsModeHQ2x:
+ scaler = HQ2x;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeHQ3x:
+ scaler = HQ3x;
+ scalerScale = 3;
+ break;
+#endif
+
+ case kGraphicsModeTV2x:
+ scaler = TV2x;
+ scalerScale = 2;
+ break;
+
+ case kGraphicsModeDotMatrix:
+ scaler = DotMatrix;
+ scalerScale = 2;
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ _scaler = scaler;
+ _scalerScale = scalerScale;
+}
+#endif
+
+- (void)setGraphicsMode {
+ [self setFilterModeForTexture:_screenTexture];
+ [self setFilterModeForTexture:_overlayTexture];
+ [self setFilterModeForTexture:_mouseCursorTexture];
+#ifdef ENABLE_IOS7_SCALERS
+ [self setScaler];
+#endif
+}
+
+- (void)updateSurface {
+ if (!g_needsScreenUpdate) {
+ return;
+ }
+ g_needsScreenUpdate = 0;
+
+ glClear(GL_COLOR_BUFFER_BIT); printOpenGLError();
+
+ [self updateMainSurface];
+
+ if (_videoContext.overlayVisible)
+ [self updateOverlaySurface];
+
+ if (_videoContext.mouseIsVisible)
+ [self updateMouseSurface];
+
+ [_context presentRenderbuffer:GL_RENDERBUFFER];
+ glFinish();
+}
+
+- (void)notifyMouseMove {
+ const GLint mouseX = (GLint)(_videoContext.mouseX * _mouseScaleX) - _mouseHotspotX;
+ const GLint mouseY = (GLint)(_videoContext.mouseY * _mouseScaleY) - _mouseHotspotY;
+
+ _mouseCoords[0].x = _mouseCoords[2].x = mouseX;
+ _mouseCoords[0].y = _mouseCoords[1].y = mouseY;
+ _mouseCoords[1].x = _mouseCoords[3].x = mouseX + _mouseWidth;
+ _mouseCoords[2].y = _mouseCoords[3].y = mouseY + _mouseHeight;
+}
+
+- (void)updateMouseCursorScaling {
+ CGRect *rect;
+ int maxWidth, maxHeight;
+
+ if (!_videoContext.overlayVisible) {
+ rect = &_gameScreenRect;
+ maxWidth = _videoContext.screenWidth;
+ maxHeight = _videoContext.screenHeight;
+ } else {
+ rect = &_overlayRect;
+ maxWidth = _videoContext.overlayWidth;
+ maxHeight = _videoContext.overlayHeight;
+ }
+
+ if (!maxWidth || !maxHeight) {
+ printf("WARNING: updateMouseCursorScaling called when screen was not ready (%d)!\n", _videoContext.overlayVisible);
+ return;
+ }
+
+ _mouseScaleX = CGRectGetWidth(*rect) / (GLfloat)maxWidth;
+ _mouseScaleY = CGRectGetHeight(*rect) / (GLfloat)maxHeight;
+
+ _mouseWidth = (GLint)(_videoContext.mouseWidth * _mouseScaleX);
+ _mouseHeight = (GLint)(_videoContext.mouseHeight * _mouseScaleY);
+
+ _mouseHotspotX = (GLint)(_videoContext.mouseHotspotX * _mouseScaleX);
+ _mouseHotspotY = (GLint)(_videoContext.mouseHotspotY * _mouseScaleY);
+
+ // We subtract the screen offset to the hotspot here to simplify the
+ // screen offset handling in the mouse code. Note the subtraction here
+ // makes sure that the offset actually gets added to the mouse position,
+ // since the hotspot offset is substracted from the position.
+ _mouseHotspotX -= (GLint)CGRectGetMinX(*rect);
+ _mouseHotspotY -= (GLint)CGRectGetMinY(*rect);
+
+ // FIXME: For now we also adapt the mouse position here. In reality we
+ // would be better off to also adjust the event position when switching
+ // from overlay to game screen or vica versa.
+ [self notifyMouseMove];
+}
+
+- (void)updateMouseCursor {
+ [self updateMouseCursorScaling];
+
+ _mouseCoords[1].u = _mouseCoords[3].u = (_videoContext.mouseWidth - 1) / (GLfloat)_videoContext.mouseTexture.w;
+ _mouseCoords[2].v = _mouseCoords[3].v = (_videoContext.mouseHeight - 1) / (GLfloat)_videoContext.mouseTexture.h;
+
+ [self setFilterModeForTexture:_mouseCursorTexture];
+ 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();
+}
+
+- (void)updateMainSurface {
+ glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _gameScreenCoords, GL_STATIC_DRAW);
+ glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0);
+ glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2));
+
+ [self setFilterModeForTexture:_screenTexture];
+
+ // Unfortunately we have to update the whole texture every frame, since glTexSubImage2D is actually slower in all cases
+ // due to the iPhone internals having to convert the whole texture back from its internal format when used.
+ // In the future we could use several tiled textures instead.
+#ifdef ENABLE_IOS7_SCALERS
+ if (_scaler) {
+ size_t neededSrcMemorySize = (size_t) (_videoContext.screenTexture.pitch * (_videoContext.screenTexture.h + 4));
+ size_t neededDstMemorySize = (size_t) (_videoContext.screenTexture.pitch * (_videoContext.screenTexture.h + 4) * _scalerScale * _scalerScale);
+ if (neededSrcMemorySize != _scalerMemorySrcSize) {
+ _scalerMemorySrc = (uint8_t *) realloc(_scalerMemorySrc, neededSrcMemorySize);
+ _scalerMemorySrcSize = neededSrcMemorySize;
+ }
+ if (neededDstMemorySize != _scalerMemoryDstSize) {
+ _scalerMemoryDst = (uint8_t *) realloc(_scalerMemoryDst, neededDstMemorySize);
+ _scalerMemoryDstSize = neededDstMemorySize;
+ }
+
+ // Clear two lines before
+ memset(_scalerMemorySrc, 0, (size_t) (_videoContext.screenTexture.pitch * 2));
+ // Copy original buffer
+ memcpy(_scalerMemorySrc + _videoContext.screenTexture.pitch * 2, _videoContext.screenTexture.getPixels(), _videoContext.screenTexture.pitch * _videoContext.screenTexture.h);
+ // Clear two lines after
+ memset(_scalerMemorySrc + _videoContext.screenTexture.pitch * (2 + _videoContext.screenTexture.h), 0, (size_t) (_videoContext.screenTexture.pitch * 2));
+ // Apply scaler
+ _scaler(_scalerMemorySrc + _videoContext.screenTexture.pitch * 2,
+ _videoContext.screenTexture.pitch,
+ _scalerMemoryDst,
+ (uint32) (_videoContext.screenTexture.pitch * _scalerScale),
+ _videoContext.screenTexture.w,
+ _videoContext.screenTexture.h);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _videoContext.screenTexture.w * _scalerScale, _videoContext.screenTexture.h * _scalerScale, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, _scalerMemoryDst); printOpenGLError();
+ }
+ else {
+#endif
+ 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();
+#ifdef ENABLE_IOS7_SCALERS
+ }
+#endif
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError();
+}
+
+- (void)updateOverlaySurface {
+ glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _overlayCoords, GL_STATIC_DRAW);
+ glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0);
+ glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2));
+
+ [self setFilterModeForTexture:_overlayTexture];
+
+ 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();
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError();
+}
+
+- (void)updateMouseSurface {
+ glBufferData(GL_ARRAY_BUFFER, sizeof(GLVertex) * 4, _mouseCoords, GL_STATIC_DRAW);
+ glVertexAttribPointer(_positionSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), 0);
+ glVertexAttribPointer(_textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLVertex), (GLvoid *) (sizeof(GLfloat) * 2));
+
+ glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError();
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError();
+}
+
+- (void)createScreenTexture {
+ const uint screenTexWidth = getSizeNextPOT(_videoContext.screenWidth);
+ const uint screenTexHeight = getSizeNextPOT(_videoContext.screenHeight);
+
+ _gameScreenCoords[1].u = _gameScreenCoords[3].u = _videoContext.screenWidth / (GLfloat)screenTexWidth;
+ _gameScreenCoords[2].v = _gameScreenCoords[3].v = _videoContext.screenHeight / (GLfloat)screenTexHeight;
+
+ _videoContext.screenTexture.create((uint16) screenTexWidth, (uint16) screenTexHeight, Graphics::createPixelFormat<565>());
+}
+
+- (void)initSurface {
+ if (_context) {
+ [self rebuildFrameBuffer];
+ }
+
+ BOOL isLandscape = (self.bounds.size.width > self.bounds.size.height); // UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation]);
+
+ int screenWidth, screenHeight;
+ if (isLandscape) {
+ screenWidth = MAX(_renderBufferWidth, _renderBufferHeight);
+ screenHeight = MIN(_renderBufferWidth, _renderBufferHeight);
+ }
+ else {
+ screenWidth = MIN(_renderBufferWidth, _renderBufferHeight);
+ screenHeight = MAX(_renderBufferWidth, _renderBufferHeight);
+ }
+
+ glBindRenderbuffer(GL_RENDERBUFFER, _viewRenderbuffer); printOpenGLError();
+
+ [self clearColorBuffer];
+
+ GLfloat adjustedWidth = _videoContext.screenWidth;
+ GLfloat adjustedHeight = _videoContext.screenHeight;
+ if (_videoContext.asprectRatioCorrection) {
+ if (_videoContext.screenWidth == 320 && _videoContext.screenHeight == 200)
+ adjustedHeight = 240;
+ else if (_videoContext.screenWidth == 640 && _videoContext.screenHeight == 400)
+ adjustedHeight = 480;
+ }
+
+ float overlayPortraitRatio;
+
+ if (isLandscape) {
+ GLfloat gameScreenRatio = adjustedWidth / adjustedHeight;
+ GLfloat screenRatio = (GLfloat)screenWidth / (GLfloat)screenHeight;
+
+ // These are the width/height according to the portrait layout!
+ int rectWidth, rectHeight;
+ int xOffset, yOffset;
+
+ if (gameScreenRatio < screenRatio) {
+ // When the game screen ratio is less than the screen ratio
+ // we need to scale the width, since the game screen was higher
+ // compared to the width than our output screen is.
+ rectWidth = (int)(screenHeight * gameScreenRatio);
+ rectHeight = screenHeight;
+ xOffset = (screenWidth - rectWidth) / 2;
+ yOffset = 0;
+ } else {
+ // When the game screen ratio is bigger than the screen ratio
+ // we need to scale the height, since the game screen was wider
+ // compared to the height than our output screen is.
+ rectWidth = screenWidth;
+ rectHeight = (int)(screenWidth / gameScreenRatio);
+ xOffset = 0;
+ yOffset = (screenHeight - rectHeight) / 2;
+ }
+
+ [_keyboardView hideKeyboard];
+
+ //printf("Rect: %i, %i, %i, %i\n", xOffset, yOffset, rectWidth, rectHeight);
+ _gameScreenRect = CGRectMake(xOffset, yOffset, rectWidth, rectHeight);
+ overlayPortraitRatio = 1.0f;
+ } else {
+ GLfloat ratio = adjustedHeight / adjustedWidth;
+ int height = (int)(screenWidth * ratio);
+ //printf("Making rect (%u, %u)\n", screenWidth, height);
+ _gameScreenRect = CGRectMake(0, 0, screenWidth, height);
+
+ CGRect keyFrame = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f);
+ if (_keyboardView == nil) {
+ _keyboardView = [[SoftKeyboard alloc] initWithFrame:keyFrame];
+ [_keyboardView setInputDelegate:self];
+ [self addSubview:[_keyboardView inputView]];
+ [self addSubview: _keyboardView];
+ }
+
+ [_keyboardView showKeyboard];
+ overlayPortraitRatio = (_videoContext.overlayHeight * ratio) / _videoContext.overlayWidth;
+ }
+ _overlayRect = CGRectMake(0, 0, screenWidth, screenHeight * overlayPortraitRatio);
+
+ _gameScreenCoords[0].x = _gameScreenCoords[2].x = CGRectGetMinX(_gameScreenRect);
+ _gameScreenCoords[0].y = _gameScreenCoords[1].y = CGRectGetMinY(_gameScreenRect);
+ _gameScreenCoords[1].x = _gameScreenCoords[3].x = CGRectGetMaxX(_gameScreenRect);
+ _gameScreenCoords[2].y = _gameScreenCoords[3].y = CGRectGetMaxY(_gameScreenRect);
+
+ _overlayCoords[1].x = _overlayCoords[3].x = CGRectGetMaxX(_overlayRect);
+ _overlayCoords[2].y = _overlayCoords[3].y = CGRectGetMaxY(_overlayRect);
+
+ [self setViewTransformation];
+ [self updateMouseCursorScaling];
+}
+
+- (void)setViewTransformation {
+ // Scale the shake offset according to the overlay size. We need this to
+ // adjust the overlay mouse click coordinates when an offset is set.
+ _scaledShakeOffsetY = (int)(_videoContext.shakeOffsetY / (GLfloat)_videoContext.screenHeight * CGRectGetHeight(_overlayRect));
+
+ glUniform1f(_shakeSlot, _scaledShakeOffsetY);
+}
+
+- (void)clearColorBuffer {
+ // The color buffer is triple-buffered, so we clear it multiple times right away to avid doing any glClears later.
+ int clearCount = 5;
+ while (clearCount-- > 0) {
+ glClear(GL_COLOR_BUFFER_BIT); printOpenGLError();
+ [_context presentRenderbuffer:GL_RENDERBUFFER];
+ glFinish();
+ }
+}
+
+- (void)addEvent:(InternalEvent)event {
+ [_eventLock lock];
+ _events.push_back(event);
+ [_eventLock unlock];
+}
+
+- (bool)fetchEvent:(InternalEvent *)event {
+ [_eventLock lock];
+ if (_events.empty()) {
+ [_eventLock unlock];
+ return false;
+ }
+
+ *event = *_events.begin();
+ _events.pop_front();
+ [_eventLock unlock];
+ return true;
+}
+
+- (bool)getMouseCoords:(CGPoint)point eventX:(int *)x eventY:(int *)y {
+ // We scale the input according to our scale factor to get actual screen
+ // coordinates.
+ point.x *= self.contentScaleFactor;
+ point.y *= self.contentScaleFactor;
+
+ CGRect *area;
+ int width, height, offsetY;
+ if (_videoContext.overlayVisible) {
+ area = &_overlayRect;
+ width = _videoContext.overlayWidth;
+ height = _videoContext.overlayHeight;
+ offsetY = _scaledShakeOffsetY;
+ } else {
+ area = &_gameScreenRect;
+ width = _videoContext.screenWidth;
+ height = _videoContext.screenHeight;
+ offsetY = _videoContext.shakeOffsetY;
+ }
+
+ point.x = (point.x - CGRectGetMinX(*area)) / CGRectGetWidth(*area);
+ point.y = (point.y - CGRectGetMinY(*area)) / CGRectGetHeight(*area);
+
+ *x = (int)(point.x * width);
+ // offsetY describes the translation of the screen in the upward direction,
+ // thus we need to add it here.
+ *y = (int)(point.y * height + offsetY);
+
+ if (!iOS7_touchpadModeEnabled()) {
+ // Clip coordinates
+ if (*x < 0 || *x > width || *y < 0 || *y > height)
+ return false;
+ }
+
+ return true;
+}
+
+- (void)deviceOrientationChanged:(UIDeviceOrientation)orientation {
+ [self addEvent:InternalEvent(kInputOrientationChanged, orientation, 0)];
+}
+
+- (UITouch *)secondTouchOtherTouchThan:(UITouch *)touch in:(NSSet *)set {
+ NSArray *all = [set allObjects];
+ for (UITouch *t in all) {
+ if (t != touch) {
+ return t;
+ }
+ }
+ return nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ int x, y;
+
+ NSSet *allTouches = [event allTouches];
+ if (allTouches.count == 1) {
+ _firstTouch = [allTouches anyObject];
+ CGPoint point = [_firstTouch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y])
+ return;
+
+ [self addEvent:InternalEvent(kInputMouseDown, x, y)];
+ }
+ else if (allTouches.count == 2) {
+ _secondTouch = [self secondTouchOtherTouchThan:_firstTouch in:allTouches];
+ if (_secondTouch) {
+ CGPoint point = [_secondTouch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y])
+ return;
+
+ [self addEvent:InternalEvent(kInputMouseSecondDown, x, y)];
+ }
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ int x, y;
+
+ NSSet *allTouches = [event allTouches];
+ for (UITouch *touch in allTouches) {
+ if (touch == _firstTouch) {
+ CGPoint point = [touch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y])
+ return;
+
+ [self addEvent:InternalEvent(kInputMouseDragged, x, y)];
+ } else if (touch == _secondTouch) {
+ CGPoint point = [touch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y])
+ return;
+
+ [self addEvent:InternalEvent(kInputMouseSecondDragged, x, y)];
+ }
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ int x, y;
+
+ NSSet *allTouches = [event allTouches];
+ if (allTouches.count == 1) {
+ UITouch *touch = [allTouches anyObject];
+ CGPoint point = [touch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y]) {
+ return;
+ }
+
+ [self addEvent:InternalEvent(kInputMouseUp, x, y)];
+ }
+ else if (allTouches.count == 2) {
+ UITouch *touch = [[allTouches allObjects] objectAtIndex:1];
+ CGPoint point = [touch locationInView:self];
+ if (![self getMouseCoords:point eventX:&x eventY:&y])
+ return;
+
+ [self addEvent:InternalEvent(kInputMouseSecondUp, x, y)];
+ }
+ _firstTouch = nil;
+ _secondTouch = nil;
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ _firstTouch = nil;
+ _secondTouch = nil;
+}
+
+- (void)twoFingersSwipeRight:(UISwipeGestureRecognizer *)recognizer {
+ [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeRight, 2)];
+}
+
+- (void)twoFingersSwipeLeft:(UISwipeGestureRecognizer *)recognizer {
+ [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeLeft, 2)];
+}
+
+- (void)twoFingersSwipeUp:(UISwipeGestureRecognizer *)recognizer {
+ [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeUp, 2)];
+}
+
+- (void)twoFingersSwipeDown:(UISwipeGestureRecognizer *)recognizer {
+ [self addEvent:InternalEvent(kInputSwipe, kUIViewSwipeDown, 2)];
+}
+
+- (void)twoFingersDoubleTap:(UITapGestureRecognizer *)recognizer {
+ [self addEvent:InternalEvent(kInputTap, kUIViewTapDouble, 2)];
+}
+
+- (void)handleKeyPress:(unichar)c {
+ [self addEvent:InternalEvent(kInputKeyPressed, c, 0)];
+}
+
+- (void)applicationSuspend {
+ [self addEvent:InternalEvent(kInputApplicationSuspended, 0, 0)];
+}
+
+- (void)applicationResume {
+ [self addEvent:InternalEvent(kInputApplicationResumed, 0, 0)];
+}
+
+@end