all repos — mgba @ c4b38790f211b65cb15af26cebe9fc25e3c19914

mGBA Game Boy Advance Emulator

src/platform/qt/DisplayGL.cpp (view raw)

  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
 34using namespace QGBA;
 35
 36QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
 37
 38uint 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
 45DisplayGL::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->moveToThread(&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
 64DisplayGL::~DisplayGL() {
 65	stopDrawing();
 66	QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
 67	m_drawThread.exit();
 68	m_drawThread.wait();
 69}
 70
 71bool DisplayGL::supportsShaders() const {
 72	return m_painter->supportsShaders();
 73}
 74
 75VideoShader* 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
 81void 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
109bool 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
147void 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
158void DisplayGL::pauseDrawing() {
159	if (m_hasStarted) {
160		m_isDrawing = false;
161		CoreController::Interrupter interrupter(m_context);
162		QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
163#ifndef Q_OS_MAC
164		setUpdatesEnabled(true);
165#endif
166	}
167}
168
169void DisplayGL::unpauseDrawing() {
170	if (m_hasStarted) {
171		m_isDrawing = true;
172		CoreController::Interrupter interrupter(m_context);
173		QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
174#ifndef Q_OS_MAC
175		setUpdatesEnabled(false);
176#endif
177	}
178}
179
180void DisplayGL::forceDraw() {
181	if (m_hasStarted) {
182		QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
183	}
184}
185
186void DisplayGL::lockAspectRatio(bool lock) {
187	Display::lockAspectRatio(lock);
188	QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
189}
190
191void DisplayGL::lockIntegerScaling(bool lock) {
192	Display::lockIntegerScaling(lock);
193	QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
194}
195
196void DisplayGL::interframeBlending(bool enable) {
197	Display::interframeBlending(enable);
198	QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
199}
200
201void DisplayGL::showOSDMessages(bool enable) {
202	Display::showOSDMessages(enable);
203	QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
204}
205
206void DisplayGL::filter(bool filter) {
207	Display::filter(filter);
208	QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
209}
210
211void DisplayGL::framePosted() {
212	m_painter->enqueue(m_context->drawContext());
213	QMetaObject::invokeMethod(m_painter.get(), "draw");
214}
215
216void DisplayGL::setShaders(struct VDir* shaders) {
217	QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
218}
219
220void DisplayGL::clearShaders() {
221	QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
222}
223
224void DisplayGL::resizeContext() {
225	m_painter->interrupt();
226	QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
227}
228
229void DisplayGL::setVideoScale(int scale) {
230	if (m_context) {
231		m_painter->interrupt();
232		mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
233	}
234	QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
235}
236
237void DisplayGL::resizeEvent(QResizeEvent* event) {
238	Display::resizeEvent(event);
239	resizePainter();
240}
241
242void DisplayGL::resizePainter() {
243	if (m_hasStarted) {
244		QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
245	}
246}
247
248void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
249	Display::setVideoProxy(proxy);
250	if (proxy) {
251		proxy->moveToThread(&m_drawThread);
252	}
253	m_painter->setVideoProxy(proxy);
254}
255
256int DisplayGL::framebufferHandle() {
257	return m_painter->glTex();
258}
259
260PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
261	: m_surface(surface)
262	, m_format(format)
263{
264	m_supportsShaders = m_format.version() >= qMakePair(2, 0);
265	for (auto& buf : m_buffers) {
266		m_free.append(&buf.front());
267	}
268}
269
270PainterGL::~PainterGL() {
271	if (m_gl) {
272		destroy();
273	}
274}
275
276void PainterGL::makeCurrent() {
277	m_gl->makeCurrent(m_surface);
278#if defined(_WIN32) && defined(USE_EPOXY)
279	epoxy_handle_external_wglMakeCurrent();
280#endif
281}
282
283void PainterGL::create() {
284	m_gl = std::make_unique<QOpenGLContext>();
285	m_gl->setFormat(m_format);
286	m_gl->create();
287	makeCurrent();
288
289#ifdef BUILD_GL
290	mGLContext* glBackend;
291#endif
292#ifdef BUILD_GLES2
293	mGLES2Context* gl2Backend;
294#endif
295
296	m_window = std::make_unique<QOpenGLPaintDevice>();
297
298#ifdef BUILD_GLES2
299	auto version = m_format.version();
300	if (version >= qMakePair(2, 0)) {
301		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
302		mGLES2ContextCreate(gl2Backend);
303		m_backend = &gl2Backend->d;
304	}
305#endif
306
307#ifdef BUILD_GL
308	 if (!m_backend) {
309		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
310		mGLContextCreate(glBackend);
311		m_backend = &glBackend->d;
312	}
313#endif
314	m_backend->swap = [](VideoBackend* v) {
315		PainterGL* painter = static_cast<PainterGL*>(v->user);
316		if (!painter->m_gl->isValid()) {
317			return;
318		}
319		painter->m_gl->swapBuffers(painter->m_surface);
320		painter->makeCurrent();
321	};
322
323	m_backend->init(m_backend, 0);
324#ifdef BUILD_GLES2
325	if (m_supportsShaders) {
326		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
327	}
328#endif
329
330	m_backend->user = this;
331	m_backend->filter = false;
332	m_backend->lockAspectRatio = false;
333	m_backend->interframeBlending = false;
334}
335
336void PainterGL::destroy() {
337	if (!m_gl) {
338		return;
339	}
340	makeCurrent();
341#ifdef BUILD_GLES2
342	if (m_shader.passes) {
343		mGLES2ShaderFree(&m_shader);
344	}
345#endif
346	m_backend->deinit(m_backend);
347	m_gl->doneCurrent();
348	m_window.reset();
349	m_gl.reset();
350
351	free(m_backend);
352	m_backend = nullptr;
353}
354
355void PainterGL::setContext(std::shared_ptr<CoreController> context) {
356	m_context = context;
357}
358
359void PainterGL::resizeContext() {
360	if (!m_context) {
361		return;
362	}
363
364	if (m_started) {
365		mCore* core = m_context->thread()->core;
366		core->reloadConfigOption(core, "videoScale", NULL);
367	}
368	m_interrupter.resume();
369
370	QSize size = m_context->screenDimensions();
371	m_backend->setDimensions(m_backend, size.width(), size.height());
372}
373
374void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
375	m_messagePainter = messagePainter;
376}
377
378void PainterGL::resize(const QSize& size) {
379	m_size = size;
380	m_window->setSize(m_size);
381	if (m_started && !m_active) {
382		forceDraw();
383	}
384}
385
386void PainterGL::lockAspectRatio(bool lock) {
387	m_backend->lockAspectRatio = lock;
388	resize(m_size);
389}
390
391void PainterGL::lockIntegerScaling(bool lock) {
392	m_backend->lockIntegerScaling = lock;
393	resize(m_size);
394}
395
396void PainterGL::interframeBlending(bool enable) {
397	m_backend->interframeBlending = enable;
398}
399
400void PainterGL::showOSD(bool enable) {
401	m_showOSD = enable;
402}
403
404void PainterGL::filter(bool filter) {
405	m_backend->filter = filter;
406	if (m_started && !m_active) {
407		forceDraw();
408	}
409}
410
411void PainterGL::start() {
412	makeCurrent();
413
414#ifdef BUILD_GLES2
415	if (m_supportsShaders && m_shader.passes) {
416		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
417	}
418#endif
419	resizeContext();
420
421	m_buffer = nullptr;
422	m_active = true;
423	m_started = true;
424	emit started();
425}
426
427void PainterGL::draw() {
428	if (!m_active || m_queue.isEmpty()) {
429		return;
430	}
431	if (m_lagging >= 1) {
432		return;
433	}
434	mCoreSync* sync = &m_context->thread()->impl->sync;
435	if (!mCoreSyncWaitFrameStart(sync)) {
436		mCoreSyncWaitFrameEnd(sync);
437		++m_lagging;
438		if ((sync->audioWait || sync->videoFrameWait) && m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
439			QTimer::singleShot(1, this, &PainterGL::draw);
440		}
441		return;
442	}
443	dequeue();
444	if (m_videoProxy) {
445		// Only block on the next frame if we're trying to run at roughly 60fps via audio
446		m_videoProxy->setBlocking(sync->audioWait && std::abs(60.f - sync->fpsTarget) < 0.1f);
447	}
448	if (!m_delayTimer.isValid()) {
449		m_delayTimer.start();
450	} else {
451		if (sync->audioWait || sync->videoFrameWait) {
452			while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
453				QThread::usleep(500);
454			}
455		}
456		m_delayTimer.restart();
457	}
458	mCoreSyncWaitFrameEnd(sync);
459
460	performDraw();
461	m_backend->swap(m_backend);
462}
463
464void PainterGL::forceDraw() {
465	performDraw();
466	if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
467		if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
468			return;
469		}
470		m_delayTimer.restart();
471	}
472	m_backend->swap(m_backend);
473}
474
475void PainterGL::stop() {
476	m_active = false;
477	m_started = false;
478	dequeueAll();
479	if (m_context) {
480		if (m_videoProxy) {
481			m_videoProxy->detach(m_context.get());
482		}
483		m_context->setFramebufferHandle(-1);
484		m_context.reset();
485		if (m_videoProxy) {
486			m_videoProxy->processData();
487		}
488	}
489	if (m_videoProxy) {
490		m_videoProxy->reset();
491		m_videoProxy->moveToThread(m_surface->thread());
492		m_videoProxy.reset();
493	}
494	m_backend->clear(m_backend);
495	m_backend->swap(m_backend);
496}
497
498void PainterGL::pause() {
499	m_active = false;
500}
501
502void PainterGL::unpause() {
503	m_lagging = 0;
504	m_active = true;
505}
506
507void PainterGL::performDraw() {
508	m_painter.begin(m_window.get());
509	m_painter.beginNativePainting();
510	float r = m_surface->devicePixelRatio();
511	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
512	if (m_buffer) {
513		m_backend->postFrame(m_backend, m_buffer);
514	}
515	m_backend->drawFrame(m_backend);
516	m_painter.endNativePainting();
517	if (m_showOSD && m_messagePainter) {
518		m_messagePainter->paint(&m_painter);
519	}
520	m_painter.end();
521}
522
523void PainterGL::enqueue(const uint32_t* backing) {
524	QMutexLocker locker(&m_mutex);
525	uint32_t* buffer = nullptr;
526	if (backing) {
527		if (m_free.isEmpty()) {
528			buffer = m_queue.dequeue();
529		} else {
530			buffer = m_free.takeLast();
531		}
532		if (buffer) {
533			QSize size = m_context->screenDimensions();
534			memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
535		}
536	}
537	m_lagging = 0;
538	m_queue.enqueue(buffer);
539}
540
541void PainterGL::dequeue() {
542	QMutexLocker locker(&m_mutex);
543	if (m_queue.isEmpty()) {
544		return;
545	}
546	uint32_t* buffer = m_queue.dequeue();
547	if (m_buffer) {
548		m_free.append(m_buffer);
549		m_buffer = nullptr;
550	}
551	m_buffer = buffer;
552}
553
554void PainterGL::dequeueAll() {
555	uint32_t* buffer = 0;
556	m_mutex.lock();
557	while (!m_queue.isEmpty()) {
558		buffer = m_queue.dequeue();
559		if (buffer) {
560			m_free.append(buffer);
561		}
562	}
563	if (buffer) {
564		m_backend->postFrame(m_backend, buffer);
565	}
566	if (m_buffer) {
567		m_free.append(m_buffer);
568		m_buffer = nullptr;
569	}
570	m_mutex.unlock();
571}
572
573void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
574	m_videoProxy = proxy;
575}
576
577void PainterGL::interrupt() {
578	m_interrupter.interrupt(m_context);
579}
580
581void PainterGL::setShaders(struct VDir* dir) {
582	if (!supportsShaders()) {
583		return;
584	}
585#ifdef BUILD_GLES2
586	if (m_shader.passes) {
587		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
588		mGLES2ShaderFree(&m_shader);
589	}
590	mGLES2ShaderLoad(&m_shader, dir);
591	mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
592#endif
593}
594
595void PainterGL::clearShaders() {
596	if (!supportsShaders()) {
597		return;
598	}
599#ifdef BUILD_GLES2
600	if (m_shader.passes) {
601		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
602		mGLES2ShaderFree(&m_shader);
603	}
604#endif
605}
606
607VideoShader* PainterGL::shaders() {
608	return &m_shader;
609}
610
611int PainterGL::glTex() {
612#ifdef BUILD_GLES2
613	if (supportsShaders()) {
614		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
615		return gl2Backend->tex;
616	}
617#endif
618#ifdef BUILD_GL
619	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
620	return glBackend->tex[0];
621#else
622	return -1;
623#endif
624}
625
626#endif