all repos — mgba @ 3e0675f539218973801f40d1abcacc2ffbf89e6c

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#if !defined(_WIN32) || defined(USE_EPOXY)
 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
198PainterGL::PainterGL(int majorVersion, VideoProxy* proxy, QGLWidget* parent)
199	: m_gl(parent)
200	, m_videoProxy(proxy)
201{
202#ifdef BUILD_GL
203	mGLContext* glBackend;
204#endif
205#if !defined(_WIN32) || defined(USE_EPOXY)
206	mGLES2Context* gl2Backend;
207#endif
208
209	m_gl->makeCurrent();
210#if defined(_WIN32) && defined(USE_EPOXY)
211	epoxy_handle_external_wglMakeCurrent();
212#endif
213
214	QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
215
216#if !defined(_WIN32) || defined(USE_EPOXY)
217	if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) {
218		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
219		mGLES2ContextCreate(gl2Backend);
220		m_backend = &gl2Backend->d;
221		m_supportsShaders = true;
222	}
223#endif
224
225#ifdef BUILD_GL
226	 if (!m_backend) {
227		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
228		mGLContextCreate(glBackend);
229		m_backend = &glBackend->d;
230		m_supportsShaders = false;
231	}
232#endif
233	m_backend->swap = [](VideoBackend* v) {
234		PainterGL* painter = static_cast<PainterGL*>(v->user);
235		if (!painter->m_gl->isVisible()) {
236			return;
237		}
238		painter->m_gl->swapBuffers();
239	};
240
241	m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
242#if !defined(_WIN32) || defined(USE_EPOXY)
243	if (m_supportsShaders) {
244		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
245	}
246#endif
247	m_gl->doneCurrent();
248
249	m_backend->user = this;
250	m_backend->filter = false;
251	m_backend->lockAspectRatio = false;
252
253	for (int i = 0; i < 2; ++i) {
254		m_free.append(new uint32_t[256 * 256]);
255	}
256}
257
258PainterGL::~PainterGL() {
259	while (!m_queue.isEmpty()) {
260		delete[] m_queue.dequeue();
261	}
262	for (auto item : m_free) {
263		delete[] item;
264	}
265	m_gl->makeCurrent();
266#if defined(_WIN32) && defined(USE_EPOXY)
267	epoxy_handle_external_wglMakeCurrent();
268#endif
269#if !defined(_WIN32) || defined(USE_EPOXY)
270	if (m_shader.passes) {
271		mGLES2ShaderFree(&m_shader);
272	}
273#endif
274	m_backend->deinit(m_backend);
275	m_gl->doneCurrent();
276	free(m_backend);
277	m_backend = nullptr;
278}
279
280void PainterGL::setContext(std::shared_ptr<CoreController> context) {
281	m_context = context;
282	resizeContext();
283}
284
285void PainterGL::resizeContext() {
286	if (!m_context) {
287		return;
288	}
289
290	if (!m_active) {
291		m_gl->makeCurrent();
292#if defined(_WIN32) && defined(USE_EPOXY)
293		epoxy_handle_external_wglMakeCurrent();
294#endif
295	}
296
297	QSize size = m_context->screenDimensions();
298	m_backend->setDimensions(m_backend, size.width(), size.height());
299	if (!m_active) {
300		m_gl->doneCurrent();
301	}
302}
303
304void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
305	m_messagePainter = messagePainter;
306}
307
308void PainterGL::resize(const QSize& size) {
309	m_size = size;
310	if (m_started && !m_active) {
311		forceDraw();
312	}
313}
314
315void PainterGL::lockAspectRatio(bool lock) {
316	m_backend->lockAspectRatio = lock;
317	if (m_started && !m_active) {
318		forceDraw();
319	}
320}
321
322void PainterGL::lockIntegerScaling(bool lock) {
323	m_backend->lockIntegerScaling = lock;
324	if (m_started && !m_active) {
325		forceDraw();
326	}
327}
328
329void PainterGL::filter(bool filter) {
330	m_backend->filter = filter;
331	if (m_started && !m_active) {
332		forceDraw();
333	}
334}
335
336void PainterGL::start() {
337	m_gl->makeCurrent();
338#if defined(_WIN32) && defined(USE_EPOXY)
339	epoxy_handle_external_wglMakeCurrent();
340#endif
341
342#if !defined(_WIN32) || defined(USE_EPOXY)
343	if (m_supportsShaders && m_shader.passes) {
344		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
345	}
346#endif
347
348	m_active = true;
349	m_started = true;
350}
351
352void PainterGL::draw() {
353	if (m_queue.isEmpty()) {
354		return;
355	}
356
357	if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
358		dequeue();
359		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
360		m_painter.begin(m_gl->context()->device());
361		performDraw();
362		m_painter.end();
363		m_backend->swap(m_backend);
364		if (!m_delayTimer.isValid()) {
365			m_delayTimer.start();
366		} else if (m_gl->format().swapInterval()) {
367			while (m_delayTimer.elapsed() < 15) {
368				QThread::usleep(100);
369			}
370			m_delayTimer.restart();
371		}
372	} else {
373		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->sync);
374	}
375	if (!m_queue.isEmpty()) {
376		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
377	}
378}
379
380void PainterGL::forceDraw() {
381	m_painter.begin(m_gl->context()->device());
382	performDraw();
383	m_painter.end();
384	m_backend->swap(m_backend);
385}
386
387void PainterGL::stop() {
388	m_active = false;
389	m_started = false;
390	dequeueAll();
391	m_backend->clear(m_backend);
392	m_backend->swap(m_backend);
393	m_gl->doneCurrent();
394	m_gl->context()->moveToThread(m_gl->thread());
395	m_context.reset();
396	moveToThread(m_gl->thread());
397	m_videoProxy->moveToThread(m_gl->thread());
398}
399
400void PainterGL::pause() {
401	m_active = false;
402}
403
404void PainterGL::unpause() {
405	m_active = true;
406}
407
408void PainterGL::performDraw() {
409	m_painter.beginNativePainting();
410#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
411	float r = m_gl->devicePixelRatioF();
412#else
413	float r = m_gl->devicePixelRatio();
414#endif
415	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
416	m_backend->drawFrame(m_backend);
417	m_painter.endNativePainting();
418	if (m_messagePainter) {
419		m_messagePainter->paint(&m_painter);
420	}
421}
422
423void PainterGL::enqueue(const uint32_t* backing) {
424	m_mutex.lock();
425	uint32_t* buffer;
426	if (m_free.isEmpty()) {
427		buffer = m_queue.dequeue();
428	} else {
429		buffer = m_free.takeLast();
430	}
431	QSize size = m_context->screenDimensions();
432	memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
433	m_queue.enqueue(buffer);
434	m_mutex.unlock();
435}
436
437void PainterGL::dequeue() {
438	m_mutex.lock();
439	if (m_queue.isEmpty()) {
440		m_mutex.unlock();
441		return;
442	}
443	uint32_t* buffer = m_queue.dequeue();
444	m_backend->postFrame(m_backend, buffer);
445	m_free.append(buffer);
446	m_mutex.unlock();
447}
448
449void PainterGL::dequeueAll() {
450	uint32_t* buffer = 0;
451	m_mutex.lock();
452	while (!m_queue.isEmpty()) {
453		buffer = m_queue.dequeue();
454		m_free.append(buffer);
455	}
456	if (buffer) {
457		m_backend->postFrame(m_backend, buffer);
458	}
459	m_mutex.unlock();
460}
461
462void PainterGL::setShaders(struct VDir* dir) {
463	if (!supportsShaders()) {
464		return;
465	}
466	if (!m_active) {
467#if !defined(_WIN32) || defined(USE_EPOXY)
468		m_gl->makeCurrent();
469#if defined(_WIN32) && defined(USE_EPOXY)
470		epoxy_handle_external_wglMakeCurrent();
471#endif
472	}
473	if (m_shader.passes) {
474		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
475		mGLES2ShaderFree(&m_shader);
476	}
477	mGLES2ShaderLoad(&m_shader, dir);
478	if (m_started) {
479		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
480	}
481	if (!m_active) {
482		m_gl->doneCurrent();
483	}
484#endif
485}
486
487void PainterGL::clearShaders() {
488	if (!supportsShaders()) {
489		return;
490	}
491	if (!m_active) {
492#if !defined(_WIN32) || defined(USE_EPOXY)
493		m_gl->makeCurrent();
494#if defined(_WIN32) && defined(USE_EPOXY)
495		epoxy_handle_external_wglMakeCurrent();
496#endif
497	}
498	if (m_shader.passes) {
499		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
500		mGLES2ShaderFree(&m_shader);
501	}
502	if (!m_active) {
503		m_gl->doneCurrent();
504	}
505#endif
506}
507
508VideoShader* PainterGL::shaders() {
509	return &m_shader;
510}
511
512#endif