1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM 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 #include "common/scummsys.h"
24 
25 #if defined(USE_OPENGL_GAME) || defined(USE_OPENGL_SHADERS) || defined(USE_GLES2)
26 
27 #include "backends/graphics3d/openglsdl/openglsdl-graphics3d.h"
28 
29 #include "backends/events/sdl/sdl-events.h"
30 #include "common/config-manager.h"
31 #include "common/file.h"
32 #include "engines/engine.h"
33 #include "graphics/conversion.h"
34 #include "graphics/pixelbuffer.h"
35 #include "graphics/opengl/context.h"
36 #include "graphics/opengl/framebuffer.h"
37 #include "graphics/opengl/surfacerenderer.h"
38 #include "graphics/opengl/system_headers.h"
39 #include "graphics/opengl/texture.h"
40 #include "graphics/opengl/tiledsurface.h"
41 
42 #ifdef USE_PNG
43 #include "image/png.h"
44 #else
45 #include "image/bmp.h"
46 #endif
47 
OpenGLSdlGraphics3dManager(SdlEventSource * eventSource,SdlWindow * window,bool supportsFrameBuffer)48 OpenGLSdlGraphics3dManager::OpenGLSdlGraphics3dManager(SdlEventSource *eventSource, SdlWindow *window, bool supportsFrameBuffer)
49 	: SdlGraphicsManager(eventSource, window),
50 #if SDL_VERSION_ATLEAST(2, 0, 0)
51 	_glContext(nullptr),
52 #endif
53 	_supportsFrameBuffer(supportsFrameBuffer),
54 	_overlayVisible(false),
55 	_overlayScreen(nullptr),
56 	_overlayBackground(nullptr),
57 	_gameRect(),
58 	_fullscreen(false),
59 	_lockAspectRatio(true),
60 	_frameBuffer(nullptr),
61 	_surfaceRenderer(nullptr),
62 	_engineRequestedWidth(0),
63 	_engineRequestedHeight(0),
64 	_transactionMode(kTransactionNone) {
65 	ConfMan.registerDefault("antialiasing", 0);
66 	ConfMan.registerDefault("aspect_ratio", true);
67 
68 	// Don't start at zero so that the value is never the same as the surface graphics manager
69 	_screenChangeCount = 1 << (sizeof(int) * 5 - 2);
70 }
71 
~OpenGLSdlGraphics3dManager()72 OpenGLSdlGraphics3dManager::~OpenGLSdlGraphics3dManager() {
73 	closeOverlay();
74 #if SDL_VERSION_ATLEAST(2, 0, 0)
75 	deinitializeRenderer();
76 #endif
77 }
78 
hasFeature(OSystem::Feature f) const79 bool OpenGLSdlGraphics3dManager::hasFeature(OSystem::Feature f) const {
80 	return
81 		(f == OSystem::kFeatureFullscreenMode) ||
82 		(f == OSystem::kFeatureOpenGLForGame) ||
83 #if SDL_VERSION_ATLEAST(2, 0, 0)
84 		(f == OSystem::kFeatureFullscreenToggleKeepsContext) ||
85 #endif
86 		(f == OSystem::kFeatureVSync) ||
87 		(f == OSystem::kFeatureAspectRatioCorrection) ||
88 		(f == OSystem::kFeatureOverlaySupportsAlpha && _overlayFormat.aBits() > 3);
89 }
90 
getFeatureState(OSystem::Feature f) const91 bool OpenGLSdlGraphics3dManager::getFeatureState(OSystem::Feature f) const {
92 	switch (f) {
93 		case OSystem::kFeatureVSync:
94 			return isVSyncEnabled();
95 		case OSystem::kFeatureFullscreenMode:
96 			return _fullscreen;
97 		case OSystem::kFeatureAspectRatioCorrection:
98 			return _lockAspectRatio;
99 		default:
100 			return false;
101 	}
102 }
103 
setFeatureState(OSystem::Feature f,bool enable)104 void OpenGLSdlGraphics3dManager::setFeatureState(OSystem::Feature f, bool enable) {
105 	switch (f) {
106 		case OSystem::kFeatureFullscreenMode:
107 			if (_fullscreen != enable) {
108 				_fullscreen = enable;
109 				if (_transactionMode == kTransactionNone)
110 					createOrUpdateScreen();
111 			}
112 			break;
113 		case OSystem::kFeatureAspectRatioCorrection:
114 			_lockAspectRatio = enable;
115 			break;
116 		default:
117 			break;
118 	}
119 }
120 
beginGFXTransaction()121 void OpenGLSdlGraphics3dManager::beginGFXTransaction() {
122 	assert(_transactionMode == kTransactionNone);
123 
124 	_transactionMode = kTransactionActive;
125 }
126 
endGFXTransaction()127 OSystem::TransactionError OpenGLSdlGraphics3dManager::endGFXTransaction() {
128 	assert(_transactionMode != kTransactionNone);
129 
130 	setupScreen();
131 
132 	_transactionMode = kTransactionNone;
133 	return OSystem::kTransactionSuccess;
134 }
135 
136 const OSystem::GraphicsMode glGraphicsModes[] = {
137 	{ "opengl3d", "OpenGL 3D", 0 },
138 	{ nullptr, nullptr, 0 }
139 };
140 
getSupportedGraphicsModes() const141 const OSystem::GraphicsMode *OpenGLSdlGraphics3dManager::getSupportedGraphicsModes() const {
142 	return glGraphicsModes;
143 }
144 
getDefaultGraphicsMode() const145 int OpenGLSdlGraphics3dManager::getDefaultGraphicsMode() const {
146 	return 0;
147 }
148 
setGraphicsMode(int mode,uint flags)149 bool OpenGLSdlGraphics3dManager::setGraphicsMode(int mode, uint flags) {
150 	assert(_transactionMode != kTransactionNone);
151 	assert(flags & OSystem::kGfxModeRender3d);
152 
153 	return true;
154 }
155 
getGraphicsMode() const156 int OpenGLSdlGraphics3dManager::getGraphicsMode() const {
157 	return 0;
158 }
159 
initSize(uint w,uint h,const Graphics::PixelFormat * format)160 void OpenGLSdlGraphics3dManager::initSize(uint w, uint h, const Graphics::PixelFormat *format) {
161 	_engineRequestedWidth = w;
162 	_engineRequestedHeight = h;
163 	if (_transactionMode == kTransactionNone)
164 		setupScreen();
165 }
166 
setupScreen()167 void OpenGLSdlGraphics3dManager::setupScreen() {
168 	assert(_transactionMode == kTransactionActive);
169 
170 	closeOverlay();
171 
172 	_antialiasing = ConfMan.getInt("antialiasing");
173 	_lockAspectRatio = ConfMan.getBool("aspect_ratio");
174 	_vsync = ConfMan.getBool("vsync");
175 
176 #if SDL_VERSION_ATLEAST(2, 0, 0)
177 	bool needsWindowReset = false;
178 	if (_window->getSDLWindow()) {
179 		// The anti-aliasing setting cannot be changed without recreating the window.
180 		// So check if the window needs to be recreated.
181 
182 		int currentSamples = 0;
183 		#if defined(__EMSCRIPTEN__)
184 		// SDL_GL_MULTISAMPLESAMPLES isn't available on a  WebGL 1.0 context
185 		// (or not bridged in Emscripten?). This forces a windows reset.
186 		currentSamples = -1;
187 		#else
188 		SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &currentSamples);
189 		#endif
190 
191 		// When rendering to a framebuffer, MSAA is enabled on that framebuffer, not on the screen
192 		int targetSamples = shouldRenderToFramebuffer() ? 0 : _antialiasing;
193 
194 		if (currentSamples != targetSamples) {
195 			needsWindowReset = true;
196 		}
197 	}
198 
199 	// Clear the GL context when going from / to the launcher
200 	SDL_GL_DeleteContext(_glContext);
201 	_glContext = nullptr;
202 
203 	if (needsWindowReset) {
204 		_window->destroyWindow();
205 	}
206 #endif
207 
208 	createOrUpdateScreen();
209 
210 	int glflag;
211 #ifdef __MORPHOS__
212 	const GLbyte *str;
213 #else
214 	const GLubyte *str;
215 #endif
216 
217 	str = glGetString(GL_VENDOR);
218 	debug("INFO: OpenGL Vendor: %s", str);
219 	str = glGetString(GL_RENDERER);
220 	debug("INFO: OpenGL Renderer: %s", str);
221 	str = glGetString(GL_VERSION);
222 	debug("INFO: OpenGL Version: %s", str);
223 	SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &glflag);
224 	debug("INFO: OpenGL Red bits: %d", glflag);
225 	SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &glflag);
226 	debug("INFO: OpenGL Green bits: %d", glflag);
227 	SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &glflag);
228 	debug("INFO: OpenGL Blue bits: %d", glflag);
229 	SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &glflag);
230 	debug("INFO: OpenGL Alpha bits: %d", glflag);
231 	SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &glflag);
232 	debug("INFO: OpenGL Z buffer depth bits: %d", glflag);
233 	SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &glflag);
234 	debug("INFO: OpenGL Double Buffer: %d", glflag);
235 	SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &glflag);
236 	debug("INFO: OpenGL Stencil buffer bits: %d", glflag);
237 #ifdef USE_GLEW
238 	debug("INFO: GLEW Version: %s", glewGetString(GLEW_VERSION));
239 #endif
240 #ifdef USE_OPENGL_SHADERS
241 	debug("INFO: GLSL version: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
242 #endif
243 }
244 
createOrUpdateScreen()245 void OpenGLSdlGraphics3dManager::createOrUpdateScreen() {
246 	closeOverlay();
247 
248 	// If the game can't adapt to any resolution, render it to a framebuffer
249 	// so it can be scaled to fill the available space.
250 	bool engineSupportsArbitraryResolutions = !g_engine || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions);
251 	bool renderToFrameBuffer = shouldRenderToFramebuffer();
252 
253 	// Choose the effective window size or fullscreen mode
254 	uint effectiveWidth;
255 	uint effectiveHeight;
256 	if (_fullscreen && (engineSupportsArbitraryResolutions || renderToFrameBuffer)) {
257 		Common::Rect fullscreenResolution = getPreferredFullscreenResolution();
258 		effectiveWidth = fullscreenResolution.width();
259 		effectiveHeight = fullscreenResolution.height();
260 	} else {
261 		effectiveWidth = _engineRequestedWidth;
262 		effectiveHeight = _engineRequestedHeight;
263 	}
264 
265 	if (!createOrUpdateGLContext(_engineRequestedWidth, _engineRequestedHeight,
266 	                             effectiveWidth, effectiveHeight,
267 	                             renderToFrameBuffer, engineSupportsArbitraryResolutions)) {
268 		warning("SDL Error: %s", SDL_GetError());
269 		g_system->quit();
270 	}
271 
272 #ifdef USE_GLEW
273 	GLenum err = glewInit();
274 #ifdef GLEW_ERROR_NO_GLX_DISPLAY
275 	if (err == GLEW_ERROR_NO_GLX_DISPLAY) {
276 		// Wayland: https://github.com/nigels-com/glew/issues/172
277 	} else
278 #endif
279 	if (err != GLEW_OK) {
280 		warning("Error: %s", glewGetErrorString(err));
281 		g_system->quit();
282 	}
283 #endif
284 
285 #if SDL_VERSION_ATLEAST(2, 0, 1)
286 	int obtainedWidth = 0, obtainedHeight = 0;
287 	SDL_GL_GetDrawableSize(_window->getSDLWindow(), &obtainedWidth, &obtainedHeight);
288 #else
289 	int obtainedWidth = effectiveWidth;
290 	int obtainedHeight = effectiveHeight;
291 #endif
292 
293 	handleResize(obtainedWidth, obtainedHeight);
294 
295 	// Compute the rectangle where to draw the game inside the effective screen
296 	_gameRect = computeGameRect(renderToFrameBuffer, _engineRequestedWidth, _engineRequestedHeight,
297 	                            obtainedWidth, obtainedHeight);
298 
299 	initializeOpenGLContext();
300 	_surfaceRenderer = OpenGL::createBestSurfaceRenderer();
301 
302 	_overlayFormat = OpenGL::TextureGL::getRGBAPixelFormat();
303 	_overlayScreen = new OpenGL::TiledSurface(obtainedWidth, obtainedHeight, _overlayFormat);
304 
305 	_screenChangeCount++;
306 
307 #if !defined(AMIGAOS) && !defined(__MORPHOS__)
308 	if (renderToFrameBuffer) {
309 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
310 		_frameBuffer = createFramebuffer(_engineRequestedWidth, _engineRequestedHeight);
311 		_frameBuffer->attach();
312 	}
313 #endif
314 }
315 
computeGameRect(bool renderToFrameBuffer,uint gameWidth,uint gameHeight,uint screenWidth,uint screenHeight)316 Math::Rect2d OpenGLSdlGraphics3dManager::computeGameRect(bool renderToFrameBuffer, uint gameWidth, uint gameHeight,
317 													  uint screenWidth, uint screenHeight) {
318 	if (renderToFrameBuffer) {
319 		if (_lockAspectRatio) {
320 			// The game is scaled to fit the screen, keeping the same aspect ratio
321 			float scale = MIN(screenHeight / float(gameHeight), screenWidth / float(gameWidth));
322 			float scaledW = scale * (gameWidth / float(screenWidth));
323 			float scaledH = scale * (gameHeight / float(screenHeight));
324 			return Math::Rect2d(
325 					Math::Vector2d(0.5 - (0.5 * scaledW), 0.5 - (0.5 * scaledH)),
326 					Math::Vector2d(0.5 + (0.5 * scaledW), 0.5 + (0.5 * scaledH))
327 			);
328 		} else {
329 			// The game occupies the whole screen
330 			return Math::Rect2d(Math::Vector2d(0, 0), Math::Vector2d(1, 1));
331 		}
332 	} else {
333 		return Math::Rect2d(Math::Vector2d(0, 0), Math::Vector2d(1, 1));
334 	}
335 }
336 
notifyResize(const int width,const int height)337 void OpenGLSdlGraphics3dManager::notifyResize(const int width, const int height) {
338 #if SDL_VERSION_ATLEAST(2, 0, 0)
339 	// Get the updated size directly from SDL, in case there are multiple
340 	// resize events in the message queue.
341 	int newWidth = 0, newHeight = 0;
342 	SDL_GL_GetDrawableSize(_window->getSDLWindow(), &newWidth, &newHeight);
343 
344 	if (newWidth == _overlayScreen->getWidth() && newHeight == _overlayScreen->getHeight()) {
345 		return; // nothing to do
346 	}
347 
348 	// Compute the rectangle where to draw the game inside the effective screen
349 	_gameRect = computeGameRect(_frameBuffer != nullptr,
350 	                            _engineRequestedWidth, _engineRequestedHeight,
351 	                            newWidth, newHeight);
352 
353 	// Update the overlay
354 	delete _overlayScreen;
355 	_overlayScreen = new OpenGL::TiledSurface(newWidth, newHeight, _overlayFormat);
356 
357 	// Clear the overlay background so it is not displayed distorted while resizing
358 	delete _overlayBackground;
359 	_overlayBackground = nullptr;
360 
361 	_screenChangeCount++;
362 #endif
363 }
364 
initializeOpenGLContext() const365 void OpenGLSdlGraphics3dManager::initializeOpenGLContext() const {
366 	OpenGL::ContextOGLType type;
367 
368 #ifdef USE_GLES2
369 	type = OpenGL::kOGLContextGLES2;
370 #else
371 	type = OpenGL::kOGLContextGL;
372 #endif
373 
374 	OpenGLContext.initialize(type);
375 
376 #if SDL_VERSION_ATLEAST(2, 0, 0)
377 	if (SDL_GL_SetSwapInterval(_vsync ? 1 : 0)) {
378 		warning("Unable to %s VSync: %s", _vsync ? "enable" : "disable", SDL_GetError());
379 	}
380 #endif
381 }
382 
OpenGLPixelFormat(uint screenBytesPerPixel,uint red,uint blue,uint green,uint alpha,int samples)383 OpenGLSdlGraphics3dManager::OpenGLPixelFormat::OpenGLPixelFormat(uint screenBytesPerPixel, uint red, uint blue, uint green, uint alpha, int samples) :
384 		bytesPerPixel(screenBytesPerPixel),
385 		redSize(red),
386 		blueSize(blue),
387 		greenSize(green),
388 		alphaSize(alpha),
389 		multisampleSamples(samples) {
390 
391 }
392 
createOrUpdateGLContext(uint gameWidth,uint gameHeight,uint effectiveWidth,uint effectiveHeight,bool renderToFramebuffer,bool engineSupportsArbitraryResolutions)393 bool OpenGLSdlGraphics3dManager::createOrUpdateGLContext(uint gameWidth, uint gameHeight,
394 													   uint effectiveWidth, uint effectiveHeight,
395 													   bool renderToFramebuffer,
396 													   bool engineSupportsArbitraryResolutions) {
397 	// Build a list of OpenGL pixel formats usable by ScummVM
398 	Common::Array<OpenGLPixelFormat> pixelFormats;
399 	if (_antialiasing > 0 && !renderToFramebuffer) {
400 		// Don't enable screen level multisampling when rendering to a framebuffer
401 		pixelFormats.push_back(OpenGLPixelFormat(32, 8, 8, 8, 8, _antialiasing));
402 		pixelFormats.push_back(OpenGLPixelFormat(16, 5, 5, 5, 1, _antialiasing));
403 		pixelFormats.push_back(OpenGLPixelFormat(16, 5, 6, 5, 0, _antialiasing));
404 	}
405 	pixelFormats.push_back(OpenGLPixelFormat(32, 8, 8, 8, 8, 0));
406 	pixelFormats.push_back(OpenGLPixelFormat(16, 5, 5, 5, 1, 0));
407 	pixelFormats.push_back(OpenGLPixelFormat(16, 5, 6, 5, 0, 0));
408 
409 	// Unfortunatly, SDL does not provide a list of valid pixel formats
410 	// for the current OpenGL implementation and hardware.
411 	// SDL may not be able to create a screen with the preferred pixel format.
412 	// Try all the pixel formats in the list until SDL returns a valid screen.
413 	Common::Array<OpenGLPixelFormat>::const_iterator it = pixelFormats.begin();
414 	for (; it != pixelFormats.end(); it++) {
415 		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, it->redSize);
416 		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, it->greenSize);
417 		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, it->blueSize);
418 		SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, it->alphaSize);
419 		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
420 		SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
421 		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
422 		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, it->multisampleSamples > 0);
423 		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, it->multisampleSamples);
424 #if !SDL_VERSION_ATLEAST(2, 0, 0)
425 		SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, _vsync ? 1 : 0);
426 #endif
427 #if SDL_VERSION_ATLEAST(2, 0, 0)
428 #ifdef USE_GLES2
429 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
430 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
431 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
432 #else
433 		// The OpenGL implementation on AmigaOS4 is close to 1.3. Until that changes we need
434 		// to use 1.3 as version or ScummVM will cease working at all on that platform.
435 		// Profile Mask has to be 0 as well.
436 		// This will be revised and removed once AmigaOS4 supports OpenGL 2.x or OpenGLES2.
437 		#ifdef __amigaos4__
438 			SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
439 			SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
440 		#else
441 			SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
442 			SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
443 		#endif
444 #endif
445 #endif
446 
447 #if SDL_VERSION_ATLEAST(2, 0, 0)
448 		uint32 sdlflags = SDL_WINDOW_OPENGL;
449 
450 #ifdef NINTENDO_SWITCH
451 		// Switch quirk: Switch seems to need this flag, otherwise the screen
452 		// is zoomed when switching from Normal graphics mode to OpenGL
453 		sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
454 #endif
455 
456 		if (renderToFramebuffer || engineSupportsArbitraryResolutions) {
457 			sdlflags |= SDL_WINDOW_RESIZABLE;
458 		}
459 
460 		if (_fullscreen) {
461 			// On Linux/X11, when toggling to fullscreen, the window manager saves
462 			// the window size to be able to restore it when going back to windowed mode.
463 			// If the user configured ScummVM to start in fullscreen mode, we first
464 			// create a window and then toggle it to fullscreen to give the window manager
465 			// a chance to save the window size. That way if the user switches back
466 			// to windowed mode, the window manager has a window size to apply instead
467 			// of leaving the window at the fullscreen resolution size.
468 			if (!_window->getSDLWindow()) {
469 				_window->createOrUpdateWindow(gameWidth, gameHeight, sdlflags);
470 			}
471 
472 			sdlflags |= SDL_WINDOW_FULLSCREEN;
473 		}
474 
475 		if (_window->createOrUpdateWindow(effectiveWidth, effectiveHeight, sdlflags)) {
476 			// Get the current GL context from SDL in case the previous one
477 			// was destroyed because the window was recreated.
478 			_glContext = SDL_GL_GetCurrentContext();
479 			if (!_glContext) {
480 				_glContext = SDL_GL_CreateContext(_window->getSDLWindow());
481 				if (_glContext) {
482 					glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
483 				}
484 			}
485 
486 			if (_glContext) {
487 				assert(SDL_GL_GetCurrentWindow() == _window->getSDLWindow());
488 				break;
489 			}
490 		}
491 
492 		_window->destroyWindow();
493 #else
494 		uint32 sdlflags = SDL_OPENGL;
495 		if (_fullscreen)
496 			sdlflags |= SDL_FULLSCREEN;
497 
498 		SDL_Surface *screen = SDL_SetVideoMode(effectiveWidth, effectiveHeight, it->bytesPerPixel, sdlflags);
499 		if (screen) {
500 			break;
501 		}
502 #endif
503 	}
504 
505 	// Display a warning if the effective pixel format is not the preferred one
506 	if (it != pixelFormats.begin() && it != pixelFormats.end()) {
507 		bool wantsAA = pixelFormats.front().multisampleSamples > 0;
508 		bool gotAA = it->multisampleSamples > 0;
509 
510 		warning("Couldn't create a %d-bit visual%s, using to %d-bit%s instead",
511 		        pixelFormats.front().bytesPerPixel,
512 		        wantsAA && !gotAA ? " with AA" : "",
513 		        it->bytesPerPixel,
514 		        wantsAA && !gotAA ? " without AA" : "");
515 	}
516 
517 	return it != pixelFormats.end();
518 }
519 
shouldRenderToFramebuffer() const520 bool OpenGLSdlGraphics3dManager::shouldRenderToFramebuffer() const {
521 	bool engineSupportsArbitraryResolutions = !g_engine || g_engine->hasFeature(Engine::kSupportsArbitraryResolutions);
522 	return !engineSupportsArbitraryResolutions && _supportsFrameBuffer;
523 }
524 
isVSyncEnabled() const525 bool OpenGLSdlGraphics3dManager::isVSyncEnabled() const {
526 #if SDL_VERSION_ATLEAST(2, 0, 0)
527 	return SDL_GL_GetSwapInterval() != 0;
528 #else
529 	int swapControl = 0;
530 	SDL_GL_GetAttribute(SDL_GL_SWAP_CONTROL, &swapControl);
531 	return swapControl != 0;
532 #endif
533 }
534 
drawOverlay()535 void OpenGLSdlGraphics3dManager::drawOverlay() {
536 	glViewport(0, 0, _overlayScreen->getWidth(), _overlayScreen->getHeight());
537 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
538 
539 	_surfaceRenderer->prepareState();
540 
541 	if (_overlayBackground) {
542 		_overlayBackground->draw(_surfaceRenderer);
543 	}
544 
545 	_surfaceRenderer->enableAlphaBlending(true);
546 	_surfaceRenderer->setFlipY(true);
547 	_overlayScreen->draw(_surfaceRenderer);
548 
549 	_surfaceRenderer->restorePreviousState();
550 }
551 
552 #if !defined(AMIGAOS) && !defined(__MORPHOS__)
createFramebuffer(uint width,uint height)553 OpenGL::FrameBuffer *OpenGLSdlGraphics3dManager::createFramebuffer(uint width, uint height) {
554 #if !defined(USE_GLES2)
555 	if (_antialiasing && OpenGLContext.framebufferObjectMultisampleSupported) {
556 		return new OpenGL::MultiSampleFrameBuffer(width, height, _antialiasing);
557 	} else
558 #endif
559 	{
560 		return new OpenGL::FrameBuffer(width, height);
561 	}
562 }
563 #endif // AMIGAOS
564 
updateScreen()565 void OpenGLSdlGraphics3dManager::updateScreen() {
566 	if (_frameBuffer) {
567 		_frameBuffer->detach();
568 		glViewport(0, 0, _overlayScreen->getWidth(), _overlayScreen->getHeight());
569 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
570 		_surfaceRenderer->prepareState();
571 		_surfaceRenderer->render(_frameBuffer, _gameRect);
572 		_surfaceRenderer->restorePreviousState();
573 	}
574 
575 	if (_overlayVisible) {
576 		_overlayScreen->update();
577 
578 		if (_overlayBackground) {
579 			_overlayBackground->update();
580 		}
581 
582 		drawOverlay();
583 	}
584 
585 #if SDL_VERSION_ATLEAST(2, 0, 0)
586 	SDL_GL_SwapWindow(_window->getSDLWindow());
587 #else
588 	SDL_GL_SwapBuffers();
589 #endif
590 
591 	if (_frameBuffer) {
592 		_frameBuffer->attach();
593 	}
594 }
595 
getHeight() const596 int16 OpenGLSdlGraphics3dManager::getHeight() const {
597 	if (_frameBuffer)
598 		return _frameBuffer->getHeight();
599 	else
600 		return _overlayScreen->getHeight();
601 }
602 
getWidth() const603 int16 OpenGLSdlGraphics3dManager::getWidth() const {
604 	if (_frameBuffer)
605 		return _frameBuffer->getWidth();
606 	else
607 		return _overlayScreen->getWidth();
608 }
609 
610 #pragma mark -
611 #pragma mark --- Overlays ---
612 #pragma mark -
613 
showOverlay()614 void OpenGLSdlGraphics3dManager::showOverlay() {
615 	if (_overlayVisible) {
616 		return;
617 	}
618 	_overlayVisible = true;
619 
620 	delete _overlayBackground;
621 	_overlayBackground = nullptr;
622 
623 	if (g_engine) {
624 		if (_frameBuffer)
625 			_frameBuffer->detach();
626 		// If there is a game running capture the screen, so that it can be shown "below" the overlay.
627 		_overlayBackground = new OpenGL::TiledSurface(_overlayScreen->getWidth(), _overlayScreen->getHeight(), _overlayFormat);
628 		Graphics::Surface *background = _overlayBackground->getBackingSurface();
629 		glReadPixels(0, 0, background->w, background->h, GL_RGBA, GL_UNSIGNED_BYTE, background->getPixels());
630 		if (_frameBuffer)
631 			_frameBuffer->attach();
632 	}
633 }
634 
hideOverlay()635 void OpenGLSdlGraphics3dManager::hideOverlay() {
636 	if (!_overlayVisible) {
637 		return;
638 	}
639 	_overlayVisible = false;
640 
641 	delete _overlayBackground;
642 	_overlayBackground = nullptr;
643 }
644 
copyRectToOverlay(const void * buf,int pitch,int x,int y,int w,int h)645 void OpenGLSdlGraphics3dManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) {
646 	_overlayScreen->copyRectToSurface(buf, pitch, x, y, w, h);
647 }
648 
clearOverlay()649 void OpenGLSdlGraphics3dManager::clearOverlay() {
650 	_overlayScreen->fill(0);
651 }
652 
grabOverlay(Graphics::Surface & surface) const653 void OpenGLSdlGraphics3dManager::grabOverlay(Graphics::Surface &surface) const {
654 	const Graphics::Surface *overlayData = _overlayScreen->getBackingSurface();
655 
656 	assert(surface.w >= overlayData->w);
657 	assert(surface.h >= overlayData->h);
658 	assert(surface.format.bytesPerPixel == overlayData->format.bytesPerPixel);
659 
660 	const byte *src = (const byte *)overlayData->getPixels();
661 	byte *dst = (byte *)surface.getPixels();
662 	Graphics::copyBlit(dst, src, surface.pitch, overlayData->pitch, overlayData->w, overlayData->h, overlayData->format.bytesPerPixel);
663 }
664 
closeOverlay()665 void OpenGLSdlGraphics3dManager::closeOverlay() {
666 	if (_overlayScreen) {
667 		delete _overlayScreen;
668 		_overlayScreen = nullptr;
669 	}
670 
671 	delete _surfaceRenderer;
672 	_surfaceRenderer = nullptr;
673 
674 	delete _frameBuffer;
675 	_frameBuffer = nullptr;
676 
677 	OpenGL::ContextGL::destroy();
678 }
679 
getOverlayHeight() const680 int16 OpenGLSdlGraphics3dManager::getOverlayHeight() const {
681 	return _overlayScreen->getHeight();
682 }
683 
getOverlayWidth() const684 int16 OpenGLSdlGraphics3dManager::getOverlayWidth() const {
685 	return _overlayScreen->getWidth();
686 }
687 
showMouse(bool visible)688 bool OpenGLSdlGraphics3dManager::showMouse(bool visible) {
689 	SDL_ShowCursor(visible);
690 	return true;
691 }
692 
warpMouse(int x,int y)693 void OpenGLSdlGraphics3dManager::warpMouse(int x, int y) {
694 	if (!_overlayVisible && _frameBuffer) {
695 		// Scale from game coordinates to screen coordinates
696 		x = (x * _gameRect.getWidth() * _overlayScreen->getWidth()) / _frameBuffer->getWidth();
697 		y = (y * _gameRect.getHeight() * _overlayScreen->getHeight()) / _frameBuffer->getHeight();
698 
699 		x += _gameRect.getTopLeft().getX() * _overlayScreen->getWidth();
700 		y += _gameRect.getTopLeft().getY() * _overlayScreen->getHeight();
701 	}
702 
703 	_window->warpMouseInWindow(x, y);
704 }
705 
transformMouseCoordinates(Common::Point & point)706 void OpenGLSdlGraphics3dManager::transformMouseCoordinates(Common::Point &point) {
707 	if (_overlayVisible || !_frameBuffer)
708 		return;
709 
710 	// Scale from screen coordinates to game coordinates
711 	point.x -= _gameRect.getTopLeft().getX() * _overlayScreen->getWidth();
712 	point.y -= _gameRect.getTopLeft().getY() * _overlayScreen->getHeight();
713 
714 	point.x = (point.x * _frameBuffer->getWidth())  / (_gameRect.getWidth() * _overlayScreen->getWidth());
715 	point.y = (point.y * _frameBuffer->getHeight()) / (_gameRect.getHeight() * _overlayScreen->getHeight());
716 
717 	// Make sure we only supply valid coordinates.
718 	point.x = CLIP<int16>(point.x, 0, _frameBuffer->getWidth() - 1);
719 	point.y = CLIP<int16>(point.y, 0, _frameBuffer->getHeight() - 1);
720 }
721 
722 #if SDL_VERSION_ATLEAST(2, 0, 0)
deinitializeRenderer()723 void OpenGLSdlGraphics3dManager::deinitializeRenderer() {
724 	SDL_GL_DeleteContext(_glContext);
725 	_glContext = nullptr;
726 }
727 #endif // SDL_VERSION_ATLEAST(2, 0, 0)
728 
saveScreenshot(const Common::String & filename) const729 bool OpenGLSdlGraphics3dManager::saveScreenshot(const Common::String &filename) const {
730 	// Largely based on the implementation from ScummVM
731 	uint width = _overlayScreen->getWidth();
732 	uint height = _overlayScreen->getHeight();
733 
734 	uint linePaddingSize = width % 4;
735 	uint lineSize = width * 3 + linePaddingSize;
736 
737 	Common::DumpFile out;
738 	if (!out.open(filename)) {
739 		return false;
740 	}
741 
742 	Common::Array<uint8> pixels;
743 	pixels.resize(lineSize * height);
744 
745 	if (_frameBuffer) {
746 		_frameBuffer->detach();
747 	}
748 	glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &pixels.front());
749 	if (_frameBuffer) {
750 		_frameBuffer->attach();
751 	}
752 
753 #ifdef SCUMM_LITTLE_ENDIAN
754 	const Graphics::PixelFormat format(3, 8, 8, 8, 0, 0, 8, 16, 0);
755 #else
756 	const Graphics::PixelFormat format(3, 8, 8, 8, 0, 16, 8, 0, 0);
757 #endif
758 	Graphics::Surface data;
759 	data.init(width, height, lineSize, &pixels.front(), format);
760 	data.flipVertical(Common::Rect(width, height));
761 #ifdef USE_PNG
762 	return Image::writePNG(out, data);
763 #else
764 	return Image::writeBMP(out, data);
765 #endif
766 }
767 
768 #endif
769