919 lines
31 KiB
C
919 lines
31 KiB
C
#include "qjs_sdl_video.h"
|
||
#include "jsffi.h"
|
||
#include "qjs_macros.h"
|
||
#include "prosperon.h"
|
||
#include "render.h"
|
||
#include "sprite.h"
|
||
#include "font.h"
|
||
#include "transform.h"
|
||
|
||
#include <SDL3/SDL.h>
|
||
#include <SDL3/SDL_gpu.h>
|
||
#include <SDL3/SDL_error.h>
|
||
#include <SDL3/SDL_properties.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
// External function declarations
|
||
extern prosperon_rt *create_actor(int argc, char **argv, void (*hook)(JSContext *));
|
||
extern const char *register_actor(const char *id, prosperon_rt *rt, int main_thread);
|
||
|
||
// SDL Window free function
|
||
void SDL_Window_free(JSRuntime *rt, SDL_Window *w)
|
||
{
|
||
SDL_DestroyWindow(w);
|
||
}
|
||
|
||
// Renderer context structure
|
||
typedef struct renderer_ctx {
|
||
SDL_Renderer *sdl;
|
||
shader_globals cam;
|
||
} renderer_ctx;
|
||
|
||
void renderer_ctx_free(JSRuntime *rt, renderer_ctx *ctx)
|
||
{
|
||
SDL_DestroyRenderer(ctx->sdl);
|
||
free(ctx);
|
||
}
|
||
|
||
void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){
|
||
SDL_DestroyTexture(t);
|
||
}
|
||
|
||
QJSCLASS(SDL_Texture,
|
||
JS_SetPropertyStr(js, j, "width", number2js(js,n->w));
|
||
JS_SetPropertyStr(js,j,"height",number2js(js,n->h));
|
||
)
|
||
|
||
// Class definitions
|
||
QJSCLASS(renderer_ctx,)
|
||
QJSCLASS(SDL_Window,)
|
||
|
||
// External function declarations
|
||
extern shader_globals camera_globals(JSContext *js, JSValue camera);
|
||
extern JSValue rect2js(JSContext *js, rect r);
|
||
extern rect js2rect(JSContext *js, JSValue v);
|
||
extern HMM_Vec2 js2vec2(JSContext *js, JSValue v);
|
||
extern JSValue vec22js(JSContext *js, HMM_Vec2 v);
|
||
extern colorf js2color(JSContext *js, JSValue v);
|
||
extern double js2number(JSContext *js, JSValue v);
|
||
extern JSValue number2js(JSContext *js, double n);
|
||
extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v);
|
||
extern JSValue SDL_Texture2js(JSContext *js, SDL_Texture *t);
|
||
extern SDL_Surface *js2SDL_Surface(JSContext *js, JSValue v);
|
||
extern JSValue SDL_Surface2js(JSContext *js, SDL_Surface *s);
|
||
extern const char *js2cstring(JSContext *js, JSValue v);
|
||
extern void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size);
|
||
extern double js_getnum_str(JSContext *js, JSValue v, const char *str);
|
||
extern sprite *js2sprite(JSContext *js, JSValue v);
|
||
extern HMM_Vec3 js2vec3(JSContext *js, JSValue v);
|
||
extern JSValue vec32js(JSContext *js, HMM_Vec3 v);
|
||
extern HMM_Vec4 js2vec4(JSContext *js, JSValue v);
|
||
extern JSValue vec42js(JSContext *js, HMM_Vec4 v);
|
||
extern JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index);
|
||
extern JSValue make_quad_indices_buffer(JSContext *js, int quads);
|
||
|
||
// Renderer world to screen transformation
|
||
static inline SDL_FPoint renderer_world_to_screen(renderer_ctx *ctx, HMM_Vec2 p)
|
||
{
|
||
HMM_Vec4 clip = HMM_MulM4V4(
|
||
ctx->cam.world_to_projection,
|
||
(HMM_Vec4){ p.x, p.y, 0.0f, 1.0f });
|
||
|
||
float inv_w = 1.0f / clip.w;
|
||
float ndc_x = clip.x * inv_w; /* −1 … +1 */
|
||
float ndc_y = clip.y * inv_w;
|
||
|
||
float scr_x = (ndc_x * 0.5f + 0.5f) * ctx->cam.render_size.x;
|
||
float scr_y = (1.0f - (ndc_y * 0.5f + 0.5f)) * ctx->cam.render_size.y;
|
||
|
||
return (SDL_FPoint){ scr_x, scr_y };
|
||
}
|
||
|
||
static inline rect renderer_worldrect_to_screen(renderer_ctx *ctx, rect r_world)
|
||
{
|
||
SDL_FPoint bl = renderer_world_to_screen(
|
||
ctx, (HMM_Vec2){ r_world.x,
|
||
r_world.y });
|
||
|
||
SDL_FPoint tr = renderer_world_to_screen(
|
||
ctx, (HMM_Vec2){ r_world.x + r_world.w,
|
||
r_world.y + r_world.h });
|
||
|
||
/* SDL wants the *top-left* corner, and y grows down. */
|
||
rect out;
|
||
out.x = SDL_min(bl.x, tr.x); /* left edge */
|
||
out.y = SDL_min(bl.y, tr.y); /* top edge */
|
||
|
||
/* always positive width / height */
|
||
out.w = fabsf(tr.x - bl.x);
|
||
out.h = fabsf(tr.y - bl.y);
|
||
return out;
|
||
}
|
||
|
||
// Window constructor function
|
||
static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv)
|
||
{
|
||
if (argc < 1 || !JS_IsObject(argv[0]))
|
||
return JS_ThrowTypeError(js, "Window constructor requires an object argument");
|
||
|
||
JSValue opts = argv[0];
|
||
|
||
// Get basic properties (defaults are handled in JavaScript)
|
||
const char *title = NULL;
|
||
JSValue title_val = JS_GetPropertyStr(js, opts, "title");
|
||
if (!JS_IsUndefined(title_val) && !JS_IsNull(title_val)) {
|
||
title = JS_ToCString(js, title_val);
|
||
}
|
||
JS_FreeValue(js, title_val);
|
||
|
||
if (!title) {
|
||
return JS_ThrowTypeError(js, "Window title is required");
|
||
}
|
||
|
||
int width = 640;
|
||
JSValue width_val = JS_GetPropertyStr(js, opts, "width");
|
||
if (!JS_IsUndefined(width_val) && !JS_IsNull(width_val)) {
|
||
width = js2number(js, width_val);
|
||
}
|
||
JS_FreeValue(js, width_val);
|
||
|
||
int height = 480;
|
||
JSValue height_val = JS_GetPropertyStr(js, opts, "height");
|
||
if (!JS_IsUndefined(height_val) && !JS_IsNull(height_val)) {
|
||
height = js2number(js, height_val);
|
||
}
|
||
JS_FreeValue(js, height_val);
|
||
|
||
// Handle window position
|
||
int x = SDL_WINDOWPOS_UNDEFINED;
|
||
JSValue x_val = JS_GetPropertyStr(js, opts, "x");
|
||
if (!JS_IsUndefined(x_val)) {
|
||
if (JS_IsString(x_val)) {
|
||
const char *pos = JS_ToCString(js, x_val);
|
||
if (strcmp(pos, "centered") == 0) x = SDL_WINDOWPOS_CENTERED;
|
||
JS_FreeCString(js, pos);
|
||
} else {
|
||
x = js2number(js, x_val);
|
||
}
|
||
}
|
||
JS_FreeValue(js, x_val);
|
||
|
||
int y = SDL_WINDOWPOS_UNDEFINED;
|
||
JSValue y_val = JS_GetPropertyStr(js, opts, "y");
|
||
if (!JS_IsUndefined(y_val)) {
|
||
if (JS_IsString(y_val)) {
|
||
const char *pos = JS_ToCString(js, y_val);
|
||
if (strcmp(pos, "centered") == 0) y = SDL_WINDOWPOS_CENTERED;
|
||
JS_FreeCString(js, pos);
|
||
} else {
|
||
y = js2number(js, y_val);
|
||
}
|
||
}
|
||
JS_FreeValue(js, y_val);
|
||
|
||
// Build window flags
|
||
Uint64 flags = 0;
|
||
|
||
// Check boolean properties
|
||
JSValue resizable = JS_GetPropertyStr(js, opts, "resizable");
|
||
if (JS_ToBool(js, resizable)) flags |= SDL_WINDOW_RESIZABLE;
|
||
JS_FreeValue(js, resizable);
|
||
|
||
JSValue fullscreen = JS_GetPropertyStr(js, opts, "fullscreen");
|
||
if (JS_ToBool(js, fullscreen)) flags |= SDL_WINDOW_FULLSCREEN;
|
||
JS_FreeValue(js, fullscreen);
|
||
|
||
JSValue hidden = JS_GetPropertyStr(js, opts, "hidden");
|
||
if (JS_ToBool(js, hidden)) flags |= SDL_WINDOW_HIDDEN;
|
||
JS_FreeValue(js, hidden);
|
||
|
||
JSValue borderless = JS_GetPropertyStr(js, opts, "borderless");
|
||
if (JS_ToBool(js, borderless)) flags |= SDL_WINDOW_BORDERLESS;
|
||
JS_FreeValue(js, borderless);
|
||
|
||
JSValue always_on_top = JS_GetPropertyStr(js, opts, "alwaysOnTop");
|
||
if (JS_ToBool(js, always_on_top)) flags |= SDL_WINDOW_ALWAYS_ON_TOP;
|
||
JS_FreeValue(js, always_on_top);
|
||
|
||
JSValue minimized = JS_GetPropertyStr(js, opts, "minimized");
|
||
if (JS_ToBool(js, minimized)) flags |= SDL_WINDOW_MINIMIZED;
|
||
JS_FreeValue(js, minimized);
|
||
|
||
JSValue maximized = JS_GetPropertyStr(js, opts, "maximized");
|
||
if (JS_ToBool(js, maximized)) flags |= SDL_WINDOW_MAXIMIZED;
|
||
JS_FreeValue(js, maximized);
|
||
|
||
JSValue mouse_grabbed = JS_GetPropertyStr(js, opts, "mouseGrabbed");
|
||
if (JS_ToBool(js, mouse_grabbed)) flags |= SDL_WINDOW_MOUSE_GRABBED;
|
||
JS_FreeValue(js, mouse_grabbed);
|
||
|
||
JSValue keyboard_grabbed = JS_GetPropertyStr(js, opts, "keyboardGrabbed");
|
||
if (JS_ToBool(js, keyboard_grabbed)) flags |= SDL_WINDOW_KEYBOARD_GRABBED;
|
||
JS_FreeValue(js, keyboard_grabbed);
|
||
|
||
JSValue high_pixel_density = JS_GetPropertyStr(js, opts, "highPixelDensity");
|
||
if (JS_ToBool(js, high_pixel_density)) flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||
JS_FreeValue(js, high_pixel_density);
|
||
|
||
JSValue transparent = JS_GetPropertyStr(js, opts, "transparent");
|
||
if (JS_ToBool(js, transparent)) flags |= SDL_WINDOW_TRANSPARENT;
|
||
JS_FreeValue(js, transparent);
|
||
|
||
JSValue not_focusable = JS_GetPropertyStr(js, opts, "notFocusable");
|
||
if (JS_ToBool(js, not_focusable)) flags |= SDL_WINDOW_NOT_FOCUSABLE;
|
||
JS_FreeValue(js, not_focusable);
|
||
|
||
// Special window types
|
||
JSValue utility = JS_GetPropertyStr(js, opts, "utility");
|
||
if (JS_ToBool(js, utility)) flags |= SDL_WINDOW_UTILITY;
|
||
JS_FreeValue(js, utility);
|
||
|
||
JSValue tooltip = JS_GetPropertyStr(js, opts, "tooltip");
|
||
if (JS_ToBool(js, tooltip)) flags |= SDL_WINDOW_TOOLTIP;
|
||
JS_FreeValue(js, tooltip);
|
||
|
||
JSValue popup_menu = JS_GetPropertyStr(js, opts, "popupMenu");
|
||
if (JS_ToBool(js, popup_menu)) flags |= SDL_WINDOW_POPUP_MENU;
|
||
JS_FreeValue(js, popup_menu);
|
||
|
||
// Graphics API flags
|
||
JSValue opengl = JS_GetPropertyStr(js, opts, "opengl");
|
||
if (JS_ToBool(js, opengl)) flags |= SDL_WINDOW_OPENGL;
|
||
JS_FreeValue(js, opengl);
|
||
|
||
JSValue vulkan = JS_GetPropertyStr(js, opts, "vulkan");
|
||
if (JS_ToBool(js, vulkan)) flags |= SDL_WINDOW_VULKAN;
|
||
JS_FreeValue(js, vulkan);
|
||
|
||
JSValue metal = JS_GetPropertyStr(js, opts, "metal");
|
||
if (JS_ToBool(js, metal)) flags |= SDL_WINDOW_METAL;
|
||
JS_FreeValue(js, metal);
|
||
|
||
// Handle parent window for tooltips, popups, etc
|
||
SDL_Window *parent = NULL;
|
||
JSValue parent_val = JS_GetPropertyStr(js, opts, "parent");
|
||
if (!JS_IsUndefined(parent_val)) {
|
||
parent = js2SDL_Window(js, parent_val);
|
||
}
|
||
JS_FreeValue(js, parent_val);
|
||
|
||
// Create window with properties if we need special handling
|
||
SDL_Window *window = NULL;
|
||
|
||
if (parent || (flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))) {
|
||
// Use SDL_CreateWindowWithProperties for advanced features
|
||
SDL_PropertiesID props = SDL_CreateProperties();
|
||
|
||
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, width);
|
||
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, height);
|
||
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title);
|
||
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, x);
|
||
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, y);
|
||
|
||
// Set boolean properties
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, flags & SDL_WINDOW_RESIZABLE);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, flags & SDL_WINDOW_FULLSCREEN);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, flags & SDL_WINDOW_HIDDEN);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, flags & SDL_WINDOW_BORDERLESS);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN, flags & SDL_WINDOW_ALWAYS_ON_TOP);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN, flags & SDL_WINDOW_MINIMIZED);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN, flags & SDL_WINDOW_MAXIMIZED);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN, flags & SDL_WINDOW_MOUSE_GRABBED);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN, flags & SDL_WINDOW_TRANSPARENT);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, !(flags & SDL_WINDOW_NOT_FOCUSABLE));
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN, flags & SDL_WINDOW_UTILITY);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, flags & SDL_WINDOW_TOOLTIP);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, flags & SDL_WINDOW_POPUP_MENU);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, flags & SDL_WINDOW_OPENGL);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, flags & SDL_WINDOW_VULKAN);
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, flags & SDL_WINDOW_METAL);
|
||
|
||
if (parent) {
|
||
SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, parent);
|
||
}
|
||
|
||
// Handle modal property
|
||
JSValue modal = JS_GetPropertyStr(js, opts, "modal");
|
||
if (JS_ToBool(js, modal) && parent) {
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN, 1);
|
||
}
|
||
JS_FreeValue(js, modal);
|
||
|
||
// Handle external graphics context
|
||
JSValue external_graphics = JS_GetPropertyStr(js, opts, "externalGraphicsContext");
|
||
if (JS_ToBool(js, external_graphics)) {
|
||
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, 1);
|
||
}
|
||
JS_FreeValue(js, external_graphics);
|
||
|
||
window = SDL_CreateWindowWithProperties(props);
|
||
SDL_DestroyProperties(props);
|
||
} else {
|
||
// Use simpler API for basic windows
|
||
window = SDL_CreateWindow(title, width, height, flags);
|
||
}
|
||
|
||
// Always free the title string since we allocated it
|
||
if (title) {
|
||
JS_FreeCString(js, title);
|
||
}
|
||
|
||
if (!window) {
|
||
return JS_ThrowReferenceError(js, "Failed to create window: %s", SDL_GetError());
|
||
}
|
||
|
||
// Set additional properties that can't be set during creation
|
||
JSValue opacity_val = JS_GetPropertyStr(js, opts, "opacity");
|
||
if (!JS_IsUndefined(opacity_val)) {
|
||
float opacity = js2number(js, opacity_val);
|
||
SDL_SetWindowOpacity(window, opacity);
|
||
}
|
||
JS_FreeValue(js, opacity_val);
|
||
|
||
// Handle text input
|
||
JSValue text_input = JS_GetPropertyStr(js, opts, "textInput");
|
||
if (JS_ToBool(js, text_input)) {
|
||
SDL_StartTextInput(window);
|
||
}
|
||
JS_FreeValue(js, text_input);
|
||
|
||
return SDL_Window2js(js, window);
|
||
}
|
||
|
||
// Window functions
|
||
JSC_SCALL(SDL_Window_make_renderer,
|
||
SDL_Window *win = js2SDL_Window(js,self);
|
||
renderer_ctx *ctx = malloc(sizeof(*ctx));
|
||
ctx->sdl = SDL_CreateRenderer(win, NULL);
|
||
ctx->cam = (shader_globals){0};
|
||
if (!ctx->sdl) {
|
||
free(ctx);
|
||
return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError());
|
||
}
|
||
SDL_SetRenderDrawBlendMode(ctx->sdl, SDL_BLENDMODE_BLEND);
|
||
return renderer_ctx2js(js,ctx);
|
||
)
|
||
|
||
JSC_CCALL(SDL_Window_fullscreen,
|
||
SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN)
|
||
)
|
||
|
||
JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue self) {
|
||
SDL_Window *window = js2SDL_Window(js,self);
|
||
return JS_NewBool(js,SDL_ScreenKeyboardShown(window));
|
||
}
|
||
|
||
JSValue js_window_theme(JSContext *js, JSValue self)
|
||
{
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_safe_area(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
rect r;
|
||
SDL_GetWindowSafeArea(w, &r);
|
||
return rect2js(js,r);
|
||
}
|
||
|
||
JSValue js_window_bordered(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_SetWindowBordered(w, JS_ToBool(js,argv[0]));
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_get_title(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
const char *title = SDL_GetWindowTitle(w);
|
||
return JS_NewString(js,title);
|
||
}
|
||
|
||
JSValue js_window_set_title(JSContext *js, JSValue self, JSValue val)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
const char *title = JS_ToCString(js,val);
|
||
SDL_SetWindowTitle(w,title);
|
||
JS_FreeCString(js,title);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_get_size(JSContext *js, JSValue self)
|
||
{
|
||
SDL_Window *win = js2SDL_Window(js,self);
|
||
int w, h;
|
||
SDL_GetWindowSize(win, &w, &h);
|
||
return vec22js(js, (HMM_Vec2){w,h});
|
||
}
|
||
|
||
JSValue js_window_set_size(JSContext *js, JSValue self, JSValue val)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
HMM_Vec2 size = js2vec2(js,val);
|
||
SDL_SetWindowSize(w,size.x,size.y);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_set_icon(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_Surface *s = js2SDL_Surface(js,argv[0]);
|
||
if (!SDL_SetWindowIcon(w,s))
|
||
return JS_ThrowReferenceError(js, "could not set window icon: %s", SDL_GetError());
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
JSValue js_window_mouse_grab(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||
{
|
||
SDL_Window *w = js2SDL_Window(js,self);
|
||
SDL_SetWindowMouseGrab(w, JS_ToBool(js,argv[0]));
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
static const JSCFunctionListEntry js_SDL_Window_funcs[] = {
|
||
MIST_FUNC_DEF(SDL_Window, fullscreen, 0),
|
||
MIST_FUNC_DEF(SDL_Window, make_renderer, 1),
|
||
MIST_FUNC_DEF(SDL_Window, keyboard_shown, 0),
|
||
MIST_FUNC_DEF(window, theme, 0),
|
||
MIST_FUNC_DEF(window, safe_area, 0),
|
||
MIST_FUNC_DEF(window, bordered, 1),
|
||
MIST_FUNC_DEF(window, set_icon, 1),
|
||
CGETSET_ADD(window, title),
|
||
CGETSET_ADD(window, size),
|
||
MIST_FUNC_DEF(window, mouse_grab, 1),
|
||
};
|
||
|
||
// Renderer functions
|
||
JSC_CCALL(SDL_Renderer_clear,
|
||
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
|
||
SDL_RenderClear(renderer);
|
||
)
|
||
|
||
JSC_CCALL(SDL_Renderer_present,
|
||
SDL_Renderer *ren = js2renderer_ctx(js,self)->sdl;
|
||
SDL_RenderPresent(ren);
|
||
)
|
||
|
||
JSC_CCALL(SDL_Renderer_draw_color,
|
||
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
|
||
colorf color = js2color(js,argv[0]);
|
||
SDL_SetRenderDrawColorFloat(renderer, color.r,color.g,color.b,color.a);
|
||
)
|
||
|
||
JSC_CCALL(renderer_load_texture,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
SDL_Surface *surf = js2SDL_Surface(js,argv[0]);
|
||
if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface.");
|
||
SDL_Texture *tex = SDL_CreateTextureFromSurface(r,surf);
|
||
if (!tex) return JS_ThrowReferenceError(js, "Could not create texture from surface: %s", SDL_GetError());
|
||
ret = SDL_Texture2js(js,tex);
|
||
JS_SetPropertyStr(js,ret,"width", number2js(js,tex->w));
|
||
JS_SetPropertyStr(js,ret,"height", number2js(js,tex->h));
|
||
)
|
||
|
||
JSC_CCALL(renderer_get_image,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
SDL_Surface *surf = NULL;
|
||
if (!JS_IsUndefined(argv[0])) {
|
||
rect rect = js2rect(js,argv[0]);
|
||
surf = SDL_RenderReadPixels(r,&rect);
|
||
} else
|
||
surf = SDL_RenderReadPixels(r,NULL);
|
||
if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer");
|
||
return SDL_Surface2js(js,surf);
|
||
)
|
||
|
||
JSC_CCALL(renderer_line,
|
||
if (!JS_IsArray(js, argv[0])) return JS_ThrowReferenceError(js, "First arugment must be an array of points.");
|
||
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
int len = JS_ArrayLength(js,argv[0]);
|
||
if (len < 2) return JS_ThrowReferenceError(js, "Must provide at least 2 points to render.");
|
||
|
||
SDL_FPoint points[len];
|
||
assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint));
|
||
for (int i = 0; i < len; i++) {
|
||
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
|
||
HMM_Vec2 wpt = js2vec2(js,val);
|
||
JS_FreeValue(js,val);
|
||
|
||
points[i] = renderer_world_to_screen(ctx, wpt);
|
||
}
|
||
|
||
SDL_RenderLines(r,points,len);
|
||
)
|
||
|
||
JSC_CCALL(renderer_point,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
if (JS_IsArray(js, argv[0])) {
|
||
int len = JS_ArrayLength(js, argv[0]);
|
||
SDL_FPoint pts[len];
|
||
|
||
for (int i = 0; i < len; ++i) {
|
||
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
|
||
HMM_Vec2 w = js2vec2(js, val);
|
||
JS_FreeValue(js, val);
|
||
pts[i] = renderer_world_to_screen(ctx, w);
|
||
}
|
||
SDL_RenderPoints(r, pts, len);
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
HMM_Vec2 w = js2vec2(js, argv[0]);
|
||
SDL_FPoint p = renderer_world_to_screen(ctx, w);
|
||
SDL_RenderPoint(r, p.x, p.y);
|
||
)
|
||
|
||
JSC_CCALL(renderer_texture,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
rect src = js2rect(js,argv[1]);
|
||
rect dst = js2rect(js,argv[2]);
|
||
dst = renderer_worldrect_to_screen(ctx, dst);
|
||
double angle;
|
||
JS_ToFloat64(js, &angle, argv[3]);
|
||
angle *= -360;
|
||
HMM_Vec2 anchor = js2vec2(js, argv[4]);
|
||
anchor.y = dst.h - anchor.y*dst.h;
|
||
anchor.x *= dst.w;
|
||
SDL_RenderTextureRotated(ctx->sdl, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
|
||
)
|
||
|
||
JSC_CCALL(renderer_rects,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
/* array-of-rectangles case */
|
||
if (JS_IsArray(js, argv[0])) {
|
||
int len = JS_ArrayLength(js, argv[0]);
|
||
if (len <= 0) return JS_UNDEFINED;
|
||
|
||
SDL_FRect rects[len];
|
||
|
||
for (int i = 0; i < len; ++i) {
|
||
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
|
||
rect w = js2rect(js, val);
|
||
JS_FreeValue(js, val);
|
||
w = renderer_worldrect_to_screen(ctx, w);
|
||
rects[i] = w;
|
||
}
|
||
|
||
if (!SDL_RenderFillRects(r, rects, len))
|
||
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
|
||
return JS_UNDEFINED;
|
||
}
|
||
|
||
/* single-rect path */
|
||
rect w = js2rect(js, argv[0]);
|
||
w = renderer_worldrect_to_screen(ctx, w);
|
||
if (!SDL_RenderFillRects(r, &w, 1))
|
||
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
|
||
)
|
||
|
||
// Should take a single struct with pos, color, uv, and indices arrays
|
||
JSC_CCALL(renderer_geometry,
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
JSValue pos = JS_GetPropertyStr(js,argv[1], "pos");
|
||
JSValue color = JS_GetPropertyStr(js,argv[1], "color");
|
||
JSValue uv = JS_GetPropertyStr(js,argv[1], "uv");
|
||
JSValue indices = JS_GetPropertyStr(js,argv[1], "indices");
|
||
int vertices = js_getnum_str(js, argv[1], "vertices");
|
||
int count = js_getnum_str(js, argv[1], "num_indices");
|
||
|
||
size_t pos_stride, indices_stride, uv_stride, color_stride;
|
||
void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL);
|
||
void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL);
|
||
void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL);
|
||
void *colordata = get_gpu_buffer(js,color,&color_stride, NULL);
|
||
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
|
||
HMM_Vec2 *trans_pos = malloc(vertices*sizeof(HMM_Vec2));
|
||
memcpy(trans_pos,posdata, sizeof(HMM_Vec2)*vertices);
|
||
|
||
for (int i = 0; i < vertices; i++) {
|
||
SDL_FPoint p = renderer_world_to_screen(ctx, trans_pos[i]);
|
||
trans_pos[i].x = p.x;
|
||
trans_pos[i].y = p.y;
|
||
}
|
||
|
||
if (!SDL_RenderGeometryRaw(r, tex, trans_pos, pos_stride,colordata,color_stride,uvdata, uv_stride, vertices, idxdata, count, indices_stride))
|
||
ret = JS_ThrowReferenceError(js, "Error rendering geometry: %s",SDL_GetError());
|
||
|
||
free(trans_pos);
|
||
|
||
JS_FreeValue(js,pos);
|
||
JS_FreeValue(js,color);
|
||
JS_FreeValue(js,uv);
|
||
JS_FreeValue(js,indices);
|
||
)
|
||
|
||
JSC_CCALL(renderer_geometry2,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
SDL_Renderer *r = ctx->sdl;
|
||
|
||
SDL_Texture *tex = js2SDL_Texture(js, argv[0]);
|
||
|
||
JSValue geo = argv[1];
|
||
int vertices = js_getnum_str(js, geo, "vertices");
|
||
int count = js_getnum_str(js, geo, "num_indices");
|
||
|
||
JSValue pos_v = JS_GetPropertyStr(js, geo, "pos");
|
||
JSValue color_v = JS_GetPropertyStr(js, geo, "color");
|
||
JSValue uv_v = JS_GetPropertyStr(js, geo, "uv");
|
||
JSValue idx_v = JS_GetPropertyStr(js, geo, "indices");
|
||
|
||
size_t pos_stride, color_stride, uv_stride, idx_stride;
|
||
void *pos_data = get_gpu_buffer(js, pos_v, &pos_stride, NULL);
|
||
void *color_data = get_gpu_buffer(js, color_v, &color_stride, NULL);
|
||
void *uv_data = get_gpu_buffer(js, uv_v, &uv_stride, NULL);
|
||
void *idx_data = get_gpu_buffer(js, idx_v, &idx_stride, NULL);
|
||
|
||
SDL_Vertex *verts = malloc(vertices * sizeof *verts);
|
||
for(int i = 0; i < vertices; ++i) {
|
||
const float *p = (const float *)((uint8_t *)pos_data + i * pos_stride);
|
||
const float *u = (const float *)((uint8_t *)uv_data + i * uv_stride);
|
||
const float *c = (const float *)((uint8_t *)color_data + i * color_stride);
|
||
|
||
SDL_FPoint screen = renderer_world_to_screen(ctx,
|
||
(HMM_Vec2){ .x = p[0], .y = p[1] });
|
||
|
||
verts[i].position = screen;
|
||
verts[i].tex_coord = (SDL_FPoint){ u[0], u[1] };
|
||
verts[i].color = (SDL_FColor){ c[0], c[1], c[2], c[3] };
|
||
}
|
||
|
||
const int *indices32 = NULL;
|
||
int *tmp_idx = NULL;
|
||
|
||
if(idx_data && count) {
|
||
if(idx_stride == 4) indices32 = (const int *)idx_data;
|
||
else {
|
||
tmp_idx = malloc(count * sizeof *tmp_idx);
|
||
if(idx_stride == 2) {
|
||
const uint16_t *src = idx_data;
|
||
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
|
||
} else { /* 8-bit */
|
||
const uint8_t *src = idx_data;
|
||
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
|
||
}
|
||
indices32 = tmp_idx;
|
||
}
|
||
}
|
||
|
||
printf("num verts, num indices: %d, %d\n", vertices, count);
|
||
|
||
if(!SDL_RenderGeometry(r, tex, verts, vertices, indices32, count))
|
||
printf("Error rendering geometry: %s\n", SDL_GetError());
|
||
|
||
free(tmp_idx);
|
||
free(verts);
|
||
|
||
JS_FreeValue(js, pos_v);
|
||
JS_FreeValue(js, color_v);
|
||
JS_FreeValue(js, uv_v);
|
||
JS_FreeValue(js, idx_v);
|
||
)
|
||
|
||
static sprite js_getsprite(JSContext *js, JSValue sp)
|
||
{
|
||
sprite *s = js2sprite(js, sp);
|
||
if (s)
|
||
return *s;
|
||
|
||
sprite pp = {0};
|
||
return pp;
|
||
}
|
||
|
||
JSC_CCALL(renderer_sprite,
|
||
renderer_ctx *ctx = js2renderer_ctx(js, self);
|
||
sprite sp = js_getsprite(js, argv[0]);
|
||
SDL_Texture *tex;
|
||
JSValue texture_val = JS_GetPropertyStr(js, sp.image, "texture");
|
||
tex = js2SDL_Texture(js, texture_val);
|
||
JS_FreeValue(js, texture_val);
|
||
rect uv;
|
||
JSValue rect_val = JS_GetPropertyStr(js, sp.image, "rect");
|
||
uv = js2rect(js, rect_val);
|
||
JS_FreeValue(js, rect_val);
|
||
|
||
float w = uv.w, h = uv.h;
|
||
|
||
HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y };
|
||
HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y };
|
||
HMM_Vec2 bl_local = { -sp.center.X, -sp.center.Y + h };
|
||
|
||
HMM_Vec2 world_tl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tl_local));
|
||
HMM_Vec2 world_tr = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tr_local));
|
||
HMM_Vec2 world_bl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, bl_local));
|
||
|
||
SDL_FPoint origin = renderer_world_to_screen(ctx, world_tl);
|
||
SDL_FPoint right = renderer_world_to_screen(ctx, world_tr);
|
||
SDL_FPoint down = renderer_world_to_screen(ctx, world_bl);
|
||
|
||
if (!SDL_RenderTextureAffine(ctx->sdl, tex, &uv, &origin, &right, &down))
|
||
return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError());
|
||
)
|
||
|
||
/* logical presentation helpers */
|
||
typedef struct { const char *name; SDL_RendererLogicalPresentation pres; } pres_entry;
|
||
|
||
static const pres_entry k_pres_table[] = {
|
||
{ "stretch", SDL_LOGICAL_PRESENTATION_STRETCH },
|
||
{ "letterbox", SDL_LOGICAL_PRESENTATION_LETTERBOX },
|
||
{ "overscan", SDL_LOGICAL_PRESENTATION_OVERSCAN },
|
||
{ "integer", SDL_LOGICAL_PRESENTATION_INTEGER_SCALE },
|
||
{ NULL, SDL_LOGICAL_PRESENTATION_DISABLED } /* fallback */
|
||
};
|
||
|
||
static JSValue logicalpresentation2js(JSContext *js,
|
||
SDL_RendererLogicalPresentation pres){
|
||
const pres_entry *it;
|
||
for(it = k_pres_table; it->name; ++it)
|
||
if(it->pres == pres) break;
|
||
return JS_NewString(js, it->name ? it->name : "disabled");
|
||
}
|
||
|
||
static SDL_RendererLogicalPresentation
|
||
js2SDL_LogicalPresentation(JSContext *js, JSValue v){
|
||
if(JS_IsUndefined(v)) return SDL_LOGICAL_PRESENTATION_DISABLED;
|
||
const char *s = JS_ToCString(js, v);
|
||
if(!s) return SDL_LOGICAL_PRESENTATION_DISABLED;
|
||
const pres_entry *it;
|
||
for(it = k_pres_table; it->name; ++it)
|
||
if(!strcmp(it->name, s)) break;
|
||
JS_FreeCString(js, s);
|
||
return it->pres;
|
||
}
|
||
|
||
JSC_CCALL(renderer_logical_size,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 v = js2vec2(js,argv[0]);
|
||
SDL_SetRenderLogicalPresentation(r,v.x,v.y,js2SDL_LogicalPresentation(js, argv[1]));
|
||
)
|
||
|
||
JSC_CCALL(renderer_viewport,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderViewport(r,NULL);
|
||
else {
|
||
rect view = js2rect(js,argv[0]);
|
||
SDL_SetRenderViewport(r,&view);
|
||
}
|
||
)
|
||
|
||
JSC_CCALL(renderer_clip,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderClipRect(r,NULL);
|
||
else {
|
||
rect view = js2rect(js,argv[0]);
|
||
SDL_SetRenderClipRect(r,&view);
|
||
}
|
||
)
|
||
|
||
JSC_CCALL(renderer_scale,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 v = js2vec2(js,argv[0]);
|
||
SDL_SetRenderScale(r, v.x, v.y);
|
||
)
|
||
|
||
JSC_CCALL(renderer_vsync,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
return JS_NewBool(js, SDL_SetRenderVSync(r,js2number(js,argv[0])));
|
||
)
|
||
|
||
// This returns the coordinates inside the
|
||
JSC_CCALL(renderer_coords,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
HMM_Vec2 pos, coord;
|
||
pos = js2vec2(js,argv[0]);
|
||
SDL_RenderCoordinatesFromWindow(r,pos.x,pos.y, &coord.x, &coord.y);
|
||
return vec22js(js,coord);
|
||
)
|
||
|
||
JSC_CCALL(renderer_camera,
|
||
renderer_ctx *ctx = js2renderer_ctx(js,self);
|
||
ctx->cam = camera_globals(js, argv[0]);
|
||
)
|
||
|
||
JSC_CCALL(renderer_target,
|
||
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
|
||
if (JS_IsUndefined(argv[0]))
|
||
SDL_SetRenderTarget(r, NULL);
|
||
else {
|
||
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
|
||
SDL_SetRenderTarget(r,tex);
|
||
}
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_renderer_ctx_funcs[] = {
|
||
MIST_FUNC_DEF(SDL_Renderer, draw_color, 1),
|
||
MIST_FUNC_DEF(SDL_Renderer, present, 0),
|
||
MIST_FUNC_DEF(SDL_Renderer, clear, 0),
|
||
MIST_FUNC_DEF(renderer, line, 1),
|
||
MIST_FUNC_DEF(renderer, point, 1),
|
||
MIST_FUNC_DEF(renderer, texture, 5),
|
||
MIST_FUNC_DEF(renderer, rects, 1),
|
||
MIST_FUNC_DEF(renderer, geometry, 2),
|
||
MIST_FUNC_DEF(renderer, geometry2, 2),
|
||
MIST_FUNC_DEF(renderer, sprite, 1),
|
||
MIST_FUNC_DEF(renderer, load_texture, 1),
|
||
MIST_FUNC_DEF(renderer, get_image, 1),
|
||
|
||
MIST_FUNC_DEF(renderer, scale, 1),
|
||
MIST_FUNC_DEF(renderer, logical_size,2),
|
||
MIST_FUNC_DEF(renderer, viewport,1),
|
||
MIST_FUNC_DEF(renderer, clip,1),
|
||
MIST_FUNC_DEF(renderer, vsync,1),
|
||
|
||
MIST_FUNC_DEF(renderer, coords, 1),
|
||
MIST_FUNC_DEF(renderer, camera, 2),
|
||
MIST_FUNC_DEF(renderer, target, 1),
|
||
};
|
||
|
||
extern SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v);
|
||
|
||
static const JSCFunctionListEntry js_sdl_video_funcs[] = {
|
||
// No regular functions, window is now a constructor
|
||
};
|
||
|
||
JSC_CCALL(texture_mode,
|
||
SDL_Texture *tex = js2SDL_Texture(js,self);
|
||
SDL_ScaleMode mode = js2SDL_ScaleMode(js,argv[0]);
|
||
SDL_SetTextureScaleMode(tex,mode);
|
||
)
|
||
|
||
static const JSCFunctionListEntry js_SDL_Texture_funcs[] = {
|
||
MIST_FUNC_DEF(texture, mode, 1),
|
||
};
|
||
|
||
// Hook function to set up endowments for the video actor
|
||
static void video_actor_hook(JSContext *js) {
|
||
// Get prosperon object
|
||
JSValue prosperon = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "prosperon");
|
||
JSValue c_types = JS_GetPropertyStr(js, prosperon, "c_types");
|
||
|
||
// Initialize classes
|
||
QJSCLASSPREP_FUNCS(SDL_Window)
|
||
QJSCLASSPREP_FUNCS(renderer_ctx)
|
||
QJSCLASSPREP_FUNCS(SDL_Texture)
|
||
|
||
JS_FreeValue(js, c_types);
|
||
|
||
// Create window constructor
|
||
JSValue window_ctor = JS_NewCFunction2(js, js_window_constructor, "window", 1, JS_CFUNC_constructor, 0);
|
||
|
||
// Set prototype on constructor
|
||
JS_SetConstructor(js, window_ctor, SDL_Window_proto);
|
||
|
||
// Get or create endowments object
|
||
JSValue endowments = JS_GetPropertyStr(js, prosperon, "endowments");
|
||
if (JS_IsUndefined(endowments)) {
|
||
endowments = JS_NewObject(js);
|
||
JS_SetPropertyStr(js, prosperon, "endowments", JS_DupValue(js, endowments));
|
||
}
|
||
|
||
// Set window constructor in endowments
|
||
JS_SetPropertyStr(js, endowments, "window", window_ctor);
|
||
|
||
JS_FreeValue(js, endowments);
|
||
JS_FreeValue(js, prosperon);
|
||
}
|
||
|
||
JSValue js_sdl_video_use(JSContext *js) {
|
||
if (!SDL_Init(SDL_INIT_VIDEO))
|
||
return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
|
||
|
||
// Generate a unique ID for the video actor
|
||
char id[64];
|
||
snprintf(id, sizeof(id), "video_%d", SDL_GetTicks());
|
||
|
||
printf("id is %s\n", id);
|
||
|
||
// Prepare argv for create_actor
|
||
// We need to create the actor on the main thread
|
||
const char *argv[] = {
|
||
"./prosperon",
|
||
"spawn",
|
||
"--id", id,
|
||
"--program", "scripts/core/_sdl_video.js",
|
||
"--main", "1"
|
||
};
|
||
int argc = 8;
|
||
|
||
// Create the actor with the hook to set up endowments
|
||
prosperon_rt *rt = create_actor(argc, (char**)argv, video_actor_hook);
|
||
return JS_NewString(js,id);
|
||
JSValue ret = JS_NewObject(js);
|
||
JS_SetPropertyStr(js, ret, "id", JS_NewString(js,id));
|
||
return ret;
|
||
}
|