diff --git a/CLAUDE.md b/CLAUDE.md index b97e8a23..ce2f3537 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,16 +1,7 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - ## Build Commands - -### Build variants -- `make` - Make and install debug version. Usually all that's needed. -- `make fast` - Build optimized version -- `make release` - Build release version with LTO and optimizations -- `make small` - Build minimal size version -- `make web` - Build for web/emscripten platform -- `make crosswin` - Cross-compile for Windows using mingw32 +Run 'make' to make cell. ### Testing After install with 'make', just run 'cell' and point it at the actor you want to launch. "cell tests/toml" runs the actor "tests/toml.js" @@ -18,15 +9,9 @@ After install with 'make', just run 'cell' and point it at the actor you want to ## Scripting language This is called "cell", a variant of JavaScript with important differences. See docs/cell.md for detailed language documentation. -### Common development commands -- `meson setup build_` - Configure build directory -- `meson compile -C build_` - Compile in build directory -- `./build_dbg/prosperon examples/` - Run example from build directory -- Copy prosperon to game directory and run: `cp build_dbg/prosperon / && cd && ./prosperon` - ## Architecture Overview -Prosperon is an actor-based game engine inspired by Douglas Crockford's Misty system. Key architectural principles: +Prosperon is an actor-based game engine. ### Actor Model - Each actor runs on its own thread diff --git a/meson.build b/meson.build index d4605ba1..982cefeb 100644 --- a/meson.build +++ b/meson.build @@ -329,7 +329,7 @@ src += [ 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_sdl_gpu.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.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', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_layout.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', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c', 'qjs_staef.c', 'qjs_layout.c', 'cell_qoi.c' ] # quirc src src += [ diff --git a/prosperon/clay.cm b/prosperon/clay.cm index 77a3bcf8..920c2c80 100644 --- a/prosperon/clay.cm +++ b/prosperon/clay.cm @@ -8,6 +8,7 @@ var graphics = use('graphics') var util = use('util') var input = use('input') var prosperon = use('prosperon') +var staef = use('staef') function normalizeSpacing(spacing) { if (typeof spacing == 'number') { @@ -216,7 +217,7 @@ clay.text = function text(str, ...configs) var config = rectify_configs(configs); config.size ??= [0,0]; config.font = graphics.get_font(config.font) - var tsize = graphics.font_text_size(config.font, str, 0, config.size.x); + var tsize = config.font.text_size(config.font, str, 0, config.size.x); tsize.x = Math.ceil(tsize.x) tsize.y = Math.ceil(tsize.y) config.size = config.size.map((x,i) => Math.max(x, tsize[i])); @@ -240,7 +241,7 @@ clay.button = function button(str, action, config = {}) { config.__proto__ = button_base; config.font = graphics.get_font(config.font) - config.size = graphics.font_text_size(config.font, str, 0, 0) + config.size = confit.font.text_size(config.font, str, 0, 0) add_item(config); config.text = str; config.action = action; @@ -251,7 +252,7 @@ clay.textbox = function(str, on_change, ...configs) { config.on_change = on_change config.text = str config.font = graphics.get_font(config.font) - var tsize = graphics.font_text_size(config.font, str, 0, 0) + var tsize = config.font.text_size(config.font, str, 0, 0) config.size ??= [0,0] config.size = [Math.ceil(tsize.x), Math.ceil(tsize.y)] config.size = [Math.max(config.size[0], config.size[0]), Math.max(config.size[1], config.size[1])] diff --git a/prosperon/prosperon.cm b/prosperon/prosperon.cm index b2ca3c05..f60a8df9 100644 --- a/prosperon/prosperon.cm +++ b/prosperon/prosperon.cm @@ -703,7 +703,7 @@ cmd_fns.draw_text = function(cmd) var font = graphics.get_font(cmd.font) if (!font[GPU]) font[GPU] = get_img_gpu(font.surface) - var mesh = graphics.make_text_buffer( + var mesh = font.make_text_buffer( cmd.text, cmd.pos, [cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a], @@ -759,7 +759,6 @@ cmd_fns.draw_slice9 = function(cmd) var img = graphics.texture(cmd.image) if (!img) return - // Use the gpu_slice9 function from geometry module to generate the mesh var slice_info = { tile_top: true, tile_bottom: true, diff --git a/prosperon/tilemap.cm b/prosperon/tilemap.cm index 7f2cbf78..7c4e973e 100644 --- a/prosperon/tilemap.cm +++ b/prosperon/tilemap.cm @@ -102,8 +102,6 @@ tilemap.prototype = for (var y = 0; y < this.tiles[x].length; y++) { var tile = this.tiles[x][y]; if (tile) { - // tile should already be an image object - // Create a unique key for each distinct image object if (!imageToKey.has(tile)) { var key = `texture_${keyCounter++}`; imageToKey.set(tile, key); diff --git a/scripts/engine.cm b/scripts/engine.cm index 14bbc426..6a028fbd 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -49,7 +49,7 @@ log.console = function(msg) log.error = function(msg = new Error()) { - var caller = caller_data(4) + var caller = caller_data(1) if (msg instanceof Error) msg = msg + "\n" + msg.stack diff --git a/scripts/graphics.cm b/scripts/graphics.cm index bb9c82b0..4acf83c3 100644 --- a/scripts/graphics.cm +++ b/scripts/graphics.cm @@ -5,6 +5,8 @@ var time = use('time') var res = use('resources') var json = use('json') var os = use('os') +var staef = use('staef') +var qoi = use('qoi') var LASTUSE = Symbol() var LOADING = Symbol() @@ -95,7 +97,13 @@ function decode_image(bytes, ext) case 'gif': return graphics.make_gif(bytes) // returns array of surfaces case 'ase': case 'aseprite': return graphics.make_aseprite(bytes) - default: return graphics.make_texture(bytes) // returns single surface + case 'qoi': return qoi.decode(bytes) // returns single surface + default: + // Try QOI first since it's fast to check + var qoi_result = qoi.decode(bytes) + if (qoi_result) return qoi_result + // Fall back to make_texture for other formats + return graphics.image_decode(bytes) // returns single surface } } @@ -221,8 +229,84 @@ graphics.texture = function texture(path) { if (!cache[id]) { var ipath = res.find_image(id) - if (!ipath) + + // If not found normally, check in accio/32k folder + if (!ipath) { + // First check if we have a cached QOI version + var cache_dir = '.prosperon/texture_cache' + var qoi_cache_path = `${cache_dir}/${id}.qoi` + + if (io.exists(qoi_cache_path)) { + // Load the cached QOI file + var qoi_bytes = io.slurpbytes(qoi_cache_path) + var cached_img = qoi.decode(qoi_bytes) + if (cached_img) { + var result = new graphics.Image(cached_img) + cache[id] = result + return cache[id] + } + } + + // Try to find the file in accio/32k with any extension + var accio_base = `accio/32k/${id}` + var found_path = null + + // Check for common image extensions + var extensions = ['', '.qoi', '.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.gif', '.ase', '.aseprite'] + for (var ext of extensions) { + var test_path = accio_base + ext + if (io.exists(test_path)) { + found_path = test_path + break + } + } + + if (found_path) { + // Load the 32k image + var bytes = io.slurpbytes(found_path) + var img_32k = decode_image(bytes, found_path.ext()) + + // Handle single surface images + if (img_32k && img_32k.width && img_32k.pixels) { + // Get the surface module for scaling + var surface = use('surface') + + var surf_4k = surface.scale(img_32k, { + width: Math.floor(img_32k.width / 8), + height: Math.floor(img_32k.height / 8), + mode: 'linear' + }) + log.console(img_32k.pixels.length) + log.console(surf_4k.pixels.length) + log.console(json.encode(surf_4k)) + + var qoi_data = surface.compress_qoi(surf_4k) + + log.console(json.encode(qoi_data)) + + if (qoi_data && qoi_data.pixels) { + if (!io.exists(cache_dir)) { + io.mkdir(cache_dir) + } + io.slurpwrite(qoi_cache_path, qoi_data.pixels) + } + + // Use the 4k version + var result = new graphics.Image(surf_4k) + cache[id] = result + return cache[id] + } + + // For now, if it's not a simple surface, just return it as-is + // (animations, etc. would need more complex handling) + var result = create_image(found_path) + cache[id] = result + return cache[id] + } + + // If still not found, return notex return graphics.texture('notex') + } var result = create_image(ipath) cache[id] = result @@ -380,7 +464,7 @@ graphics.get_font = function get_font(path) { if (fontcache[fontstr]) return fontcache[fontstr] var data = io.slurpbytes(fullpath) - var font = graphics.make_font(data, size) + var font = new staef.font(data, size) fontcache[fontstr] = font diff --git a/source/cell_qoi.c b/source/cell_qoi.c new file mode 100644 index 00000000..43f018f5 --- /dev/null +++ b/source/cell_qoi.c @@ -0,0 +1,180 @@ +#include "cell_qoi.h" +#include "qoi.h" +#include "qjs_blob.h" +#include "qjs_sdl.h" +#include +#include + +// Helper function to check for integer overflow in size calculations +static int check_size_overflow(size_t a, size_t b, size_t c, size_t *result) +{ + if (a > SIZE_MAX / b) return 1; + size_t temp = a * b; + if (temp > SIZE_MAX / c) return 1; + *result = temp * c; + return 0; +} + +// QOI compression/encoding +JSValue js_qoi_encode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv) +{ + if (argc < 1) + return JS_ThrowTypeError(js, "compress_qoi requires an object argument"); + + // Check if width/height properties exist + JSValue width_val = JS_GetPropertyStr(js, argv[0], "width"); + JSValue height_val = JS_GetPropertyStr(js, argv[0], "height"); + + if (JS_IsNull(width_val) || JS_IsNull(height_val)) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "compress_qoi requires width and height properties"); + } + + int width, height; + if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return JS_ThrowTypeError(js, "width and height must be numbers"); + } + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + if (width < 1 || height < 1) + return JS_ThrowRangeError(js, "width and height must be at least 1"); + + // Get pixel format + JSValue format_val = JS_GetPropertyStr(js, argv[0], "format"); + SDL_PixelFormat format = js2SDL_PixelFormat(js, format_val); + JS_FreeValue(js, format_val); + + if (format == SDL_PIXELFORMAT_UNKNOWN) + return JS_ThrowTypeError(js, "Invalid or missing pixel format"); + + // Get pixels + JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels"); + size_t pixel_len; + void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); + JS_FreeValue(js, pixels_val); + + if (!pixel_data) + return JS_ThrowTypeError(js, "pixels property must be a stoned blob"); + + // Validate buffer size + int bytes_per_pixel = SDL_BYTESPERPIXEL(format); + size_t required_size; + if (check_size_overflow(width, height, bytes_per_pixel, &required_size)) { + JS_FreeValue(js, pixels_val); + return JS_ThrowRangeError(js, "Image dimensions too large"); + } + + if (pixel_len < required_size) + return JS_ThrowRangeError(js, "pixels buffer too small for %dx%d format (need %zu bytes, got %zu)", + width, height, required_size, pixel_len); + + // Get colorspace (optional, default to sRGB) + int colorspace = 0; // QOI_SRGB + if (argc > 1) { + colorspace = JS_ToBool(js, argv[1]); + } + + // Determine number of channels based on format + int channels = SDL_ISPIXELFORMAT_ALPHA(format) ? 4 : 3; + + // Prepare QOI descriptor + qoi_desc desc = { + .width = width, + .height = height, + .channels = channels, + .colorspace = colorspace + }; + + // Encode to QOI + int out_len; + void *qoi_data = qoi_encode(pixel_data, &desc, &out_len); + + if (!qoi_data) + return JS_ThrowInternalError(js, "QOI encoding failed"); + + // Create result object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, height)); + JS_SetPropertyStr(js, result, "format", JS_NewString(js, "qoi")); + JS_SetPropertyStr(js, result, "channels", JS_NewInt32(js, channels)); + JS_SetPropertyStr(js, result, "colorspace", JS_NewInt32(js, colorspace)); + + JSValue compressed_pixels = js_new_blob_stoned_copy(js, qoi_data, out_len); + free(qoi_data); // Free the QOI buffer after copying to blob + JS_SetPropertyStr(js, result, "pixels", compressed_pixels); + + return result; +} + +// QOI decompression/decoding +JSValue js_qoi_decode(JSContext *js, JSValue this_val, int argc, JSValueConst *argv) +{ + size_t len; + void *raw = js_get_blob_data(js, &len, argv[0]); + if (!raw) return JS_ThrowReferenceError(js, "could not get QOI data from array buffer"); + + qoi_desc desc; + void *data = qoi_decode(raw, len, &desc, 0); // 0 means use channels from file + + if (!data) + return JS_NULL; // Return null if not valid QOI + + // QOI always decodes to either RGB or RGBA based on the file's channel count + int channels = desc.channels; + int pitch = desc.width * channels; + size_t pixels_size = pitch * desc.height; + + // If it's RGB, convert to RGBA for consistency + void *rgba_data = data; + if (channels == 3) { + rgba_data = malloc(desc.width * desc.height * 4); + if (!rgba_data) { + free(data); + return JS_ThrowOutOfMemory(js); + } + + // Convert RGB to RGBA + unsigned char *src = (unsigned char*)data; + unsigned char *dst = (unsigned char*)rgba_data; + for (int i = 0; i < desc.width * desc.height; i++) { + dst[i*4] = src[i*3]; + dst[i*4+1] = src[i*3+1]; + dst[i*4+2] = src[i*3+2]; + dst[i*4+3] = 255; + } + free(data); + pitch = desc.width * 4; + pixels_size = pitch * desc.height; + } + + // Create JS object with surface data + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, desc.width)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, desc.height)); + JS_SetPropertyStr(js, obj, "format", JS_NewString(js, "rgba32")); + JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, pitch)); + JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, rgba_data, pixels_size)); + JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, 8)); + JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, 0)); + JS_SetPropertyStr(js, obj, "colorspace", JS_NewInt32(js, desc.colorspace)); + + free(rgba_data); + return obj; +} + +static const JSCFunctionListEntry js_qoi_funcs[] = { + MIST_FUNC_DEF(qoi, encode, 1), + MIST_FUNC_DEF(qoi, decode, 1) +}; + +JSValue js_qoi_use(JSContext *js) +{ + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_qoi_funcs, countof(js_qoi_funcs)); + return mod; +} diff --git a/source/cell_qoi.h b/source/cell_qoi.h new file mode 100644 index 00000000..5b699e4b --- /dev/null +++ b/source/cell_qoi.h @@ -0,0 +1,8 @@ +#ifndef CELL_QOI_H +#define CELL_QOI_H + +#include + +JSValue js_qoi_use(JSContext *js); + +#endif // CELL_QOI_H \ No newline at end of file diff --git a/source/config.c b/source/config.c index 5ae39455..fe4dda1e 100644 --- a/source/config.c +++ b/source/config.c @@ -26,9 +26,6 @@ #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_BOX -#define STB_IMAGE_RESIZE_IMPLEMENTATION -#include "stb_image_resize2.h" - #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" diff --git a/source/jsffi.c b/source/jsffi.c index 92af31bc..ee763240 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -3,13 +3,10 @@ #include "datastream.h" #include "qjs_sdl.h" #include "qjs_sdl_input.h" -#include "qjs_io.h" -#include "qjs_fd.h" #include "transform.h" #include "stb_ds.h" #include "stb_image.h" #include "stb_rect_pack.h" -#include "stb_image_write.h" #include "string.h" #include #include @@ -19,13 +16,11 @@ #include #include #include "render.h" -#include "model.h" #include "HandmadeMath.h" #include "par/par_streamlines.h" #include "par/par_shapes.h" #include #include "cute_aseprite.h" -#include "cgltf.h" #include "cell.h" #include "qjs_blob.h" @@ -57,52 +52,18 @@ #include "qjs_utf8.h" #include "qjs_fit.h" #include "qjs_text.h" +#include "qjs_staef.h" +#include "qjs_io.h" +#include "qjs_fd.h" #ifndef NSTEAM #include "qjs_steam.h" #endif -#include - -void gui_input(SDL_Event *e); - -#ifdef _WIN32 -#include -#else -#include -#include -#ifdef __linux__ -#include -#endif -#endif - -#include "wildmatch.h" - -#include "freelist.h" - -#include "sprite.h" - -#include -#include -#include -#include -#include -#include - -int randombytes(void *buf, size_t n); - -int trace = 0; - // External transform function declarations extern JSClassID js_transform_id; JSValue transform2js(JSContext *js, transform *t); transform *js2transform(JSContext *js, JSValue v); -#ifdef __APPLE__ -#include -//#else -//#include -#endif - // Random number generation constants for MT19937-64 #define NN STATE_VECTOR_LENGTH #define MM STATE_VECTOR_M @@ -233,39 +194,6 @@ JSValue js_getproperty(JSContext *js, JSValue v, const char *prop) return ret; } -void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr) -{ - free(ptr); -} - -JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index) -{ - JSValue tstack[3]; - tstack[1] = JS_NULL; - tstack[2] = JS_NULL; - // TODO: always copying; implement "takeover" - tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1); -// JSValue ret = JS_NewTypedArray(js, 3, tstack, type); -// JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements)); -// JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type))); -// JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index)); -// JS_FreeValue(js,tstack[0]); -// return ret; - return JS_NULL; -} - -void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size) -{ - return NULL; -/* size_t o, len, bytes, msize; - JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes); - void *data = js_get_blob_data(js, &msize, buf); - JS_FreeValue(js,buf); - if (stride) *stride = js_getnum_str(js, argv, "stride"); - if (size) *size = msize; - return data;*/ -} - JSValue make_quad_indices_buffer(JSContext *js, int quads) { cell_rt *rt = JS_GetContextOpaque(js); @@ -355,10 +283,6 @@ JSValue quads_to_mesh(JSContext *js, text_vert *buffer) return ret; } -#ifndef _WIN32 -#include -#endif - typedef struct lrtb lrtb; lrtb js2lrtb(JSContext *js, JSValue v) @@ -388,7 +312,6 @@ char *js2strdup(JSContext *js, JSValue v) { #include "qjs_macros.h" -QJSCLASS(font,) QJSCLASS(datastream,) JSValue angle2js(JSContext *js,double g) { @@ -613,27 +536,6 @@ irect js2irect(JSContext *js, JSValue v) return rect; } -rect transform_rect(SDL_Renderer *ren, rect in, HMM_Mat3 *t) -{ - HMM_Vec3 bottom_left = (HMM_Vec3){in.x,in.y,1.0}; - HMM_Vec3 transformed_bl = HMM_MulM3V3(*t, bottom_left); - in.x = transformed_bl.x; - in.y = transformed_bl.y; - in.y = in.y - in.h; // should be done for any platform that draws rectangles from top left - return in; -} - -HMM_Vec2 transform_point(SDL_Renderer *ren, HMM_Vec2 in, HMM_Mat3 *t) -{ - rect logical; - SDL_GetRenderLogicalPresentationRect(ren, &logical); - in.y *= -1; - in.y += logical.h; - in.x -= t->Columns[2].x; - in.y -= t->Columns[2].y; - return in; -} - JSValue rect2js(JSContext *js,rect rect) { JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "x", number2js(js, rect.x)); @@ -643,168 +545,6 @@ JSValue rect2js(JSContext *js,rect rect) { return obj; } -JSValue ints2js(JSContext *js,int *ints) { - JSValue arr = JS_NewArray(js); - for (int i = 0; i < arrlen(ints); i++) - JS_SetPropertyUint32(js, arr,i, number2js(js,ints[i])); - - return arr; -} - -int vec_between(HMM_Vec2 p, HMM_Vec2 a, HMM_Vec2 b) { - HMM_Vec2 n; - n.x = b.x - a.x; - n.y = b.y - a.y; - n = HMM_NormV2(n); - - return HMM_DotV2(n, HMM_SubV2(p,a)) > 0 && HMM_DotV2(HMM_MulV2F(n, -1), HMM_SubV2(p,b)) > 0; -} - -/* Determines between which two points in 'segs' point 'p' falls. - 0 indicates 'p' comes before the first point. - arrlen(segs) indicates it comes after the last point. -*/ -int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) { - float shortest = slop < 0 ? INFINITY : slop; - int best = -1; - - for (int i = 0; i < arrlen(segs) - 1; i++) { - float a = (segs[i + 1].y - segs[i].y) / (segs[i + 1].x - segs[i].x); - float c = segs[i].y - (a * segs[i].x); - float b = -1; - - float dist = fabsf(a * p.x + b * p.y + c) / sqrt(pow(a, 2) + 1); - - if (dist > shortest) continue; - - int between = vec_between(p, segs[i], segs[i + 1]); - - if (between) { - shortest = dist; - best = i + 1; - } else { - if (i == 0 && HMM_DistV2(p,segs[0]) < slop) { - shortest = dist; - best = i; - } else if (i == arrlen(segs) - 2 && HMM_DistV2(p,arrlast(segs)) < slop) { - shortest = dist; - best = arrlen(segs); - } - } - } - - if (best == 1) { - HMM_Vec2 n; - n.x = segs[1].x - segs[0].x; - n.y = segs[1].y - segs[0].y; - n = HMM_NormV2(n); - if (HMM_DotV2(n, HMM_SubV2(p,segs[0])) < 0 ){ - if (HMM_DistV2(p, segs[0]) >= slop) - best = -1; - else - best = 0; - } - } - - if (best == arrlen(segs) - 1) { - HMM_Vec2 n; - n.x = segs[best - 1].x - segs[best].x; - n.y = segs[best - 1].y - segs[best - 1].y; - n = HMM_NormV2(n); - - if (HMM_DotV2(n, HMM_SubV2(p, segs[best])) < 0) { - if (HMM_DistV2(p, segs[best]) >= slop) - best = -1; - else - best = arrlen(segs); - } - } - - return best; -} - -JSC_CCALL(os_make_text_buffer, - const char *s = JS_ToCString(js, argv[0]); - rect rectpos = js2rect(js,argv[1]); - font *f = js2font(js,argv[4]); - colorf c = js2color(js,argv[2]); - int wrap = js2number(js,argv[3]); - HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y }; - text_vert *buffer = renderText(s, startpos, f, c, wrap); - ret = quads_to_mesh(js,buffer); - JS_FreeCString(js, s); - arrfree(buffer); -) - -JSValue js_util_camera_globals(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - JSValue camera = argv[0]; - if(JS_IsNull(camera)) return JS_NULL; - - HMM_Vec2 size; HMM_Vec3 pos; HMM_Quat rotation; - double fov = 0; int ortho; double near_z = 0; double far_z = 0; - HMM_Vec2 anchor; - - JS_GETPROP(js, size, camera, size, vec2) - JS_GETPROP(js, fov, camera, fov, number) - JS_GETPROP(js, ortho, camera, ortho, bool) - JS_GETPROP(js, near_z, camera, near_z, number) - JS_GETPROP(js, far_z, camera, far_z, number) - JS_GETPROP(js, anchor, camera, anchor, vec2) - JS_GETPROP(js, pos, camera, pos, vec3) - JS_GETPROP(js, rotation, camera, rotation, quat) - - rotation.w = 1; - - HMM_Mat4 proj, view; - - if(ortho) { - float left = -anchor.x * size.x; - float bottom = -anchor.y * size.y; - float right = left + size.x; - float top = bottom + size.y; - proj = HMM_Orthographic_RH_NO(left, right, bottom, top, -1.0f, 1.0f); - } else { - proj = HMM_Perspective_RH_NO(fov, size.x/size.y, near_z, far_z); - proj.Columns[1] = HMM_MulV4F(proj.Columns[1], -1.0f); - } - - view = HMM_MulM4( - HMM_InvTranslate(HMM_Translate(pos)), - HMM_InvRotate (HMM_QToM4(rotation)) - ); - - JSValue data = JS_NewObject(js); - - HMM_Mat4 world_to_projection = HMM_MulM4(proj, view); - HMM_Mat4 projection_to_world = HMM_InvGeneralM4(world_to_projection); - HMM_Vec3 camera_dir_world = HMM_NormV3( - HMM_QVRot((HMM_Vec3){0,0,-1}, rotation) - ); - - JS_SetPropertyStr(js, data, "world_to_projection", - js_new_blob_stoned_copy(js, world_to_projection.em, - sizeof(float)*16)); - JS_SetPropertyStr(js, data, "projection_to_world", - js_new_blob_stoned_copy(js, projection_to_world.em, - sizeof(float)*16)); - JS_SetPropertyStr(js, data, "world_to_view", - js_new_blob_stoned_copy(js, view.em, sizeof(float)*16)); - JS_SetPropertyStr(js, data, "view_to_projection", - js_new_blob_stoned_copy(js, proj.em, sizeof(float)*16)); - - JS_SetPropertyStr(js, data, "camera_pos_world", vec32js(js, pos)); - JS_SetPropertyStr(js, data, "camera_dir_world", vec32js(js, camera_dir_world)); - JS_SetPropertyStr(js, data, "render_size", vec22js(js, size)); - JS_SetPropertyStr(js, data, "viewport_size", vec22js(js, (HMM_Vec2){0.5,0.5})); - JS_SetPropertyStr(js, data, "viewport_offset", vec22js(js, (HMM_Vec2){0,0})); - JS_SetPropertyStr(js, data, "viewport_min_z", number2js(js, near_z)); - JS_SetPropertyStr(js, data, "viewport_max_z", number2js(js, far_z)); - - return data; -} - - static JSValue floats2array(JSContext *js, float *vals, size_t len) { JSValue arr = JS_NewArray(js); for (size_t i = 0; i < len; i++) { @@ -865,21 +605,6 @@ JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropert JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); } JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_NULL; } -JSValue js_array_get_xy(JSContext *js, JSValue self) -{ - JSValue arr = JS_NewArray(js); - JS_SetPropertyUint32(js,arr,0,JS_GetPropertyUint32(js,self,0)); - JS_SetPropertyUint32(js,arr,1,JS_GetPropertyUint32(js,self,1)); - return arr; -} - -JSValue js_array_set_xy(JSContext *js, JSValue self, JSValue v) -{ - JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0)); - JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1)); - return JS_NULL; -} - // Single-value accessors JSValue js_array_get_r(JSContext *js, JSValue self) @@ -907,43 +632,6 @@ JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js, self, 3, val); return JS_NULL; } // Multi-value accessors - -JSValue js_array_get_rgb(JSContext *js, JSValue self) -{ - JSValue arr = JS_NewArray(js); - JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0)); - JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1)); - JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2)); - return arr; -} - -JSValue js_array_set_rgb(JSContext *js, JSValue self, JSValue val) -{ - JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0)); - JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1)); - JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2)); - return JS_NULL; -} - -JSValue js_array_get_rgba(JSContext *js, JSValue self) -{ - JSValue arr = JS_NewArray(js); - JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0)); - JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1)); - JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2)); - JS_SetPropertyUint32(js, arr, 3, JS_GetPropertyUint32(js, self, 3)); - return arr; -} - -JSValue js_array_set_rgba(JSContext *js, JSValue self, JSValue val) -{ - JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0)); - JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1)); - JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2)); - JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3)); - return JS_NULL; -} - static const JSCFunctionListEntry js_array_funcs[] = { PROTO_FUNC_DEF(array, add, 1), PROTO_FUNC_DEF(array, sub, 1), @@ -952,13 +640,10 @@ static const JSCFunctionListEntry js_array_funcs[] = { PROTO_FUNC_DEF(array, lerp, 2), JS_CGETSET_DEF("x", js_array_get_x,js_array_set_x), JS_CGETSET_DEF("y", js_array_get_y, js_array_set_y), - JS_CGETSET_DEF("xy", js_array_get_xy, js_array_set_xy), JS_CGETSET_DEF("r", js_array_get_r, js_array_set_r), JS_CGETSET_DEF("g", js_array_get_g, js_array_set_g), JS_CGETSET_DEF("b", js_array_get_b, js_array_set_b), JS_CGETSET_DEF("a", js_array_get_a, js_array_set_a), - JS_CGETSET_DEF("rgb", js_array_get_rgb, js_array_set_rgb), - JS_CGETSET_DEF("rgba", js_array_get_rgba, js_array_set_rgba), }; JSC_CCALL(number_lerp, @@ -1022,52 +707,8 @@ static const JSCFunctionListEntry js_datastream_funcs[] = { CGETSET_ADD(datastream, callback), }; -JSC_GETSET(font, linegap, number) -JSC_GET(font, height, number) -JSC_GET(font, ascent, number) -JSC_GET(font, descent, number) - -JSC_CCALL(graphics_font_text_size, - font *f = js2font(js,argv[0]); - const char *str = JS_ToCString(js, argv[1]); - float letterSpacing = js2number(js,argv[2]); - float wrap = js2number(js,argv[3]); - ret = vec22js(js,measure_text(str, f, letterSpacing, wrap)); - JS_FreeCString(js, str); -) - -static const JSCFunctionListEntry js_font_funcs[] = { - CGETSET_ADD(font, linegap), - MIST_GET(font, height), - MIST_GET(font, ascent), - MIST_GET(font, descent), -}; - -JSC_CCALL(os_image_info, - size_t len; - void *raw = js_get_blob_data(js, &len, argv[0]); - if (!raw) - return JS_ThrowReferenceError(js, "could not load image with array buffer"); - - int depth = stbi_is_16_bit_from_memory(raw, len) ? 16 : 8; - int width, height, comp; - if (!stbi_info_from_memory(raw, len, &width, &height, &comp)) - return JS_ThrowReferenceError(js, "could not parse image info: %s", stbi_failure_reason()); - - int hdr = stbi_is_hdr_from_memory(raw, len); - - JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, width)); - JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, height)); - JS_SetPropertyStr(js, obj, "components", JS_NewInt32(js, comp)); - JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, depth)); - JS_SetPropertyStr(js, obj, "hdr", JS_NewBool(js, hdr)); - - ret = obj; -) - // input: (encoded image data of jpg, png, bmp, tiff) -JSC_CCALL(os_make_texture, +JSC_CCALL(os_image_decode, size_t len; void *raw = js_get_blob_data(js, &len, argv[0]); if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer"); @@ -1228,39 +869,6 @@ JSC_CCALL(os_make_aseprite, cute_aseprite_free(ase); ) -JSC_CCALL(os_make_font, - size_t len; - void *data = js_get_blob_data(js,&len,argv[0]); - if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data"); - font *f = MakeFont(data, len, js2number(js,argv[1])); - if (!f) return JS_ThrowReferenceError(js, "could not create font"); - ret = font2js(js,f); - - // 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_new_blob_stoned_copy(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)); ) @@ -1279,15 +887,6 @@ JSC_CCALL(os_srand, m_seedRand(mrand, js2number(js,argv[0])); ) -JSValue make_color_buffer(JSContext *js, colorf c, int verts) -{ - HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts); - for (int i = 0; i < verts; i++) - colordata[i] = c; - - return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, 0, 4, 0, 0); -} - JSC_CCALL(os_make_line_prim, return JS_NULL; /* @@ -1322,7 +921,6 @@ JSC_CCALL(os_make_line_prim, JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), 0,2,1,0)); JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices)); - JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices)); JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3)); JS_SetPropertyStr(js,prim,"first_index", number2js(js,0)); @@ -1397,60 +995,8 @@ JSC_CCALL(os_rectpack, } ) - -JSC_CCALL(os_insertion_sort, - JSValue arr = argv[0]; - JSValue cmp = argv[1]; - int len = JS_ArrayLength(js, arr); - - for (int i = 1; i < len; i++) { - JSValue key = JS_GetPropertyUint32(js, arr, i); - int j = i - 1; - - while (j >= 0) { - JSValue arr_j = JS_GetPropertyUint32(js, arr, j); - - JSValue ret = JS_Call(js, cmp, JS_NULL, 2, (JSValue[]){ arr_j, key }); - if (JS_IsException(ret)) { - JS_FreeValue(js,arr_j); - JS_FreeValue(js,key); - return ret; - } - double c = js2number(js, ret); - JS_FreeValue(js, ret); - - if (c > 0) { - JS_SetPropertyUint32(js, arr, j + 1, arr_j); - j--; - } else { - JS_FreeValue(js, arr_j); - break; - } - } - - JS_SetPropertyUint32(js, arr, j + 1, key); - } - - ret = JS_DupValue(js,arr); -) - -JSC_CCALL(os_power_state, - SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL); - switch(state) { - case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status"); - case SDL_POWERSTATE_UNKNOWN: return JS_NULL; - case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery"); - case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery"); - case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging"); - case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged"); - } - return JS_NULL; -) - static const JSCFunctionListEntry js_util_funcs[] = { MIST_FUNC_DEF(os, guid, 0), - MIST_FUNC_DEF(os, insertion_sort, 2), - MIST_FUNC_DEF(util, camera_globals, 1), }; JSC_CCALL(graphics_hsl_to_rgb, @@ -1473,47 +1019,13 @@ JSC_CCALL(graphics_hsl_to_rgb, return color2js(js, (colorf){r+m, g+m, b+m, 1}); ) -JSC_CCALL(graphics_save_png, - const char *file = JS_ToCString(js, argv[0]); - int w, h, comp, pitch; - JS_ToInt32(js, &w, argv[1]); - JS_ToInt32(js, &h, argv[2]); - JS_ToInt32(js, &pitch, argv[4]); - size_t size; - void *data = js_get_blob_data(js, &size, argv[3]); - - if (!stbi_write_png(file, w, h, 4, data, pitch)) - return JS_ThrowInternalError(js, "Could not write png"); -) - -JSC_CCALL(graphics_save_jpg, - const char *file = JS_ToCString(js, argv[0]); - int w, h, comp, pitch, quality; - JS_ToInt32(js, &w, argv[1]); - JS_ToInt32(js, &h, argv[2]); - JS_ToInt32(js, &pitch, argv[4]); - JS_ToInt32(js, &quality, argv[5]); - if (!quality) quality = 80; - size_t size; - void *data = js_get_blob_data(js, &size, argv[3]); - - if (!stbi_write_jpg(file, w, h, 4, data, quality)) - return JS_ThrowInternalError(js, "Could not write png"); -) - static const JSCFunctionListEntry js_graphics_funcs[] = { - MIST_FUNC_DEF(os, make_text_buffer, 5), MIST_FUNC_DEF(os, rectpack, 3), - MIST_FUNC_DEF(os, image_info, 1), - MIST_FUNC_DEF(os, make_texture, 1), + MIST_FUNC_DEF(os, image_decode, 1), MIST_FUNC_DEF(os, make_gif, 1), MIST_FUNC_DEF(os, make_aseprite, 1), - MIST_FUNC_DEF(os, make_font, 2), MIST_FUNC_DEF(os, make_line_prim, 5), MIST_FUNC_DEF(graphics, hsl_to_rgb, 3), - MIST_FUNC_DEF(graphics, save_png, 4), - MIST_FUNC_DEF(graphics, save_jpg, 4), - MIST_FUNC_DEF(graphics, font_text_size, 4), }; static const JSCFunctionListEntry js_video_funcs[] = { @@ -1592,6 +1104,7 @@ JSC_CCALL(os_value_id, #include "qjs_nota.h" #include "qjs_layout.h" #include "qjs_sdl_gpu.h" +#include "cell_qoi.h" JSValue js_imgui_use(JSContext *js); #define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use} @@ -1627,9 +1140,11 @@ void ffi_load(JSContext *js) arrput(rt->module_registry, MISTLINE(utf8)); arrput(rt->module_registry, MISTLINE(fit)); arrput(rt->module_registry, MISTLINE(text)); + arrput(rt->module_registry, MISTLINE(staef)); arrput(rt->module_registry, MISTLINE(wota)); arrput(rt->module_registry, MISTLINE(nota)); arrput(rt->module_registry, MISTLINE(sdl_gpu)); + arrput(rt->module_registry, MISTLINE(qoi)); // power user arrput(rt->module_registry, MISTLINE(js)); diff --git a/source/qjs_crypto.h b/source/qjs_crypto.h index 10258658..b33f238d 100644 --- a/source/qjs_crypto.h +++ b/source/qjs_crypto.h @@ -4,5 +4,6 @@ #include "cell.h" JSValue js_crypto_use(JSContext *ctx); +int randombytes(void *buf, size_t n); #endif diff --git a/source/qjs_geometry.c b/source/qjs_geometry.c index 01f63916..3f7d47ac 100644 --- a/source/qjs_geometry.c +++ b/source/qjs_geometry.c @@ -473,117 +473,6 @@ static HMM_Vec3 base_quad[4] = { {1.0,1.0,1.0} }; -JSC_CCALL(gpu_make_sprite_mesh, - size_t quads = JS_ArrayLength(js, argv[0]); - size_t verts = quads*4; - size_t count = quads*6; - - // Prepare arrays on CPU - HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts); - HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts); - HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts); - - for (int i = 0; i < quads; i++) { - JSValue sub = JS_GetPropertyUint32(js,argv[0],i); - - transform *tr; - rect src; - HMM_Vec4 color; - JS_GETATOM(js,src,sub,src,rect) - JS_GETATOM(js,color,sub,color,color) - JS_GETATOM(js,tr,sub,transform,transform) - JS_FreeValue(js,sub); - - size_t base = i*4; - if (tr) { - HMM_Mat3 trmat = transform2mat3(tr); - for (int j = 0; j < 4; j++) - posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; - } else { - rect dst; - JS_GETATOM(js,dst,sub,rect,rect); - posdata[base+0] = (HMM_Vec2){dst.x,dst.y}; - posdata[base + 1] = (HMM_Vec2){ dst.x+dst.w, dst.y }; - posdata[base + 2] = (HMM_Vec2){ dst.x, dst.y+dst.h }; - posdata[base + 3] = (HMM_Vec2){ dst.x+dst.w, dst.y+dst.h }; - } - - uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h}; - uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h}; - uvdata[base+2] = (HMM_Vec2){src.x, src.y}; - uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y}; - - colordata[base+0] = color; - colordata[base+1] = color; - colordata[base+2] = color; - colordata[base+3] = color; - } - - // Check old mesh - JSValue old_mesh = JS_NULL; - if (argc > 1) - old_mesh = argv[1]; - - // Needed sizes - size_t pos_size = sizeof(*posdata)*verts; - size_t uv_size = sizeof(*uvdata)*verts; - size_t color_size = sizeof(*colordata)*verts; - - BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, "pos", pos_size, 0, 2, 1, 0); - BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, 0, 2, 1, 0); - BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", color_size, 0, 4, 1, 0); - - int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new; - - ret = JS_NewObject(js); - - if (need_new_all) { - // Create all new buffers - JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, 0, 2, 1,0); - JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, 0, 2, 1,0); - JSValue new_color = make_gpu_buffer(js, colordata, color_size, 0, 0, 1,0); - - JS_SetPropertyStr(js, ret, "pos", new_pos); - JS_SetPropertyStr(js, ret, "uv", new_uv); - JS_SetPropertyStr(js, ret, "color", new_color); - - // Indices - JSValue indices = make_quad_indices_buffer(js, quads); - JS_SetPropertyStr(js, ret, "indices", indices); - } else { - // Reuse the old buffers - // Just copy data into existing buffers via their pointers - memcpy(pos_chk.ptr, posdata, pos_size); - memcpy(uv_chk.ptr, uvdata, uv_size); - memcpy(color_chk.ptr, colordata, color_size); - - // Duplicate old references since we're returning a new object - JS_SetPropertyStr(js, ret, "pos", JS_DupValue(js, pos_chk.val)); - JS_SetPropertyStr(js, ret, "uv", JS_DupValue(js, uv_chk.val)); - JS_SetPropertyStr(js, ret, "color", JS_DupValue(js, color_chk.val)); - - // Indices can remain the same if they were also large enough. If using a shared global index buffer: - JSValue indices = make_quad_indices_buffer(js, quads); - JS_SetPropertyStr(js, ret, "indices", indices); - } - - JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts)); - JS_SetPropertyStr(js, ret, "count", number2js(js, count)); - JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count)); - - // Free temporary CPU arrays - free(posdata); - free(uvdata); - free(colordata); - - // Free old buffer values if they were fetched - if (!JS_IsNull(pos_chk.val)) JS_FreeValue(js, pos_chk.val); - if (!JS_IsNull(uv_chk.val)) JS_FreeValue(js, uv_chk.val); - if (!JS_IsNull(color_chk.val)) JS_FreeValue(js, color_chk.val); - - return ret; -) - struct quad_buffers { HMM_Vec2 *pos; HMM_Vec2 *uv; @@ -601,27 +490,6 @@ struct quad_buffers quad_buffers_new(int verts) return b; } -JSValue quadbuffers_to_mesh(JSContext *js, struct quad_buffers buffers) -{ - JSValue jspos = make_gpu_buffer(js, buffers.pos, sizeof(HMM_Vec2)*buffers.verts, 0, 2, 0, 0); - JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, 0, 2,0,0); - JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, 0, 4,0,0); - - size_t quads = buffers.verts/4; - size_t count = buffers.verts/2*3; - JSValue jsidx = make_quad_indices_buffer(js, quads); - - JSValue ret = JS_NewObject(js); - JS_SetPropertyStr(js, ret, "pos", jspos); - JS_SetPropertyStr(js, ret, "uv", jsuv); - JS_SetPropertyStr(js, ret, "color", jscolor); - JS_SetPropertyStr(js, ret, "indices", jsidx); - JS_SetPropertyStr(js, ret, "vertices", number2js(js, buffers.verts)); - JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count)); - - return ret; -} - int sort_sprite(const sprite *a, const sprite *b) { if (a->layer != b->layer) return a->layer - b->layer; @@ -634,142 +502,6 @@ int sort_sprite(const sprite *a, const sprite *b) return 0; } -JSC_CCALL(gpu_make_sprite_queue, - sprite *sprites = NULL; - size_t quads = 0; - int needfree = 1; - - if (js_is_blob(js, argv[0])) { - // test for fastest - size_t size; - sprite *sprites = js_get_blob_data(js, &size, argv[0]); - quads = size/sizeof(*sprites); - needfree = 0; - for (int i = 0; i < quads; i++) - JS_DupValue(js,sprites[i].image); - } else { - quads = JS_ArrayLength(js, argv[0]); - if (quads == 0) - return JS_ThrowReferenceError(js, "Expected an array of sprites with length > 0."); - - arrsetcap(sprites, quads); - - for (int i = 0; i < quads; i++) { - JSValue sub = JS_GetPropertyUint32(js, argv[0], i); - sprite *jsp = js2sprite(js, sub); - if (jsp) { - arrput(sprites, *jsp); - JS_DupValue(js,jsp->image); - } - else { - sprite sp = {0}; - JS_GETATOM(js, sp.pos, sub, pos, vec2) - JS_GETATOM(js, sp.center, sub, center, vec2) - JS_GETATOM(js, sp.skew, sub, skew, vec2) - JS_GETATOM(js, sp.scale, sub, scale, vec2) - JS_GETATOM(js, sp.color,sub,color,color) - JS_GETATOM(js, sp.layer,sub,layer,number) - sp.image = JS_GetPropertyStr(js,sub,"image"); - sprite_apply(&sp); - arrput(sprites,sp); - } - JS_FreeValue(js, sub); - } - } - - qsort(sprites, quads, sizeof(sprite), sort_sprite); - - struct quad_buffers buffers = quad_buffers_new(quads*4); - - const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} }; - - rect uv; - rect uv_px; - JSValue cur_img = JS_NULL; - - for (size_t i = 0; i < quads; i++) { - sprite *s = &sprites[i]; - if (JS_IsNull(cur_img) || !JS_StrictEq(js, s->image, cur_img)) { - cur_img = s->image; - JS_GETATOM(js, uv, cur_img, rect, rect) - JS_GETATOM(js, uv_px, cur_img, rect_px, rect) - } - - HMM_Vec2 px_size = { - uv_px.w * s->scale.X, - uv_px.h * s->scale.Y - }; - - HMM_Vec2 anchor = { - px_size.X * s->center.X, - px_size.Y * s->center.Y - }; - - size_t base = i * 4; - - for (int v = 0; v < 4; v++) { - HMM_Vec2 lp = { - local[v].X * px_size.X - anchor.X, - local[v].Y * px_size.Y - anchor.Y - }; - - HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp)); - - buffers.pos[base + v] = world; - buffers.color[base + v] = s->color; - } - - /* UVs are still top-left-origin pixel coords, so keep previous packing */ - buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h }; - buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h }; - buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y }; - buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y }; - } - - JSValue mesh = quadbuffers_to_mesh(js, buffers); - - ret = JS_NewArray(js); - int first_index = 0; - int count = 0; - int n = 0; - JSValue img = JS_NULL; - - for (int i = 0; i < quads; i++) { - if (!JS_SameValue(js, sprites[i].image, img)) { - if (count > 0) { - JSValue q = JS_NewObject(js); - JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); - JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); - JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); - JS_SetPropertyStr(js, q, "image", img); - JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); - JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); - JS_SetPropertyUint32(js, ret, n++, q); - } - first_index = i*6; - count = 1; - img = JS_DupValue(js, sprites[i].image); - } else count++; - JS_FreeValue(js,sprites[i].image); - } - - if (count > 0) { - JSValue q = JS_NewObject(js); - JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); - JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); - JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); - JS_SetPropertyStr(js, q, "image", img); - JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); - JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); - JS_SetPropertyUint32(js, ret, n++, q); - } - - if (needfree) - arrfree(sprites); - - JS_FreeValue(js, mesh); -) - JSC_CCALL(geometry_rect_transform, // argv[0] = world‐space rect rect r = js2rect(js, argv[0]); @@ -899,14 +631,11 @@ JSC_CCALL(geometry_tilemap_to_data, xy_data[base + 7] = world_y + size_y; // Set UVs (normalized 0-1 for now) - uv_data[base + 0] = 0.0f; - uv_data[base + 1] = 0.0f; - uv_data[base + 2] = 1.0f; - uv_data[base + 3] = 0.0f; - uv_data[base + 4] = 0.0f; - uv_data[base + 5] = 1.0f; - uv_data[base + 6] = 1.0f; - uv_data[base + 7] = 1.0f; + uv_data[base + 0] = 0; uv_data[base + 1] = 1.0f; // now samples the TOP of the image + uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f; + uv_data[base + 4] = 0; uv_data[base + 5] = 0; + uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0; + // Set colors (check if tile has color property) SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f}; @@ -1270,82 +999,6 @@ JSC_CCALL(geometry_transform_xy_blob, ret = transformed_blob; ) -// RENDERITEM SYSTEM -typedef struct { - int layer; - float y; - JSValue val; -} RenderItem; - -#define MAX_RENDER_ITEMS 64000 -static RenderItem render_items[MAX_RENDER_ITEMS]; -static int render_item_count = 0; - -JSC_CCALL(geometry_renderitem_push, - if (argc < 3) { - return JS_ThrowTypeError(js, "renderitem_push requires 3 arguments: layer, y, val"); - } - - if (render_item_count >= MAX_RENDER_ITEMS) { - return JS_ThrowTypeError(js, "Maximum render items exceeded"); - } - - int layer; - double y; - - if (JS_ToInt32(js, &layer, argv[0]) < 0) { - return JS_ThrowTypeError(js, "layer must be a number"); - } - - if (JS_ToFloat64(js, &y, argv[1]) < 0) { - return JS_ThrowTypeError(js, "y must be a number"); - } - - render_items[render_item_count].layer = layer; - render_items[render_item_count].y = (float)y; - render_items[render_item_count].val = JS_DupValue(js, argv[2]); - render_item_count++; - - return JS_NULL; -) - -static int compare_render_items(const void *a, const void *b) -{ - const RenderItem *item_a = (const RenderItem *)a; - const RenderItem *item_b = (const RenderItem *)b; - - if (item_a->layer != item_b->layer) { - return item_a->layer - item_b->layer; - } - -// if (item_a->y != item_b->y) { -// return (item_b->y > item_a->y) ? 1 : -1; -// } - - return 0; -} - -JSC_CCALL(geometry_renderitem_sort, - qsort(render_items, render_item_count, sizeof(RenderItem), compare_render_items); - - JSValue result = JS_NewArray(js); - - for (int i = 0; i < render_item_count; i++) { - JS_SetPropertyUint32(js, result, i, JS_DupValue(js, render_items[i].val)); - } - - return result; -) - -JSC_CCALL(geometry_renderitem_clear, - for (int i = 0; i < render_item_count; i++) { - JS_FreeValue(js, render_items[i].val); - } - render_item_count = 0; - - return JS_NULL; -) - JSC_CCALL(geometry_array_blob, JSValue arr = argv[0]; size_t len = JS_ArrayLength(js,arr); @@ -1463,11 +1116,6 @@ static const JSCFunctionListEntry js_geometry_funcs[] = { MIST_FUNC_DEF(geometry, transform_xy_blob, 2), MIST_FUNC_DEF(gpu, tile, 4), MIST_FUNC_DEF(gpu, slice9, 4), - MIST_FUNC_DEF(gpu, make_sprite_mesh, 2), - MIST_FUNC_DEF(gpu, make_sprite_queue, 4), - MIST_FUNC_DEF(geometry, renderitem_push, 3), - MIST_FUNC_DEF(geometry, renderitem_sort, 0), - MIST_FUNC_DEF(geometry, renderitem_clear, 0), MIST_FUNC_DEF(geometry, array_blob, 2), MIST_FUNC_DEF(geometry, make_quad_indices, 1), MIST_FUNC_DEF(geometry, make_rect_quad, 3), diff --git a/source/qjs_imgui.cpp b/source/qjs_imgui.cpp index a55072b8..29e6c2d2 100644 --- a/source/qjs_imgui.cpp +++ b/source/qjs_imgui.cpp @@ -23,6 +23,8 @@ #include "imgui_impl_sdlrenderer3.h" //#include "imgui_impl_sdlgpu3.h" +#include "qjs_renderer.h" + #include "qjs_macros.h" // Forward declarations for the functions we need @@ -338,14 +340,17 @@ JSC_CCALL(imgui_pushid, JSC_CCALL(imgui_popid, ImGui::PopID(); ) JSC_CCALL(imgui_image, - SDL_Texture *tex = js2SDL_Texture(js,argv[0]); +/* SDL_Texture *tex = js2SDL_Texture(js,argv[0]); ImGui::Image((ImTextureID)tex, ImVec2(100, 100), ImVec2(0,0), ImVec2(1,1)); +*/ ) JSC_SCALL(imgui_imagebutton, +/* SDL_Texture *tex = js2SDL_Texture(js,argv[1]); if (ImGui::ImageButton(str, (ImTextureID)tex, ImVec2(100, 100))) JS_Call(js, argv[2], JS_NULL, 0, NULL); +*/ ) JSC_CCALL(imgui_sameline, ImGui::SameLine(js2number(js, argv[0])) ) @@ -589,7 +594,7 @@ JSC_CCALL(imgui_newframe, ) JSC_CCALL(imgui_endframe, - ImGui::Render(); +/* ImGui::Render(); SDL_Renderer *renderer = js2SDL_Renderer(js,argv[0]); int w, h; @@ -598,6 +603,7 @@ JSC_CCALL(imgui_endframe, SDL_SetRenderLogicalPresentation(renderer,0,0,SDL_LOGICAL_PRESENTATION_DISABLED); ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer); SDL_SetRenderLogicalPresentation(renderer,w,h,mode); +*/ ) JSC_CCALL(imgui_wantmouse, @@ -609,7 +615,7 @@ JSC_CCALL(imgui_wantkeys, ) JSC_CCALL(imgui_init, - ImGui::CreateContext(); +/* ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; @@ -632,6 +638,7 @@ JSC_CCALL(imgui_init, #ifdef ENABLE_IMNODES ImNodes::CreateContext(); #endif +*/ ) // ==================== IMPLOT FUNCTIONS ==================== diff --git a/source/qjs_sdl_surface.c b/source/qjs_sdl_surface.c index 2cbf8307..ea962e50 100644 --- a/source/qjs_sdl_surface.c +++ b/source/qjs_sdl_surface.c @@ -601,7 +601,7 @@ JSC_CCALL(surface_constructor, if (!raw) { JS_FreeValue(js, pixels_val); - return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer"); + return JS_ThrowTypeError(js, "pixels property must be a stoned blob"); } // Get pitch if provided, otherwise calculate it @@ -658,6 +658,251 @@ static const JSCFunctionListEntry js_SDL_Surface_funcs[] = { JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL), }; +// Helper function to create SDL_Surface from image object +static SDL_Surface* image_to_surface(JSContext *js, JSValue img_obj) +{ + // Get width and height + JSValue width_val = JS_GetPropertyStr(js, img_obj, "width"); + JSValue height_val = JS_GetPropertyStr(js, img_obj, "height"); + + if (JS_IsNull(width_val) || JS_IsNull(height_val)) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return NULL; + } + + int width, height; + if (JS_ToInt32(js, &width, width_val) < 0 || JS_ToInt32(js, &height, height_val) < 0) { + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + return NULL; + } + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + // Get format + JSValue format_val = JS_GetPropertyStr(js, img_obj, "format"); + SDL_PixelFormat format = js2pixelformat(js, format_val); + JS_FreeValue(js, format_val); + + if (format == SDL_PIXELFORMAT_UNKNOWN) + format = SDL_PIXELFORMAT_RGBA32; // default + + // Get pixels + JSValue pixels_val = JS_GetPropertyStr(js, img_obj, "pixels"); + size_t pixel_len; + void *pixel_data = js_get_blob_data(js, &pixel_len, pixels_val); + + if (!pixel_data) { + JS_FreeValue(js, pixels_val); + return NULL; + } + + // Get pitch (optional) + int pitch; + JSValue pitch_val = JS_GetPropertyStr(js, img_obj, "pitch"); + if (!JS_IsNull(pitch_val)) { + pitch = js2number(js, pitch_val); + JS_FreeValue(js, pitch_val); + } else { + pitch = width * SDL_BYTESPERPIXEL(format); + } + + // Create a copy of pixel data since SDL_Surface will own it + void *pixels_copy = malloc(pixel_len); + if (!pixels_copy) { + JS_FreeValue(js, pixels_val); + return NULL; + } + memcpy(pixels_copy, pixel_data, pixel_len); + JS_FreeValue(js, pixels_val); + + SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch); + if (!surface) { + free(pixels_copy); + return NULL; + } + + return surface; +} + +// Helper function to convert SDL_Surface back to image object +static JSValue surface_to_image(JSContext *js, SDL_Surface *surf) +{ + JSValue obj = JS_NewObject(js); + + JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w)); + JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h)); + JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format)); + 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_NULL; + } + locked = 1; + } + + // Add pixels as stoned blob + size_t byte_size = surf->pitch * surf->h; + JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size); + JS_SetPropertyStr(js, obj, "pixels", pixels); + + // Unlock if we locked + if (locked) + SDL_UnlockSurface(surf); + + // Add depth and hdr properties for completeness + JS_SetPropertyStr(js, obj, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(surf->format))); + JS_SetPropertyStr(js, obj, "hdr", JS_FALSE); + + return obj; +} + +// Scale function for image objects +JSC_CCALL(surface_scale_img, + if (argc < 2) + return JS_ThrowTypeError(js, "scale requires image and options objects"); + + SDL_Surface *src = image_to_surface(js, argv[0]); + if (!src) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + // Get width and height from options + JSValue width_val = JS_GetPropertyStr(js, argv[1], "width"); + JSValue height_val = JS_GetPropertyStr(js, argv[1], "height"); + + int new_width = src->w, new_height = src->h; + if (!JS_IsNull(width_val)) + JS_ToInt32(js, &new_width, width_val); + if (!JS_IsNull(height_val)) + JS_ToInt32(js, &new_height, height_val); + + JS_FreeValue(js, width_val); + JS_FreeValue(js, height_val); + + // Get scale mode + JSValue mode_val = JS_GetPropertyStr(js, argv[1], "mode"); + SDL_ScaleMode mode = js2SDL_ScaleMode(js, mode_val); + JS_FreeValue(js, mode_val); + + SDL_Surface *dst = SDL_ScaleSurface(src, new_width, new_height, mode); + SDL_DestroySurface(src); + + if (!dst) + return JS_ThrowInternalError(js, "Scale failed: %s", SDL_GetError()); + + JSValue result = surface_to_image(js, dst); + SDL_DestroySurface(dst); + return result; +) + +// Fill function for image objects +JSC_CCALL(surface_fill_img, + if (argc < 2) + return JS_ThrowTypeError(js, "fill requires image and color"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + colorf color = js2color(js, argv[1]); + rect r = { + .x = 0, + .y = 0, + .w = surf->w, + .h = surf->h + }; + + SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255)); + + JSValue result = surface_to_image(js, surf); + SDL_DestroySurface(surf); + return result; +) + +// Rect function for image objects +JSC_CCALL(surface_rect_img, + if (argc < 3) + return JS_ThrowTypeError(js, "rect requires image, rectangle, and color"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "First argument must be a valid image object"); + + rect r = js2rect(js, argv[1]); + colorf color = js2color(js, argv[2]); + + SDL_FillSurfaceRect(surf, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255, color.g*255, color.b*255, color.a*255)); + + JSValue result = surface_to_image(js, surf); + SDL_DestroySurface(surf); + return result; +) + +// Blit function for image objects +JSC_CCALL(surface_blit_img, + if (argc < 2) + return JS_ThrowTypeError(js, "blit requires destination and source images"); + + SDL_Surface *dst = image_to_surface(js, argv[0]); + if (!dst) + return JS_ThrowTypeError(js, "First argument must be a valid destination image"); + + SDL_Surface *src = image_to_surface(js, argv[1]); + if (!src) { + SDL_DestroySurface(dst); + return JS_ThrowTypeError(js, "Second argument must be a valid source image"); + } + + irect dr = {0}, *pdr = NULL; + if (argc > 2 && !JS_IsNull(argv[2])) { + dr = js2irect(js, argv[2]); + pdr = &dr; + } + + irect sr = {0}, *psr = NULL; + if (argc > 3 && !JS_IsNull(argv[3])) { + sr = js2irect(js, argv[3]); + psr = &sr; + } + + SDL_ScaleMode mode = SDL_SCALEMODE_LINEAR; + if (argc > 4) + mode = js2SDL_ScaleMode(js, argv[4]); + + SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE); + SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode); + + SDL_DestroySurface(src); + JSValue result = surface_to_image(js, dst); + SDL_DestroySurface(dst); + return result; +) + +// Duplicate function for image objects +JSC_CCALL(surface_dup_img, + if (argc < 1) + return JS_ThrowTypeError(js, "dup requires an image object"); + + SDL_Surface *surf = image_to_surface(js, argv[0]); + if (!surf) + return JS_ThrowTypeError(js, "Argument must be a valid image object"); + + SDL_Surface *dup = SDL_DuplicateSurface(surf); + SDL_DestroySurface(surf); + + if (!dup) + return JS_ThrowInternalError(js, "Duplicate failed: %s", SDL_GetError()); + + JSValue result = surface_to_image(js, dup); + SDL_DestroySurface(dup); + return result; +) + // Generic convert function for pixel format/colorspace conversion JSC_CCALL(surface_convert_generic, if (argc < 2) @@ -776,9 +1021,22 @@ JSC_CCALL(surface_convert_generic, return JS_ThrowInternalError(js, "Pixel conversion failed: %s", SDL_GetError()); } - // Return a stoned blob with the converted pixels - ret = js_new_blob_stoned_copy(js, dst_pixels, dst_size); + // Create result image object + JSValue result = JS_NewObject(js); + JS_SetPropertyStr(js, result, "width", JS_NewInt32(js, src_width)); + JS_SetPropertyStr(js, result, "height", JS_NewInt32(js, src_height)); + JS_SetPropertyStr(js, result, "format", pixelformat2js(js, dst_format)); + JS_SetPropertyStr(js, result, "pitch", JS_NewInt32(js, dst_pitch)); + + JSValue pixels = js_new_blob_stoned_copy(js, dst_pixels, dst_size); free(dst_pixels); + JS_SetPropertyStr(js, result, "pixels", pixels); + + // Add depth and hdr for consistency + JS_SetPropertyStr(js, result, "depth", JS_NewInt32(js, SDL_BITSPERPIXEL(dst_format))); + JS_SetPropertyStr(js, result, "hdr", JS_FALSE); + + return result; ) JSValue js_sdl_surface_use(JSContext *js) @@ -801,5 +1059,12 @@ JSValue js_sdl_surface_use(JSContext *js) JS_SetPropertyStr(js, ctor, "compress_bc4", JS_NewCFunction(js, js_surface_compress_bc4, "compress_bc4", 1)); JS_SetPropertyStr(js, ctor, "compress_bc5", JS_NewCFunction(js, js_surface_compress_bc5, "compress_bc5", 1)); + // Add standalone image manipulation functions + JS_SetPropertyStr(js, ctor, "scale", JS_NewCFunction(js, js_surface_scale_img, "scale", 2)); + JS_SetPropertyStr(js, ctor, "fill", JS_NewCFunction(js, js_surface_fill_img, "fill", 2)); + JS_SetPropertyStr(js, ctor, "rect", JS_NewCFunction(js, js_surface_rect_img, "rect", 3)); + JS_SetPropertyStr(js, ctor, "blit", JS_NewCFunction(js, js_surface_blit_img, "blit", 5)); + JS_SetPropertyStr(js, ctor, "dup", JS_NewCFunction(js, js_surface_dup_img, "dup", 1)); + return ctor; } \ No newline at end of file diff --git a/source/qjs_sdl_video.c b/source/qjs_sdl_video.c index 75025883..4013cf4b 100644 --- a/source/qjs_sdl_video.c +++ b/source/qjs_sdl_video.c @@ -21,24 +21,6 @@ void SDL_Window_free(JSRuntime *rt, SDL_Window *w) SDL_DestroyWindow(w); } -void SDL_Renderer_free(JSRuntime *rt, SDL_Renderer *r) -{ - SDL_DestroyRenderer(r); -} - -void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){ - SDL_DestroyTexture(t); -} - -QJSCLASS(SDL_Texture, - 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(SDL_Renderer,) QJSCLASS(SDL_Window,) void SDL_Cursor_free(JSRuntime *rt, SDL_Cursor *c) @@ -56,12 +38,8 @@ extern JSValue vec22js(JSContext *js, HMM_Vec2 v); extern colorf js2color(JSContext *js, JSValue v); extern double js2number(JSContext *js, JSValue v); extern JSValue number2js(JSContext *js, double n); -extern SDL_Texture *js2SDL_Texture(JSContext *js, JSValue v); -extern JSValue SDL_Texture2js(JSContext *js, SDL_Texture *t); extern SDL_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); @@ -258,17 +236,6 @@ static JSValue js_window_constructor(JSContext *js, JSValueConst new_target, int return window_obj; } -// Window functions -JSC_SCALL(SDL_Window_make_renderer, - SDL_Window *win = js2SDL_Window(js,self); - SDL_Renderer *renderer = SDL_CreateRenderer(win, NULL); - if (!renderer) { - return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError()); - } - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - return SDL_Renderer2js(js,renderer); -) - JSC_CCALL(SDL_Window_fullscreen, SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN) ) @@ -743,7 +710,6 @@ JSValue js_window_sync(JSContext *js, JSValue self, int argc, JSValue *argv) static const JSCFunctionListEntry js_SDL_Window_funcs[] = { MIST_FUNC_DEF(SDL_Window, fullscreen, 0), - MIST_FUNC_DEF(SDL_Window, make_renderer, 1), MIST_FUNC_DEF(SDL_Window, keyboard_shown, 0), MIST_FUNC_DEF(window, theme, 0), MIST_FUNC_DEF(window, safe_area, 0), @@ -781,839 +747,6 @@ static const JSCFunctionListEntry js_SDL_Window_funcs[] = { MIST_FUNC_DEF(window, updateSurfaceRects, 1), }; -// Renderer functions -JSC_CCALL(SDL_Renderer_clear, - SDL_Renderer *renderer = js2SDL_Renderer(js,self); - SDL_RenderClear(renderer); -) - -JSC_CCALL(SDL_Renderer_present, - SDL_Renderer *ren = js2SDL_Renderer(js,self); - SDL_RenderPresent(ren); -) - -JSC_CCALL(renderer_load_texture, - 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); -) - -JSC_CCALL(renderer_get_image, - SDL_Renderer *r = js2SDL_Renderer(js,self); - SDL_Surface *surf = NULL; - if (!JS_IsNull(argv[0])) { - rect rect = js2rect(js,argv[0]); - surf = SDL_RenderReadPixels(r,&rect); - } else - surf = SDL_RenderReadPixels(r,NULL); - if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer"); - return SDL_Surface2js(js,surf); -) - -JSC_CCALL(renderer_line, - if (!JS_IsArray(js, argv[0])) return JS_ThrowReferenceError(js, "First arugment must be an array of points."); - - 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."); - - SDL_FPoint points[len]; - assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint)); - for (int i = 0; i < len; i++) { - JSValue val = JS_GetPropertyUint32(js,argv[0],i); - HMM_Vec2 pt = js2vec2(js,val); - JS_FreeValue(js,val); - - points[i] = (SDL_FPoint){pt.x, pt.y}; - } - - SDL_RenderLines(r,points,len); -) - -JSC_CCALL(renderer_point, - SDL_Renderer *r = js2SDL_Renderer(js, self); - - if (JS_IsArray(js, argv[0])) { - int len = JS_ArrayLength(js, argv[0]); - SDL_FPoint pts[len]; - - for (int i = 0; i < len; ++i) { - JSValue val = JS_GetPropertyUint32(js, argv[0], i); - HMM_Vec2 pt = js2vec2(js, val); - JS_FreeValue(js, val); - pts[i] = (SDL_FPoint){pt.x, pt.y}; - } - SDL_RenderPoints(r, pts, len); - return JS_NULL; - } - - HMM_Vec2 pt = js2vec2(js, argv[0]); - SDL_RenderPoint(r, pt.x, pt.y); -) - -JSC_CCALL(renderer_texture, - SDL_Renderer *ren = js2SDL_Renderer(js, self); - SDL_Texture *tex = js2SDL_Texture(js,argv[0]); - rect src = js2rect(js,argv[1]); - rect dst = js2rect(js,argv[2]); - 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; - float r,g,b,a; - SDL_GetRenderDrawColorFloat(ren, &r,&g,&b,&a); - SDL_SetTextureColorModFloat(tex, r,g,b); - SDL_SetTextureAlphaModFloat(tex, a); - SDL_RenderTextureRotated(ren, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE); - SDL_SetTextureColorModFloat(tex,1,1,1); - SDL_SetTextureAlphaModFloat(tex,a); -) - -JSC_CCALL(renderer_rects, - SDL_Renderer *r = js2SDL_Renderer(js, self); - - /* array-of-rectangles case */ - if (JS_IsArray(js, argv[0])) { - int len = JS_ArrayLength(js, argv[0]); - if (len <= 0) return JS_NULL; - - SDL_FRect rects[len]; - - for (int i = 0; i < len; ++i) { - JSValue val = JS_GetPropertyUint32(js, argv[0], i); - rect rect = js2rect(js, val); - JS_FreeValue(js, val); - rects[i] = rect; - } - - if (!SDL_RenderFillRects(r, rects, len)) - return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError()); - return JS_NULL; - } - - /* single-rect path */ - 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, - 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"); - JSValue indices = JS_GetPropertyStr(js,argv[1], "indices"); - int vertices = js_getnum_str(js, argv[1], "vertices"); - int count = js_getnum_str(js, argv[1], "num_indices"); - - size_t pos_stride, indices_stride, uv_stride, color_stride; - void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL); - void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL); - void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL); - void *colordata = get_gpu_buffer(js,color,&color_stride, NULL); - - SDL_Texture *tex = js2SDL_Texture(js,argv[0]); - - 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()); - - JS_FreeValue(js,pos); - JS_FreeValue(js,color); - JS_FreeValue(js,uv); - JS_FreeValue(js,indices); -) - -JSC_CCALL(renderer_geometry_raw, - SDL_Renderer *r = js2SDL_Renderer(js,self); - - // argv[0] is texture - SDL_Texture *tex = NULL; - if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsNull(argv[0])) - tex = js2SDL_Texture(js,argv[0]); - - // Get blob data - size_t xy_size, color_size, uv_size, indices_size; - void *xy_data = js_get_blob_data(js, &xy_size, argv[1]); - int xy_stride = js2number(js, argv[2]); - void *color_data = js_get_blob_data(js, &color_size, argv[3]); - int color_stride = js2number(js, argv[4]); - void *uv_data = js_get_blob_data(js, &uv_size, argv[5]); - int uv_stride = js2number(js, argv[6]); - int num_vertices = js2number(js, argv[7]); - void *indices_data = js_get_blob_data(js, &indices_size, argv[8]); - int num_indices = js2number(js, argv[9]); - int size_indices = js2number(js, argv[10]); - - if (!xy_data || !color_data || !uv_data) - return JS_ThrowTypeError(js, "Invalid geometry data"); - - if (!SDL_RenderGeometryRaw(r, tex, - (const float *)xy_data, xy_stride, - (const SDL_FColor *)color_data, color_stride, - (const float *)uv_data, uv_stride, - num_vertices, - indices_data, num_indices, size_indices)) - return JS_ThrowReferenceError(js, "Error rendering geometry: %s", SDL_GetError()); -) - -JSC_CCALL(renderer_geometry2, - SDL_Renderer *r = js2SDL_Renderer(js, self); - - SDL_Texture *tex = js2SDL_Texture(js, argv[0]); - - JSValue geo = argv[1]; - int vertices = js_getnum_str(js, geo, "vertices"); - int count = js_getnum_str(js, geo, "num_indices"); - - JSValue pos_v = JS_GetPropertyStr(js, geo, "pos"); - JSValue color_v = JS_GetPropertyStr(js, geo, "color"); - JSValue uv_v = JS_GetPropertyStr(js, geo, "uv"); - JSValue idx_v = JS_GetPropertyStr(js, geo, "indices"); - - size_t pos_stride, color_stride, uv_stride, idx_stride; - void *pos_data = get_gpu_buffer(js, pos_v, &pos_stride, NULL); - void *color_data = get_gpu_buffer(js, color_v, &color_stride, NULL); - void *uv_data = get_gpu_buffer(js, uv_v, &uv_stride, NULL); - void *idx_data = get_gpu_buffer(js, idx_v, &idx_stride, NULL); - - SDL_Vertex *verts = malloc(vertices * sizeof *verts); - for(int i = 0; i < vertices; ++i) { - const float *p = (const float *)((uint8_t *)pos_data + i * pos_stride); - const float *u = (const float *)((uint8_t *)uv_data + i * uv_stride); - const float *c = (const float *)((uint8_t *)color_data + i * color_stride); - - 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] }; - } - - const int *indices32 = NULL; - int *tmp_idx = NULL; - - if(idx_data && count) { - if(idx_stride == 4) indices32 = (const int *)idx_data; - else { - tmp_idx = malloc(count * sizeof *tmp_idx); - if(idx_stride == 2) { - const uint16_t *src = idx_data; - for(int i = 0; i < count; ++i) tmp_idx[i] = src[i]; - } else { /* 8-bit */ - const uint8_t *src = idx_data; - for(int i = 0; i < count; ++i) tmp_idx[i] = src[i]; - } - indices32 = tmp_idx; - } - } - - printf("num verts, num indices: %d, %d\n", vertices, count); - - if(!SDL_RenderGeometry(r, tex, verts, vertices, indices32, count)) - printf("Error rendering geometry: %s\n", SDL_GetError()); - - free(tmp_idx); - free(verts); - - JS_FreeValue(js, pos_v); - JS_FreeValue(js, color_v); - JS_FreeValue(js, uv_v); - JS_FreeValue(js, idx_v); -) - -static sprite js_getsprite(JSContext *js, JSValue sp) -{ - sprite *s = js2sprite(js, sp); - if (s) - return *s; - - sprite pp = {0}; - return pp; -} - -JSC_CCALL(renderer_sprite, - 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"); - tex = js2SDL_Texture(js, texture_val); - JS_FreeValue(js, texture_val); - rect uv; - JSValue rect_val = JS_GetPropertyStr(js, sp.image, "rect"); - uv = js2rect(js, rect_val); - JS_FreeValue(js, rect_val); - - float w = uv.w, h = uv.h; - - HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y }; - HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y }; - HMM_Vec2 bl_local = { -sp.center.X, -sp.center.Y + h }; - - HMM_Vec2 world_tl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tl_local)); - HMM_Vec2 world_tr = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tr_local)); - HMM_Vec2 world_bl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, bl_local)); - - SDL_FPoint origin = (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(r, tex, &uv, &origin, &right, &down)) - return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError()); -) - -/* logical presentation helpers */ -typedef struct { const char *name; SDL_RendererLogicalPresentation pres; } pres_entry; - -static const pres_entry k_pres_table[] = { - { "stretch", SDL_LOGICAL_PRESENTATION_STRETCH }, - { "letterbox", SDL_LOGICAL_PRESENTATION_LETTERBOX }, - { "overscan", SDL_LOGICAL_PRESENTATION_OVERSCAN }, - { "integer", SDL_LOGICAL_PRESENTATION_INTEGER_SCALE }, - { NULL, SDL_LOGICAL_PRESENTATION_DISABLED } /* fallback */ -}; - -static JSValue logicalpresentation2js(JSContext *js, - SDL_RendererLogicalPresentation pres){ - const pres_entry *it; - for(it = k_pres_table; it->name; ++it) - if(it->pres == pres) break; - return JS_NewString(js, it->name ? it->name : "disabled"); -} - -static SDL_RendererLogicalPresentation -js2SDL_LogicalPresentation(JSContext *js, JSValue v){ - if(JS_IsNull(v)) return SDL_LOGICAL_PRESENTATION_DISABLED; - const char *s = JS_ToCString(js, v); - if(!s) return SDL_LOGICAL_PRESENTATION_DISABLED; - const pres_entry *it; - for(it = k_pres_table; it->name; ++it) - if(!strcmp(it->name, s)) break; - JS_FreeCString(js, s); - return it->pres; -} - -// Renderer getter/setter functions - -// 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_IsNull(val)) - SDL_SetRenderTarget(r, NULL); - else { - SDL_Texture *tex = js2SDL_Texture(js,val); - SDL_SetRenderTarget(r,tex); - } - return JS_NULL; -} - -// 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_NULL; -} - -// 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_IsNull(val)) - SDL_SetRenderViewport(r,NULL); - else { - rect view = js2rect(js,val); - SDL_SetRenderViewport(r,&view); - } - return JS_NULL; -} - -// 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_IsNull(val)) - SDL_SetRenderClipRect(r,NULL); - else { - rect clip = js2rect(js,val); - SDL_SetRenderClipRect(r,&clip); - } - return JS_NULL; -} - -// 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}); -} - -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_NULL; -} - -// 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_NULL; -} - -// 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_NULL; -} - -// 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_NULL; -} - -// 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_NULL; -} - -// 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); -) - -// 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); -) - -// Flush any pending rendering commands -JSC_CCALL(renderer_flush, - SDL_Renderer *r = js2SDL_Renderer(js,self); - SDL_FlushRenderer(r); -) - -// 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_IsNull(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_IsNull(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_IsNull(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), - MIST_FUNC_DEF(renderer, point, 1), - MIST_FUNC_DEF(renderer, texture, 5), - MIST_FUNC_DEF(renderer, rects, 1), - MIST_FUNC_DEF(renderer, geometry, 2), - MIST_FUNC_DEF(renderer, geometry_raw, 11), - MIST_FUNC_DEF(renderer, geometry2, 2), - MIST_FUNC_DEF(renderer, sprite, 1), - MIST_FUNC_DEF(renderer, load_texture, 1), - MIST_FUNC_DEF(renderer, get_image, 1), - MIST_FUNC_DEF(renderer, coordsFromWindow, 1), - MIST_FUNC_DEF(renderer, coordsToWindow, 1), - MIST_FUNC_DEF(renderer, flush, 0), - - // 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), - - // 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; -} - -// 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_IsNull(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_IsNull(pixels_val)) { - // Create surface first, then texture - size_t size; - uint8_t *pixels = js_get_blob_data(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[] = { - 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), -}; - // Cursor creation function JSC_CCALL(sdl_create_cursor, SDL_Surface *surf = js2SDL_Surface(js, argv[0]); @@ -1637,191 +770,6 @@ JSC_CCALL(sdl_set_cursor, SDL_SetCursor(cursor); ) -// Texture getter/setter functions - -// Alpha mod getter/setter -JSValue js_texture_get_alphaMod(JSContext *js, JSValue self) -{ - SDL_Texture *tex = js2SDL_Texture(js,self); - 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_NULL; -} - -// 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_NULL; -} - -// 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_NULL; -} - -// 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_NULL; -} - -// 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_IsNull(argv[0])) - update_rect = js2rect(js, argv[0]); - - if (argc >= 2) { - size_t size; - pixels = js_get_blob_data(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_IsNull(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()); -) - -// 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[] = { - 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; -) - -#include "qjs_wota.h" - JSValue js_sdl_video_use(JSContext *js) { if (!SDL_Init(SDL_INIT_VIDEO)) return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError()); @@ -1830,8 +778,6 @@ JSValue js_sdl_video_use(JSContext *js) { // Initialize classes QJSCLASSPREP_FUNCS(SDL_Window) - QJSCLASSPREP_FUNCS(SDL_Renderer) - QJSCLASSPREP_FUNCS(SDL_Texture) QJSCLASSPREP_NO_FUNCS(SDL_Cursor) // Create window constructor @@ -1840,19 +786,8 @@ JSValue js_sdl_video_use(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); - // Set constructors in endowments JS_SetPropertyStr(js, ret, "window", window_ctor); - JS_SetPropertyStr(js, ret, "texture", texture_ctor); - - // Add utility function - JS_SetPropertyStr(js, ret, "createWindowAndRenderer", - JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4)); // Add cursor functions JS_SetPropertyStr(js, ret, "createCursor", diff --git a/source/qjs_staef.c b/source/qjs_staef.c new file mode 100644 index 00000000..2fd65547 --- /dev/null +++ b/source/qjs_staef.c @@ -0,0 +1,128 @@ +#include "qjs_staef.h" +#include "font.h" +#include "qjs_macros.h" +#include "HandmadeMath.h" +#include "render.h" +#include "stb_ds.h" +#include "cell.h" +#include "qjs_sdl.h" +#include + +// External functions from jsffi.c +typedef HMM_Vec4 colorf; +extern colorf js2color(JSContext *js, JSValue v); +extern JSValue vec22js(JSContext *js, HMM_Vec2 v); +extern HMM_Vec2 js2vec2(JSContext *js, JSValue v); +extern rect js2rect(JSContext *js, JSValue v); +extern double js2number(JSContext *js, JSValue v); +extern JSValue number2js(JSContext *js, double g); +extern void *js_get_blob_data(JSContext *js, size_t *len, JSValue v); +extern JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t size); +extern JSValue quads_to_mesh(JSContext *js, text_vert *buffer); +extern int uncaught_exception(JSContext *js, JSValue ret); + +// QuickJS class for font +QJSCLASS(font,) + +// Font constructor +JSC_CCALL(staef_font_new, + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data"); + + double height = js2number(js, argv[1]); + font *f = MakeFont(data, len, (int)height); + if (!f) return JS_ThrowReferenceError(js, "could not create font"); + + ret = font2js(js, f); + + // 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_new_blob_stoned_copy(js, f->surface->pixels, byte_size)); + + if (locked) + SDL_UnlockSurface(f->surface); + + JS_SetPropertyStr(js, ret, "surface", surfData); + } +) + +// Calculate text size +JSC_CCALL(staef_font_text_size, + font *f = js2font(js, self); + const char *str = JS_ToCString(js, argv[0]); + float letterSpacing = argc > 1 ? js2number(js, argv[1]) : 0; + float wrap = argc > 2 ? js2number(js, argv[2]) : -1; + + ret = vec22js(js, measure_text(str, f, letterSpacing, wrap)); + JS_FreeCString(js, str); +) + +// Generate text buffer (mesh data) +JSC_CCALL(staef_font_make_text_buffer, + font *f = js2font(js, self); + const char *s = JS_ToCString(js, argv[0]); + rect rectpos = js2rect(js, argv[1]); + colorf c = js2color(js, argv[2]); + float wrap = argc > 3 ? js2number(js, argv[3]) : -1; + + HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y }; + text_vert *buffer = renderText(s, startpos, f, c, wrap); + ret = quads_to_mesh(js, buffer); + JS_FreeCString(js, s); + arrfree(buffer); +) + +// Font property getters/setters +JSC_GETSET(font, linegap, number) + +// Font methods +static const JSCFunctionListEntry js_font_funcs[] = { + MIST_FUNC_DEF(staef_font, text_size, 3), + MIST_FUNC_DEF(staef_font, make_text_buffer, 4), + CGETSET_ADD(font, linegap), +}; + +// Font constructor function +static JSValue js_font_constructor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) +{ + return js_staef_font_new(ctx, JS_NULL, argc, argv); +} + +// Initialize the staef module +JSValue js_staef_use(JSContext *js) +{ + JSValue mod = JS_NewObject(js); + JSValue proto; + + // Initialize font class + JS_NewClassID(&js_font_id); + JS_NewClass(JS_GetRuntime(js), js_font_id, &js_font_class); + proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, proto, js_font_funcs, countof(js_font_funcs)); + JS_SetClassProto(js, js_font_id, proto); + + // Create font constructor + JSValue font_ctor = JS_NewCFunction2(js, js_font_constructor, "font", 2, JS_CFUNC_constructor, 0); + JS_SetConstructor(js, font_ctor, proto); + + // Add font constructor to module (lowercase to match "new staef.font") + JS_SetPropertyStr(js, mod, "font", font_ctor); + + return mod; +} diff --git a/source/qjs_staef.h b/source/qjs_staef.h new file mode 100644 index 00000000..93547aea --- /dev/null +++ b/source/qjs_staef.h @@ -0,0 +1,8 @@ +#ifndef QJS_STAEF_H +#define QJS_STAEF_H + +#include "quickjs.h" + +JSValue js_staef_use(JSContext *js); + +#endif // QJS_STAEF_H diff --git a/source/qoi.h b/source/qoi.h index f2800b0c..63381a9d 100644 --- a/source/qoi.h +++ b/source/qoi.h @@ -427,7 +427,7 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) { run = 0; } - index_pos = QOI_COLOR_HASH(px) % 64; + index_pos = QOI_COLOR_HASH(px) & (64 - 1); if (index[index_pos].v == px.v) { bytes[p++] = QOI_OP_INDEX | index_pos; @@ -574,7 +574,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { run = (b1 & 0x3f); } - index[QOI_COLOR_HASH(px) % 64] = px; + index[QOI_COLOR_HASH(px) & (64 - 1)] = px; } pixels[px_pos + 0] = px.rgba.r; @@ -646,4 +646,4 @@ void *qoi_read(const char *filename, qoi_desc *desc, int channels) { } #endif /* QOI_NO_STDIO */ -#endif /* QOI_IMPLEMENTATION */ +#endif /* QOI_IMPLEMENTATION */ \ No newline at end of file diff --git a/tests/qr_drag.ce b/tests/qr_drag.ce index 02448638..ee8643da 100644 --- a/tests/qr_drag.ce +++ b/tests/qr_drag.ce @@ -136,7 +136,7 @@ $_.receiver(e => { case "drop_file": log.console(`got ${e.data} dropped`) var data = io.slurpbytes(e.data) - img = graphics.make_texture(data) + img = graphics.image_decode(data) var qr_surf = img;//extract_qr_surface(img) var qr_surf_scaled = qr_surf.scale([qr_surf.width/4, qr_surf.height/4]) var image = {surface:qr_surf_scaled}