Merge branch 'feature/canvas'

This commit is contained in:
Vicki Pfau 2023-05-01 21:40:57 -07:00
commit 201f0df4c2
29 changed files with 1303 additions and 137 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,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*);

View File

@ -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>

View File

@ -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

View File

@ -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);

View File

@ -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)

BIN
res/scripts/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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;

View File

@ -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) {

View File

@ -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);

View File

@ -92,6 +92,7 @@ struct mGLES2Context {
struct mGLES2Shader initialShader;
struct mGLES2Shader finalShader;
struct mGLES2Shader interframeShader;
struct mGLES2Shader overlayShader;
struct mGLES2Shader* shaders;
size_t nShaders;

View File

@ -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());

View File

@ -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();

View File

@ -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() {

View File

@ -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");
}

View File

@ -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;
};

View File

@ -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) {

View File

@ -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;
};
}

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -1,5 +1,6 @@
include(ExportDirectory)
set(SOURCE_FILES
canvas.c
context.c
input.c
image.c

289
src/script/canvas.c Normal file
View File

@ -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;

View File

@ -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);

View File

@ -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),
)

View File

@ -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),
)

View File

@ -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;