diff --git a/scripts/core/_sdl_video.js b/scripts/core/_sdl_video.js index 78c26d47..769b512f 100644 --- a/scripts/core/_sdl_video.js +++ b/scripts/core/_sdl_video.js @@ -88,6 +88,9 @@ $_.receiver(function(msg) { case 'texture': response = handle_texture(msg); break; + case 'surface': + response = handle_surface(msg); + break; default: response = {error: "Unknown kind: " + msg.kind}; } @@ -101,65 +104,107 @@ $_.receiver(function(msg) { // Window operations function handle_window(msg) { + // Special case: create doesn't need an existing window + if (msg.op === 'create') { + var config = Object.assign({}, default_window, msg.data || {}); + var id = allocate_id(); + var window = new prosperon.endowments.window(config); + resources.window[id] = window; + return {id: id, data: {size: window.size}}; + } + + // All other operations require a valid window ID + if (!msg.id || !resources.window[msg.id]) { + return {error: "Invalid window id: " + msg.id}; + } + + var win = resources.window[msg.id]; + switch (msg.op) { - case 'create': - var config = Object.assign({}, default_window, msg.data || {}); - var id = allocate_id(); - var window = new prosperon.endowments.window(config); - resources.window[id] = window; - return {id: id, data: {width: window.width, height: window.height}}; - case 'destroy': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; - } - resources.window[msg.id].destroy(); + win.destroy(); delete resources.window[msg.id]; return {success: true}; case 'show': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; - } - resources.window[msg.id].show(); + win.visible = true; return {success: true}; case 'hide': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; - } - resources.window[msg.id].hide(); + win.visible = false; return {success: true}; - case 'set_title': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; + case 'get': + var prop = msg.data ? msg.data.property : null; + if (!prop) return {error: "Missing property name"}; + + // Handle special cases + if (prop === 'surface') { + var surf = win.surface; + if (!surf) return {data: null}; + var surf_id = allocate_id(); + resources.surface[surf_id] = surf; + return {data: surf_id}; } - if (!msg.data || !msg.data.title) { - return {error: "Missing title in data"}; + + return {data: win[prop]}; + + case 'set': + var prop = msg.data ? msg.data.property : null; + var value = msg.data ? msg.data.value : undefined; + if (!prop) return {error: "Missing property name"}; + + // Validate property is settable + var readonly = ['id', 'pixelDensity', 'displayScale', 'sizeInPixels', 'flags', 'surface']; + if (readonly.indexOf(prop) !== -1) { + return {error: "Property '" + prop + "' is read-only"}; } - resources.window[msg.id].title = msg.data.title; + + win[prop] = value; return {success: true}; - case 'get_size': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; - } - var win = resources.window[msg.id]; - return {data: {width: win.width, height: win.height}}; - - case 'set_size': - if (!msg.id || !resources.window[msg.id]) { - return {error: "Invalid window id: " + msg.id}; - } - if (!msg.data || typeof msg.data.width !== 'number' || typeof msg.data.height !== 'number') { - return {error: "Missing or invalid width/height in data"}; - } - var win = resources.window[msg.id]; - win.width = msg.data.width; - win.height = msg.data.height; + case 'fullscreen': + win.fullscreen(); return {success: true}; + case 'updateSurface': + win.updateSurface(); + return {success: true}; + + case 'updateSurfaceRects': + if (!msg.data || !msg.data.rects) return {error: "Missing rects array"}; + win.updateSurfaceRects(msg.data.rects); + return {success: true}; + + case 'raise': + win.raise(); + return {success: true}; + + case 'restore': + win.restore(); + return {success: true}; + + case 'flash': + win.flash(msg.data ? msg.data.operation : 'briefly'); + return {success: true}; + + case 'sync': + win.sync(); + return {success: true}; + + case 'setIcon': + if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"}; + var surf = resources.surface[msg.data.surface_id]; + if (!surf) return {error: "Invalid surface id"}; + win.set_icon(surf); + return {success: true}; + + case 'makeRenderer': + var renderer = win.make_renderer(); + var renderer_id = allocate_id(); + resources.renderer[renderer_id] = renderer; + return {id: renderer_id}; + default: return {error: "Unknown window operation: " + msg.op}; } @@ -167,41 +212,233 @@ function handle_window(msg) { // Renderer operations function handle_renderer(msg) { + // Special case: createWindowAndRenderer creates both + if (msg.op === 'createWindowAndRenderer') { + var data = msg.data || {}; + var result = prosperon.endowments.createWindowAndRenderer( + data.title || "Prosperon Window", + data.width || 640, + data.height || 480, + data.flags || 0 + ); + var win_id = allocate_id(); + var ren_id = allocate_id(); + resources.window[win_id] = result.window; + resources.renderer[ren_id] = result.renderer; + return {window_id: win_id, renderer_id: ren_id}; + } + + // All other operations require a valid renderer ID + if (!msg.id || !resources.renderer[msg.id]) { + return {error: "Invalid renderer id: " + msg.id}; + } + + var ren = resources.renderer[msg.id]; + switch (msg.op) { - case 'create': - if (!msg.data || !msg.data.window_id) { - return {error: "Missing window_id in data"}; - } - if (!resources.window[msg.data.window_id]) { - return {error: "Invalid window id: " + msg.data.window_id}; - } - var id = allocate_id(); - var renderer = prosperon.endowments.renderer.create(resources.window[msg.data.window_id]); - resources.renderer[id] = renderer; - return {id: id}; - case 'destroy': - if (!msg.id || !resources.renderer[msg.id]) { - return {error: "Invalid renderer id: " + msg.id}; - } - resources.renderer[msg.id].destroy(); delete resources.renderer[msg.id]; + // Renderer is automatically destroyed when all references are gone return {success: true}; case 'clear': - if (!msg.id || !resources.renderer[msg.id]) { - return {error: "Invalid renderer id: " + msg.id}; - } - resources.renderer[msg.id].clear(); + ren.clear(); return {success: true}; case 'present': - if (!msg.id || !resources.renderer[msg.id]) { - return {error: "Invalid renderer id: " + msg.id}; - } - resources.renderer[msg.id].present(); + ren.present(); return {success: true}; + case 'flush': + ren.flush(); + return {success: true}; + + case 'get': + var prop = msg.data ? msg.data.property : null; + if (!prop) return {error: "Missing property name"}; + + // Handle special cases + if (prop === 'window') { + var win = ren.window; + if (!win) return {data: null}; + // Find window ID + for (var id in resources.window) { + if (resources.window[id] === win) { + return {data: id}; + } + } + // Window not tracked, add it + var win_id = allocate_id(); + resources.window[win_id] = win; + return {data: win_id}; + } + + // Handle special getters that might return objects + if (prop === 'drawColor') { + var color = ren[prop]; + if (color && typeof color === 'object') { + // Convert color object to array format [r,g,b,a] + return {data: [color.r || 0, color.g || 0, color.b || 0, color.a || 255]}; + } + } + + return {data: ren[prop]}; + + case 'set': + var prop = msg.prop + var value = msg.value + if (!prop) return {error: "Missing property name"}; + + // Validate property is settable + var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea']; + if (readonly.indexOf(prop) !== -1) { + return {error: "Property '" + prop + "' is read-only"}; + } + + // Special handling for render target + if (prop === 'target' && value !== null && value !== undefined) { + var tex = resources.texture[value]; + if (!tex) return {error: "Invalid texture id"}; + value = tex; + } + + ren[prop] = value; + return {success: true}; + + case 'line': + if (!msg.data || !msg.data.points) return {error: "Missing points array"}; + ren.line(msg.data.points); + return {success: true}; + + case 'point': + if (!msg.data || !msg.data.points) return {error: "Missing points"}; + ren.point(msg.data.points); + return {success: true}; + + case 'rect': + if (!msg.data || !msg.data.rect) return {error: "Missing rect"}; + ren.rect(msg.data.rect); + return {success: true}; + + case 'fillRect': + if (!msg.data || !msg.data.rect) return {error: "Missing rect"}; + ren.fillRect(msg.data.rect); + return {success: true}; + + case 'rects': + if (!msg.data || !msg.data.rects) return {error: "Missing rects"}; + ren.rects(msg.data.rects); + return {success: true}; + + case 'lineTo': + if (!msg.data || !msg.data.a || !msg.data.b) return {error: "Missing points a and b"}; + ren.lineTo(msg.data.a, msg.data.b); + return {success: true}; + + case 'texture': + if (!msg.data) return {error: "Missing texture data"}; + var tex_id = msg.data.texture_id; + if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"}; + ren.texture( + resources.texture[tex_id], + msg.data.src || {x:0, y:0, w:1, h:1}, + msg.data.dst || {x:0, y:0, w:100, h:100}, + msg.data.angle || 0, + msg.data.anchor || {x:0.5, y:0.5} + ); + return {success: true}; + + case 'sprite': + if (!msg.data || !msg.data.sprite) return {error: "Missing sprite data"}; + ren.sprite(msg.data.sprite); + return {success: true}; + + case 'geometry': + if (!msg.data) return {error: "Missing geometry data"}; + var tex_id = msg.data.texture_id; + var tex = tex_id ? resources.texture[tex_id] : null; + ren.geometry(tex, msg.data.geometry); + return {success: true}; + + case 'debugText': + if (!msg.data || !msg.data.text) return {error: "Missing text"}; + ren.debugText(msg.data.pos || {x:0, y:0}, msg.data.text); + return {success: true}; + + case 'clipEnabled': + return {data: ren.clipEnabled()}; + + case 'texture9Grid': + if (!msg.data) return {error: "Missing data"}; + var tex_id = msg.data.texture_id; + if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"}; + ren.texture9Grid( + resources.texture[tex_id], + msg.data.src, + msg.data.leftWidth, + msg.data.rightWidth, + msg.data.topHeight, + msg.data.bottomHeight, + msg.data.scale, + msg.data.dst + ); + return {success: true}; + + case 'textureTiled': + if (!msg.data) return {error: "Missing data"}; + var tex_id = msg.data.texture_id; + if (!tex_id || !resources.texture[tex_id]) return {error: "Invalid texture id"}; + ren.textureTiled( + resources.texture[tex_id], + msg.data.src, + msg.data.scale || 1.0, + msg.data.dst + ); + return {success: true}; + + case 'readPixels': + var surf = ren.readPixels(msg.data ? msg.data.rect : null); + if (!surf) return {error: "Failed to read pixels"}; + var surf_id = allocate_id(); + resources.surface[surf_id] = surf; + return {id: surf_id}; + + case 'loadTexture': + if (!msg.data || !msg.data.surface_id) return {error: "Missing surface_id"}; + var surf = resources.surface[msg.data.surface_id]; + if (!surf) return {error: "Invalid surface id"}; + var tex = ren.load_texture(surf); + var tex_id = allocate_id(); + resources.texture[tex_id] = tex; + return {id: tex_id}; + + case 'createTexture': + if (!msg.data || !msg.data.width || !msg.data.height) { + return {error: "Missing width or height"}; + } + var tex = ren.createTexture( + msg.data.format || 'rgba8888', + msg.data.access || 'static', + msg.data.width, + msg.data.height + ); + if (!tex) return {error: "Failed to create texture"}; + var tex_id = allocate_id(); + resources.texture[tex_id] = tex; + return {id: tex_id, data: {size: tex.size}}; + + case 'flush': + ren.flush(); + return {success: true}; + + case 'coordsFromWindow': + if (!msg.data || !msg.data.pos) return {error: "Missing pos"}; + return {data: ren.coordsFromWindow(msg.data.pos)}; + + case 'coordsToWindow': + if (!msg.data || !msg.data.pos) return {error: "Missing pos"}; + return {data: ren.coordsToWindow(msg.data.pos)}; + default: return {error: "Unknown renderer operation: " + msg.op}; } @@ -209,12 +446,120 @@ function handle_renderer(msg) { // Texture operations function handle_texture(msg) { + // Special case: create needs a renderer + if (msg.op === 'create') { + if (!msg.data) return {error: "Missing texture data"}; + var ren_id = msg.data.renderer_id; + if (!ren_id || !resources.renderer[ren_id]) return {error: "Invalid renderer id"}; + + var tex; + var renderer = resources.renderer[ren_id]; + + // Create from surface + if (msg.data.surface_id) { + var surf = resources.surface[msg.data.surface_id]; + if (!surf) return {error: "Invalid surface id"}; + tex = new prosperon.endowments.texture(renderer, surf); + } + // Create from properties + else if (msg.data.width && msg.data.height) { + tex = new prosperon.endowments.texture(renderer, { + width: msg.data.width, + height: msg.data.height, + format: msg.data.format || 'rgba8888', + pixels: msg.data.pixels, + pitch: msg.data.pitch + }); + } + else { + return {error: "Must provide either surface_id or width/height"}; + } + + var tex_id = allocate_id(); + resources.texture[tex_id] = tex; + return {id: tex_id, data: {size: tex.size}}; + } + + // All other operations require a valid texture ID + if (!msg.id || !resources.texture[msg.id]) { + return {error: "Invalid texture id: " + msg.id}; + } + + var tex = resources.texture[msg.id]; + switch (msg.op) { - case 'create': - // TODO: Implement texture creation - return {error: "Texture operations not yet implemented"}; + case 'destroy': + delete resources.texture[msg.id]; + // Texture is automatically destroyed when all references are gone + return {success: true}; + + case 'get': + var prop = msg.data ? msg.data.property : null; + if (!prop) return {error: "Missing property name"}; + return {data: tex[prop]}; + + case 'set': + var prop = msg.data ? msg.data.property : null; + var value = msg.data ? msg.data.value : undefined; + if (!prop) return {error: "Missing property name"}; + + // Validate property is settable + var readonly = ['size', 'width', 'height']; + if (readonly.indexOf(prop) !== -1) { + return {error: "Property '" + prop + "' is read-only"}; + } + + tex[prop] = value; + return {success: true}; + + case 'update': + if (!msg.data) return {error: "Missing update data"}; + tex.update( + msg.data.rect || null, + msg.data.pixels, + msg.data.pitch || 0 + ); + return {success: true}; + + case 'lock': + var result = tex.lock(msg.data ? msg.data.rect : null); + return {data: result}; + + case 'unlock': + tex.unlock(); + return {success: true}; + + case 'query': + return {data: tex.query()}; default: return {error: "Unknown texture operation: " + msg.op}; } } + +// Surface operations (mainly for cleanup) +function handle_surface(msg) { + switch (msg.op) { + case 'destroy': + if (!msg.id || !resources.surface[msg.id]) { + return {error: "Invalid surface id: " + msg.id}; + } + delete resources.surface[msg.id]; + return {success: true}; + + default: + return {error: "Unknown surface operation: " + msg.op}; + } +} + +// Utility function to create window and renderer +prosperon.endowments = prosperon.endowments || {}; + +// Export resource info for debugging +prosperon.sdl_video = { + resources: resources, + next_id: function() { return next_id; }, + // Export the actor reference for external access + actor: $_ +}; + \ No newline at end of file diff --git a/source/jsffi.c b/source/jsffi.c index 517c0e6b..16eef912 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -190,7 +190,6 @@ TO = js2##TYPE(JS, val); \ JS_FreeValue(JS, val); } \ int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); } -static inline const char *js2cstring(JSContext *js, JSValue v) { return JS_ToCString(js,v); } JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); } double js2number(JSContext *js, JSValue v) { diff --git a/source/qjs_macros.h b/source/qjs_macros.h index e60302e2..c7940cc4 100644 --- a/source/qjs_macros.h +++ b/source/qjs_macros.h @@ -99,7 +99,7 @@ JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \ return TYPE##2js(js,js2##ID (js, self)->ENTRY); } \ #define QJSCLASS(TYPE, ...)\ -static JSClassID js_##TYPE##_id;\ +JSClassID js_##TYPE##_id;\ static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\ TYPE *n = JS_GetOpaque(val, js_##TYPE##_id);\ TracyCFreeN(n, #TYPE); \ diff --git a/source/qjs_sdl_video.c b/source/qjs_sdl_video.c index b3cb1a3f..a20e7806 100644 --- a/source/qjs_sdl_video.c +++ b/source/qjs_sdl_video.c @@ -2,9 +2,7 @@ #include "jsffi.h" #include "qjs_macros.h" #include "prosperon.h" -#include "render.h" #include "sprite.h" -#include "font.h" #include "transform.h" #include @@ -24,16 +22,9 @@ 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) +void SDL_Renderer_free(JSRuntime *rt, SDL_Renderer *r) { - SDL_DestroyRenderer(ctx->sdl); - free(ctx); + SDL_DestroyRenderer(r); } void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){ @@ -41,16 +32,17 @@ void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){ } QJSCLASS(SDL_Texture, - JS_SetPropertyStr(js, j, "width", number2js(js,n->w)); - JS_SetPropertyStr(js,j,"height",number2js(js,n->h)); + float w, h; + SDL_GetTextureSize(n, &w, &h); + JS_SetPropertyStr(js, j, "width", number2js(js,w)); + JS_SetPropertyStr(js,j,"height",number2js(js,h)); ) // Class definitions -QJSCLASS(renderer_ctx,) +QJSCLASS(SDL_Renderer,) 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); @@ -62,7 +54,10 @@ 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 SDL_Window *js2SDL_Window(JSContext *js, JSValue v); +extern JSValue SDL_Window2js(JSContext *js, SDL_Window *w); +extern SDL_Renderer *js2SDL_Renderer(JSContext *js, JSValue v); +extern JSValue SDL_Renderer2js(JSContext *js, SDL_Renderer *r); 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); @@ -72,44 +67,11 @@ 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); +extern JSClassID js_SDL_Surface_id; -// 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; -} +// Forward declarations for blend mode helpers +static JSValue blendmode2js(JSContext *js, SDL_BlendMode mode); +static SDL_BlendMode js2blendmode(JSContext *js, JSValue v); // Window constructor function static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) @@ -291,15 +253,12 @@ static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int // 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); + SDL_Renderer *renderer = SDL_CreateRenderer(win, NULL); + if (!renderer) { return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError()); } - SDL_SetRenderDrawBlendMode(ctx->sdl, SDL_BLENDMODE_BLEND); - return renderer_ctx2js(js,ctx); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + return SDL_Renderer2js(js,renderer); ) JSC_CCALL(SDL_Window_fullscreen, @@ -691,6 +650,45 @@ JSValue js_window_get_sizeInPixels(JSContext *js, JSValue self) return vec22js(js, (HMM_Vec2){width,height}); } +// Surface related +JSValue js_window_get_surface(JSContext *js, JSValue self) +{ + SDL_Window *w = js2SDL_Window(js,self); + SDL_Surface *surf = SDL_GetWindowSurface(w); + if (!surf) return JS_NULL; + return SDL_Surface2js(js, surf); +} + +JSValue js_window_updateSurface(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + if (!SDL_UpdateWindowSurface(w)) + return JS_ThrowReferenceError(js, "Failed to update window surface: %s", SDL_GetError()); + return JS_UNDEFINED; +} + +JSValue js_window_updateSurfaceRects(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + SDL_Window *w = js2SDL_Window(js,self); + + if (!JS_IsArray(js, argv[0])) + return JS_ThrowTypeError(js, "Expected array of rectangles"); + + int len = JS_ArrayLength(js, argv[0]); + SDL_Rect rects[len]; + + for (int i = 0; i < len; i++) { + JSValue val = JS_GetPropertyUint32(js, argv[0], i); + rect r = js2rect(js, val); + rects[i] = (SDL_Rect){r.x, r.y, r.w, r.h}; + JS_FreeValue(js, val); + } + + if (!SDL_UpdateWindowSurfaceRects(w, rects, len)) + return JS_ThrowReferenceError(js, "Failed to update window surface rects: %s", SDL_GetError()); + return JS_UNDEFINED; +} + JSValue js_window_get_flags(JSContext *js, JSValue self) { SDL_Window *w = js2SDL_Window(js,self); @@ -739,38 +737,33 @@ static const JSCFunctionListEntry js_SDL_Window_funcs[] = { JS_CGETSET_DEF("displayScale", js_window_get_displayScale, NULL), JS_CGETSET_DEF("sizeInPixels", js_window_get_sizeInPixels, NULL), JS_CGETSET_DEF("flags", js_window_get_flags, NULL), + JS_CGETSET_DEF("surface", js_window_get_surface, NULL), + MIST_FUNC_DEF(window, updateSurface, 0), + MIST_FUNC_DEF(window, updateSurfaceRects, 1), }; // Renderer functions JSC_CCALL(SDL_Renderer_clear, - SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl; + SDL_Renderer *renderer = js2SDL_Renderer(js,self); SDL_RenderClear(renderer); ) JSC_CCALL(SDL_Renderer_present, - SDL_Renderer *ren = js2renderer_ctx(js,self)->sdl; + SDL_Renderer *ren = js2SDL_Renderer(js,self); 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_Renderer *r = js2SDL_Renderer(js,self); 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_Renderer *r = js2SDL_Renderer(js,self); SDL_Surface *surf = NULL; if (!JS_IsUndefined(argv[0])) { rect rect = js2rect(js,argv[0]); @@ -784,8 +777,7 @@ JSC_CCALL(renderer_get_image, 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; + SDL_Renderer *r = js2SDL_Renderer(js,self); int len = JS_ArrayLength(js,argv[0]); if (len < 2) return JS_ThrowReferenceError(js, "Must provide at least 2 points to render."); @@ -794,18 +786,17 @@ JSC_CCALL(renderer_line, 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); + HMM_Vec2 pt = js2vec2(js,val); JS_FreeValue(js,val); - points[i] = renderer_world_to_screen(ctx, wpt); + points[i] = (SDL_FPoint){pt.x, pt.y}; } SDL_RenderLines(r,points,len); ) JSC_CCALL(renderer_point, - renderer_ctx *ctx = js2renderer_ctx(js, self); - SDL_Renderer *r = ctx->sdl; + SDL_Renderer *r = js2SDL_Renderer(js, self); if (JS_IsArray(js, argv[0])) { int len = JS_ArrayLength(js, argv[0]); @@ -813,37 +804,34 @@ JSC_CCALL(renderer_point, for (int i = 0; i < len; ++i) { JSValue val = JS_GetPropertyUint32(js, argv[0], i); - HMM_Vec2 w = js2vec2(js, val); + HMM_Vec2 pt = js2vec2(js, val); JS_FreeValue(js, val); - pts[i] = renderer_world_to_screen(ctx, w); + pts[i] = (SDL_FPoint){pt.x, pt.y}; } 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); + HMM_Vec2 pt = js2vec2(js, argv[0]); + SDL_RenderPoint(r, pt.x, pt.y); ) JSC_CCALL(renderer_texture, - renderer_ctx *ctx = js2renderer_ctx(js, self); + SDL_Renderer *r = js2SDL_Renderer(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); + SDL_RenderTextureRotated(r, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE); ) JSC_CCALL(renderer_rects, - renderer_ctx *ctx = js2renderer_ctx(js, self); - SDL_Renderer *r = ctx->sdl; + SDL_Renderer *r = js2SDL_Renderer(js, self); /* array-of-rectangles case */ if (JS_IsArray(js, argv[0])) { @@ -854,10 +842,9 @@ JSC_CCALL(renderer_rects, for (int i = 0; i < len; ++i) { JSValue val = JS_GetPropertyUint32(js, argv[0], i); - rect w = js2rect(js, val); + rect rect = js2rect(js, val); JS_FreeValue(js, val); - w = renderer_worldrect_to_screen(ctx, w); - rects[i] = w; + rects[i] = rect; } if (!SDL_RenderFillRects(r, rects, len)) @@ -866,16 +853,14 @@ JSC_CCALL(renderer_rects, } /* single-rect path */ - rect w = js2rect(js, argv[0]); - w = renderer_worldrect_to_screen(ctx, w); - if (!SDL_RenderFillRects(r, &w, 1)) + rect rect = js2rect(js, argv[0]); + if (!SDL_RenderFillRects(r, &rect, 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; + SDL_Renderer *r = js2SDL_Renderer(js,self); JSValue pos = JS_GetPropertyStr(js,argv[1], "pos"); JSValue color = JS_GetPropertyStr(js,argv[1], "color"); JSValue uv = JS_GetPropertyStr(js,argv[1], "uv"); @@ -891,20 +876,9 @@ JSC_CCALL(renderer_geometry, 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)) + if (!SDL_RenderGeometryRaw(r, tex, posdata, 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); @@ -912,8 +886,7 @@ JSC_CCALL(renderer_geometry, ) JSC_CCALL(renderer_geometry2, - renderer_ctx *ctx = js2renderer_ctx(js, self); - SDL_Renderer *r = ctx->sdl; + SDL_Renderer *r = js2SDL_Renderer(js, self); SDL_Texture *tex = js2SDL_Texture(js, argv[0]); @@ -938,10 +911,7 @@ JSC_CCALL(renderer_geometry2, 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].position = (SDL_FPoint){ p[0], p[1] }; verts[i].tex_coord = (SDL_FPoint){ u[0], u[1] }; verts[i].color = (SDL_FColor){ c[0], c[1], c[2], c[3] }; } @@ -989,7 +959,7 @@ static sprite js_getsprite(JSContext *js, JSValue sp) } JSC_CCALL(renderer_sprite, - renderer_ctx *ctx = js2renderer_ctx(js, self); + SDL_Renderer *r = js2SDL_Renderer(js, self); sprite sp = js_getsprite(js, argv[0]); SDL_Texture *tex; JSValue texture_val = JS_GetPropertyStr(js, sp.image, "texture"); @@ -1010,11 +980,11 @@ JSC_CCALL(renderer_sprite, 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); + SDL_FPoint origin = (SDL_FPoint){world_tl.x, world_tl.y}; + SDL_FPoint right = (SDL_FPoint){world_tr.x, world_tr.y}; + SDL_FPoint down = (SDL_FPoint){world_bl.x, world_bl.y}; - if (!SDL_RenderTextureAffine(ctx->sdl, tex, &uv, &origin, &right, &down)) + if (!SDL_RenderTextureAffine(r, tex, &uv, &origin, &right, &down)) return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError()); ) @@ -1049,69 +1019,355 @@ js2SDL_LogicalPresentation(JSContext *js, JSValue v){ 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])); -) +// Renderer getter/setter functions -JSC_CCALL(renderer_viewport, - SDL_Renderer *r = js2renderer_ctx(js,self)->sdl; - if (JS_IsUndefined(argv[0])) +// Target getter/setter +JSValue js_renderer_get_target(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_Texture *tex = SDL_GetRenderTarget(r); + if (!tex) return JS_NULL; + return SDL_Texture2js(js, tex); +} + +JSValue js_renderer_set_target(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(val) || JS_IsUndefined(val)) + SDL_SetRenderTarget(r, NULL); + else { + SDL_Texture *tex = js2SDL_Texture(js,val); + SDL_SetRenderTarget(r,tex); + } + return JS_UNDEFINED; +} + +// Logical presentation getter/setter +JSValue js_renderer_get_logicalPresentation(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + int w, h; + SDL_RendererLogicalPresentation mode; + SDL_GetRenderLogicalPresentation(r, &w, &h, &mode); + JSValue ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "width", number2js(js, w)); + JS_SetPropertyStr(js, ret, "height", number2js(js, h)); + JS_SetPropertyStr(js, ret, "mode", logicalpresentation2js(js, mode)); + return ret; +} + +JSValue js_renderer_set_logicalPresentation(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + double w = js_getnum_str(js, val, "width"); + double h = js_getnum_str(js, val, "height"); + JSValue mode_val = JS_GetPropertyStr(js, val, "mode"); + SDL_RendererLogicalPresentation mode = js2SDL_LogicalPresentation(js, mode_val); + JS_FreeValue(js, mode_val); + SDL_SetRenderLogicalPresentation(r, w, h, mode); + return JS_UNDEFINED; +} + +// Viewport getter/setter +JSValue js_renderer_get_viewport(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect viewport; + SDL_GetRenderViewport(r, &viewport); + return rect2js(js, viewport); +} + +JSValue js_renderer_set_viewport(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(val) || JS_IsUndefined(val)) SDL_SetRenderViewport(r,NULL); else { - rect view = js2rect(js,argv[0]); + rect view = js2rect(js,val); SDL_SetRenderViewport(r,&view); } -) + return JS_UNDEFINED; +} -JSC_CCALL(renderer_clip, - SDL_Renderer *r = js2renderer_ctx(js,self)->sdl; - if (JS_IsUndefined(argv[0])) +// Clip rect getter/setter +JSValue js_renderer_get_clipRect(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect clip; + SDL_GetRenderClipRect(r, &clip); + return rect2js(js, clip); +} + +JSValue js_renderer_set_clipRect(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + if (JS_IsNull(val) || JS_IsUndefined(val)) SDL_SetRenderClipRect(r,NULL); else { - rect view = js2rect(js,argv[0]); - SDL_SetRenderClipRect(r,&view); + rect clip = js2rect(js,val); + SDL_SetRenderClipRect(r,&clip); } -) + return JS_UNDEFINED; +} -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); -) +// Scale getter/setter +JSValue js_renderer_get_scale(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + float x, y; + SDL_GetRenderScale(r, &x, &y); + return vec22js(js, (HMM_Vec2){x, y}); +} -JSC_CCALL(renderer_vsync, - SDL_Renderer *r = js2renderer_ctx(js,self)->sdl; - return JS_NewBool(js, SDL_SetRenderVSync(r,js2number(js,argv[0]))); -) +JSValue js_renderer_set_scale(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + HMM_Vec2 scale = js2vec2(js,val); + SDL_SetRenderScale(r, scale.x, scale.y); + return JS_UNDEFINED; +} -// This returns the coordinates inside the -JSC_CCALL(renderer_coords, - SDL_Renderer *r = js2renderer_ctx(js,self)->sdl; +// Draw color getter/setter (float version) +JSValue js_renderer_get_drawColor(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + float red, green, blue, alpha; + SDL_GetRenderDrawColorFloat(r, &red, &green, &blue, &alpha); + JSValue ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "r", number2js(js, red)); + JS_SetPropertyStr(js, ret, "g", number2js(js, green)); + JS_SetPropertyStr(js, ret, "b", number2js(js, blue)); + JS_SetPropertyStr(js, ret, "a", number2js(js, alpha)); + return ret; +} + +JSValue js_renderer_set_drawColor(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + colorf color = js2color(js,val); + SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a); + return JS_UNDEFINED; +} + +// Color scale getter/setter +JSValue js_renderer_get_colorScale(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + float scale; + SDL_GetRenderColorScale(r, &scale); + return number2js(js, scale); +} + +JSValue js_renderer_set_colorScale(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + float scale = js2number(js,val); + SDL_SetRenderColorScale(r, scale); + return JS_UNDEFINED; +} + +// Draw blend mode getter/setter +JSValue js_renderer_get_drawBlendMode(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_BlendMode mode; + SDL_GetRenderDrawBlendMode(r, &mode); + return blendmode2js(js, mode); +} + +JSValue js_renderer_set_drawBlendMode(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_BlendMode mode = js2blendmode(js,val); + SDL_SetRenderDrawBlendMode(r, mode); + return JS_UNDEFINED; +} + +// VSync getter/setter +JSValue js_renderer_get_vsync(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + int vsync; + SDL_GetRenderVSync(r, &vsync); + return number2js(js, vsync); +} + +JSValue js_renderer_set_vsync(JSContext *js, JSValue self, JSValue val) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + int vsync = js2number(js,val); + SDL_SetRenderVSync(r, vsync); + return JS_UNDEFINED; +} + +// Read-only properties +JSValue js_renderer_get_window(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_Window *win = SDL_GetRenderWindow(r); + if (!win) return JS_NULL; + return SDL_Window2js(js, win); +} + +JSValue js_renderer_get_name(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + const char *name = SDL_GetRendererName(r); + return JS_NewString(js, name ? name : ""); +} + +JSValue js_renderer_get_outputSize(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + int w, h; + SDL_GetRenderOutputSize(r, &w, &h); + return vec22js(js, (HMM_Vec2){w, h}); +} + +JSValue js_renderer_get_currentOutputSize(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + int w, h; + SDL_GetCurrentRenderOutputSize(r, &w, &h); + return vec22js(js, (HMM_Vec2){w, h}); +} + +JSValue js_renderer_get_logicalPresentationRect(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect presentRect; + SDL_GetRenderLogicalPresentationRect(r, &presentRect); + return rect2js(js, presentRect); +} + +JSValue js_renderer_get_safeArea(JSContext *js, JSValue self) +{ + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect safe; + SDL_GetRenderSafeArea(r, &safe); + return rect2js(js, safe); +} + +// Converts window coordinates to renderer coordinates +JSC_CCALL(renderer_coordsFromWindow, + SDL_Renderer *r = js2SDL_Renderer(js,self); 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]); +// Converts renderer coordinates to window coordinates +JSC_CCALL(renderer_coordsToWindow, + SDL_Renderer *r = js2SDL_Renderer(js,self); + HMM_Vec2 pos, coord; + pos = js2vec2(js,argv[0]); + SDL_RenderCoordinatesToWindow(r,pos.x,pos.y, &coord.x, &coord.y); + return vec22js(js,coord); ) -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); - } +// Flush any pending rendering commands +JSC_CCALL(renderer_flush, + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_FlushRenderer(r); ) -static const JSCFunctionListEntry js_renderer_ctx_funcs[] = { - MIST_FUNC_DEF(SDL_Renderer, draw_color, 1), +// Additional rendering functions +JSC_CCALL(renderer_rect, + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect rect = js2rect(js, argv[0]); + if (!SDL_RenderRect(r, &rect)) + return JS_ThrowReferenceError(js, "SDL_RenderRect: %s", SDL_GetError()); +) + +JSC_CCALL(renderer_fillRect, + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect rect = js2rect(js, argv[0]); + if (!SDL_RenderFillRect(r, &rect)) + return JS_ThrowReferenceError(js, "SDL_RenderFillRect: %s", SDL_GetError()); +) + +// Render a single line from point A to point B +JSC_CCALL(renderer_lineTo, + SDL_Renderer *r = js2SDL_Renderer(js,self); + HMM_Vec2 a = js2vec2(js, argv[0]); + HMM_Vec2 b = js2vec2(js, argv[1]); + if (!SDL_RenderLine(r, a.x, a.y, b.x, b.y)) + return JS_ThrowReferenceError(js, "SDL_RenderLine: %s", SDL_GetError()); +) + +// Check if clipping is enabled +JSC_CCALL(renderer_clipEnabled, + SDL_Renderer *r = js2SDL_Renderer(js,self); + return JS_NewBool(js, SDL_RenderClipEnabled(r)); +) + +// Render texture with 9-slice grid +JSC_CCALL(renderer_texture9Grid, + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_Texture *tex = js2SDL_Texture(js, argv[0]); + rect src_rect = js2rect(js, argv[1]); + double left_w = js2number(js,argv[2]); + double right_w = js2number(js,argv[3]); + double top_h = js2number(js,argv[4]); + double bottom_h = js2number(js, argv[5]); + double tex_scale = js2number(js, argv[6]); + rect dst_rect = js2rect(js, argv[7]); + + if (!SDL_RenderTexture9Grid(r, tex, &src_rect, left_w, right_w, top_h, bottom_h, tex_scale, &dst_rect)) + return JS_ThrowReferenceError(js, "Failed to render 9-grid texture: %s", SDL_GetError()); +) + +// Render tiled texture +JSC_CCALL(renderer_textureTiled, + SDL_Renderer *r = js2SDL_Renderer(js,self); + SDL_Texture *tex = js2SDL_Texture(js, argv[0]); + + rect src_rect = {0}; + if (argc > 1 && !JS_IsNull(argv[1]) && !JS_IsUndefined(argv[1])) + src_rect = js2rect(js, argv[1]); + + float scale = 1.0f; + if (argc > 2) + scale = js2number(js, argv[2]); + + rect dst_rect = {0}; + if (argc > 3 && !JS_IsNull(argv[3]) && !JS_IsUndefined(argv[3])) + dst_rect = js2rect(js, argv[3]); + + if (!SDL_RenderTextureTiled(r, tex, + (src_rect.w > 0 && src_rect.h > 0) ? &src_rect : NULL, + scale, + (dst_rect.w > 0 && dst_rect.h > 0) ? &dst_rect : NULL)) + return JS_ThrowReferenceError(js, "Failed to render tiled texture: %s", SDL_GetError()); +) + +// Render debug text +JSC_CCALL(renderer_debugText, + SDL_Renderer *r = js2SDL_Renderer(js,self); + HMM_Vec2 pos = js2vec2(js, argv[0]); + const char *text = JS_ToCString(js, argv[1]); + int ren = SDL_RenderDebugText(r, pos.x, pos.y, text); + JS_FreeCString(js,text); + if (!ren) + return JS_ThrowReferenceError(js, "Failed to render debug text: %s", SDL_GetError()); +) + +// Read pixels from renderer +JSC_CCALL(renderer_readPixels, + SDL_Renderer *r = js2SDL_Renderer(js,self); + rect read_rect = {0}; + if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) + read_rect = js2rect(js, argv[0]); + + SDL_Surface *surf = SDL_RenderReadPixels(r, + (read_rect.w > 0 && read_rect.h > 0) ? &read_rect : NULL); + if (!surf) + return JS_ThrowReferenceError(js, "Failed to read pixels: %s", SDL_GetError()); + return SDL_Surface2js(js, surf); +) + +static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = { MIST_FUNC_DEF(SDL_Renderer, present, 0), MIST_FUNC_DEF(SDL_Renderer, clear, 0), MIST_FUNC_DEF(renderer, line, 1), @@ -1123,34 +1379,377 @@ static const JSCFunctionListEntry js_renderer_ctx_funcs[] = { MIST_FUNC_DEF(renderer, sprite, 1), MIST_FUNC_DEF(renderer, load_texture, 1), MIST_FUNC_DEF(renderer, get_image, 1), + MIST_FUNC_DEF(renderer, coordsFromWindow, 1), + MIST_FUNC_DEF(renderer, coordsToWindow, 1), + MIST_FUNC_DEF(renderer, flush, 0), - 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), + // Additional shape drawing functions + MIST_FUNC_DEF(renderer, rect, 1), + MIST_FUNC_DEF(renderer, fillRect, 1), + MIST_FUNC_DEF(renderer, lineTo, 2), + MIST_FUNC_DEF(renderer, debugText, 2), + MIST_FUNC_DEF(renderer, readPixels, 1), + MIST_FUNC_DEF(renderer, clipEnabled, 0), + MIST_FUNC_DEF(renderer, texture9Grid, 6), + MIST_FUNC_DEF(renderer, textureTiled, 4), - MIST_FUNC_DEF(renderer, coords, 1), - MIST_FUNC_DEF(renderer, camera, 2), - MIST_FUNC_DEF(renderer, target, 1), + // Getter/setter properties + CGETSET_ADD(renderer, target), + CGETSET_ADD(renderer, logicalPresentation), + CGETSET_ADD(renderer, viewport), + CGETSET_ADD(renderer, clipRect), + CGETSET_ADD(renderer, scale), + CGETSET_ADD(renderer, drawColor), + CGETSET_ADD(renderer, colorScale), + CGETSET_ADD(renderer, drawBlendMode), + CGETSET_ADD(renderer, vsync), + + // Read-only properties + JS_CGETSET_DEF("window", js_renderer_get_window, NULL), + JS_CGETSET_DEF("name", js_renderer_get_name, NULL), + JS_CGETSET_DEF("outputSize", js_renderer_get_outputSize, NULL), + JS_CGETSET_DEF("currentOutputSize", js_renderer_get_currentOutputSize, NULL), + JS_CGETSET_DEF("logicalPresentationRect", js_renderer_get_logicalPresentationRect, NULL), + JS_CGETSET_DEF("safeArea", js_renderer_get_safeArea, NULL), }; extern SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v); +// Blend mode helper +static JSValue blendmode2js(JSContext *js, SDL_BlendMode mode) { + switch(mode) { + case SDL_BLENDMODE_NONE: return JS_NewString(js, "none"); + case SDL_BLENDMODE_BLEND: return JS_NewString(js, "blend"); + case SDL_BLENDMODE_BLEND_PREMULTIPLIED: return JS_NewString(js, "blend_premultiplied"); + case SDL_BLENDMODE_ADD: return JS_NewString(js, "add"); + case SDL_BLENDMODE_ADD_PREMULTIPLIED: return JS_NewString(js, "add_premultiplied"); + case SDL_BLENDMODE_MOD: return JS_NewString(js, "mod"); + case SDL_BLENDMODE_MUL: return JS_NewString(js, "mul"); + default: return JS_NewString(js, "invalid"); + } +} + +static SDL_BlendMode js2blendmode(JSContext *js, JSValue v) { + if (JS_IsNumber(v)) { + return js2number(js, v); + } + const char *str = JS_ToCString(js, v); + SDL_BlendMode mode = SDL_BLENDMODE_BLEND; + if (str) { + if (strcmp(str, "none") == 0) mode = SDL_BLENDMODE_NONE; + else if (strcmp(str, "blend") == 0) mode = SDL_BLENDMODE_BLEND; + else if (strcmp(str, "blend_premultiplied") == 0) mode = SDL_BLENDMODE_BLEND_PREMULTIPLIED; + else if (strcmp(str, "add") == 0) mode = SDL_BLENDMODE_ADD; + else if (strcmp(str, "add_premultiplied") == 0) mode = SDL_BLENDMODE_ADD_PREMULTIPLIED; + else if (strcmp(str, "mod") == 0) mode = SDL_BLENDMODE_MOD; + else if (strcmp(str, "mul") == 0) mode = SDL_BLENDMODE_MUL; + JS_FreeCString(js, str); + } + return mode; +} + +// Convert pixel format string to SDL_PixelFormat +static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v) { + if (JS_IsNumber(v)) { + return js2number(js, v); + } + const char *str = JS_ToCString(js, v); + SDL_PixelFormat fmt = SDL_PIXELFORMAT_RGBA8888; + if (str) { + if (strcmp(str, "rgba8888") == 0) fmt = SDL_PIXELFORMAT_RGBA8888; + else if (strcmp(str, "bgra8888") == 0) fmt = SDL_PIXELFORMAT_BGRA8888; + else if (strcmp(str, "argb8888") == 0) fmt = SDL_PIXELFORMAT_ARGB8888; + else if (strcmp(str, "abgr8888") == 0) fmt = SDL_PIXELFORMAT_ABGR8888; + else if (strcmp(str, "rgba32") == 0) fmt = SDL_PIXELFORMAT_RGBA32; + else if (strcmp(str, "bgra32") == 0) fmt = SDL_PIXELFORMAT_BGRA32; + else if (strcmp(str, "argb32") == 0) fmt = SDL_PIXELFORMAT_ARGB32; + else if (strcmp(str, "abgr32") == 0) fmt = SDL_PIXELFORMAT_ABGR32; + JS_FreeCString(js, str); + } + return fmt; +} + +// Texture constructor +static JSValue js_texture_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) +{ + if (argc < 2) + return JS_ThrowTypeError(js, "Texture constructor requires renderer and either surface or object with width/height/format"); + + SDL_Renderer *renderer = js2SDL_Renderer(js, argv[0]); + if (!renderer) + return JS_ThrowReferenceError(js, "Invalid renderer"); + + SDL_Texture *tex = NULL; + + // Check if second arg is a surface + if (JS_GetClassID(argv[1]) == js_SDL_Surface_id) { + SDL_Surface *surf = js2SDL_Surface(js, argv[1]); + tex = SDL_CreateTextureFromSurface(renderer, surf); + if (!tex) + return JS_ThrowReferenceError(js, "Failed to create texture from surface: %s", SDL_GetError()); + } + else if (JS_IsObject(argv[1])) { + // Create from properties object + JSValue obj = argv[1]; + + int width = js_getnum_str(js, obj, "width"); + int height = js_getnum_str(js, obj, "height"); + + JSValue format_val = JS_GetPropertyStr(js, obj, "format"); + SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888; + if (!JS_IsUndefined(format_val)) + format = js2pixelformat(js, format_val); + JS_FreeValue(js, format_val); + + // Check for pixels data + JSValue pixels_val = JS_GetPropertyStr(js, obj, "pixels"); + if (!JS_IsUndefined(pixels_val)) { + // Create surface first, then texture + size_t size; + uint8_t *pixels = JS_GetArrayBuffer(js, &size, pixels_val); + + if (!pixels) { + JS_FreeValue(js, pixels_val); + return JS_ThrowTypeError(js, "pixels must be an ArrayBuffer"); + } + + int pitch = js_getnum_str(js, obj, "pitch"); + if (pitch == 0) { + // Calculate pitch from format + int bpp = SDL_BYTESPERPIXEL(format); + pitch = width * bpp; + } + + SDL_Surface *surf = SDL_CreateSurfaceFrom(width, height, format, pixels, pitch); + if (!surf) { + JS_FreeValue(js, pixels_val); + return JS_ThrowReferenceError(js, "Failed to create surface: %s", SDL_GetError()); + } + + tex = SDL_CreateTextureFromSurface(renderer, surf); + SDL_DestroySurface(surf); + JS_FreeValue(js, pixels_val); + + if (!tex) + return JS_ThrowReferenceError(js, "Failed to create texture: %s", SDL_GetError()); + } + else { + // Create empty texture + tex = SDL_CreateTexture(renderer, format, SDL_TEXTUREACCESS_STATIC, width, height); + if (!tex) + return JS_ThrowReferenceError(js, "Failed to create texture: %s", SDL_GetError()); + } + } + else { + return JS_ThrowTypeError(js, "Second argument must be a surface or object with width/height/format"); + } + + JSValue tex_obj = SDL_Texture2js(js, tex); + return tex_obj; +} + static const JSCFunctionListEntry js_sdl_video_funcs[] = { - // No regular functions, window is now a constructor + JS_PROP_INT32_DEF("BLENDMODE_NONE", SDL_BLENDMODE_NONE, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("BLENDMODE_BLEND", SDL_BLENDMODE_BLEND, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("BLENDMODE_ADD", SDL_BLENDMODE_ADD, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("BLENDMODE_MOD", SDL_BLENDMODE_MOD, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("BLENDMODE_MUL", SDL_BLENDMODE_MUL, JS_PROP_CONFIGURABLE), }; -JSC_CCALL(texture_mode, +// Texture getter/setter functions + +// Alpha mod getter/setter +JSValue js_texture_get_alphaMod(JSContext *js, JSValue self) +{ SDL_Texture *tex = js2SDL_Texture(js,self); - SDL_ScaleMode mode = js2SDL_ScaleMode(js,argv[0]); - SDL_SetTextureScaleMode(tex,mode); + float alpha; + SDL_GetTextureAlphaModFloat(tex, &alpha); + return number2js(js, alpha); +} + +JSValue js_texture_set_alphaMod(JSContext *js, JSValue self, JSValue val) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + float alpha = js2number(js,val); + SDL_SetTextureAlphaModFloat(tex, alpha); + return JS_UNDEFINED; +} + +// Color mod getter/setter +JSValue js_texture_get_colorMod(JSContext *js, JSValue self) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + float r, g, b; + SDL_GetTextureColorModFloat(tex, &r, &g, &b); + JSValue ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "r", number2js(js, r)); + JS_SetPropertyStr(js, ret, "g", number2js(js, g)); + JS_SetPropertyStr(js, ret, "b", number2js(js, b)); + return ret; +} + +JSValue js_texture_set_colorMod(JSContext *js, JSValue self, JSValue val) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + colorf color = js2color(js,val); + SDL_SetTextureColorModFloat(tex, color.r, color.g, color.b); + return JS_UNDEFINED; +} + +// Blend mode getter/setter +JSValue js_texture_get_blendMode(JSContext *js, JSValue self) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_BlendMode mode; + SDL_GetTextureBlendMode(tex, &mode); + return blendmode2js(js, mode); +} + +JSValue js_texture_set_blendMode(JSContext *js, JSValue self, JSValue val) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_BlendMode mode = js2blendmode(js,val); + SDL_SetTextureBlendMode(tex, mode); + return JS_UNDEFINED; +} + +// Scale mode getter/setter +JSValue js_texture_get_scaleMode(JSContext *js, JSValue self) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_ScaleMode mode; + SDL_GetTextureScaleMode(tex, &mode); + const char *str = "nearest"; + if (mode == SDL_SCALEMODE_LINEAR) str = "linear"; + return JS_NewString(js, str); +} + +JSValue js_texture_set_scaleMode(JSContext *js, JSValue self, JSValue val) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_ScaleMode mode = js2SDL_ScaleMode(js,val); + SDL_SetTextureScaleMode(tex, mode); + return JS_UNDEFINED; +} + +// Size getter (read-only) +JSValue js_texture_get_size(JSContext *js, JSValue self) +{ + SDL_Texture *tex = js2SDL_Texture(js,self); + float w, h; + SDL_GetTextureSize(tex, &w, &h); + return vec22js(js, (HMM_Vec2){w, h}); +} + +// Update texture data +JSC_CCALL(texture_update, + SDL_Texture *tex = js2SDL_Texture(js,self); + rect update_rect = {0}; + void *pixels = NULL; + int pitch = 0; + + if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) + update_rect = js2rect(js, argv[0]); + + if (argc >= 2) { + size_t size; + pixels = JS_GetArrayBuffer(js, &size, argv[1]); + if (!pixels) + return JS_ThrowTypeError(js, "Second argument must be an ArrayBuffer"); + } + + if (argc >= 3) + pitch = js2number(js, argv[2]); + + if (!SDL_UpdateTexture(tex, + (update_rect.w > 0 && update_rect.h > 0) ? &update_rect : NULL, + pixels, pitch)) + return JS_ThrowReferenceError(js, "Failed to update texture: %s", SDL_GetError()); +) + +// Lock texture for direct pixel access +JSC_CCALL(texture_lock, + SDL_Texture *tex = js2SDL_Texture(js,self); + rect lock_rect = {0}; + void *pixels; + int pitch; + + if (argc >= 1 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0])) + lock_rect = js2rect(js, argv[0]); + + if (!SDL_LockTexture(tex, + (lock_rect.w > 0 && lock_rect.h > 0) ? &lock_rect : NULL, + &pixels, &pitch)) + return JS_ThrowReferenceError(js, "Failed to lock texture: %s", SDL_GetError()); + + // Create ArrayBuffer view of the locked pixels + float w, h; + SDL_GetTextureSize(tex, &w, &h); + int height = lock_rect.h > 0 ? lock_rect.h : (int)h; + size_t buffer_size = pitch * height; + + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "pixels", JS_NewArrayBuffer(js, pixels, buffer_size, NULL, NULL, 0)); + JS_SetPropertyStr(js, ret, "pitch", number2js(js, pitch)); + return ret; +) + +// Unlock texture +JSC_CCALL(texture_unlock, + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_UnlockTexture(tex); +) + +// Query texture info +JSC_CCALL(texture_query, + SDL_Texture *tex = js2SDL_Texture(js,self); + SDL_PixelFormat format; + int access, w, h; + + if (!SDL_GetTextureProperties(tex)) + return JS_ThrowReferenceError(js, "Failed to query texture"); + + ret = JS_NewObject(js); + // TODO: Get these from properties once SDL3 API is clear + return ret; ) static const JSCFunctionListEntry js_SDL_Texture_funcs[] = { - MIST_FUNC_DEF(texture, mode, 1), + CGETSET_ADD(texture, alphaMod), + CGETSET_ADD(texture, colorMod), + CGETSET_ADD(texture, blendMode), + CGETSET_ADD(texture, scaleMode), + JS_CGETSET_DEF("size", js_texture_get_size, NULL), + MIST_FUNC_DEF(texture, update, 3), + MIST_FUNC_DEF(texture, lock, 1), + MIST_FUNC_DEF(texture, unlock, 0), + MIST_FUNC_DEF(texture, query, 0), }; +// Additional renderer functions + +// Create and destroy window/renderer pair +JSC_CCALL(sdl_createWindowAndRenderer, + const char *title = JS_ToCString(js, argv[0]); + int width = js2number(js, argv[1]); + int height = js2number(js, argv[2]); + SDL_WindowFlags flags = argc > 3 ? js2number(js, argv[3]) : 0; + + SDL_Window *window; + SDL_Renderer *renderer; + + if (!SDL_CreateWindowAndRenderer(title, width, height, flags, &window, &renderer)) { + JS_FreeCString(js,title); + return JS_ThrowReferenceError(js, "Failed to create window and renderer: %s", SDL_GetError()); + } + + JS_FreeCString(js,title); + ret = JS_NewObject(js); + JS_SetPropertyStr(js, ret, "window", SDL_Window2js(js, window)); + JS_SetPropertyStr(js, ret, "renderer", SDL_Renderer2js(js, renderer)); + return ret; +) + // Hook function to set up endowments for the video actor static void video_actor_hook(JSContext *js) { // Get prosperon object @@ -1159,7 +1758,7 @@ static void video_actor_hook(JSContext *js) { // Initialize classes QJSCLASSPREP_FUNCS(SDL_Window) - QJSCLASSPREP_FUNCS(renderer_ctx) + QJSCLASSPREP_FUNCS(SDL_Renderer) QJSCLASSPREP_FUNCS(SDL_Texture) JS_FreeValue(js, c_types); @@ -1170,6 +1769,12 @@ static void video_actor_hook(JSContext *js) { // Set prototype on constructor JS_SetConstructor(js, window_ctor, SDL_Window_proto); + // Create texture constructor + JSValue texture_ctor = JS_NewCFunction2(js, js_texture_constructor, "texture", 2, JS_CFUNC_constructor, 0); + + // Set prototype on constructor + JS_SetConstructor(js, texture_ctor, SDL_Texture_proto); + // Get or create endowments object JSValue endowments = JS_GetPropertyStr(js, prosperon, "endowments"); if (JS_IsUndefined(endowments)) { @@ -1177,8 +1782,13 @@ static void video_actor_hook(JSContext *js) { JS_SetPropertyStr(js, prosperon, "endowments", JS_DupValue(js, endowments)); } - // Set window constructor in endowments + // Set constructors in endowments JS_SetPropertyStr(js, endowments, "window", window_ctor); + JS_SetPropertyStr(js, endowments, "texture", texture_ctor); + + // Add utility function + JS_SetPropertyStr(js, endowments, "createWindowAndRenderer", + JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4)); JS_FreeValue(js, endowments); JS_FreeValue(js, prosperon); @@ -1190,7 +1800,7 @@ JSValue js_sdl_video_use(JSContext *js) { // Generate a unique ID for the video actor char id[64]; - snprintf(id, sizeof(id), "video_%d", SDL_GetTicks()); + snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks()); printf("id is %s\n", id); @@ -1206,9 +1816,6 @@ JSValue js_sdl_video_use(JSContext *js) { int argc = 8; // Create the actor with the hook to set up endowments - prosperon_rt *rt = create_actor(argc, (char**)argv, video_actor_hook); + 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; } diff --git a/tests/sdl_video.js b/tests/sdl_video.js index e7b26a9f..266de047 100644 --- a/tests/sdl_video.js +++ b/tests/sdl_video.js @@ -1,11 +1,28 @@ var video = use('sdl_video') -var myid -var myrender +var window +var renderer -send({__ACTORDATA__:{id:video}}, {kind:"window", op: "create"}, ({id}) => { - myid = id +var act = {__ACTORDATA__:{id:video}} + +function handle_response(data) +{ + if (data.error) + console.log(json.encode(data)) +} + +send(act, {kind:"window", op: "create"}, ({id}) => { + window = id console.log(`made window id ${id}`) + + send(act, {kind:"window", op:"makeRenderer", id: window}, ({id}) => { + renderer = id + console.log(`made renderer with id ${id}`) + + send(act, {kind:"renderer", id: renderer, op:"set", prop: "drawColor", value:[0.6,0.4,0.5,1]}, handle_response) + send(act, {kind:"renderer", id: renderer, op:"clear"}, handle_response) + send(act, {kind:"renderer", id: renderer, op: "present"}, handle_response) + }) }) $_.delay($_.stop, 2) \ No newline at end of file