From cba6f4fd594d88ef3e9a5623401bc10894c8bf2d Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 29 Jul 2025 19:14:58 -0500 Subject: [PATCH] gpu renderer --- prosperon/prosperon.cm | 423 ++++++++++++++++++++++++++--------------- scripts/graphics.cm | 7 - source/qjs_geometry.c | 2 +- source/qjs_sdl_gpu.c | 1 - 4 files changed, 271 insertions(+), 162 deletions(-) diff --git a/prosperon/prosperon.cm b/prosperon/prosperon.cm index fd56fb22..667d4218 100644 --- a/prosperon/prosperon.cm +++ b/prosperon/prosperon.cm @@ -7,27 +7,12 @@ var surface = use('surface') var sdl_gpu = use('sdl_gpu') var io = use('io') var geometry = use('geometry') +var blob = use('blob') var os = use('os') -// suppose your render‐surface is W×H pixels, and -// your camera.viewport = { x:0, y:0, width:1, height:1 }; -// so vpW = W*1, vpH = H*1 +var win_size = {width:500,height:500} -def vpW = 640 -def vpH = 360 - -// how many world‑units from center to left/right? -// if zoom = 1 means 1 world‑unit == 1 pixel: -def halfW = vpW * 0.5; -def halfH = vpH * 0.5; - -// define your clipping‐volume in camera‑space: -def l = -halfW, r = +halfW; -def b = -halfH, t = +halfH; -def n = 0, f = 1; - -// Metal wants z in [0,1], so we use the “zero‐to‐one” variant: function makeOrthoMetal(l,r,b,t,n,f){ return [ 2/(r-l), 0, 0, 0, @@ -37,10 +22,26 @@ function makeOrthoMetal(l,r,b,t,n,f){ ] } -def P = makeOrthoMetal(l,r,b,t,n,f); +function make_camera_pblob(camera) { + def zoom = camera.zoom; + def cw = win_size.width; + def ch = win_size.height; + // how big is the world window? + def world_w = cw / zoom; + def world_h = ch / zoom; + + // compute world‐space bounds so that camera.pos lands at the anchor + // anchor.x = 0 → origin at left; 1 → origin at right + def l = camera.pos[0] - camera.anchor[0] * world_w; + def b = camera.pos[1] - camera.anchor[1] * world_h; + def r = l + world_w; + def t = b + world_h; + + // now build the Metal/Vulkan‐style ortho (z ∈ [0,1]) + def mat = makeOrthoMetal(l, r, b, t, 0, 1); + return geometry.array_blob(mat); +} -var Pblob = geometry.array_blob(P) -log.console(Pblob.length) var driver = "vulkan" switch(os.platform()) { @@ -57,9 +58,9 @@ switch(os.platform()) { } var default_sampler = { - min_filter: "nearest", - mag_filter: "nearest", - mipmap: "linear", + min_filter: "linear", + mag_filter: "linear", + mipmap: "nearest", u: "repeat", v: "repeat", w: "repeat", @@ -67,7 +68,7 @@ var default_sampler = { max_anisotropy: 0, compare_op: "none", min_lod: 0, - max_lod: 0, + max_lod: 2, anisotropy: false, compare: false }; @@ -128,7 +129,7 @@ var default_window = { tooltip: false, // Tooltip window (requires parent) popupMenu: false, // Popup menu window (requires parent) - // Graphics API flags (let SDL choose if not specified) + // Graphics API flags (var SDL choose if not specified) opengl: false, // Force OpenGL context vulkan: false, // Force Vulkan context metal: false, // Force Metal context (macOS) @@ -166,11 +167,12 @@ var pipeline_cache = {} function upload(copypass, buffer, toblob) { + stone(toblob) var trans = new sdl_gpu.transfer_buffer(device, { size: toblob.length/8, usage:"upload" }) - + trans.copy_blob(device, toblob) copypass.upload_to_buffer({ @@ -200,7 +202,7 @@ function make_shader(sh_file) entrypoint: shader_type == "msl" ? "main0" : "main" } - shader.gpu = new sdl_gpu.shader(device, shader) + shader[GPU] = new sdl_gpu.shader(device, shader) shader.reflection = refl; shader_cache[file] = shader shader.file = sh_file @@ -210,13 +212,9 @@ function make_shader(sh_file) var usepipe function load_pipeline(config) { - log.console(usepipe) if (usepipe) return usepipe - config.vertex = make_shader(config.vertex).gpu - config.fragment = make_shader(config.fragment).gpu - log.console(json.encode(config)) - log.console("ANOTHER NEW PIPELINE") - log.console(config) + config.vertex = make_shader(config.vertex)[GPU] + config.fragment = make_shader(config.fragment)[GPU] usepipe = new sdl_gpu.graphics_pipeline(device, config) log.console(usepipe) return usepipe @@ -327,17 +325,6 @@ var images = {} var renderer_commands = [] -var win_size = {width:500,height:500} -var logical = {width:500,height:500} - -function get_img_gpu(img) -{ - if (img.gpu) return img.gpu - var surf = new surface(img.cpu) - img.gpu = device.load_texture(surf, 0) - return img.gpu -} - ///// input ///// var input_cb var input_rate = 1/60 @@ -405,7 +392,8 @@ function poll_input() { } if (event.type.startsWith('mouse_') && event.pos && event.pos.y) - event.pos.y = -event.pos.y + logical.height + event.pos.y = -event.pos.y + win_size.height +// event.pos.y = -event.pos.y + logical.height filteredEvents.push(event) } @@ -430,7 +418,7 @@ var sprite_pipeline = { cull: "none", target: { color_targets: [ - {format: device.swapchain_format(window), blend:disabled_blend_state} + {format: device.swapchain_format(window), blend:alpha_blend_state} ], }, vertex_buffer_descriptions: [ { slot:0, input_rate: "vertex", instance_step_rate: 0, @@ -449,97 +437,252 @@ var sprite_pipeline = { var pipey = load_pipeline(sprite_pipeline) -prosperon.create_batch = function create_batch(draw_cmds, done) { - var img = graphics.texture("pockle") - var pipeline = pipey - - var geom = geometry.make_rect_quad({x:-320,y:-180,width:640,height:360}) - geom.indices = geometry.make_quad_indices(1) - - var cmd_buffer = device.acquire_cmd_buffer() - - cmd_buffer.push_vertex_uniform_data(0, Pblob) +var GPU = Symbol() - var pos_buffer = new sdl_gpu.buffer(device, { - vertex: true, - size: geom.xy.length/8 - }) - - var uv_buffer = new sdl_gpu.buffer(device, { - vertex: true, - size: geom.uv.length/8 - }) - - var color_buffer = new sdl_gpu.buffer(device, { - vertex: true, - size: geom.color.length/8 - }) - - var index_buffer = new sdl_gpu.buffer(device, { - index: true, - size: geom.indices.length/8 +var cur_cam +var cmd_fns = {} +cmd_fns.camera = function(cmd) +{ + draw_queue.push(cmd) +} + +var new_tex = [] + +function get_img_gpu(surface) +{ + var full_mip = Math.floor(Math.log2(Math.max(surface.width,surface.height))) + 1 + var gpu = new sdl_gpu.texture(device, { + width: surface.width, + height: surface.height, + layers: 1, + mip_levels: full_mip, + samples: 0, + type: "2d", + format: "rgba8", + sampler: true, + color_target: true }) - var cpy_pass = cmd_buffer.copy_pass() + var tbuf = new sdl_gpu.transfer_buffer(device, { + size: surface.pixels.length/8, + usage: "upload" + }) - upload(cpy_pass, pos_buffer, geom.xy) - upload(cpy_pass, uv_buffer, geom.uv) - upload(cpy_pass, color_buffer, geom.color) - upload(cpy_pass, index_buffer, geom.indices) - - if (!img.gpu) { - img.gpu = new sdl_gpu.texture(device, { - width: img.width, - height: img.height, - layers: 1, - mip_levels: 1, - samples: 0, - type: "2d", - format: "rgba8", - sampler: true, - }) - - var tbuf = new sdl_gpu.transfer_buffer(device, { - size: img.cpu.pixels.length/8, - usage: "upload" - }) - - tbuf.copy_blob(device, img.cpu.pixels) - - cpy_pass.upload_to_texture({ - transfer_buffer: tbuf, - offset: 0, - pixels_per_row: img.cpu.width, - rows_per_lay: img.cpu.height, - }, { - texture: img.gpu, - mip_level: 0, - layer: 0, - x: 0, y: 0, z: 0, - w: img.cpu.width, - h: img.cpu.height, - d: 1 - }, false); + tbuf.copy_blob(device, surface.pixels) + + copy_pass.upload_to_texture({ + transfer_buffer: tbuf, + offset: 0, + pixels_per_row: surface.width, + rows_per_layer: surface.height, + }, { + texture: gpu, + mip_level: 0, + layer: 0, + x: 0, y: 0, z: 0, + w: surface.width, + h: surface.height, + d: 1 + }, false); + + new_tex.push(gpu) + + return gpu +} + +var pos_blob +var uv_blob +var color_blob +var index_blob + +var draw_queue = [] +var index_count = 0 +var vertex_count = 0 + +function render_geom(geom, img) +{ + if (!img[GPU]) { + if (img.surface) + img[GPU] = get_img_gpu(img.surface) + else + img[GPU] = get_img_gpu(img.cpu) } - cpy_pass.end() - - var pass = cmd_buffer.swapchain_pass(window) + pos_blob.write_blob(geom.xy) + uv_blob.write_blob(geom.uv) + color_blob.write_blob(geom.color) + index_blob.write_blob(geom.indices) - pass.viewport({ - x: 0, y: 0, - width: 640, - height: 360 + draw_queue.push({ + pipeline:pipey, + texture: img[GPU], + num_indices: geom.num_indices, + first_index: index_count, + vertex_offset: vertex_count }) - pass.bind_pipeline(pipeline) - pass.bind_buffers(0, [{buffer:pos_buffer,offset:0}, {buffer:uv_buffer, offset:0}, {buffer:color_buffer, offset:0}]) - pass.bind_index_buffer({buffer:index_buffer,offset:0}, 16) - pass.bind_samplers(false, 0, [{texture:img.gpu, sampler:std_sampler}]) - pass.draw_indexed(6, 1, 0, 0, 0) + vertex_count += (geom.xy.length/8) / 8 + index_count += geom.num_indices +} + +cmd_fns.draw_image = function(cmd) +{ + var img = graphics.texture(cmd.image) + var geom = geometry.make_rect_quad({x:cmd.rect.x, y:cmd.rect.y, width: img.width, height: img.height}) + geom.indices = geometry.make_quad_indices(1) + geom.num_indices = 6 - pass.end() - cmd_buffer.submit() + render_geom(geom, img) +} + +cmd_fns.draw_text = function(cmd) +{ + if (!cmd.text || !cmd.pos) return + + var font = graphics.get_font(cmd.font) + if (!font[GPU]) + font[GPU] = get_img_gpu(font.surface) + var mesh = graphics.make_text_buffer( + cmd.text, + cmd.pos, + [cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a], + cmd.wrap || 0, + font + ) + + render_geom(mesh, font) +} + +cmd_fns.tilemap = function(cmd) +{ + var geometryCommands = cmd.tilemap.draw() + + for (var geomCmd of geometryCommands) { + var img = graphics.texture(geomCmd.image) + if (!img) continue + + render_geom(geomCmd.geometry, img) + } +} + +cmd_fns.geometry = function(cmd) +{ + if (typeof cmd.image == 'object') { + render_geom(cmd.geometry, cmd.image) + return + } + var img = graphics.texture(cmd.image) + if (!img) return + + render_geom(cmd.geometry, img) +} + +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, + tile_left: true, + tile_right: true, + tile_center_x: true, + tile_center_y: true + } + + // Convert single slice value to LRTB object if needed + var slice_lrtb = cmd.slice + if (typeof cmd.slice == 'number') { + slice_lrtb = { + l: cmd.slice, + r: cmd.slice, + t: cmd.slice, + b: cmd.slice + } + } + + var mesh = geometry.slice9(img, cmd.rect, slice_lrtb, slice_info) + + render_geom(mesh, img) +} + +var copy_pass + +prosperon.create_batch = function create_batch(draw_cmds, done) { + pos_blob = new blob + uv_blob = new blob + color_blob = new blob + index_blob = new blob + draw_queue = [] + index_count = 0 + vertex_count = 0 + new_tex = [] + + var render_queue = device.acquire_cmd_buffer() + copy_pass = render_queue.copy_pass() + + for (var cmd of draw_cmds) + if (cmd_fns[cmd.cmd]) + cmd_fns[cmd.cmd](cmd) + + var pos_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:pos_blob.length/8}); + var uv_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:uv_blob.length/8}); + var color_buffer = new sdl_gpu.buffer(device,{ vertex:true, size:color_blob.length/8}); + var index_buffer = new sdl_gpu.buffer(device,{ index:true, size:index_blob.length/8}); + + upload(copy_pass, pos_buffer, pos_blob) + upload(copy_pass, uv_buffer, uv_blob) + upload(copy_pass, color_buffer, color_blob) + upload(copy_pass, index_buffer, index_blob) + + copy_pass.end(); + + for (var g of new_tex) + render_queue.generate_mipmaps(g) + + var render_pass = render_queue.swapchain_pass(window) + + render_pass.bind_pipeline(pipey) + + render_pass.bind_buffers(0, [ + { buffer: pos_buffer, offset: 0 }, + { buffer: uv_buffer, offset: 0 }, + { buffer: color_buffer, offset: 0 } + ]) + + render_pass.bind_index_buffer( + { buffer: index_buffer, offset: 0 }, // the binding itself is in bytes + 16 // 16 = Uint32 indices + ); + + for (var cmd of draw_queue) { + if (cmd.camera) { + render_pass.viewport({ + x: cmd.camera.viewport.x*win_size.width, + y: cmd.camera.viewport.y * win_size.height, + width: cmd.camera.viewport.width * win_size.width, + height: cmd.camera.viewport.height * win_size.height + }) + + cur_cam = make_camera_pblob(cmd.camera) + render_queue.push_vertex_uniform_data(0, cur_cam) + continue + } + render_pass.bind_samplers(false, 0, [{texture:cmd.texture, sampler: std_sampler}]) + + render_pass.draw_indexed( + cmd.num_indices, + 1, + cmd.first_index, + cmd.vertex_offset, + 0 + ) + } + + render_pass.end() + render_queue.submit() if (done) done() } @@ -590,30 +733,4 @@ prosperon.set_window = function(config) if (window_cmds[c]) window_cmds[c](config[c]) } -prosperon.set_renderer = function(config) -{ -} - -// Function to load textures directly to the renderer -prosperon.load_texture = function(surface_data) { - var surf = new surface(surface_data) - if (!surf) return null - - var tex = renderer.load_texture(surf) - if (!tex) return null - - // Set pixel mode to nearest for all textures - tex.scaleMode = "nearest" - - var tex_id = allocate_id() - resources.texture[tex_id] = tex - - return { - id: tex_id, - texture: tex, - width: tex.width, - height: tex.height - } -} - return prosperon diff --git a/scripts/graphics.cm b/scripts/graphics.cm index 305d5115..bb9c82b0 100644 --- a/scripts/graphics.cm +++ b/scripts/graphics.cm @@ -14,7 +14,6 @@ var cache = {} // cpu is the surface graphics.Image = function(surfaceData) { this.cpu = surfaceData || null; - this.gpu = 0; this.texture = 0; this.surface = this.cpu; this.width = surfaceData?.width || 0; @@ -24,12 +23,6 @@ graphics.Image = function(surfaceData) { this[LASTUSE] = time.number(); } -// Add methods to prototype -graphics.Image.prototype.unload_gpu = function() { - this.gpu = 0; - this.texture = 0; -} - graphics.Image.prototype.unload_cpu = function() { this.cpu = null; this.surface = null; diff --git a/source/qjs_geometry.c b/source/qjs_geometry.c index 8d52eba0..0ebcd4ad 100644 --- a/source/qjs_geometry.c +++ b/source/qjs_geometry.c @@ -1463,7 +1463,7 @@ static const JSCFunctionListEntry js_geometry_funcs[] = { MIST_FUNC_DEF(geometry, sprites_to_data, 1), MIST_FUNC_DEF(geometry, transform_xy_blob, 2), MIST_FUNC_DEF(gpu, tile, 4), - MIST_FUNC_DEF(gpu, slice9, 3), + 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), diff --git a/source/qjs_sdl_gpu.c b/source/qjs_sdl_gpu.c index 666aadf9..28b5a6fe 100644 --- a/source/qjs_sdl_gpu.c +++ b/source/qjs_sdl_gpu.c @@ -603,7 +603,6 @@ static JSValue js_gpu_graphics_pipeline_constructor(JSContext *js, JSValueConst vbd[i].pitch = pitch; vbd[i].input_rate = input_rate; vbd[i].instance_step_rate = step_rate; - printf("slot %d, pitch %d ...\n", vbd[i].slot, vbd[i].pitch); } JS_FreeValue(js, elem); }