From dc6639b30bf18cbf2fafffc054cd967b3d19d34b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 12 Apr 2023 22:27:26 -0700 Subject: [PATCH] Video: Add backend call proxying for cross-thread interaction --- include/mgba/feature/proxy-backend.h | 81 ++++++++ include/mgba/feature/video-backend.h | 3 + src/feature/CMakeLists.txt | 1 + src/feature/proxy-backend.c | 284 +++++++++++++++++++++++++++ src/feature/video-backend.c | 2 + 5 files changed, 371 insertions(+) create mode 100644 include/mgba/feature/proxy-backend.h create mode 100644 src/feature/proxy-backend.c diff --git a/include/mgba/feature/proxy-backend.h b/include/mgba/feature/proxy-backend.h new file mode 100644 index 000000000..8c0b9e427 --- /dev/null +++ b/include/mgba/feature/proxy-backend.h @@ -0,0 +1,81 @@ +/* Copyright (c) 2013-2023 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/. */ +#ifndef PROXY_BACKEND_H +#define PROXY_BACKEND_H + +#include + +CXX_GUARD_START + +#include +#include +#include + +enum mVideoBackendCommandType { + mVB_CMD_DUMMY = 0, + mVB_CMD_INIT, + mVB_CMD_DEINIT, + mVB_CMD_SET_LAYER_DIMENSIONS, + mVB_CMD_LAYER_DIMENSIONS, + mVB_CMD_SWAP, + mVB_CMD_CLEAR, + mVB_CMD_CONTEXT_RESIZED, + mVB_CMD_SET_IMAGE_SIZE, + mVB_CMD_IMAGE_SIZE, + mVB_CMD_SET_IMAGE, + mVB_CMD_DRAW_FRAME, +}; + +union mVideoBackendCommandData { + struct mRectangle dims; + struct { + int width; + int height; + } s; + struct { + unsigned width; + unsigned height; + } u; + const void* image; +}; + +struct mVideoBackendCommand { + enum mVideoBackendCommandType cmd; + + union { + WHandle handle; + enum VideoLayer layer; + }; + union mVideoBackendCommandData data; +}; + +struct mVideoProxyBackend { + struct VideoBackend d; + struct VideoBackend* backend; + + struct RingFIFO in; + struct RingFIFO out; + + Mutex inLock; + Mutex outLock; + Condition inWait; + Condition outWait; + + void (*wakeupCb)(struct mVideoProxyBackend*, void* context); + void* context; +}; + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend); +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy); + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out); +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block); + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType); + +CXX_GUARD_END + +#endif diff --git a/include/mgba/feature/video-backend.h b/include/mgba/feature/video-backend.h index 17584fea4..7baf82483 100644 --- a/include/mgba/feature/video-backend.h +++ b/include/mgba/feature/video-backend.h @@ -11,6 +11,7 @@ CXX_GUARD_START #include +#include #ifdef _WIN32 #include @@ -19,6 +20,8 @@ typedef HWND WHandle; typedef void* WHandle; #endif +mLOG_DECLARE_CATEGORY(VIDEO); + enum VideoLayer { VIDEO_LAYER_BACKGROUND = 0, VIDEO_LAYER_BEZEL, diff --git a/src/feature/CMakeLists.txt b/src/feature/CMakeLists.txt index f18028ff0..b692338f4 100644 --- a/src/feature/CMakeLists.txt +++ b/src/feature/CMakeLists.txt @@ -1,6 +1,7 @@ include(ExportDirectory) set(SOURCE_FILES commandline.c + proxy-backend.c thread-proxy.c updater.c video-backend.c diff --git a/src/feature/proxy-backend.c b/src/feature/proxy-backend.c new file mode 100644 index 000000000..fb6e555ed --- /dev/null +++ b/src/feature/proxy-backend.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2013-2023 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 + +static void _mVideoProxyBackendInit(struct VideoBackend* v, WHandle handle) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_INIT, + .handle = handle + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDeinit(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DEINIT, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_LAYER_DIMENSIONS, + .layer = layer, + .data = { + .dims = *dims + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendLayerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_LAYER_DIMENSIONS, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + memcpy(dims, &out.dims, sizeof(*dims)); +} + +static void _mVideoProxyBackendSwap(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SWAP, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendClear(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CLEAR, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendContextResized(struct VideoBackend* v, unsigned w, unsigned h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_CONTEXT_RESIZED, + .data = { + .u = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE_SIZE, + .layer = layer, + .data = { + .s = { + .width = w, + .height = h, + } + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendImageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_IMAGE_SIZE, + .layer = layer, + }; + union mVideoBackendCommandData out; + mVideoProxyBackendSubmit(proxy, &cmd, &out); + *w = out.s.width; + *h = out.s.height; +} + +static void _mVideoProxyBackendSetImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_SET_IMAGE, + .layer = layer, + .data = { + .image = frame + } + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static void _mVideoProxyBackendDrawFrame(struct VideoBackend* v) { + struct mVideoProxyBackend* proxy = (struct mVideoProxyBackend*) v; + struct mVideoBackendCommand cmd = { + .cmd = mVB_CMD_DRAW_FRAME, + }; + mVideoProxyBackendSubmit(proxy, &cmd, NULL); +} + +static bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block); +static void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out); + +void mVideoProxyBackendInit(struct mVideoProxyBackend* proxy, struct VideoBackend* backend) { + proxy->d.init = _mVideoProxyBackendInit; + proxy->d.deinit = _mVideoProxyBackendDeinit; + proxy->d.setLayerDimensions = _mVideoProxyBackendSetLayerDimensions; + proxy->d.layerDimensions = _mVideoProxyBackendLayerDimensions; + proxy->d.swap = _mVideoProxyBackendSwap; + proxy->d.clear = _mVideoProxyBackendClear; + proxy->d.contextResized = _mVideoProxyBackendContextResized; + proxy->d.setImageSize = _mVideoProxyBackendSetImageSize; + proxy->d.imageSize = _mVideoProxyBackendImageSize; + proxy->d.setImage = _mVideoProxyBackendSetImage; + proxy->d.drawFrame = _mVideoProxyBackendDrawFrame; + proxy->backend = backend; + + RingFIFOInit(&proxy->in, 0x400); + RingFIFOInit(&proxy->out, 0x400); + MutexInit(&proxy->inLock); + MutexInit(&proxy->outLock); + ConditionInit(&proxy->inWait); + ConditionInit(&proxy->outWait); + + proxy->wakeupCb = NULL; + proxy->context = NULL; +} + +void mVideoProxyBackendDeinit(struct mVideoProxyBackend* proxy) { + ConditionDeinit(&proxy->inWait); + ConditionDeinit(&proxy->outWait); + MutexDeinit(&proxy->inLock); + MutexDeinit(&proxy->outLock); + RingFIFODeinit(&proxy->in); + RingFIFODeinit(&proxy->out); +} + +void mVideoProxyBackendSubmit(struct mVideoProxyBackend* proxy, const struct mVideoBackendCommand* cmd, union mVideoBackendCommandData* out) { + MutexLock(&proxy->inLock); + while (!RingFIFOWrite(&proxy->in, cmd, sizeof(*cmd))) { + mLOG(VIDEO, DEBUG, "Can't write command. Proxy thread asleep?"); + ConditionWait(&proxy->inWait, &proxy->inLock); + } + MutexUnlock(&proxy->inLock); + if (proxy->wakeupCb) { + proxy->wakeupCb(proxy, proxy->context); + } + + if (!mVideoProxyBackendCommandIsBlocking(cmd->cmd)) { + return; + } + + MutexLock(&proxy->outLock); + while (!RingFIFORead(&proxy->out, out, sizeof(*out))) { + ConditionWait(&proxy->outWait, &proxy->outLock); + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendRun(struct mVideoProxyBackend* proxy, bool block) { + bool ok = false; + do { + struct mVideoBackendCommand cmd; + union mVideoBackendCommandData out; + if (mVideoProxyBackendReadIn(proxy, &cmd, block)) { + switch (cmd.cmd) { + case mVB_CMD_DUMMY: + break; + case mVB_CMD_INIT: + proxy->backend->init(proxy->backend, cmd.handle); + break; + case mVB_CMD_DEINIT: + proxy->backend->deinit(proxy->backend); + break; + case mVB_CMD_SET_LAYER_DIMENSIONS: + proxy->backend->setLayerDimensions(proxy->backend, cmd.layer, &cmd.data.dims); + break; + case mVB_CMD_LAYER_DIMENSIONS: + proxy->backend->layerDimensions(proxy->backend, cmd.layer, &out.dims); + break; + case mVB_CMD_SWAP: + proxy->backend->swap(proxy->backend); + break; + case mVB_CMD_CLEAR: + proxy->backend->clear(proxy->backend); + break; + case mVB_CMD_CONTEXT_RESIZED: + proxy->backend->contextResized(proxy->backend, cmd.data.u.width, cmd.data.u.height); + break; + case mVB_CMD_SET_IMAGE_SIZE: + proxy->backend->setImageSize(proxy->backend, cmd.layer, cmd.data.s.width, cmd.data.s.height); + break; + case mVB_CMD_IMAGE_SIZE: + proxy->backend->imageSize(proxy->backend, cmd.layer, &out.s.width, &out.s.height); + break; + case mVB_CMD_SET_IMAGE: + proxy->backend->setImage(proxy->backend, cmd.layer, cmd.data.image); + break; + case mVB_CMD_DRAW_FRAME: + proxy->backend->drawFrame(proxy->backend); + break; + } + if (mVideoProxyBackendCommandIsBlocking(cmd.cmd)) { + mVideoProxyBackendWriteOut(proxy, &out); + } + ok = true; + } + } while (block); + return ok; +} + +bool mVideoProxyBackendReadIn(struct mVideoProxyBackend* proxy, struct mVideoBackendCommand* cmd, bool block) { + bool gotCmd = false; + MutexLock(&proxy->inLock); + do { + gotCmd = RingFIFORead(&proxy->in, cmd, sizeof(*cmd)); + ConditionWake(&proxy->inWait); + // TODO: interlock? + if (block && !gotCmd) { + mLOG(VIDEO, DEBUG, "Can't read command. Runner thread asleep?"); + } + } while (block && !gotCmd); + MutexUnlock(&proxy->inLock); + return gotCmd; +} + +void mVideoProxyBackendWriteOut(struct mVideoProxyBackend* proxy, const union mVideoBackendCommandData* out) { + bool gotReply = false; + MutexLock(&proxy->outLock); + while (!gotReply) { + gotReply = RingFIFOWrite(&proxy->out, out, sizeof(*out)); + ConditionWake(&proxy->outWait); + // TOOD: interlock? + if (!gotReply) { + mLOG(VIDEO, DEBUG, "Can't write reply. Runner thread asleep?"); + } + } + MutexUnlock(&proxy->outLock); +} + +bool mVideoProxyBackendCommandIsBlocking(enum mVideoBackendCommandType cmd) { + switch (cmd) { + case mVB_CMD_DUMMY: + case mVB_CMD_CONTEXT_RESIZED: + case mVB_CMD_SET_LAYER_DIMENSIONS: + case mVB_CMD_CLEAR: + case mVB_CMD_SET_IMAGE_SIZE: + case mVB_CMD_DRAW_FRAME: + return false; + case mVB_CMD_INIT: + case mVB_CMD_DEINIT: + case mVB_CMD_LAYER_DIMENSIONS: + case mVB_CMD_SWAP: + case mVB_CMD_IMAGE_SIZE: + case mVB_CMD_SET_IMAGE: + return true; + } + + return true; +} diff --git a/src/feature/video-backend.c b/src/feature/video-backend.c index ffe916edc..ae2f0257d 100644 --- a/src/feature/video-backend.c +++ b/src/feature/video-backend.c @@ -5,6 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include +mLOG_DEFINE_CATEGORY(VIDEO, "Video backend", "video"); + void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) { memset(frame, 0, sizeof(*frame)); int i;