mirror of https://github.com/mgba-emu/mgba.git
Qt: Add VideoProxy
This commit is contained in:
parent
bb7f41e8cc
commit
42813bb197
|
@ -47,6 +47,7 @@ struct mCore {
|
|||
struct mTiming* timing;
|
||||
struct mDebugger* debugger;
|
||||
struct mDebuggerSymbols* symbolTable;
|
||||
struct mVideoLogger* videoLogger;
|
||||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
struct mDirectorySet dirs;
|
||||
|
|
|
@ -151,6 +151,7 @@ static bool _GBACoreInit(struct mCore* core) {
|
|||
core->timing = &gba->timing;
|
||||
core->debugger = NULL;
|
||||
core->symbolTable = NULL;
|
||||
core->videoLogger = NULL;
|
||||
gbacore->overrides = NULL;
|
||||
gbacore->debuggerPlatform = NULL;
|
||||
gbacore->cheatDevice = NULL;
|
||||
|
@ -392,11 +393,16 @@ static void _GBACoreReset(struct mCore* core) {
|
|||
#ifndef DISABLE_THREADING
|
||||
int fakeBool;
|
||||
if (mCoreConfigGetIntValue(&core->config, "threadedVideo", &fakeBool) && fakeBool) {
|
||||
gbacore->proxyRenderer.logger = &gbacore->threadProxy.d;
|
||||
if (!core->videoLogger) {
|
||||
core->videoLogger = &gbacore->threadProxy.d;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (core->videoLogger) {
|
||||
gbacore->proxyRenderer.logger = core->videoLogger;
|
||||
GBAVideoProxyRendererCreate(&gbacore->proxyRenderer, renderer);
|
||||
renderer = &gbacore->proxyRenderer.d;
|
||||
}
|
||||
#endif
|
||||
GBAVideoAssociateRenderer(&gba->video, renderer);
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ set(SOURCE_FILES
|
|||
utils.cpp
|
||||
Window.cpp
|
||||
VFileDevice.cpp
|
||||
VideoProxy.cpp
|
||||
VideoView.cpp)
|
||||
|
||||
set(UI_FILES
|
||||
|
|
|
@ -19,6 +19,7 @@ struct VideoShader;
|
|||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
class VideoProxy;
|
||||
|
||||
class Display : public QWidget {
|
||||
Q_OBJECT
|
||||
|
@ -47,6 +48,7 @@ public:
|
|||
virtual bool isDrawing() const = 0;
|
||||
virtual bool supportsShaders() const = 0;
|
||||
virtual VideoShader* shaders() = 0;
|
||||
virtual VideoProxy* videoProxy() { return nullptr; }
|
||||
|
||||
signals:
|
||||
void showCursor();
|
||||
|
|
|
@ -34,10 +34,12 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
|
|||
// This can spontaneously re-enter into this->resizeEvent before creation is done, so we
|
||||
// need to make sure it's initialized to nullptr before we assign the new object to it
|
||||
m_gl = new EmptyGLWidget(format, this);
|
||||
m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), m_gl);
|
||||
m_painter = new PainterGL(format.majorVersion() < 2 ? 1 : m_gl->format().majorVersion(), &m_videoProxy, m_gl);
|
||||
m_gl->setMouseTracking(true);
|
||||
m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work?
|
||||
setUpdatesEnabled(false); // Prevent paint events, which can cause race conditions
|
||||
|
||||
connect(&m_videoProxy, &VideoProxy::dataAvailable, &m_videoProxy, &VideoProxy::processData);
|
||||
}
|
||||
|
||||
DisplayGL::~DisplayGL() {
|
||||
|
@ -74,6 +76,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
m_gl->context()->doneCurrent();
|
||||
m_gl->context()->moveToThread(m_drawThread);
|
||||
m_painter->moveToThread(m_drawThread);
|
||||
m_videoProxy.moveToThread(m_drawThread);
|
||||
connect(m_drawThread, &QThread::started, m_painter, &PainterGL::start);
|
||||
m_drawThread->start();
|
||||
|
||||
|
@ -184,8 +187,16 @@ void DisplayGL::resizePainter() {
|
|||
}
|
||||
}
|
||||
|
||||
PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
|
||||
VideoProxy* DisplayGL::videoProxy() {
|
||||
if (supportsShaders()) {
|
||||
return &m_videoProxy;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PainterGL::PainterGL(int majorVersion, VideoProxy* proxy, QGLWidget* parent)
|
||||
: m_gl(parent)
|
||||
, m_videoProxy(proxy)
|
||||
{
|
||||
#ifdef BUILD_GL
|
||||
mGLContext* glBackend;
|
||||
|
@ -347,7 +358,7 @@ void PainterGL::draw() {
|
|||
m_backend->swap(m_backend);
|
||||
if (!m_delayTimer.isValid()) {
|
||||
m_delayTimer.start();
|
||||
} else {
|
||||
} else if (m_gl->format().swapInterval()) {
|
||||
while (m_delayTimer.elapsed() < 15) {
|
||||
QThread::usleep(100);
|
||||
}
|
||||
|
@ -382,6 +393,7 @@ void PainterGL::stop() {
|
|||
m_gl->context()->moveToThread(m_gl->thread());
|
||||
m_context.reset();
|
||||
moveToThread(m_gl->thread());
|
||||
m_videoProxy->moveToThread(m_gl->thread());
|
||||
}
|
||||
|
||||
void PainterGL::pause() {
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include <QQueue>
|
||||
#include <QThread>
|
||||
|
||||
#include "VideoProxy.h"
|
||||
|
||||
#include "platform/video-backend.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -49,6 +51,7 @@ public:
|
|||
bool isDrawing() const override { return m_isDrawing; }
|
||||
bool supportsShaders() const override;
|
||||
VideoShader* shaders() override;
|
||||
VideoProxy* videoProxy() override;
|
||||
|
||||
public slots:
|
||||
void stopDrawing() override;
|
||||
|
@ -75,13 +78,14 @@ private:
|
|||
PainterGL* m_painter;
|
||||
QThread* m_drawThread = nullptr;
|
||||
std::shared_ptr<CoreController> m_context;
|
||||
VideoProxy m_videoProxy;
|
||||
};
|
||||
|
||||
class PainterGL : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PainterGL(int majorVersion, QGLWidget* parent);
|
||||
PainterGL(int majorVersion, VideoProxy* proxy, QGLWidget* parent);
|
||||
~PainterGL();
|
||||
|
||||
void setContext(std::shared_ptr<CoreController>);
|
||||
|
@ -126,6 +130,7 @@ private:
|
|||
QSize m_size;
|
||||
MessagePainter* m_messagePainter = nullptr;
|
||||
QElapsedTimer m_delayTimer;
|
||||
VideoProxy* m_videoProxy;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/* Copyright (c) 2013-2018 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "VideoProxy.h"
|
||||
|
||||
#include "CoreController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
VideoProxy::VideoProxy() {
|
||||
mVideoLoggerRendererCreate(&m_logger.d, false);
|
||||
m_logger.d.block = true;
|
||||
|
||||
m_logger.d.init = &cbind<&VideoProxy::init>;
|
||||
m_logger.d.reset = &cbind<&VideoProxy::reset>;
|
||||
m_logger.d.deinit = &cbind<&VideoProxy::deinit>;
|
||||
m_logger.d.lock = &cbind<&VideoProxy::lock>;
|
||||
m_logger.d.unlock = &cbind<&VideoProxy::unlock>;
|
||||
m_logger.d.wait = &cbind<&VideoProxy::wait>;
|
||||
m_logger.d.wake = &callback<void, int>::func<&VideoProxy::wake>;
|
||||
|
||||
m_logger.d.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>;
|
||||
m_logger.d.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>;
|
||||
}
|
||||
|
||||
void VideoProxy::attach(CoreController* controller) {
|
||||
CoreController::Interrupter interrupter(controller);
|
||||
controller->thread()->core->videoLogger = &m_logger.d;
|
||||
}
|
||||
|
||||
void VideoProxy::processData() {
|
||||
mVideoLoggerRendererRun(&m_logger.d, false);
|
||||
m_fromThreadCond.wakeAll();
|
||||
}
|
||||
|
||||
void VideoProxy::init() {
|
||||
RingFIFOInit(&m_dirtyQueue, 0x80000);
|
||||
}
|
||||
|
||||
void VideoProxy::reset() {
|
||||
RingFIFOClear(&m_dirtyQueue);
|
||||
}
|
||||
|
||||
void VideoProxy::deinit() {
|
||||
RingFIFODeinit(&m_dirtyQueue);
|
||||
}
|
||||
|
||||
bool VideoProxy::writeData(const void* data, size_t length) {
|
||||
while (!RingFIFOWrite(&m_dirtyQueue, data, length)) {
|
||||
emit dataAvailable();
|
||||
m_mutex.lock();
|
||||
m_toThreadCond.wakeAll();
|
||||
m_fromThreadCond.wait(&m_mutex);
|
||||
m_mutex.unlock();
|
||||
}
|
||||
emit dataAvailable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoProxy::readData(void* data, size_t length, bool block) {
|
||||
bool read = false;
|
||||
while (true) {
|
||||
read = RingFIFORead(&m_dirtyQueue, data, length);
|
||||
if (!block || read) {
|
||||
break;
|
||||
}
|
||||
m_mutex.lock();
|
||||
m_fromThreadCond.wakeAll();
|
||||
m_toThreadCond.wait(&m_mutex);
|
||||
m_mutex.unlock();
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
void VideoProxy::lock() {
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
void VideoProxy::unlock() {
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void VideoProxy::wait() {
|
||||
while (RingFIFOSize(&m_dirtyQueue)) {
|
||||
emit dataAvailable();
|
||||
m_toThreadCond.wakeAll();
|
||||
m_fromThreadCond.wait(&m_mutex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProxy::wake(int y) {
|
||||
if ((y & 15) == 15) {
|
||||
m_toThreadCond.wakeAll();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/* Copyright (c) 2013-2018 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include <mgba/feature/video-logger.h>
|
||||
#include <mgba-util/ring-fifo.h>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
||||
class VideoProxy : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoProxy();
|
||||
|
||||
void attach(CoreController*);
|
||||
|
||||
signals:
|
||||
void dataAvailable();
|
||||
|
||||
public slots:
|
||||
void processData();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void reset();
|
||||
void deinit();
|
||||
|
||||
bool writeData(const void* data, size_t length);
|
||||
bool readData(void* data, size_t length, bool block);
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
void wait();
|
||||
void wake(int y);
|
||||
|
||||
template<typename T, typename... A> struct callback {
|
||||
using type = T (VideoProxy::*)(A...);
|
||||
|
||||
template<type F> static T func(mVideoLogger* logger, A... args) {
|
||||
VideoProxy* proxy = reinterpret_cast<Logger*>(logger)->p;
|
||||
return (proxy->*F)(args...);
|
||||
}
|
||||
};
|
||||
|
||||
template<void (VideoProxy::*F)()> static void cbind(mVideoLogger* logger) { callback<void>::func<F>(logger); }
|
||||
|
||||
struct Logger {
|
||||
mVideoLogger d;
|
||||
VideoProxy* p;
|
||||
} m_logger = {{}, this};
|
||||
|
||||
RingFIFO m_dirtyQueue;
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_toThreadCond;
|
||||
QWaitCondition m_fromThreadCond;
|
||||
};
|
||||
|
||||
}
|
|
@ -52,6 +52,7 @@
|
|||
#include "ShaderSelector.h"
|
||||
#include "ShortcutController.h"
|
||||
#include "TileView.h"
|
||||
#include "VideoProxy.h"
|
||||
#include "VideoView.h"
|
||||
|
||||
#ifdef USE_DISCORD_RPC
|
||||
|
@ -716,9 +717,6 @@ void Window::gameStarted() {
|
|||
if (m_savedScale > 0) {
|
||||
resizeFrame(size * m_savedScale);
|
||||
}
|
||||
if (!m_display) {
|
||||
reloadDisplayDriver();
|
||||
}
|
||||
attachWidget(m_display.get());
|
||||
m_display->setMinimumSize(size);
|
||||
setFocus();
|
||||
|
@ -1713,6 +1711,14 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
appendMRU(fname);
|
||||
}
|
||||
|
||||
if (!m_display) {
|
||||
reloadDisplayDriver();
|
||||
}
|
||||
|
||||
if (m_display->videoProxy()) {
|
||||
m_display->videoProxy()->attach(controller);
|
||||
}
|
||||
|
||||
m_controller = std::shared_ptr<CoreController>(controller);
|
||||
m_inputController.recalibrateAxes();
|
||||
m_controller->setInputController(&m_inputController);
|
||||
|
|
Loading…
Reference in New Issue