draw2d now can send batches of draws to video backends

This commit is contained in:
2025-05-26 12:48:19 -05:00
parent db1afb6477
commit 8074e2a82e
2 changed files with 245 additions and 68 deletions

View File

@@ -439,6 +439,35 @@ function handle_renderer(msg) {
if (!msg.data || !msg.data.pos) return {error: "Missing pos"}; if (!msg.data || !msg.data.pos) return {error: "Missing pos"};
return {data: ren.coordsToWindow(msg.data.pos)}; return {data: ren.coordsToWindow(msg.data.pos)};
case 'batch':
// Execute a batch of operations
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
var results = [];
for (var i = 0; i < msg.data.length; i++) {
var cmd = msg.data[i];
if (!cmd.op) {
results.push({error: "Command at index " + i + " missing op"});
continue;
}
// Create a temporary message object for the command
var temp_msg = {
kind: 'renderer',
id: msg.id,
op: cmd.op,
prop: cmd.prop,
value: cmd.value,
data: cmd.data
};
// Recursively call handle_renderer for each command
var result = handle_renderer(temp_msg);
results.push(result);
}
return {results: results};
default: default:
return {error: "Unknown renderer operation: " + msg.op}; return {error: "Unknown renderer operation: " + msg.op};
} }

View File

@@ -1,4 +1,3 @@
var render = use('render')
var graphics = use('graphics') var graphics = use('graphics')
var math = use('math') var math = use('math')
var util = use('util') var util = use('util')
@@ -11,20 +10,78 @@ A collection of 2D drawing functions that operate in screen space. Provides prim
for lines, rectangles, text, sprite drawing, etc. Immediate mode. for lines, rectangles, text, sprite drawing, etc. Immediate mode.
` `
/*var whiteimage = {} // Draw command accumulator
whiteimage = graphics.make_surface([1,1]) var commands = []
whiteimage.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
render.load_texture(whiteimage)
*/
if (render.point) // Renderer info set by moth
draw.point = function(pos,size,opt = {color:Color.white}, pipeline) { var renderer_actor = null
render.settings(opt) var renderer_id = null
render.pipeline(pipeline)
render.point([pos]) // Set the renderer
draw.set_renderer = function(actor, id) {
renderer_actor = actor
renderer_id = id
}
// Clear accumulated commands
draw.clear = function() {
commands = []
}
// Get accumulated commands
draw.get_commands = function() {
return commands
}
// Flush all commands to renderer
draw.flush = function() {
if (!renderer_actor || !renderer_id || commands.length === 0) return
// Convert commands to batch format
var batch_data = commands.map(function(cmd) {
return {
op: cmd.op,
prop: cmd.prop,
value: cmd.value,
data: cmd.data
}
})
// Send all commands in a single batch message
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "batch",
data: batch_data
})
// Clear commands after sending
commands = []
}
// Helper to add a command
function add_command(cmd) {
commands.push(cmd)
}
// Drawing functions
draw.point = function(pos, size, opt = {color: Color.white}, pipeline) {
if (opt.color) {
add_command({
kind: "renderer",
id: renderer_id,
op: "set",
prop: "drawColor",
value: opt.color
})
} }
else add_command({
draw.point = function() { throw new Error('Backend cannot draw points.') } kind: "renderer",
id: renderer_id,
op: "point",
data: {points: [pos]}
})
}
draw.point[prosperon.DOC] = ` draw.point[prosperon.DOC] = `
:param pos: A 2D position ([x, y]) where the point should be drawn. :param pos: A 2D position ([x, y]) where the point should be drawn.
:param size: The size of the point. :param size: The size of the point.
@@ -81,7 +138,14 @@ function software_ellipse(pos, radii, opt)
[cx + x, cy + y], [cx - x, cy + y], [cx + x, cy + y], [cx - x, cy + y],
[cx + x, cy - y], [cx - x, cy - y] [cx + x, cy - y], [cx - x, cy - y]
].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle)) ].filter(pt => within_wedge(pt[0]-cx, pt[1]-cy, start, end, full_circle))
if (pts.length) render.point(pts) if (pts.length) {
add_command({
kind: "renderer",
id: renderer_id,
op: "point",
data: {points: pts}
})
}
} }
while (px < py) { while (px < py) {
@@ -139,7 +203,14 @@ function software_ellipse(pos, radii, opt)
} }
} }
} }
if (strips.length) render.rects(strips) if (strips.length) {
add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {rects: strips}
})
}
} }
var ellipse_def = { var ellipse_def = {
@@ -150,17 +221,20 @@ var ellipse_def = {
thickness: 1, thickness: 1,
} }
if (render.ellipse) draw.ellipse = function(pos, radii, def, pipeline) {
draw.ellipse = function(pos, radii, def, pipeline) { var opt = def ? {...ellipse_def, ...def} : ellipse_def
} if (opt.color) {
else add_command({
draw.ellipse = function(pos, radii, def, pipeline) { kind: "renderer",
var opt = def ? {...ellipse_def, ...def} : ellipse_def id: renderer_id,
render.settings(opt) op: "set",
render.pipeline(pipeline) prop: "drawColor",
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1]) value: opt.color
software_ellipse(pos, radii, opt) })
} }
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
software_ellipse(pos, radii, opt)
}
var line_def = { var line_def = {
color: Color.white, color: Color.white,
@@ -176,9 +250,21 @@ draw.line = function(points, def, pipeline)
else else
opt = line_def opt = line_def
render.settings(opt) if (opt.color) {
render.pipeline(pipeline) add_command({
render.line(points) kind: "renderer",
id: renderer_id,
op: "set",
prop: "drawColor",
value: opt.color
})
}
add_command({
kind: "renderer",
id: renderer_id,
op: "line",
data: {points: points}
})
} }
draw.cross = function render_cross(pos, size, def, pipe) { draw.cross = function render_cross(pos, size, def, pipe) {
@@ -203,14 +289,24 @@ draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def
function software_outline_rect(rect, thickness) function software_outline_rect(rect, thickness)
{ {
if (thickness <= 0) { if (thickness <= 0) {
render.rectangle(rect); add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return; return;
} }
/* stroke swallows the whole thing → fill instead */ /* stroke swallows the whole thing → fill instead */
if ((thickness << 1) >= rect.width || if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height) { (thickness << 1) >= rect.height) {
render.rectangle(rect) // filled add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return return
} }
@@ -219,14 +315,21 @@ function software_outline_rect(rect, thickness)
x1 = rect.x + rect.width, x1 = rect.x + rect.width,
y1 = rect.y + rect.height y1 = rect.y + rect.height
render.rects([ add_command({
{ x:x0, y:y0, width:rect.width, height:thickness }, // top kind: "renderer",
{ x:x0, y:y1-thickness, width:rect.width, height:thickness }, // bottom id: renderer_id,
{ x:x0, y:y0+thickness, width:thickness, op: "rects",
height:rect.height - (thickness<<1) }, // left data: {
{ x:x1-thickness, y:y0+thickness, width:thickness, rects: [
height:rect.height - (thickness<<1) } // right { x:x0, y:y0, width:rect.width, height:thickness }, // top
]) { x:x0, y:y1-thickness, width:rect.width, height:thickness }, // bottom
{ x:x0, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) }, // left
{ x:x1-thickness, y:y0+thickness, width:thickness,
height:rect.height - (thickness<<1) } // right
]
}
})
} }
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
@@ -260,16 +363,23 @@ function software_round_rect(rect, radius, thickness = 1)
const r_in = radius - thickness const r_in = radius - thickness
/* straight bands (top/bottom/left/right) ------------------------- */ /* straight bands (top/bottom/left/right) ------------------------- */
render.rects([ add_command({
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), kind: "renderer",
height:thickness }, // top id: renderer_id,
{ x:x0 + radius, y:y1 - thickness + 1, op: "rects",
width:rect.width - (radius << 1), height:thickness }, // bottom data: {
{ x:x0, y:y0 + radius, width:thickness, rects: [
height:rect.height - (radius << 1) }, // left { x:x0 + radius, y:y0, width:rect.width - (radius << 1),
{ x:x1 - thickness + 1, y:y0 + radius, width:thickness, height:thickness }, // top
height:rect.height - (radius << 1) } // right { x:x0 + radius, y:y1 - thickness + 1,
]) width:rect.width - (radius << 1), height:thickness }, // bottom
{ x:x0, y:y0 + radius, width:thickness,
height:rect.height - (radius << 1) }, // left
{ x:x1 - thickness + 1, y:y0 + radius, width:thickness,
height:rect.height - (radius << 1) } // right
]
}
})
/* corner arcs ---------------------------------------------------- */ /* corner arcs ---------------------------------------------------- */
const strips = [] const strips = []
@@ -296,7 +406,12 @@ function software_round_rect(rect, radius, thickness = 1)
) )
} }
render.rects(strips) add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {rects: strips}
})
} }
/* ------------------------------------------------------------------------ /* ------------------------------------------------------------------------
@@ -312,18 +427,32 @@ function software_fill_round_rect(rect, radius)
y1 = rect.y + rect.height - 1 y1 = rect.y + rect.height - 1
/* main column */ /* main column */
render.rects([ add_command({
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1), kind: "renderer",
height:rect.height } id: renderer_id,
]) op: "rects",
data: {
rects: [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:rect.height }
]
}
})
/* side columns */ /* side columns */
render.rects([ add_command({
{ x:x0, y:y0 + radius, width:radius, kind: "renderer",
height:rect.height - (radius << 1) }, id: renderer_id,
{ x:x1 - radius + 1, y:y0 + radius, width:radius, op: "rects",
height:rect.height - (radius << 1) } data: {
]) rects: [
{ x:x0, y:y0 + radius, width:radius,
height:rect.height - (radius << 1) },
{ x:x1 - radius + 1, y:y0 + radius, width:radius,
height:rect.height - (radius << 1) }
]
}
})
/* corner caps */ /* corner caps */
const cx_l = x0 + radius, cx_r = x1 - radius const cx_l = x0 + radius, cx_r = x1 - radius
@@ -342,7 +471,12 @@ function software_fill_round_rect(rect, radius)
) )
} }
render.rects(caps) add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {rects: caps}
})
} }
var rect_def = { var rect_def = {
@@ -352,8 +486,15 @@ var rect_def = {
} }
draw.rectangle = function render_rectangle(rect, def, pipeline) { draw.rectangle = function render_rectangle(rect, def, pipeline) {
var opt = def ? {...rect_def, ...def} : rect_def var opt = def ? {...rect_def, ...def} : rect_def
render.settings(opt) if (opt.color) {
render.pipeline(pipeline) add_command({
kind: "renderer",
id: renderer_id,
op: "set",
prop: "drawColor",
value: opt.color
})
}
var t = opt.thickness|0 var t = opt.thickness|0
@@ -361,7 +502,12 @@ draw.rectangle = function render_rectangle(rect, def, pipeline) {
if (opt.radius) if (opt.radius)
software_fill_round_rect(rect, opt.radius) software_fill_round_rect(rect, opt.radius)
else else
render.rectangle(rect) add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return return
} }
@@ -387,7 +533,7 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
if (typeof image === "string") if (typeof image === "string")
image = graphics.texture(image) image = graphics.texture(image)
render.slice9(image, rect, slice, slice9_info, pipeline); // TODO: Implement slice9 rendering via SDL video actor
} }
draw.slice9[prosperon.DOC] = ` draw.slice9[prosperon.DOC] = `
:param image: An image object or string path to a texture. :param image: An image object or string path to a texture.
@@ -416,8 +562,9 @@ draw.image = function image(image, rect = [0,0], rotation = 0, anchor = [0,0], s
rect.width ??= image.texture.width rect.width ??= image.texture.width
rect.height ??= image.texture.height rect.height ??= image.texture.height
info ??= image_info; info ??= image_info;
render.settings(info)
render.image(image, rect, rotation, anchor, shear, info) // TODO: Handle texture loading and sending texture_id
// For now, we skip image rendering as it requires texture management
} }
function software_circle(pos, radius) function software_circle(pos, radius)
@@ -460,7 +607,8 @@ var sysfont = graphics.get_font('fonts/c64.ttf', 8)
draw.text = function text(text, rect, font = sysfont, size = 0, color = Color.white, wrap = 0, pipeline) { draw.text = function text(text, rect, font = sysfont, size = 0, color = Color.white, wrap = 0, pipeline) {
if (typeof font === 'string') font = graphics.get_font(font) if (typeof font === 'string') font = graphics.get_font(font)
var mesh = graphics.make_text_buffer(text, rect, 0, color, wrap, font) var mesh = graphics.make_text_buffer(text, rect, 0, color, wrap, font)
render.geometry(font, mesh)
// TODO: Handle text rendering via geometry
} }
draw.text[prosperon.DOC] = ` draw.text[prosperon.DOC] = `
:param text: The string to draw. :param text: The string to draw.