all repos — mgba @ 4fd8fe12cb30685b2cd9dbe5748c57ab9b4e240d

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