all repos — mgba @ 1baa9287f34855f9eaceab89eb7703cb928f5599

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 (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
320	float r = m_gl->devicePixelRatioF();
321#else
322	float r = m_gl->devicePixelRatio();
323#endif
324	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
325	if (m_started && !m_active) {
326		forceDraw();
327	}
328}
329
330void PainterGL::lockAspectRatio(bool lock) {
331	m_backend->lockAspectRatio = lock;
332	resize(m_size);
333}
334
335void PainterGL::lockIntegerScaling(bool lock) {
336	m_backend->lockIntegerScaling = lock;
337	resize(m_size);
338}
339
340void PainterGL::filter(bool filter) {
341	m_backend->filter = filter;
342	if (m_started && !m_active) {
343		forceDraw();
344	}
345}
346
347void PainterGL::start() {
348	m_gl->makeCurrent();
349#if defined(_WIN32) && defined(USE_EPOXY)
350	epoxy_handle_external_wglMakeCurrent();
351#endif
352
353#ifdef BUILD_GLES2
354	if (m_supportsShaders && m_shader.passes) {
355		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
356	}
357#endif
358
359	m_active = true;
360	m_started = true;
361}
362
363void PainterGL::draw() {
364	if (m_queue.isEmpty()) {
365		return;
366	}
367
368	if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
369		dequeue();
370		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
371		m_painter.begin(m_gl->context()->device());
372		performDraw();
373		m_painter.end();
374		m_backend->swap(m_backend);
375		if (!m_delayTimer.isValid()) {
376			m_delayTimer.start();
377		} else if (m_gl->format().swapInterval()) {
378			while (m_delayTimer.elapsed() < 15) {
379				QThread::usleep(100);
380			}
381			m_delayTimer.restart();
382		}
383	} else {
384		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
385	}
386	if (!m_queue.isEmpty()) {
387		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
388	}
389}
390
391void PainterGL::forceDraw() {
392	m_painter.begin(m_gl->context()->device());
393	performDraw();
394	m_painter.end();
395	m_backend->swap(m_backend);
396}
397
398void PainterGL::stop() {
399	m_active = false;
400	m_started = false;
401	dequeueAll();
402	m_backend->clear(m_backend);
403	m_backend->swap(m_backend);
404	m_gl->doneCurrent();
405	m_gl->context()->moveToThread(m_gl->thread());
406	m_context.reset();
407	moveToThread(m_gl->thread());
408	m_videoProxy->moveToThread(m_gl->thread());
409}
410
411void PainterGL::pause() {
412	m_active = false;
413}
414
415void PainterGL::unpause() {
416	m_active = true;
417}
418
419void PainterGL::performDraw() {
420	m_painter.beginNativePainting();
421	m_backend->drawFrame(m_backend);
422	m_painter.endNativePainting();
423	if (m_messagePainter) {
424		m_messagePainter->paint(&m_painter);
425	}
426}
427
428void PainterGL::enqueue(const uint32_t* backing) {
429	m_mutex.lock();
430	uint32_t* buffer = nullptr;
431	if (backing) {
432		if (m_free.isEmpty()) {
433			buffer = m_queue.dequeue();
434		} else {
435			buffer = m_free.takeLast();
436		}
437		QSize size = m_context->screenDimensions();
438		memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
439	}
440	m_queue.enqueue(buffer);
441	m_mutex.unlock();
442}
443
444void PainterGL::dequeue() {
445	m_mutex.lock();
446	if (m_queue.isEmpty()) {
447		m_mutex.unlock();
448		return;
449	}
450	uint32_t* buffer = m_queue.dequeue();
451	if (buffer) {
452		m_backend->postFrame(m_backend, buffer);
453		m_free.append(buffer);
454	}
455	m_mutex.unlock();
456}
457
458void PainterGL::dequeueAll() {
459	uint32_t* buffer = 0;
460	m_mutex.lock();
461	while (!m_queue.isEmpty()) {
462		buffer = m_queue.dequeue();
463		if (buffer) {
464			m_free.append(buffer);
465		}
466	}
467	if (buffer) {
468		m_backend->postFrame(m_backend, buffer);
469	}
470	m_mutex.unlock();
471}
472
473void PainterGL::setShaders(struct VDir* dir) {
474	if (!supportsShaders()) {
475		return;
476	}
477	if (!m_active) {
478#ifdef BUILD_GLES2
479		m_gl->makeCurrent();
480#if defined(_WIN32) && defined(USE_EPOXY)
481		epoxy_handle_external_wglMakeCurrent();
482#endif
483	}
484	if (m_shader.passes) {
485		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
486		mGLES2ShaderFree(&m_shader);
487	}
488	mGLES2ShaderLoad(&m_shader, dir);
489	if (m_started) {
490		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
491	}
492	if (!m_active) {
493		m_gl->doneCurrent();
494	}
495#endif
496}
497
498void PainterGL::clearShaders() {
499	if (!supportsShaders()) {
500		return;
501	}
502	if (!m_active) {
503#ifdef BUILD_GLES2
504		m_gl->makeCurrent();
505#if defined(_WIN32) && defined(USE_EPOXY)
506		epoxy_handle_external_wglMakeCurrent();
507#endif
508	}
509	if (m_shader.passes) {
510		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
511		mGLES2ShaderFree(&m_shader);
512	}
513	if (!m_active) {
514		m_gl->doneCurrent();
515	}
516#endif
517}
518
519VideoShader* PainterGL::shaders() {
520	return &m_shader;
521}
522
523int PainterGL::glTex() {
524#ifdef BUILD_GLES2
525	if (supportsShaders()) {
526		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
527		return gl2Backend->tex;
528	}
529#endif
530#ifdef BUILD_GL
531	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
532	return glBackend->tex;
533#else
534	return -1;
535#endif
536}
537
538#endif