all repos — mgba @ 8450417086c5b70904f93f2db720456d54c9cfeb

mGBA Game Boy Advance Emulator

src/platform/qt/DisplayGL.cpp (view raw)

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