draw2d now can send batches of draws to video backends
This commit is contained in:
@@ -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};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user