move surface to its own module

This commit is contained in:
2025-05-26 15:59:28 -05:00
parent af21e10e97
commit a63e5c5b55
11 changed files with 642 additions and 421 deletions

View File

@@ -195,7 +195,7 @@ sources = []
src += [ src += [
'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c',
'render.c','simplex.c','spline.c', 'transform.c','prosperon.c', 'wildmatch.c', 'render.c','simplex.c','spline.c', 'transform.c','prosperon.c', 'wildmatch.c',
'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.c', 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_os.c', 'qjs_actor.c',
'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c' 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c'
] ]
# quirc src # quirc src

View File

@@ -1,6 +1,8 @@
// SDL Video Actor // SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations // This actor runs on the main thread and handles all SDL video operations
var surface = use('surface')
// Default window configuration - documents all available window options // Default window configuration - documents all available window options
var default_window = { var default_window = {
// Basic properties // Basic properties
@@ -431,11 +433,11 @@ function handle_renderer(msg) {
} }
// Load from raw surface object (for graphics module) // Load from raw surface object (for graphics module)
else if (msg.data.surface) { else if (msg.data.surface) {
tex = ren.load_texture(msg.data); tex = ren.load_texture(new surface(msg.data));
} }
// Direct surface data // Direct surface data
else if (msg.data.width && msg.data.height) { else if (msg.data.width && msg.data.height) {
tex = ren.load_texture(msg.data); tex = ren.load_texture(new surface(msg.data));
} }
else { else {
return {error: "Must provide surface_id or surface data"}; return {error: "Must provide surface_id or surface data"};
@@ -446,25 +448,8 @@ function handle_renderer(msg) {
resources.texture[tex_id] = tex; resources.texture[tex_id] = tex;
return { return {
id: tex_id, id: tex_id,
width: tex.width,
height: tex.height
}; };
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': case 'flush':
ren.flush(); ren.flush();
return {success: true}; return {success: true};
@@ -539,6 +524,7 @@ function handle_texture(msg) {
}); });
} }
else { else {
console.log(json.encode(msg.data))
return {error: "Must provide either surface_id or width/height"}; return {error: "Must provide either surface_id or width/height"};
} }

View File

@@ -34,6 +34,8 @@ var wota = hidden.wota
var console_mod = hidden.console var console_mod = hidden.console
var use_embed = hidden.use_embed var use_embed = hidden.use_embed
var use_dyn = hidden.use_dyn var use_dyn = hidden.use_dyn
var enet = hidden.enet
var nota = hidden.nota
// Strip hidden from prosperon so nothing else can access it // Strip hidden from prosperon so nothing else can access it
delete prosperon.hidden delete prosperon.hidden
@@ -150,37 +152,21 @@ console.error = function error(e) {
if (e instanceof Error) if (e instanceof Error)
pprint(`${e.name} : ${e.message} pprint(`${e.name} : ${e.message}
${e.stack}`, 4) ${e.stack}`, 4)
else else {
pprint(e,4) var stack = new Error()
pprint(`${e}
${stack.stack}`,4)
}
} }
console.panic = function panic(e) { console.panic = function panic(e) {
pprint(e, 5) pprint(e, 5)
os.quit() os.quit()
} }
console.assert = function assert(op, str = `assertion failed [value '${op}']`) { console.assert = function assert(op, str = `assertion failed [value '${op}']`) {
if (!op) console.panic(str) if (!op) console.panic(str)
} }
//os.on('uncaught_exception', function(e) { console.error(e); })
console[prosperon.DOC] = {
doc: "The console object provides various logging, debugging, and output methods.",
spam: "Output a spam-level message for very verbose logging.",
debug: "Output a debug-level message.",
info: "Output info level message.",
warn: "Output warn level message.",
error: "Output error level message, and print stacktrace.",
panic: "Output a panic-level message and exit the program.",
assert: "If the condition is false, print an error and panic.",
critical: "Output critical level message, and exit game immediately.",
write: "Write raw text to console.",
say: "Write raw text to console, plus a newline.",
log: "Output directly to in game console.",
level: "Set level to output logging to console.",
stack: "Output a stacktrace to console.",
clear: "Clear console."
}
var BASEPATH = 'scripts/core/base.js' var BASEPATH = 'scripts/core/base.js'
var script = io.slurp(BASEPATH) var script = io.slurp(BASEPATH)
var fnname = "base" var fnname = "base"
@@ -316,11 +302,9 @@ script = `(function ${fnname}() { ${script}; })`
} }
*/ */
var enet = use('enet')
var util = use('util') var util = use('util')
var math = use('math') var math = use('math')
var crypto = use('crypto') var crypto = use('crypto')
var nota = use('nota')
var dying = false var dying = false

View File

@@ -505,10 +505,10 @@ draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], s
// Calculate source rectangle from image.rect (UV coords) // Calculate source rectangle from image.rect (UV coords)
var src_rect = { var src_rect = {
x: image.rect.x * texture.width, x: image.rect.x * image.width,
y: image.rect.y * texture.height, y: image.rect.y * image.height,
width: image.rect.width * texture.width, width: image.rect.width * image.width,
height: image.rect.height * texture.height height: image.rect.height * image.height
} }
// Handle flipping // Handle flipping

View File

@@ -21,65 +21,84 @@ var LOADING = Symbol()
var cache = new Map() var cache = new Map()
// When creating an image, do an Object.create(graphics.Image) // Image constructor function
graphics.Image = { graphics.Image = function(surfaceData) {
get gpu() { // Initialize private properties
this[LASTUSE] = os.now(); this[CPU] = surfaceData || undefined;
if (!this[GPU] && !this[LOADING]) { this[GPU] = undefined;
this[LOADING] = true; this[LOADING] = false;
var self = this; this[LASTUSE] = os.now();
this.rect = {x:0, y:0, width:1, height:1};
// Send message to load texture }
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: this[CPU]
}, function(response) {
if (response.error) {
console.error("Failed to load texture:", response.error);
self[LOADING] = false;
} else {
self[GPU] = response;
decorate_rect_px(self);
self[LOADING] = false;
}
});
}
return this[GPU]
},
get texture() { return this.gpu },
get cpu() { // Define getters and methods on the prototype
this[LASTUSE] = os.now(); Object.defineProperties(graphics.Image.prototype, {
// Note: Reading texture back from GPU requires async operation gpu: {
// For now, return the CPU data if available get: function() {
return this[CPU] this[LASTUSE] = os.now();
if (!this[GPU] && !this[LOADING]) {
this[LOADING] = true;
var self = this;
// Send message to load texture
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: this[CPU]
}, function(response) {
if (response.error) {
console.error("Failed to load texture:", response.error)
self[LOADING] = false;
} else {
self[GPU] = response;
decorate_rect_px(self);
self[LOADING] = false;
}
});
}
return this[GPU]
}
}, },
get surface() { return this.cpu }, texture: {
get: function() { return this.gpu }
get width() { },
if (this[GPU]) return this[GPU].width
if (this[CPU]) return this[CPU].width cpu: {
return 0 get: function() {
this[LASTUSE] = os.now();
// Note: Reading texture back from GPU requires async operation
// For now, return the CPU data if available
return this[CPU]
}
}, },
get height() { surface: {
if (this[GPU]) return this[GPU].height get: function() { return this.cpu }
if (this[CPU]) return this[CPU].height
return 0
}, },
unload_gpu() { width: {
this[GPU] = undefined get: function() {
return this[CPU].width
}
}, },
unload_cpu() { height: {
this[CPU] = undefined get: function() {
}, return this[CPU].height
}
}
});
// Add methods to prototype
graphics.Image.prototype.unload_gpu = function() {
this[GPU] = undefined
}
graphics.Image.prototype.unload_cpu = function() {
this[CPU] = undefined
} }
function calc_image_size(img) { function calc_image_size(img) {
@@ -105,13 +124,7 @@ function decorate_rect_px(img) {
function make_handle(obj) function make_handle(obj)
{ {
return Object.assign(Object.create(graphics.Image), { return new graphics.Image(obj);
rect:{x:0,y:0,width:1,height:1},
[CPU]:obj,
[GPU]:undefined,
[LOADING]:false,
[LASTUSE]:os.now()
})
} }
function wrapSurface(surf, maybeRect){ function wrapSurface(surf, maybeRect){
@@ -144,7 +157,7 @@ function create_image(path){
try{ try{
const bytes = io.slurpbytes(path); const bytes = io.slurpbytes(path);
let raw = decode_image(bytes, path.ext()); let raw = decode_image(bytes, path.ext());
/* ── Case A: static image ─────────────────────────────────── */ /* ── Case A: static image ─────────────────────────────────── */
if(raw.surface) if(raw.surface)
return make_handle(raw.surface); return make_handle(raw.surface);
@@ -210,10 +223,9 @@ graphics.texture_from_data = function(data)
{ {
if (!(data instanceof ArrayBuffer)) return undefined if (!(data instanceof ArrayBuffer)) return undefined
var surface = graphics.make_texture(data); var image = graphics.make_texture(data);
var img = make_handle(surface); var img = make_handle(image)
// Trigger GPU load (async)
img.gpu; img.gpu;
return img; return img;
@@ -234,7 +246,7 @@ graphics.from = function(id, data)
} }
graphics.texture = function texture(path) { graphics.texture = function texture(path) {
if (path.__proto__ === graphics.Image) return path if (path instanceof graphics.Image) return path
if (typeof path !== 'string') if (typeof path !== 'string')
throw new Error('need a string for graphics.texture') throw new Error('need a string for graphics.texture')
@@ -243,7 +255,8 @@ graphics.texture = function texture(path) {
if (cache.has(id)) return cache.get(id) if (cache.has(id)) return cache.get(id)
var ipath = res.find_image(id) var ipath = res.find_image(id)
if (!ipath) throw new Error(`unknown image ${id}`) if (!ipath)
throw new Error(`unknown image ${id}`)
var image = create_image(ipath) var image = create_image(ipath)
cache.set(id, image) cache.set(id, image)
@@ -340,8 +353,6 @@ graphics.get_font = function get_font(path, size) {
console.error("Failed to load font texture:", response.error); console.error("Failed to load font texture:", response.error);
} else { } else {
font.texture = response; font.texture = response;
console.log('loaded font texture');
console.log(json.encode(font.texture));
} }
}); });
@@ -418,12 +429,6 @@ graphics.cull_sprites[prosperon.DOC] = `
Filter an array of sprites to only those visible in the provided cameras view. Filter an array of sprites to only those visible in the provided cameras view.
` `
graphics.make_surface[prosperon.DOC] = `
:param dimensions: The size object {width, height}, or an array [w,h].
:return: A blank RGBA surface with the given dimensions, typically for software rendering or icons.
Create a blank surface in RAM.
`
graphics.make_cursor[prosperon.DOC] = ` graphics.make_cursor[prosperon.DOC] = `
:param opts: An object with {surface, hotx, hoty} or similar. :param opts: An object with {surface, hotx, hoty} or similar.
:return: An SDL_Cursor object referencing the given surface for a custom mouse cursor. :return: An SDL_Cursor object referencing the given surface for a custom mouse cursor.

View File

@@ -48,6 +48,7 @@
#include "qjs_spline.h" #include "qjs_spline.h"
#include "qjs_js.h" #include "qjs_js.h"
#include "qjs_debug.h" #include "qjs_debug.h"
#include "qjs_sdl_surface.h"
#ifndef NSTEAM #ifndef NSTEAM
#include "qjs_steam.h" #include "qjs_steam.h"
#endif #endif
@@ -375,11 +376,6 @@ char *js2strdup(JSContext *js, JSValue v) {
#include "qjs_macros.h" #include "qjs_macros.h"
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
if (s->flags & SDL_SURFACE_PREALLOCATED)
free(s->pixels);
SDL_DestroySurface(s);
}
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c) void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{ {
@@ -389,11 +385,6 @@ void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
QJSCLASS(font,) QJSCLASS(font,)
QJSCLASS(datastream,) QJSCLASS(datastream,)
QJSCLASS(SDL_Surface,
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
)
JSValue angle2js(JSContext *js,double g) { JSValue angle2js(JSContext *js,double g) {
return number2js(js,g*HMM_RadToTurn); return number2js(js,g*HMM_RadToTurn);
} }
@@ -994,178 +985,6 @@ static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
return it->fmt; return it->fmt;
} }
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
static const scale_entry k_scale_table[] = {
{ "nearest", SDL_SCALEMODE_NEAREST },
{ "linear", SDL_SCALEMODE_LINEAR },
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
};
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(it->mode == mode) break;
return JS_NewString(js, it->name ? it->name : "nearest");
}
SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
if(JS_IsUndefined(v)) return SDL_SCALEMODE_NEAREST;
const char *s = JS_ToCString(js, v);
if(!s) return SDL_SCALEMODE_NEAREST;
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(!strcmp(it->name, s)) break;
JS_FreeCString(js, s);
return it->mode;
}
/* -------------------------------------------------------------------------
* surface.blit(dstrect, src, srcrect, scalemode)
* -------------------------------------------------------------------------*/
JSC_CCALL(surface_blit,
SDL_Surface *dst = js2SDL_Surface(js, self);
irect dr = {0}, *pdr = NULL;
if(!JS_IsUndefined(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
if (!src)
return JS_ThrowReferenceError(js, "Argument must be a surface.");
irect sr = {0}, *psr = NULL;
if(!JS_IsUndefined(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
printf("src %p, dst %p, src rect %g,%g,%g,%g [%p], dst rect %g,%g,%g,%g [%p]\n", src, dst, sr.x, sr.y, sr.w, sr.h, psr, dr.x, dr.y, dr.w, dr.h, pdr);
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
)
JSC_CCALL(surface_scale,
SDL_Surface *src = js2SDL_Surface(js,self);
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
ret = SDL_Surface2js(js,new);
)
static SDL_PixelFormatDetails pdetails = {
.format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
.bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits
.bytes_per_pixel = 4, // 4 bytes per pixel
.padding = {0, 0}, // Unused padding
.Rmask = 0xFF000000, // Red mask
.Gmask = 0x00FF0000, // Green mask
.Bmask = 0x0000FF00, // Blue mask
.Amask = 0x000000FF, // Alpha mask
.Rbits = 8, // 8 bits for Red
.Gbits = 8, // 8 bits for Green
.Bbits = 8, // 8 bits for Blue
.Abits = 8, // 8 bits for Alpha
.Rshift = 24, // Red shift
.Gshift = 16, // Green shift
.Bshift = 8, // Blue shift
.Ashift = 0 // Alpha shift
};
JSC_CCALL(surface_fill,
SDL_Surface *src = js2SDL_Surface(js,self);
colorf color = js2color(js,argv[0]);
rect r = {
.x = 0,
.y = 0,
.w = src->w,
.h = src->h
};
SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_rect,
SDL_Surface *dst = js2SDL_Surface(js,self);
rect r = js2rect(js,argv[0]);
colorf color = js2color(js,argv[1]);
SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_convert,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
return SDL_Surface2js(js, dst);
)
JSC_CCALL(surface_dup,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_Surface *conv = SDL_DuplicateSurface(surf);
if (!conv)
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
return SDL_Surface2js(js,conv);
)
JSC_CCALL(surface_pixels,
SDL_Surface *surf = js2SDL_Surface(js,self);
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
*/
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = surf->pitch*surf->h;
ret = JS_NewArrayBufferCopy(js, surf->pixels, byte_size);
if (locked)
SDL_UnlockSurface(surf);
)
JSC_CCALL(surface_get_width,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->w);
)
JSC_CCALL(surface_get_height,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->h);
)
JSC_CCALL(surface_get_format,
SDL_Surface *s = js2SDL_Surface(js,self);
return pixelformat2js(js, s->format);
)
JSC_CCALL(surface_get_pitch,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->pitch);
)
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
MIST_FUNC_DEF(surface, blit, 4),
MIST_FUNC_DEF(surface, scale, 1),
MIST_FUNC_DEF(surface, fill,1),
MIST_FUNC_DEF(surface, rect,2),
MIST_FUNC_DEF(surface, dup, 0),
MIST_FUNC_DEF(surface, pixels, 0),
MIST_FUNC_DEF(surface, convert, 1),
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
};
JSC_CCALL(os_guid, JSC_CCALL(os_guid,
SDL_GUID guid; SDL_GUID guid;
randombytes(guid.data, 16); randombytes(guid.data, 16);
@@ -1240,44 +1059,20 @@ JSC_CCALL(os_make_texture,
} }
int pitch = width*4; int pitch = width*4;
int fmt = SDL_PIXELFORMAT_RGBA32; size_t pixels_size = pitch * height;
SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,fmt, data, pitch); // Create JS object with surface data
if (!surf) { JSValue obj = JS_NewObject(js);
free(data); JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width));
return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError()); JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height));
} JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch));
ret = SDL_Surface2js(js,surf); JS_SetPropertyStr(js, obj, "pixels", JS_NewArrayBufferCopy(js, data, pixels_size));
free(data);
ret = obj;
) )
JSC_CCALL(graphics_surface_from_pixels,
int w, h, pitch;
SDL_PixelFormat fmt;
JS_GETATOM(js, w, argv[0], width, number)
JS_GETATOM(js, h, argv[0], height, number)
JS_GETATOM(js, fmt, argv[0], format, pixelformat)
JS_GETATOM(js, pitch, argv[0], pitch, number)
JSValue js_px = JS_GetPropertyStr(js, argv[0], "buffer");
size_t len;
void *raw = JS_GetArrayBuffer(js, &len, js_px);
JS_FreeValue(js, js_px);
if (!raw || !w || !h || !fmt)
return JS_ThrowReferenceError(js, "Invalid source.");
void *newraw = malloc(len);
memcpy(newraw, raw, len);
SDL_Surface *s = SDL_CreateSurfaceFrom(w, h, fmt, newraw, pitch);
if (!s)
return JS_ThrowInternalError(js, "Unable to create surface: %s", SDL_GetError());
return SDL_Surface2js(js, s);
)
// input: (gif image data) // input: (gif image data)
JSC_CCALL(os_make_gif, JSC_CCALL(os_make_gif,
@@ -1296,8 +1091,14 @@ JSC_CCALL(os_make_gif,
ret = gif; ret = gif;
if (frames == 1) { if (frames == 1) {
// still image, so return just that // still image, so return surface data object
JS_SetPropertyStr(js, gif, "surface", SDL_Surface2js(js,SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32, pixels, width*4))); JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, pixels, width*height*4));
JS_SetPropertyStr(js, gif, "surface", surfData);
return gif; return gif;
} }
@@ -1306,19 +1107,18 @@ JSC_CCALL(os_make_gif,
for (int i = 0; i < frames; i++) { for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js); JSValue frame = JS_NewObject(js);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)delays[i]/1000.0)); JS_SetPropertyStr(js, frame, "time", number2js(js,(float)delays[i]/1000.0));
void *frame_pixels = malloc(width*height*4);
if (!frame_pixels) { // Create surface data object instead of SDL_Surface
JS_FreeValue(js,gif); JSValue surfData = JS_NewObject(js);
ret = JS_ThrowOutOfMemory(js); JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, width));
goto CLEANUP; JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, height));
} JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
memcpy(frame_pixels, (unsigned char*)pixels+(width*height*4*i), width*height*4); JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, width*4));
SDL_Surface *framesurf = SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32,frame_pixels, width*4);
if (!framesurf) { void *frame_pixels = (unsigned char*)pixels+(width*height*4*i);
ret = JS_ThrowReferenceError(js, "failed to create SDL_Surface: %s", SDL_GetError()); JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, frame_pixels, width*height*4));
goto CLEANUP;
} JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,framesurf));
JS_SetPropertyUint32(js, delay_arr, i, frame); JS_SetPropertyUint32(js, delay_arr, i, frame);
} }
@@ -1332,10 +1132,16 @@ CLEANUP:
JSValue aseframe2js(JSContext *js, ase_frame_t aframe) JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
{ {
JSValue frame = JS_NewObject(js); JSValue frame = JS_NewObject(js);
void *frame_pixels = malloc(aframe.ase->w*aframe.ase->h*4);
memcpy(frame_pixels, aframe.pixels, aframe.ase->w*aframe.ase->h*4); // Create surface data object instead of SDL_Surface
SDL_Surface *surf = SDL_CreateSurfaceFrom(aframe.ase->w, aframe.ase->h, SDL_PIXELFORMAT_RGBA32, frame_pixels, aframe.ase->w*4); JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,surf)); JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, aframe.ase->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, aframe.ase->h));
JS_SetPropertyStr(js, surfData, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, aframe.ase->w*4));
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, aframe.pixels, aframe.ase->w*aframe.ase->h*4));
JS_SetPropertyStr(js, frame, "surface", surfData);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0)); JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
return frame; return frame;
} }
@@ -1393,11 +1199,6 @@ JSC_CCALL(os_make_aseprite,
cute_aseprite_free(ase); cute_aseprite_free(ase);
) )
JSC_CCALL(os_make_surface,
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_Surface *surface = SDL_CreateSurface(wh.x, wh.y, SDL_PIXELFORMAT_RGBA32);
ret = SDL_Surface2js(js, surface);
)
// TODO: Implement this correctly // TODO: Implement this correctly
JSC_CCALL(os_make_cursor, JSC_CCALL(os_make_cursor,
@@ -1418,7 +1219,31 @@ JSC_CCALL(os_make_font,
font *f = MakeFont(data, len, js2number(js,argv[1])); font *f = MakeFont(data, len, js2number(js,argv[1]));
if (!f) return JS_ThrowReferenceError(js, "could not create font"); if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js,f); ret = font2js(js,f);
JS_SetPropertyStr(js, ret, "surface", SDL_Surface2js(js,f->surface));
// Create surface data object for the font's atlas
if (f->surface) {
JSValue surfData = JS_NewObject(js);
JS_SetPropertyStr(js, surfData, "width", JS_NewInt32(js, f->surface->w));
JS_SetPropertyStr(js, surfData, "height", JS_NewInt32(js, f->surface->h));
JS_SetPropertyStr(js, surfData, "format", pixelformat2js(js, f->surface->format));
JS_SetPropertyStr(js, surfData, "pitch", JS_NewInt32(js, f->surface->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(f->surface)) {
if (SDL_LockSurface(f->surface) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = f->surface->pitch * f->surface->h;
JS_SetPropertyStr(js, surfData, "pixels", JS_NewArrayBufferCopy(js, f->surface->pixels, byte_size));
if (locked)
SDL_UnlockSurface(f->surface);
JS_SetPropertyStr(js, ret, "surface", surfData);
}
) )
JSC_SCALL(os_system, ret = number2js(js,system(str)); ) JSC_SCALL(os_system, ret = number2js(js,system(str)); )
@@ -1478,9 +1303,17 @@ static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
uint8_t *rgb = malloc(frame->height*frame->width*4); uint8_t *rgb = malloc(frame->height*frame->width*4);
memset(rgb,255,frame->height*frame->width*4); memset(rgb,255,frame->height*frame->width*4);
plm_frame_to_rgba(frame, rgb, frame->width*4); plm_frame_to_rgba(frame, rgb, frame->width*4);
SDL_Surface *surf = SDL_CreateSurfaceFrom(frame->width,frame->height, SDL_PIXELFORMAT_RGBA32, rgb, frame->width*4);
// Create surface data object instead of SDL_Surface
JSValue surfData = JS_NewObject(ds->js);
JS_SetPropertyStr(ds->js, surfData, "width", JS_NewInt32(ds->js, frame->width));
JS_SetPropertyStr(ds->js, surfData, "height", JS_NewInt32(ds->js, frame->height));
JS_SetPropertyStr(ds->js, surfData, "format", JS_NewString(ds->js, "rgba32"));
JS_SetPropertyStr(ds->js, surfData, "pitch", JS_NewInt32(ds->js, frame->width*4));
JS_SetPropertyStr(ds->js, surfData, "pixels", JS_NewArrayBufferCopy(ds->js, rgb, frame->height*frame->width*4));
JSValue s[1]; JSValue s[1];
s[0] = SDL_Surface2js(ds->js,surf); s[0] = surfData;
JSValue cb = JS_DupValue(ds->js,ds->callback); JSValue cb = JS_DupValue(ds->js,ds->callback);
JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s); JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s);
JS_FreeValue(ds->js,cb); JS_FreeValue(ds->js,cb);
@@ -1668,14 +1501,12 @@ static const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(os, make_gif, 1), MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1), MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, cull_sprites, 2), MIST_FUNC_DEF(os, cull_sprites, 2),
MIST_FUNC_DEF(os, make_surface, 1),
MIST_FUNC_DEF(os, make_cursor, 1), MIST_FUNC_DEF(os, make_cursor, 1),
MIST_FUNC_DEF(os, make_font, 2), MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_line_prim, 5), MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3), MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
MIST_FUNC_DEF(graphics, save_png, 4), MIST_FUNC_DEF(graphics, save_png, 4),
MIST_FUNC_DEF(graphics, save_jpg, 4), MIST_FUNC_DEF(graphics, save_jpg, 4),
MIST_FUNC_DEF(graphics, surface_from_pixels, 1),
}; };
static const JSCFunctionListEntry js_video_funcs[] = { static const JSCFunctionListEntry js_video_funcs[] = {
@@ -1763,36 +1594,41 @@ void ffi_load(JSContext *js)
prosperon_rt *rt = JS_GetContextOpaque(js); prosperon_rt *rt = JS_GetContextOpaque(js);
m_seedRand(&rt->mrand, time(NULL)); m_seedRand(&rt->mrand, time(NULL));
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use})); // cell modules
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
// actor module moved to hidden_fn
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
arrput(rt->module_registry, MISTLINE(time)); arrput(rt->module_registry, MISTLINE(time));
arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use})); arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use}));
arrput(rt->module_registry, MISTLINE(blob));
// extra
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(miniz));
// power user
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(util));
// prosperon
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use}));
arrput(rt->module_registry, ((ModuleEntry){"surface", js_sdl_surface_use}));
arrput(rt->module_registry, MISTLINE(spline)); arrput(rt->module_registry, MISTLINE(spline));
arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use})); arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use}));
arrput(rt->module_registry, MISTLINE(graphics)); arrput(rt->module_registry, MISTLINE(graphics));
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(util));
arrput(rt->module_registry, MISTLINE(video)); arrput(rt->module_registry, MISTLINE(video));
arrput(rt->module_registry, MISTLINE(soloud)); arrput(rt->module_registry, MISTLINE(soloud));
arrput(rt->module_registry, MISTLINE(layout)); arrput(rt->module_registry, MISTLINE(layout));
arrput(rt->module_registry, MISTLINE(miniz));
// arrput(rt->module_registry, MISTLINE(imgui)); // arrput(rt->module_registry, MISTLINE(imgui));
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use})); arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(nota));
arrput(rt->module_registry, MISTLINE(enet));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(blob));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
arrput(rt->module_registry, ((ModuleEntry){"sdl_video", js_sdl_video_use}));
// console module moved to hidden_fn
arrput(rt->module_registry, MISTLINE(rtree)); arrput(rt->module_registry, MISTLINE(rtree));
arrput(rt->module_registry, MISTLINE(sprite)); arrput(rt->module_registry, MISTLINE(sprite));
arrput(rt->module_registry, MISTLINE(transform)); arrput(rt->module_registry, MISTLINE(transform));
@@ -1812,7 +1648,6 @@ void ffi_load(JSContext *js)
JSValue c_types = JS_NewObject(js); JSValue c_types = JS_NewObject(js);
JS_SetPropertyStr(js,prosp, "c_types", c_types); JS_SetPropertyStr(js,prosp, "c_types", c_types);
QJSCLASSPREP_FUNCS(SDL_Surface)
QJSCLASSPREP_FUNCS(font); QJSCLASSPREP_FUNCS(font);
QJSCLASSPREP_FUNCS(datastream); QJSCLASSPREP_FUNCS(datastream);
@@ -1849,6 +1684,8 @@ void ffi_load(JSContext *js)
JS_SetPropertyStr(js, hidden_fn, "actor", js_actor_use(js)); JS_SetPropertyStr(js, hidden_fn, "actor", js_actor_use(js));
JS_SetPropertyStr(js, hidden_fn, "wota", js_wota_use(js)); JS_SetPropertyStr(js, hidden_fn, "wota", js_wota_use(js));
JS_SetPropertyStr(js, hidden_fn, "console", js_console_use(js)); JS_SetPropertyStr(js, hidden_fn, "console", js_console_use(js));
JS_SetPropertyStr(js, hidden_fn, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_fn, "enet", js_enet_use(js));
// Add functions that should only be accessible to engine.js // Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1)); JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));

364
source/qjs_sdl_surface.c Normal file
View File

@@ -0,0 +1,364 @@
#include "qjs_sdl_surface.h"
#include "qjs_macros.h"
#include "jsffi.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_surface.h>
#include <string.h>
// Helper functions from jsffi.c that need to be declared
extern JSValue number2js(JSContext *js, double g);
extern double js2number(JSContext *js, JSValue v);
extern rect js2rect(JSContext *js, JSValue v);
extern irect js2irect(JSContext *js, JSValue v);
extern colorf js2color(JSContext *js, JSValue v);
extern HMM_Vec2 js2vec2(JSContext *js, JSValue v);
// Type conversion functions
typedef struct { const char *name; SDL_PixelFormat fmt; } fmt_entry;
static const fmt_entry k_fmt_table[] = {
{ "rgba32", SDL_PIXELFORMAT_RGBA32 },
{ "argb32", SDL_PIXELFORMAT_ARGB32 },
{ "bgra32", SDL_PIXELFORMAT_BGRA32 },
{ "abgr32", SDL_PIXELFORMAT_ABGR32 },
{ "rgb565", SDL_PIXELFORMAT_RGB565 },
{ "bgr565", SDL_PIXELFORMAT_BGR565 },
{ "rgb24", SDL_PIXELFORMAT_RGB24 },
{ "bgr24", SDL_PIXELFORMAT_BGR24 },
{ "rgb332", SDL_PIXELFORMAT_RGB332 },
{ "rgba64", SDL_PIXELFORMAT_RGBA64 },
{ "rgb48", SDL_PIXELFORMAT_RGB48 },
{ NULL, SDL_PIXELFORMAT_UNKNOWN }
};
static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
{
fmt_entry *it;
for (it = (fmt_entry*)k_fmt_table; it->name; it++)
if (it->fmt == fmt)
break;
if (it->name)
return JS_NewString(js, it->name);
return JS_UNDEFINED;
}
static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
{
if (JS_IsUndefined(v)) return SDL_PIXELFORMAT_UNKNOWN;
const char *s = JS_ToCString(js, v);
if (!s) return SDL_PIXELFORMAT_UNKNOWN;
fmt_entry *it;
for (it = (fmt_entry*)k_fmt_table; it->name; it++)
if (!strcmp(it->name, s))
break;
JS_FreeCString(js,s);
return it->fmt;
}
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
static const scale_entry k_scale_table[] = {
{ "nearest", SDL_SCALEMODE_NEAREST },
{ "linear", SDL_SCALEMODE_LINEAR },
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
};
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(it->mode == mode) break;
return JS_NewString(js, it->name ? it->name : "nearest");
}
SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
if(JS_IsUndefined(v)) return SDL_SCALEMODE_NEAREST;
const char *s = JS_ToCString(js, v);
if(!s) return SDL_SCALEMODE_NEAREST;
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(!strcmp(it->name, s)) break;
JS_FreeCString(js, s);
return it->mode;
}
// SDL_Surface free function
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
if (s->flags & SDL_SURFACE_PREALLOCATED)
free(s->pixels);
SDL_DestroySurface(s);
}
// Class definition
QJSCLASS(SDL_Surface,)
// SDL_Surface methods
JSC_CCALL(surface_blit,
SDL_Surface *dst = js2SDL_Surface(js, self);
irect dr = {0}, *pdr = NULL;
if(!JS_IsUndefined(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
if (!src)
return JS_ThrowReferenceError(js, "Argument must be a surface.");
irect sr = {0}, *psr = NULL;
if(!JS_IsUndefined(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
)
JSC_CCALL(surface_scale,
SDL_Surface *src = js2SDL_Surface(js,self);
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
ret = SDL_Surface2js(js,new);
)
static SDL_PixelFormatDetails pdetails = {
.format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
.bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits
.bytes_per_pixel = 4, // 4 bytes per pixel
.padding = {0, 0}, // Unused padding
.Rmask = 0xFF000000, // Red mask
.Gmask = 0x00FF0000, // Green mask
.Bmask = 0x0000FF00, // Blue mask
.Amask = 0x000000FF, // Alpha mask
.Rbits = 8, // 8 bits for Red
.Gbits = 8, // 8 bits for Green
.Bbits = 8, // 8 bits for Blue
.Abits = 8, // 8 bits for Alpha
.Rshift = 24, // Red shift
.Gshift = 16, // Green shift
.Bshift = 8, // Blue shift
.Ashift = 0 // Alpha shift
};
JSC_CCALL(surface_fill,
SDL_Surface *src = js2SDL_Surface(js,self);
colorf color = js2color(js,argv[0]);
rect r = {
.x = 0,
.y = 0,
.w = src->w,
.h = src->h
};
SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_rect,
SDL_Surface *dst = js2SDL_Surface(js,self);
rect r = js2rect(js,argv[0]);
colorf color = js2color(js,argv[1]);
SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_convert,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
return SDL_Surface2js(js, dst);
)
JSC_CCALL(surface_dup,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_Surface *conv = SDL_DuplicateSurface(surf);
if (!conv)
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
return SDL_Surface2js(js,conv);
)
JSC_CCALL(surface_pixels,
SDL_Surface *surf = js2SDL_Surface(js,self);
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
*/
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = surf->pitch*surf->h;
ret = JS_NewArrayBufferCopy(js, surf->pixels, byte_size);
if (locked)
SDL_UnlockSurface(surf);
)
JSC_CCALL(surface_get_width,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->w);
)
JSC_CCALL(surface_get_height,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->h);
)
JSC_CCALL(surface_get_format,
SDL_Surface *s = js2SDL_Surface(js,self);
return pixelformat2js(js, s->format);
)
JSC_CCALL(surface_get_pitch,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->pitch);
)
JSC_CCALL(surface_toJSON,
SDL_Surface *surf = js2SDL_Surface(js,self);
// Create the result object
JSValue obj = JS_NewObject(js);
// Add width and height
JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w));
JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h));
// Add format
JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format));
// Add pitch
JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch));
// Lock surface if needed
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0) {
JS_FreeValue(js, obj);
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
}
locked = 1;
}
// Add pixels as ArrayBuffer
size_t byte_size = surf->pitch * surf->h;
JSValue pixels = JS_NewArrayBufferCopy(js, surf->pixels, byte_size);
JS_SetPropertyStr(js, obj, "pixels", pixels);
// Unlock if we locked
if (locked)
SDL_UnlockSurface(surf);
return obj;
)
// Constructor for SDL_Surface
JSC_CCALL(surface_constructor,
if (argc < 1)
return JS_ThrowTypeError(js, "Surface constructor requires an object argument");
// Get width and height
int width, height;
JS_GETATOM(js, width, argv[0], width, number)
JS_GETATOM(js, height, argv[0], height, number)
if (!width || !height)
return JS_ThrowTypeError(js, "Surface constructor requires width and height properties");
// Check for pixel format
SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA32; // default
JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
if (!JS_IsUndefined(format_val)) {
format = js2pixelformat(js, format_val);
}
JS_FreeValue(js, format_val);
// Check for pixel data
JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
if (!JS_IsUndefined(pixels_val)) {
// Create surface from pixel data
size_t len;
void *raw = JS_GetArrayBuffer(js, &len, pixels_val);
if (!raw) {
JS_FreeValue(js, pixels_val);
return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer");
}
// Get pitch if provided, otherwise calculate it
int pitch;
JSValue pitch_val = JS_GetPropertyStr(js, argv[0], "pitch");
if (!JS_IsUndefined(pitch_val)) {
pitch = js2number(js, pitch_val);
JS_FreeValue(js, pitch_val);
} else {
// Calculate pitch based on format
int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
pitch = width * bytes_per_pixel;
}
// Copy the pixel data
void *pixels_copy = malloc(len);
if (!pixels_copy) {
JS_FreeValue(js, pixels_val);
return JS_ThrowOutOfMemory(js);
}
memcpy(pixels_copy, raw, len);
SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch);
if (!surface) {
free(pixels_copy);
JS_FreeValue(js, pixels_val);
return JS_ThrowInternalError(js, "Failed to create surface from pixels: %s", SDL_GetError());
}
JS_FreeValue(js, pixels_val);
ret = SDL_Surface2js(js, surface);
} else {
// Create blank surface
SDL_Surface *surface = SDL_CreateSurface(width, height, format);
if (!surface)
return JS_ThrowInternalError(js, "Failed to create surface: %s", SDL_GetError());
ret = SDL_Surface2js(js, surface);
}
)
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
MIST_FUNC_DEF(surface, blit, 4),
MIST_FUNC_DEF(surface, scale, 1),
MIST_FUNC_DEF(surface, fill,1),
MIST_FUNC_DEF(surface, rect,2),
MIST_FUNC_DEF(surface, dup, 0),
MIST_FUNC_DEF(surface, pixels, 0),
MIST_FUNC_DEF(surface, convert, 1),
MIST_FUNC_DEF(surface, toJSON, 0),
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
};
JSValue js_sdl_surface_use(JSContext *js)
{
QJSCLASSPREP_FUNCS(SDL_Surface)
// Return a constructor function that creates SDL_Surface objects
JSValue ctor = JS_NewCFunction2(js, js_surface_constructor, "surface", 1, JS_CFUNC_constructor, 0);
JSValue proto = JS_GetClassProto(js, js_SDL_Surface_id);
JS_SetConstructor(js, ctor, proto);
JS_FreeValue(js, proto);
return ctor;
}

16
source/qjs_sdl_surface.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef QJS_SDL_SURFACE_H
#define QJS_SDL_SURFACE_H
#include <quickjs.h>
#include <SDL3/SDL_surface.h>
JSValue js_sdl_surface_use(JSContext *js);
// Functions generated by QJSCLASS macro
JSValue SDL_Surface2js(JSContext *js, SDL_Surface *s);
SDL_Surface *js2SDL_Surface(JSContext *js, JSValue val);
// Free function for SDL_Surface
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s);
#endif

View File

@@ -1,6 +1,7 @@
#include "qjs_sdl_video.h" #include "qjs_sdl_video.h"
#include "jsffi.h" #include "jsffi.h"
#include "qjs_macros.h" #include "qjs_macros.h"
#include "qjs_sdl_surface.h"
#include "prosperon.h" #include "prosperon.h"
#include "sprite.h" #include "sprite.h"
#include "transform.h" #include "transform.h"
@@ -52,8 +53,6 @@ extern double js2number(JSContext *js, JSValue v);
extern JSValue number2js(JSContext *js, double n); extern JSValue number2js(JSContext *js, double n);
extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v); extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v);
extern JSValue SDL_Texture2js(JSContext *js, SDL_Texture *t); 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 SDL_Window *js2SDL_Window(JSContext *js, JSValue v); extern SDL_Window *js2SDL_Window(JSContext *js, JSValue v);
extern JSValue SDL_Window2js(JSContext *js, SDL_Window *w); extern JSValue SDL_Window2js(JSContext *js, SDL_Window *w);
extern SDL_Renderer *js2SDL_Renderer(JSContext *js, JSValue v); extern SDL_Renderer *js2SDL_Renderer(JSContext *js, JSValue v);
@@ -1802,8 +1801,6 @@ JSValue js_sdl_video_use(JSContext *js) {
char id[64]; char id[64];
snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks()); snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks());
printf("id is %s\n", id);
// Prepare argv for create_actor // Prepare argv for create_actor
// We need to create the actor on the main thread // We need to create the actor on the main thread
const char *argv[] = { const char *argv[] = {

View File

@@ -3,8 +3,6 @@ var draw2d
var graphics var graphics
var os = use('os'); var os = use('os');
use('tracy').level = 1
// Create SDL video actor // Create SDL video actor
var video = use('sdl_video'); var video = use('sdl_video');
var video_actor = {__ACTORDATA__:{id:video}}; var video_actor = {__ACTORDATA__:{id:video}};
@@ -61,9 +59,9 @@ function start_drawing() {
var bunny_image = null; var bunny_image = null;
try { try {
bunny_image = graphics.texture('tests/bunny.png'); bunny_image = graphics.texture('tests/bunny.png');
console.log("Loaded bunny image");
} catch (e) { } catch (e) {
console.error("Failed to load bunny image:", e); console.log("Failed to load bunny image:", e);
console.log(e)
} }
function draw_frame() { function draw_frame() {
@@ -186,31 +184,28 @@ function start_drawing() {
); );
} }
// Draw the bunny image if loaded var img = "tests/bunny.png"
if (bunny_image) { draw2d.image(img, {x: 500, y: 450, width: 64, height: 64});
// Static bunny
draw2d.image(bunny_image, {x: 500, y: 450, width: 64, height: 64}); // Rotating bunny
var rotation = t * 0.5;
// Rotating bunny draw2d.image(
var rotation = t * 0.5; img,
draw2d.image( {x: 600, y: 450, width: 64, height: 64},
bunny_image, rotation,
{x: 600, y: 450, width: 64, height: 64}, [0.5, 0.5] // Center anchor
rotation, );
[0.5, 0.5] // Center anchor
); // Bouncing bunny with tint
var bounce_y = 500 + Math.sin(t * 3) * 20;
// Bouncing bunny with tint draw2d.image(
var bounce_y = 500 + Math.sin(t * 3) * 20; img,
draw2d.image( {x: 700, y: bounce_y, width: 48, height: 48},
bunny_image, 0,
{x: 700, y: bounce_y, width: 48, height: 48}, [0.5, 1], // Bottom center anchor
0, [0, 0], // No shear
[0.5, 1], // Bottom center anchor {color: [1, 0.5, 0.5, 1]} // Red tint
[0, 0], // No shear );
{color: [1, 0.5, 0.5, 1]} // Red tint
);
}
// Flush all commands to renderer // Flush all commands to renderer
draw2d.flush(); draw2d.flush();

37
tests/surface.js Normal file
View File

@@ -0,0 +1,37 @@
// Test SDL_Surface module
var Surface = use('surface');
// Test creating a surface
var surf = new Surface({width: 100, height: 100});
console.log("Created surface:", surf.width, "x", surf.height);
console.log(json.encode(surf))
// Test fill
surf.fill([1, 0, 0, 1]); // Red
// Test dup
var surf2 = surf.dup();
console.log("Duplicated surface:", surf2.width, "x", surf2.height);
// Test scale
var surf3 = surf.scale([50, 50], "linear");
console.log("Scaled surface:", surf3.width, "x", surf3.height);
// Test format
console.log("Surface format:", surf.format);
// Test pixels
var pixels = surf.pixels();
console.log("Got pixels array buffer, length:", pixels.byteLength);
// Test creating surface with custom format
var surf4 = new Surface({width: 64, height: 64, format: "rgb24"});
console.log("Created RGB24 surface:", surf4.width, "x", surf4.height, "format:", surf4.format);
// Test creating surface from pixels
var pixelData = new ArrayBuffer(32 * 32 * 4); // 32x32 RGBA
var surf5 = new Surface({width: 32, height: 32, pixels: pixelData});
console.log("Created surface from pixels:", surf5.width, "x", surf5.height);
console.log("Surface module test passed!");