all repos — mgba @ 6562e1cfecca7e0927e1a065b80131eb8c5e6f30

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_gl);
 38	m_gl->setMouseTracking(true);
 39	m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
 40}
 41
 42DisplayGL::~DisplayGL() {
 43	stopDrawing();
 44	delete m_painter;
 45}
 46
 47bool DisplayGL::supportsShaders() const {
 48	return m_painter->supportsShaders();
 49}
 50
 51VideoShader* DisplayGL::shaders() {
 52	VideoShader* shaders = nullptr;
 53	if (m_drawThread) {
 54		QMetaObject::invokeMethod(m_painter, "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
 55	} else {
 56		shaders = m_painter->shaders();
 57	}
 58	return shaders;
 59}
 60
 61void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
 62	if (m_drawThread) {
 63		return;
 64	}
 65	m_isDrawing = true;
 66	m_painter->setContext(controller);
 67	m_painter->setMessagePainter(messagePainter());
 68	m_context = controller;
 69	m_painter->resize(size());
 70	m_gl->move(0, 0);
 71	m_drawThread = new QThread(this);
 72	m_drawThread->setObjectName("Painter Thread");
 73	m_gl->context()->doneCurrent();
 74	m_gl->context()->moveToThread(m_drawThread);
 75	m_painter->moveToThread(m_drawThread);
 76	connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start);
 77	m_drawThread->start();
 78
 79	lockAspectRatio(isAspectRatioLocked());
 80	lockIntegerScaling(isIntegerScalingLocked());
 81	filter(isFiltered());
 82#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
 83	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
 84#else
 85	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
 86#endif
 87	resizePainter();
 88}
 89
 90void DisplayGL::stopDrawing() {
 91	if (m_drawThread) {
 92		m_isDrawing = false;
 93		CoreController::Interrupter interrupter(m_context);
 94		QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
 95		m_drawThread->exit();
 96		m_drawThread = nullptr;
 97	}
 98	m_context.reset();
 99}
100
101void DisplayGL::pauseDrawing() {
102	if (m_drawThread) {
103		m_isDrawing = false;
104		CoreController::Interrupter interrupter(m_context);
105		QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
106	}
107}
108
109void DisplayGL::unpauseDrawing() {
110	if (m_drawThread) {
111		m_isDrawing = true;
112		CoreController::Interrupter interrupter(m_context);
113		QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
114	}
115}
116
117void DisplayGL::forceDraw() {
118	if (m_drawThread) {
119		QMetaObject::invokeMethod(m_painter, "forceDraw");
120	}
121}
122
123void DisplayGL::lockAspectRatio(bool lock) {
124	Display::lockAspectRatio(lock);
125	if (m_drawThread) {
126		QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
127	}
128}
129
130void DisplayGL::lockIntegerScaling(bool lock) {
131	Display::lockIntegerScaling(lock);
132	if (m_drawThread) {
133		QMetaObject::invokeMethod(m_painter, "lockIntegerScaling", Q_ARG(bool, lock));
134	}
135}
136
137void DisplayGL::filter(bool filter) {
138	Display::filter(filter);
139	if (m_drawThread) {
140		QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
141	}
142}
143
144void DisplayGL::framePosted() {
145	if (m_drawThread) {
146		m_painter->enqueue(m_context->drawContext());
147		QMetaObject::invokeMethod(m_painter, "draw");
148	}
149}
150
151void DisplayGL::setShaders(struct VDir* shaders) {
152	if (m_drawThread) {
153		QMetaObject::invokeMethod(m_painter, "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
154	} else {
155		m_painter->setShaders(shaders);
156	}
157}
158
159void DisplayGL::clearShaders() {
160	QMetaObject::invokeMethod(m_painter, "clearShaders");
161}
162
163
164void DisplayGL::resizeContext() {
165	if (m_drawThread) {
166		m_isDrawing = false;
167		CoreController::Interrupter interrupter(m_context);
168		QMetaObject::invokeMethod(m_painter, "resizeContext", Qt::BlockingQueuedConnection);
169	}
170}
171
172void DisplayGL::resizeEvent(QResizeEvent* event) {
173	Display::resizeEvent(event);
174	resizePainter();
175}
176
177void DisplayGL::resizePainter() {
178	if (m_gl) {
179		m_gl->resize(size());
180	}
181	if (m_drawThread) {
182		QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
183	}
184}
185
186PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
187	: m_gl(parent)
188{
189#ifdef BUILD_GL
190	mGLContext* glBackend;
191#endif
192#if !defined(_WIN32) || defined(USE_EPOXY)
193	mGLES2Context* gl2Backend;
194#endif
195
196	m_gl->makeCurrent();
197#if defined(_WIN32) && defined(USE_EPOXY)
198	epoxy_handle_external_wglMakeCurrent();
199#endif
200
201	QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
202
203#if !defined(_WIN32) || defined(USE_EPOXY)
204	if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) {
205		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
206		mGLES2ContextCreate(gl2Backend);
207		m_backend = &gl2Backend->d;
208		m_supportsShaders = true;
209	}
210#endif
211
212#ifdef BUILD_GL
213	 if (!m_backend) {
214		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
215		mGLContextCreate(glBackend);
216		m_backend = &glBackend->d;
217		m_supportsShaders = false;
218	}
219#endif
220	m_backend->swap = [](VideoBackend* v) {
221		PainterGL* painter = static_cast<PainterGL*>(v->user);
222		painter->m_gl->swapBuffers();
223	};
224
225	m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
226#if !defined(_WIN32) || defined(USE_EPOXY)
227	if (m_supportsShaders) {
228		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
229	}
230#endif
231	m_gl->doneCurrent();
232
233	m_backend->user = this;
234	m_backend->filter = false;
235	m_backend->lockAspectRatio = false;
236
237	for (int i = 0; i < 2; ++i) {
238		m_free.append(new uint32_t[256 * 256]);
239	}
240}
241
242PainterGL::~PainterGL() {
243	while (!m_queue.isEmpty()) {
244		delete[] m_queue.dequeue();
245	}
246	for (auto item : m_free) {
247		delete[] item;
248	}
249	m_gl->makeCurrent();
250#if defined(_WIN32) && defined(USE_EPOXY)
251	epoxy_handle_external_wglMakeCurrent();
252#endif
253#if !defined(_WIN32) || defined(USE_EPOXY)
254	if (m_shader.passes) {
255		mGLES2ShaderFree(&m_shader);
256	}
257#endif
258	m_backend->deinit(m_backend);
259	m_gl->doneCurrent();
260	free(m_backend);
261	m_backend = nullptr;
262}
263
264void PainterGL::setContext(std::shared_ptr<CoreController> context) {
265	m_context = context;
266	resizeContext();
267}
268
269void PainterGL::resizeContext() {
270	if (!m_context) {
271		return;
272	}
273
274	m_gl->makeCurrent();
275#if defined(_WIN32) && defined(USE_EPOXY)
276	epoxy_handle_external_wglMakeCurrent();
277#endif
278	QSize size = m_context->screenDimensions();
279	m_backend->setDimensions(m_backend, size.width(), size.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()) {
334		return;
335	}
336
337	if (mCoreSyncWaitFrameStart(&m_context->thread()->impl->sync) || !m_queue.isEmpty()) {
338		dequeue();
339		mCoreSyncWaitFrameEnd(&m_context->thread()->impl->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->thread()->impl->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	m_context.reset();
380	moveToThread(m_gl->thread());
381}
382
383void PainterGL::pause() {
384	m_active = false;
385}
386
387void PainterGL::unpause() {
388	m_active = true;
389}
390
391void PainterGL::performDraw() {
392	m_painter.beginNativePainting();
393#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
394	float r = m_gl->devicePixelRatioF();
395#else
396	float r = m_gl->devicePixelRatio();
397#endif
398	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
399	m_backend->drawFrame(m_backend);
400	m_painter.endNativePainting();
401	if (m_messagePainter) {
402		m_messagePainter->paint(&m_painter);
403	}
404}
405
406void PainterGL::enqueue(const uint32_t* backing) {
407	m_mutex.lock();
408	uint32_t* buffer;
409	if (m_free.isEmpty()) {
410		buffer = m_queue.dequeue();
411	} else {
412		buffer = m_free.takeLast();
413	}
414	QSize size = m_context->screenDimensions();
415	memcpy(buffer, backing, size.width() * size.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}
486
487#endif