all repos — mgba @ d0771b78e22e89b5523badee256ec6e15467dc54

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, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 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
267void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
268	m_messagePainter = messagePainter;
269}
270
271void PainterGL::resize(const QSize& size) {
272	m_size = size;
273	if (m_started && !m_active) {
274		forceDraw();
275	}
276}
277
278void PainterGL::lockAspectRatio(bool lock) {
279	m_backend->lockAspectRatio = lock;
280	if (m_started && !m_active) {
281		forceDraw();
282	}
283}
284
285void PainterGL::filter(bool filter) {
286	m_backend->filter = filter;
287	if (m_started && !m_active) {
288		forceDraw();
289	}
290}
291
292void PainterGL::start() {
293	m_gl->makeCurrent();
294#if defined(_WIN32) && defined(USE_EPOXY)
295	epoxy_handle_external_wglMakeCurrent();
296#endif
297
298#if !defined(_WIN32) || defined(USE_EPOXY)
299	if (m_supportsShaders && m_shader.passes) {
300		GBAGLES2ShaderAttach(reinterpret_cast<GBAGLES2Context*>(m_backend), static_cast<GBAGLES2Shader*>(m_shader.passes), m_shader.nPasses);
301	}
302#endif
303
304	m_gl->doneCurrent();
305	m_active = true;
306	m_started = true;
307}
308
309void PainterGL::draw() {
310	if (m_queue.isEmpty() || !mCoreThreadIsActive(m_context)) {
311		return;
312	}
313	if (mCoreSyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) {
314		dequeue();
315		mCoreSyncWaitFrameEnd(&m_context->sync);
316		m_painter.begin(m_gl->context()->device());
317		performDraw();
318		m_painter.end();
319		m_backend->swap(m_backend);
320	} else {
321		mCoreSyncWaitFrameEnd(&m_context->sync);
322	}
323	if (!m_queue.isEmpty()) {
324		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
325	}
326}
327
328void PainterGL::forceDraw() {
329	m_painter.begin(m_gl->context()->device());
330	performDraw();
331	m_painter.end();
332	m_backend->swap(m_backend);
333}
334
335void PainterGL::stop() {
336	m_active = false;
337	m_started = false;
338	m_gl->makeCurrent();
339#if defined(_WIN32) && defined(USE_EPOXY)
340	epoxy_handle_external_wglMakeCurrent();
341#endif
342	dequeueAll();
343	m_backend->clear(m_backend);
344	m_backend->swap(m_backend);
345	m_gl->doneCurrent();
346	m_gl->context()->moveToThread(m_gl->thread());
347	moveToThread(m_gl->thread());
348}
349
350void PainterGL::pause() {
351	m_active = false;
352}
353
354void PainterGL::unpause() {
355	m_active = true;
356}
357
358void PainterGL::performDraw() {
359	m_painter.beginNativePainting();
360	float r = m_gl->devicePixelRatio();
361	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
362	m_backend->drawFrame(m_backend);
363	m_painter.endNativePainting();
364	if (m_messagePainter) {
365		m_messagePainter->paint(&m_painter);
366	}
367}
368
369void PainterGL::enqueue(const uint32_t* backing) {
370	m_mutex.lock();
371	uint32_t* buffer;
372	if (m_free.isEmpty()) {
373		buffer = m_queue.dequeue();
374	} else {
375		buffer = m_free.takeLast();
376	}
377	memcpy(buffer, backing, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
378	m_queue.enqueue(buffer);
379	m_mutex.unlock();
380}
381
382void PainterGL::dequeue() {
383	m_mutex.lock();
384	if (m_queue.isEmpty()) {
385		m_mutex.unlock();
386		return;
387	}
388	uint32_t* buffer = m_queue.dequeue();
389	m_backend->postFrame(m_backend, buffer);
390	m_free.append(buffer);
391	m_mutex.unlock();
392}
393
394void PainterGL::dequeueAll() {
395	uint32_t* buffer = 0;
396	m_mutex.lock();
397	while (!m_queue.isEmpty()) {
398		buffer = m_queue.dequeue();
399		m_free.append(buffer);
400	}
401	if (buffer) {
402		m_backend->postFrame(m_backend, buffer);
403	}
404	m_mutex.unlock();
405}
406
407void PainterGL::setShaders(struct VDir* dir) {
408	if (!supportsShaders()) {
409		return;
410	}
411#if !defined(_WIN32) || defined(USE_EPOXY)
412	m_gl->makeCurrent();
413#if defined(_WIN32) && defined(USE_EPOXY)
414	epoxy_handle_external_wglMakeCurrent();
415#endif
416	if (m_shader.passes) {
417		GBAGLES2ShaderDetach(reinterpret_cast<GBAGLES2Context*>(m_backend));
418		GBAGLES2ShaderFree(&m_shader);
419	}
420	GBAGLES2ShaderLoad(&m_shader, dir);
421	if (m_started) {
422		GBAGLES2ShaderAttach(reinterpret_cast<GBAGLES2Context*>(m_backend), static_cast<GBAGLES2Shader*>(m_shader.passes), m_shader.nPasses);
423	}
424	m_gl->doneCurrent();
425#endif
426}
427
428void PainterGL::clearShaders() {
429	if (!supportsShaders()) {
430		return;
431	}
432#if !defined(_WIN32) || defined(USE_EPOXY)
433	m_gl->makeCurrent();
434#if defined(_WIN32) && defined(USE_EPOXY)
435	epoxy_handle_external_wglMakeCurrent();
436#endif
437	if (m_shader.passes) {
438		GBAGLES2ShaderDetach(reinterpret_cast<GBAGLES2Context*>(m_backend));
439		GBAGLES2ShaderFree(&m_shader);
440	}
441	m_gl->doneCurrent();
442#endif
443}
444
445VideoShader* PainterGL::shaders() {
446	return &m_shader;
447}