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
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']

View File

@@ -10,6 +10,25 @@ var cur = {};
cur.images = [];
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)
{
make_pipeline(pipeline)
@@ -82,6 +101,16 @@ sprite_pipeline.target = {
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);
dbgline_pipeline.vertex = "dbgline.vert.hlsl"
dbgline_pipeline.fragment = "dbgline.frag.hlsl"
@@ -354,10 +383,9 @@ function upload_model(model)
var bufs = [];
for (var i in model) {
if (typeof model[i] !== 'object') continue;
if (i === 'indices') model[i].index = true;
bufs.push(model[i]);
}
tbuffer = render._main.upload(this, bufs, tbuffer);
render._main.upload(this, bufs);
}
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;
var groups = [];
var lasttex = sprites[0].image;
var group = [];
var first = 0;
for (var i = 0; i < sprites.length; i++) {
if (lasttex !== sprites[i].image) {
groups.push({image:lasttex, num_indices:(i-first)*6, first_index:first*6});
lasttex = sprites[i].image;
first = i;
group = [];
sprites[i].mesh = mesh;
sprites[i].first_index = i*6;
sprites[i].num_indices = 6;
}
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.push(sprites[i])
group.num_indices = count*6;
var newgroup = {image:sprites[i].image, first_index:group.first_index+group.num_indices};
group = newgroup;
groups.push(group);
count=1;
}
groups.push({
image:lasttex,num_indices:(sprites.length-first)*6,first_index:first*6
})
group.num_indices = count*6;
return groups;
}
@@ -464,36 +496,49 @@ function render_camera(cmds, camera)
}
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 camera = prosperon.camera;
var draw_cmds = group_sprites_by_texture(render_queue);
for (var group of draw_cmds) {
var pipeline = sprite_pipeline;
var mesh = spritemesh;
var img = group.image;
img.sampler = std_sampler;
bind_pipeline(pass, pipeline);
bind_mat(pass, pipeline, {diffuse:img});
bind_model(pass,pipeline,spritemesh);
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
if (typeof camslot !== 'undefined')
cmds.camera(camera, pass, undefined, camslot);
var modelslot = get_pipeline_ubo_slot(pipeline, "model");
if (typeof modelslot !== 'undefined') {
var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo);
cmds.push_vertex_uniform_data(modelslot, ubo);
var pipeline;
var mesh;
var img;
var modelslot;
for (var group of render_queue) {
if (pipeline != group.pipeline) {
pipeline = group.pipeline;
bind_pipeline(pass, pipeline);
var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer');
if (typeof camslot !== 'undefined')
cmds.camera(camera, pass, undefined, camslot);
modelslot = get_pipeline_ubo_slot(pipeline, "model");
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();
render_queue = [];
spritemesh = undefined;
}
function mode_rect(src,dst,mode = "stretch")
@@ -573,6 +618,7 @@ var quad_model;
render.init = function () {
shader_type = render._main.shader_format()[0];
prosperon.font = render.get_font('fonts/c64.ttf', 8);
std_sampler = render._main.make_sampler({
min_filter: "nearest",
@@ -624,14 +670,11 @@ function draw_sprites()
var sparray = layer[img];
if (sparray.length === 0) continue;
var geometry = render._main.make_sprite_mesh(sparray);
render.geometry(sparray[0], geometry);
}
}
}
render.circle = function render_circle(pos, radius, color, inner_radius = 1) {
check_flush();
if (inner_radius >= 1) inner_radius = inner_radius / radius;
else if (inner_radius < 0) inner_radius = 1.0;
@@ -656,58 +699,6 @@ render.poly = function render_poly(points, color, transform) {
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 = function render_line(points, color = Color.white, thickness = 1, pipe = base_pipeline) {
// 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.text = function text(text, rect, font = cur_font, size = 0, color = Color.white, wrap = 0, pipe = base_pipeline) {
// if (typeof font === 'string')
// font = render.get_font(font)
// var mesh = os.make_text_buffer(text, rect, 0, color, wrap, font);
// render._main.geometry(font.texture, mesh);
render.text = function text(text, rect, font = prosperon.font, size = 0, color = Color.white, wrap = 0, pipeline = sprite_pipeline) {
if (typeof font === 'string')
font = render.get_font(font)
var mesh = os.make_text_buffer(text, rect, 0, color, wrap, font);
full_upload(mesh)
render_queue.push({
type: 'text',
text,
rect,
font,
size,
color,
wrap,
pipe
type: 'geometry',
mesh: mesh,
image: font,
texture:font.texture,
pipeline,
first_index:0,
num_indices:mesh.indices.length
});
return;
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)
pos.y -= rect.anchor_y*(font.ascent-font.descent);
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;
@@ -793,34 +783,6 @@ render.text_size = function(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 = {
compare: "always",
fail_op: "replace",
@@ -850,7 +812,6 @@ render.stencil_writer = stencil_writer;
// objects by default draw where the stencil buffer is 0
render.fillmask = function fillmask(ref)
{
render.forceflush();
var pipe = stencil_writer(ref);
render.use_shader('screenfill.cg', pipe);
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];
if (!lasttex) {
check_flush(flush_img);
lasttex = tex;
}
if (lasttex !== tex) {
flush_img();
lasttex = tex;
}
render._main.tile(image.texture, rect, image.rect, 1);
return;
}
render.geometry = function geometry(material, geometry)
{
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) {
render.image = function image(image, rect = [0,0], rotation = 0, color = Color.white, pipeline = sprite_pipeline) {
if (!image) throw Error ('Need an image to render.')
if (typeof image === "string")
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();
T.rect(rect);
render_queue.push({
type: 'sprite',
transform: T,
color: color,
image:image,
@@ -949,71 +895,16 @@ render.images = function images(image, rects)
if (!image) throw Error ('Need an image to render.');
if (typeof image === "string") image = game.texture(image);
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
render.slice9 = function slice9(image, rect = [0,0], slice = 0, color = Color.white) {
render.image(image,rect,undefined,color); return;
if (typeof image === 'string')
image = game.texture(image);
rect.width ??= image.texture.width;
rect.height ??= image.texture.height;
slice = clay.normalizeSpacing(slice);
render.image(image,rect,undefined,color);
render._main.slice9(image.texture, rect, slice);
};
var textssbos = [];
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 datas= [];
@@ -1034,6 +925,8 @@ render.get_font = function get_font(path,size)
return fontcache[fontstr];
}
render.doc = "Draw shapes in screen space.";
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.";
@@ -1296,7 +1189,6 @@ try {
});
try { prosperon.appupdate(dt); } catch(e) { console.error(e) }
input.procdown();
try {
update_emitters(dt * game.timescale);

View File

@@ -259,7 +259,6 @@ Cmdline.register_order(
driver = "metal"
break
}
console.log(os.sys())
render._main = prosperon.window.make_gpu(false, driver);
render._main.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);
color *= input.color;
clip(color.a-0.01);
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) { }
#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(transform)
@@ -1661,7 +1662,7 @@ JSC_CCALL(os_make_text_buffer,
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 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);
JS_FreeCString(js, s);
arrfree(buffer);
ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos_atom, jspos);
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.
// image: a standard prosperon image of a surface, rect, and texture
// 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,
JSValue sprites = argv[0];
JSValue old = argv[1];
@@ -4784,6 +4782,17 @@ static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = {
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,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
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),
};
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[] = {
MIST_FUNC_DEF(texture, name, 1),
};
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_GPUSampler)
// QJSCLASSPREP_FUNCS(SDL_GPUShader)
// QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
// QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)