all repos — mgba @ c0120cd58608da068d1218796a02f2f3468608b0

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