Video: Add backend call proxying for cross-thread interaction

This commit is contained in:
Vicki Pfau 2023-04-12 22:27:26 -07:00
parent 90420586e6
commit dc6639b30b
5 changed files with 371 additions and 0 deletions

View File

@ -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 <mgba-util/common.h>
CXX_GUARD_START
#include <mgba-util/ring-fifo.h>
#include <mgba-util/threading.h>
#include <mgba/feature/video-backend.h>
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

View File

@ -11,6 +11,7 @@
CXX_GUARD_START
#include <mgba-util/geometry.h>
#include <mgba/core/log.h>
#ifdef _WIN32
#include <windows.h>
@ -19,6 +20,8 @@ typedef HWND WHandle;
typedef void* WHandle;
#endif
mLOG_DECLARE_CATEGORY(VIDEO);
enum VideoLayer {
VIDEO_LAYER_BACKGROUND = 0,
VIDEO_LAYER_BEZEL,

View File

@ -1,6 +1,7 @@
include(ExportDirectory)
set(SOURCE_FILES
commandline.c
proxy-backend.c
thread-proxy.c
updater.c
video-backend.c

284
src/feature/proxy-backend.c Normal file
View File

@ -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 <mgba/feature/proxy-backend.h>
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;
}

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/feature/video-backend.h>
mLOG_DEFINE_CATEGORY(VIDEO, "Video backend", "video");
void VideoBackendGetFrame(const struct VideoBackend* v, struct mRectangle* frame) {
memset(frame, 0, sizeof(*frame));
int i;