/* 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. * */ #include "iphone_video.h" #include "iphone_common.h" static iPhoneView *sharedInstance = nil; static int _width = 0; static int _height = 0; static int _fullWidth; static int _fullHeight; static CGRect _screenRect; static char* _textureBuffer = 0; static int _textureWidth = 0; static int _textureHeight = 0; static char* _overlayTexBuffer = 0; static int _overlayTexWidth = 0; static int _overlayTexHeight = 0; static int _overlayWidth = 0; static int _overlayHeight = 0; static float _overlayPortraitRatio = 1.0f; NSLock* _lock = nil; static int _needsScreenUpdate = 0; static int _overlayIsEnabled = 0; static UITouch* _firstTouch = NULL; static UITouch* _secondTouch = NULL; static short* _mouseCursor = NULL; static int _mouseCursorHeight = 0; static int _mouseCursorWidth = 0; static int _mouseX = 0; static int _mouseY = 0; // static long lastTick = 0; // static int frames = 0; #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; } void iPhone_setMouseCursor(short* buffer, int width, int height) { _mouseCursor = buffer; _mouseCursorWidth = width; _mouseCursorHeight = height; [sharedInstance performSelectorOnMainThread:@selector(updateMouseCursor) withObject:nil waitUntilDone: YES]; } void iPhone_enableOverlay(int state) { _overlayIsEnabled = state; [sharedInstance performSelectorOnMainThread:@selector(clearColorBuffer) withObject:nil waitUntilDone: YES]; } int iPhone_getScreenHeight() { return _overlayHeight; } int iPhone_getScreenWidth() { return _overlayWidth; } bool iPhone_isHighResDevice() { return _fullHeight > 480; } void iPhone_updateScreen(int mouseX, int mouseY) { //printf("Mouse: (%i, %i)\n", mouseX, mouseY); //_mouseX = _overlayHeight - (float)mouseX / _width * _overlayHeight; //_mouseY = (float)mouseY / _height * _overlayWidth; //_mouseX = _overlayHeight - mouseX; //_mouseY = mouseY; _mouseX = (_overlayWidth - mouseX) / (float)_overlayWidth * _overlayHeight; _mouseY = mouseY / (float)_overlayHeight * _overlayWidth; if (!_needsScreenUpdate) { _needsScreenUpdate = 1; [sharedInstance performSelectorOnMainThread:@selector(updateSurface) withObject:nil waitUntilDone: NO]; } } void iPhone_updateScreenRect(unsigned short* screen, int x1, int y1, int x2, int y2) { int y; for (y = y1; y < y2; ++y) memcpy(&_textureBuffer[(y * _textureWidth + x1 )* 2], &screen[y * _width + x1], (x2 - x1) * 2); } void iPhone_updateOverlayRect(unsigned short* screen, int x1, int y1, int x2, int y2) { int y; //printf("Overlaywidth: %u, fullwidth %u\n", _overlayWidth, _fullWidth); for (y = y1; y < y2; ++y) memcpy(&_overlayTexBuffer[(y * _overlayTexWidth + x1 )* 2], &screen[y * _overlayWidth + x1], (x2 - x1) * 2); } void iPhone_initSurface(int width, int height) { _width = width; _height = height; [sharedInstance performSelectorOnMainThread:@selector(initSurface) withObject:nil waitUntilDone: YES]; } bool iPhone_fetchEvent(int *outEvent, float *outX, float *outY) { id event = [sharedInstance getEvent]; if (event == nil) { return false; } id type = [event objectForKey:@"type"]; if (type == nil) { printf("fetchEvent says: No type!\n"); return false; } *outEvent = [type intValue]; *outX = [[event objectForKey:@"x"] floatValue]; *outY = [[event objectForKey:@"y"] floatValue]; return true; } uint getSizeNextPOT(uint size) { if ((size & (size - 1)) || !size) { int log = 0; while (size >>= 1) ++log; size = (2 << log); } return size; } const char* iPhone_getDocumentsDir() { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; return [documentsDirectory UTF8String]; } bool getLocalMouseCoords(CGPoint *point) { if (_overlayIsEnabled) { point->x = point->x / _overlayHeight; point->y = point->y / _overlayWidth; } else { if (point->x < _screenRect.origin.x || point->x >= _screenRect.origin.x + _screenRect.size.width || point->y < _screenRect.origin.y || point->y >= _screenRect.origin.y + _screenRect.size.height) { return false; } point->x = (point->x - _screenRect.origin.x) / _screenRect.size.width; point->y = (point->y - _screenRect.origin.y) / _screenRect.size.height; } return true; } @implementation iPhoneView + (Class) layerClass { return [CAEAGLLayer class]; } - (id)initWithFrame:(struct CGRect)frame { self = [super initWithFrame: frame]; if([[UIScreen mainScreen] respondsToSelector: NSSelectorFromString(@"scale")]) { if([self respondsToSelector: NSSelectorFromString(@"contentScaleFactor")]) { //self.contentScaleFactor = [[UIScreen mainScreen] scale]; } } _fullWidth = frame.size.width; _fullHeight = frame.size.height; _screenLayer = nil; sharedInstance = self; _lock = [NSLock new]; _keyboardView = nil; _context = nil; _screenTexture = 0; _overlayTexture = 0; _mouseCursorTexture = 0; return self; } -(void) dealloc { [super dealloc]; if (_keyboardView != nil) { [_keyboardView dealloc]; } if (_screenTexture) free(_textureBuffer); free(_overlayTexBuffer); } - (void *)getSurface { return _screenSurface; } - (void)drawRect:(CGRect)frame { // if (lastTick == 0) { // lastTick = time(0); // } // // frames++; // if (time(0) > lastTick) { // lastTick = time(0); // printf("FPS: %i\n", frames); // frames = 0; // } } - (void)updateSurface { if (!_needsScreenUpdate) { return; } _needsScreenUpdate = 0; if (_overlayIsEnabled) { glClear(GL_COLOR_BUFFER_BIT); printOpenGLError(); } [self updateMainSurface]; if (_overlayIsEnabled) { [self updateOverlaySurface]; [self updateMouseSurface]; } glBindRenderbufferOES(GL_RENDERBUFFER_OES, _viewRenderbuffer); printOpenGLError(); [_context presentRenderbuffer:GL_RENDERBUFFER_OES]; } -(void)updateMouseCursor { if (_mouseCursorTexture == 0) { glGenTextures(1, &_mouseCursorTexture); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); printOpenGLError(); } glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSizeNextPOT(_mouseCursorWidth), getSizeNextPOT(_mouseCursorHeight), 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _mouseCursor); printOpenGLError(); free(_mouseCursor); _mouseCursor = NULL; } - (void)updateMainSurface { GLfloat vertices[] = { 0.0f + _heightOffset, 0.0f + _widthOffset, _visibleWidth - _heightOffset, 0.0f + _widthOffset, 0.0f + _heightOffset, _visibleHeight - _widthOffset, _visibleWidth - _heightOffset, _visibleHeight - _widthOffset }; float texWidth = _width / (float)_textureWidth; float texHeight = _height / (float)_textureHeight; const GLfloat texCoords[] = { texWidth, 0.0f, 0.0f, 0.0f, texWidth, texHeight, 0.0f, texHeight }; glVertexPointer(2, GL_FLOAT, 0, vertices); printOpenGLError(); glTexCoordPointer(2, GL_FLOAT, 0, texCoords); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _screenTexture); printOpenGLError(); // 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. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _textureWidth, _textureHeight, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, _textureBuffer); printOpenGLError(); glDisable(GL_BLEND); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); } - (void)updateOverlaySurface { GLfloat vertices[] = { 0.0f, 0.0f, _overlayHeight, 0.0f, 0.0f, _overlayWidth * _overlayPortraitRatio, _overlayHeight, _overlayWidth * _overlayPortraitRatio }; float texWidth = _overlayWidth / (float)_overlayTexWidth; float texHeight = _overlayHeight / (float)_overlayTexHeight; const GLfloat texCoords[] = { texWidth, 0.0f, 0.0f, 0.0f, texWidth, texHeight, 0.0f, texHeight }; glVertexPointer(2, GL_FLOAT, 0, vertices); printOpenGLError(); glTexCoordPointer(2, GL_FLOAT, 0, texCoords); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _overlayTexture); printOpenGLError(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _overlayTexWidth, _overlayTexHeight, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, _overlayTexBuffer); printOpenGLError(); glEnable(GL_BLEND); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); } - (void)updateMouseSurface { int width = _mouseCursorWidth / (float)_backingWidth * _backingHeight; int height = _mouseCursorHeight / (float)_backingHeight * _backingWidth; GLfloat vertices[] = { _mouseX, _mouseY, _mouseX + height, _mouseY, _mouseX, _mouseY + width, _mouseX + height, _mouseY + width }; //printf("Cursor: width %u height %u\n", _mouseCursorWidth, _mouseCursorHeight); float texWidth = _mouseCursorWidth / (float)getSizeNextPOT(_mouseCursorWidth); float texHeight = _mouseCursorHeight / (float)getSizeNextPOT(_mouseCursorHeight); const GLfloat texCoords[] = { texWidth, 0.0f, 0.0f, 0.0f, texWidth, texHeight, 0.0f, texHeight }; glVertexPointer(2, GL_FLOAT, 0, vertices); printOpenGLError(); glTexCoordPointer(2, GL_FLOAT, 0, texCoords); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _mouseCursorTexture); printOpenGLError(); glEnable(GL_BLEND); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); printOpenGLError(); } - (void)initSurface { _textureWidth = getSizeNextPOT(_width); _textureHeight = getSizeNextPOT(_height); UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; //printf("Window: (%d, %d), Surface: (%d, %d), Texture(%d, %d)\n", _fullWidth, _fullHeight, _width, _height, _textureWidth, _textureHeight); if (_context == nil) { orientation = UIDeviceOrientationLandscapeRight; CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer; eaglLayer.opaque = YES; eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat, nil]; _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; if (!_context || [EAGLContext setCurrentContext:_context]) { glGenFramebuffersOES(1, &_viewFramebuffer); printOpenGLError(); glGenRenderbuffersOES(1, &_viewRenderbuffer); printOpenGLError(); glBindFramebufferOES(GL_FRAMEBUFFER_OES, _viewFramebuffer); printOpenGLError(); glBindRenderbufferOES(GL_RENDERBUFFER_OES, _viewRenderbuffer); printOpenGLError(); [_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _viewRenderbuffer); printOpenGLError(); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &_backingWidth); printOpenGLError(); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &_backingHeight); printOpenGLError(); if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { NSLog(@"Failed to make complete framebuffer object %x.", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); return; } _overlayHeight = _backingWidth; _overlayWidth = _backingHeight; _overlayTexWidth = getSizeNextPOT(_overlayHeight); _overlayTexHeight = getSizeNextPOT(_overlayWidth); int textureSize = _overlayTexWidth * _overlayTexHeight * 2; _overlayTexBuffer = (char *)malloc(textureSize); memset(_overlayTexBuffer, 0, textureSize); glViewport(0, 0, _backingWidth, _backingHeight); printOpenGLError(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); printOpenGLError(); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); printOpenGLError(); glEnableClientState(GL_TEXTURE_COORD_ARRAY); printOpenGLError(); glEnableClientState(GL_VERTEX_ARRAY); printOpenGLError(); } } glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (orientation == UIDeviceOrientationLandscapeRight) { glRotatef(-90, 0, 0, 1); printOpenGLError(); } else if (orientation == UIDeviceOrientationLandscapeLeft) { glRotatef(90, 0, 0, 1); printOpenGLError(); } else { glRotatef(180, 0, 0, 1); printOpenGLError(); } glOrthof(0, _backingWidth, 0, _backingHeight, 0, 1); printOpenGLError(); if (_screenTexture > 0) { glDeleteTextures(1, &_screenTexture); printOpenGLError(); } glGenTextures(1, &_screenTexture); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _screenTexture); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); printOpenGLError(); if (_overlayTexture > 0) { glDeleteTextures(1, &_overlayTexture); printOpenGLError(); } glGenTextures(1, &_overlayTexture); printOpenGLError(); glBindTexture(GL_TEXTURE_2D, _overlayTexture); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); printOpenGLError(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); printOpenGLError(); if (_textureBuffer) { free(_textureBuffer); } int textureSize = _textureWidth * _textureHeight * 2; _textureBuffer = (char*)malloc(textureSize); memset(_textureBuffer, 0, textureSize); glBindRenderbufferOES(GL_RENDERBUFFER_OES, _viewRenderbuffer); printOpenGLError(); [self clearColorBuffer]; if (_keyboardView != nil) { [_keyboardView removeFromSuperview]; [[_keyboardView inputView] removeFromSuperview]; } if (orientation == UIDeviceOrientationLandscapeLeft || orientation == UIDeviceOrientationLandscapeRight) { _visibleHeight = _backingHeight; _visibleWidth = _backingWidth; float ratioDifference = ((float)_height / (float)_width) / ((float)_fullWidth / (float)_fullHeight); int rectWidth, rectHeight; if (ratioDifference < 1.0f) { rectWidth = _fullWidth * ratioDifference; rectHeight = _fullHeight; _widthOffset = (_fullWidth - rectWidth) / 2; _heightOffset = 0; } else { rectWidth = _fullWidth; rectHeight = _fullHeight / ratioDifference; _heightOffset = (_fullHeight - rectHeight) / 2; _widthOffset = 0; } //printf("Rect: %i, %i, %i, %i\n", _widthOffset, _heightOffset, rectWidth, rectHeight); _screenRect = CGRectMake(_widthOffset, _heightOffset, rectWidth, rectHeight); _overlayPortraitRatio = 1.0f; } else { float ratio = (float)_height / (float)_width; int height = _fullWidth * ratio; //printf("Making rect (%u, %u)\n", _fullWidth, height); _screenRect = CGRectMake(0, 0, _fullWidth - 1, height - 1); _visibleHeight = height; _visibleWidth = _backingWidth; _heightOffset = 0.0f; _widthOffset = 0.0f; 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 inputView] becomeFirstResponder]; _overlayPortraitRatio = (_overlayHeight * ratio) / _overlayWidth; } } - (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_OES]; } } - (id)getEvent { if (_events == nil || [_events count] == 0) { return nil; } id event = [_events objectAtIndex: 0]; [_events removeObjectAtIndex: 0]; return event; } - (void)addEvent:(NSDictionary*)event { if (_events == nil) _events = [[NSMutableArray alloc] init]; [_events addObject: event]; } - (void)deviceOrientationChanged:(int)orientation { [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputOrientationChanged], @"type", [NSNumber numberWithFloat:(float)orientation], @"x", [NSNumber numberWithFloat:0], @"y", nil ] ]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *allTouches = [event allTouches]; switch ([allTouches count]) { case 1: { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; _firstTouch = touch; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDown], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; break; } case 2: { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; _secondTouch = touch; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseSecondDown], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; break; } } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { //NSSet *allTouches = [event allTouches]; for (UITouch* touch in touches) { if (touch == _firstTouch) { CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDragged], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; } else if (touch == _secondTouch) { CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseSecondDragged], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; } } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *allTouches = [event allTouches]; switch ([allTouches count]) { case 1: { UITouch *touch = [[allTouches allObjects] objectAtIndex:0]; CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseUp], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; break; } case 2: { UITouch *touch = [[allTouches allObjects] objectAtIndex:1]; CGPoint point = [touch locationInView:self]; if (!getLocalMouseCoords(&point)) return; [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseSecondUp], @"type", [NSNumber numberWithFloat:point.x], @"x", [NSNumber numberWithFloat:point.y], @"y", nil ] ]; break; } } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { } - (void)handleKeyPress:(unichar)c { [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputKeyPressed], @"type", [NSNumber numberWithFloat:(float)c], @"x", [NSNumber numberWithFloat:0], @"y", nil ] ]; } - (BOOL)canHandleSwipes { return TRUE; } - (int)swipe:(int)num withEvent:(struct __GSEvent *)event { //printf("swipe: %i\n", num); [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputSwipe], @"type", [NSNumber numberWithFloat:(float)num], @"x", [NSNumber numberWithFloat:0], @"y", nil ] ]; } - (void)applicationSuspend { [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputApplicationSuspended], @"type", [NSNumber numberWithFloat:0], @"x", [NSNumber numberWithFloat:0], @"y", nil ] ]; } - (void)applicationResume { [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputApplicationResumed], @"type", [NSNumber numberWithFloat:0], @"x", [NSNumber numberWithFloat:0], @"y", nil ] ]; } @end