render update

This commit is contained in:
2025-07-27 17:08:29 -05:00
parent d5789598a0
commit 5ae95aee01
5 changed files with 232 additions and 503 deletions

View File

@@ -53,7 +53,7 @@ clay.contain = layout.contain;
clay.draw = function draw(fn)
{
size = [prosperon.logical.x, prosperon.logical.y]
var size = [prosperon.logical.x, prosperon.logical.y]
lay_ctx.reset();
boxes = [];
var root = lay_ctx.item();
@@ -69,7 +69,7 @@ clay.draw = function draw(fn)
id:root,
config:root_config
});
fn()
lay_ctx.run();
// Adjust bounding boxes for padding

View File

@@ -1,5 +1,71 @@
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');
@@ -7,9 +73,9 @@ var time = use('time')
var tilemap = use('tilemap')
var geometry = use('geometry')
var res = use('resources')
var input = use('input')
var video = arg[0]
var graphics = use('graphics', arg[0])
var graphics = use('graphics')
var camera = {}
@@ -88,17 +154,11 @@ 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
})
if (cmd.material && cmd.material.color && typeof cmd.material.color == 'object') {
renderer.drawColor = cmd.material.color
}
switch(cmd.cmd) {
case "camera":
@@ -107,91 +167,29 @@ function translate_draw_commands(commands) {
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}
})
}
renderer.fillRect(cmd.rect)
break
case "draw_line":
renderer_commands.push({
op: "line",
data: {points: cmd.points.map(p => {
var points = cmd.points.map(p => {
var pt = worldToScreenPoint(p, camera)
return [pt.x, pt.y]
})}
return[pt.x, pt.y]
})
renderer.line(points)
break
case "draw_point":
cmd.pos = worldToScreenPoint(cmd.pos, camera)
renderer_commands.push({
op: "point",
data: {points: [cmd.pos]}
})
renderer.point(cmd.pos)
break
case "draw_image":
var img = graphics.texture(cmd.image)
if (!img.gpu) {
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
var gpu = img.gpu
if (!gpu) break
if (!cmd.scale) cmd.scale = {x:1,y:1}
cmd.rect.width ??= img.width
@@ -199,56 +197,14 @@ function translate_draw_commands(commands) {
cmd.rect.width = cmd.rect.width * cmd.scale.x
cmd.rect.height = cmd.rect.height * cmd.scale.y
cmd.rect = worldToScreenRect(cmd.rect, camera)
if (cmd.info && (cmd.info.tile_x || cmd.info.tile_y)) {
// Use tiling functionality
var tile_buffer = geometry.tile(
img,
img.rect, // source rect in UV coords
cmd.rect, // destination rect in screen coords
{
repeat_x: cmd.info.tile_x || false,
repeat_y: cmd.info.tile_y || false
}
renderer.texture(
gpu,
img.rect,
cmd.rect,
0,
{x:0,y:0}
)
if (tile_buffer) {
// Transform XY coordinates using camera matrix
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(tile_buffer.xy, camera_params)
// Create transformed geometry object
var transformed_geom = {
xy: transformed_xy,
xy_stride: tile_buffer.xy_stride,
uv: tile_buffer.uv,
uv_stride: tile_buffer.uv_stride,
color: tile_buffer.color,
color_stride: tile_buffer.color_stride,
indices: tile_buffer.indices,
num_vertices: tile_buffer.num_vertices,
num_indices: tile_buffer.num_indices,
size_indices: tile_buffer.size_indices,
texture_id: gpu
}
renderer_commands.push({
op: "geometry_raw",
data: transformed_geom
})
}
} else {
// Normal non-tiled rendering
renderer_commands.push({
op: "texture",
data: {
texture_id: gpu,
dst: cmd.rect,
src: img.rect
}
})
}
break
case "draw_text":
@@ -257,7 +213,11 @@ function translate_draw_commands(commands) {
// Get font from the font string (e.g., "smalle.16")
var font = graphics.get_font(cmd.font)
if (!font || !font.texture || !font.texture.id) break
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(
@@ -277,7 +237,7 @@ function translate_draw_commands(commands) {
var transformed_xy = geometry.transform_xy_blob(text_mesh.xy, camera_params)
// Create transformed geometry object
var transformed_geom = {
var geom = {
xy: transformed_xy,
xy_stride: text_mesh.xy_stride,
uv: text_mesh.uv,
@@ -287,36 +247,10 @@ function translate_draw_commands(commands) {
indices: text_mesh.indices,
num_vertices: text_mesh.num_vertices,
num_indices: text_mesh.num_indices,
size_indices: text_mesh.size_indices,
texture_id: font.texture.id
size_indices: text_mesh.size_indices
}
renderer_commands.push({
op: "geometry_raw",
data: transformed_geom
})
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,
src: img.rect,
leftWidth: cmd.slice,
rightWidth: cmd.slice,
topHeight: cmd.slice,
bottomHeight: cmd.slice,
scale: 1.0,
dst: cmd.rect
}
})
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":
@@ -326,8 +260,14 @@ function translate_draw_commands(commands) {
// Process each geometry command (one per texture)
for (var geomCmd of geometryCommands) {
var img = graphics.texture(geomCmd.image)
if (!img) continue
// Get GPU texture following draw_image pattern
if (!img.gpu) {
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
var gpu = img.gpu
if (!gpu) continue
// Transform geometry through camera and send to renderer
var geom = geomCmd.geometry
@@ -336,39 +276,27 @@ function translate_draw_commands(commands) {
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
// Create new geometry object with transformed coordinates
var transformed_geom = {
xy: transformed_xy,
xy_stride: geom.xy_stride,
uv: geom.uv,
uv_stride: geom.uv_stride,
color: geom.color,
color_stride: geom.color_stride,
indices: geom.indices,
num_vertices: geom.num_vertices,
num_indices: geom.num_indices,
size_indices: geom.size_indices,
texture_id: gpu
}
renderer_commands.push({
op: "geometry_raw",
data: transformed_geom
})
// Render directly instead of pushing to commands
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 texture_id
var gpu
if (cmd.texture_id) {
// Use the provided texture ID directly
texture_id = cmd.texture_id
// If texture_id provided, assume it's already a GPU texture
gpu = cmd.texture_id
} else {
// Fall back to looking up by image path
var img = graphics.texture(cmd.image)
var gpu = img.gpu
if (!gpu) break
texture_id = gpu
if (!img) break
// Get GPU texture following draw_image pattern
if (!img.gpu) {
var surf = new surface(img.cpu)
img.gpu = renderer.load_texture(surf)
}
gpu = img.gpu
}
// Transform geometry through camera and send to renderer
@@ -378,25 +306,8 @@ function translate_draw_commands(commands) {
var camera_params = [camera.a, camera.c, camera.e, camera.f]
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
// Create new geometry object with transformed coordinates
var transformed_geom = {
xy: transformed_xy,
xy_stride: geom.xy_stride,
uv: geom.uv,
uv_stride: geom.uv_stride,
color: geom.color,
color_stride: geom.color_stride,
indices: geom.indices,
num_vertices: geom.num_vertices,
num_indices: geom.num_indices,
size_indices: geom.size_indices,
texture_id: texture_id
}
renderer_commands.push({
op: "geometry_raw",
data: transformed_geom
})
// Render directly instead of pushing to commands
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
}
})
@@ -405,30 +316,80 @@ function translate_draw_commands(commands) {
}
///// input /////
var input = use('input')
var input_cb
var input_rate = 1/60
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
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
}
if (ev.type == 'quit')
// 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 (ev.type.includes('key')) {
if (ev.key)
ev.key = input.keyname(ev.key)
if (event.type.includes('key')) {
if (event.key)
event.key = input.keyname(event.key)
}
if (ev.type.startsWith('mouse_') && ev.pos && ev.pos.y)
ev.pos.y = -ev.pos.y + logical.height
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)
}
@@ -440,20 +401,19 @@ prosperon.input = function(fn)
// 2) helper to build & send a batch, then call done()
prosperon.create_batch = 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'}
]
renderer.drawColor = {r:0.1,g:0.1,b:0.15,a:1}
renderer.clear()
if (draw_cmds && draw_cmds.length)
batch.push(...translate_draw_commands(draw_cmds))
var commands = 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'}
)
renderer.drawColor = {r:1,g:1,b:1,a:1}
imgui.endframe(renderer)
imgui.newframe()
send(video, {kind:'renderer', op:'batch', data:batch}, done)
renderer.present()
if (done) done()
}
////////// dmon hot reload ////////
@@ -492,7 +452,7 @@ prosperon.dmon = function()
var window_cmds = {
size(size) {
send(video, {kind: 'window', op:'set', data: {property: 'size', value: size}})
window.size = size
},
}
@@ -506,7 +466,7 @@ var renderer_cmds = {
resolution(e) {
logical.width = e.width
logical.height = e.height
send(video, {kind:"renderer", op:'set', prop:'logicalPresentation', value: {...e}})
renderer.logicalPresentation = {...e}
}
}
@@ -516,4 +476,30 @@ prosperon.set_renderer = function(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

View File

@@ -108,25 +108,12 @@ function full_upload(buffers) {
cmds.submit();
}
full_upload[cell.DOC] = `Acquire a command buffer and upload the provided data buffers to the GPU, then submit.
:param buffers: An array of data buffers to be uploaded.
:return: None
`
function bind_pipeline(pass, pipeline) {
make_pipeline(pipeline)
pass.bind_pipeline(pipeline.gpu)
pass.pipeline = pipeline;
}
bind_pipeline[cell.DOC] = `Ensure the specified pipeline is created on the GPU and bind it to the given render pass.
:param pass: The current render pass to bind the pipeline to.
:param pipeline: The pipeline object containing shader and state info.
:return: None
`
var main_pass;
var cornflower = [62/255,96/255,113/255,1];
@@ -141,13 +128,6 @@ function get_pipeline_ubo_slot(pipeline, name) {
return null;
}
get_pipeline_ubo_slot[cell.DOC] = `Return the index of a uniform buffer block within the pipeline's vertex reflection data by name suffix.
:param pipeline: The pipeline whose vertex reflection is inspected.
:param name: A string suffix to match against the uniform buffer block name.
:return: The integer index of the matching UBO, or null if not found.
`
function transpose4x4(val) {
var out = [];
out[0] = val[0]; out[1] = val[4]; out[2] = val[8]; out[3] = val[12];
@@ -157,12 +137,6 @@ function transpose4x4(val) {
return out;
}
transpose4x4[cell.DOC] = `Return a new 4x4 matrix array that is the transpose of the passed matrix.
:param val: An array of length 16 representing a 4x4 matrix in row-major format.
:return: A new array of length 16 representing the transposed matrix.
`
function ubo_obj_to_array(pipeline, name, obj) {
var ubo;
for (var i = 0; i < pipeline.vertex.reflection.ubos.length; i++) {
@@ -190,14 +164,6 @@ function ubo_obj_to_array(pipeline, name, obj) {
return buf;
}
ubo_obj_to_array[cell.DOC] = `Construct an ArrayBuffer containing UBO data from the provided object, matching the pipeline's reflection info.
:param pipeline: The pipeline whose vertex reflection is read for UBO structure.
:param name: The name suffix that identifies the target UBO in the reflection data.
:param obj: An object whose properties match the UBO members.
:return: An ArrayBuffer containing packed UBO data.
`
function type_to_byte_count(type) {
switch (type) {
case 'float': return 4;
@@ -209,12 +175,6 @@ function type_to_byte_count(type) {
}
}
type_to_byte_count[cell.DOC] = `Return the byte size for known float-based types.
:param type: A string type identifier (e.g., 'float', 'vec2', 'vec3', 'vec4', 'mat4').
:return: Integer number of bytes.
`
var sprite_model_ubo = {
model: unit_transform,
color: [1,1,1,1]
@@ -288,12 +248,6 @@ function make_pipeline(pipeline) {
pipeline.gpu = context.make_pipeline(pipeline);
}
make_pipeline[cell.DOC] = `Create and store a GPU pipeline object if it has not already been created.
:param pipeline: An object describing the pipeline state, shaders, and reflection data.
:return: None
`
var shader_type;
function make_shader(sh_file) {
@@ -319,12 +273,6 @@ function make_shader(sh_file) {
return shader
}
make_shader[cell.DOC] = `Load and compile a shader from disk, caching the result. Reflective metadata is also loaded.
:param sh_file: The base filename (without extension) of the shader to compile.
:return: A shader object with GPU and reflection data attached.
`
var render_queue = [];
var hud_queue = [];
@@ -355,12 +303,6 @@ function upload_model(model) {
context.upload(this, bufs);
}
upload_model[cell.DOC] = `Upload all buffer-like properties of the given model to the GPU.
:param model: An object whose buffer properties are to be uploaded.
:return: None
`
function bind_model(pass, pipeline, model) {
var buffers = pipeline.vertex_buffer_descriptions;
var bufs = [];
@@ -373,14 +315,6 @@ function bind_model(pass, pipeline, model) {
pass.bind_index_buffer(model.indices);
}
bind_model[cell.DOC] = `Bind the model's vertex and index buffers for the given pipeline and render pass.
:param pass: The current render pass.
:param pipeline: The pipeline object with vertex buffer descriptions.
:param model: The model object containing matching buffers and an index buffer.
:return: None
`
function bind_mat(pass, pipeline, mat) {
var imgs = [];
var refl = pipeline.fragment.reflection;
@@ -396,14 +330,6 @@ function bind_mat(pass, pipeline, mat) {
}
}
bind_mat[cell.DOC] = `Bind the material images and samplers needed by the pipeline's fragment shader.
:param pass: The current render pass.
:param pipeline: The pipeline whose fragment shader reflection indicates required textures.
:param mat: An object mapping the required image names to {texture, sampler}.
:return: None
`
function group_sprites_by_texture(sprites, mesh) {
if (sprites.length == 0) return;
for (var i = 0; i < sprites.length; i++) {
@@ -433,13 +359,6 @@ function group_sprites_by_texture(sprites, mesh) {
*/
}
group_sprites_by_texture[cell.DOC] = `Assign each sprite to the provided mesh, generating index data as needed.
:param sprites: An array of sprite objects.
:param mesh: A mesh object (pos, color, uv, indices, etc.) to link to each sprite.
:return: None
`
var main_color = {
type:"2d",
format: "rgba8",
@@ -562,13 +481,6 @@ function render_camera(cmds, camera) {
hud_queue = [];
}
render_camera[cell.DOC] = `Render a scene using the provided camera, drawing both render queue and HUD queue items.
:param cmds: A command buffer obtained from the GPU context.
:param camera: The camera object (with size, optional target, etc.).
:return: None
`
var swaps = [];
render.present = function() {
os.clean_transforms();
@@ -602,11 +514,6 @@ render.present = function() {
}
}
render.present[cell.DOC] = `Perform the per-frame rendering and present the final swapchain image, including imgui pass if available.
:return: None
`
var stencil_write = {
compare: "always",
fail_op: "replace",
@@ -637,12 +544,6 @@ function fillmask(ref) {
render.draw(shape.quad);
}
render.fillmask[cell.DOC] = `Draw a fullscreen shape using a 'screenfill' shader to populate the stencil buffer with a given reference.
:param ref: The stencil reference value to write.
:return: None
`
var stencil_invert = {
compare: "always",
fail_op: "invert",
@@ -671,36 +572,14 @@ function mask(image, pos, scale, rotation = 0, ref = 1) {
render.draw(shape.quad);
}
render.mask[cell.DOC] = `Draw an image to the stencil buffer, marking its area with a specified reference value.
:param image: A texture or string path (which is converted to a texture).
:param pos: The translation (x, y) for the image placement.
:param scale: Optional scaling applied to the texture.
:param rotation: Optional rotation in radians (unused by default).
:param ref: The stencil reference value to write.
:return: None
`
render.viewport = function(rect) {
context.viewport(rect);
}
render.viewport[cell.DOC] = `Set the GPU viewport to the specified rectangle.
:param rect: A rectangle [x, y, width, height].
:return: None
`
render.scissor = function(rect) {
render.viewport(rect)
}
render.scissor[cell.DOC] = `Set the GPU scissor region to the specified rectangle (alias of render.viewport).
:param rect: A rectangle [x, y, width, height].
:return: None
`
var std_sampler
if (tracy) tracy.gpu_init()
@@ -712,32 +591,16 @@ render.queue = function(cmd) {
current_queue.push(cmd)
}
render.queue[cell.DOC] = `Enqueue one or more draw commands. These commands are batched until render_camera is called.
:param cmd: Either a single command object or an array of command objects.
:return: None
`
render.setup_draw = function() {
current_queue = render_queue;
prosperon.draw();
}
render.setup_draw[cell.DOC] = `Switch the current queue to the primary scene render queue, then invoke 'prosperon.draw' if defined.
:return: None
`
render.setup_hud = function() {
current_queue = hud_queue;
prosperon.hud();
}
render.setup_hud[cell.DOC] = `Switch the current queue to the HUD render queue, then invoke 'prosperon.hud' if defined.
:return: None
`
render.initialize = function(config)
{
var default_conf = {

View File

@@ -630,7 +630,7 @@ var replies = {}
globalThis.send = function send(actor, message, reply) {
if (typeof actor != 'object')
throw new Error('Must send to an actor object. Provided: ' + actor);
throw new Error(`Must send to an actor object. Provided: ${json.encode(actor)}`);
if (typeof message != 'object')
throw new Error('Message must be an object')

View File

@@ -1,54 +1,18 @@
var graphics = this
graphics[cell.DOC] = `
Provides functionality for loading and managing images, fonts, textures, and sprite meshes.
Includes both JavaScript and C-implemented routines for creating geometry buffers, performing
rectangle packing, etc.
`
var renderer_actor = arg?.[0] || null
var io = use('io')
var time = use('time')
var res = use('resources')
var json = use('json')
var os = use('os')
var GPU = Symbol()
var CPU = Symbol()
var LASTUSE = Symbol()
var LOADING = Symbol()
var cache = {}
var pending_gpu_loads = []
graphics.setup = function(renderer)
{
renderer_actor = renderer
// Process any pending GPU loads
if (renderer_actor && pending_gpu_loads.length > 0) {
log.console(`Processing ${pending_gpu_loads.length} pending GPU loads`)
for (var img of pending_gpu_loads) {
img.loadGPU()
}
pending_gpu_loads = []
}
// Also process any cached images that need GPU loading
if (renderer_actor) {
for (var key in cache) {
var img = cache[key]
if (img instanceof graphics.Image && img.cpu && img.gpu == 0) {
img.loadGPU()
}
}
}
}
// Image constructor function
// cpu is the surface
graphics.Image = function(surfaceData) {
// Initialize properties
this.cpu = surfaceData || null;
this.gpu = 0;
this.texture = 0;
@@ -58,40 +22,6 @@ graphics.Image = function(surfaceData) {
this.rect = {x:0, y:0, width:this.width, height:this.height};
this[LOADING] = false;
this[LASTUSE] = time.number();
// Load GPU texture if renderer is available, otherwise queue it
if (renderer_actor && this.cpu) {
this.loadGPU();
} else if (this.cpu) {
// Queue for later GPU loading when renderer is available
pending_gpu_loads.push(this)
}
}
graphics.Image.prototype.loadGPU = function() {
if (!this[LOADING] && renderer_actor && this.cpu) {
this[LOADING] = true;
var self = this;
send(renderer_actor, {
kind: "renderer",
op: "loadTexture",
data: this.cpu
}, function(response) {
if (response.error) {
log.error("Failed to load texture:")
log.error(response.error)
self[LOADING] = false;
} else {
// Store the full response as texture (has width/height)
self.texture = response;
// Store just the ID as gpu
self.gpu = response.id || response;
decorate_rect_px(self);
self[LOADING] = false;
}
});
}
}
// Add methods to prototype
@@ -197,19 +127,13 @@ function create_image(path){
return makeAnim(wrapFrames(raw), true);
}
/* ── Case C: ASE helpers returned { animName:{frames,loop}, … } or single frame ── */
if(typeof raw == 'object' && !raw.width) {
// Check if it's a single surface from ASE (single frame, no tags)
if(raw.surface) {
if(raw.surface)
return new graphics.Image(raw.surface)
}
// Check if it's an untagged animation (multiple frames, no tags)
// This happens when ASE has no tags but multiple frames
if(raw.frames && Array.isArray(raw.frames) && raw.loop != null)
return makeAnim(wrapFrames(raw.frames), !!raw.loop);
// Multiple named animations from ASE (with tags)
def anims = {};
for(def [name, anim] of Object.entries(raw)){
if(anim && Array.isArray(anim.frames))
@@ -240,9 +164,6 @@ image.dimensions = function() {
}
return [width, height].scale([this.rect[2], this.rect[3]])
}
image.dimensions[cell.DOC] = `
:return: A 2D array [width, height] that is the scaled size of this image (texture size * rect size).
`
var spritesheet
var sheet_frames = []
@@ -261,10 +182,6 @@ function pack_into_sheet(images) {
graphics.is_image = function(obj) {
if (obj.texture && obj.rect) return true
}
graphics.is_image[cell.DOC] = `
:param obj: An object to check.
:return: True if 'obj' has a .texture and a .rect property, indicating it's an image object.
`
graphics.texture_from_data = function(data)
{
@@ -278,7 +195,7 @@ graphics.texture_from_data = function(data)
return img;
}
graphics.from_surface = function(id, surf)
graphics.from_surface = function(surf)
{
return make_handle(surf)
}
@@ -388,32 +305,20 @@ graphics.texture = function texture(path) {
return cached
}
graphics.texture[cell.DOC] = `
:param path: A string path to an image file or an already-loaded image object.
:return: An image object with {surface, texture, frames?, etc.} depending on the format.
Load or retrieve a cached image, converting it into a GPU texture. If 'path' is already an object, its returned directly.
`
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_size[cell.DOC] = `
:return: The total estimated memory size of all cached textures in RAM, in bytes. (Not yet implemented.)
`
graphics.texture.total_vram = function() {
var vram = 0
// Not yet implemented, presumably sum of GPU memory usage
return vram
}
graphics.texture.total_vram[cell.DOC] = `
:return: The total estimated GPU memory usage of all cached textures, in bytes. (Not yet implemented.)
`
graphics.tex_hotreload = function tex_hotreload(file) {
// Extract just the filename without path and extension
var basename = file.split('/').pop().split('.')[0]
// Check if this basename exists in our cache
@@ -445,17 +350,7 @@ graphics.tex_hotreload = function tex_hotreload(file) {
oldimg.rect = {x:0, y:0, width:img.cpu.width, height:img.cpu.height}
decorate_rect_px(oldimg)
}
// If the texture was on GPU, trigger reload
if (oldGPU && renderer_actor) {
oldimg.loadGPU()
}
}
graphics.tex_hotreload[cell.DOC] = `
:param file: The file path that was changed on disk.
:return: None
Reload the image for the given file, updating the cached copy in memory and GPU.
`
/**
Merges specific properties from nv into ov, using an array of property names.
@@ -494,21 +389,6 @@ graphics.get_font = function get_font(path) {
var data = io.slurpbytes(fullpath)
var font = graphics.make_font(data, size)
// Load font texture via renderer actor (async)
if (renderer_actor) {
send(renderer_actor, {
kind: "renderer",
op: "loadTexture",
data: font.surface
}, function(response) {
if (response.error) {
log.error("Failed to load font texture:", response.error);
} else {
font.texture = response;
}
});
}
fontcache[fontstr] = font
return font