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"};
|
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};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user