Files
prosperon/source/qjs_sdl_video.c

919 lines
31 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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