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