all repos — mgba @ 24929909f28df24461e62f36e80f0a60675facd3

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_gl->context()->makeCurrent();
104#if defined(_WIN32) && defined(USE_EPOXY)
105		epoxy_handle_external_wglMakeCurrent();
106#endif
107	}
108	m_context.reset();
109}
110
111void DisplayGL::pauseDrawing() {
112	if (m_drawThread) {
113		m_isDrawing = false;
114		CoreController::Interrupter interrupter(m_context);
115		QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
116	}
117}
118
119void DisplayGL::unpauseDrawing() {
120	if (m_drawThread) {
121		m_isDrawing = true;
122		CoreController::Interrupter interrupter(m_context);
123		QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
124	}
125}
126
127void DisplayGL::forceDraw() {
128	if (m_drawThread) {
129		QMetaObject::invokeMethod(m_painter, "forceDraw");
130	}
131}
132
133void DisplayGL::lockAspectRatio(bool lock) {
134	Display::lockAspectRatio(lock);
135	if (m_drawThread) {
136		QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
137	}
138}
139
140void DisplayGL::lockIntegerScaling(bool lock) {
141	Display::lockIntegerScaling(lock);
142	if (m_drawThread) {
143		QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock));
144	}
145}
146
147void DisplayGL::filter(bool filter) {
148	Display::filter(filter);
149	if (m_drawThread) {
150		QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
151	}
152}
153
154void DisplayGL::framePosted() {
155	if (m_drawThread) {
156		m_painter->enqueue(m_context->drawContext());
157		QMetaObject::invokeMethod(m_painter, "draw");
158	}
159}
160
161void DisplayGL::setShaders(struct VDir* shaders) {
162	if (m_drawThread) {
163		QMetaObject::invokeMethod(m_painter, "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
164	} else {
165		m_painter->setShaders(shaders);
166	}
167}
168
169void DisplayGL::clearShaders() {
170	QMetaObject::invokeMethod(m_painter, "clearShaders");
171}
172
173
174void DisplayGL::resizeContext() {
175	if (m_drawThread) {
176		m_isDrawing = false;
177		CoreController::Interrupter interrupter(m_context);
178		QMetaObject::invokeMethod(m_painter, "resizeContext", Qt::BlockingQueuedConnection);
179	}
180}
181
182void DisplayGL::resizeEvent(QResizeEvent* event) {
183	Display::resizeEvent(event);
184	resizePainter();
185}
186
187void DisplayGL::resizePainter() {
188	if (m_gl) {
189		m_gl->resize(size());
190	}
191	if (m_drawThread) {
192		QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
193	}
194}
195
196VideoProxy* DisplayGL::videoProxy() {
197	if (supportsShaders()) {
198		return &m_videoProxy;
199	}
200	return nullptr;
201}
202
203int DisplayGL::framebufferHandle() {
204	return m_painter->glTex();
205}
206
207PainterGL::PainterGL(int majorVersion, VideoProxy* proxy, QGLWidget* parent)
208	: m_gl(parent)
209	, m_videoProxy(proxy)
210{
211#ifdef BUILD_GL
212	mGLContext* glBackend;
213#endif
214#ifdef BUILD_GLES2
215	mGLES2Context* gl2Backend;
216#endif
217
218	m_gl->makeCurrent();
219#if defined(_WIN32) && defined(USE_EPOXY)
220	epoxy_handle_external_wglMakeCurrent();
221#endif
222
223	QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
224
225#ifdef BUILD_GLES2
226	if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) {
227		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
228		mGLES2ContextCreate(gl2Backend);
229		m_backend = &gl2Backend->d;
230		m_supportsShaders = true;
231	}
232#endif
233
234#ifdef BUILD_GL
235	 if (!m_backend) {
236		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
237		mGLContextCreate(glBackend);
238		m_backend = &glBackend->d;
239		m_supportsShaders = false;
240	}
241#endif
242	m_backend->swap = [](VideoBackend* v) {
243		PainterGL* painter = static_cast<PainterGL*>(v->user);
244		if (!painter->m_gl->isVisible()) {
245			return;
246		}
247		painter->m_gl->swapBuffers();
248	};
249
250	m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
251#ifdef BUILD_GLES2
252	if (m_supportsShaders) {
253		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
254	}
255#endif
256	m_gl->doneCurrent();
257
258	m_backend->user = this;
259	m_backend->filter = false;
260	m_backend->lockAspectRatio = false;
261
262	for (int i = 0; i < 2; ++i) {
263		m_free.append(new uint32_t[1024 * 2048]);
264	}
265}
266
267PainterGL::~PainterGL() {
268	while (!m_queue.isEmpty()) {
269		delete[] m_queue.dequeue();
270	}
271	for (auto item : m_free) {
272		delete[] item;
273	}
274	m_gl->makeCurrent();
275#if defined(_WIN32) && defined(USE_EPOXY)
276	epoxy_handle_external_wglMakeCurrent();
277#endif
278#ifdef BUILD_GLES2
279	if (m_shader.passes) {
280		mGLES2ShaderFree(&m_shader);
281	}
282#endif
283	m_backend->deinit(m_backend);
284	m_gl->doneCurrent();
285	free(m_backend);
286	m_backend = nullptr;
287}
288
289void PainterGL::setContext(std::shared_ptr<CoreController> context) {
290	m_context = context;
291	resizeContext();
292}
293
294void PainterGL::resizeContext() {
295	if (!m_context) {
296		return;
297	}
298
299	if (!m_active) {
300		m_gl->makeCurrent();
301#if defined(_WIN32) && defined(USE_EPOXY)
302		epoxy_handle_external_wglMakeCurrent();
303#endif
304	}
305
306	QSize size = m_context->screenDimensions();
307	m_backend->setDimensions(m_backend, size.width(), size.height());
308	if (!m_active) {
309		m_gl->doneCurrent();
310	}
311}
312
313void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
314	m_messagePainter = messagePainter;
315}
316
317void PainterGL::resize(const QSize& size) {
318	m_size = size;
319	if (m_started && !m_active) {
320		forceDraw();
321	}
322}
323
324void PainterGL::lockAspectRatio(bool lock) {
325	m_backend->lockAspectRatio = lock;
326	if (m_started && !m_active) {
327		forceDraw();
328	}
329}
330
331void PainterGL::lockIntegerScaling(bool lock) {
332	m_backend->lockIntegerScaling = lock;
333	if (m_started && !m_active) {
334		forceDraw();
335	}
336}
337
338void PainterGL::filter(bool filter) {
339	m_backend->filter = filter;
340	if (m_started && !m_active) {
341		forceDraw();
342	}
343}
344
345void PainterGL::start() {
346	m_gl->makeCurrent();
347#if defined(_WIN32) && defined(USE_EPOXY)
348	epoxy_handle_external_wglMakeCurrent();
349#endif
350
351#ifdef BUILD_GLES2
352	if (m_supportsShaders && m_shader.passes) {
353		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
354	}
355#endif
356
357	m_active = true;
358	m_started = true;
359}
360
361void PainterGL::draw() {
362	if (m_queue.isEmpty()) {
363		return;
364	}
365
366	if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
367		dequeue();
368		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
369		m_painter.begin(m_gl->context()->device());
370		performDraw();
371		m_painter.end();
372		m_backend->swap(m_backend);
373		if (!m_delayTimer.isValid()) {
374			m_delayTimer.start();
375		} else if (m_gl->format().swapInterval()) {
376			while (m_delayTimer.elapsed() < 15) {
377				QThread::usleep(100);
378			}
379			m_delayTimer.restart();
380		}
381	} else {
382		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
383	}
384	if (!m_queue.isEmpty()) {
385		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
386	}
387}
388
389void PainterGL::forceDraw() {
390	m_painter.begin(m_gl->context()->device());
391	performDraw();
392	m_painter.end();
393	m_backend->swap(m_backend);
394}
395
396void PainterGL::stop() {
397	m_active = false;
398	m_started = false;
399	dequeueAll();
400	m_backend->clear(m_backend);
401	m_backend->swap(m_backend);
402	m_gl->doneCurrent();
403	m_gl->context()->moveToThread(m_gl->thread());
404	m_context.reset();
405	moveToThread(m_gl->thread());
406	m_videoProxy->moveToThread(m_gl->thread());
407}
408
409void PainterGL::pause() {
410	m_active = false;
411}
412
413void PainterGL::unpause() {
414	m_active = true;
415}
416
417void PainterGL::performDraw() {
418	m_painter.beginNativePainting();
419#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
420	float r = m_gl->devicePixelRatioF();
421#else
422	float r = m_gl->devicePixelRatio();
423#endif
424	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
425	m_backend->drawFrame(m_backend);
426	m_painter.endNativePainting();
427	if (m_messagePainter) {
428		m_messagePainter->paint(&m_painter);
429	}
430}
431
432void PainterGL::enqueue(const uint32_t* backing) {
433	m_mutex.lock();
434	uint32_t* buffer = nullptr;
435	if (backing) {
436		if (m_free.isEmpty()) {
437			buffer = m_queue.dequeue();
438		} else {
439			buffer = m_free.takeLast();
440		}
441		QSize size = m_context->screenDimensions();
442		memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
443	}
444	m_queue.enqueue(buffer);
445	m_mutex.unlock();
446}
447
448void PainterGL::dequeue() {
449	m_mutex.lock();
450	if (m_queue.isEmpty()) {
451		m_mutex.unlock();
452		return;
453	}
454	uint32_t* buffer = m_queue.dequeue();
455	if (buffer) {
456		m_backend->postFrame(m_backend, buffer);
457		m_free.append(buffer);
458	}
459	m_mutex.unlock();
460}
461
462void PainterGL::dequeueAll() {
463	uint32_t* buffer = 0;
464	m_mutex.lock();
465	while (!m_queue.isEmpty()) {
466		buffer = m_queue.dequeue();
467		if (buffer) {
468			m_free.append(buffer);
469		}
470	}
471	if (buffer) {
472		m_backend->postFrame(m_backend, buffer);
473	}
474	m_mutex.unlock();
475}
476
477void PainterGL::setShaders(struct VDir* dir) {
478	if (!supportsShaders()) {
479		return;
480	}
481	if (!m_active) {
482#ifdef BUILD_GLES2
483		m_gl->makeCurrent();
484#if defined(_WIN32) && defined(USE_EPOXY)
485		epoxy_handle_external_wglMakeCurrent();
486#endif
487	}
488	if (m_shader.passes) {
489		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
490		mGLES2ShaderFree(&m_shader);
491	}
492	mGLES2ShaderLoad(&m_shader, dir);
493	if (m_started) {
494		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
495	}
496	if (!m_active) {
497		m_gl->doneCurrent();
498	}
499#endif
500}
501
502void PainterGL::clearShaders() {
503	if (!supportsShaders()) {
504		return;
505	}
506	if (!m_active) {
507#ifdef BUILD_GLES2
508		m_gl->makeCurrent();
509#if defined(_WIN32) && defined(USE_EPOXY)
510		epoxy_handle_external_wglMakeCurrent();
511#endif
512	}
513	if (m_shader.passes) {
514		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
515		mGLES2ShaderFree(&m_shader);
516	}
517	if (!m_active) {
518		m_gl->doneCurrent();
519	}
520#endif
521}
522
523VideoShader* PainterGL::shaders() {
524	return &m_shader;
525}
526
527int PainterGL::glTex() {
528#ifdef BUILD_GLES2
529	if (supportsShaders()) {
530		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
531		return gl2Backend->tex;
532	}
533#endif
534#ifdef BUILD_GL
535	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
536	return glBackend->tex;
537#else
538	return -1;
539#endif
540}
541
542#endif