1 /* Copyright (c) 2013-2019 Jeffrey Pfau
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DisplayGL.h"
7 
8 #if defined(BUILD_GL) || defined(BUILD_GLES2)
9 
10 #include <QApplication>
11 #include <QMutexLocker>
12 #include <QOffscreenSurface>
13 #include <QOpenGLContext>
14 #include <QOpenGLPaintDevice>
15 #include <QResizeEvent>
16 #include <QScreen>
17 #include <QTimer>
18 #include <QWindow>
19 
20 #include <cmath>
21 
22 #include <mgba/core/core.h>
23 #include <mgba-util/math.h>
24 #ifdef BUILD_GL
25 #include "platform/opengl/gl.h"
26 #endif
27 #ifdef BUILD_GLES2
28 #include "platform/opengl/gles2.h"
29 #ifdef _WIN32
30 #include <epoxy/wgl.h>
31 #endif
32 #endif
33 
34 using namespace QGBA;
35 
36 QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
37 
qHash(const QSurfaceFormat & format,uint seed)38 uint qHash(const QSurfaceFormat& format, uint seed) {
39 	QByteArray representation;
40 	QDataStream stream(&representation, QIODevice::WriteOnly);
41 	stream << format.version() << format.renderableType() << format.profile();
42 	return qHash(representation, seed);
43 }
44 
DisplayGL(const QSurfaceFormat & format,QWidget * parent)45 DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
46 	: Display(parent)
47 {
48 	setAttribute(Qt::WA_NativeWindow);
49 	windowHandle()->create();
50 
51 	m_painter = std::make_unique<PainterGL>(windowHandle(), format);
52 	m_drawThread.setObjectName("Painter Thread");
53 	m_painter->setThread(&m_drawThread);
54 
55 	connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
56 	connect(m_painter.get(), &PainterGL::started, this, [this] {
57 		m_hasStarted = true;
58 		resizePainter();
59 		emit drawingStarted();
60 	});
61 	m_drawThread.start();
62 }
63 
~DisplayGL()64 DisplayGL::~DisplayGL() {
65 	stopDrawing();
66 	QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
67 	m_drawThread.exit();
68 	m_drawThread.wait();
69 }
70 
supportsShaders() const71 bool DisplayGL::supportsShaders() const {
72 	return m_painter->supportsShaders();
73 }
74 
shaders()75 VideoShader* DisplayGL::shaders() {
76 	VideoShader* shaders = nullptr;
77 	QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
78 	return shaders;
79 }
80 
startDrawing(std::shared_ptr<CoreController> controller)81 void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
82 	if (m_isDrawing) {
83 		return;
84 	}
85 	m_isDrawing = true;
86 	m_painter->setContext(controller);
87 	m_painter->setMessagePainter(messagePainter());
88 	m_context = controller;
89 	if (videoProxy()) {
90 		videoProxy()->moveToThread(&m_drawThread);
91 	}
92 
93 	lockAspectRatio(isAspectRatioLocked());
94 	lockIntegerScaling(isIntegerScalingLocked());
95 	interframeBlending(hasInterframeBlending());
96 	showOSDMessages(isShowOSD());
97 	filter(isFiltered());
98 
99 #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
100 	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
101 #else
102 	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
103 #endif
104 	CoreController::Interrupter interrupter(controller);
105 	QMetaObject::invokeMethod(m_painter.get(), "start");
106 	setUpdatesEnabled(false);
107 }
108 
supportsFormat(const QSurfaceFormat & format)109 bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
110 	if (!s_supports.contains(format)) {
111 		QOpenGLContext context;
112 		context.setFormat(format);
113 		if (!context.create()) {
114 			s_supports[format] = false;
115 			return false;
116 		}
117 		auto foundVersion = context.format().version();
118 		if (foundVersion == format.version()) {
119 			// Match!
120 			s_supports[format] = true;
121 		} else if (format.version() >= qMakePair(3, 2) && foundVersion > format.version()) {
122 			// At least as good
123 			s_supports[format] = true;
124 		} else if (format.majorVersion() == 1 && (foundVersion < qMakePair(3, 0) ||
125 		           context.format().profile() == QSurfaceFormat::CompatibilityProfile ||
126 		           context.format().testOption(QSurfaceFormat::DeprecatedFunctions))) {
127 			// Supports the old stuff
128 			s_supports[format] = true;
129 		} else if (!context.isOpenGLES() && format.version() >= qMakePair(2, 1) && foundVersion < qMakePair(3, 0) && foundVersion >= qMakePair(2, 1)) {
130 			// Weird edge case we support if ARB_framebuffer_object is present
131 			QOffscreenSurface surface;
132 			surface.create();
133 			if (!context.makeCurrent(&surface)) {
134 				s_supports[format] = false;
135 				return false;
136 			}
137 			s_supports[format] = context.hasExtension("GL_ARB_framebuffer_object");
138 			context.doneCurrent();
139 		} else {
140 			// No match
141 			s_supports[format] = false;
142 		}
143 	}
144 	return s_supports[format];
145 }
146 
stopDrawing()147 void DisplayGL::stopDrawing() {
148 	if (m_hasStarted || m_isDrawing) {
149 		m_isDrawing = false;
150 		m_hasStarted = false;
151 		CoreController::Interrupter interrupter(m_context);
152 		QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
153 		setUpdatesEnabled(true);
154 	}
155 	m_context.reset();
156 }
157 
pauseDrawing()158 void DisplayGL::pauseDrawing() {
159 	if (m_hasStarted) {
160 		m_isDrawing = false;
161 		QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
162 #ifndef Q_OS_MAC
163 		setUpdatesEnabled(true);
164 #endif
165 	}
166 }
167 
unpauseDrawing()168 void DisplayGL::unpauseDrawing() {
169 	if (m_hasStarted) {
170 		m_isDrawing = true;
171 		QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
172 #ifndef Q_OS_MAC
173 		setUpdatesEnabled(false);
174 #endif
175 	}
176 }
177 
forceDraw()178 void DisplayGL::forceDraw() {
179 	if (m_hasStarted) {
180 		QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
181 	}
182 }
183 
lockAspectRatio(bool lock)184 void DisplayGL::lockAspectRatio(bool lock) {
185 	Display::lockAspectRatio(lock);
186 	QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
187 }
188 
lockIntegerScaling(bool lock)189 void DisplayGL::lockIntegerScaling(bool lock) {
190 	Display::lockIntegerScaling(lock);
191 	QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
192 }
193 
interframeBlending(bool enable)194 void DisplayGL::interframeBlending(bool enable) {
195 	Display::interframeBlending(enable);
196 	QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
197 }
198 
showOSDMessages(bool enable)199 void DisplayGL::showOSDMessages(bool enable) {
200 	Display::showOSDMessages(enable);
201 	QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
202 }
203 
filter(bool filter)204 void DisplayGL::filter(bool filter) {
205 	Display::filter(filter);
206 	QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
207 }
208 
framePosted()209 void DisplayGL::framePosted() {
210 	m_painter->enqueue(m_context->drawContext());
211 	QMetaObject::invokeMethod(m_painter.get(), "draw");
212 }
213 
setShaders(struct VDir * shaders)214 void DisplayGL::setShaders(struct VDir* shaders) {
215 	QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
216 }
217 
clearShaders()218 void DisplayGL::clearShaders() {
219 	QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
220 }
221 
resizeContext()222 void DisplayGL::resizeContext() {
223 	m_painter->interrupt();
224 	QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
225 }
226 
setVideoScale(int scale)227 void DisplayGL::setVideoScale(int scale) {
228 	if (m_context) {
229 		m_painter->interrupt();
230 		mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
231 	}
232 	QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
233 }
234 
resizeEvent(QResizeEvent * event)235 void DisplayGL::resizeEvent(QResizeEvent* event) {
236 	Display::resizeEvent(event);
237 	resizePainter();
238 }
239 
resizePainter()240 void DisplayGL::resizePainter() {
241 	if (m_hasStarted) {
242 		QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
243 	}
244 }
245 
setVideoProxy(std::shared_ptr<VideoProxy> proxy)246 void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
247 	Display::setVideoProxy(proxy);
248 	if (proxy) {
249 		proxy->moveToThread(&m_drawThread);
250 	}
251 	m_painter->setVideoProxy(proxy);
252 }
253 
framebufferHandle()254 int DisplayGL::framebufferHandle() {
255 	return m_painter->glTex();
256 }
257 
PainterGL(QWindow * surface,const QSurfaceFormat & format)258 PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
259 	: m_surface(surface)
260 	, m_format(format)
261 {
262 	m_supportsShaders = m_format.version() >= qMakePair(2, 0);
263 	for (auto& buf : m_buffers) {
264 		m_free.append(&buf.front());
265 	}
266 	connect(&m_drawTimer, &QTimer::timeout, this, &PainterGL::draw);
267 	m_drawTimer.setSingleShot(true);
268 }
269 
~PainterGL()270 PainterGL::~PainterGL() {
271 	if (m_gl) {
272 		destroy();
273 	}
274 }
275 
setThread(QThread * thread)276 void PainterGL::setThread(QThread* thread) {
277 	moveToThread(thread);
278 	m_drawTimer.moveToThread(thread);
279 }
280 
makeCurrent()281 void PainterGL::makeCurrent() {
282 	m_gl->makeCurrent(m_surface);
283 #if defined(_WIN32) && defined(USE_EPOXY)
284 	epoxy_handle_external_wglMakeCurrent();
285 #endif
286 }
287 
create()288 void PainterGL::create() {
289 	m_gl = std::make_unique<QOpenGLContext>();
290 	m_gl->setFormat(m_format);
291 	m_gl->create();
292 	makeCurrent();
293 
294 #ifdef BUILD_GL
295 	mGLContext* glBackend;
296 #endif
297 #ifdef BUILD_GLES2
298 	mGLES2Context* gl2Backend;
299 #endif
300 
301 	m_window = std::make_unique<QOpenGLPaintDevice>();
302 
303 #ifdef BUILD_GLES2
304 	auto version = m_format.version();
305 	if (version >= qMakePair(2, 0)) {
306 		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
307 		mGLES2ContextCreate(gl2Backend);
308 		m_backend = &gl2Backend->d;
309 	}
310 #endif
311 
312 #ifdef BUILD_GL
313 	 if (!m_backend) {
314 		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
315 		mGLContextCreate(glBackend);
316 		m_backend = &glBackend->d;
317 	}
318 #endif
319 	m_backend->swap = [](VideoBackend* v) {
320 		PainterGL* painter = static_cast<PainterGL*>(v->user);
321 		if (!painter->m_gl->isValid()) {
322 			return;
323 		}
324 		painter->m_gl->swapBuffers(painter->m_surface);
325 		painter->makeCurrent();
326 	};
327 
328 	m_backend->init(m_backend, 0);
329 #ifdef BUILD_GLES2
330 	if (m_supportsShaders) {
331 		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
332 	}
333 #endif
334 
335 	m_backend->user = this;
336 	m_backend->filter = false;
337 	m_backend->lockAspectRatio = false;
338 	m_backend->interframeBlending = false;
339 }
340 
destroy()341 void PainterGL::destroy() {
342 	if (!m_gl) {
343 		return;
344 	}
345 	makeCurrent();
346 #ifdef BUILD_GLES2
347 	if (m_shader.passes) {
348 		mGLES2ShaderFree(&m_shader);
349 	}
350 #endif
351 	m_backend->deinit(m_backend);
352 	m_gl->doneCurrent();
353 	m_window.reset();
354 	m_gl.reset();
355 
356 	free(m_backend);
357 	m_backend = nullptr;
358 }
359 
setContext(std::shared_ptr<CoreController> context)360 void PainterGL::setContext(std::shared_ptr<CoreController> context) {
361 	m_context = context;
362 }
363 
resizeContext()364 void PainterGL::resizeContext() {
365 	if (!m_context) {
366 		return;
367 	}
368 
369 	if (m_started) {
370 		mCore* core = m_context->thread()->core;
371 		core->reloadConfigOption(core, "videoScale", NULL);
372 	}
373 	m_interrupter.resume();
374 
375 	QSize size = m_context->screenDimensions();
376 	if (m_dims == size) {
377 		return;
378 	}
379 	dequeueAll(false);
380 	m_backend->setDimensions(m_backend, size.width(), size.height());
381 }
382 
setMessagePainter(MessagePainter * messagePainter)383 void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
384 	m_messagePainter = messagePainter;
385 }
386 
resize(const QSize & size)387 void PainterGL::resize(const QSize& size) {
388 	m_size = size;
389 	m_window->setSize(m_size);
390 	if (m_started && !m_active) {
391 		forceDraw();
392 	}
393 }
394 
lockAspectRatio(bool lock)395 void PainterGL::lockAspectRatio(bool lock) {
396 	m_backend->lockAspectRatio = lock;
397 	resize(m_size);
398 }
399 
lockIntegerScaling(bool lock)400 void PainterGL::lockIntegerScaling(bool lock) {
401 	m_backend->lockIntegerScaling = lock;
402 	resize(m_size);
403 }
404 
interframeBlending(bool enable)405 void PainterGL::interframeBlending(bool enable) {
406 	m_backend->interframeBlending = enable;
407 }
408 
showOSD(bool enable)409 void PainterGL::showOSD(bool enable) {
410 	m_showOSD = enable;
411 }
412 
filter(bool filter)413 void PainterGL::filter(bool filter) {
414 	m_backend->filter = filter;
415 	if (m_started && !m_active) {
416 		forceDraw();
417 	}
418 }
419 
start()420 void PainterGL::start() {
421 	makeCurrent();
422 
423 #ifdef BUILD_GLES2
424 	if (m_supportsShaders && m_shader.passes) {
425 		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
426 	}
427 #endif
428 	resizeContext();
429 
430 	m_buffer = nullptr;
431 	m_active = true;
432 	m_started = true;
433 	emit started();
434 }
435 
draw()436 void PainterGL::draw() {
437 	if (!m_started || m_queue.isEmpty()) {
438 		return;
439 	}
440 	mCoreSync* sync = &m_context->thread()->impl->sync;
441 	if (!mCoreSyncWaitFrameStart(sync)) {
442 		mCoreSyncWaitFrameEnd(sync);
443 		if (!sync->audioWait && !sync->videoFrameWait) {
444 			return;
445 		}
446 		if (m_delayTimer.elapsed() >= 1000 / m_surface->screen()->refreshRate()) {
447 			return;
448 		}
449 		if (!m_drawTimer.isActive()) {
450 			m_drawTimer.start(1);
451 		}
452 		return;
453 	}
454 	dequeue();
455 	bool forceRedraw = !m_videoProxy;
456 	if (!m_delayTimer.isValid()) {
457 		m_delayTimer.start();
458 	} else {
459 		if (sync->audioWait || sync->videoFrameWait) {
460 			while (m_delayTimer.nsecsElapsed() + 1'000'000 < 1'000'000'000 / sync->fpsTarget) {
461 				QThread::usleep(500);
462 			}
463 			forceRedraw = sync->videoFrameWait;
464 		}
465 		if (!forceRedraw) {
466 			forceRedraw = m_delayTimer.nsecsElapsed() + 1'000'000 >= 1'000'000'000 / m_surface->screen()->refreshRate();
467 		}
468 	}
469 	mCoreSyncWaitFrameEnd(sync);
470 
471 	if (forceRedraw) {
472 		m_delayTimer.restart();
473 		performDraw();
474 		m_backend->swap(m_backend);
475 	}
476 
477 	QMutexLocker locker(&m_mutex);
478 	if (!m_queue.isEmpty()) {
479 		QTimer::singleShot(1, this, &PainterGL::draw);
480 	}
481 }
482 
forceDraw()483 void PainterGL::forceDraw() {
484 	performDraw();
485 	if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
486 		if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
487 			return;
488 		}
489 		m_delayTimer.restart();
490 	}
491 	m_backend->swap(m_backend);
492 }
493 
stop()494 void PainterGL::stop() {
495 	m_drawTimer.stop();
496 	m_active = false;
497 	m_started = false;
498 	dequeueAll(false);
499 	if (m_context) {
500 		if (m_videoProxy) {
501 			m_videoProxy->detach(m_context.get());
502 		}
503 		m_context->setFramebufferHandle(-1);
504 		m_context.reset();
505 		if (m_videoProxy) {
506 			m_videoProxy->processData();
507 		}
508 	}
509 	if (m_videoProxy) {
510 		m_videoProxy->reset();
511 		m_videoProxy->moveToThread(m_surface->thread());
512 		m_videoProxy.reset();
513 	}
514 	m_backend->clear(m_backend);
515 	m_backend->swap(m_backend);
516 }
517 
pause()518 void PainterGL::pause() {
519 	m_drawTimer.stop();
520 	m_active = false;
521 	dequeueAll(true);
522 }
523 
unpause()524 void PainterGL::unpause() {
525 	m_active = true;
526 }
527 
performDraw()528 void PainterGL::performDraw() {
529 	m_painter.begin(m_window.get());
530 	m_painter.beginNativePainting();
531 	float r = m_surface->devicePixelRatio();
532 	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
533 	if (m_buffer) {
534 		m_backend->postFrame(m_backend, m_buffer);
535 	}
536 	m_backend->drawFrame(m_backend);
537 	m_painter.endNativePainting();
538 	if (m_showOSD && m_messagePainter) {
539 		m_messagePainter->paint(&m_painter);
540 	}
541 	m_painter.end();
542 }
543 
enqueue(const uint32_t * backing)544 void PainterGL::enqueue(const uint32_t* backing) {
545 	QMutexLocker locker(&m_mutex);
546 	uint32_t* buffer = nullptr;
547 	if (backing) {
548 		if (m_free.isEmpty()) {
549 			buffer = m_queue.dequeue();
550 		} else {
551 			buffer = m_free.takeLast();
552 		}
553 		if (buffer) {
554 			QSize size = m_context->screenDimensions();
555 			memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
556 		}
557 	}
558 	m_queue.enqueue(buffer);
559 }
560 
dequeue()561 void PainterGL::dequeue() {
562 	QMutexLocker locker(&m_mutex);
563 	if (m_queue.isEmpty()) {
564 		return;
565 	}
566 	uint32_t* buffer = m_queue.dequeue();
567 	if (m_buffer) {
568 		m_free.append(m_buffer);
569 		m_buffer = nullptr;
570 	}
571 	m_buffer = buffer;
572 }
573 
dequeueAll(bool keep)574 void PainterGL::dequeueAll(bool keep) {
575 	QMutexLocker locker(&m_mutex);
576 	uint32_t* buffer = 0;
577 	while (!m_queue.isEmpty()) {
578 		buffer = m_queue.dequeue();
579 		if (keep) {
580 			if (m_buffer && buffer) {
581 				m_free.append(m_buffer);
582 				m_buffer = buffer;
583 			}
584 		} else if (buffer) {
585 			m_free.append(buffer);
586 		}
587 	}
588 	if (m_buffer && !keep) {
589 		m_free.append(m_buffer);
590 		m_buffer = nullptr;
591 	}
592 }
593 
setVideoProxy(std::shared_ptr<VideoProxy> proxy)594 void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
595 	m_videoProxy = proxy;
596 }
597 
interrupt()598 void PainterGL::interrupt() {
599 	m_interrupter.interrupt(m_context);
600 }
601 
setShaders(struct VDir * dir)602 void PainterGL::setShaders(struct VDir* dir) {
603 	if (!supportsShaders()) {
604 		return;
605 	}
606 #ifdef BUILD_GLES2
607 	if (m_shader.passes) {
608 		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
609 		mGLES2ShaderFree(&m_shader);
610 	}
611 	mGLES2ShaderLoad(&m_shader, dir);
612 	mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
613 #endif
614 }
615 
clearShaders()616 void PainterGL::clearShaders() {
617 	if (!supportsShaders()) {
618 		return;
619 	}
620 #ifdef BUILD_GLES2
621 	if (m_shader.passes) {
622 		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
623 		mGLES2ShaderFree(&m_shader);
624 	}
625 #endif
626 }
627 
shaders()628 VideoShader* PainterGL::shaders() {
629 	return &m_shader;
630 }
631 
glTex()632 int PainterGL::glTex() {
633 #ifdef BUILD_GLES2
634 	if (supportsShaders()) {
635 		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
636 		return gl2Backend->tex;
637 	}
638 #endif
639 #ifdef BUILD_GL
640 	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
641 	return glBackend->tex[0];
642 #else
643 	return -1;
644 #endif
645 }
646 
647 #endif
648