Scripting: Add canvas API

This commit is contained in:
Vicki Pfau 2023-04-12 22:28:04 -07:00
parent 18d0ad6ff9
commit dda5634189
9 changed files with 341 additions and 4 deletions

View File

@ -26,10 +26,21 @@ 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,23 @@
/* 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*);
CXX_GUARD_END
#endif

View File

@ -14,6 +14,7 @@
#include "MessagePainter.h"
struct VDir;
struct VideoBackend;
struct VideoShader;
namespace QGBA {

View File

@ -970,6 +970,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();
}
@ -1020,6 +1026,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. "
@ -1061,8 +1076,15 @@ void Window::reloadDisplayDriver() {
m_display->setBackgroundImage(QImage{m_config->getOption("backgroundImage")});
std::shared_ptr<VideoProxy> proxy = std::make_shared<VideoProxy>();
if (!proxy) {
proxy = std::make_shared<VideoProxy>();
}
m_display->setVideoProxy(proxy);
#ifdef ENABLE_SCRIPTING
if (m_scripting) {
m_scripting->setVideoBackend(proxy->backend());
}
#endif
}
void Window::reloadAudioDriver() {

View File

@ -87,6 +87,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()) {
@ -304,11 +308,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);

View File

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

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

@ -0,0 +1,269 @@
/* 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;
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;
};
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;
}
}
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;
}
static unsigned _mScriptCanvasHeight(struct mScriptCanvasContext* canvas) {
if (!canvas->backend) {
return 0;
}
unsigned w, h;
VideoBackendGetFrameSize(canvas->backend, &w, &h);
return h;
}
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;
}
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;
}
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;
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,
.y = layer->y,
.width = layer->image->width,
.height = layer->image->height,
};
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));
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;