all repos — mgba @ cb7f150cc2b647ae5a9dca2175bd5464d7d5e42f

mGBA Game Boy Advance Emulator

src/platform/qt/DisplayGL.cpp (view raw)

  1/* Copyright (c) 2013-2019 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 <QOpenGLContext>
 14#include <QOpenGLPaintDevice>
 15#include <QMutexLocker>
 16#include <QResizeEvent>
 17#include <QScreen>
 18#include <QTimer>
 19#include <QWindow>
 20
 21#include <mgba/core/core.h>
 22#include <mgba-util/math.h>
 23#ifdef BUILD_GL
 24#include "platform/opengl/gl.h"
 25#endif
 26#ifdef BUILD_GLES2
 27#include "platform/opengl/gles2.h"
 28#ifdef _WIN32
 29#include <epoxy/wgl.h>
 30#endif
 31#endif
 32
 33using namespace QGBA;
 34
 35DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
 36	: Display(parent)
 37{
 38	setAttribute(Qt::WA_NativeWindow);
 39	windowHandle()->create();
 40
 41	m_painter = std::make_unique<PainterGL>(windowHandle(), format);
 42}
 43
 44DisplayGL::~DisplayGL() {
 45	stopDrawing();
 46}
 47
 48bool DisplayGL::supportsShaders() const {
 49	return m_painter->supportsShaders();
 50}
 51
 52VideoShader* DisplayGL::shaders() {
 53	VideoShader* shaders = nullptr;
 54	if (m_drawThread) {
 55		QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
 56	} else {
 57		shaders = m_painter->shaders();
 58	}
 59	return shaders;
 60}
 61
 62void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
 63	if (m_drawThread) {
 64		return;
 65	}
 66	m_isDrawing = true;
 67	m_painter->setContext(controller);
 68	m_painter->setMessagePainter(messagePainter());
 69	m_context = controller;
 70	m_drawThread = new QThread(this);
 71	m_drawThread->setObjectName("Painter Thread");
 72	m_painter->moveToThread(m_drawThread);
 73	if (videoProxy()) {
 74		videoProxy()->moveToThread(m_drawThread);
 75	}
 76	connect(m_drawThread, &QThread::started, m_painter.get(), &PainterGL::start);
 77	connect(m_painter.get(), &PainterGL::started, this, [this] {
 78		m_hasStarted = true;
 79		resizePainter();
 80		emit drawingStarted();
 81	});
 82
 83	lockAspectRatio(isAspectRatioLocked());
 84	lockIntegerScaling(isIntegerScalingLocked());
 85	interframeBlending(hasInterframeBlending());
 86	showOSDMessages(isShowOSD());
 87	filter(isFiltered());
 88
 89	m_drawThread->start();
 90
 91#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
 92	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
 93#else
 94	messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
 95#endif
 96	setUpdatesEnabled(false);
 97}
 98
 99void DisplayGL::stopDrawing() {
100	if (m_drawThread) {
101		m_isDrawing = false;
102		m_hasStarted = false;
103		CoreController::Interrupter interrupter(m_context);
104		QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
105		m_drawThread->exit();
106		m_drawThread->wait();
107		m_drawThread = nullptr;
108		setUpdatesEnabled(true);
109	}
110	m_context.reset();
111}
112
113void DisplayGL::pauseDrawing() {
114	if (m_drawThread) {
115		m_isDrawing = false;
116		CoreController::Interrupter interrupter(m_context);
117		QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
118#ifndef Q_OS_MAC
119		setUpdatesEnabled(true);
120#endif
121	}
122}
123
124void DisplayGL::unpauseDrawing() {
125	if (m_drawThread) {
126		m_isDrawing = true;
127		CoreController::Interrupter interrupter(m_context);
128		QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
129#ifndef Q_OS_MAC
130		setUpdatesEnabled(false);
131#endif
132	}
133}
134
135void DisplayGL::forceDraw() {
136	if (m_drawThread) {
137		QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
138	}
139}
140
141void DisplayGL::lockAspectRatio(bool lock) {
142	Display::lockAspectRatio(lock);
143	if (m_drawThread) {
144		QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
145	}
146}
147
148void DisplayGL::lockIntegerScaling(bool lock) {
149	Display::lockIntegerScaling(lock);
150	if (m_drawThread) {
151		QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
152	}
153}
154
155void DisplayGL::interframeBlending(bool enable) {
156	Display::interframeBlending(enable);
157	if (m_drawThread) {
158		QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
159	}
160}
161
162void DisplayGL::showOSDMessages(bool enable) {
163	Display::showOSDMessages(enable);
164	if (m_drawThread) {
165		QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
166	}
167}
168
169void DisplayGL::filter(bool filter) {
170	Display::filter(filter);
171	if (m_drawThread) {
172		QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
173	}
174}
175
176void DisplayGL::framePosted() {
177	if (m_drawThread) {
178		m_painter->enqueue(m_context->drawContext());
179		QMetaObject::invokeMethod(m_painter.get(), "draw");
180	}
181}
182
183void DisplayGL::setShaders(struct VDir* shaders) {
184	if (m_drawThread) {
185		QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
186	} else {
187		m_painter->setShaders(shaders);
188	}
189}
190
191void DisplayGL::clearShaders() {
192	QMetaObject::invokeMethod(m_painter.get(), "clearShaders");
193}
194
195void DisplayGL::resizeContext() {
196	if (m_drawThread) {
197		CoreController::Interrupter interrupter(m_context);
198		QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
199	}
200}
201
202void DisplayGL::setVideoScale(int scale) {
203	if (m_drawThread) {
204		CoreController::Interrupter interrupter(m_context);
205		mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
206		QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
207	}
208}
209
210void DisplayGL::resizeEvent(QResizeEvent* event) {
211	Display::resizeEvent(event);
212	resizePainter();
213}
214
215void DisplayGL::resizePainter() {
216	if (m_drawThread && m_hasStarted) {
217		QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
218	}
219}
220
221void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
222	Display::setVideoProxy(proxy);
223	if (m_drawThread && proxy) {
224		proxy->moveToThread(m_drawThread);
225	}
226	m_painter->setVideoProxy(proxy);
227}
228
229int DisplayGL::framebufferHandle() {
230	return m_painter->glTex();
231}
232
233PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
234	: m_surface(surface)
235	, m_format(format)
236{
237	for (auto& buf : m_buffers) {
238		m_free.append(&buf.front());
239	}
240}
241
242PainterGL::~PainterGL() {
243	if (m_gl) {
244		destroy();
245	}
246}
247
248void PainterGL::makeCurrent() {
249	m_gl->makeCurrent(m_surface);
250#if defined(_WIN32) && defined(USE_EPOXY)
251	epoxy_handle_external_wglMakeCurrent();
252#endif
253}
254
255void PainterGL::create() {
256	m_gl = std::make_unique<QOpenGLContext>();
257	m_gl->setFormat(m_format);
258	m_gl->create();
259	makeCurrent();
260
261	auto version = m_gl->format().version();
262	QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
263
264	int forceVersion = 0;
265	if (m_format.majorVersion() < 2) {
266		forceVersion = 1;
267	}
268
269	if ((version == qMakePair(2, 1) && !extensions.contains("GL_ARB_framebuffer_object")) || version == qMakePair(2, 0)) {
270		QSurfaceFormat newFormat(m_format);
271		newFormat.setVersion(1, 4);
272		forceVersion = 1;
273		m_gl->doneCurrent();
274		m_gl->setFormat(newFormat);
275		m_gl->create();
276		makeCurrent();
277	}
278
279#ifdef BUILD_GL
280	mGLContext* glBackend;
281#endif
282#ifdef BUILD_GLES2
283	mGLES2Context* gl2Backend;
284#endif
285
286	m_window = std::make_unique<QOpenGLPaintDevice>();
287
288#ifdef BUILD_GLES2
289	version = m_gl->format().version();
290	extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
291	if (forceVersion != 1 && ((version == qMakePair(2, 1) && extensions.contains("GL_ARB_framebuffer_object")) || version.first > 2)) {
292		gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
293		mGLES2ContextCreate(gl2Backend);
294		m_backend = &gl2Backend->d;
295		m_supportsShaders = true;
296	}
297#endif
298
299#ifdef BUILD_GL
300	 if (!m_backend) {
301		glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
302		mGLContextCreate(glBackend);
303		m_backend = &glBackend->d;
304		m_supportsShaders = false;
305	}
306#endif
307	m_backend->swap = [](VideoBackend* v) {
308		PainterGL* painter = static_cast<PainterGL*>(v->user);
309		if (!painter->m_gl->isValid()) {
310			return;
311		}
312		painter->m_gl->swapBuffers(painter->m_surface);
313		painter->makeCurrent();
314	};
315
316	m_backend->init(m_backend, 0);
317#ifdef BUILD_GLES2
318	if (m_supportsShaders) {
319		m_shader.preprocessShader = static_cast<void*>(&reinterpret_cast<mGLES2Context*>(m_backend)->initialShader);
320	}
321#endif
322
323	m_backend->user = this;
324	m_backend->filter = false;
325	m_backend->lockAspectRatio = false;
326	m_backend->interframeBlending = false;
327}
328
329void PainterGL::destroy() {
330	if (!m_gl) {
331		return;
332	}
333	makeCurrent();
334	if (m_context) {
335		if (m_videoProxy) {
336			m_videoProxy->detach(m_context.get());
337		}
338		m_context->setFramebufferHandle(-1);
339		if (m_videoProxy) {
340			m_videoProxy->processData();
341		}
342	}
343#ifdef BUILD_GLES2
344	if (m_shader.passes) {
345		mGLES2ShaderFree(&m_shader);
346	}
347#endif
348	m_backend->deinit(m_backend);
349	m_gl->doneCurrent();
350	m_window.reset();
351	m_gl.reset();
352
353	free(m_backend);
354	m_backend = nullptr;
355}
356
357void PainterGL::setContext(std::shared_ptr<CoreController> context) {
358	m_context = context;
359}
360
361void PainterGL::resizeContext() {
362	if (!m_context) {
363		return;
364	}
365
366	if (m_started) {
367		mCore* core = m_context->thread()->core;
368		core->reloadConfigOption(core, "videoScale", NULL);
369	}
370
371	QSize size = m_context->screenDimensions();
372	m_backend->setDimensions(m_backend, size.width(), size.height());
373}
374
375void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
376	m_messagePainter = messagePainter;
377}
378
379void PainterGL::resize(const QSize& size) {
380	m_size = size;
381	m_window->setSize(m_size);
382	if (m_started && !m_active) {
383		forceDraw();
384	}
385}
386
387void PainterGL::lockAspectRatio(bool lock) {
388	m_backend->lockAspectRatio = lock;
389	resize(m_size);
390}
391
392void PainterGL::lockIntegerScaling(bool lock) {
393	m_backend->lockIntegerScaling = lock;
394	resize(m_size);
395}
396
397void PainterGL::interframeBlending(bool enable) {
398	m_backend->interframeBlending = enable;
399}
400
401void PainterGL::showOSD(bool enable) {
402	m_showOSD = enable;
403}
404
405void PainterGL::filter(bool filter) {
406	m_backend->filter = filter;
407	if (m_started && !m_active) {
408		forceDraw();
409	}
410}
411
412void PainterGL::start() {
413	if (!m_gl) {
414		create();
415	}
416	makeCurrent();
417
418#ifdef BUILD_GLES2
419	if (m_supportsShaders && m_shader.passes) {
420		mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
421	}
422#endif
423	resizeContext();
424
425	m_buffer = nullptr;
426	m_active = true;
427	m_started = true;
428	emit started();
429}
430
431void PainterGL::draw() {
432	if (!m_active || m_queue.isEmpty()) {
433		return;
434	}
435	mCoreSync* sync = &m_context->thread()->impl->sync;
436	mCoreSyncWaitFrameStart(sync);
437	dequeue();
438	if (!m_delayTimer.isValid()) {
439		m_delayTimer.start();
440	} else if (sync->audioWait || sync->videoFrameWait) {
441		while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
442			QThread::usleep(500);
443		}
444		m_delayTimer.restart();
445	}
446	mCoreSyncWaitFrameEnd(sync);
447
448	forceDraw();
449}
450
451void PainterGL::forceDraw() {
452	m_painter.begin(m_window.get());
453	performDraw();
454	m_painter.end();
455	if (!m_context->thread()->impl->sync.audioWait && !m_context->thread()->impl->sync.videoFrameWait) {
456		if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
457			return;
458		}
459		m_delayTimer.restart();
460	}
461	m_backend->swap(m_backend);
462}
463
464void PainterGL::stop() {
465	m_active = false;
466	m_started = false;
467	dequeueAll();
468	m_backend->clear(m_backend);
469	m_backend->swap(m_backend);
470	if (m_videoProxy) {
471		m_videoProxy->reset();
472	}
473	destroy();
474	moveToThread(m_surface->thread());
475	if (m_videoProxy) {
476		m_videoProxy->moveToThread(m_surface->thread());
477	}
478}
479
480void PainterGL::pause() {
481	m_active = false;
482}
483
484void PainterGL::unpause() {
485	m_active = true;
486}
487
488void PainterGL::performDraw() {
489	m_painter.beginNativePainting();
490	float r = m_surface->devicePixelRatio();
491	m_backend->resized(m_backend, m_size.width() * r, m_size.height() * r);
492	if (m_buffer) {
493		m_backend->postFrame(m_backend, m_buffer);
494	}
495	m_backend->drawFrame(m_backend);
496	m_painter.endNativePainting();
497	if (m_showOSD && m_messagePainter) {
498		m_messagePainter->paint(&m_painter);
499	}
500}
501
502void PainterGL::enqueue(const uint32_t* backing) {
503	QMutexLocker locker(&m_mutex);
504	uint32_t* buffer = nullptr;
505	if (backing) {
506		if (m_free.isEmpty()) {
507			buffer = m_queue.dequeue();
508		} else {
509			buffer = m_free.takeLast();
510		}
511		if (buffer) {
512			QSize size = m_context->screenDimensions();
513			memcpy(buffer, backing, size.width() * size.height() * BYTES_PER_PIXEL);
514		}
515	}
516	m_queue.enqueue(buffer);
517}
518
519void PainterGL::dequeue() {
520	QMutexLocker locker(&m_mutex);
521	if (m_queue.isEmpty()) {
522		return;
523	}
524	uint32_t* buffer = m_queue.dequeue();
525	if (m_buffer) {
526		m_free.append(m_buffer);
527		m_buffer = nullptr;
528	}
529	m_buffer = buffer;
530}
531
532void PainterGL::dequeueAll() {
533	uint32_t* buffer = 0;
534	m_mutex.lock();
535	while (!m_queue.isEmpty()) {
536		buffer = m_queue.dequeue();
537		if (buffer) {
538			m_free.append(buffer);
539		}
540	}
541	if (buffer) {
542		m_backend->postFrame(m_backend, buffer);
543	}
544	if (m_buffer) {
545		m_free.append(m_buffer);
546		m_buffer = nullptr;
547	}
548	m_mutex.unlock();
549}
550
551void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
552	m_videoProxy = proxy;
553}
554
555void PainterGL::setShaders(struct VDir* dir) {
556	if (!supportsShaders()) {
557		return;
558	}
559#ifdef BUILD_GLES2
560	if (!m_started) {
561		return; // TODO
562	}
563	if (m_shader.passes) {
564		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
565		mGLES2ShaderFree(&m_shader);
566	}
567	mGLES2ShaderLoad(&m_shader, dir);
568	mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
569#endif
570}
571
572void PainterGL::clearShaders() {
573	if (!supportsShaders()) {
574		return;
575	}
576#ifdef BUILD_GLES2
577	if (!m_started) {
578		return; // TODO
579	}
580	if (m_shader.passes) {
581		mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
582		mGLES2ShaderFree(&m_shader);
583	}
584#endif
585}
586
587VideoShader* PainterGL::shaders() {
588	return &m_shader;
589}
590
591int PainterGL::glTex() {
592#ifdef BUILD_GLES2
593	if (supportsShaders()) {
594		mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(m_backend);
595		return gl2Backend->tex;
596	}
597#endif
598#ifdef BUILD_GL
599	mGLContext* glBackend = reinterpret_cast<mGLContext*>(m_backend);
600	return glBackend->tex[0];
601#else
602	return -1;
603#endif
604}
605
606#endif