Files
cell/source/qjs_renderer.c

505 lines
17 KiB
C

#include "qjs_renderer.h"
#include <SDL3/SDL.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "HandmadeMath.h"
QJSCLASS(SDL_Renderer,)
rect transform_rect(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;
}
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(SDL_Renderer_draw_color,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
colorf color = js2color(js,argv[0]);
SDL_SetRenderDrawColorFloat(renderer, color.r,color.g,color.b,color.a);
)
JSC_CCALL(SDL_Renderer_rect,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = JS_ArrayLength(js,argv[0]);
rect rects[len];
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
rects[i] = transform_rect(js2rect(js,val), &cam_mat);
JS_FreeValue(js,val);
}
SDL_RenderRects(r,rects,len);
return JS_UNDEFINED;
}
rect rect = js2rect(js,argv[0]);
rect = transform_rect(rect, &cam_mat);
SDL_RenderRect(r, &rect);
)
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);
JS_SetPropertyStr(js,ret,"width", number2js(js,tex->w));
JS_SetPropertyStr(js,ret,"height", number2js(js,tex->h));
)
JSC_CCALL(SDL_Renderer_fillrect,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = JS_ArrayLength(js,argv[0]);
rect rects[len];
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
rects[i] = js2rect(js,val);
JS_FreeValue(js,val);
}
if (!SDL_RenderFillRects(r,rects,len))
return JS_ThrowReferenceError(js, "Could not render rectangle: %s", SDL_GetError());
}
rect rect = transform_rect(js2rect(js,argv[0]),&cam_mat);
if (!SDL_RenderFillRect(r, &rect))
return JS_ThrowReferenceError(js, "Could not render rectangle: %s", SDL_GetError());
)
JSC_CCALL(renderer_texture,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
rect dst = transform_rect(js2rect(js,argv[1]), &cam_mat);
if (!JS_IsUndefined(argv[3])) {
colorf color = js2color(js,argv[3]);
SDL_SetTextureColorModFloat(tex, color.r, color.g, color.b);
SDL_SetTextureAlphaModFloat(tex,color.a);
}
if (JS_IsUndefined(argv[2]))
SDL_RenderTexture(renderer,tex,NULL,&dst);
else {
rect src = js2rect(js,argv[2]);
SDL_RenderTextureRotated(renderer, tex, &src, &dst, 0, NULL, SDL_FLIP_NONE);
}
)
JSC_CCALL(renderer_tile,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
if (!renderer) return JS_ThrowTypeError(js,"self was not a renderer");
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
if (!tex) return JS_ThrowTypeError(js,"first argument was not a texture");
rect dst = js2rect(js,argv[1]);
if (!dst.w) dst.w = tex->w;
if (!dst.h) dst.h = tex->h;
float scale = js2number(js,argv[3]);
if (!scale) scale = 1;
if (JS_IsUndefined(argv[2]))
SDL_RenderTextureTiled(renderer,tex,NULL,scale, &dst);
else {
rect src = js2rect(js,argv[2]);
SDL_RenderTextureTiled(renderer,tex,&src,scale, &dst);
}
)
JSC_CCALL(renderer_slice9,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
lrtb bounds = js2lrtb(js,argv[2]);
rect src, dst;
src = transform_rect(js2rect(js,argv[3]),&cam_mat);
dst = transform_rect(js2rect(js,argv[1]), &cam_mat);
SDL_RenderTexture9Grid(renderer, tex,
JS_IsUndefined(argv[3]) ? NULL : &src,
bounds.l, bounds.r, bounds.t, bounds.b, 0.0,
JS_IsUndefined(argv[1]) ? NULL : &dst);
)
JSC_CCALL(renderer_get_image,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_Surface *surf = NULL;
if (!JS_IsUndefined(argv[0])) {
rect rect = js2rect(js,argv[0]);
surf = SDL_RenderReadPixels(r,&rect);
} else
surf = SDL_RenderReadPixels(r,NULL);
if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer");
return SDL_Surface2js(js,surf);
)
JSC_SCALL(renderer_fasttext,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[2])) {
colorf color = js2color(js,argv[2]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
HMM_Vec2 pos = js2vec2(js,argv[1]);
pos.y += 8;
HMM_Vec2 tpos = HMM_MulM3V3(cam_mat, (HMM_Vec3){pos.x,pos.y,1}).xy;
SDL_RenderDebugText(r, tpos.x, tpos.y, str);
)
JSC_CCALL(renderer_line,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = JS_ArrayLength(js,argv[0]);
HMM_Vec2 points[len];
assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint));
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
points[i] = js2vec2(js,val);
JS_FreeValue(js,val);
}
SDL_RenderLines(r,points,len);
}
)
JSC_CCALL(renderer_point,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = JS_ArrayLength(js,argv[0]);
HMM_Vec2 points[len];
assert(sizeof(HMM_Vec2) ==sizeof(SDL_FPoint));
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
points[i] = js2vec2(js,val);
JS_FreeValue(js,val);
}
SDL_RenderPoints(r, points, len);
return JS_UNDEFINED;
}
HMM_Vec2 point = transform_point(r, js2vec2(js,argv[0]), &cam_mat);
SDL_RenderPoint(r,point.x,point.y);
)
// Function to translate a list of 2D points
void Translate2DPoints(HMM_Vec2 *points, int count, HMM_Vec3 position, HMM_Quat rotation, HMM_Vec3 scale) {
// Precompute the 2D rotation matrix from the quaternion
float xx = rotation.x * rotation.x;
float yy = rotation.y * rotation.y;
float zz = rotation.z * rotation.z;
float xy = rotation.x * rotation.y;
float zw = rotation.z * rotation.w;
// Extract 2D affine rotation and scaling
float m00 = (1.0f - 2.0f * (yy + zz)) * scale.x; // Row 1, Column 1
float m01 = (2.0f * (xy + zw)) * scale.y; // Row 1, Column 2
float m10 = (2.0f * (xy - zw)) * scale.x; // Row 2, Column 1
float m11 = (1.0f - 2.0f * (xx + zz)) * scale.y; // Row 2, Column 2
// Translation components (ignore the z position)
float tx = position.x;
float ty = position.y;
// Transform each point
for (int i = 0; i < count; ++i) {
HMM_Vec2 p = points[i];
points[i].x = m00 * p.x + m01 * p.y + tx;
points[i].y = m10 * p.x + m11 * p.y + ty;
}
}
// 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], "count");
size_t pos_stride, indices_stride, uv_stride, color_stride;
void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL);
void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL);
void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL);
void *colordata = get_gpu_buffer(js,color,&color_stride, NULL);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
HMM_Vec2 *trans_pos = malloc(vertices*sizeof(HMM_Vec2));
memcpy(trans_pos,posdata, sizeof(HMM_Vec2)*vertices);
for (int i = 0; i < vertices; i++)
trans_pos[i] = HMM_MulM3V3(cam_mat, (HMM_Vec3){trans_pos[i].x, trans_pos[i].y, 1}).xy;
if (!SDL_RenderGeometryRaw(r, tex, trans_pos, pos_stride,colordata,color_stride,uvdata, uv_stride, vertices, idxdata, count, indices_stride))
ret = JS_ThrowReferenceError(js, "Error rendering geometry: %s",SDL_GetError());
free(trans_pos);
JS_FreeValue(js,pos);
JS_FreeValue(js,color);
JS_FreeValue(js,uv);
JS_FreeValue(js,indices);
)
JSC_CCALL(renderer_logical_size,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 v = js2vec2(js,argv[0]);
SDL_SetRenderLogicalPresentation(r,v.x,v.y,SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
)
JSC_CCALL(renderer_viewport,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderViewport(r,NULL);
else {
rect view = js2rect(js,argv[0]);
SDL_SetRenderViewport(r,&view);
}
)
JSC_CCALL(renderer_get_viewport,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_Rect vp;
SDL_GetRenderViewport(r, &vp);
rect re;
re.x = vp.x;
re.y = vp.y;
re.h = vp.h;
re.w = vp.w;
return rect2js(js,re);
)
JSC_CCALL(renderer_clip,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderClipRect(r,NULL);
else {
rect view = js2rect(js,argv[0]);
SDL_SetRenderClipRect(r,&view);
}
)
JSC_CCALL(renderer_scale,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 v = js2vec2(js,argv[0]);
SDL_SetRenderScale(r, v.x, v.y);
)
JSC_CCALL(renderer_vsync,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_SetRenderVSync(r,js2number(js,argv[0]));
)
// This returns the coordinates inside the
JSC_CCALL(renderer_coords,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 pos, coord;
pos = js2vec2(js,argv[0]);
SDL_RenderCoordinatesFromWindow(r,pos.x,pos.y, &coord.x, &coord.y);
return vec22js(js,coord);
)
JSC_CCALL(renderer_camera,
int centered = JS_ToBool(js,argv[1]);
SDL_Renderer *ren = js2SDL_Renderer(js,self);
SDL_Rect vp;
SDL_GetRenderViewport(ren, &vp);
HMM_Mat3 proj;
proj.Columns[0] = (HMM_Vec3){1,0,0};
proj.Columns[1] = (HMM_Vec3){0,-1,0};
if (centered)
proj.Columns[2] = (HMM_Vec3){vp.w/2.0,vp.h/2.0,1};
else
proj.Columns[2] = (HMM_Vec3){0,vp.h,1};
transform *tra = js2transform(js,argv[0]);
HMM_Mat3 view;
view.Columns[0] = (HMM_Vec3){1,0,0};
view.Columns[1] = (HMM_Vec3){0,1,0};
view.Columns[2] = (HMM_Vec3){-tra->pos.x, -tra->pos.y,1};
cam_mat = HMM_MulM3(proj,view);
)
JSC_CCALL(renderer_screen2world,
HMM_Mat3 inv = HMM_InvGeneralM3(cam_mat);
HMM_Vec3 pos = js2vec3(js,argv[0]);
return vec22js(js, HMM_MulM3V3(inv, pos).xy);
)
JSC_CCALL(renderer_target,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderTarget(r, NULL);
else {
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
SDL_SetRenderTarget(r,tex);
}
)
// Given an array of sprites, make the necessary geometry
// A sprite is expected to have:
// transform: a transform encoding position and rotation. its scale is in pixels - so a scale of 1 means the image will draw only on a single pixel.
// image: a standard prosperon image of a surface, rect, and texture
// color: the color this sprite should be hued by
JSC_CCALL(renderer_make_sprite_mesh,
JSValue sprites = argv[0];
size_t quads = JS_ArrayLength(js,argv[0]);
size_t verts = quads*4;
size_t count = quads*6;
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,sprites,i);
JSValue jstransform = JS_GetPropertyStr(js,sub,"transform");
JSValue jssrc = JS_GetPropertyStr(js,sub,"src");
JSValue jscolor = JS_GetPropertyStr(js,sub,"color");
HMM_Vec4 color;
rect src;
if (JS_IsUndefined(jssrc))
src = (rect){.x = 0, .y = 0, .w = 1, .h = 1};
else
src = js2rect(js,jssrc);
if (JS_IsUndefined(jscolor))
color = (HMM_Vec4){1,1,1,1};
else
color = js2vec4(js,jscolor);
// Calculate the base index for the current quad
size_t base = i * 4;
// Define the UV coordinates based on the source rectangle
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] = color;
colordata[base+1] = color;
colordata[base+2] = color;
colordata[base+3] = color;
JS_FreeValue(js,jstransform);
JS_FreeValue(js,sub);
JS_FreeValue(js,jscolor);
JS_FreeValue(js,jssrc);
}
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "pos", make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetPropertyStr(js, ret, "uv", make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetPropertyStr(js, ret, "color", make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 4, 0,0));
JS_SetPropertyStr(js, ret, "indices", make_quad_indices_buffer(js, quads));
JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
JS_SetPropertyStr(js, ret, "count", number2js(js, count));
)
static const JSCFunctionListEntry js_renderer_funcs[] = {
JS_CFUNC_DEF("clear", 0, js_renderer_clear),
JS_CFUNC_DEF("present", 0, js_renderer_present),
JS_CFUNC_DEF("draw_color", 1, js_renderer_draw_color),
JS_CFUNC_DEF("rect", 2, js_renderer_rect),
JS_CFUNC_DEF("fillrect", 2, js_renderer_fillrect),
JS_CFUNC_DEF("line", 2, js_renderer_line),
JS_CFUNC_DEF("point", 2, js_renderer_point),
JS_CFUNC_DEF("load_texture", 1, js_renderer_load_texture),
JS_CFUNC_DEF("texture", 4, js_renderer_texture),
JS_CFUNC_DEF("slice9", 4, js_renderer_slice9),
JS_CFUNC_DEF("tile", 4, js_renderer_tile),
JS_CFUNC_DEF("get_image", 1, js_renderer_get_image),
JS_CFUNC_DEF("fasttext", 2, js_renderer_fasttext),
JS_CFUNC_DEF("geometry", 2, js_renderer_geometry),
JS_CFUNC_DEF("scale", 1, js_renderer_scale),
JS_CFUNC_DEF("logical_size", 1, js_renderer_logical_size),
JS_CFUNC_DEF("viewport", 1, js_renderer_viewport),
JS_CFUNC_DEF("clip", 1, js_renderer_clip),
JS_CFUNC_DEF("vsync", 1, js_renderer_vsync),
JS_CFUNC_DEF("coords", 1, js_renderer_coords),
JS_CFUNC_DEF("camera", 2, js_renderer_camera),
JS_CFUNC_DEF("get_viewport", 0, js_renderer_get_viewport),
JS_CFUNC_DEF("screen2world", 1, js_renderer_screen2world),
JS_CFUNC_DEF("target", 1, js_renderer_target),
JS_CFUNC_DEF("make_sprite_mesh",2, js_renderer_make_sprite_mesh),
};
JSC_CCALL(mod_create,
SDL_Window *win = js2SDL_Window(js,self);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, win);
SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, str);
SDL_Renderer *r = SDL_CreateRendererWithProperties(props);
SDL_DestroyProperties(props);
if (!r) return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError());
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
return SDL_Renderer2js(js,r);
)
static const JSCFunctionListEntry js_mod_funcs[] = {
JS_CFUNC_DEF("create", 1, js_mod_create)
};
JSValue js_renderer_use(JSContext *ctx) {
// Create an object that will hold all the "renderer" methods
JSValue obj = JS_NewObject(ctx);
// Add all the above C functions as properties of that object
JS_SetPropertyFunctionList(ctx, obj,
js_renderer_funcs,
sizeof(js_renderer_funcs)/sizeof(JSCFunctionListEntry));
return obj;
}