text rendering

This commit is contained in:
2025-01-08 17:54:25 -06:00
parent 9d5243e9c3
commit 293eb509da
5 changed files with 128 additions and 219 deletions

View File

@@ -89,7 +89,7 @@ if get_option('enet')
endif endif
sources = [] sources = []
src += ['anim.c', 'config.c', 'datastream.c','font.c','gameobject.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','warp.c','yugine.c', 'wildmatch.c'] src += ['anim.c', 'config.c', 'datastream.c','font.c','gameobject.c','HandmadeMath.c','jsffi.c','model.c','render.c','script.c','simplex.c','spline.c', 'timer.c', 'transform.c','warp.c','yugine.c', 'wildmatch.c', 'sprite.c']
imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp'] imsrc = ['GraphEditor.cpp','ImCurveEdit.cpp','ImGradient.cpp','imgui_draw.cpp','imgui_tables.cpp','imgui_widgets.cpp','imgui.cpp','ImGuizmo.cpp','imnodes.cpp','implot_items.cpp','implot.cpp', 'imgui_impl_sdlrenderer3.cpp', 'imgui_impl_sdl3.cpp']

View File

@@ -10,6 +10,25 @@ var cur = {};
cur.images = []; cur.images = [];
cur.samplers = []; cur.samplers = [];
function full_upload(mesh)
{
var cmds = render._main.acquire_cmd_buffer();
render._main.upload(cmds, [mesh.pos, mesh.uv, mesh.color, mesh.indices]);
cmds.submit();
}
function queue_sprite_mesh(queue)
{
var sprites = queue.filter(x => x.type === 'sprite');
var mesh = render._main.make_sprite_mesh(sprites);
for (var i = 0; i < sprites.length; i++) {
sprites[i].mesh = mesh;
sprites[i].first_index = i*6;
sprites[i].num_indices = 6;
}
full_upload(mesh);
}
function bind_pipeline(pass, pipeline) function bind_pipeline(pass, pipeline)
{ {
make_pipeline(pipeline) make_pipeline(pipeline)
@@ -82,6 +101,16 @@ sprite_pipeline.target = {
depth: "d32 float s8" depth: "d32 float s8"
}; };
sprite_pipeline.blend = {
enabled:true,
src_rgb: "src_alpha", // zero/one/src_color/one_minus_src_color/dst_color/one_minus_dst_color/src_alpha/one_minus_src_alpha/dst_alpha/one_minus_dst_alpha/constant_color/one_minus_constant_color/src_alpha_saturate
dst_rgb: "one_minus_src_alpha",
op_rgb: "add", // add/sub/rev_sub/min/max
src_alpha: "one",
dst_alpha: "one_minus_src_alpha",
op_alpha: "add"
};
var dbgline_pipeline = Object.create(base_pipeline); var dbgline_pipeline = Object.create(base_pipeline);
dbgline_pipeline.vertex = "dbgline.vert.hlsl" dbgline_pipeline.vertex = "dbgline.vert.hlsl"
dbgline_pipeline.fragment = "dbgline.frag.hlsl" dbgline_pipeline.fragment = "dbgline.frag.hlsl"
@@ -354,10 +383,9 @@ function upload_model(model)
var bufs = []; var bufs = [];
for (var i in model) { for (var i in model) {
if (typeof model[i] !== 'object') continue; if (typeof model[i] !== 'object') continue;
if (i === 'indices') model[i].index = true;
bufs.push(model[i]); bufs.push(model[i]);
} }
tbuffer = render._main.upload(this, bufs, tbuffer); render._main.upload(this, bufs);
} }
function bind_model(pass,pipeline,model) function bind_model(pass,pipeline,model)
@@ -391,27 +419,31 @@ function bind_mat(pass, pipeline, mat)
} }
} }
function group_sprites_by_texture(sprites) function group_sprites_by_texture(sprites, mesh)
{ {
if (sprites.length === 0) return; if (sprites.length === 0) return;
var groups = [];
var lasttex = sprites[0].image;
var group = [];
var first = 0;
for (var i = 0; i < sprites.length; i++) { for (var i = 0; i < sprites.length; i++) {
if (lasttex !== sprites[i].image) { sprites[i].mesh = mesh;
groups.push({image:lasttex, num_indices:(i-first)*6, first_index:first*6}); sprites[i].first_index = i*6;
lasttex = sprites[i].image; sprites[i].num_indices = 6;
first = i; }
group = []; return;
var groups = [];
var group = {image:sprites[0].image, first_index:0};
var count = 1;
for (var i = 1; i < sprites.length; i++) {
if (sprites[i].image === group.image) {
count++;
continue;
} }
group.num_indices = count*6;
group.push(sprites[i]) var newgroup = {image:sprites[i].image, first_index:group.first_index+group.num_indices};
group = newgroup;
groups.push(group);
count=1;
} }
groups.push({ group.num_indices = count*6;
image:lasttex,num_indices:(sprites.length-first)*6,first_index:first*6
})
return groups; return groups;
} }
@@ -464,36 +496,49 @@ function render_camera(cmds, camera)
} }
if (render_queue.length == 0) return; if (render_queue.length == 0) return;
var spritemesh = render._main.make_sprite_mesh(render_queue); queue_sprite_mesh(render_queue);
cmds.upload_model(spritemesh);
var pass = cmds.render_pass(camera.target); var pass = cmds.render_pass(camera.target);
var camera = prosperon.camera; var camera = prosperon.camera;
var draw_cmds = group_sprites_by_texture(render_queue); var pipeline;
for (var group of draw_cmds) { var mesh;
var pipeline = sprite_pipeline; var img;
var mesh = spritemesh; var modelslot;
var img = group.image;
img.sampler = std_sampler; for (var group of render_queue) {
bind_pipeline(pass, pipeline); if (pipeline != group.pipeline) {
bind_mat(pass, pipeline, {diffuse:img}); pipeline = group.pipeline;
bind_model(pass,pipeline,spritemesh); bind_pipeline(pass, pipeline);
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
if (typeof camslot !== 'undefined') var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
cmds.camera(camera, pass, undefined, camslot); if (typeof camslot !== 'undefined')
var modelslot = get_pipeline_ubo_slot(pipeline, "model"); cmds.camera(camera, pass, undefined, camslot);
if (typeof modelslot !== 'undefined') {
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo); modelslot = get_pipeline_ubo_slot(pipeline, "model");
cmds.push_vertex_uniform_data(modelslot, ubo); if (typeof modelslot !== 'undefined') {
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
cmds.push_vertex_uniform_data(modelslot, ubo);
}
} }
pass.draw_indexed(group.num_indices, 1, mesh.first_index, 0, 0);
if (mesh != group.mesh) {
mesh = group.mesh;
bind_model(pass,pipeline,mesh);
}
if (img != group.image) {
img = group.image;
img.sampler = std_sampler;
bind_mat(pass,pipeline,{diffuse:img});
}
pass.draw_indexed(group.num_indices, 1, group.first_index, 0, 0);
} }
pass.end(); pass.end();
render_queue = []; render_queue = [];
spritemesh = undefined;
} }
function mode_rect(src,dst,mode = "stretch") function mode_rect(src,dst,mode = "stretch")
@@ -573,6 +618,7 @@ var quad_model;
render.init = function () { render.init = function () {
shader_type = render._main.shader_format()[0]; shader_type = render._main.shader_format()[0];
prosperon.font = render.get_font('fonts/c64.ttf', 8);
std_sampler = render._main.make_sampler({ std_sampler = render._main.make_sampler({
min_filter: "nearest", min_filter: "nearest",
@@ -624,14 +670,11 @@ function draw_sprites()
var sparray = layer[img]; var sparray = layer[img];
if (sparray.length === 0) continue; if (sparray.length === 0) continue;
var geometry = render._main.make_sprite_mesh(sparray); var geometry = render._main.make_sprite_mesh(sparray);
render.geometry(sparray[0], geometry);
} }
} }
} }
render.circle = function render_circle(pos, radius, color, inner_radius = 1) { render.circle = function render_circle(pos, radius, color, inner_radius = 1) {
check_flush();
if (inner_radius >= 1) inner_radius = inner_radius / radius; if (inner_radius >= 1) inner_radius = inner_radius / radius;
else if (inner_radius < 0) inner_radius = 1.0; else if (inner_radius < 0) inner_radius = 1.0;
@@ -656,58 +699,6 @@ render.poly = function render_poly(points, color, transform) {
render.draw(buffer); render.draw(buffer);
}; };
var nextflush = undefined;
function flush() {
nextflush?.();
nextflush = undefined;
}
// If flush_fn was already on deck, it does not flush. Otherwise, flushes and then sets the flush fn
function check_flush(flush_fn) {
if (!nextflush) nextflush = flush_fn;
else if (nextflush !== flush_fn) {
nextflush();
nextflush = flush_fn;
}
}
render.flush = check_flush;
render.forceflush = function forceflush()
{
if (nextflush) nextflush();
nextflush = undefined;
cur.shader = undefined;
}
var poly_cache = [];
var poly_idx = 0;
var poly_ssbo;
function poly_e() {
var e;
poly_idx++;
if (poly_idx > poly_cache.length) {
e = {
transform: os.make_transform(),
color: Color.white,
};
poly_cache.push(e);
return e;
}
var e = poly_cache[poly_idx - 1];
e.transform.unit();
return e;
}
function flush_poly() {
if (poly_idx === 0) return;
render.use_shader(queued_shader, queued_pipe);
var base = render.make_particle_ssbo(poly_cache.slice(0, poly_idx), poly_ssbo);
render.use_mat({baseinstance:base});
render.draw(shape.quad, poly_ssbo, poly_idx);
poly_idx = 0;
}
// render.line has uv and can be texture mapped; dbg_line is hardware standard lines // render.line has uv and can be texture mapped; dbg_line is hardware standard lines
render.line = function render_line(points, color = Color.white, thickness = 1, pipe = base_pipeline) { render.line = function render_line(points, color = Color.white, thickness = 1, pipe = base_pipeline) {
// render._main.line(points, color); // render._main.line(points, color);
@@ -755,21 +746,22 @@ render.rectangle = function render_rectangle(rect, color = Color.white, pipe = b
render._main.fillrect(rect,color); render._main.fillrect(rect,color);
}; };
render.text = function text(text, rect, font = cur_font, size = 0, color = Color.white, wrap = 0, pipe = base_pipeline) { render.text = function text(text, rect, font = prosperon.font, size = 0, color = Color.white, wrap = 0, pipeline = sprite_pipeline) {
// if (typeof font === 'string') if (typeof font === 'string')
// font = render.get_font(font) font = render.get_font(font)
// var mesh = os.make_text_buffer(text, rect, 0, color, wrap, font); var mesh = os.make_text_buffer(text, rect, 0, color, wrap, font);
// render._main.geometry(font.texture, mesh); full_upload(mesh)
render_queue.push({ render_queue.push({
type: 'text', type: 'geometry',
text, mesh: mesh,
rect, image: font,
font, texture:font.texture,
size, pipeline,
color, first_index:0,
wrap, num_indices:mesh.indices.length
pipe
}); });
return; return;
if (typeof font === 'string') if (typeof font === 'string')
@@ -781,8 +773,6 @@ render.text = function text(text, rect, font = cur_font, size = 0, color = Color
if (rect.anchor_y) if (rect.anchor_y)
pos.y -= rect.anchor_y*(font.ascent-font.descent); pos.y -= rect.anchor_y*(font.ascent-font.descent);
os.make_text_buffer(str, pos, size, color, wrap, font); // this puts text into buffer os.make_text_buffer(str, pos, size, color, wrap, font); // this puts text into buffer
cur_font = font;
check_flush(render.flush_text);
}; };
var tttsize = render.text_size; var tttsize = render.text_size;
@@ -793,34 +783,6 @@ render.text_size = function(str, font, ...args)
return tttsize(str,font, ...args); return tttsize(str,font, ...args);
} }
var lasttex = undefined;
var img_cache = [];
var img_idx = 0;
function flush_img() {
if (img_idx === 0) return;
render.use_shader(spritessboshader);
var startidx = render.make_sprite_ssbo(img_cache.slice(0, img_idx), sprite_ssbo);
render.use_mat({baseinstance:startidx});
cur.images = [lasttex];
render.draw(shape.quad, sprite_ssbo, img_idx);
lasttex = undefined;
img_idx = 0;
}
function img_e() {
img_idx++;
if (img_idx > img_cache.length) {
var e = {
transform: os.make_transform(),
shade: Color.white,
};
img_cache.push(e);
return e;
}
return img_cache[img_idx - 1];
}
var stencil_write = { var stencil_write = {
compare: "always", compare: "always",
fail_op: "replace", fail_op: "replace",
@@ -850,7 +812,6 @@ render.stencil_writer = stencil_writer;
// objects by default draw where the stencil buffer is 0 // objects by default draw where the stencil buffer is 0
render.fillmask = function fillmask(ref) render.fillmask = function fillmask(ref)
{ {
render.forceflush();
var pipe = stencil_writer(ref); var pipe = stencil_writer(ref);
render.use_shader('screenfill.cg', pipe); render.use_shader('screenfill.cg', pipe);
render.draw(shape.quad); render.draw(shape.quad);
@@ -907,27 +868,11 @@ render.tile = function tile(image, rect = [0,0], color = Color.white)
var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y]; var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y];
if (!lasttex) {
check_flush(flush_img);
lasttex = tex;
}
if (lasttex !== tex) {
flush_img();
lasttex = tex;
}
render._main.tile(image.texture, rect, image.rect, 1); render._main.tile(image.texture, rect, image.rect, 1);
return; return;
} }
render.geometry = function geometry(material, geometry) render.image = function image(image, rect = [0,0], rotation = 0, color = Color.white, pipeline = sprite_pipeline) {
{
render._main.geometry(material.diffuse.texture, geometry);
}
// queues to be flushed later
render.image = function image(image, rect = [0,0], rotation = 0, color = Color.white, pipeline = base_pipeline) {
if (!image) throw Error ('Need an image to render.') if (!image) throw Error ('Need an image to render.')
if (typeof image === "string") if (typeof image === "string")
image = game.texture(image); image = game.texture(image);
@@ -937,6 +882,7 @@ render.image = function image(image, rect = [0,0], rotation = 0, color = Color.w
var T = os.make_transform(); var T = os.make_transform();
T.rect(rect); T.rect(rect);
render_queue.push({ render_queue.push({
type: 'sprite',
transform: T, transform: T,
color: color, color: color,
image:image, image:image,
@@ -949,71 +895,16 @@ render.images = function images(image, rects)
if (!image) throw Error ('Need an image to render.'); if (!image) throw Error ('Need an image to render.');
if (typeof image === "string") image = game.texture(image); if (typeof image === "string") image = game.texture(image);
for (var rect of rects) render.image(image,rect); for (var rect of rects) render.image(image,rect);
return;
var tex = image.texture;
if (!tex) return;
var image_size = calc_image_size(image);
if (!lasttex) {
check_flush(flush_img);
lasttex = tex;
}
if (lasttex !== tex) {
flush_img();
lasttex = tex;
}
// rects = rects.flat();
var rect = rects[0];
var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y];
var offset = size.scale([rect.anchor_x, rect.anchor_y]);
for (var rect of rects) {
var e = img_e();
var pos = [rect.x,rect.y].sub(offset);
e.transform.trs(pos, undefined, size);
e.image = image;
e.shade = Color.white;
}
return;
} }
// slice is given in pixels // slice is given in pixels
render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white) { render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white) {
render.image(image,rect,undefined,color); return; render.image(image,rect,undefined,color);
if (typeof image === 'string')
image = game.texture(image);
rect.width ??= image.texture.width;
rect.height ??= image.texture.height;
slice = clay.normalizeSpacing(slice);
render._main.slice9(image.texture, rect, slice); render._main.slice9(image.texture, rect, slice);
}; };
var textssbos = []; var textssbos = [];
var tdraw = 0; var tdraw = 0;
var cur_font = undefined;
render.flush_text = function flush_text() {
if (!render.textshader) return;
tdraw++;
if (textssbos.length < tdraw) textssbos.push(render.make_textssbo());
var textssbo = textssbos[tdraw - 1];
var amt = render.flushtext(textssbo); // load from buffer into ssbo
if (amt === 0) {
tdraw--;
return;
}
render.use_shader(render.textshader);
render.use_mat({ text: cur_font.texture });
render.draw(shape.quad, textssbo, amt);
};
var fontcache = {}; var fontcache = {};
var datas= []; var datas= [];
@@ -1034,6 +925,8 @@ render.get_font = function get_font(path,size)
return fontcache[fontstr]; return fontcache[fontstr];
} }
render.doc = "Draw shapes in screen space."; render.doc = "Draw shapes in screen space.";
render.cross.doc = "Draw a cross centered at pos, with arm length size."; render.cross.doc = "Draw a cross centered at pos, with arm length size.";
render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle."; render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle.";
@@ -1296,7 +1189,6 @@ try {
}); });
try { prosperon.appupdate(dt); } catch(e) { console.error(e) } try { prosperon.appupdate(dt); } catch(e) { console.error(e) }
input.procdown(); input.procdown();
try { try {
update_emitters(dt * game.timescale); update_emitters(dt * game.timescale);

View File

@@ -259,7 +259,6 @@ Cmdline.register_order(
driver = "metal" driver = "metal"
break break
} }
console.log(os.sys())
render._main = prosperon.window.make_gpu(false, driver); render._main = prosperon.window.make_gpu(false, driver);
render._main.window = prosperon.window; render._main.window = prosperon.window;
render._main.claim_window(prosperon.window); render._main.claim_window(prosperon.window);

View File

@@ -8,5 +8,6 @@ float4 main(PSInput input) : SV_TARGET
{ {
float4 color = diffuse.Sample(smp, input.uv); float4 color = diffuse.Sample(smp, input.uv);
color *= input.color; color *= input.color;
clip(color.a-0.01);
return color; return color;
} }

View File

@@ -1082,7 +1082,8 @@ void SDL_GPUCopyPass_free(JSRuntime *rt, SDL_GPUCopyPass *c) { }
void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) { } void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) { }
#define GPURELEASECLASS(NAME) \ #define GPURELEASECLASS(NAME) \
void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { SDL_ReleaseGPU##NAME(global_gpu, c); } \ void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { \
SDL_ReleaseGPU##NAME(global_gpu, c); } \
QJSCLASS(SDL_GPU##NAME) \ QJSCLASS(SDL_GPU##NAME) \
QJSCLASS(transform) QJSCLASS(transform)
@@ -1661,7 +1662,7 @@ JSC_CCALL(os_make_text_buffer,
color[i] = buffer[i].color; color[i] = buffer[i].color;
} }
arrfree(buffer);
JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0); JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0); JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
@@ -1672,7 +1673,7 @@ JSC_CCALL(os_make_text_buffer,
JSValue jsidx = make_quad_indices_buffer(js, quads); JSValue jsidx = make_quad_indices_buffer(js, quads);
JS_FreeCString(js, s); JS_FreeCString(js, s);
arrfree(buffer);
ret = JS_NewObject(js); ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos_atom, jspos); JS_SetProperty(js, ret, pos_atom, jspos);
JS_SetProperty(js, ret, uv_atom, jsuv); JS_SetProperty(js, ret, uv_atom, jsuv);
@@ -3222,9 +3223,6 @@ static void generate_normals(float* positions, size_t vertex_count, uint16_t* in
// 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. // 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 // image: a standard prosperon image of a surface, rect, and texture
// color: the color this sprite should be hued by // color: the color this sprite should be hued by
// This might change depending on the backend, so best to not investigate. It should be consumed with "renderer_geometry"
// It might be a list of rectangles, it might be a handful of buffers, etc ..
JSC_CCALL(renderer_make_sprite_mesh, JSC_CCALL(renderer_make_sprite_mesh,
JSValue sprites = argv[0]; JSValue sprites = argv[0];
JSValue old = argv[1]; JSValue old = argv[1];
@@ -4784,6 +4782,17 @@ static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = {
MIST_FUNC_DEF(cmd, blit, 1), MIST_FUNC_DEF(cmd, blit, 1),
}; };
JSC_SCALL(buffer_name,
SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js,self);
SDL_GPUDevice *gpu;
JS_GETPROP(js, gpu, self, gpu, SDL_GPUDevice)
SDL_SetGPUBufferName(gpu,buffer,str);
)
static const JSCFunctionListEntry js_SDL_GPUBuffer_funcs[] = {
MIST_FUNC_DEF(buffer, name, 1),
};
JSC_CCALL(compute_dispatch, JSC_CCALL(compute_dispatch,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self); SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
SDL_DispatchGPUCompute(pass,js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2])); SDL_DispatchGPUCompute(pass,js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]));
@@ -4975,7 +4984,15 @@ static const JSCFunctionListEntry js_SDL_Texture_funcs[] = {
MIST_FUNC_DEF(texture, mode, 1), MIST_FUNC_DEF(texture, mode, 1),
}; };
JSC_SCALL(texture_name,
SDL_GPUTexture *texture = js2SDL_GPUTexture(js,self);
SDL_GPUDevice *gpu;
JS_GETPROP(js,gpu,self,gpu,SDL_GPUDevice)
SDL_SetGPUTextureName(gpu,texture,str);
)
static const JSCFunctionListEntry js_SDL_GPUTexture_funcs[] = { static const JSCFunctionListEntry js_SDL_GPUTexture_funcs[] = {
MIST_FUNC_DEF(texture, name, 1),
}; };
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0]))) JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))
@@ -6556,7 +6573,7 @@ void ffi_load(JSContext *js) {
// QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline) // QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline)
// QJSCLASSPREP_FUNCS(SDL_GPUSampler) // QJSCLASSPREP_FUNCS(SDL_GPUSampler)
// QJSCLASSPREP_FUNCS(SDL_GPUShader) // QJSCLASSPREP_FUNCS(SDL_GPUShader)
// QJSCLASSPREP_FUNCS(SDL_GPUBuffer) QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
// QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer) // QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)