all repos — mgba @ 78e4083a562a1ce34a29dee8ecdb315401c9fb1a

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#include <QApplication>
  9#include <QResizeEvent>
 10#include <QTimer>
 11
 12#include <mgba/core/core.h>
 13#include <mgba/core/thread.h>
 14#ifdef BUILD_GL
 15#include "platform/opengl/gl.h"
 16#endif
 17#if !defined(_WIN32) || defined(USE_EPOXY)
 18#include "platform/opengl/gles2.h"
 19#ifdef _WIN32
 20#include <epoxy/wgl.h>
 21#endif
 22#endif
 23
 24using namespace QGBA;
 25
 26DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
 27	: Display(parent)
 28	, m_isDrawing(false)
 29	, m_gl(new EmptyGLWidget(format, this))
 30	, m_drawThread(nullptr)
 31	, m_context(nullptr)
 32{
 33	m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), m_gl);
 34	m_gl->setMouseTracking(true);
 35	m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
 36}
 37
 38DisplayGL::~DisplayGL() {
 39	delete m_painter;
 40}
 41
 42bool DisplayGL::supportsShaders() const {
 43	return m_painter->supportsShaders();
 44}
 45
 46VideoShader* DisplayGL::shaders() {
 47	VideoShader* shaders = nullptr;
 48	if (m_drawThread) {
 49		QMetaObject::invokeMethod(m_painter, "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
 50	} else {
 51		shaders = m_painter->shaders();
 52	}
 53	return shaders;
 54}
 55
 56void DisplayGL::startDrawing(mCoreThread* thread) {
 57	if (m_drawThread) {
 58		return;
 59	}
 60	m_isDrawing = true;
 61	m_painter->setContext(thread);
 62	m_painter->setMessagePainter(messagePainter());
 63	m_context = thread;
 64	m_painter->resize(size());
 65	m_gl->move(0, 0);
 66	m_drawThread = new QThread(this);
 67	m_drawThread->setObjectName("Painter Thread");
 68	m_gl->context()->doneCurrent();
 69	m_gl->context()->moveToThread(m_drawThread);
 70	m_painter->moveToThread(m_drawThread);
 71	connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
 72	m_drawThread->start();
 73	mCoreSyncSetVideoSync(&m_context->sync, false);
 74
 75	lockAspectRatio(isAspectRatioLocked());
 76	lockIntegerScaling(isIntegerScalingLocked());
 77	filter(isFiltered());
 78#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
 79	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
 80#else
 81	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
 82#endif
 83	resizePainter();
 84}
 85
 86void DisplayGL::stopDrawing() {
 87	if (m_drawThread) {
 88		m_isDrawing = false;
 89		if (mCoreThreadIsActive(m_context)) {
 90			mCoreThreadInterrupt(m_context);
 91		}
 92		QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
 93		m_drawThread->exit();
 94		m_drawThread = nullptr;
 95		if (mCoreThreadIsActive(m_context)) {
 96			mCoreThreadContinue(m_context);
 97		}
 98	}
 99}
100
101void DisplayGL::pauseDrawing() {
102	if (m_drawThread) {
103		m_isDrawing = false;
104		if (mCoreThreadIsActive(m_context)) {
105			mCoreThreadInterrupt(m_context);
106		}
107		QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
108		if (mCoreThreadIsActive(m_context)) {
109			mCoreThreadContinue(m_context);
110		}
111	}
112}
113
114void DisplayGL::unpauseDrawing() {
115	if (m_drawThread) {
116		m_isDrawing = true;
117		if (mCoreThreadIsActive(m_context)) {
118			mCoreThreadInterrupt(m_context);
119		}
120		QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
121		if (mCoreThreadIsActive(m_context)) {
122			mCoreThreadContinue(m_context);
123		}
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(const uint32_t* buffer) {
155	if (m_drawThread && buffer) {
156		m_painter->enqueue(buffer);
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
173void DisplayGL::resizeEvent(QResizeEvent* event) {
174	Display::resizeEvent(event);
175	resizePainter();
176}
177
178void DisplayGL::resizePainter() {
179	m_gl->resize(size());
180	if (m_drawThread) {
181		QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
182	}
183}
184
185PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
186	: m_gl(parent)
187	, m_active(false)
188	, m_started(false)
189	, m_context(nullptr)
190	, m_shader{}
191	, m_backend(nullptr)
192	, m_messagePainter(nullptr)
193{
194#ifdef BUILD_GL
195	mGLContext* glBackend;
196#endif
197#if !defined(_WIN32) || defined(USE_EPOXY)
198	mGLES2Context* gl2Backend;
199#endif
200
201#if !defined(_WIN32) || defined(USE_EPOXY)
202	if (majorVersion >= 2) {
203		gl2Backend = new mGLES2Context;
204		mGLES2ContextCreate(gl2Backend);
205		m_backend = &gl2Backend->d;
206		m_supportsShaders = true;
207	}
208#endif
209
210#ifdef BUILD_GL
211	 if (!m_backend) {
212		glBackend = new mGLContext;
213		mGLContextCreate(glBackend);
214		m_backend = &glBackend->d;
215		m_supportsShaders = false;
216	}
217#endif
218	m_backend->swap = [](VideoBackend* v) {
219		PainterGL* painter = static_cast<PainterGL*>(v->user);
220		painter->m_gl->swapBuffers();
221	};
222
223	m_gl->makeCurrent();
224#if defined(_WIN32) && defined(USE_EPOXY)
225	epoxy_handle_external_wglMakeCurrent();
226#endif
227	m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
228#if !defined(_WIN32) || defined(USE_EPOXY)
229	if (m_supportsShaders) {
230		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
231	}
232#endif
233	m_gl->doneCurrent();
234
235	m_backend->user = this;
236	m_backend->filter = false;
237	m_backend->lockAspectRatio = false;
238
239	for (int i = 0; i < 2; ++i) {
240		m_free.append(new uint32_t[256 * 256]);
241	}
242}
243
244PainterGL::~PainterGL() {
245	while (!m_queue.isEmpty()) {
246		delete[] m_queue.dequeue();
247	}
248	for (auto item : m_free) {
249		delete[] item;
250	}
251	m_gl->makeCurrent();
252#if defined(_WIN32) && defined(USE_EPOXY)
253	epoxy_handle_external_wglMakeCurrent();
254#endif
255#if !defined(_WIN32) || defined(USE_EPOXY)
256	if (m_shader.passes) {
257		mGLES2ShaderFree(&m_shader);
258	}
259#endif
260	m_backend->deinit(m_backend);
261	m_gl->doneCurrent();
262	delete m_backend;
263	m_backend = nullptr;
264}
265
266void PainterGL::setContext(mCoreThread* context) {
267	m_context = context;
268
269	if (!context) {
270		return;
271	}
272
273	m_gl->makeCurrent();
274#if defined(_WIN32) && defined(USE_EPOXY)
275	epoxy_handle_external_wglMakeCurrent();
276#endif
277	unsigned width, height;
278	context->core->desiredVideoDimensions(context->core, &width, &height);
279	m_backend->setDimensions(m_backend, width, height);
280	m_gl->doneCurrent();
281}
282
283void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
284	m_messagePainter = messagePainter;
285}
286
287void PainterGL::resize(const QSize& size) {
288	m_size = size;
289	if (m_started && !m_active) {
290		forceDraw();
291	}
292}
293
294void PainterGL::lockAspectRatio(bool lock) {
295	m_backend->lockAspectRatio = lock;
296	if (m_started && !m_active) {
297		forceDraw();
298	}
299}
300
301void PainterGL::lockIntegerScaling(bool lock) {
302	m_backend->lockIntegerScaling = lock;
303	if (m_started && !m_active) {
304		forceDraw();
305	}
306}
307
308void PainterGL::filter(bool filter) {
309	m_backend->filter = filter;
310	if (m_started && !m_active) {
311		forceDraw();
312	}
313}
314
315void PainterGL::start() {
316	m_gl->makeCurrent();
317#if defined(_WIN32) && defined(USE_EPOXY)
318	epoxy_handle_external_wglMakeCurrent();
319#endif
320
321#if !defined(_WIN32) || defined(USE_EPOXY)
322	if (m_supportsShaders && m_shader.passes) {
323		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
324	}
325#endif
326
327	m_gl->doneCurrent();
328	m_active = true;
329	m_started = true;
330}
331
332void PainterGL::draw() {
333	if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) {
334		return;
335	}
336
337	if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) {
338		dequeue();
339		mCoreSyncWaitFrameEnd(&m_context->sync);
340		m_painter.begin(m_gl->context()->device());
341		performDraw();
342		m_painter.end();
343		m_backend->swap(m_backend);
344		if (!m_delayTimer.isValid()) {
345			m_delayTimer.start();
346		} else {
347			while (m_delayTimer.elapsed() < 15) {
348				QThread::usleep(100);
349			}
350			m_delayTimer.restart();
351		}
352	} else {
353		mCoreSyncWaitFrameEnd(&m_context->sync);
354	}
355	if (!m_queue.isEmpty()) {
356		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
357	}
358}
359
360void PainterGL::forceDraw() {
361	m_painter.begin(m_gl->context()->device());
362	performDraw();
363	m_painter.end();
364	m_backend->swap(m_backend);
365}
366
367void PainterGL::stop() {
368	m_active = false;
369	m_started = false;
370	m_gl->makeCurrent();
371#if defined(_WIN32) && defined(USE_EPOXY)
372	epoxy_handle_external_wglMakeCurrent();
373#endif
374	dequeueAll();
375	m_backend->clear(m_backend);
376	m_backend->swap(m_backend);
377	m_gl->doneCurrent();
378	m_gl->context()->moveToThread(m_gl->thread());
379	moveToThread(m_gl->thread());
380}
381
382void PainterGL::pause() {
383	m_active = false;
384}
385
386void PainterGL::unpause() {
387	m_active = true;
388}
389
390void PainterGL::performDraw() {
391	m_painter.beginNativePainting();
392#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
393	float r = m_gl->devicePixelRatioF();
394#else
395	float r = m_gl->devicePixelRatio();
396#endif
397	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
398	m_backend->drawFrame(m_backend);
399	m_painter.endNativePainting();
400	if (m_messagePainter) {
401		m_messagePainter->paint(&m_painter);
402	}
403}
404
405void PainterGL::enqueue(const uint32_t* backing) {
406	m_mutex.lock();
407	uint32_t* buffer;
408	if (m_free.isEmpty()) {
409		buffer = m_queue.dequeue();
410	} else {
411		buffer = m_free.takeLast();
412	}
413	unsigned width, height;
414	m_context->core->desiredVideoDimensions(m_context->core, &width, &height);
415	memcpy(buffer, backing, width * height * BYTES_PER_PIXEL);
416	m_queue.enqueue(buffer);
417	m_mutex.unlock();
418}
419
420void PainterGL::dequeue() {
421	m_mutex.lock();
422	if (m_queue.isEmpty()) {
423		m_mutex.unlock();
424		return;
425	}
426	uint32_t* buffer = m_queue.dequeue();
427	m_backend->postFrame(m_backend, buffer);
428	m_free.append(buffer);
429	m_mutex.unlock();
430}
431
432void PainterGL::dequeueAll() {
433	uint32_t* buffer = 0;
434	m_mutex.lock();
435	while (!m_queue.isEmpty()) {
436		buffer = m_queue.dequeue();
437		m_free.append(buffer);
438	}
439	if (buffer) {
440		m_backend->postFrame(m_backend, buffer);
441	}
442	m_mutex.unlock();
443}
444
445void PainterGL::setShaders(struct VDir* dir) {
446	if (!supportsShaders()) {
447		return;
448	}
449#if !defined(_WIN32) || defined(USE_EPOXY)
450	m_gl->makeCurrent();
451#if defined(_WIN32) && defined(USE_EPOXY)
452	epoxy_handle_external_wglMakeCurrent();
453#endif
454	if (m_shader.passes) {
455		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
456		mGLES2ShaderFree(&m_shader);
457	}
458	mGLES2ShaderLoad(&m_shader, dir);
459	if (m_started) {
460		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
461	}
462	m_gl->doneCurrent();
463#endif
464}
465
466void PainterGL::clearShaders() {
467	if (!supportsShaders()) {
468		return;
469	}
470#if !defined(_WIN32) || defined(USE_EPOXY)
471	m_gl->makeCurrent();
472#if defined(_WIN32) && defined(USE_EPOXY)
473	epoxy_handle_external_wglMakeCurrent();
474#endif
475	if (m_shader.passes) {
476		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
477		mGLES2ShaderFree(&m_shader);
478	}
479	m_gl->doneCurrent();
480#endif
481}
482
483VideoShader* PainterGL::shaders() {
484	return &m_shader;
485}