mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'feature/canvas'
This commit is contained in:
commit
201f0df4c2
|
@ -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
|
|
@ -11,6 +11,7 @@
|
|||
CXX_GUARD_START
|
||||
|
||||
#include <mgba-util/geometry.h>
|
||||
#include <mgba/core/log.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
@ -19,14 +20,27 @@ typedef HWND WHandle;
|
|||
typedef void* WHandle;
|
||||
#endif
|
||||
|
||||
mLOG_DECLARE_CATEGORY(VIDEO);
|
||||
|
||||
enum VideoLayer {
|
||||
VIDEO_LAYER_BACKGROUND = 0,
|
||||
VIDEO_LAYER_BEZEL,
|
||||
VIDEO_LAYER_IMAGE,
|
||||
VIDEO_LAYER_OVERLAY,
|
||||
VIDEO_LAYER_OVERLAY0,
|
||||
VIDEO_LAYER_OVERLAY1,
|
||||
VIDEO_LAYER_OVERLAY2,
|
||||
VIDEO_LAYER_OVERLAY3,
|
||||
VIDEO_LAYER_OVERLAY4,
|
||||
VIDEO_LAYER_OVERLAY5,
|
||||
VIDEO_LAYER_OVERLAY6,
|
||||
VIDEO_LAYER_OVERLAY7,
|
||||
VIDEO_LAYER_OVERLAY8,
|
||||
VIDEO_LAYER_OVERLAY9,
|
||||
VIDEO_LAYER_MAX
|
||||
};
|
||||
|
||||
#define VIDEO_LAYER_OVERLAY_COUNT VIDEO_LAYER_MAX - VIDEO_LAYER_OVERLAY0
|
||||
|
||||
struct VideoBackend {
|
||||
void (*init)(struct VideoBackend*, WHandle handle);
|
||||
void (*deinit)(struct VideoBackend*);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#define M_SCRIPT_H
|
||||
|
||||
#include <mgba/script/base.h>
|
||||
#include <mgba/script/canvas.h>
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/input.h>
|
||||
#include <mgba/script/macros.h>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/* 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 M_SCRIPT_CANVAS_H
|
||||
#define M_SCRIPT_CANVAS_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/script/context.h>
|
||||
#include <mgba/script/macros.h>
|
||||
|
||||
struct VideoBackend;
|
||||
void mScriptContextAttachCanvas(struct mScriptContext* context);
|
||||
void mScriptCanvasUpdate(struct mScriptContext* context);
|
||||
void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend*);
|
||||
void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -335,6 +335,8 @@ bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator
|
|||
void mScriptFrameInit(struct mScriptFrame* frame);
|
||||
void mScriptFrameDeinit(struct mScriptFrame* frame);
|
||||
|
||||
struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args);
|
||||
|
||||
void mScriptClassInit(struct mScriptTypeClass* cls);
|
||||
void mScriptClassDeinit(struct mScriptTypeClass* cls);
|
||||
|
||||
|
@ -343,6 +345,7 @@ bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, s
|
|||
bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue*);
|
||||
bool mScriptObjectCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) ;
|
||||
void mScriptObjectFree(struct mScriptValue* obj);
|
||||
struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args);
|
||||
|
||||
bool mScriptPopS32(struct mScriptList* list, int32_t* out);
|
||||
bool mScriptPopU32(struct mScriptList* list, uint32_t* out);
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
math.randomseed(os.time())
|
||||
local state = {}
|
||||
state.logo = image.load(script.dir .. "/logo.png")
|
||||
state.overlay = canvas:newLayer(state.logo.width, state.logo.height)
|
||||
state.overlay.image:drawImageOpaque(state.logo, 0, 0)
|
||||
state.x = math.random() * (canvas:screenWidth() - state.logo.width)
|
||||
state.y = math.random() * (canvas:screenHeight() - state.logo.height)
|
||||
state.direction = math.floor(math.random() * 3)
|
||||
state.speed = 0.5
|
||||
|
||||
state.overlay:setPosition(math.floor(state.x), math.floor(state.y))
|
||||
state.overlay:update()
|
||||
|
||||
function state.update()
|
||||
if state.direction & 1 == 1 then
|
||||
state.x = state.x + 1
|
||||
if state.x > canvas:screenWidth() - state.logo.width then
|
||||
state.x = (canvas:screenWidth() - state.logo.width) * 2 - state.x
|
||||
state.direction = state.direction ~ 1
|
||||
end
|
||||
else
|
||||
state.x = state.x - 1
|
||||
if state.x < 0 then
|
||||
state.x = -state.x
|
||||
state.direction = state.direction ~ 1
|
||||
end
|
||||
end
|
||||
if state.direction & 2 == 2 then
|
||||
state.y = state.y + 1
|
||||
if state.y > canvas:screenHeight() - state.logo.height then
|
||||
state.y = (canvas:screenHeight() - state.logo.height) * 2 - state.y
|
||||
state.direction = state.direction ~ 2
|
||||
end
|
||||
else
|
||||
state.y = state.y - 1
|
||||
if state.y < 0 then
|
||||
state.y = -state.y
|
||||
state.direction = state.direction ~ 2
|
||||
end
|
||||
end
|
||||
state.overlay:setPosition(math.floor(state.x), math.floor(state.y))
|
||||
end
|
||||
|
||||
callbacks:add("frame", state.update)
|
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,6 +1,7 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
commandline.c
|
||||
proxy-backend.c
|
||||
thread-proxy.c
|
||||
updater.c
|
||||
video-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 <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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -145,13 +145,33 @@ static void _setFrame(struct mRectangle* dims, struct mRectangle* frame) {
|
|||
GLint viewport[4];
|
||||
glGetIntegerv(GL_VIEWPORT, viewport);
|
||||
glScissor(viewport[0] + (dims->x - frame->x) * viewport[2] / frame->width,
|
||||
viewport[1] + (dims->y - frame->y) * viewport[3] / frame->height,
|
||||
viewport[1] + (frame->height + frame->y - dims->height - dims->y) * viewport[3] / frame->height,
|
||||
dims->width * viewport[2] / frame->width,
|
||||
dims->height * viewport[3] / frame->height);
|
||||
glLoadIdentity();
|
||||
glTranslatef(dims->x, dims->y, 0);
|
||||
glScalef(toPow2(dims->width), toPow2(dims->height), 1);
|
||||
}
|
||||
|
||||
static void _drawLayers(struct mGLContext* context, int start, int end) {
|
||||
struct mRectangle frame;
|
||||
VideoBackendGetFrame(&context->d, &frame);
|
||||
|
||||
int layer;
|
||||
for (layer = start; layer < end; ++layer) {
|
||||
if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
_setFilter(&context->d);
|
||||
_setFrame(&context->layerDims[layer], &frame);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
void mGLContextDrawFrame(struct VideoBackend* v) {
|
||||
struct mGLContext* context = (struct mGLContext*) v;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
@ -166,36 +186,26 @@ void mGLContextDrawFrame(struct VideoBackend* v) {
|
|||
VideoBackendGetFrame(v, &frame);
|
||||
glOrtho(frame.x, frame.x + frame.width, frame.y + frame.height, frame.y, 0, 1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
int layer;
|
||||
for (layer = 0; layer < VIDEO_LAYER_IMAGE; ++layer) {
|
||||
if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[layer]);
|
||||
_setFilter(v);
|
||||
glPushMatrix();
|
||||
_setFrame(&context->layerDims[layer], &frame);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glPopMatrix();
|
||||
}
|
||||
_drawLayers(context, 0, VIDEO_LAYER_IMAGE);
|
||||
|
||||
_setFrame(&context->layerDims[VIDEO_LAYER_IMAGE], &frame);
|
||||
glDisable(GL_BLEND);
|
||||
if (v->interframeBlending) {
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
glBlendColor(1, 1, 1, 0.5);
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex ^ 1]);
|
||||
_setFilter(v);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
glBlendColor(1, 1, 1, 0.5);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[context->activeTex]);
|
||||
_setFilter(v);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
_drawLayers(context, VIDEO_LAYER_IMAGE + 1, VIDEO_LAYER_MAX);
|
||||
}
|
||||
|
||||
static void mGLContextSetImageSize(struct VideoBackend* v, enum VideoLayer layer, int width, int height) {
|
||||
|
|
|
@ -81,6 +81,15 @@ static const char* const _nullFragmentShader =
|
|||
" gl_FragColor = color;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _thruFragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 color = texture2D(tex, texCoord);\n"
|
||||
" gl_FragColor = color;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _interframeFragmentShader =
|
||||
"varying vec2 texCoord;\n"
|
||||
"uniform sampler2D tex;\n"
|
||||
|
@ -159,8 +168,9 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
|||
uniforms[3].max.fvec3[1] = 1.0f;
|
||||
uniforms[3].max.fvec3[2] = 1.0f;
|
||||
mGLES2ShaderInit(&context->initialShader, _vertexShader, _fragmentShader, -1, -1, false, uniforms, 4);
|
||||
mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, 0, 0);
|
||||
mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, 0, 0);
|
||||
mGLES2ShaderInit(&context->finalShader, 0, 0, 0, 0, false, NULL, 0);
|
||||
mGLES2ShaderInit(&context->interframeShader, 0, _interframeFragmentShader, -1, -1, false, NULL, 0);
|
||||
mGLES2ShaderInit(&context->overlayShader, _vertexShader, _thruFragmentShader, -1, -1, false, NULL, 0);
|
||||
|
||||
#ifdef BUILD_GLES3
|
||||
if (context->initialShader.vao != (GLuint) -1) {
|
||||
|
@ -170,10 +180,17 @@ static void mGLES2ContextInit(struct VideoBackend* v, WHandle handle) {
|
|||
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
|
||||
glBindVertexArray(context->interframeShader.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
|
||||
glBindVertexArray(context->overlayShader.vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, context->vbo);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
glDeleteFramebuffers(1, &context->overlayShader.fbo);
|
||||
glDeleteTextures(1, &context->overlayShader.tex);
|
||||
context->overlayShader.fbo = context->initialShader.fbo;
|
||||
context->overlayShader.tex = context->initialShader.tex;
|
||||
|
||||
glDeleteFramebuffers(1, &context->finalShader.fbo);
|
||||
glDeleteTextures(1, &context->finalShader.tex);
|
||||
context->finalShader.fbo = 0;
|
||||
|
@ -221,8 +238,8 @@ static void mGLES2ContextSetLayerDimensions(struct VideoBackend* v, enum VideoLa
|
|||
context->shaders[n].dirty = true;
|
||||
}
|
||||
}
|
||||
context->initialShader.dirty = true;
|
||||
context->interframeShader.dirty = true;
|
||||
glBindTexture(GL_TEXTURE_2D, context->initialShader.tex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame.width, frame.height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
context->width = frame.width;
|
||||
context->height = frame.height;
|
||||
}
|
||||
|
@ -245,6 +262,8 @@ static void mGLES2ContextDeinit(struct VideoBackend* v) {
|
|||
mGLES2ShaderDeinit(&context->initialShader);
|
||||
mGLES2ShaderDeinit(&context->finalShader);
|
||||
mGLES2ShaderDeinit(&context->interframeShader);
|
||||
context->overlayShader.fbo = 0;
|
||||
mGLES2ShaderDeinit(&context->overlayShader);
|
||||
free(context->initialShader.uniforms);
|
||||
}
|
||||
|
||||
|
@ -321,7 +340,7 @@ static void _drawShaderEx(struct mGLES2Context* context, struct mGLES2Shader* sh
|
|||
}
|
||||
|
||||
if (layer >= 0 && layer < VIDEO_LAYER_MAX) {
|
||||
glViewport(context->layerDims[layer].x - context->x, context->layerDims[layer].y - context->y, context->layerDims[layer].width, context->layerDims[layer].height);
|
||||
glViewport(context->layerDims[layer].x - context->x, context->height - context->layerDims[layer].y - context->layerDims[layer].height + context->y, context->layerDims[layer].width, context->layerDims[layer].height);
|
||||
} else {
|
||||
glViewport(padW, padH, drawW, drawH);
|
||||
}
|
||||
|
@ -423,12 +442,17 @@ void mGLES2ContextDrawFrame(struct VideoBackend* v) {
|
|||
context->finalShader.filter = v->filter;
|
||||
|
||||
int layer;
|
||||
for (layer = 0; layer <= VIDEO_LAYER_IMAGE; ++layer) {
|
||||
for (layer = 0; layer < VIDEO_LAYER_MAX; ++layer) {
|
||||
if (context->layerDims[layer].width < 1 || context->layerDims[layer].height < 1) {
|
||||
continue;
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, context->tex[layer]);
|
||||
_drawShaderEx(context, &context->initialShader, layer);
|
||||
if (layer != VIDEO_LAYER_IMAGE) {
|
||||
context->overlayShader.blend = layer > VIDEO_LAYER_BACKGROUND;
|
||||
_drawShaderEx(context, &context->overlayShader, layer);
|
||||
} else {
|
||||
_drawShaderEx(context, &context->initialShader, layer);
|
||||
}
|
||||
if (layer != VIDEO_LAYER_IMAGE) {
|
||||
continue;
|
||||
}
|
||||
|
@ -659,10 +683,14 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f
|
|||
}
|
||||
|
||||
void mGLES2ShaderDeinit(struct mGLES2Shader* shader) {
|
||||
glDeleteTextures(1, &shader->tex);
|
||||
if (shader->tex) {
|
||||
glDeleteTextures(1, &shader->tex);
|
||||
}
|
||||
glDeleteShader(shader->fragmentShader);
|
||||
glDeleteProgram(shader->program);
|
||||
glDeleteFramebuffers(1, &shader->fbo);
|
||||
if (shader->fbo) {
|
||||
glDeleteFramebuffers(1, &shader->fbo);
|
||||
}
|
||||
#ifdef BUILD_GLES3
|
||||
if (shader->vao != (GLuint) -1) {
|
||||
glDeleteVertexArrays(1, &shader->vao);
|
||||
|
|
|
@ -92,6 +92,7 @@ struct mGLES2Context {
|
|||
struct mGLES2Shader initialShader;
|
||||
struct mGLES2Shader finalShader;
|
||||
struct mGLES2Shader interframeShader;
|
||||
struct mGLES2Shader overlayShader;
|
||||
|
||||
struct mGLES2Shader* shaders;
|
||||
size_t nShaders;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "DisplayGL.h"
|
||||
#include "DisplayQt.h"
|
||||
#include "LogController.h"
|
||||
#include "VideoProxy.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -124,6 +125,13 @@ void QGBA::Display::configure(ConfigController* config) {
|
|||
#endif
|
||||
}
|
||||
|
||||
VideoBackend* QGBA::Display::videoBackend() {
|
||||
if (m_videoProxy) {
|
||||
return m_videoProxy->backend();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void QGBA::Display::resizeEvent(QResizeEvent*) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
m_messagePainter.resize(size(), devicePixelRatioF());
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "MessagePainter.h"
|
||||
|
||||
struct VDir;
|
||||
struct VideoBackend;
|
||||
struct VideoShader;
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -59,6 +60,7 @@ public:
|
|||
|
||||
virtual void setVideoProxy(std::shared_ptr<VideoProxy> proxy) { m_videoProxy = proxy; }
|
||||
std::shared_ptr<VideoProxy> videoProxy() { return m_videoProxy; }
|
||||
virtual VideoBackend* videoBackend();
|
||||
|
||||
signals:
|
||||
void drawingStarted();
|
||||
|
|
|
@ -944,6 +944,9 @@ void PainterGL::dequeueAll(bool keep) {
|
|||
|
||||
void PainterGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
||||
m_videoProxy = proxy;
|
||||
if (proxy) {
|
||||
proxy->setProxiedBackend(m_backend);
|
||||
}
|
||||
}
|
||||
|
||||
void PainterGL::interrupt() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* 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
|
||||
|
@ -19,13 +19,28 @@ using namespace QGBA;
|
|||
DisplayQt::DisplayQt(QWidget* parent)
|
||||
: Display(parent)
|
||||
{
|
||||
m_backend.init = &DisplayQt::init;
|
||||
m_backend.deinit = &DisplayQt::deinit;
|
||||
m_backend.setLayerDimensions = &DisplayQt::setLayerDimensions;
|
||||
m_backend.layerDimensions = &DisplayQt::layerDimensions;
|
||||
m_backend.swap = &DisplayQt::swap;
|
||||
m_backend.clear = &DisplayQt::clear;
|
||||
m_backend.contextResized = &DisplayQt::contextResized;
|
||||
m_backend.setImageSize = &DisplayQt::setImageSize;
|
||||
m_backend.imageSize = &DisplayQt::imageSize;
|
||||
m_backend.setImage = &DisplayQt::setImage;
|
||||
m_backend.drawFrame = &DisplayQt::drawFrame;
|
||||
m_backend.filter = isFiltered();
|
||||
m_backend.lockAspectRatio = isAspectRatioLocked();
|
||||
m_backend.lockIntegerScaling = isIntegerScalingLocked();
|
||||
m_backend.interframeBlending = hasInterframeBlending();
|
||||
m_backend.user = this;
|
||||
}
|
||||
|
||||
void DisplayQt::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||
QSize size = controller->screenDimensions();
|
||||
m_width = size.width();
|
||||
m_height = size.height();
|
||||
m_backing = QImage();
|
||||
m_oldBacking = QImage();
|
||||
m_isDrawing = true;
|
||||
m_context = controller;
|
||||
|
@ -39,44 +54,51 @@ void DisplayQt::stopDrawing() {
|
|||
|
||||
void DisplayQt::lockAspectRatio(bool lock) {
|
||||
Display::lockAspectRatio(lock);
|
||||
m_backend.lockAspectRatio = lock;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::lockIntegerScaling(bool lock) {
|
||||
Display::lockIntegerScaling(lock);
|
||||
m_backend.lockIntegerScaling = lock;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::interframeBlending(bool lock) {
|
||||
Display::interframeBlending(lock);
|
||||
m_backend.interframeBlending = lock;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::filter(bool filter) {
|
||||
Display::filter(filter);
|
||||
m_backend.filter = filter;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayQt::framePosted() {
|
||||
update();
|
||||
const color_t* buffer = m_context->drawContext();
|
||||
if (const_cast<const QImage&>(m_backing).bits() == reinterpret_cast<const uchar*>(buffer)) {
|
||||
if (const_cast<const QImage&>(m_layers[VIDEO_LAYER_IMAGE]).bits() == reinterpret_cast<const uchar*>(buffer)) {
|
||||
return;
|
||||
}
|
||||
m_oldBacking = m_backing;
|
||||
m_oldBacking = m_layers[VIDEO_LAYER_IMAGE];
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB16);
|
||||
m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB16);
|
||||
#else
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB555);
|
||||
m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_RGB555);
|
||||
#endif
|
||||
#else
|
||||
m_backing = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_ARGB32);
|
||||
m_backing = m_backing.convertToFormat(QImage::Format_RGB32);
|
||||
m_layers[VIDEO_LAYER_IMAGE] = QImage(reinterpret_cast<const uchar*>(buffer), m_width, m_height, QImage::Format_ARGB32);
|
||||
m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].convertToFormat(QImage::Format_RGB32);
|
||||
#endif
|
||||
#ifndef COLOR_5_6_5
|
||||
m_backing = m_backing.rgbSwapped();
|
||||
m_layers[VIDEO_LAYER_IMAGE] = m_layers[VIDEO_LAYER_IMAGE].rgbSwapped();
|
||||
#endif
|
||||
m_layerDims[VIDEO_LAYER_IMAGE].setWidth(m_width);
|
||||
m_layerDims[VIDEO_LAYER_IMAGE].setHeight(m_height);
|
||||
redoBounds();
|
||||
}
|
||||
|
||||
void DisplayQt::resizeContext() {
|
||||
|
@ -88,12 +110,13 @@ void DisplayQt::resizeContext() {
|
|||
m_width = size.width();
|
||||
m_height = size.height();
|
||||
m_oldBacking = QImage();
|
||||
m_backing = QImage();
|
||||
m_layers[VIDEO_LAYER_IMAGE] = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayQt::setBackgroundImage(const QImage& image) {
|
||||
m_background = image;
|
||||
m_layers[VIDEO_LAYER_BACKGROUND] = image;
|
||||
redoBounds();
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -104,91 +127,125 @@ void DisplayQt::paintEvent(QPaintEvent*) {
|
|||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
}
|
||||
|
||||
QRect bgRect(0, 0, m_background.width(), m_background.height());
|
||||
QRect imRect(0, 0, m_width, m_height);
|
||||
QSize outerFrame = contentSize();
|
||||
struct mRectangle frame;
|
||||
VideoBackendGetFrame(&m_backend, &frame);
|
||||
QPoint origin(-frame.x, -frame.y);
|
||||
QRect full(clampSize(contentSize(), size(), isAspectRatioLocked(), isIntegerScalingLocked()));
|
||||
painter.save();
|
||||
painter.translate(full.topLeft());
|
||||
painter.scale(full.width() / static_cast<qreal>(frame.width), full.height() / static_cast<qreal>(frame.height));
|
||||
|
||||
if (bgRect.width() > imRect.width()) {
|
||||
imRect.moveLeft(bgRect.width() - imRect.width());
|
||||
} else {
|
||||
bgRect.moveLeft(imRect.width() - bgRect.width());
|
||||
}
|
||||
|
||||
if (bgRect.height() > imRect.height()) {
|
||||
imRect.moveTop(bgRect.height() - imRect.height());
|
||||
} else {
|
||||
bgRect.moveTop(imRect.height() - bgRect.height());
|
||||
}
|
||||
|
||||
QRect full(clampSize(outerFrame, size(), isAspectRatioLocked(), isIntegerScalingLocked()));
|
||||
|
||||
if (m_background.isNull()) {
|
||||
imRect = full;
|
||||
} else {
|
||||
if (imRect.x()) {
|
||||
imRect.moveLeft(imRect.x() * full.width() / bgRect.width() / 2);
|
||||
imRect.setWidth(imRect.width() * full.width() / bgRect.width());
|
||||
bgRect.setWidth(full.width());
|
||||
} else {
|
||||
bgRect.moveLeft(bgRect.x() * full.width() / imRect.width() / 2);
|
||||
bgRect.setWidth(bgRect.width() * full.width() / imRect.width());
|
||||
imRect.setWidth(full.width());
|
||||
}
|
||||
if (imRect.y()) {
|
||||
imRect.moveTop(imRect.y() * full.height() / bgRect.height() / 2);
|
||||
imRect.setHeight(imRect.height() * full.height() / bgRect.height());
|
||||
bgRect.setHeight(full.height());
|
||||
} else {
|
||||
bgRect.moveTop(bgRect.y() * full.height() / imRect.height() / 2);
|
||||
bgRect.setHeight(bgRect.height() * full.height() / imRect.height());
|
||||
imRect.setHeight(full.height());
|
||||
}
|
||||
|
||||
if (bgRect.right() > imRect.right()) {
|
||||
if (bgRect.right() < full.right()) {
|
||||
imRect.translate((full.right() - bgRect.right()), 0);
|
||||
bgRect.translate((full.right() - bgRect.right()), 0);
|
||||
}
|
||||
} else {
|
||||
if (imRect.right() < full.right()) {
|
||||
bgRect.translate((full.right() - imRect.right()), 0);
|
||||
imRect.translate((full.right() - imRect.right()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (bgRect.bottom() > imRect.bottom()) {
|
||||
if (bgRect.bottom() < full.bottom()) {
|
||||
imRect.translate(0, (full.bottom() - bgRect.bottom()));
|
||||
bgRect.translate(0, (full.bottom() - bgRect.bottom()));
|
||||
}
|
||||
} else {
|
||||
if (imRect.bottom() < full.bottom()) {
|
||||
bgRect.translate(0, (full.bottom() - imRect.bottom()));
|
||||
imRect.translate(0, (full.bottom() - imRect.bottom()));
|
||||
}
|
||||
}
|
||||
painter.drawImage(bgRect, m_background);
|
||||
if (!m_layers[VIDEO_LAYER_BACKGROUND].isNull()) {
|
||||
painter.drawImage(m_layerDims[VIDEO_LAYER_BACKGROUND].translated(origin), m_layers[VIDEO_LAYER_BACKGROUND]);
|
||||
}
|
||||
|
||||
if (hasInterframeBlending()) {
|
||||
painter.drawImage(imRect, m_oldBacking, QRect(0, 0, m_width, m_height));
|
||||
painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_oldBacking, QRect(0, 0, m_width, m_height));
|
||||
painter.setOpacity(0.5);
|
||||
}
|
||||
painter.drawImage(imRect, m_backing, QRect(0, 0, m_width, m_height));
|
||||
painter.drawImage(m_layerDims[VIDEO_LAYER_IMAGE].translated(origin), m_layers[VIDEO_LAYER_IMAGE], QRect(0, 0, m_width, m_height));
|
||||
|
||||
for (int i = VIDEO_LAYER_IMAGE + 1; i < VIDEO_LAYER_MAX; ++i) {
|
||||
if (m_layers[i].isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
painter.drawImage(m_layerDims[i].translated(origin), m_layers[i]);
|
||||
}
|
||||
|
||||
painter.restore();
|
||||
painter.setOpacity(1);
|
||||
if (isShowOSD() || isShowFrameCounter()) {
|
||||
messagePainter()->paint(&painter);
|
||||
}
|
||||
}
|
||||
|
||||
QSize DisplayQt::contentSize() const {
|
||||
QSize outerFrame(m_width, m_height);
|
||||
void DisplayQt::redoBounds() {
|
||||
const static std::initializer_list<VideoLayer> centeredLayers{VIDEO_LAYER_BACKGROUND};
|
||||
mRectangle frame = {0};
|
||||
frame.width = m_width;
|
||||
frame.height = m_height;
|
||||
|
||||
if (m_background.width() > outerFrame.width()) {
|
||||
outerFrame.setWidth(m_background.width());
|
||||
for (VideoLayer l : centeredLayers) {
|
||||
mRectangle dims{};
|
||||
dims.width = m_layers[l].width();
|
||||
dims.height = m_layers[l].height();
|
||||
mRectangleCenter(&frame, &dims);
|
||||
m_layerDims[l].setX(dims.x);
|
||||
m_layerDims[l].setY(dims.y);
|
||||
m_layerDims[l].setWidth(dims.width);
|
||||
m_layerDims[l].setHeight(dims.height);
|
||||
}
|
||||
if (m_background.height() > outerFrame.height()) {
|
||||
outerFrame.setHeight(m_background.height());
|
||||
}
|
||||
return outerFrame;
|
||||
}
|
||||
|
||||
QSize DisplayQt::contentSize() const {
|
||||
unsigned w, h;
|
||||
VideoBackendGetFrameSize(&m_backend, &w, &h);
|
||||
return {w, h};
|
||||
}
|
||||
|
||||
void DisplayQt::init(struct VideoBackend*, WHandle) {
|
||||
}
|
||||
|
||||
void DisplayQt::deinit(struct VideoBackend*) {
|
||||
}
|
||||
|
||||
void DisplayQt::setLayerDimensions(struct VideoBackend* v, enum VideoLayer layer, const struct mRectangle* dims) {
|
||||
DisplayQt* self = static_cast<DisplayQt*>(v->user);
|
||||
if (layer > self->m_layerDims.size()) {
|
||||
return;
|
||||
}
|
||||
self->m_layerDims[layer] = QRect(dims->x, dims->y, dims->width, dims->height);
|
||||
}
|
||||
|
||||
void DisplayQt::layerDimensions(const struct VideoBackend* v, enum VideoLayer layer, struct mRectangle* dims) {
|
||||
DisplayQt* self = static_cast<DisplayQt*>(v->user);
|
||||
if (layer > self->m_layerDims.size()) {
|
||||
return;
|
||||
}
|
||||
QRect rect = self->m_layerDims[layer];
|
||||
dims->x = rect.x();
|
||||
dims->y = rect.y();
|
||||
dims->width = rect.width();
|
||||
dims->height = rect.height();
|
||||
}
|
||||
|
||||
void DisplayQt::swap(struct VideoBackend*) {
|
||||
}
|
||||
|
||||
void DisplayQt::clear(struct VideoBackend*) {
|
||||
}
|
||||
|
||||
void DisplayQt::contextResized(struct VideoBackend*, unsigned, unsigned) {
|
||||
}
|
||||
|
||||
void DisplayQt::setImageSize(struct VideoBackend* v, enum VideoLayer layer, int w, int h) {
|
||||
DisplayQt* self = static_cast<DisplayQt*>(v->user);
|
||||
if (layer > self->m_layers.size()) {
|
||||
return;
|
||||
}
|
||||
self->m_layers[layer] = QImage(w, h, QImage::Format_ARGB32);
|
||||
}
|
||||
|
||||
void DisplayQt::imageSize(struct VideoBackend* v, enum VideoLayer layer, int* w, int* h) {
|
||||
DisplayQt* self = static_cast<DisplayQt*>(v->user);
|
||||
if (layer > self->m_layers.size()) {
|
||||
return;
|
||||
}
|
||||
*w = self->m_layers[layer].width();
|
||||
*h = self->m_layers[layer].height();
|
||||
}
|
||||
|
||||
void DisplayQt::setImage(struct VideoBackend* v, enum VideoLayer layer, const void* frame) {
|
||||
DisplayQt* self = static_cast<DisplayQt*>(v->user);
|
||||
if (layer > self->m_layers.size()) {
|
||||
return;
|
||||
}
|
||||
QImage image = self->m_layers[layer];
|
||||
image = QImage(static_cast<const uchar*>(frame), image.width(), image.height(), QImage::Format_ARGB32).rgbSwapped();
|
||||
self->m_layers[layer] = image;
|
||||
}
|
||||
|
||||
void DisplayQt::drawFrame(struct VideoBackend* v) {
|
||||
QMetaObject::invokeMethod(static_cast<DisplayQt*>(v->user), "update");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* 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
|
||||
|
@ -10,6 +10,9 @@
|
|||
#include <QImage>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mgba/feature/video-backend.h>
|
||||
#include <array>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class DisplayQt : public Display {
|
||||
|
@ -23,6 +26,7 @@ public:
|
|||
bool supportsShaders() const override { return false; }
|
||||
VideoShader* shaders() override { return nullptr; }
|
||||
QSize contentSize() const override;
|
||||
VideoBackend* videoBackend() override { return &m_backend; }
|
||||
|
||||
public slots:
|
||||
void stopDrawing() override;
|
||||
|
@ -44,12 +48,27 @@ protected:
|
|||
virtual void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
void redoBounds();
|
||||
|
||||
static void init(struct VideoBackend*, WHandle);
|
||||
static void deinit(struct VideoBackend*);
|
||||
static void setLayerDimensions(struct VideoBackend*, enum VideoLayer, const struct mRectangle*);
|
||||
static void layerDimensions(const struct VideoBackend*, enum VideoLayer, struct mRectangle*);
|
||||
static void swap(struct VideoBackend*);
|
||||
static void clear(struct VideoBackend*);
|
||||
static void contextResized(struct VideoBackend*, unsigned w, unsigned 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 setImage(struct VideoBackend*, enum VideoLayer, const void* frame);
|
||||
static void drawFrame(struct VideoBackend*);
|
||||
|
||||
VideoBackend m_backend{};
|
||||
std::array<QRect, VIDEO_LAYER_MAX> m_layerDims;
|
||||
std::array<QImage, VIDEO_LAYER_MAX> m_layers;
|
||||
bool m_isDrawing = false;
|
||||
int m_width = -1;
|
||||
int m_height = -1;
|
||||
QImage m_backing{nullptr};
|
||||
QImage m_oldBacking{nullptr};
|
||||
QImage m_background;
|
||||
std::shared_ptr<CoreController> m_context = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -24,12 +24,26 @@ VideoProxy::VideoProxy() {
|
|||
m_logger.unlock = &cbind<&VideoProxy::unlock>;
|
||||
m_logger.wait = &cbind<&VideoProxy::wait>;
|
||||
m_logger.wake = &callback<void, int>::func<&VideoProxy::wake>;
|
||||
RingFIFOInit(&m_dirtyQueue, 0x80000);
|
||||
|
||||
m_logger.writeData = &callback<bool, const void*, size_t>::func<&VideoProxy::writeData>;
|
||||
m_logger.readData = &callback<bool, void*, size_t, bool>::func<&VideoProxy::readData>;
|
||||
m_logger.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>;
|
||||
|
||||
mVideoProxyBackendInit(&m_backend, nullptr);
|
||||
m_backend.context = this;
|
||||
m_backend.wakeupCb = [](struct mVideoProxyBackend*, void* context) {
|
||||
VideoProxy* self = static_cast<VideoProxy*>(context);
|
||||
QMetaObject::invokeMethod(self, "commandAvailable");
|
||||
};
|
||||
|
||||
connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData);
|
||||
connect(this, &VideoProxy::commandAvailable, this, &VideoProxy::processCommands);
|
||||
}
|
||||
|
||||
VideoProxy::~VideoProxy() {
|
||||
mVideoProxyBackendDeinit(&m_backend);
|
||||
RingFIFODeinit(&m_dirtyQueue);
|
||||
}
|
||||
|
||||
void VideoProxy::attach(CoreController* controller) {
|
||||
|
@ -44,13 +58,21 @@ void VideoProxy::detach(CoreController* controller) {
|
|||
}
|
||||
}
|
||||
|
||||
void VideoProxy::setProxiedBackend(VideoBackend* backend) {
|
||||
// TODO: This needs some safety around it
|
||||
m_backend.backend = backend;
|
||||
}
|
||||
|
||||
void VideoProxy::processData() {
|
||||
mVideoLoggerRendererRun(&m_logger, false);
|
||||
m_fromThreadCond.wakeAll();
|
||||
}
|
||||
|
||||
void VideoProxy::processCommands() {
|
||||
mVideoProxyBackendRun(&m_backend, false);
|
||||
}
|
||||
|
||||
void VideoProxy::init() {
|
||||
RingFIFOInit(&m_dirtyQueue, 0x80000);
|
||||
}
|
||||
|
||||
void VideoProxy::reset() {
|
||||
|
@ -61,7 +83,6 @@ void VideoProxy::reset() {
|
|||
}
|
||||
|
||||
void VideoProxy::deinit() {
|
||||
RingFIFODeinit(&m_dirtyQueue);
|
||||
}
|
||||
|
||||
bool VideoProxy::writeData(const void* data, size_t length) {
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QReadWriteLock>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include <mgba/feature/video-logger.h>
|
||||
#include <mgba/feature/proxy-backend.h>
|
||||
#include <mgba-util/ring-fifo.h>
|
||||
|
||||
namespace QGBA {
|
||||
|
@ -21,16 +24,22 @@ Q_OBJECT
|
|||
|
||||
public:
|
||||
VideoProxy();
|
||||
~VideoProxy();
|
||||
|
||||
void attach(CoreController*);
|
||||
void detach(CoreController*);
|
||||
void setBlocking(bool block) { m_logger.waitOnFlush = block; }
|
||||
|
||||
VideoBackend* backend() { return &m_backend.d; }
|
||||
void setProxiedBackend(VideoBackend*);
|
||||
|
||||
signals:
|
||||
void dataAvailable();
|
||||
void commandAvailable();
|
||||
|
||||
public slots:
|
||||
void processData();
|
||||
void processCommands();
|
||||
void reset();
|
||||
void handleEvent(int);
|
||||
|
||||
|
@ -62,10 +71,19 @@ private:
|
|||
VideoProxy* p;
|
||||
} m_logger;
|
||||
|
||||
struct mVideoProxyBackend m_backend;
|
||||
|
||||
RingFIFO m_dirtyQueue;
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_toThreadCond;
|
||||
QWaitCondition m_fromThreadCond;
|
||||
|
||||
QReadWriteLock m_backendInLock;
|
||||
QReadWriteLock m_backendOutLock;
|
||||
QQueue<QByteArray> m_backendIn;
|
||||
QQueue<QByteArray> m_backendOut;
|
||||
QWaitCondition m_toBackendThreadCond;
|
||||
QWaitCondition m_fromBackendThreadCond;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -633,6 +633,8 @@ void Window::scriptingOpen() {
|
|||
m_scripting->setController(m_controller);
|
||||
m_display->installEventFilter(m_scripting.get());
|
||||
}
|
||||
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
}
|
||||
ScriptingView* view = new ScriptingView(m_scripting.get(), m_config);
|
||||
openView(view);
|
||||
|
@ -965,6 +967,12 @@ void Window::gameStopped() {
|
|||
updateTitle();
|
||||
|
||||
if (m_pendingClose) {
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||
if (m_scripting && proxy) {
|
||||
m_scripting->setVideoBackend(nullptr);
|
||||
}
|
||||
#endif
|
||||
m_display.reset();
|
||||
close();
|
||||
}
|
||||
|
@ -1015,6 +1023,15 @@ void Window::reloadDisplayDriver() {
|
|||
m_display->stopDrawing();
|
||||
detachWidget();
|
||||
}
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setVideoBackend(nullptr);
|
||||
}
|
||||
#endif
|
||||
std::shared_ptr<VideoProxy> proxy;
|
||||
if (m_display) {
|
||||
proxy = m_display->videoProxy();
|
||||
}
|
||||
m_display = std::unique_ptr<QGBA::Display>(Display::create(this));
|
||||
if (!m_display) {
|
||||
LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. "
|
||||
|
@ -1055,6 +1072,16 @@ void Window::reloadDisplayDriver() {
|
|||
#endif
|
||||
|
||||
m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")});
|
||||
|
||||
if (!proxy) {
|
||||
proxy = std::make_shared<VideoProxy>();
|
||||
}
|
||||
m_display->setVideoProxy(proxy);
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Window::reloadAudioDriver() {
|
||||
|
@ -1079,12 +1106,7 @@ void Window::changeRenderer() {
|
|||
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) {
|
||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||
if (!proxy) {
|
||||
proxy = std::make_shared<VideoProxy>();
|
||||
}
|
||||
m_display->setVideoProxy(proxy);
|
||||
proxy->attach(m_controller.get());
|
||||
m_display->videoProxy()->attach(m_controller.get());
|
||||
|
||||
int fb = m_display->framebufferHandle();
|
||||
if (fb >= 0) {
|
||||
|
@ -1092,11 +1114,7 @@ void Window::changeRenderer() {
|
|||
m_config->updateOption("videoScale");
|
||||
}
|
||||
} else {
|
||||
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
|
||||
if (proxy) {
|
||||
proxy->detach(m_controller.get());
|
||||
m_display->setVideoProxy({});
|
||||
}
|
||||
m_display->videoProxy()->detach(m_controller.get());
|
||||
m_controller->setFramebufferHandle(-1);
|
||||
}
|
||||
}
|
||||
|
@ -1889,6 +1907,11 @@ void Window::setupOptions() {
|
|||
videoScale->connect([this](const QVariant& value) {
|
||||
if (m_display) {
|
||||
m_display->setVideoScale(value.toInt());
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_controller && m_scripting) {
|
||||
m_scripting->updateVideoScale();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
@ -2132,6 +2155,8 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
|||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setController(m_controller);
|
||||
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -70,12 +70,16 @@ void ScriptingController::setController(std::shared_ptr<CoreController> controll
|
|||
return;
|
||||
}
|
||||
clearController();
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
m_controller = controller;
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
m_controller->thread()->scriptContext = &m_scriptContext;
|
||||
if (m_controller->hasStarted()) {
|
||||
mScriptContextAttachCore(&m_scriptContext, m_controller->thread()->core);
|
||||
}
|
||||
updateVideoScale();
|
||||
connect(m_controller.get(), &CoreController::stopping, this, &ScriptingController::clearController);
|
||||
}
|
||||
|
||||
|
@ -87,6 +91,10 @@ void ScriptingController::setInputController(InputController* input) {
|
|||
connect(m_inputController, &InputController::updated, this, &ScriptingController::updateGamepad);
|
||||
}
|
||||
|
||||
void ScriptingController::setVideoBackend(VideoBackend* backend) {
|
||||
mScriptCanvasUpdateBackend(&m_scriptContext, backend);
|
||||
}
|
||||
|
||||
bool ScriptingController::loadFile(const QString& path) {
|
||||
VFileDevice vf(path, QIODevice::ReadOnly);
|
||||
if (!vf.isOpen()) {
|
||||
|
@ -131,6 +139,13 @@ void ScriptingController::clearController() {
|
|||
m_controller.reset();
|
||||
}
|
||||
|
||||
void ScriptingController::updateVideoScale() {
|
||||
if (!m_controller) {
|
||||
return;
|
||||
}
|
||||
mScriptCanvasSetInternalScale(&m_scriptContext, m_controller->videoScale());
|
||||
}
|
||||
|
||||
void ScriptingController::reset() {
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
m_bufferModel->reset();
|
||||
|
@ -304,11 +319,13 @@ void ScriptingController::detachGamepad() {
|
|||
void ScriptingController::init() {
|
||||
mScriptContextInit(&m_scriptContext);
|
||||
mScriptContextAttachStdlib(&m_scriptContext);
|
||||
mScriptContextAttachCanvas(&m_scriptContext);
|
||||
mScriptContextAttachImage(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
#ifdef USE_JSON_C
|
||||
mScriptContextAttachStorage(&m_scriptContext);
|
||||
#endif
|
||||
mScriptContextAttachSocket(&m_scriptContext);
|
||||
mScriptContextAttachInput(&m_scriptContext);
|
||||
mScriptContextRegisterEngines(&m_scriptContext);
|
||||
|
||||
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
class QKeyEvent;
|
||||
class QTextDocument;
|
||||
|
||||
struct VideoBackend;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class CoreController;
|
||||
|
@ -36,6 +38,7 @@ public:
|
|||
|
||||
void setController(std::shared_ptr<CoreController> controller);
|
||||
void setInputController(InputController* controller);
|
||||
void setVideoBackend(VideoBackend* backend);
|
||||
|
||||
bool loadFile(const QString& path);
|
||||
bool load(VFileDevice& vf, const QString& name);
|
||||
|
@ -53,6 +56,7 @@ signals:
|
|||
|
||||
public slots:
|
||||
void clearController();
|
||||
void updateVideoScale();
|
||||
void reset();
|
||||
void runCode(const QString& code);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
include(ExportDirectory)
|
||||
set(SOURCE_FILES
|
||||
canvas.c
|
||||
context.c
|
||||
input.c
|
||||
image.c
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
/* 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/script/canvas.h>
|
||||
|
||||
#include <mgba/feature/video-backend.h>
|
||||
#include <mgba/script/base.h>
|
||||
#include <mgba-util/image.h>
|
||||
|
||||
struct mScriptCanvasContext;
|
||||
struct mScriptCanvasLayer {
|
||||
struct VideoBackend* backend;
|
||||
enum VideoLayer layer;
|
||||
struct mImage* image;
|
||||
int x;
|
||||
int y;
|
||||
unsigned scale;
|
||||
bool dirty;
|
||||
bool sizeDirty;
|
||||
bool dimsDirty;
|
||||
bool contentsDirty;
|
||||
};
|
||||
|
||||
struct mScriptCanvasContext {
|
||||
struct mScriptCanvasLayer overlays[VIDEO_LAYER_OVERLAY_COUNT];
|
||||
struct VideoBackend* backend;
|
||||
struct mScriptContext* context;
|
||||
uint32_t frameCbid;
|
||||
unsigned scale;
|
||||
};
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCanvasContext);
|
||||
mSCRIPT_DECLARE_STRUCT(mScriptCanvasLayer);
|
||||
|
||||
static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer);
|
||||
static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer);
|
||||
|
||||
static int _getNextAvailableOverlay(struct mScriptCanvasContext* context) {
|
||||
if (!context->backend) {
|
||||
return -1;
|
||||
}
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
int w = -1, h = -1;
|
||||
context->backend->imageSize(context->backend, VIDEO_LAYER_OVERLAY0 + i, &w, &h);
|
||||
if (w <= 0 && h <= 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void mScriptCanvasContextDeinit(struct mScriptCanvasContext* context) {
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
if (context->overlays[i].image) {
|
||||
mScriptCanvasLayerDestroy(&context->overlays[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mScriptCanvasUpdateBackend(struct mScriptContext* context, struct VideoBackend* backend) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptCanvasContext* canvas = value->value.opaque;
|
||||
canvas->backend = backend;
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
struct mScriptCanvasLayer* layer = &canvas->overlays[i];
|
||||
layer->backend = backend;
|
||||
layer->dirty = true;
|
||||
layer->dimsDirty = true;
|
||||
layer->sizeDirty = true;
|
||||
layer->contentsDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void mScriptCanvasSetInternalScale(struct mScriptContext* context, unsigned scale) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptCanvasContext* canvas = value->value.opaque;
|
||||
if (scale < 1) {
|
||||
scale = 1;
|
||||
}
|
||||
canvas->scale = scale;
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
canvas->overlays[i].scale = scale;
|
||||
}
|
||||
}
|
||||
|
||||
static void _mScriptCanvasUpdate(struct mScriptCanvasContext* canvas) {
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
mScriptCanvasLayerUpdate(&canvas->overlays[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned _mScriptCanvasWidth(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
unsigned w, h;
|
||||
VideoBackendGetFrameSize(canvas->backend, &w, &h);
|
||||
return w / canvas->scale;
|
||||
}
|
||||
|
||||
static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
unsigned w, h;
|
||||
VideoBackendGetFrameSize(canvas->backend, &w, &h);
|
||||
return h / canvas->scale;
|
||||
}
|
||||
|
||||
static int _mScriptCanvasScreenWidth(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
struct mRectangle dims;
|
||||
canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
return dims.width / canvas->scale;
|
||||
}
|
||||
|
||||
static int _mScriptCanvasScreenHeight(struct mScriptCanvasContext* canvas) {
|
||||
if (!canvas->backend) {
|
||||
return 0;
|
||||
}
|
||||
struct mRectangle dims;
|
||||
canvas->backend->layerDimensions(canvas->backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
return dims.height / canvas->scale;
|
||||
}
|
||||
|
||||
void mScriptCanvasUpdate(struct mScriptContext* context) {
|
||||
struct mScriptValue* value = mScriptContextGetGlobal(context, "canvas");
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
struct mScriptCanvasContext* canvas = value->value.opaque;
|
||||
_mScriptCanvasUpdate(canvas);
|
||||
}
|
||||
|
||||
static struct mScriptValue* mScriptCanvasLayerCreate(struct mScriptCanvasContext* context, int w, int h) {
|
||||
if (w <= 0 || h <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
int next = _getNextAvailableOverlay(context);
|
||||
if (next < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mScriptCanvasLayer* layer = &context->overlays[next];
|
||||
if (layer->image) {
|
||||
// This shouldn't exist yet
|
||||
abort();
|
||||
}
|
||||
|
||||
layer->image = mImageCreate(w, h, mCOLOR_ABGR8);
|
||||
layer->dirty = true;
|
||||
layer->dimsDirty = true;
|
||||
layer->sizeDirty = true;
|
||||
layer->contentsDirty = true;
|
||||
layer->scale = context->scale;
|
||||
mScriptCanvasLayerUpdate(layer);
|
||||
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasLayer));
|
||||
value->value.opaque = layer;
|
||||
return value;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerDestroy(struct mScriptCanvasLayer* layer) {
|
||||
struct mRectangle frame = {0};
|
||||
if (layer->backend) {
|
||||
layer->backend->setLayerDimensions(layer->backend, layer->layer, &frame);
|
||||
layer->backend->setImageSize(layer->backend, layer->layer, 0, 0);
|
||||
}
|
||||
mImageDestroy(layer->image);
|
||||
layer->image = NULL;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerUpdate(struct mScriptCanvasLayer* layer) {
|
||||
if (!layer->dirty || !layer->image || !layer->backend) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct VideoBackend* backend = layer->backend;
|
||||
if (layer->sizeDirty) {
|
||||
backend->setImageSize(backend, layer->layer, layer->image->width, layer->image->height);
|
||||
layer->sizeDirty = false;
|
||||
// Resizing the image invalidates the contents in many backends
|
||||
layer->contentsDirty = true;
|
||||
}
|
||||
if (layer->dimsDirty) {
|
||||
struct mRectangle frame = {
|
||||
.x = layer->x * layer->scale,
|
||||
.y = layer->y * layer->scale,
|
||||
.width = layer->image->width * layer->scale,
|
||||
.height = layer->image->height * layer->scale,
|
||||
};
|
||||
backend->setLayerDimensions(backend, layer->layer, &frame);
|
||||
layer->dimsDirty = false;
|
||||
}
|
||||
if (layer->contentsDirty) {
|
||||
backend->setImage(backend, layer->layer, layer->image->data);
|
||||
layer->contentsDirty = false;
|
||||
}
|
||||
layer->dirty = false;
|
||||
}
|
||||
|
||||
|
||||
static void mScriptCanvasLayerSetPosition(struct mScriptCanvasLayer* layer, int32_t x, int32_t y) {
|
||||
layer->x = x;
|
||||
layer->y = y;
|
||||
layer->dimsDirty = true;
|
||||
layer->dirty = true;
|
||||
}
|
||||
|
||||
static void mScriptCanvasLayerInvalidate(struct mScriptCanvasLayer* layer) {
|
||||
layer->contentsDirty = true;
|
||||
layer->dirty = true;
|
||||
}
|
||||
|
||||
void mScriptContextAttachCanvas(struct mScriptContext* context) {
|
||||
struct mScriptCanvasContext* canvas = calloc(1, sizeof(*canvas));
|
||||
canvas->scale = 1;
|
||||
size_t i;
|
||||
for (i = 0; i < VIDEO_LAYER_OVERLAY_COUNT; ++i) {
|
||||
canvas->overlays[i].layer = VIDEO_LAYER_OVERLAY0 + i;
|
||||
}
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCanvasContext));
|
||||
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
value->value.opaque = canvas;
|
||||
|
||||
canvas->context = context;
|
||||
struct mScriptValue* lambda = mScriptObjectBindLambda(value, "update", NULL);
|
||||
canvas->frameCbid = mScriptContextAddCallback(context, "frame", lambda);
|
||||
mScriptValueDeref(lambda);
|
||||
|
||||
mScriptContextSetGlobal(context, "canvas", value);
|
||||
mScriptContextSetDocstring(context, "canvas", "Singleton instance of struct::mScriptCanvasContext");
|
||||
}
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, _deinit, mScriptCanvasContextDeinit, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, W(mScriptCanvasLayer), newLayer, mScriptCanvasLayerCreate, 2, S32, width, S32, height);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasContext, update, _mScriptCanvasUpdate, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, width, _mScriptCanvasWidth, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, U32, height, _mScriptCanvasHeight, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenWidth, _mScriptCanvasScreenWidth, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCanvasContext, S32, screenHeight, _mScriptCanvasScreenHeight, 0);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptCanvasContext)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"A canvas that can be used for drawing images on or around the screen."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCanvasContext)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Create a new layer of a given size. If multiple layers overlap, the most recently created one takes priority.")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, newLayer)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, update)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, width)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, height)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenWidth)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasContext, screenHeight)
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, update, mScriptCanvasLayerInvalidate, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCanvasLayer, setPosition, mScriptCanvasLayerSetPosition, 2, S32, x, S32, y);
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mScriptCanvasLayer)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"An individual layer of a drawable canvas."
|
||||
)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Mark the contents of the layer as needed to be repainted")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, update)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Set the position of the layer in the canvas")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCanvasLayer, setPosition)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The image that has the pixel contents of the image")
|
||||
mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptCanvasLayer, PS(mImage), image)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The current x (horizontal) position of this layer")
|
||||
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, x)
|
||||
mSCRIPT_DEFINE_DOCSTRING("The current y (vertical) position of this layer")
|
||||
mSCRIPT_DEFINE_STRUCT_CONST_MEMBER(mScriptCanvasLayer, S32, y)
|
||||
mSCRIPT_DEFINE_END;
|
|
@ -235,12 +235,15 @@ void mScriptContextTriggerCallback(struct mScriptContext* context, const char* c
|
|||
do {
|
||||
struct mScriptFrame frame;
|
||||
struct mScriptCallbackInfo* info = TableIteratorGetValue(table, &iter);
|
||||
mScriptFrameInit(&frame);
|
||||
if (args) {
|
||||
mScriptListCopy(&frame.arguments, args);
|
||||
struct mScriptValue* fn = mScriptContextAccessWeakref(context, info->fn);
|
||||
if (fn) {
|
||||
mScriptFrameInit(&frame);
|
||||
if (args) {
|
||||
mScriptListCopy(&frame.arguments, args);
|
||||
}
|
||||
mScriptInvoke(fn, &frame);
|
||||
mScriptFrameDeinit(&frame);
|
||||
}
|
||||
mScriptInvoke(info->fn, &frame);
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
||||
if (info->oneshot) {
|
||||
*UInt32ListAppend(&oneshots) = info->id;
|
||||
|
@ -255,7 +258,15 @@ void mScriptContextTriggerCallback(struct mScriptContext* context, const char* c
|
|||
}
|
||||
|
||||
static uint32_t mScriptContextAddCallbackInternal(struct mScriptContext* context, const char* callback, struct mScriptValue* fn, bool oneshot) {
|
||||
if (fn->type->base != mSCRIPT_TYPE_FUNCTION) {
|
||||
if (fn->type == mSCRIPT_TYPE_MS_WEAKREF) {
|
||||
struct mScriptValue* weakref = mScriptContextAccessWeakref(context, fn);
|
||||
if (!weakref) {
|
||||
return 0;
|
||||
}
|
||||
if (weakref->type->base != mSCRIPT_TYPE_FUNCTION) {
|
||||
return 0;
|
||||
}
|
||||
} else if (fn->type->base != mSCRIPT_TYPE_FUNCTION) {
|
||||
return 0;
|
||||
}
|
||||
struct Table* table = HashTableLookup(&context->callbacks, callback);
|
||||
|
|
|
@ -126,9 +126,53 @@ M_TEST_DEFINE(oneshot) {
|
|||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
static void _tableIncrement(struct mScriptValue* table) {
|
||||
assert_non_null(table);
|
||||
struct mScriptValue* value = mScriptTableLookup(table, &mSCRIPT_MAKE_CHARP("key"));
|
||||
assert_non_null(value);
|
||||
assert_ptr_equal(value->type, mSCRIPT_TYPE_MS_S32);
|
||||
++value->value.s32;
|
||||
}
|
||||
|
||||
mSCRIPT_BIND_VOID_FUNCTION(tableIncrement, _tableIncrement, 1, WTABLE, table);
|
||||
|
||||
M_TEST_DEFINE(callbackWeakref) {
|
||||
SETUP_LUA;
|
||||
|
||||
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||
struct mScriptList args;
|
||||
mScriptListInit(&args, 1);
|
||||
mScriptValueWrap(table, mScriptListAppend(&args));
|
||||
struct mScriptValue* lambda = mScriptLambdaCreate0(&tableIncrement, &args);
|
||||
mScriptListDeinit(&args);
|
||||
struct mScriptValue* weakref = mScriptContextMakeWeakref(&context, lambda);
|
||||
mScriptContextAddCallback(&context, "test", weakref);
|
||||
|
||||
struct mScriptValue* key = mScriptStringCreateFromUTF8("key");
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
|
||||
value->value.s32 = 1;
|
||||
mScriptTableInsert(table, key, value);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
assert_int_equal(value->value.s32, 2);
|
||||
|
||||
mScriptContextClearWeakref(&context, weakref->value.u32);
|
||||
mScriptValueDeref(weakref);
|
||||
|
||||
mScriptContextTriggerCallback(&context, "test", NULL);
|
||||
assert_int_equal(value->value.s32, 2);
|
||||
|
||||
mScriptValueDeref(table);
|
||||
mScriptValueDeref(key);
|
||||
mScriptValueDeref(value);
|
||||
|
||||
mScriptContextDeinit(&context);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStdlib,
|
||||
cmocka_unit_test(bitMask),
|
||||
cmocka_unit_test(bitUnmask),
|
||||
cmocka_unit_test(callbacks),
|
||||
cmocka_unit_test(oneshot),
|
||||
cmocka_unit_test(callbackWeakref),
|
||||
)
|
||||
|
|
|
@ -82,6 +82,10 @@ static bool isNullStruct(struct Test* arg) {
|
|||
return !arg;
|
||||
}
|
||||
|
||||
static void increment(struct Test* t) {
|
||||
++t->a;
|
||||
}
|
||||
|
||||
mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0);
|
||||
mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored);
|
||||
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in);
|
||||
|
@ -95,6 +99,7 @@ mSCRIPT_BIND_FUNCTION(boundIsSequential, S32, isSequential, 1, LIST, list);
|
|||
mSCRIPT_BIND_FUNCTION(boundIsNullCharp, BOOL, isNullCharp, 1, CHARP, arg);
|
||||
mSCRIPT_BIND_FUNCTION(boundIsNullStruct, BOOL, isNullStruct, 1, S(Test), arg);
|
||||
mSCRIPT_BIND_FUNCTION_WITH_DEFAULTS(boundAddIntWithDefaults, S32, addInts, 2, S32, a, S32, b);
|
||||
mSCRIPT_BIND_VOID_FUNCTION(boundIncrement, increment, 1, S(Test), this);
|
||||
|
||||
mSCRIPT_DEFINE_FUNCTION_BINDING_DEFAULTS(boundAddIntWithDefaults)
|
||||
mSCRIPT_NO_DEFAULT,
|
||||
|
@ -1336,6 +1341,28 @@ M_TEST_DEFINE(nullStruct) {
|
|||
mScriptFrameDeinit(&frame);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(lambda0) {
|
||||
struct mScriptList args;
|
||||
struct Test t = {
|
||||
.a = 0
|
||||
};
|
||||
|
||||
mScriptListInit(&args, 1);
|
||||
mSCRIPT_PUSH(&args, S(Test), &t);
|
||||
struct mScriptValue* fn = mScriptLambdaCreate0(&boundIncrement, &args);
|
||||
assert_non_null(fn);
|
||||
mScriptListDeinit(&args);
|
||||
|
||||
struct mScriptFrame frame;
|
||||
mScriptFrameInit(&frame);
|
||||
assert_int_equal(t.a, 0);
|
||||
assert_true(mScriptInvoke(fn, &frame));
|
||||
assert_int_equal(t.a, 1);
|
||||
mScriptFrameDeinit(&frame);
|
||||
|
||||
mScriptValueDeref(fn);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE(mScript,
|
||||
cmocka_unit_test(voidArgs),
|
||||
cmocka_unit_test(voidFunc),
|
||||
|
@ -1373,4 +1400,5 @@ M_TEST_SUITE_DEFINE(mScript,
|
|||
cmocka_unit_test(invokeList),
|
||||
cmocka_unit_test(nullString),
|
||||
cmocka_unit_test(nullStruct),
|
||||
cmocka_unit_test(lambda0),
|
||||
)
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
#include <mgba-util/string.h>
|
||||
#include <mgba-util/table.h>
|
||||
|
||||
struct mScriptLambda {
|
||||
struct mScriptValue* fn;
|
||||
struct mScriptList arguments;
|
||||
};
|
||||
|
||||
static void _allocList(struct mScriptValue*);
|
||||
static void _freeList(struct mScriptValue*);
|
||||
|
||||
|
@ -47,6 +52,10 @@ static bool _boolEqual(const struct mScriptValue*, const struct mScriptValue*);
|
|||
static bool _charpEqual(const struct mScriptValue*, const struct mScriptValue*);
|
||||
static bool _stringEqual(const struct mScriptValue*, const struct mScriptValue*);
|
||||
|
||||
static void _lambdaAlloc(struct mScriptValue* val);
|
||||
static void _lambdaFree(struct mScriptValue* val);
|
||||
static bool _callLambda0(struct mScriptFrame* frame, void* context);
|
||||
|
||||
const struct mScriptType mSTVoid = {
|
||||
.base = mSCRIPT_TYPE_VOID,
|
||||
.size = 0,
|
||||
|
@ -268,6 +277,25 @@ const struct mScriptType mSTWeakref = {
|
|||
.hash = NULL,
|
||||
};
|
||||
|
||||
const struct mScriptType mSTLambda0 = {
|
||||
.base = mSCRIPT_TYPE_FUNCTION,
|
||||
.size = sizeof(struct mScriptLambda),
|
||||
.name = "lambda",
|
||||
.details = {
|
||||
.function = {
|
||||
.parameters = {
|
||||
.count = 0,
|
||||
},
|
||||
.returnType = {
|
||||
.count = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
.alloc = _lambdaAlloc,
|
||||
.free = _lambdaFree,
|
||||
.hash = NULL,
|
||||
};
|
||||
|
||||
struct mScriptValue mScriptValueNull = {
|
||||
.type = &mSTVoid,
|
||||
.refs = mSCRIPT_VALUE_UNREF
|
||||
|
@ -826,6 +854,32 @@ bool _stringEqual(const struct mScriptValue* a, const struct mScriptValue* b) {
|
|||
return strncmp(valA, valB, lenA) == 0;
|
||||
}
|
||||
|
||||
void _lambdaAlloc(struct mScriptValue* value) {
|
||||
struct mScriptLambda* lambda = calloc(1, sizeof(*lambda));
|
||||
struct mScriptFunction* fn = calloc(1, sizeof(*fn));
|
||||
fn->context = lambda;
|
||||
mScriptListInit(&lambda->arguments, 0);
|
||||
value->value.opaque = fn;
|
||||
}
|
||||
|
||||
void _lambdaFree(struct mScriptValue* value) {
|
||||
struct mScriptFunction* fn = value->value.opaque;
|
||||
struct mScriptLambda* lambda = fn->context;
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(&lambda->arguments); ++i) {
|
||||
struct mScriptValue* val = mScriptListGetPointer(&lambda->arguments, i);
|
||||
if (val->type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
continue;
|
||||
}
|
||||
val = mScriptValueUnwrap(val);
|
||||
mScriptValueDeref(val);
|
||||
}
|
||||
mScriptListDeinit(&lambda->arguments);
|
||||
mScriptValueDeref(lambda->fn);
|
||||
free(lambda);
|
||||
free(fn);
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type) {
|
||||
// TODO: Use an arena instead of just the generic heap
|
||||
struct mScriptValue* val = malloc(sizeof(*val));
|
||||
|
@ -1074,6 +1128,44 @@ void mScriptFrameDeinit(struct mScriptFrame* frame) {
|
|||
mScriptListDeinit(&frame->arguments);
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptLambdaCreate0(struct mScriptValue* fn, struct mScriptList* args) {
|
||||
struct mScriptValue* value = mScriptValueAlloc(&mSTLambda0);
|
||||
struct mScriptFunction* lfn = value->value.opaque;
|
||||
struct mScriptLambda* lambda = lfn->context;
|
||||
lfn->call = _callLambda0;
|
||||
lambda->fn = fn;
|
||||
mScriptValueRef(fn);
|
||||
if (args) {
|
||||
mScriptListCopy(&lambda->arguments, args);
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(args); ++i) {
|
||||
struct mScriptValue* val = mScriptListGetPointer(args, i);
|
||||
if (val->type->base != mSCRIPT_TYPE_WRAPPER) {
|
||||
continue;
|
||||
}
|
||||
val = mScriptValueUnwrap(val);
|
||||
mScriptValueRef(val);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool _callLambda0(struct mScriptFrame* frame, void* context) {
|
||||
if (mScriptListSize(&frame->arguments)) {
|
||||
return false;
|
||||
}
|
||||
struct mScriptLambda* lambda = context;
|
||||
struct mScriptFrame subframe;
|
||||
mScriptFrameInit(&subframe);
|
||||
mScriptListCopy(&subframe.arguments, &lambda->arguments);
|
||||
bool ok = mScriptInvoke(lambda->fn, &subframe);
|
||||
if (mScriptListSize(&subframe.returnValues)) {
|
||||
ok = false;
|
||||
}
|
||||
mScriptFrameDeinit(&subframe);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScriptClassInitDetails* details, bool child) {
|
||||
const char* docstring = NULL;
|
||||
|
||||
|
@ -1590,6 +1682,40 @@ void mScriptObjectFree(struct mScriptValue* value) {
|
|||
}
|
||||
}
|
||||
|
||||
struct mScriptValue* mScriptObjectBindLambda(struct mScriptValue* obj, const char* member, struct mScriptList* args) {
|
||||
if (obj->type->base != mSCRIPT_TYPE_OBJECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct mScriptTypeClass* cls = obj->type->details.cls;
|
||||
if (!cls) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mScriptClassInit(cls);
|
||||
|
||||
struct mScriptList arguments;
|
||||
struct mScriptValue fn;
|
||||
if (!mScriptObjectGetConst(obj, member, &fn)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mScriptListInit(&arguments, 0);
|
||||
mScriptValueWrap(obj, mScriptListAppend(&arguments));
|
||||
if (args) {
|
||||
size_t i;
|
||||
for (i = 0; i < mScriptListSize(args); ++i) {
|
||||
memcpy(mScriptListAppend(&arguments), mScriptListGetConstPointer(args, i), sizeof(struct mScriptValue));
|
||||
}
|
||||
}
|
||||
|
||||
struct mScriptValue* value = mScriptValueAlloc(fn.type);
|
||||
struct mScriptValue* lambda = mScriptLambdaCreate0(value, &arguments);
|
||||
mScriptValueDeref(value);
|
||||
mScriptListDeinit(&arguments);
|
||||
return lambda;
|
||||
}
|
||||
|
||||
bool mScriptPopS32(struct mScriptList* list, int32_t* out) {
|
||||
mSCRIPT_POP(list, S32, val);
|
||||
*out = val;
|
||||
|
|
Loading…
Reference in New Issue