Files
prosperon/debug_imgui.cm
2026-01-16 20:56:16 -06:00

486 lines
14 KiB
Plaintext

// debug_imgui.cm - ImGui Debug Windows for Render Architecture
//
// Provides debug windows for inspecting:
// - Scene tree and node properties
// - Render graph and pass connections
// - Effect parameters
// - Performance statistics
var debug_imgui = {}
var json = use('json')
// State
var _show_scene_tree = false
var _show_render_graph = false
var _show_effects = false
var _show_stats = true
var _show_targets = false
var _selected_node = null
var _selected_pass = null
var _expanded_nodes = {}
// Toggle windows
debug_imgui.toggle_scene_tree = function() { _show_scene_tree = !_show_scene_tree }
debug_imgui.toggle_render_graph = function() { _show_render_graph = !_show_render_graph }
debug_imgui.toggle_effects = function() { _show_effects = !_show_effects }
debug_imgui.toggle_stats = function() { _show_stats = !_show_stats }
debug_imgui.toggle_targets = function() { _show_targets = !_show_targets }
// Main render function - call from imgui callback
debug_imgui.render = function(imgui, scene_graph, render_plan, stats) {
// Menu bar for toggling windows
_render_menu(imgui)
if (_show_scene_tree && scene_graph) {
_render_scene_tree(imgui, scene_graph)
}
if (_show_render_graph && render_plan) {
_render_graph_view(imgui, render_plan)
}
if (_show_effects) {
_render_effects_panel(imgui)
}
if (_show_stats && stats) {
_render_stats(imgui, stats)
}
if (_show_targets && render_plan) {
_render_targets(imgui, render_plan)
}
if (_selected_node) {
_render_node_inspector(imgui, _selected_node)
}
if (_selected_pass) {
_render_pass_inspector(imgui, _selected_pass)
}
}
// Render debug menu
function _render_menu(imgui) {
imgui.mainmenubar(function() {
imgui.menu("Debug", function() {
if (imgui.menuitem("Scene Tree", null, function(){}, _show_scene_tree)) {
_show_scene_tree = !_show_scene_tree
}
if (imgui.menuitem("Render Graph", null, function(){}, _show_render_graph)) {
_show_render_graph = !_show_render_graph
}
if (imgui.menuitem("Effects", null, function(){}, _show_effects)) {
_show_effects = !_show_effects
}
if (imgui.menuitem("Statistics", null, function(){}, _show_stats)) {
_show_stats = !_show_stats
}
if (imgui.menuitem("Render Targets", null, function(){}, _show_targets)) {
_show_targets = !_show_targets
}
})
})
}
// Render scene tree window
function _render_scene_tree(imgui, scene_graph) {
imgui.window("Scene Tree", function() {
if (!scene_graph.root) {
imgui.text("No scene root")
return
}
imgui.text("Total nodes: " + text(scene_graph.stats.total_nodes))
imgui.text("Dirty this frame: " + text(scene_graph.stats.dirty_this_frame))
imgui.text("Geometry rebuilds: " + text(scene_graph.stats.geometry_rebuilds))
imgui.text("---")
_render_node_tree(imgui, scene_graph.root, 0)
})
}
// Render a node and its children recursively
function _render_node_tree(imgui, node, depth) {
if (!node) return
var id = node.id || 'unknown'
var type = node.type || 'node'
var label = type + " [" + id + "]"
// Add dirty indicator
if (is_number(node.dirty) && node.dirty > 0) {
label += " *"
}
var has_children = node.children && node.children.length > 0
if (has_children) {
imgui.tree(label, function() {
// Show node summary
_render_node_summary(imgui, node)
// Recurse children
for (var i = 0; i < node.children.length; i++) {
_render_node_tree(imgui, node.children[i], depth + 1)
}
})
} else {
// Leaf node - show as selectable
if (imgui.button(label)) {
_selected_node = node
}
imgui.sameline(0)
_render_node_summary(imgui, node)
}
}
// Render node summary inline
function _render_node_summary(imgui, node) {
var info = []
if (node.pos) {
info.push("pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")")
}
if (node.width && node.height) {
info.push("size:" + text(node.width) + "x" + text(node.height))
}
if (node.image) {
info.push("img:" + node.image)
}
if (node.text) {
var t = node.text
if (t.length > 20) t = text(t, 0, 17) + "..."
info.push("\"" + t + "\"")
}
if (node.effects && node.effects.length > 0) {
var fx = []
for (var i = 0; i < node.effects.length; i++) {
fx.push(node.effects[i].type)
}
info.push("fx:[" + text(fx, ",") + "]")
}
if (info.length > 0) {
imgui.text(" " + text(info, " "))
}
}
// Render node inspector window
function _render_node_inspector(imgui, node) {
imgui.window("Node Inspector", function() {
if (imgui.button("Close")) {
_selected_node = null
return
}
imgui.text("ID: " + (node.id || 'none'))
imgui.text("Type: " + (node.type || 'unknown'))
imgui.text("Layer: " + text(node.layer || 0))
imgui.text("Dirty: " + text(node.dirty || 0))
imgui.text("---")
// Position
if (node.pos) {
imgui.text("Position")
var pos = imgui.slider("X", node.pos.x, -1000, 1000)
if (pos != node.pos.x) node.pos.x = pos
pos = imgui.slider("Y", node.pos.y, -1000, 1000)
if (pos != node.pos.y) node.pos.y = pos
}
// Size
if (node.width != null) {
imgui.text("Size")
node.width = imgui.slider("Width", node.width, 0, 1000)
node.height = imgui.slider("Height", node.height, 0, 1000)
}
// Opacity
if (node.opacity != null) {
node.opacity = imgui.slider("Opacity", node.opacity, 0, 1)
}
// Color
if (node.color) {
imgui.text("Color")
node.color.r = imgui.slider("R", node.color.r, 0, 1)
node.color.g = imgui.slider("G", node.color.g, 0, 1)
node.color.b = imgui.slider("B", node.color.b, 0, 1)
node.color.a = imgui.slider("A", node.color.a, 0, 1)
}
// World transform (read-only)
if (node.world_pos) {
imgui.text("---")
imgui.text("World Position: (" + text(round(node.world_pos.x * 100) / 100) + ", " + text(round(node.world_pos.y * 100) / 100) + ")")
}
if (node.world_opacity != null) {
imgui.text("World Opacity: " + text(round(node.world_opacity * 100) / 100))
}
// Image
if (node.image) {
imgui.text("---")
imgui.text("Image: " + node.image)
}
// Text
if (node.text != null) {
imgui.text("---")
imgui.text("Text: " + node.text)
if (node.font) imgui.text("Font: " + node.font)
if (node.size) imgui.text("Size: " + text(node.size))
}
// Effects
if (node.effects && node.effects.length > 0) {
imgui.text("---")
imgui.text("Effects:")
for (var i = 0; i < node.effects.length; i++) {
var fx = node.effects[i]
imgui.tree(fx.type, function() {
for (var k in fx) {
if (k != 'type' && k != 'source') {
var v = fx[k]
if (is_number(v)) {
fx[k] = imgui.slider(k, v, 0, 10)
} else {
imgui.text(k + ": " + text(v))
}
}
}
})
}
}
// Geometry cache info
if (node.geom_cache) {
imgui.text("---")
imgui.text("Geometry Cache:")
imgui.text(" Vertices: " + text(node.geom_cache.vert_count || 0))
imgui.text(" Indices: " + text(node.geom_cache.index_count || 0))
if (node.geom_cache.texture_key) {
imgui.text(" Texture: " + node.geom_cache.texture_key)
}
}
})
}
// Render graph view window
function _render_graph_view(imgui, plan) {
imgui.window("Render Graph", function() {
if (!plan || !plan.passes) {
imgui.text("No render plan")
return
}
imgui.text("Passes: " + text(plan.passes.length))
imgui.text("Targets: " + text(array(plan.targets || {}).length))
imgui.text("Persistent: " + text(array(plan.persistent_targets || {}).length))
imgui.text("---")
for (var i = 0; i < plan.passes.length; i++) {
var pass = plan.passes[i]
var label = text(i) + ": " + pass.type
if (pass.shader) label += " [" + pass.shader + "]"
if (pass.renderer) label += " [" + pass.renderer + "]"
if (imgui.button(label)) {
_selected_pass = pass
}
// Show target info
imgui.sameline(0)
var target_info = ""
if (pass.target) {
if (pass.target == 'screen') {
target_info = "-> screen"
} else if (pass.target.key) {
target_info = "-> " + pass.target.key
}
}
if (pass.output) {
if (pass.output.key) {
target_info = "-> " + pass.output.key
}
}
if (target_info) imgui.text(target_info)
}
})
}
// Render pass inspector
function _render_pass_inspector(imgui, pass) {
imgui.window("Pass Inspector", function() {
if (imgui.button("Close")) {
_selected_pass = null
return
}
imgui.text("Type: " + pass.type)
if (pass.shader) imgui.text("Shader: " + pass.shader)
if (pass.renderer) imgui.text("Renderer: " + pass.renderer)
if (pass.blend) imgui.text("Blend: " + pass.blend)
if (pass.presentation) imgui.text("Presentation: " + pass.presentation)
// Target info
imgui.text("---")
if (pass.target) {
if (pass.target == 'screen') {
imgui.text("Target: screen")
} else if (pass.target.key) {
imgui.text("Target: " + pass.target.key)
if (pass.target.w) imgui.text(" Size: " + text(pass.target.w) + "x" + text(pass.target.h))
}
}
if (pass.input) {
imgui.text("Input: " + (pass.input.key || 'unknown'))
}
if (pass.output) {
imgui.text("Output: " + (pass.output.key || 'unknown'))
}
// Uniforms
if (pass.uniforms) {
imgui.text("---")
imgui.text("Uniforms:")
for (var k in pass.uniforms) {
var v = pass.uniforms[k]
if (is_array(v)) {
imgui.text(" " + k + ": [" + text(v, ", ") + "]")
} else {
imgui.text(" " + k + ": " + text(v))
}
}
}
// Clear color
if (pass.color) {
imgui.text("---")
imgui.text("Clear: rgba(" +
text(round(pass.color.r * 255)) + "," +
text(round(pass.color.g * 255)) + "," +
text(round(pass.color.b * 255)) + "," +
text(round(pass.color.a * 100) / 100) + ")")
}
// Source size
if (pass.source_size) {
imgui.text("Source size: " + text(pass.source_size.w || pass.source_size.width) + "x" + text(pass.source_size.h || pass.source_size.height))
}
if (pass.dest_size) {
imgui.text("Dest size: " + text(pass.dest_size.w || pass.dest_size.width) + "x" + text(pass.dest_size.h || pass.dest_size.height))
}
})
}
// Render effects panel
function _render_effects_panel(imgui) {
var effects_mod = use('effects')
imgui.window("Effects Registry", function() {
var effect_list = effects_mod.list()
imgui.text("Registered effects: " + text(effect_list.length))
imgui.text("---")
for (var i = 0; i < effect_list.length; i++) {
var name = effect_list[i]
var deff = effects_mod.get(name)
imgui.tree(name, function() {
imgui.text("Type: " + (deff.type || 'unknown'))
imgui.text("Requires target: " + (deff.requires_target ? "yes" : "no"))
if (deff.params) {
imgui.text("Parameters:")
for (var k in deff.params) {
var p = deff.params[k]
var info = k
if (p.default != null) info += " = " + text(p.default)
if (p.type) info += " (" + p.type + ")"
if (p.required) info += " [required]"
imgui.text(" " + info)
}
}
})
}
})
}
// Render statistics
function _render_stats(imgui, stats) {
imgui.window("Render Statistics", function() {
if (stats.scene) {
imgui.text("Scene:")
imgui.text(" Total nodes: " + text(stats.scene.total_nodes || 0))
imgui.text(" Dirty nodes: " + text(stats.scene.dirty_this_frame || 0))
imgui.text(" Geometry rebuilds: " + text(stats.scene.geometry_rebuilds || 0))
}
if (stats.render) {
imgui.text("---")
imgui.text("Render:")
imgui.text(" Draw calls: " + text(stats.render.draw_calls || 0))
imgui.text(" Triangles: " + text(stats.render.triangles || 0))
imgui.text(" Batches: " + text(stats.render.batches || 0))
}
if (stats.targets) {
imgui.text("---")
imgui.text("Targets:")
imgui.text(" Active: " + text(stats.targets.active || 0))
imgui.text(" Pooled: " + text(stats.targets.pooled || 0))
imgui.text(" Memory: " + text(stats.targets.memory_mb || 0) + " MB")
}
if (stats.fps) {
imgui.text("---")
imgui.text("FPS: " + text(round(stats.fps)))
imgui.text("Frame time: " + text(round(stats.frame_time_ms * 100) / 100) + " ms")
}
})
}
// Render targets view
function _render_targets(imgui, plan) {
imgui.window("Render Targets", function() {
if (!plan) {
imgui.text("No render plan")
return
}
imgui.text("Temporary Targets:")
if (plan.targets) {
for (var key in plan.targets) {
var t = plan.targets[key]
imgui.text(" " + key + ": " + text(t.width) + "x" + text(t.height))
}
}
imgui.text("---")
imgui.text("Persistent Targets:")
if (plan.persistent_targets) {
for (var key in plan.persistent_targets) {
var t = plan.persistent_targets[key]
imgui.text(" " + key + ": " + text(t.width) + "x" + text(t.height ))
}
}
})
}
return debug_imgui