mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-13 07:14:59 +00:00
996 lines
31 KiB
Plaintext
996 lines
31 KiB
Plaintext
/* 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 "backends/platform/ios7/ios7_video.h"
|
|
|
|
#include "graphics/colormasks.h"
|
|
#include "common/system.h"
|
|
#include "backends/platform/ios7/ios7_app_delegate.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;
|
|
}
|
|
|
|
static inline void execute_on_main_thread(void (^block)(void)) {
|
|
if ([NSThread currentThread] == [NSThread mainThread]) {
|
|
block();
|
|
}
|
|
else {
|
|
dispatch_sync(dispatch_get_main_queue(), block);
|
|
}
|
|
}
|
|
|
|
void iOS7_updateScreen() {
|
|
//printf("Mouse: (%i, %i)\n", mouseX, mouseY);
|
|
if (!g_needsScreenUpdate) {
|
|
g_needsScreenUpdate = 1;
|
|
execute_on_main_thread(^{
|
|
[[iOS7AppDelegate iPhoneView] updateSurface];
|
|
});
|
|
}
|
|
}
|
|
|
|
bool iOS7_fetchEvent(InternalEvent *event) {
|
|
__block bool fetched;
|
|
execute_on_main_thread(^{
|
|
fetched = [[iOS7AppDelegate iPhoneView] fetchEvent:event];
|
|
});
|
|
return fetched;
|
|
}
|
|
|
|
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 = _videoContext.filtering ? GL_LINEAR : GL_NEAREST;
|
|
|
|
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 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
|