#include "qjs_renderer.h" #include "quickjs.h" #include #include "qjs_macros.h" #include #include #include #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_arrlen(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_SetProperty(js,ret,width, number2js(js,tex->w)); JS_SetProperty(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_arrlen(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_arrlen(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_arrlen(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_arrlen(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_GetProperty(js,sub,transform); JSValue jssrc = JS_GetProperty(js,sub,src); JSValue jscolor = JS_GetProperty(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_SetProperty(js, ret, pos, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0)); JS_SetProperty(js, ret, uv, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0)); JS_SetProperty(js, ret, color, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 4, 0,0)); JS_SetProperty(js, ret, indices, make_quad_indices_buffer(js, quads)); JS_SetProperty(js, ret, vertices, number2js(js, verts)); JS_SetProperty(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; }