mesa/src/intel/vulkan/bvh/visualize_json.html
Kevin Chuang 5098c0c5df anv: Add INTEL_DEBUG for bvh dump and visualization tools
This commit allows you to dump different regions of memory related to
bvh building. An additional script to decode the memory dump is also
added, and you're able to view the built bvh in 3D view in html. See the
included README.md for usage.

Rework:
- you can now view the actual child_coord in internalNode in html
- change exponent to be int8_t in the interpreter
- fix the actual coordinates using an updated formula
- now you can have 3D view of the bvh
- blockIncr could be 2 and vk_aabb should be first
- Now, if any bvh dump is enabled, we will zero out tlas, to prevent gpu
  hang caused by incorrect tlas traversal
- rootNodeOffset is back to the beginning
- Add INTEL_DEBUG=bvh_no_build.
- Fix type of dump_size
- add assertion for a 4B alignment
- when clearing out bvh, only clear out everything after
  (header+bvh_offset)
- TODO: instead of dumping on destory, track in the command buffer

Acked-by: Lionel Landwerlin <lionel.g.landwerlin@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/31588>
2024-12-04 10:41:45 +00:00

394 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BVH Visualization</title>
</head>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
}
#container {
width: 100%;
height: 50%;
border: 2px solid #ccc;
background-color: #fff;
padding: 10px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.node circle {
fill: #999;
stroke: steelblue;
stroke-width: 1.5px;
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
.tooltip {
position: absolute;
text-align: left;
width: 300px;
height: auto;
padding: 10px;
font: 12px sans-serif;
background: lightsteelblue;
border: 1px solid #333;
border-radius: 8px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
overflow-y: auto;
max-height: 400px;
visibility: hidden;
}
.tooltip h3 {
margin: 0;
font-size: 14px;
font-weight: bold;
}
.tooltip p {
margin: 5px 0;
font-size: 12px;
word-wrap: break-word;
}
.tooltip .indent {
margin-left: 20px;
}
#header {
margin-bottom: 20px;
padding: 10px;
background-color: #e0e0e0;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
#header h2 {
margin-top: 0;
color: #333;
}
#header p {
margin: 5px 0;
font-size: 14px;
line-height: 1.5;
}
#threeContainer {
width: 75%;
height: 100%;
}
#toggleControls {
width: 25%;
height: 100%;
overflow-y: auto;
margin-left: 20px;
border: 2px solid #ccc;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
padding: 10px;
}
.toggle-container {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.toggle-container label {
font-size: 14px;
margin-left: 10px;
}
#mainContainer {
display: flex;
width: 100%;
height: 50%;
justify-content: space-between;
margin-bottom: 20px;
}
</style>
<body>
<div id="header"></div>
<div id="container">
<div class="tooltip"></div>
<svg id="canvas"></svg>
</div>
<br></br>
<br></br>
<br></br>
<div id="mainContainer">
<div id="threeContainer"></div>
<div id="toggleControls"></div>
</div>
<br></br>
<br></br>
<br></br>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.130.1/examples/js/controls/OrbitControls.js"></script>
<script>
function formatProperties(properties, depth = 0, maxDepth = 2, index = '') {
if (depth > maxDepth) {
return '<p><strong>...</strong></p>';
}
let html = '';
for (const [key, value] of Object.entries(properties)) {
const currentIndex = index ? `${index}.${key}` : key;
if (typeof value === 'object' && value !== null) {
html += `<p><strong>${currentIndex}:</strong><div class="indent">${formatProperties(value, depth + 1, maxDepth, currentIndex)}</div></p>`;
} else {
let displayValue = value;
if (typeof value === 'string' && value.length > 100) {
displayValue = value.substring(0, 100) + '...';
}
html += `<p><strong>${currentIndex}:</strong> ${displayValue}</p>`;
}
}
return html;
}
d3.json('bvh_dump.json').then(function(treeData) {
document.getElementById('header').innerHTML = `<h2>Header Information</h2>${formatProperties(treeData.header)}`;
var width = 1400,
height = 400; // Adjusted for half the height
var svg = d3.select("#canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function (event) {
g.attr("transform", event.transform);
}))
.append("g");
var g = svg.append("g");
var root = d3.hierarchy(treeData.nodes.find(n => n.id === 0), function(d) {
return treeData.relationships[d.id].map(id => treeData.nodes.find(n => n.id === id));
});
var treeLayout = d3.tree().size([height, width - 250]);
treeLayout(root);
var link = g.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(d => d.y)
.y(d => d.x));
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.y},${d.x})`);
node.append("circle")
.attr("r", 10)
.on("click", function(event, d) {
const propertiesHtml = formatProperties(d.data.properties);
d3.select(".tooltip")
.html(`<h3>ID: ${d.data.id}</h3>${propertiesHtml}`)
.style("visibility", "visible")
.style("left", (event.pageX + 20) + "px")
.style("top", (event.pageY - 20) + "px");
d3.selectAll("circle").attr("r", 10);
d3.select(this).attr("r", 15);
});
node.append("text")
.attr("dy", 3)
.attr("x", d => d.children ? -12 : 12)
.style("text-anchor", d => d.children ? "end" : "start")
.text(d => d.data.type);
d3.select("body").on("click", function(event) {
if (!event.target.closest(".node")) {
d3.select(".tooltip").style("visibility", "hidden");
d3.selectAll("circle").attr("r", 10);
}
});
initThreeJs(treeData);
});
function initThreeJs(treeData) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth * 0.7 / 800, 0.1, 1000); // Adjusted for the new layout
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth * 0.7, 800); // Adjusted for half the height
document.getElementById('threeContainer').appendChild(renderer.domElement);
// Add orbit controls for zoom and rotate
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.maxPolarAngle = Math.PI / 2;
// Add grid helper and axis helper for reference
const gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const geometries = [];
function createBox(coord, color, id) {
const geometry = new THREE.BoxGeometry(
coord.x_upper - coord.x_lower,
coord.y_upper - coord.y_lower,
coord.z_upper - coord.z_lower
);
const edges = new THREE.EdgesGeometry(geometry);
const material = new THREE.LineBasicMaterial({ color: color });
const box = new THREE.LineSegments(edges, material);
box.position.set(
(coord.x_upper + coord.x_lower) / 2,
(coord.y_upper + coord.y_lower) / 2,
(coord.z_upper + coord.z_lower) / 2
);
scene.add(box);
geometries.push(box);
addToggleControl(box, id);
}
function createTriangle(vertices, leafColor, id) {
const geometry = new THREE.BufferGeometry();
const verticesArray = new Float32Array(vertices.flat());
geometry.setAttribute('position', new THREE.BufferAttribute(verticesArray, 3));
const material = new THREE.MeshBasicMaterial({ color: leafColor, side: THREE.DoubleSide, wireframe: true });
const triangle = new THREE.Mesh(geometry, material);
scene.add(triangle);
geometries.push(triangle);
addToggleControl(triangle, id);
}
function createInstance(instanceProperties, color, id) {
const geometry = new THREE.SphereGeometry(0.1, 32, 32); // Create a sphere geometry
const material = new THREE.MeshBasicMaterial({ color: color, wireframe: false }); // Create a material with wireframe
const sphere = new THREE.Mesh(geometry, material); // Create a mesh
const { obj2world_p } = instanceProperties.part0;
sphere.position.set(obj2world_p[0], obj2world_p[1], obj2world_p[2]);
scene.add(sphere);
geometries.push(sphere);
addToggleControl(sphere, id);
}
function addToggleControl(geometry, id) {
const toggleContainer = document.createElement('div');
toggleContainer.className = 'toggle-container';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.addEventListener('change', () => {
geometry.visible = checkbox.checked;
});
const labelElement = document.createElement('label');
labelElement.textContent = `${id}`;
toggleContainer.appendChild(checkbox);
toggleContainer.appendChild(labelElement);
document.getElementById('toggleControls').appendChild(toggleContainer);
}
function handleNode(node, parentType) {
const leafColor = 0xffa500;
const internalColor = 0x00ff00;
if (node.type === 'AnvInternalNode') {
// Check if the internal node is a fatLeaf
const isFatProceduralLeaf = node.properties.node_type.nodeType === 0x3;
const isFatInstanceLeaf = node.properties.node_type.nodeType === 0x1;
node.properties.child_data.forEach((child, index) => {
if (child.blockIncr !== 1 && child.blockIncr !== 2) {
return;
}
const childIsProcedural = child.startPrim === 0x3 || isFatProceduralLeaf;
const childIsInstance = child.startPrim === 0x1 || isFatInstanceLeaf;
const color = (childIsProcedural || childIsInstance) ? leafColor : internalColor;
let label = node.id + "'s child box";
label += (childIsProcedural) ? " also a procedural leaf" : "";
label += (childIsInstance) ? " also a instance leaf" : "";
createBox(node.properties.actual_coords[index], color, label);
});
} else {
switch (node.type) {
case 'AnvQuadLeafNode':
createTriangle(node.properties.v, leafColor, `Triangle. NodeID=${node.id}`);
break;
case 'AnvInstanceLeaf':
// Skip. Already drawn by parents
break;
case 'AnvAabbLeafNode':
// Skip. Already drawn by parents
break;
}
}
}
// Draw AABB from header
const headerAABB = treeData.header.aabb;
createBox({
x_lower: headerAABB.min_x,
x_upper: headerAABB.max_x,
y_lower: headerAABB.min_y,
y_upper: headerAABB.max_y,
z_lower: headerAABB.min_z,
z_upper: headerAABB.max_z
}, 0xff00ff, 'Root AABB');
// Draw nodes
treeData.nodes.forEach(node => {
handleNode(node, node.properties.node_type);
});
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
}
</script>
</body>
</html>