all repos — mgba @ fc3e47a4bab1465b91e7368c6396b222f65f51cb

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