all repos — mgba @ ef68c84e76bfd95b454bcbc3eec4d6946d23a282

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
 11extern "C" {
 12#include "gba/supervisor/thread.h"
 13
 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}
 24
 25using namespace QGBA;
 26
 27DisplayGL::DisplayGL(const QGLFormat& format, bool force1, QWidget* parent)
 28	: Display(parent)
 29	, m_isDrawing(false)
 30	, m_gl(new EmptyGLWidget(format, this))
 31	, m_drawThread(nullptr)
 32	, m_context(nullptr)
 33{
 34	QGLFormat::OpenGLVersionFlags versions = QGLFormat::openGLVersionFlags();
 35	if (force1) {
 36		versions &= QGLFormat::OpenGL_Version_1_1 |
 37		            QGLFormat::OpenGL_Version_1_2 |
 38		            QGLFormat::OpenGL_Version_1_3 |
 39		            QGLFormat::OpenGL_Version_1_4 |
 40		            QGLFormat::OpenGL_Version_1_5;
 41	}
 42	m_painter = new PainterGL(m_gl, versions);
 43	m_gl->setMouseTracking(true);
 44	m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
 45}
 46
 47DisplayGL::~DisplayGL() {
 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(mCoreThread* thread) {
 66	if (m_drawThread) {
 67		return;
 68	}
 69	m_isDrawing = true;
 70	m_painter->setContext(thread);
 71	m_painter->setMessagePainter(messagePainter());
 72	m_context = thread;
 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	connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
 81	m_drawThread->start();
 82	mCoreSyncSetVideoSync(&m_context->sync, false);
 83
 84	lockAspectRatio(isAspectRatioLocked());
 85	filter(isFiltered());
 86	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
 87	resizePainter();
 88}
 89
 90void DisplayGL::stopDrawing() {
 91	if (m_drawThread) {
 92		m_isDrawing = false;
 93		if (mCoreThreadIsActive(m_context)) {
 94			mCoreThreadInterrupt(m_context);
 95		}
 96		QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
 97		m_drawThread->exit();
 98		m_drawThread = nullptr;
 99		if (mCoreThreadIsActive(m_context)) {
100			mCoreThreadContinue(m_context);
101		}
102	}
103}
104
105void DisplayGL::pauseDrawing() {
106	if (m_drawThread) {
107		m_isDrawing = false;
108		if (mCoreThreadIsActive(m_context)) {
109			mCoreThreadInterrupt(m_context);
110		}
111		QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
112		if (mCoreThreadIsActive(m_context)) {
113			mCoreThreadContinue(m_context);
114		}
115	}
116}
117
118void DisplayGL::unpauseDrawing() {
119	if (m_drawThread) {
120		m_isDrawing = true;
121		if (mCoreThreadIsActive(m_context)) {
122			mCoreThreadInterrupt(m_context);
123		}
124		QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
125		if (mCoreThreadIsActive(m_context)) {
126			mCoreThreadContinue(m_context);
127		}
128	}
129}
130
131void DisplayGL::forceDraw() {
132	if (m_drawThread) {
133		QMetaObject::invokeMethod(m_painter, "forceDraw");
134	}
135}
136
137void DisplayGL::lockAspectRatio(bool lock) {
138	Display::lockAspectRatio(lock);
139	if (m_drawThread) {
140		QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
141	}
142}
143
144void DisplayGL::filter(bool filter) {
145	Display::filter(filter);
146	if (m_drawThread) {
147		QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
148	}
149}
150
151void DisplayGL::framePosted(const uint32_t* buffer) {
152	if (m_drawThread && buffer) {
153		m_painter->enqueue(buffer);
154		QMetaObject::invokeMethod(m_painter, "draw");
155	}
156}
157
158void DisplayGL::setShaders(struct VDir* shaders) {
159	if (m_drawThread) {
160		QMetaObject::invokeMethod(m_painter, "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
161	} else {
162		m_painter->setShaders(shaders);
163	}
164}
165
166void DisplayGL::clearShaders() {
167	QMetaObject::invokeMethod(m_painter, "clearShaders");
168}
169
170void DisplayGL::resizeEvent(QResizeEvent* event) {
171	Display::resizeEvent(event);
172	resizePainter();
173}
174
175void DisplayGL::resizePainter() {
176	m_gl->resize(size());
177	if (m_drawThread) {
178		QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
179	}
180}
181
182PainterGL::PainterGL(QGLWidget* parent, QGLFormat::OpenGLVersionFlags glVersion)
183	: m_gl(parent)
184	, m_active(false)
185	, m_started(false)
186	, m_context(nullptr)
187	, m_shader{}
188	, m_backend(nullptr)
189	, m_messagePainter(nullptr)
190{
191#ifdef BUILD_GL
192	GBAGLContext* glBackend;
193#endif
194#if !defined(_WIN32) || defined(USE_EPOXY)
195	GBAGLES2Context* gl2Backend;
196#endif
197
198#if !defined(_WIN32) || defined(USE_EPOXY)
199	if (glVersion & (QGLFormat::OpenGL_Version_3_0 | QGLFormat::OpenGL_ES_Version_2_0)) {
200		gl2Backend = new GBAGLES2Context;
201		GBAGLES2ContextCreate(gl2Backend);
202		m_backend = &gl2Backend->d;
203		m_supportsShaders = true;
204	}
205#endif
206
207#ifdef BUILD_GL
208	 if (!m_backend) {
209		glBackend = new GBAGLContext;
210		GBAGLContextCreate(glBackend);
211		m_backend = &glBackend->d;
212		m_supportsShaders = false;
213	}
214#endif
215	m_backend->swap = [](VideoBackend* v) {
216		PainterGL* painter = static_cast<PainterGL*>(v->user);
217		painter->m_gl->swapBuffers();
218	};
219
220	m_gl->makeCurrent();
221#if defined(_WIN32) && defined(USE_EPOXY)
222	epoxy_handle_external_wglMakeCurrent();
223#endif
224	m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
225#if !defined(_WIN32) || defined(USE_EPOXY)
226	if (m_supportsShaders) {
227		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<GBAGLES2Context*>(m_backend)->initialShader);
228	}
229#endif
230	m_gl->doneCurrent();
231
232	m_backend->user = this;
233	m_backend->filter = false;
234	m_backend->lockAspectRatio = false;
235
236	for (int i = 0; i < 2; ++i) {
237		m_free.append(new uint32_t[256 * 256]);
238	}
239}
240
241PainterGL::~PainterGL() {
242	while (!m_queue.isEmpty()) {
243		delete[] m_queue.dequeue();
244	}
245	for (auto item : m_free) {
246		delete[] item;
247	}
248	m_gl->makeCurrent();
249#if defined(_WIN32) && defined(USE_EPOXY)
250	epoxy_handle_external_wglMakeCurrent();
251#endif
252#if !defined(_WIN32) || defined(USE_EPOXY)
253	if (m_shader.passes) {
254		GBAGLES2ShaderFree(&m_shader);
255	}
256#endif
257	m_backend->deinit(m_backend);
258	m_gl->doneCurrent();
259	delete m_backend;
260	m_backend = nullptr;
261}
262
263void PainterGL::setContext(mCoreThread* context) {
264	m_context = context;
265
266	if (!context) {
267		return;
268	}
269
270	m_gl->makeCurrent();
271#if defined(_WIN32) && defined(USE_EPOXY)
272	epoxy_handle_external_wglMakeCurrent();
273#endif
274	unsigned width, height;
275	context->core->desiredVideoDimensions(context->core, &width, &height);
276	m_backend->setDimensions(m_backend, width, height);
277	m_gl->doneCurrent();
278}
279
280void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
281	m_messagePainter = messagePainter;
282}
283
284void PainterGL::resize(const QSize& size) {
285	m_size = size;
286	if (m_started && !m_active) {
287		forceDraw();
288	}
289}
290
291void PainterGL::lockAspectRatio(bool lock) {
292	m_backend->lockAspectRatio = lock;
293	if (m_started && !m_active) {
294		forceDraw();
295	}
296}
297
298void PainterGL::filter(bool filter) {
299	m_backend->filter = filter;
300	if (m_started && !m_active) {
301		forceDraw();
302	}
303}
304
305void PainterGL::start() {
306	m_gl->makeCurrent();
307#if defined(_WIN32) && defined(USE_EPOXY)
308	epoxy_handle_external_wglMakeCurrent();
309#endif
310
311#if !defined(_WIN32) || defined(USE_EPOXY)
312	if (m_supportsShaders && m_shader.passes) {
313		GBAGLES2ShaderAttach(reinterpret_cast<GBAGLES2Context*>(m_backend), static_cast<GBAGLES2Shader*>(m_shader.passes), m_shader.nPasses);
314	}
315#endif
316
317	m_gl->doneCurrent();
318	m_active = true;
319	m_started = true;
320}
321
322void PainterGL::draw() {
323	if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) {
324		return;
325	}
326	if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) {
327		dequeue();
328		mCoreSyncWaitFrameEnd(&m_context->sync);
329		m_painter.begin(m_gl->context()->device());
330		performDraw();
331		m_painter.end();
332		m_backend->swap(m_backend);
333	} else {
334		mCoreSyncWaitFrameEnd(&m_context->sync);
335	}
336	if (!m_queue.isEmpty()) {
337		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
338	}
339}
340
341void PainterGL::forceDraw() {
342	m_painter.begin(m_gl->context()->device());
343	performDraw();
344	m_painter.end();
345	m_backend->swap(m_backend);
346}
347
348void PainterGL::stop() {
349	m_active = false;
350	m_started = false;
351	m_gl->makeCurrent();
352#if defined(_WIN32) && defined(USE_EPOXY)
353	epoxy_handle_external_wglMakeCurrent();
354#endif
355	dequeueAll();
356	m_backend->clear(m_backend);
357	m_backend->swap(m_backend);
358	m_gl->doneCurrent();
359	m_gl->context()->moveToThread(m_gl->thread());
360	moveToThread(m_gl->thread());
361}
362
363void PainterGL::pause() {
364	m_active = false;
365}
366
367void PainterGL::unpause() {
368	m_active = true;
369}
370
371void PainterGL::performDraw() {
372	m_painter.beginNativePainting();
373	float r = m_gl->devicePixelRatio();
374	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
375	m_backend->drawFrame(m_backend);
376	m_painter.endNativePainting();
377	if (m_messagePainter) {
378		m_messagePainter->paint(&m_painter);
379	}
380}
381
382void PainterGL::enqueue(const uint32_t* backing) {
383	m_mutex.lock();
384	uint32_t* buffer;
385	if (m_free.isEmpty()) {
386		buffer = m_queue.dequeue();
387	} else {
388		buffer = m_free.takeLast();
389	}
390	unsigned width, height;
391	m_context->core->desiredVideoDimensions(m_context->core, &width, &height);
392	memcpy(buffer, backing, width * height * BYTES_PER_PIXEL);
393	m_queue.enqueue(buffer);
394	m_mutex.unlock();
395}
396
397void PainterGL::dequeue() {
398	m_mutex.lock();
399	if (m_queue.isEmpty()) {
400		m_mutex.unlock();
401		return;
402	}
403	uint32_t* buffer = m_queue.dequeue();
404	m_backend->postFrame(m_backend, buffer);
405	m_free.append(buffer);
406	m_mutex.unlock();
407}
408
409void PainterGL::dequeueAll() {
410	uint32_t* buffer = 0;
411	m_mutex.lock();
412	while (!m_queue.isEmpty()) {
413		buffer = m_queue.dequeue();
414		m_free.append(buffer);
415	}
416	if (buffer) {
417		m_backend->postFrame(m_backend, buffer);
418	}
419	m_mutex.unlock();
420}
421
422void PainterGL::setShaders(struct VDir* dir) {
423	if (!supportsShaders()) {
424		return;
425	}
426#if !defined(_WIN32) || defined(USE_EPOXY)
427	m_gl->makeCurrent();
428#if defined(_WIN32) && defined(USE_EPOXY)
429	epoxy_handle_external_wglMakeCurrent();
430#endif
431	if (m_shader.passes) {
432		GBAGLES2ShaderDetach(reinterpret_cast<GBAGLES2Context*>(m_backend));
433		GBAGLES2ShaderFree(&m_shader);
434	}
435	GBAGLES2ShaderLoad(&m_shader, dir);
436	if (m_started) {
437		GBAGLES2ShaderAttach(reinterpret_cast<GBAGLES2Context*>(m_backend), static_cast<GBAGLES2Shader*>(m_shader.passes), m_shader.nPasses);
438	}
439	m_gl->doneCurrent();
440#endif
441}
442
443void PainterGL::clearShaders() {
444	if (!supportsShaders()) {
445		return;
446	}
447#if !defined(_WIN32) || defined(USE_EPOXY)
448	m_gl->makeCurrent();
449#if defined(_WIN32) && defined(USE_EPOXY)
450	epoxy_handle_external_wglMakeCurrent();
451#endif
452	if (m_shader.passes) {
453		GBAGLES2ShaderDetach(reinterpret_cast<GBAGLES2Context*>(m_backend));
454		GBAGLES2ShaderFree(&m_shader);
455	}
456	m_gl->doneCurrent();
457#endif
458}
459
460VideoShader* PainterGL::shaders() {
461	return &m_shader;
462}