optimize canvas redraw and batch Lua canvas operations

This commit is contained in:
Face
2025-09-09 19:47:21 +03:00
parent 72d4648ed1
commit 4b827d0692
3 changed files with 73 additions and 17 deletions

View File

@@ -136,7 +136,7 @@ func _exit_tree():
if background_panel and is_instance_valid(background_panel):
if background_panel.get_parent():
background_panel.get_parent().remove_child(background_panel)
background_panel.get_parent().remove_child.call_deferred(background_panel)
background_panel.queue_free()
if dev_tools and is_instance_valid(dev_tools):

View File

@@ -8,6 +8,8 @@ var canvas_height: int = 150
var draw_commands: Array = []
var context_2d: CanvasContext2D = null
var context_shader: CanvasContextShader = null
var pending_redraw: bool = false
var max_draw_commands: int = 1000
class CanvasContext2D:
var canvas: HTMLCanvas
@@ -41,8 +43,7 @@ class CanvasContext2D:
"color": color,
"transform": current_transform
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
func strokeRect(x: float, y: float, width: float, height: float, color_hex: String = "", stroke_width: float = 0.0):
var color = _parse_color(stroke_style if color_hex.is_empty() else color_hex)
@@ -57,10 +58,14 @@ class CanvasContext2D:
"stroke_width": width_val,
"transform": current_transform
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
func clearRect(x: float, y: float, width: float, height: float):
if x == 0 and y == 0 and width >= canvas.canvas_width and height >= canvas.canvas_height:
canvas.draw_commands.clear()
canvas._do_redraw()
return
var cmd = {
"type": "clearRect",
"x": x,
@@ -68,8 +73,7 @@ class CanvasContext2D:
"width": width,
"height": height
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
func drawCircle(x: float, y: float, radius: float, color_hex: String = "#000000", filled: bool = true):
var cmd = {
@@ -81,8 +85,7 @@ class CanvasContext2D:
"filled": filled,
"transform": current_transform
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
func drawText(x: float, y: float, text: String, color_hex: String = "#000000"):
var color = _parse_color(fill_style if color_hex == "#000000" else color_hex)
@@ -95,8 +98,7 @@ class CanvasContext2D:
"font_size": _parse_font_size(font),
"transform": current_transform
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
# Path-based drawing functions
func beginPath():
@@ -149,8 +151,7 @@ class CanvasContext2D:
"line_cap": line_cap,
"line_join": line_join
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
func fill():
if current_path.size() < 3:
@@ -161,8 +162,7 @@ class CanvasContext2D:
"path": current_path.duplicate(),
"color": _parse_color(fill_style)
}
canvas.draw_commands.append(cmd)
canvas.queue_redraw()
canvas._add_draw_command(cmd)
# Transformation functions
func save():
@@ -328,6 +328,10 @@ func withContext(context_type: String):
func _draw():
draw_rect(Rect2(Vector2.ZERO, size), Color.TRANSPARENT)
# Skip if too many commands to prevent frame drops
if draw_commands.size() > max_draw_commands * 2:
return
for cmd in draw_commands:
match cmd.type:
"fillRect":
@@ -407,6 +411,31 @@ func _draw():
if path.size() > 2:
draw_colored_polygon(path, clr)
func _add_draw_command(cmd: Dictionary):
_optimize_command(cmd)
draw_commands.append(cmd)
if draw_commands.size() > max_draw_commands:
draw_commands = draw_commands.slice(draw_commands.size() - max_draw_commands)
if not pending_redraw:
pending_redraw = true
call_deferred("_do_redraw")
func _optimize_command(cmd: Dictionary):
# Remove redundant consecutive clearRect commands
if cmd.type == "clearRect" and draw_commands.size() > 0:
var last_cmd = draw_commands[-1]
if last_cmd.type == "clearRect" and \
last_cmd.x == cmd.x and last_cmd.y == cmd.y and \
last_cmd.width == cmd.width and last_cmd.height == cmd.height:
draw_commands.pop_back()
func _do_redraw():
pending_redraw = false
queue_redraw()
func clear():
draw_commands.clear()
queue_redraw()
_do_redraw()

View File

@@ -3,8 +3,35 @@ extends RefCounted
# This file mainly creates operations that are handled by canvas.gd
static var pending_operations: Dictionary = {}
static var batch_timer: SceneTreeTimer = null
static func emit_canvas_operation(lua_api: LuaAPI, operation: Dictionary) -> void:
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
var element_id = operation.get("element_id", "")
if not pending_operations.has(element_id):
pending_operations[element_id] = []
pending_operations[element_id].append(operation)
if not batch_timer or batch_timer.time_left <= 0:
var scene_tree = lua_api.get_tree() if lua_api else Engine.get_main_loop()
if scene_tree:
batch_timer = scene_tree.create_timer(0.001) # 1ms batch window
batch_timer.timeout.connect(_flush_pending_operations.bind(lua_api))
static func _flush_pending_operations(lua_api: LuaAPI) -> void:
if not lua_api or not lua_api.is_inside_tree():
pending_operations.clear()
return
for element_id in pending_operations:
var operations = pending_operations[element_id]
for operation in operations:
lua_api.threaded_vm.call_deferred("_emit_dom_operation_request", operation)
pending_operations.clear()
batch_timer = null
static func _element_withContext_wrapper(vm: LuauVM) -> int:
var lua_api = vm.get_meta("lua_api") as LuaAPI