all repos — mgba @ 33098926577f52a74730de1708a427e275a9bc88

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 "CoreController.h"
 11
 12#include <QApplication>
 13#include <QMutexLocker>
 14#include <QOffscreenSurface>
 15#include <QOpenGLContext>
 16#include <QOpenGLPaintDevice>
 17#include <QResizeEvent>
 18#include <QScreen>
 19#include <QTimer>
 20#include <QWindow>
 21
 22#include <cmath>
 23
 24#include <mgba/core/core.h>
 25#include <mgba-util/math.h>
 26#ifdef BUILD_GL
 27#include "platform/opengl/gl.h"
 28#endif
 29#ifdef BUILD_GLES2
 30#include "platform/opengl/gles2.h"
 31#ifdef _WIN32
 32#include <epoxy/wgl.h>
 33#endif
 34#endif
 35
 36using namespace QGBA;
 37
 38QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
 39
 40uint qHash(const QSurfaceFormat& format, uint seed) {
 41	QByteArray representation;
 42	QDataStream stream(&representation, QIODevice::WriteOnly);
 43	stream << format.version() << format.renderableType() << format.profile();
 44	return qHash(representation, seed);
 45}
 46
 47DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
 48	: Display(parent)
 49{
 50	setAttribute(Qt::WA_NativeWindow);
 51	windowHandle()->create();
 52
 53	m_painter = std::make_unique<PainterGL>(windowHandle(), format);
 54	m_drawThread.setObjectName("Painter Thread");
 55	m_painter->moveToThread(&m_drawThread);
 56
 57	connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
 58	connect(m_painter.get(), &PainterGL::started, this, [this] {
 59		m_hasStarted = true;
 60		resizePainter();
 61		emit drawingStarted();
 62	});
 63	m_drawThread.start();
 64}
 65
 66DisplayGL::~DisplayGL() {
 67	stopDrawing();
 68	QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
 69	m_drawThread.exit();
 70	m_drawThread.wait();
 71}
 72
 73bool DisplayGL::supportsShaders() const {
 74	return m_painter->supportsShaders();
 75}
 76
 77VideoShader* DisplayGL::shaders() {
 78	VideoShader* shaders = nullptr;
 79	QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
 80	return shaders;
 81}
 82
 83void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
 84	if (m_isDrawing) {
 85		return;
 86	}
 87	m_isDrawing = true;
 88	m_painter->setContext(controller);
 89	m_painter->setMessagePainter(messagePainter());
 90	m_context = controller;
 91	if (videoProxy()) {
 92		videoProxy()->moveToThread(&m_drawThread);
 93	}
 94
 95	lockAspectRatio(isAspectRatioLocked());
 96	lockIntegerScaling(isIntegerScalingLocked());
 97	interframeBlending(hasInterframeBlending());
 98	showOSDMessages(isShowOSD());
 99	filter(isFiltered());
100
101#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
102	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
103#else
104	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
105#endif
106	QMetaObject::invokeMethod(m_painter.get(), "start");
107	setUpdatesEnabled(false);
108}
109
110bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
111	if (!s_supports.contains(format)) {
112		QOpenGLContext context;
113		context.setFormat(format);
114		if (!context.create()) {
115			s_supports[format] = false;
116			return false;
117		}
118		auto foundVersion = context.format().version();
119		if (foundVersion == format.version()) {
120			// Match!
121			s_supports[format] = true;
122		} else if (format.version() >= qMakePair(3, 2) && foundVersion > format.version()) {
123			// At least as good
124			s_supports[format] = true;
125		} else if (format.majorVersion() == 1 && (foundVersion < qMakePair(3, 0) ||
126		           context.format().profile() == QSurfaceFormat::CompatibilityProfile ||
127		           context.format().testOption(QSurfaceFormat::DeprecatedFunctions))) {
128			// Supports the old stuff
129			s_supports[format] = true;
130		} else if (!context.isOpenGLES() && format.version() >= qMakePair(2, 1) && foundVersion < qMakePair(3, 0) && foundVersion >= qMakePair(2, 1)) {
131			// Weird edge case we support if ARB_framebuffer_object is present
132			QOffscreenSurface surface;
133			surface.create();
134			if (!context.makeCurrent(&surface)) {
135				s_supports[format] = false;
136				return false;
137			}
138			s_supports[format] = context.hasExtension("GL_ARB_framebuffer_object");
139			context.doneCurrent();
140		} else {
141			// No match
142			s_supports[format] = false;
143		}
144	}
145	return s_supports[format];
146}
147
148void DisplayGL::stopDrawing() {
149	if (m_hasStarted || m_isDrawing) {
150		m_isDrawing = false;
151		m_hasStarted = false;
152		CoreController::Interrupter interrupter(m_context);
153		QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
154		setUpdatesEnabled(true);
155	}
156	m_context.reset();
157}
158
159void DisplayGL::pauseDrawing() {
160	if (m_hasStarted) {
161		m_isDrawing = false;
162		CoreController::Interrupter interrupter(m_context);
163		QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
164#ifndef Q_OS_MAC
165		setUpdatesEnabled(true);
166#endif
167	}
168}
169
170void DisplayGL::unpauseDrawing() {
171	if (m_hasStarted) {
172		m_isDrawing = true;
173		CoreController::Interrupter interrupter(m_context);
174		QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
175#ifndef Q_OS_MAC
176		setUpdatesEnabled(false);
177#endif
178	}
179}
180
181void DisplayGL::forceDraw() {
182	if (m_hasStarted) {
183		QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
184	}
185}
186
187void DisplayGL::lockAspectRatio(bool lock) {
188	Display::lockAspectRatio(lock);
189	QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
190}
191
192void DisplayGL::lockIntegerScaling(bool lock) {
193	Display::lockIntegerScaling(lock);
194	QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
195}
196
197void DisplayGL::interframeBlending(bool enable) {
198	Display::interframeBlending(enable);
199	QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
200}
201
202void DisplayGL::showOSDMessages(bool enable) {
203	Display::showOSDMessages(enable);
204	QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
205}
206
207void DisplayGL::filter(bool filter) {
208	Display::filter(filter);
209	QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
210}
211
212void DisplayGL::framePosted() {
213	m_painter->enqueue(m_context->drawContext());
214	QMetaObject::invokeMethod(m_painter.get(), "draw");
215}
216
217void DisplayGL::setShaders(struct VDir* shaders) {
218	QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
219}
220
221void DisplayGL::clearShaders() {
222	QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
223}
224
225void DisplayGL::resizeContext() {
226	QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
227}
228
229void DisplayGL::setVideoScale(int scale) {
230	if (m_context) {
231		CoreController::Interrupter interrupter(m_context);
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		CoreController::Interrupter interrupter(m_context);
366		mCore* core = m_context->thread()->core;
367		core->reloadConfigOption(core, "videoScale", NULL);
368	}
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::setShaders(struct VDir* dir) {
578	if (!supportsShaders()) {
579		return;
580	}
581#ifdef BUILD_GLES2
582	if (m_shader.passes) {
583		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
584		mGLES2ShaderFree(&m_shader);
585	}
586	mGLES2ShaderLoad(&m_shader, dir);
587	mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
588#endif
589}
590
591void PainterGL::clearShaders() {
592	if (!supportsShaders()) {
593		return;
594	}
595#ifdef BUILD_GLES2
596	if (m_shader.passes) {
597		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
598		mGLES2ShaderFree(&m_shader);
599	}
600#endif
601}
602
603VideoShader* PainterGL::shaders() {
604	return &m_shader;
605}
606
607int PainterGL::glTex() {
608#ifdef BUILD_GLES2
609	if (supportsShaders()) {
610		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
611		return gl2Backend->tex;
612	}
613#endif
614#ifdef BUILD_GL
615	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
616	return glBackend->tex[0];
617#else
618	return -1;
619#endif
620}
621
622#endif