diff --git a/prosperon/_sdl_gpu.cm b/prosperon/_sdl_gpu.cm deleted file mode 100644 index 540a7fe5..00000000 --- a/prosperon/_sdl_gpu.cm +++ /dev/null @@ -1,655 +0,0 @@ -var render = {} - -var io = use('io') -var controller = use('controller') -var tracy = use('tracy') -var graphics = use('graphics') -var imgui = use('imgui') -var transform = use('transform') -var color = use('color') - -var base_pipeline = { - vertex: "sprite.vert", - fragment: "sprite.frag", - primitive: "triangle", // point, line, linestrip, triangle, trianglestrip - fill: true, // false for lines - depth: { - compare: "greater_equal", // never/less/equal/less_equal/greater/not_equal/greater_equal/always - test: false, - write: false, - bias: 0, - bias_slope_scale: 0, - bias_clamp: 0 - }, - stencil: { - enabled: true, - front: { - compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always - fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap - depth_fail: "keep", - pass: "keep" - }, - back: { - compare: "equal", // never/less/equal/less_equal/greater/neq/greq/always - fail: "keep", // keep/zero/replace/incr_clamp/decr_clamp/invert/incr_wrap/decr_wrap - depth_fail: "keep", - pass: "keep" - }, - test: true, - compare_mask: 0, - write_mask: 0 - }, - blend: { - enabled: false, - src_rgb: "zero", // 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: "zero", - op_rgb: "add", // add/sub/rev_sub/min/max - src_alpha: "one", - dst_alpha: "zero", - op_alpha: "add" - }, - cull: "none", // none/front/back - face: "cw", // cw/ccw - alpha_to_coverage: false, - multisample: { - count: 1, // number of multisamples - mask: 0xFFFFFFFF, - domask: false - }, - label: "scripted pipeline", - target: {} -} - -var sprite_pipeline = Object.create(base_pipeline); -sprite_pipeline.blend = { - enabled:true, - src_rgb: "src_alpha", - dst_rgb: "one_minus_src_alpha", - op_rgb: "add", // add/sub/rev_sub/min/max - src_alpha: "one", - dst_alpha: "zero", - op_alpha: "add" -}; - -var context; - -sprite_pipeline.target = { - color_targets: [{ - format:"rgba8", - blend:sprite_pipeline.blend - }], - depth: "d32 float s8" -}; - -var driver = "vulkan" -switch(os.platform()) { - case "Linux": - driver = "vulkan" - break - case "Windows": -// driver = "direct3d12" - driver = "vulkan" - break - case "macOS": - driver = "metal" - break -} - -var unit_transform = new transform; - -var cur = {}; -cur.images = []; -cur.samplers = []; - -var tbuffer; -function full_upload(buffers) { - var cmds = context.acquire_cmd_buffer(); - tbuffer = context.upload(cmds, buffers, tbuffer); - cmds.submit(); -} - -function bind_pipeline(pass, pipeline) { - make_pipeline(pipeline) - pass.bind_pipeline(pipeline.gpu) - pass.pipeline = pipeline; -} - -var main_pass; - -var cornflower = [62/255,96/255,113/255,1]; - -function get_pipeline_ubo_slot(pipeline, name) { - if (!pipeline.vertex.reflection.ubos) return; - for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) { - var ubo = pipeline.vertex.reflection.ubos[i]; - if (ubo.name.endsWith(name)) - return i; - } - return null; -} - -function transpose4x4(val) { - var out = []; - out[0] = val[0]; out[1] = val[4]; out[2] = val[8]; out[3] = val[12]; - out[4] = val[1]; out[5] = val[5]; out[6] = val[9]; out[7] = val[13]; - out[8] = val[2]; out[9] = val[6]; out[10] = val[10];out[11] = val[14]; - out[12] = val[3];out[13] = val[7];out[14] = val[11];out[15] = val[15]; - return out; -} - -function ubo_obj_to_array(pipeline, name, obj) { - var ubo; - for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) { - ubo = pipeline.vertex.reflection.ubos[i]; - if (ubo.name.endsWith(name)) break; - } - var type = pipeline.vertex.reflection.types[ubo.type]; - var len = 0; - for (var mem of type.members) - len += type_to_byte_count(mem.type); - - var buf = new ArrayBuffer(len); - var view = new DataView(buf); - - for (var mem of type.members) { - var val = obj[mem.name]; - if (!val) throw new Error (`Could not find ${mem.name} on supplied object`); - - if (mem.name == 'model') - val = transpose4x4(val.array()); - - for (var i = 0; i < val.length; i++) - view.setFloat32(mem.offset + i*4, val[i],true); - } - return buf; -} - -function type_to_byte_count(type) { - switch (type) { - case 'float': return 4; - case 'vec2': return 8; - case 'vec3': return 12; - case 'vec4': return 16; - case 'mat4': return 64; - default: throw new Error("Unknown or unsupported float-based type: " + type); - } -} - -var sprite_model_ubo = { - model: unit_transform, - color: [1,1,1,1] -}; - -var shader_cache = {}; -var shader_times = {}; - -function make_pipeline(pipeline) { - if (pipeline.hasOwnProperty("gpu")) return; // this pipeline has already been made - - if (typeof pipeline.vertex == 'string') - pipeline.vertex = make_shader(pipeline.vertex); - if (typeof pipeline.fragment == 'string') - pipeline.fragment = make_shader(pipeline.fragment) - - // 1) Reflection data for vertex shader - var refl = pipeline.vertex.reflection - if (!refl || !refl.inputs || !Array.isArray(refl.inputs)) { - pipeline.gpu = context.make_pipeline(pipeline); - return; - } - - var inputs = refl.inputs - var buffer_descriptions = [] - var attributes = [] - - // 2) Build buffer + attribute for each reflection input - for (var i = 0; i < inputs.length; i++) { - var inp = inputs[i] - var typeStr = inp.type - var nameStr = (inp.name || "").toUpperCase() - var pitch = 4 - var fmt = "float1" - - if (typeStr == "vec2") { - pitch = 8 - fmt = "float2" - } else if (typeStr == "vec3") { - pitch = 12 - fmt = "float3" - } else if (typeStr == "vec4") { - if (nameStr.indexOf("COLOR") >= 0) { - pitch = 16 - fmt = "color" - } else { - pitch = 16 - fmt = "float4" - } - } - - buffer_descriptions.push({ - slot: i, - pitch: pitch, - input_rate: "vertex", - instance_step_rate: 0, - name:inp.name.split(".").pop() - }) - - attributes.push({ - location: inp.location, - buffer_slot: i, - format: fmt, - offset: 0 - }) - } - - pipeline.vertex_buffer_descriptions = buffer_descriptions - pipeline.vertex_attributes = attributes - - pipeline.gpu = context.make_pipeline(pipeline); -} - -var shader_type; - -function make_shader(sh_file) { - var file = `shaders/${shader_type}/${sh_file}.${shader_type}` - if (shader_cache[file]) return shader_cache[file] - var refl = json.decode(io.slurp(`shaders/reflection/${sh_file}.json`)) - - var shader = { - code: io.slurpbytes(file), - format: shader_type, - stage: sh_file.endsWith("vert") ? "vertex" : "fragment", - num_samplers: refl.separate_samplers ? refl.separate_samplers.length : 0, - num_textures: 0, - num_storage_buffers: refl.separate_storage_buffers ? refl.separate_storage_buffers.length : 0, - num_uniform_buffers: refl.ubos ? refl.ubos.length : 0, - entrypoint: shader_type == "msl" ? "main0" : "main" - } - - shader.gpu = context.make_shader(shader) - shader.reflection = refl; - shader_cache[file] = shader - shader.file = sh_file - return shader -} - -var render_queue = []; -var hud_queue = []; - -var current_queue = render_queue; - -var std_sampler = { - min_filter: "nearest", - mag_filter: "nearest", - mipmap: "linear", - u: "repeat", - v: "repeat", - w: "repeat", - mip_bias: 0, - max_anisotropy: 0, - compare_op: "none", - min_lod: 0, - max_lod: 0, - anisotropy: false, - compare: false -}; - -function upload_model(model) { - var bufs = []; - for (var i in model) { - if (typeof model[i] != 'object') continue; - bufs.push(model[i]); - } - context.upload(this, bufs); -} - -function bind_model(pass, pipeline, model) { - var buffers = pipeline.vertex_buffer_descriptions; - var bufs = []; - if (buffers) - for (var b of buffers) { - if (b.name in model) bufs.push(model[b.name]) - else throw Error (`could not find buffer ${b.name} on model`); - } - pass.bind_buffers(0,bufs); - pass.bind_index_buffer(model.indices); -} - -function bind_mat(pass, pipeline, mat) { - var imgs = []; - var refl = pipeline.fragment.reflection; - if (refl.separate_images) { - for (var i of refl.separate_images) { - if (i.name in mat) { - var tex = mat[i.name]; - imgs.push({texture:tex.texture, sampler:tex.sampler}); - } else - throw Error (`could not find all necessary images: ${i.name}`) - } - pass.bind_samplers(false, 0,imgs); - } -} - -function group_sprites_by_texture(sprites, mesh) { - if (sprites.length == 0) return; - for (var i = 0; i < sprites.length; i++) { - sprites[i].mesh = mesh; - sprites[i].first_index = i*6; - sprites[i].num_indices = 6; - } - return; - // The code below is an alternate approach to grouping by image. Currently not in use. - /* - 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; - var newgroup = {image:sprites[i].image, first_index:group.first_index+group.num_indices}; - group = newgroup; - groups.push(group); - count=1; - } - group.num_indices = count*6; - return groups; - */ -} - -var main_color = { - type:"2d", - format: "rgba8", - layers: 1, - mip_levels: 1, - samples: 0, - sampler:true, - color_target:true -}; - -var main_depth = { - type: "2d", - format: "d32 float s8", - layers:1, - mip_levels:1, - samples:0, - sampler:true, - depth_target:true -}; - -function render_camera(cmds, camera) { - var pass; - delete camera.target // TODO: HORRIBLE - if (!camera.target) { - main_color.width = main_depth.width = camera.size.x; - main_color.height = main_depth.height = camera.size.y; - camera.target = { - color_targets: [{ - texture: context.texture(main_color), - mip_level:0, - layer: 0, - load:"clear", - store:"store", - clear: cornflower - }], - depth_stencil: { - texture: context.texture(main_depth), - clear:1, - load:"dont_care", - store:"dont_care", - stencil_load:"dont_care", - stencil_store:"dont_care", - stencil_clear:0 - } - }; - } - - var buffers = []; - buffers = buffers.concat(graphics.queue_sprite_mesh(render_queue)); - var unique_meshes = [...new Set(render_queue.map(x => x.mesh))]; - for (var q of unique_meshes) - buffers = buffers.concat([q.pos, q.color, q.uv, q.indices]); - - buffers = buffers.concat(graphics.queue_sprite_mesh(hud_queue)); - for (var q of hud_queue) - if (q.type == 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color, q.mesh.uv, q.mesh.indices]); - - full_upload(buffers) - - var pass = cmds.render_pass(camera.target); - - var pipeline = sprite_pipeline; - bind_pipeline(pass,pipeline); - - var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer'); - if (camslot != null) - cmds.camera(camera, camslot); - - modelslot = get_pipeline_ubo_slot(pipeline, "model"); - if (modelslot != null) { - var ubo = ubo_obj_to_array(pipeline, 'model', sprite_model_ubo); - cmds.push_vertex_uniform_data(modelslot, ubo); - } - - var mesh; - var img; - var modelslot; - - cmds.push_debug_group("draw") - for (var group of render_queue) { - if (mesh != group.mesh) { - mesh = group.mesh; - bind_model(pass,pipeline,mesh); - } - - if (group.image && 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); - } - cmds.pop_debug_group() - - cmds.push_debug_group("hud") - var camslot = get_pipeline_ubo_slot(pipeline, 'TransformBuffer'); - if (camslot != null) - cmds.hud(camera.size, camslot); - - for (var group of hud_queue) { - if (mesh != group.mesh) { - mesh = group.mesh; - bind_model(pass,pipeline,mesh); - } - - if (group.image && 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); - } - cmds.pop_debug_group(); - - pass?.end(); - - render_queue = []; - hud_queue = []; -} - -var swaps = []; -render.present = function() { - os.clean_transforms(); - var cmds = context.acquire_cmd_buffer(); - render_camera(cmds, prosperon.camera); - var swapchain_tex = cmds.acquire_swapchain(); - if (!swapchain_tex) - cmds.cancel(); - else { - var torect = prosperon.camera.draw_rect(prosperon.window.size); - torect.texture = swapchain_tex; - if (swapchain_tex) { - cmds.blit({ - src: prosperon.camera.target.color_targets[0].texture, - dst: torect, - filter:"nearest", - load: "clear" - }); - - if (imgui) { // draws any imgui commands present - cmds.push_debug_group("imgui") - imgui.prepend(cmds); - var pass = cmds.render_pass({ - color_targets:[{texture:swapchain_tex}]}); - imgui.endframe(cmds,pass); - pass.end(); - cmds.pop_debug_group() - } - } - cmds.submit() - } -} - -var stencil_write = { - compare: "always", - fail_op: "replace", - depth_fail_op: "replace", - pass_op: "replace" -}; - -function stencil_writer(ref) { - var pipe = Object.create(base_pipeline); - Object.assign(pipe, { - stencil: { - enabled: true, - front: stencil_write, - back: stencil_write, - write:true, - read:true, - ref:ref - }, - write_mask: colormask.none - }); - return pipe; -}.hashify(); - -// objects by default draw where the stencil buffer is 0 -function fillmask(ref) { - var pipe = stencil_writer(ref); - render.use_shader('screenfill.cg', pipe); - render.draw(shape.quad); -} - -var stencil_invert = { - compare: "always", - fail_op: "invert", - depth_fail_op: "invert", - pass_op: "invert" -}; - -function mask(image, pos, scale, rotation = 0, ref = 1) { - if (typeof image == 'string') - image = graphics.texture(image); - - var tex = image.texture; - if (scale) scale = scale.div([tex.width,tex.height]); - else scale = [1,1,1] - - var pipe = stencil_writer(ref); - render.use_shader('sprite.cg', pipe); - var t = new transform; - t.trs(pos, null, scale); - set_model(t); - render.use_mat({ - diffuse:image.texture, - rect: image.rect, - shade: color.white - }); - render.draw(shape.quad); -} - -render.viewport = function(rect) { - context.viewport(rect); -} - -render.scissor = function(rect) { - render.viewport(rect) -} - -var std_sampler - -if (tracy) tracy.gpu_init() - -render.queue = function(cmd) { - if (Array.isArray(cmd)) - for (var i of cmd) current_queue.push(i) - else - current_queue.push(cmd) -} - -render.setup_draw = function() { - current_queue = render_queue; - prosperon.draw(); -} - -render.setup_hud = function() { - current_queue = hud_queue; - prosperon.hud(); -} - -render.initialize = function(config) -{ - var default_conf = { - title:`Prosperon [${prosperon.version}-${prosperon.revision}]`, - width: 1280, - height: 720, - icon: graphics.make_texture(io.slurpbytes('icons/moon.gif')), - high_dpi:0, - alpha:1, - fullscreen:0, - sample_count:1, - enable_clipboard:true, - enable_dragndrop: true, - max_dropped_files: 1, - swap_interval: 1, - name: "Prosperon", - version:prosperon.version + "-" + prosperon.revision, - identifier: "world.pockle.prosperon", - creator: "Pockle World LLC", - copyright: "Copyright Pockle World 2025", - type: "game", - url: "https://prosperon.dev" - } - - config.__proto__ = default_conf - - prosperon.camera = use('ext/camera').make() - prosperon.camera.size = [config.width,config.height] - - prosperon.window = prosperon.engine_start(config) - - context = prosperon.window.make_gpu(false,driver) - context.window = prosperon.window - context.claim_window(prosperon.window) - context.set_swapchain('sdr', 'vsync') - - if (imgui) imgui.init(context, prosperon.window) - - shader_type = context.shader_format()[0]; - - std_sampler = context.make_sampler({ - min_filter: "nearest", - mag_filter: "nearest", - mipmap_mode: "nearest", - address_mode_u: "repeat", - address_mode_v: "repeat", - address_mode_w: "repeat" - }); -} - -return render - diff --git a/prosperon/_sprite.cm b/prosperon/_sprite.cm deleted file mode 100644 index 1c25564d..00000000 --- a/prosperon/_sprite.cm +++ /dev/null @@ -1,241 +0,0 @@ -var graphics = use('graphics') -var color = use('color') - -var sprite = { - image: null, - set color(x) { this._sprite.color = x; }, - get color() { return this._sprite.color; }, - anim_speed: 1, - play(str, loop = true, reverse = false, fn) { - if (!this.animset) { - fn?.(); - return; - } - - if (typeof str == 'string') { - if (!this.animset[str]) { - fn?.(); - return; - } - this.anim = this.animset[str]; - } - - var playing = this.anim; - - var stop; - - this.del_anim?.(); - this.del_anim = () => { - this.del_anim = null; - advance = null; - stop?.(); - }; - - var f = 0; - if (reverse) f = playing.frames.length - 1; - - var advance = (time) => { - var done = false; - if (reverse) { - f = (((f - 1) % playing.frames.length) + playing.frames.length) % playing.frames.length; - if (f == playing.frames.length - 1) done = true; - } else { - f = (f + 1) % playing.frames.length; - if (f == 0) done = true; - } - - this.image = playing.frames[f]; - - if (done) { - // notify requestor - fn?.(); - if (!loop) { - this?.stop(); - return; - } - } - - return playing.frames[f].time/this.anim_speed; - } - stop = this.delay(advance, playing.frames[f].time/this.anim_speed); - advance(); - }, - stop() { - this.del_anim?.(); - }, - set path(p) { - var image = graphics.texture(p); - if (!image) { - log.warn(`Could not find image ${p}.`); - return; - } - - this._p = p; - - this.del_anim?.(); - if (image.texture) - this.image = image; - else if (image.frames) { - // It's an animation - this.anim = image; - this.image = image.frames[0]; - this.animset = [this.anim] - this.play() - } else { - // Maybe an animset; try to grab the first one - for (var anim in image) { - if (image[anim].frames) { - this.anim = image[anim]; - this.image = image[anim].frames[0]; - this.animset = image; - this.play() - break; - } - } - } - - this.transform.scale = this.image.texture.dim //[this.image.texture.width, this.image.texture.height] - this._sprite.set_image(this.image) - }, - get path() { - return this._p; - }, - garbage: function() { - this.del_anim?.(); - this.anim = null; - tree.delete(this._sprite) - this.transform.parent = null - for (var t of this.transform.children()) - t.parent = null - delete this.transform.sprite - delete this._sprite -// log.console("CLEARED SPRITE") - }, - anchor: [0, 0], - set layer(v) { this._sprite.layer = v; }, - get layer() { return this._sprite.layer; }, - pick() { - return this; - }, - boundingbox() { - return Object.freeze(this._sprite.rect) // freeze so it can't be modified on the outside - } -}; - -sprite.setanchor = function (anch) { - var off = [0, 0]; - switch (anch) { - case "ll": - break; - case "lm": - off = [-0.5, 0]; - break; - case "lr": - off = [-1, 0]; - break; - case "ml": - off = [0, -0.5]; - break; - case "mm": - off = [-0.5, -0.5]; - break; - case "mr": - off = [-1, -0.5]; - break; - case "ul": - off = [0, -1]; - break; - case "um": - off = [-0.5, -1]; - break; - case "ur": - off = [-1, -1]; - break; - } - this.anchor = off; - this.pos = this.dimensions().scale(off); -}; - -sprite.inputs = {}; -sprite.inputs.kp9 = function () { - this.setanchor("ll"); -}; -sprite.inputs.kp8 = function () { - this.setanchor("lm"); -}; -sprite.inputs.kp7 = function () { - this.setanchor("lr"); -}; -sprite.inputs.kp6 = function () { - this.setanchor("ml"); -}; -sprite.inputs.kp5 = function () { - this.setanchor("mm"); -}; -sprite.inputs.kp4 = function () { - this.setanchor("mr"); -}; -sprite.inputs.kp3 = function () { - this.setanchor("ur"); -}; -sprite.inputs.kp2 = function () { - this.setanchor("um"); -}; -sprite.inputs.kp1 = function () { - this.setanchor("ul"); -}; - -var rtree = use('rtree') - -var tree = new rtree -sprite.tree = tree; - -var IN = Symbol() - -sprite.t_hook = function() { - var msp = this.sprite._sprite; - if (this[IN]) - tree.delete(msp); - msp.rect = this.torect() - msp.set_affine(this) - tree.add(msp) - this[IN] = true -} - -Object.mixin(sprite,use("transform")) - -sprite.to_queue = function(ysort = false) -{ - var pos = prosperon.camera.transform.pos; - var size = prosperon.camera.size; - var camrect = { - x:pos.x-size.x/2, - y:pos.y-size.y/2, - width:size.x, - height:size.y - }; - var culled = sprite.tree.query(camrect) - if (culled.length == 0) return []; - var cmd = graphics.make_sprite_queue(culled, prosperon.camera, null, 1); - return cmd; -} - -return sprite; - ---- - -var color = use('color') -var transform = use('transform') -var sprite = use('sprite') - -this.transform = new transform; -if (this.overling.transform) - this.transform.parent = this.overling.transform; - -this.transform.change_hook = $.t_hook; -var msp = new sprite -this._sprite = msp; -msp.color = color.white; -this.transform.sprite = this - - diff --git a/prosperon/prosperon.ce b/prosperon/prosperon.ce deleted file mode 100644 index 111fe5a1..00000000 --- a/prosperon/prosperon.ce +++ /dev/null @@ -1,504 +0,0 @@ -var os = use('os'); -var io = use('io'); -var transform = use('transform'); -var rasterize = use('rasterize'); -var time = use('time') -var tilemap = use('tilemap') -var json = use('json') - -// Frame timing variables -var framerate = 60 - -var game = args[0] - -io.writepath(game) - -var video - -//var cnf = use('accio/config') - -$_.start(e => { - if (e.type != 'greet') return - video = e.actor - graphics = use('graphics', video) - send(video, {kind:"window", op:"makeRenderer"}, e => { - $_.start(e => { - if (gameactor) return - gameactor = e.actor - $_.couple(gameactor) - start_pipeline() - }, args[0], $_, video) - }) -}, 'prosperon/sdl_video', {}) - -var geometry = use('geometry') -var dmon = use('dmon') -var res = use('resources') - -// Start watching for file changes -dmon.watch('.') - -var camera = {} - -function updateCameraMatrix(cam) { - def win_w = logical.width - def win_h = logical.height - def view_w = (cam.size?.[0] ?? win_w) / cam.zoom - def view_h = (cam.size?.[1] ?? win_h) / cam.zoom - - def ox = cam.pos[0] - view_w * (cam.anchor?.[0] ?? 0) - def oy = cam.pos[1] - view_h * (cam.anchor?.[1] ?? 0) - - def vx = (cam.viewport?.x ?? 0) * win_w - def vy = (cam.viewport?.y ?? 0) * win_h - def vw = (cam.viewport?.width ?? 1) * win_w - def vh = (cam.viewport?.height ?? 1) * win_h - - def sx = vw / view_w - def sy = vh / view_h // flip-Y later - - /* affine matrix that SDL wants (Y going down) */ - cam.a = sx - cam.c = vx - sx * ox - cam.e = -sy // <-- minus = flip Y - cam.f = vy + vh + sy * oy - - /* convenience inverses */ - cam.ia = 1 / cam.a - cam.ic = -cam.c / cam.a - cam.ie = 1 / cam.e - cam.if = -cam.f / cam.e - - camera = cam -} - -//---- forward transform ---- -function worldToScreenPoint([x,y], camera) { - return { - x: camera.a * x + camera.c, - y: camera.e * y + camera.f - }; -} - -//---- inverse transform ---- -function screenToWorldPoint(pos, camera) { - return { - x: camera.ia * pos[0] + camera.ic, - y: camera.ie * pos[1] + camera.if - }; -} - -//---- rectangle (two corner) ---- -function worldToScreenRect({x,y,width,height}, camera) { - // map bottom-left and top-right - def x1 = camera.a * x + camera.c; - def y1 = camera.e * y + camera.f; - def x2 = camera.a * (x + width) + camera.c; - def y2 = camera.e * (y + height) + camera.f; - - return { - x:Math.min(x1,x2), - y:Math.min(y1,y2), - width:Math.abs(x2-x1), - height:Math.abs(y2-y1) - } -} - -var graphics - -var gameactor - -var images = {} - -var renderer_commands = [] - -var win_size = {width:500,height:500} -var logical = {width:500,height:500} - -// Convert high-level draw commands to low-level renderer commands -function translate_draw_commands(commands) { - if (!graphics) return - - renderer_commands.length = 0 - - commands.forEach(function(cmd) { - if (cmd.material && cmd.material.color) { - renderer_commands.push({ - op: "set", - prop: "drawColor", - value: cmd.material.color - }) - } - - switch(cmd.cmd) { - case "camera": - updateCameraMatrix(cmd.camera, win_size.width, win_size.height) - break - - case "draw_rect": - cmd.rect = worldToScreenRect(cmd.rect, camera) - // Handle rectangles with optional rounding and thickness - if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) { - // Rounded rectangle - var thickness = (cmd.opt.thickness == 0) ? 0 : (cmd.opt.thickness || 1) - var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness) - - if (raster_result.type == 'rect') { - renderer_commands.push({ - op: "fillRect", - data: {rect: raster_result.data} - }) - } else if (raster_result.type == 'rects') { - raster_result.data.forEach(function(rect) { - renderer_commands.push({ - op: "fillRect", - data: {rect: rect} - }) - }) - } - } else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) { - // Outlined rectangle - var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness) - - if (raster_result.type == 'rect') { - renderer_commands.push({ - op: "fillRect", - data: {rect: raster_result.data} - }) - } else if (raster_result.type == 'rects') { - renderer_commands.push({ - op: "rects", - data: {rects: raster_result.data} - }) - } - } else { - renderer_commands.push({ - op: "fillRect", - data: {rect: cmd.rect} - }) - } - break - - case "draw_circle": - case "draw_ellipse": - cmd.pos = worldToScreenPoint(cmd.pos, camera) - // Rasterize ellipse to points or rects - var radii = cmd.radii || [cmd.radius, cmd.radius] - var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {}) - - if (raster_result.type == 'points') { - renderer_commands.push({ - op: "point", - data: {points: raster_result.data} - }) - } else if (raster_result.type == 'rects') { - // Use 'rects' operation for multiple rectangles - renderer_commands.push({ - op: "rects", - data: {rects: raster_result.data} - }) - } - break - - case "draw_line": - renderer_commands.push({ - op: "line", - data: {points: cmd.points.map(p => { - var pt = worldToScreenPoint(p, camera) - return [pt.x, pt.y] - })} - }) - break - - case "draw_point": - cmd.pos = worldToScreenPoint(cmd.pos, camera) - renderer_commands.push({ - op: "point", - data: {points: [cmd.pos]} - }) - break - - case "draw_image": - var img = graphics.texture(cmd.image) - var gpu = img.gpu - if (!gpu) break - - cmd.rect.width ??= img.width - cmd.rect.height ??= img.height - cmd.rect = worldToScreenRect(cmd.rect, camera) - - renderer_commands.push({ - op: "texture", - data: { - texture_id: gpu.id, - dst: cmd.rect, - src: img.rect - } - }) - - break - - case "draw_text": - if (!cmd.text) break - if (!cmd.pos) break - var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera) - var pos = {x: rect.x, y: rect.y} - renderer_commands.push({ - op: "debugText", - data: { - pos, - text: cmd.text - } - }) - break - - case "draw_slice9": - var img = graphics.texture(cmd.image) - var gpu = img.gpu - if (!gpu) break - - cmd.rect = worldToScreenRect(cmd.rect, camera) - - renderer_commands.push({ - op: "texture9Grid", - data: { - texture_id: gpu.id, - src: img.rect, - leftWidth: cmd.slice, - rightWidth: cmd.slice, - topHeight: cmd.slice, - bottomHeight: cmd.slice, - scale: 1.0, - dst: cmd.rect - } - }) - break - - case "tilemap": - // Group tiles by texture to batch draw calls - var textureGroups = {} - var tilePositions = [] - - // Collect all tiles and their positions - tilemap.for(cmd.tilemap, (tile, {x,y}) => { - if (tile) { - tilePositions.push({tile, x, y}) - } - }) - - // Group tiles by texture - tilePositions.forEach(({tile, x, y}) => { - var img = graphics.texture(tile) - if (img && img.gpu) { - var texId = img.gpu.id - if (!textureGroups[texId]) { - textureGroups[texId] = { - texture: img, - tiles: [] - } - } - textureGroups[texId].tiles.push({x, y, img}) - } - }) - - // Generate draw commands for each texture group - Object.keys(textureGroups).forEach(texId => { - var group = textureGroups[texId] - var tiles = group.tiles - - // Create a temporary tilemap with only tiles from this texture - // Apply tilemap position to the offset to shift the world coordinates - var tempMap = { - tiles: [], - offset_x: cmd.tilemap.offset_x + (cmd.tilemap.pos.x / cmd.tilemap.size_x), - offset_y: cmd.tilemap.offset_y + (cmd.tilemap.pos.y / cmd.tilemap.size_y), - size_x: cmd.tilemap.size_x, - size_y: cmd.tilemap.size_y - } - - // Build sparse array for this texture's tiles - tiles.forEach(({x, y, img}) => { - var arrayX = x - cmd.tilemap.offset_x - var arrayY = y - cmd.tilemap.offset_y - if (!tempMap.tiles[arrayX]) tempMap.tiles[arrayX] = [] - tempMap.tiles[arrayX][arrayY] = img - }) - - // Generate geometry for this texture group - var geom = geometry.tilemap_to_data(tempMap) - geom.texture_id = parseInt(texId) - - renderer_commands.push({ - op: "geometry_raw", - data: geom - }) - }) - break - } - }) - - return renderer_commands -} - -var parseq = use('parseq', $_.delay) - -// Wrap `send(actor,msg,cb)` into a parseq “requestor” -// • on success: cb(data) → value=data, reason=null -// • on failure: cb(null,err) -function rpc_req(actor, msg) { - return (cb, _) => { - send(actor, msg, data => { - if (data.error) - cb(null, data) - else - cb(data) - }) - } -} - -var pending_draw = null -var pending_next = null -var last_time = time.number() - -var input = use('input') - -var input_state = { - poll: 1/framerate -} - -// 1) input runs completely independently -function poll_input() { - send(video, {kind:'input', op:'get'}, evs => { - for (var ev of evs) { - if (ev.type == 'window_pixel_size_changed') { - win_size.width = ev.width - win_size.height = ev.height - } - - if (ev.type == 'quit') - $_.stop() - - if (ev.type.includes('key')) { - if (ev.key) - ev.key = input.keyname(ev.key) - } - - if (ev.type.startsWith('mouse_')) - ev.pos.y = -ev.pos.y + logical.height - } - - send(gameactor, evs) - }) - $_.delay(poll_input, input_state.poll) -} - -// 2) helper to build & send a batch, then call done() -function create_batch(draw_cmds, done) { - def batch = [ - {op:'set', prop:'drawColor', value:{r:0.1,g:0.1,b:0.15,a:1}}, - {op:'clear'} - ] - if (draw_cmds && draw_cmds.length) - batch.push(...translate_draw_commands(draw_cmds)) - - batch.push( - {op:'set', prop:'drawColor', value:{r:1,g:1,b:1,a:1}}, - {op:'imgui_render'}, - {op:'present'} - ) - - send(video, {kind:'renderer', op:'batch', data:batch}, done) -} - -// File watching loop -function poll_file_changes() { - dmon.poll(e => { - if (e.action == 'modify' || e.action == 'create') { - // Check if it's an image file - var ext = e.file.split('.').pop().toLowerCase() - var imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tga', 'webp', 'qoi', 'ase', 'aseprite'] - - if (imageExts.includes(ext)) { - // Try to find the full path for this image - var possiblePaths = [ - e.file, - e.root + e.file, - res.find_image(e.file.split('/').pop().split('.')[0]) - ].filter(p => p) - - for (var path of possiblePaths) { - graphics.tex_hotreload(path) - } - } - } - }) - - // Schedule next poll in 0.5 seconds - $_.delay(poll_file_changes, 0.5) -} - -// 3) kick off the very first update→draw -function start_pipeline() { - poll_input() - poll_file_changes() // Start file watching loop - send(gameactor, {kind:'update', dt:0}, () => { - send(gameactor, {kind:'draw'}, cmds => { - pending_draw = cmds - render_step() - }) - }) -} - -function render_step() { - // a) Calculate actual dt since last frame - def now = time.number() - def dt = now - last_time - last_time = now - - // b) Send update with actual dt, then wait for draw response - send(gameactor, {kind:'update', dt}, () => { - send(gameactor, {kind:'draw'}, cmds => { - // Only render after receiving draw commands - pending_draw = cmds - - // c) render the current frame - create_batch(pending_draw, _ => { // time to render - def frame_end = time.number() - def wait_time = Math.max(0, (frame_end - now) - 1/framerate) - - // e) Schedule next frame - $_.delay(render_step, wait_time) - }) - }) - }) -} - -$_.receiver(e => { - switch(e.op) { - case 'resolution': - log.console(json.encode(e)) - send(video, { - kind:'renderer', - op:'set', - prop:'logicalPresentation', - value: {...e} - }) - logical.width = e.width - logical.height = e.height - break - case 'window_size': - send(video, { - kind:'window', - op:'set', - data: {property: 'size', value: [e.width, e.height]} - }) - break - case 'framerate': - // Allow setting target framerate dynamically - if (e.fps && e.fps > 0) { - framerate = e.fps - input_state.poll = 1/framerate - } - break - } -}) diff --git a/prosperon/prosperon.cm b/prosperon/prosperon.cm index 727002db..88dabe51 100644 --- a/prosperon/prosperon.cm +++ b/prosperon/prosperon.cm @@ -48,7 +48,6 @@ function make_camera_pblob(camera) { return geometry.array_blob(mat); } - var driver = "vulkan" switch(os.platform()) { case "Linux": @@ -689,8 +688,10 @@ cmd_fns.draw_image = function(cmd) else img = cmd.image - cmd.rect.width ??= img.width - cmd.rect.height ??= img.height + if (cmd.rect.width && !cmd.rect.height) + cmd.rect.height = cmd.rect.width * img.height / img.width + else if (cmd.rect.height && !cmd.rect.width) + cmd.rect.width = cmd.rect.height * img.width / img.height var geom = geometry.make_rect_quad(cmd.rect) geom.indices = geometry.make_quad_indices(1) diff --git a/prosperon/prosperon_sdl_renderer.cm b/prosperon/prosperon_sdl_renderer.cm deleted file mode 100644 index 7620fbe8..00000000 --- a/prosperon/prosperon_sdl_renderer.cm +++ /dev/null @@ -1,502 +0,0 @@ -var prosperon = {} - -// This file is hard coded for the SDL renderer case - -var video = use('sdl_video') -var imgui = use('imgui') -var surface = use('surface') - -var default_window = { - // Basic properties - title: "Prosperon Window", - width: 640, - height: 480, - - // Position - can be numbers or "centered" - x: null, // SDL_WINDOWPOS_null by default - y: null, // SDL_WINDOWPOS_null by default - - // Window behavior flags - resizable: true, - fullscreen: false, - hidden: false, - borderless: false, - alwaysOnTop: false, - minimized: false, - maximized: false, - - // Input grabbing - mouseGrabbed: false, - keyboardGrabbed: false, - - // Display properties - highPixelDensity: false, - transparent: false, - opacity: 1.0, // 0.0 to 1.0 - - // Focus behavior - notFocusable: false, - - // Special window types (mutually exclusive) - utility: false, // Utility window (not in taskbar) - tooltip: false, // Tooltip window (requires parent) - popupMenu: false, // Popup menu window (requires parent) - - // Graphics API flags (let SDL choose if not specified) - opengl: false, // Force OpenGL context - vulkan: false, // Force Vulkan context - metal: false, // Force Metal context (macOS) - - // Advanced properties - parent: null, // Parent window for tooltips/popups/modal - modal: false, // Modal to parent window (requires parent) - externalGraphicsContext: false, // Use external graphics context - - // Input handling - textInput: true, // Enable text input on creation -} - -var win_config = arg[0] || {} -win_config.__proto__ = default_window - -var window = new video.window(win_config) -var renderer = window.make_renderer() - -// Initialize ImGui with the window and renderer -imgui.init(window, renderer) -imgui.newframe() - -var os = use('os'); -var io = use('io'); -var rasterize = use('rasterize'); -var time = use('time') -var tilemap = use('tilemap') -var geometry = use('geometry') -var res = use('resources') -var input = use('input') - -var graphics = use('graphics') - -var camera = {} - -function updateCameraMatrix(cam) { - def win_w = logical.width - def win_h = logical.height - def view_w = (cam.size?.[0] ?? win_w) / cam.zoom - def view_h = (cam.size?.[1] ?? win_h) / cam.zoom - - def ox = cam.pos[0] - view_w * (cam.anchor?.[0] ?? 0) - def oy = cam.pos[1] - view_h * (cam.anchor?.[1] ?? 0) - - def vx = (cam.viewport?.x ?? 0) * win_w - def vy = (cam.viewport?.y ?? 0) * win_h - def vw = (cam.viewport?.width ?? 1) * win_w - def vh = (cam.viewport?.height ?? 1) * win_h - - def sx = vw / view_w - def sy = vh / view_h // flip-Y later - - /* affine matrix that SDL wants (Y going down) */ - cam.a = sx - cam.c = vx - sx * ox - cam.e = -sy // <-- minus = flip Y - cam.f = vy + vh + sy * oy - - /* convenience inverses */ - cam.ia = 1 / cam.a - cam.ic = -cam.c / cam.a - cam.ie = 1 / cam.e - cam.if = -cam.f / cam.e - - camera = cam -} - -//---- forward transform ---- -function worldToScreenPoint([x,y], camera) { - return { - x: camera.a * x + camera.c, - y: camera.e * y + camera.f - }; -} - -//---- inverse transform ---- -function screenToWorldPoint(pos, camera) { - return { - x: camera.ia * pos[0] + camera.ic, - y: camera.ie * pos[1] + camera.if - }; -} - -//---- rectangle (two corner) ---- -function worldToScreenRect({x,y,width,height}, camera) { - // map bottom-left and top-right - def x1 = camera.a * x + camera.c; - def y1 = camera.e * y + camera.f; - def x2 = camera.a * (x + width) + camera.c; - def y2 = camera.e * (y + height) + camera.f; - - return { - x:Math.min(x1,x2), - y:Math.min(y1,y2), - width:Math.abs(x2-x1), - height:Math.abs(y2-y1) - } -} - -var gameactor - -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 = renderer.load_texture(surf) - return img.gpu -} - -// Convert high-level draw commands to low-level renderer commands -function translate_draw_commands(commands) { - renderer_commands.length = 0 - - commands.forEach(function(cmd) { - if (cmd.material && cmd.material.color && typeof cmd.material.color == 'object') { - renderer.drawColor = cmd.material.color - } - switch(cmd.cmd) { - case "camera": - updateCameraMatrix(cmd.camera, win_size.width, win_size.height) - break - - case "draw_rect": - cmd.rect = worldToScreenRect(cmd.rect, camera) - renderer.fillRect(cmd.rect) - break - - case "draw_line": - var points = cmd.points.map(p => { - var pt = worldToScreenPoint(p, camera) - return[pt.x, pt.y] - }) - renderer.line(points) - break - - case "draw_point": - cmd.pos = worldToScreenPoint(cmd.pos, camera) - renderer.point(cmd.pos) - break - - case "draw_image": - var img = graphics.texture(cmd.image) - var gpu = get_img_gpu(img) - - if (!cmd.scale) cmd.scale = {x:1,y:1} - cmd.rect.width ??= img.width - cmd.rect.height ??= img.height - cmd.rect.width = cmd.rect.width * cmd.scale.x - cmd.rect.height = cmd.rect.height * cmd.scale.y - cmd.rect = worldToScreenRect(cmd.rect, camera) - renderer.texture( - gpu, - img.rect, - cmd.rect, - 0, - {x:0,y:0} - ) - - break - - case "draw_slice9": - var img = graphics.texture(cmd.image) - var gpu = get_img_gpu(img) - if (!gpu) break - var rect = worldToScreenRect(cmd.rect, camera) - renderer.texture9Grid( - gpu, - img.rect, - cmd.slice, - cmd.slice, - cmd.slice, - cmd.slice, - 1, - rect - ) - break - - case "draw_text": - if (!cmd.text) break - if (!cmd.pos) break - - // Get font from the font string (e.g., "smalle.16") - var font = graphics.get_font(cmd.font) - if (!font.gpu) { - var surf = new surface(font.surface) - font.gpu = renderer.load_texture(surf) - } - var gpu = font.gpu - - // Create text geometry buffer - var text_mesh = graphics.make_text_buffer( - cmd.text, - {x: cmd.pos.x, y: cmd.pos.y}, - [cmd.material.color.r, cmd.material.color.g, cmd.material.color.b, cmd.material.color.a], - cmd.wrap || 0, - font - ) - - if (!text_mesh) break - - if (text_mesh.xy.length == 0) break - - // Transform XY coordinates using camera matrix - var camera_params = [camera.a, camera.c, camera.e, camera.f] - var transformed_xy = geometry.transform_xy_blob(text_mesh.xy, camera_params) - - // Create transformed geometry object - var geom = { - xy: transformed_xy, - xy_stride: text_mesh.xy_stride, - uv: text_mesh.uv, - uv_stride: text_mesh.uv_stride, - color: text_mesh.color, - color_stride: text_mesh.color_stride, - indices: text_mesh.indices, - num_vertices: text_mesh.num_vertices, - num_indices: text_mesh.num_indices, - size_indices: text_mesh.size_indices - } - - renderer.geometry_raw(gpu, geom.xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices) - break - - case "tilemap": - var geometryCommands = cmd.tilemap.draw() - - for (var geomCmd of geometryCommands) { - var img = graphics.texture(geomCmd.image) - if (!img) continue - - if (!img.gpu) { - var surf = new surface(img.cpu) - img.gpu = renderer.load_texture(surf) - } - var gpu = img.gpu - - var geom = geomCmd.geometry - - var camera_params = [camera.a, camera.c, camera.e, camera.f] - var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params) - - renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices) - } - break - - case "geometry": - var gpu = get_img_gpu(cmd.image) - log.console(json.encode(cmd)) - var geom = cmd.geometry - - var camera_params = [camera.a, camera.c, camera.e, camera.f] - var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params) - - renderer.geometry_raw(gpu, transformed_xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices) - break - } - }) - - return renderer_commands -} - -///// input ///// -var input_cb -var input_rate = 1/60 -function poll_input() { - var evs = input.get_events() - - // Filter and transform events - if (renderer && Array.isArray(evs)) { - var filteredEvents = [] - var wantMouse = imgui.wantmouse() - var wantKeys = imgui.wantkeys() - - for (var i = 0; i < evs.length; i++) { - var event = evs[i] - var shouldInclude = true - - // Filter mouse events if ImGui wants mouse input - if (wantMouse && (event.type == 'mouse_motion' || - event.type == 'mouse_button_down' || - event.type == 'mouse_button_up' || - event.type == 'mouse_wheel')) { - shouldInclude = false - } - - // Filter keyboard events if ImGui wants keyboard input - if (wantKeys && (event.type == 'key_down' || - event.type == 'key_up' || - event.type == 'text_input' || - event.type == 'text_editing')) { - shouldInclude = false - } - - if (shouldInclude) { - // Transform mouse coordinates from window to renderer coordinates - if (event.pos && (event.type == 'mouse_motion' || - event.type == 'mouse_button_down' || - event.type == 'mouse_button_up' || - event.type == 'mouse_wheel')) { - // Convert window coordinates to renderer logical coordinates - var logicalPos = renderer.coordsFromWindow(event.pos) - event.pos = logicalPos - } - // Handle drop events which also have position - if (event.pos && (event.type == 'drop_file' || - event.type == 'drop_text' || - event.type == 'drop_position')) { - var logicalPos = renderer.coordsFromWindow(event.pos) - event.pos = logicalPos - } - - // Handle window events - if (event.type == 'window_pixel_size_changed') { - win_size.width = event.width - win_size.height = event.height - } - - if (event.type == 'quit') - $_.stop() - - if (event.type.includes('key')) { - if (event.key) - event.key = input.keyname(event.key) - } - - if (event.type.startsWith('mouse_') && event.pos && event.pos.y) - event.pos.y = -event.pos.y + logical.height - - filteredEvents.push(event) - } - } - - evs = filteredEvents - } - - input_cb(evs) - $_.delay(poll_input, input_rate) -} - -prosperon.input = function(fn) -{ - input_cb = fn - poll_input() -} - -// 2) helper to build & send a batch, then call done() -prosperon.create_batch = function create_batch(draw_cmds, done) { - renderer.drawColor = {r:0.1,g:0.1,b:0.15,a:1} - renderer.clear() - - if (draw_cmds && draw_cmds.length) - var commands = translate_draw_commands(draw_cmds) - - renderer.drawColor = {r:1,g:1,b:1,a:1} - imgui.endframe(renderer) - imgui.newframe() - - renderer.present() - - if (done) done() -} - -////////// dmon hot reload //////// -function poll_file_changes() { - dmon.poll(e => { - if (e.action == 'modify' || e.action == 'create') { - // Check if it's an image file - var ext = e.file.split('.').pop().toLowerCase() - var imageExts = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tga', 'webp', 'qoi', 'ase', 'aseprite'] - - if (imageExts.includes(ext)) { - // Try to find the full path for this image - var possiblePaths = [ - e.file, - e.root + e.file, - res.find_image(e.file.split('/').pop().split('.')[0]) - ].filter(p => p) - - for (var path of possiblePaths) { - graphics.tex_hotreload(path) - } - } - } - }) - - // Schedule next poll in 0.5 seconds - $_.delay(poll_file_changes, 0.5) -} - -var dmon = use('dmon') -prosperon.dmon = function() -{ - dmon.watch('.') - poll_file_changes() -} - -var window_cmds = { - size(size) { - window.size = size - }, -} - -prosperon.set_window = function(config) -{ - for (var c in config) - if (window_cmds[c]) window_cmds[c](config[c]) -} - -var renderer_cmds = { - resolution(e) { - logical.width = e.width - logical.height = e.height - renderer.logicalPresentation = {...e} - } -} - -prosperon.set_renderer = function(config) -{ - for (var c in config) - if (renderer_cmds[c]) renderer_cmds[c](config[c]) -} - -prosperon.init = function() { - // No longer needed since we initialize directly -} - -// 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 ca15a74c..49fa58bf 100644 --- a/scripts/graphics.cm +++ b/scripts/graphics.cm @@ -230,75 +230,7 @@ graphics.texture = function texture(path) { if (!cache[id]) { var ipath = res.find_image(id) - // If not found normally, check in accio/32k folder if (!ipath) { - // First check if we have a cached QOI version - var cache_dir = '.prosperon/texture_cache' - var qoi_cache_path = `${cache_dir}/${id}.qoi` - - if (io.exists(qoi_cache_path)) { - // Load the cached QOI file - var qoi_bytes = io.slurpbytes(qoi_cache_path) - var cached_img = qoi.decode(qoi_bytes) - if (cached_img) { - var result = new graphics.Image(cached_img) - cache[id] = result - return cache[id] - } - } - - // Try to find the file in accio/32k with any extension - var accio_base = `accio/32k/${id}` - var found_path = null - - // Check for common image extensions - var extensions = ['', '.qoi', '.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.gif', '.ase', '.aseprite'] - for (var ext of extensions) { - var test_path = accio_base + ext - if (io.exists(test_path)) { - found_path = test_path - break - } - } - - if (found_path) { - // Load the 32k image - var bytes = io.slurpbytes(found_path) - var img_32k = decode_image(bytes, found_path.ext()) - - // Handle single surface images - if (img_32k && img_32k.width && img_32k.pixels) { - // Get the surface module for scaling - var surface = use('surface') - - var surf_4k = surface.scale(img_32k, { - width: Math.floor(img_32k.width / 8), - height: Math.floor(img_32k.height / 8), - mode: 'linear' - }) - - var qoi_data = qoi.encode(surf_4k) - - if (qoi_data && qoi_data.pixels) { - if (!io.exists(cache_dir)) { - io.mkdir(cache_dir) - } - io.slurpwrite(qoi_cache_path, qoi_data.pixels) - } - - // Use the 4k version - var result = new graphics.Image(surf_4k) - cache[id] = result - return cache[id] - } - - // For now, if it's not a simple surface, just return it as-is - // (animations, etc. would need more complex handling) - var result = create_image(found_path) - cache[id] = result - return cache[id] - } - // If still not found, return notex return graphics.texture('notex') } @@ -378,18 +310,6 @@ graphics.texture = function texture(path) { return cached } -graphics.texture.total_size = function() { - var size = 0 - // Not yet implemented, presumably sum of (texture.width * texture.height * 4) for images in RAM - return size -} - -graphics.texture.total_vram = function() { - var vram = 0 - // Not yet implemented, presumably sum of GPU memory usage - return vram -} - graphics.tex_hotreload = function tex_hotreload(file) { var basename = file.split('/').pop().split('.')[0]