all repos — mgba @ 870c8ebb8c529daeb5326c836322a72114a76c5d

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
 15using namespace QGBA;
 16
 17DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
 18	: Display(parent)
 19	, m_isDrawing(false)
 20	, m_gl(new EmptyGLWidget(format, this))
 21	, m_painter(new PainterGL(m_gl))
 22	, m_drawThread(nullptr)
 23	, m_context(nullptr)
 24{
 25	m_gl->setMouseTracking(true);
 26	m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
 27}
 28
 29DisplayGL::~DisplayGL() {
 30	delete m_painter;
 31}
 32
 33void DisplayGL::startDrawing(GBAThread* thread) {
 34	if (m_drawThread) {
 35		return;
 36	}
 37	m_isDrawing = true;
 38	m_painter->setContext(thread);
 39	m_painter->setMessagePainter(messagePainter());
 40	m_context = thread;
 41	m_painter->resize(size());
 42	m_gl->move(0, 0);
 43	m_drawThread = new QThread(this);
 44	m_drawThread->setObjectName("Painter Thread");
 45	m_gl->context()->doneCurrent();
 46	m_gl->context()->moveToThread(m_drawThread);
 47	m_painter->moveToThread(m_drawThread);
 48	connect(m_drawThread, SIGNAL(started()), m_painter, SLOT(start()));
 49	m_drawThread->start();
 50	GBASyncSetVideoSync(&m_context->sync, false);
 51
 52	lockAspectRatio(isAspectRatioLocked());
 53	filter(isFiltered());
 54	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
 55	resizePainter();
 56}
 57
 58void DisplayGL::stopDrawing() {
 59	if (m_drawThread) {
 60		m_isDrawing = false;
 61		if (GBAThreadIsActive(m_context)) {
 62			GBAThreadInterrupt(m_context);
 63		}
 64		QMetaObject::invokeMethod(m_painter, "stop", Qt::BlockingQueuedConnection);
 65		m_drawThread->exit();
 66		m_drawThread = nullptr;
 67		if (GBAThreadIsActive(m_context)) {
 68			GBAThreadContinue(m_context);
 69		}
 70	}
 71}
 72
 73void DisplayGL::pauseDrawing() {
 74	if (m_drawThread) {
 75		m_isDrawing = false;
 76		if (GBAThreadIsActive(m_context)) {
 77			GBAThreadInterrupt(m_context);
 78		}
 79		QMetaObject::invokeMethod(m_painter, "pause", Qt::BlockingQueuedConnection);
 80		if (GBAThreadIsActive(m_context)) {
 81			GBAThreadContinue(m_context);
 82		}
 83	}
 84}
 85
 86void DisplayGL::unpauseDrawing() {
 87	if (m_drawThread) {
 88		m_isDrawing = true;
 89		if (GBAThreadIsActive(m_context)) {
 90			GBAThreadInterrupt(m_context);
 91		}
 92		QMetaObject::invokeMethod(m_painter, "unpause", Qt::BlockingQueuedConnection);
 93		if (GBAThreadIsActive(m_context)) {
 94			GBAThreadContinue(m_context);
 95		}
 96	}
 97}
 98
 99void DisplayGL::forceDraw() {
100	if (m_drawThread) {
101		QMetaObject::invokeMethod(m_painter, "forceDraw");
102	}
103}
104
105void DisplayGL::lockAspectRatio(bool lock) {
106	Display::lockAspectRatio(lock);
107	if (m_drawThread) {
108		QMetaObject::invokeMethod(m_painter, "lockAspectRatio", Q_ARG(bool, lock));
109	}
110}
111
112void DisplayGL::filter(bool filter) {
113	Display::filter(filter);
114	if (m_drawThread) {
115		QMetaObject::invokeMethod(m_painter, "filter", Q_ARG(bool, filter));
116	}
117}
118
119void DisplayGL::framePosted(const uint32_t* buffer) {
120	if (m_drawThread && buffer) {
121		m_painter->enqueue(buffer);
122		QMetaObject::invokeMethod(m_painter, "draw");
123	}
124}
125
126void DisplayGL::resizeEvent(QResizeEvent* event) {
127	Display::resizeEvent(event);
128	resizePainter();
129}
130
131void DisplayGL::resizePainter() {
132	m_gl->resize(size());
133	if (m_drawThread) {
134		QMetaObject::invokeMethod(m_painter, "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
135	}
136}
137
138PainterGL::PainterGL(QGLWidget* parent)
139	: m_gl(parent)
140	, m_active(false)
141	, m_context(nullptr)
142	, m_messagePainter(nullptr)
143{
144#ifdef BUILD_GL
145	GBAGLContextCreate(&m_backend);
146#elif defined(BUILD_GLES2)
147	GBAGLES2ContextCreate(&m_backend);
148#endif
149	m_backend.d.swap = [](VideoBackend* v) {
150		PainterGL* painter = static_cast<PainterGL*>(v->user);
151		painter->m_gl->swapBuffers();
152	};
153	m_backend.d.user = this;
154	m_backend.d.filter = false;
155	m_backend.d.lockAspectRatio = false;
156
157	for (int i = 0; i < 2; ++i) {
158		m_free.append(new uint32_t[256 * 256]);
159	}
160}
161
162PainterGL::~PainterGL() {
163	while (!m_queue.isEmpty()) {
164		delete[] m_queue.dequeue();
165	}
166	for (auto item : m_free) {
167		delete[] item;
168	}
169}
170
171void PainterGL::setContext(GBAThread* context) {
172	m_context = context;
173}
174
175void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
176	m_messagePainter = messagePainter;
177}
178
179void PainterGL::resize(const QSize& size) {
180	m_size = size;
181	if (m_active) {
182		forceDraw();
183	}
184}
185
186void PainterGL::lockAspectRatio(bool lock) {
187	m_backend.d.lockAspectRatio = lock;
188	if (m_active) {
189		forceDraw();
190	}
191}
192
193void PainterGL::filter(bool filter) {
194	m_backend.d.filter = filter;
195	if (m_active) {
196		forceDraw();
197	}
198}
199
200void PainterGL::start() {
201	m_gl->makeCurrent();
202	m_backend.d.init(&m_backend.d, reinterpret_cast<WHandle>(m_gl->winId()));
203	m_gl->doneCurrent();
204	m_active = true;
205}
206
207void PainterGL::draw() {
208	if (m_queue.isEmpty()) {
209		return;
210	}
211	if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) {
212		dequeue();
213		m_painter.begin(m_gl->context()->device());
214		performDraw();
215		m_painter.end();
216		GBASyncWaitFrameEnd(&m_context->sync);
217		m_backend.d.swap(&m_backend.d);
218	} else {
219		GBASyncWaitFrameEnd(&m_context->sync);
220	}
221	if (!m_queue.isEmpty()) {
222		QMetaObject::invokeMethod(this, "draw", Qt::QueuedConnection);
223	}
224}
225
226void PainterGL::forceDraw() {
227	m_painter.begin(m_gl->context()->device());
228	performDraw();
229	m_painter.end();
230	m_backend.d.swap(&m_backend.d);
231}
232
233void PainterGL::stop() {
234	m_active = false;
235	m_gl->makeCurrent();
236	dequeueAll();
237	m_backend.d.clear(&m_backend.d);
238	m_backend.d.swap(&m_backend.d);
239	m_backend.d.deinit(&m_backend.d);
240	m_gl->doneCurrent();
241	m_gl->context()->moveToThread(m_gl->thread());
242	moveToThread(m_gl->thread());
243}
244
245void PainterGL::pause() {
246	m_active = false;
247	// Make sure both buffers are filled
248	m_gl->makeCurrent();
249	dequeueAll();
250	forceDraw();
251	forceDraw();
252	m_gl->doneCurrent();
253}
254
255void PainterGL::unpause() {
256	m_active = true;
257}
258
259void PainterGL::performDraw() {
260	m_painter.beginNativePainting();
261	float r = m_gl->devicePixelRatio();
262	m_backend.d.resized(&m_backend.d, m_size.width() * r, m_size.height() * r);
263	m_backend.d.drawFrame(&m_backend.d);
264	m_painter.endNativePainting();
265	if (m_messagePainter) {
266		m_messagePainter->paint(&m_painter);
267	}
268}
269
270void PainterGL::enqueue(const uint32_t* backing) {
271	m_mutex.lock();
272	uint32_t* buffer;
273	if (m_free.isEmpty()) {
274		buffer = m_queue.dequeue();
275	} else {
276		buffer = m_free.takeLast();
277	}
278	memcpy(buffer, backing, 256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
279	m_queue.enqueue(buffer);
280	m_mutex.unlock();
281}
282
283void PainterGL::dequeue() {
284	m_mutex.lock();
285	if (m_queue.isEmpty()) {
286		m_mutex.unlock();
287		return;
288	}
289	uint32_t* buffer = m_queue.dequeue();
290	m_backend.d.postFrame(&m_backend.d, buffer);
291	m_free.append(buffer);
292	m_mutex.unlock();
293}
294
295void PainterGL::dequeueAll() {
296	uint32_t* buffer;
297	m_mutex.lock();
298	while (!m_queue.isEmpty()) {
299		buffer = m_queue.dequeue();
300		m_free.append(buffer);
301	}
302	m_backend.d.postFrame(&m_backend.d, buffer);
303	m_mutex.unlock();
304}