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"};
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:
return {error: "Unknown renderer operation: " + msg.op};
}

View File

@@ -1,4 +1,3 @@
var render = use('render')
var graphics = use('graphics')
var math = use('math')
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.
`
/*var whiteimage = {}
whiteimage = graphics.make_surface([1,1])
whiteimage.rect({x:0,y:0,width:1,height:1}, [1,1,1,1])
render.load_texture(whiteimage)
*/
// Draw command accumulator
var commands = []
if (render.point)
draw.point = function(pos,size,opt = {color:Color.white}, pipeline) {
render.settings(opt)
render.pipeline(pipeline)
render.point([pos])
// Renderer info set by moth
var renderer_actor = null
var renderer_id = null
// 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
draw.point = function() { throw new Error('Backend cannot draw points.') }
add_command({
kind: "renderer",
id: renderer_id,
op: "point",
data: {points: [pos]}
})
}
draw.point[prosperon.DOC] = `
:param pos: A 2D position ([x, y]) where the point should be drawn.
: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]
].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) {
@@ -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 = {
@@ -150,17 +221,20 @@ var ellipse_def = {
thickness: 1,
}
if (render.ellipse)
draw.ellipse = function(pos, radii, def, pipeline) {
}
else
draw.ellipse = function(pos, radii, def, pipeline) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def
render.settings(opt)
render.pipeline(pipeline)
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
software_ellipse(pos, radii, opt)
draw.ellipse = function(pos, radii, def, pipeline) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def
if (opt.color) {
add_command({
kind: "renderer",
id: renderer_id,
op: "set",
prop: "drawColor",
value: opt.color
})
}
if (opt.thickness <= 0) opt.thickness = Math.max(radii[0], radii[1])
software_ellipse(pos, radii, opt)
}
var line_def = {
color: Color.white,
@@ -176,9 +250,21 @@ draw.line = function(points, def, pipeline)
else
opt = line_def
render.settings(opt)
render.pipeline(pipeline)
render.line(points)
if (opt.color) {
add_command({
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) {
@@ -203,14 +289,24 @@ draw.arrow = function render_arrow(start, end, wingspan = 4, wingangle = 10, def
function software_outline_rect(rect, thickness)
{
if (thickness <= 0) {
render.rectangle(rect);
add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return;
}
/* stroke swallows the whole thing → fill instead */
if ((thickness << 1) >= rect.width ||
(thickness << 1) >= rect.height) {
render.rectangle(rect) // filled
add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return
}
@@ -219,14 +315,21 @@ function software_outline_rect(rect, thickness)
x1 = rect.x + rect.width,
y1 = rect.y + rect.height
render.rects([
{ 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
])
add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {
rects: [
{ 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
/* straight bands (top/bottom/left/right) ------------------------- */
render.rects([
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:thickness }, // top
{ 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
])
add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {
rects: [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:thickness }, // top
{ 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 ---------------------------------------------------- */
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
/* main column */
render.rects([
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:rect.height }
])
add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
data: {
rects: [
{ x:x0 + radius, y:y0, width:rect.width - (radius << 1),
height:rect.height }
]
}
})
/* side columns */
render.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) }
])
add_command({
kind: "renderer",
id: renderer_id,
op: "rects",
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 */
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 = {
@@ -352,8 +486,15 @@ var rect_def = {
}
draw.rectangle = function render_rectangle(rect, def, pipeline) {
var opt = def ? {...rect_def, ...def} : rect_def
render.settings(opt)
render.pipeline(pipeline)
if (opt.color) {
add_command({
kind: "renderer",
id: renderer_id,
op: "set",
prop: "drawColor",
value: opt.color
})
}
var t = opt.thickness|0
@@ -361,7 +502,12 @@ draw.rectangle = function render_rectangle(rect, def, pipeline) {
if (opt.radius)
software_fill_round_rect(rect, opt.radius)
else
render.rectangle(rect)
add_command({
kind: "renderer",
id: renderer_id,
op: "fillRect",
data: {rect: rect}
})
return
}
@@ -387,7 +533,7 @@ draw.slice9 = function slice9(image, rect = [0,0], slice = 0, info = slice9_info
if (typeof image === "string")
image = graphics.texture(image)
render.slice9(image, rect, slice, slice9_info, pipeline);
// TODO: Implement slice9 rendering via SDL video actor
}
draw.slice9[prosperon.DOC] = `
: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.height ??= image.texture.height
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)
@@ -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) {
if (typeof font === 'string') font = graphics.get_font(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] = `
:param text: The string to draw.