all repos — mgba @ 55679df8fc49fca3d5f8584d16b90e9bb734a922

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