all repos — mgba @ 5bcf243139275654fe879b05c3d931a337c0fe90

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 (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 if (sync->audioWait || sync->videoFrameWait) {
457		while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
458			QThread::usleep(500);
459		}
460	}
461	mCoreSyncWaitFrameEnd(sync);
462
463	performDraw();
464	m_backend->swap(m_backend);
465	m_delayTimer.restart();
466}
467
468void PainterGL::forceDraw() {
469	performDraw();
470	if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
471		if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
472			return;
473		}
474		m_delayTimer.restart();
475	}
476	m_backend->swap(m_backend);
477}
478
479void PainterGL::stop() {
480	m_active = false;
481	m_started = false;
482	dequeueAll();
483	m_backend->clear(m_backend);
484	m_backend->swap(m_backend);
485	if (m_videoProxy) {
486		m_videoProxy->reset();
487	}
488	destroy();
489	moveToThread(m_surface->thread());
490	if (m_videoProxy) {
491		m_videoProxy->moveToThread(m_surface->thread());
492	}
493}
494
495void PainterGL::pause() {
496	m_active = false;
497}
498
499void PainterGL::unpause() {
500	m_lagging = 0;
501	m_active = true;
502}
503
504void PainterGL::performDraw() {
505	m_painter.begin(m_window.get());
506	m_painter.beginNativePainting();
507	float r = m_surface->devicePixelRatio();
508	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
509	if (m_buffer) {
510		m_backend->postFrame(m_backend, m_buffer);
511	}
512	m_backend->drawFrame(m_backend);
513	m_painter.endNativePainting();
514	if (m_showOSD && m_messagePainter) {
515		m_messagePainter->paint(&m_painter);
516	}
517	m_painter.end();
518}
519
520void PainterGL::enqueue(const uint32_t* backing) {
521	QMutexLocker locker(&m_mutex);
522	uint32_t* buffer = nullptr;
523	if (backing) {
524		if (m_free.isEmpty()) {
525			buffer = m_queue.dequeue();
526		} else {
527			buffer = m_free.takeLast();
528		}
529		if (buffer) {
530			QSize size = m_context->screenDimensions();
531			memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
532		}
533	}
534	m_lagging = 0;
535	m_queue.enqueue(buffer);
536}
537
538void PainterGL::dequeue() {
539	QMutexLocker locker(&m_mutex);
540	if (m_queue.isEmpty()) {
541		return;
542	}
543	uint32_t* buffer = m_queue.dequeue();
544	if (m_buffer) {
545		m_free.append(m_buffer);
546		m_buffer = nullptr;
547	}
548	m_buffer = buffer;
549	return;
550}
551
552void PainterGL::dequeueAll() {
553	uint32_t* buffer = 0;
554	m_mutex.lock();
555	while (!m_queue.isEmpty()) {
556		buffer = m_queue.dequeue();
557		if (buffer) {
558			m_free.append(buffer);
559		}
560	}
561	if (buffer) {
562		m_backend->postFrame(m_backend, buffer);
563	}
564	if (m_buffer) {
565		m_free.append(m_buffer);
566		m_buffer = nullptr;
567	}
568	m_mutex.unlock();
569}
570
571void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
572	m_videoProxy = proxy;
573}
574
575void PainterGL::setShaders(struct VDir* dir) {
576	if (!supportsShaders()) {
577		return;
578	}
579#ifdef BUILD_GLES2
580	if (!m_started) {
581		return; // TODO
582	}
583	if (m_shader.passes) {
584		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
585		mGLES2ShaderFree(&m_shader);
586	}
587	mGLES2ShaderLoad(&m_shader, dir);
588	mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
589#endif
590}
591
592void PainterGL::clearShaders() {
593	if (!supportsShaders()) {
594		return;
595	}
596#ifdef BUILD_GLES2
597	if (!m_started) {
598		return; // TODO
599	}
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