// 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 && length(node.children) > 0 if (has_children) { imgui.tree(label, function() { // Show node summary _render_node_summary(imgui, node) // Recurse children for (var i = 0; i < length(node.children); 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) { push(info, "pos:(" + text(round(node.pos.x)) + "," + text(round(node.pos.y)) + ")") } if (node.width && node.height) { push(info, "size:" + text(node.width) + "x" + text(node.height)) } if (node.image) { push(info, "img:" + node.image) } if (node.text) { var t = node.text if (length(t) > 20) t = text(t, 0, 17) + "..." push(info, "\"" + t + "\"") } if (node.effects && length(node.effects) > 0) { var fx = [] for (var i = 0; i < length(node.effects); i++) { push(fx, node.effects[i].type) } push(info, "fx:[" + text(fx, ",") + "]") } if (length(info) > 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 && length(node.effects) > 0) { imgui.text("---") imgui.text("Effects:") for (var i = 0; i < length(node.effects); i++) { var fx = node.effects[i] imgui.tree(fx.type, function() { arrfor(array(fx), k => { 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(length(plan.passes))) imgui.text("Targets: " + text(length(array(plan.targets || {})))) imgui.text("Persistent: " + text(length(array(plan.persistent_targets || {})))) imgui.text("---") for (var i = 0; i < length(plan.passes); 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:") arrfor(array(pass.uniforms), k => { 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(length(effect_list))) imgui.text("---") for (var i = 0; i < length(effect_list); 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:") arrfor(array(deff.params), k => { 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) { arrfor(array(plan.targets), key => { 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) { arrfor(array(plan.persistent_targets), key => { var t = plan.persistent_targets[key] imgui.text(" " + key + ": " + text(t.width) + "x" + text(t.height )) }) } }) } return debug_imgui