486 lines
14 KiB
Plaintext
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
|