Qt: Add option to lock the maximum frame size (closes #1493)

This commit is contained in:
Vicki Pfau 2024-04-14 20:39:58 -07:00
parent be85200b3e
commit d1a6e6b747
14 changed files with 110 additions and 24 deletions

View File

@ -1,5 +1,6 @@
0.11.0: (Future) 0.11.0: (Future)
Features: Features:
- New option to lock the maximum frame size
- Scripting: New `input` API for getting raw keyboard/mouse/controller state - Scripting: New `input` API for getting raw keyboard/mouse/controller state
- Scripting: New `storage` API for saving data for a script, e.g. settings - Scripting: New `storage` API for saving data for a script, e.g. settings
- Scripting: Debugger integration to allow for breakpoints and watchpoints - Scripting: Debugger integration to allow for breakpoints and watchpoints

View File

@ -38,6 +38,8 @@ union mVideoBackendCommandData {
struct { struct {
unsigned width; unsigned width;
unsigned height; unsigned height;
unsigned maxW;
unsigned maxH;
} u; } u;
const void* image; const void* image;
}; };

View File

@ -48,7 +48,7 @@ struct VideoBackend {
void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); void (*layerDimensions)(const struct VideoBackend*, enum VideoLayer, struct mRectangle*);
void (*swap)(struct VideoBackend*); void (*swap)(struct VideoBackend*);
void (*clear)(struct VideoBackend*); void (*clear)(struct VideoBackend*);
void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h); void (*contextResized)(struct VideoBackend*, unsigned w, unsigned h, unsigned maxW, unsigned maxH);
void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h); void (*setImageSize)(struct VideoBackend*, enum VideoLayer, int w, int h);
void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h); void (*imageSize)(struct VideoBackend*, enum VideoLayer, int* w, int* h);
void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame); void (*setImage)(struct VideoBackend*, enum VideoLayer, const void* frame);

View File

@ -61,7 +61,7 @@ static void _mVideoProxyBackendClear(struct VideoBackend* v) {
mVideoProxyBackendSubmit(proxy, &cmd, NULL); mVideoProxyBackendSubmit(proxy, &cmd, NULL);
} }
static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w, unsigned h) { static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w, unsigned h, unsigned maxW, unsigned maxH) {
struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v;
struct mVideoBackendCommand cmd = { struct mVideoBackendCommand cmd = {
.cmd = mVB_CMD_CONTEXT_RESIZED, .cmd = mVB_CMD_CONTEXT_RESIZED,
@ -69,6 +69,8 @@ static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w
.u = { .u = {
.width = w, .width = w,
.height = h, .height = h,
.maxW = maxW,
.maxH = maxH,
} }
} }
}; };
@ -139,8 +141,8 @@ void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBacken
proxy->d.drawFrame = _mVideoProxyBackendDrawFrame; proxy->d.drawFrame = _mVideoProxyBackendDrawFrame;
proxy->backend = backend; proxy->backend = backend;
RingFIFOInit(&proxy->in, 0x400); RingFIFOInit(&proxy->in, sizeof(union mVideoBackendCommandData) * 0x80);
RingFIFOInit(&proxy->out, 0x400); RingFIFOInit(&proxy->out, sizeof(union mVideoBackendCommandData) * 0x80);
MutexInit(&proxy->inLock); MutexInit(&proxy->inLock);
MutexInit(&proxy->outLock); MutexInit(&proxy->outLock);
ConditionInit(&proxy->inWait); ConditionInit(&proxy->inWait);
@ -209,7 +211,7 @@ bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block) {
proxy->backend->clear(proxy->backend); proxy->backend->clear(proxy->backend);
break; break;
case mVB_CMD_CONTEXT_RESIZED: case mVB_CMD_CONTEXT_RESIZED:
proxy->backend->contextResized(proxy->backend, cmd.data.u.width, cmd.data.u.height); proxy->backend->contextResized(proxy->backend, cmd.data.u.width, cmd.data.u.height, cmd.data.u.maxW, cmd.data.u.maxH);
break; break;
case mVB_CMD_SET_IMAGE_SIZE: case mVB_CMD_SET_IMAGE_SIZE:
proxy->backend->setImageSize(proxy->backend, cmd.layer, cmd.data.s.width, cmd.data.s.height); proxy->backend->setImageSize(proxy->backend, cmd.layer, cmd.data.s.width, cmd.data.s.height);

View File

@ -103,20 +103,29 @@ static void mGLContextDeinit(struct VideoBackend* v) {
glDeleteTextures(VIDEO_LAYER_MAX, context->layers); glDeleteTextures(VIDEO_LAYER_MAX, context->layers);
} }
static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h) { static void mGLContextResized(struct VideoBackend* v, unsigned w, unsigned h, unsigned maxW, unsigned maxH) {
unsigned drawW = w; unsigned drawW = w;
unsigned drawH = h; unsigned drawH = h;
unsigned maxW;
unsigned maxH; if (maxW && drawW > maxW) {
VideoBackendGetFrameSize(v, &maxW, &maxH); drawW = maxW;
}
if (maxH && drawH > maxH) {
drawH = maxH;
}
unsigned lockW;
unsigned lockH;
VideoBackendGetFrameSize(v, &lockW, &lockH);
if (v->lockAspectRatio) { if (v->lockAspectRatio) {
lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); lockAspectRatioUInt(lockW, lockH, &drawW, &drawH);
} }
if (v->lockIntegerScaling) { if (v->lockIntegerScaling) {
lockIntegerRatioUInt(maxW, &drawW); lockIntegerRatioUInt(lockW, &drawW);
lockIntegerRatioUInt(maxH, &drawH); lockIntegerRatioUInt(lockH, &drawH);
} }
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();

View File

@ -271,20 +271,28 @@ static void mGLES2ContextDeinit(struct VideoBackend* v) {
free(context->initialShader.uniforms); free(context->initialShader.uniforms);
} }
static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h) { static void mGLES2ContextResized(struct VideoBackend* v, unsigned w, unsigned h, unsigned maxW, unsigned maxH) {
struct mGLES2Context* context = (struct mGLES2Context*) v; struct mGLES2Context* context = (struct mGLES2Context*) v;
unsigned drawW = w; unsigned drawW = w;
unsigned drawH = h; unsigned drawH = h;
unsigned maxW = context->width; if (maxW && drawW > maxW) {
unsigned maxH = context->height; drawW = maxW;
}
if (maxH && drawH > maxH) {
drawH = maxH;
}
unsigned lockW = context->width;
unsigned lockH = context->height;
if (v->lockAspectRatio) { if (v->lockAspectRatio) {
lockAspectRatioUInt(maxW, maxH, &drawW, &drawH); lockAspectRatioUInt(lockW, lockH, &drawW, &drawH);
} }
if (v->lockIntegerScaling) { if (v->lockIntegerScaling) {
lockIntegerRatioUInt(maxW, &drawW); lockIntegerRatioUInt(lockW, &drawW);
lockIntegerRatioUInt(maxH, &drawH); lockIntegerRatioUInt(lockH, &drawH);
} }
size_t n; size_t n;
for (n = 0; n < context->nShaders; ++n) { for (n = 0; n < context->nShaders; ++n) {

View File

@ -57,6 +57,7 @@ public:
virtual void setVideoScale(int) {} virtual void setVideoScale(int) {}
virtual void setBackgroundImage(const QImage&) = 0; virtual void setBackgroundImage(const QImage&) = 0;
virtual QSize contentSize() const = 0; virtual QSize contentSize() const = 0;
virtual void setMaximumSize(const QSize& size) = 0;
virtual void setVideoProxy(std::shared_ptr<VideoProxy> proxy) { m_videoProxy = std::move(proxy); } virtual void setVideoProxy(std::shared_ptr<VideoProxy> proxy) { m_videoProxy = std::move(proxy); }
std::shared_ptr<VideoProxy> videoProxy() { return m_videoProxy; } std::shared_ptr<VideoProxy> videoProxy() { return m_videoProxy; }
@ -105,6 +106,7 @@ private:
bool m_filter = false; bool m_filter = false;
QTimer m_mouseTimer; QTimer m_mouseTimer;
std::shared_ptr<VideoProxy> m_videoProxy; std::shared_ptr<VideoProxy> m_videoProxy;
QSize m_maxSize;
}; };
} }

View File

@ -524,6 +524,10 @@ int DisplayGL::framebufferHandle() {
return m_painter->glTex(); return m_painter->glTex();
} }
void DisplayGL::setMaximumSize(const QSize& size) {
QMetaObject::invokeMethod(m_painter.get(), "setMaximumSize", Q_ARG(const QSize&, size));
}
PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format) PainterGL::PainterGL(QWindow* window, mGLWidget* widget, const QSurfaceFormat& format)
: m_window(window) : m_window(window)
, m_format(format) , m_format(format)
@ -720,6 +724,11 @@ void PainterGL::resize(const QSize& size) {
} }
} }
void PainterGL::setMaximumSize(const QSize& size) {
m_maxSize = size;
resizeContext();
}
void PainterGL::lockAspectRatio(bool lock) { void PainterGL::lockAspectRatio(bool lock) {
m_backend->lockAspectRatio = lock; m_backend->lockAspectRatio = lock;
resize(m_size); resize(m_size);
@ -911,7 +920,11 @@ void PainterGL::unpause() {
void PainterGL::performDraw() { void PainterGL::performDraw() {
float r = m_window->devicePixelRatio(); float r = m_window->devicePixelRatio();
m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r); QSize maxSize = m_maxSize;
if (!maxSize.isValid()) {
maxSize = QSize(0, 0);
}
m_backend->contextResized(m_backend, m_size.width() * r, m_size.height() * r, maxSize.width() * r, maxSize.height() * r);
if (m_buffer) { if (m_buffer) {
m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer); m_backend->setImage(m_backend, VIDEO_LAYER_IMAGE, m_buffer);
} }

View File

@ -95,6 +95,7 @@ public:
void setVideoProxy(std::shared_ptr<VideoProxy>) override; void setVideoProxy(std::shared_ptr<VideoProxy>) override;
int framebufferHandle() override; int framebufferHandle() override;
QSize contentSize() const override { return m_cachedContentSize; } QSize contentSize() const override { return m_cachedContentSize; }
void setMaximumSize(const QSize& size) override;
static bool highestCompatible(QSurfaceFormat&); static bool highestCompatible(QSurfaceFormat&);
static bool supportsFormat(const QSurfaceFormat&); static bool supportsFormat(const QSurfaceFormat&);
@ -172,6 +173,7 @@ public slots:
void pause(); void pause();
void unpause(); void unpause();
void resize(const QSize& size); void resize(const QSize& size);
void setMaximumSize(const QSize& size);
void lockAspectRatio(bool lock); void lockAspectRatio(bool lock);
void lockIntegerScaling(bool lock); void lockIntegerScaling(bool lock);
void interframeBlending(bool enable); void interframeBlending(bool enable);
@ -230,6 +232,7 @@ private:
VideoBackend* m_backend = nullptr; VideoBackend* m_backend = nullptr;
QSize m_size; QSize m_size;
QSize m_dims; QSize m_dims;
QSize m_maxSize;
MessagePainter* m_messagePainter = nullptr; MessagePainter* m_messagePainter = nullptr;
QElapsedTimer m_delayTimer; QElapsedTimer m_delayTimer;
std::shared_ptr<VideoProxy> m_videoProxy; std::shared_ptr<VideoProxy> m_videoProxy;

View File

@ -134,7 +134,20 @@ void DisplayQt::paintEvent(QPaintEvent*) {
if (!drawSize.isValid() || drawSize.width() < 1 || drawSize.height() < 1) { if (!drawSize.isValid() || drawSize.width() < 1 || drawSize.height() < 1) {
return; return;
} }
QRect full(clampSize(contentSize(), size(), isAspectRatioLocked(), isIntegerScalingLocked())); QSize usedSize = size();
QPoint screenOrigin(0, 0);
if (m_maxSize.isValid()) {
if (m_maxSize.width() < usedSize.width()) {
screenOrigin.setX((usedSize.width() - m_maxSize.width()) / 2);
usedSize.setWidth(m_maxSize.width());
}
if (m_maxSize.height() < usedSize.height()) {
screenOrigin.setY((usedSize.height() - m_maxSize.height()) / 2);
usedSize.setHeight(m_maxSize.height());
}
}
QRect full(clampSize(contentSize(), usedSize, isAspectRatioLocked(), isIntegerScalingLocked()));
full.translate(screenOrigin);
painter.save(); painter.save();
painter.translate(full.topLeft()); painter.translate(full.topLeft());
painter.scale(full.width() / static_cast<qreal>(frame.width), full.height() / static_cast<qreal>(frame.height)); painter.scale(full.width() / static_cast<qreal>(frame.width), full.height() / static_cast<qreal>(frame.height));
@ -220,7 +233,7 @@ void DisplayQt::swap(struct VideoBackend*) {
void DisplayQt::clear(struct VideoBackend*) { void DisplayQt::clear(struct VideoBackend*) {
} }
void DisplayQt::contextResized(struct VideoBackend*, unsigned, unsigned) { void DisplayQt::contextResized(struct VideoBackend*, unsigned, unsigned, unsigned, unsigned) {
} }
void DisplayQt::setImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { void DisplayQt::setImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) {

View File

@ -27,6 +27,8 @@ public:
VideoShader* shaders() override { return nullptr; } VideoShader* shaders() override { return nullptr; }
QSize contentSize() const override; QSize contentSize() const override;
VideoBackend* videoBackend() override { return &m_backend; } VideoBackend* videoBackend() override { return &m_backend; }
void setMaximumSize(const QSize& size) override { m_maxSize = size; }
public slots: public slots:
void stopDrawing() override; void stopDrawing() override;
@ -56,7 +58,7 @@ private:
static void layerDimensions(const struct VideoBackend*, enum VideoLayer, struct mRectangle*); static void layerDimensions(const struct VideoBackend*, enum VideoLayer, struct mRectangle*);
static void swap(struct VideoBackend*); static void swap(struct VideoBackend*);
static void clear(struct VideoBackend*); static void clear(struct VideoBackend*);
static void contextResized(struct VideoBackend*, unsigned w, unsigned h); static void contextResized(struct VideoBackend*, unsigned w, unsigned h, unsigned maxW, unsigned maxH);
static void setImageSize(struct VideoBackend*, enum VideoLayer, int w, int h); static void setImageSize(struct VideoBackend*, enum VideoLayer, int w, int h);
static void imageSize(struct VideoBackend*, enum VideoLayer, int* w, int* h); static void imageSize(struct VideoBackend*, enum VideoLayer, int* w, int* h);
static void setImage(struct VideoBackend*, enum VideoLayer, const void* frame); static void setImage(struct VideoBackend*, enum VideoLayer, const void* frame);
@ -69,6 +71,7 @@ private:
int m_width = -1; int m_width = -1;
int m_height = -1; int m_height = -1;
QImage m_oldBacking{nullptr}; QImage m_oldBacking{nullptr};
QSize m_maxSize;
std::shared_ptr<CoreController> m_context = nullptr; std::shared_ptr<CoreController> m_context = nullptr;
}; };

View File

@ -252,6 +252,9 @@ void Window::argumentsPassed() {
void Window::resizeFrame(const QSize& size) { void Window::resizeFrame(const QSize& size) {
QSize newSize(size); QSize newSize(size);
if (!m_config->getOption("lockFrameSize").toInt()) {
m_savedSize = size;
}
if (windowHandle()) { if (windowHandle()) {
QRect geom = windowHandle()->screen()->availableGeometry(); QRect geom = windowHandle()->screen()->availableGeometry();
if (newSize.width() > geom.width()) { if (newSize.width() > geom.width()) {
@ -1522,7 +1525,10 @@ void Window::setupMenu(QMenuBar* menubar) {
for (int i = 1; i <= 8; ++i) { for (int i = 1; i <= 8; ++i) {
auto setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() { auto setSize = m_actions.addAction(tr("%1×").arg(QString::number(i)), QString("frame.%1x").arg(QString::number(i)), [this, i]() {
auto setSize = m_frameSizes[i]; auto setSize = m_frameSizes[i];
showNormal(); bool lockFrameSize = m_config->getOption("lockFrameSize").toInt();
if (!lockFrameSize) {
showNormal();
}
#if defined(M_CORE_GBA) #if defined(M_CORE_GBA)
QSize minimumSize = QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS); QSize minimumSize = QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
#elif defined(M_CORE_GB) #elif defined(M_CORE_GB)
@ -1538,7 +1544,11 @@ void Window::setupMenu(QMenuBar* menubar) {
size *= i; size *= i;
m_savedScale = i; m_savedScale = i;
m_config->setOption("scaleMultiplier", i); // TODO: Port to other m_config->setOption("scaleMultiplier", i); // TODO: Port to other
m_savedSize = size;
resizeFrame(size); resizeFrame(size);
if (lockFrameSize) {
m_display->setMaximumSize(size);
}
setSize->setActive(true); setSize->setActive(true);
}, "frame"); }, "frame");
setSize->setExclusive(true); setSize->setExclusive(true);
@ -1553,8 +1563,22 @@ void Window::setupMenu(QMenuBar* menubar) {
#else #else
fullscreenKeys = QKeySequence("Ctrl+F"); fullscreenKeys = QKeySequence("Ctrl+F");
#endif #endif
m_actions.addSeparator("frame");
m_actions.addAction(tr("Toggle fullscreen"), "fullscreen", this, &Window::toggleFullScreen, "frame", fullscreenKeys); m_actions.addAction(tr("Toggle fullscreen"), "fullscreen", this, &Window::toggleFullScreen, "frame", fullscreenKeys);
ConfigOption* lockFrameSize = m_config->addOption("lockFrameSize");
lockFrameSize->addBoolean(tr("&Lock frame size"), &m_actions, "frame");
lockFrameSize->connect([this](const QVariant& value) {
if (m_display) {
if (value.toBool()) {
m_display->setMaximumSize(m_display->size());
} else {
m_display->setMaximumSize({});
}
}
}, this);
m_config->updateOption("lockFrameSize");
ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio"); ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");
lockAspectRatio->addBoolean(tr("Lock aspect ratio"), &m_actions, "av"); lockAspectRatio->addBoolean(tr("Lock aspect ratio"), &m_actions, "av");
lockAspectRatio->connect([this](const QVariant& value) { lockAspectRatio->connect([this](const QVariant& value) {
@ -2216,6 +2240,11 @@ void Window::setController(CoreController* controller, const QString& fname) {
void Window::attachDisplay() { void Window::attachDisplay() {
m_display->attach(m_controller); m_display->attach(m_controller);
connect(m_display.get(), &QGBA::Display::drawingStarted, this, &Window::changeRenderer); connect(m_display.get(), &QGBA::Display::drawingStarted, this, &Window::changeRenderer);
if (m_config->getOption("lockFrameSize").toInt()) {
m_display->setMaximumSize(m_savedSize);
} else {
m_display->setMaximumSize({});
}
m_display->startDrawing(m_controller); m_display->startDrawing(m_controller);
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING

View File

@ -194,6 +194,7 @@ private:
std::unique_ptr<QGBA::Display> m_display; std::unique_ptr<QGBA::Display> m_display;
QSize m_initialSize; QSize m_initialSize;
QSize m_savedSize;
int m_savedScale; int m_savedScale;
// TODO: Move these to a new class // TODO: Move these to a new class

View File

@ -15,7 +15,7 @@
#endif #endif
void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) { void mSDLGLDoViewport(int w, int h, struct VideoBackend* v) {
v->contextResized(v, w, h); v->contextResized(v, w, h, 0, 0);
v->clear(v); v->clear(v);
v->swap(v); v->swap(v);
v->clear(v); v->clear(v);