rti: Initial commit
Some checks are pending
macOS-CI / macOS-CI (dri) (push) Waiting to run
macOS-CI / macOS-CI (xlib) (push) Waiting to run

RTI (ray tracing inspector) is a GUI for inspecting acceleration
structures and ray tracing dispatches. There are some general classes
for application lifetime, gpu resource managment and rendering. The
visualization and BVH/node info needs to be implemented by
driver/hardware specific backends (By overriding the virtual functions
in rti_file_view).

Reviewed-by: Natalie Vock <natalie.vock@gmx.de>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41806>
This commit is contained in:
Konstantin Seurer 2026-05-26 15:45:48 +02:00 committed by Marge Bot
parent 8c0dc4f88f
commit a86a30fff2
25 changed files with 5888 additions and 11 deletions

View file

@ -298,7 +298,7 @@ fedora-release:
-D glvnd=enabled
-D platforms=x11,wayland
EXTRA_OPTION: >
-D tools=drm-shim,etnaviv,freedreno,glsl,intel,nir,nouveau,lima,panfrost,imagination
-D tools=drm-shim,etnaviv,freedreno,glsl,intel,nir,nouveau,lima,panfrost,imagination,rti
-D vulkan-layers=device-select,overlay
-D intel-rt=enabled
-D imagination-srv=true

View file

@ -53,6 +53,7 @@ DEPS=(
libglfw3-dev
"libllvm${LLVM_VERSION}"
libpciaccess-dev
libsdl3-dev
libunwind-dev
libva-dev
libvulkan-dev

View file

@ -79,6 +79,7 @@ DEPS=(
python3-ply
python3-pycparser
python3-yaml
SDL3-devel
spirv-tools-devel
spirv-llvm-translator-devel
vulkan-headers

View file

@ -19,18 +19,18 @@ include:
- .gitlab-ci/conditional-build-image-tags.yml
variables:
DEBIAN_BUILD_BASE_TAG: "20260604-rust196"
DEBIAN_BUILD_TAG: "20260604-bindgen"
DEBIAN_BUILD_BASE_TAG: "20260621-sdl3"
DEBIAN_BUILD_TAG: "20260621-sdl3"
DEBIAN_TEST_BASE_TAG: "20260604-rust196"
DEBIAN_TEST_ANDROID_TAG: "20260620-angle-83"
DEBIAN_TEST_GL_TAG: "20260620-angle-83"
DEBIAN_TEST_VIDEO_TAG: "20260512-vkuprev"
DEBIAN_TEST_VK_TAG: "20260604-vkd3d"
DEBIAN_TEST_BASE_TAG: "20260621-sdl3"
DEBIAN_TEST_ANDROID_TAG: "20260621-sdl3"
DEBIAN_TEST_GL_TAG: "20260621-sdl3"
DEBIAN_TEST_VIDEO_TAG: "20260621-sdl3"
DEBIAN_TEST_VK_TAG: "20260621-sdl3"
ALPINE_X86_64_BUILD_TAG: "20260604-rust196"
FEDORA_X86_64_BUILD_TAG: "20260604-f44"
FEDORA_X86_64_BUILD_TAG: "20260621-sdl3"
KERNEL_TAG: "v6.19-mesa-712d"
KERNEL_REPO: "gfx-ci/linux"

View file

@ -129,13 +129,14 @@ if with_tools.contains('all')
'nir',
'nouveau',
'panfrost',
'rti',
'zink',
]
endif
with_any_vulkan_layers = get_option('vulkan-layers').length() != 0
with_intel_tools = with_tools.contains('intel') or with_tools.contains('intel-ui')
with_imgui = with_intel_tools or with_vulkan_overlay_layer
with_imgui = with_intel_tools or with_vulkan_overlay_layer or with_tools.contains('rti')
dri_drivers_path = get_option('dri-drivers-path')
if dri_drivers_path == ''

View file

@ -552,7 +552,7 @@ option(
value : [],
choices : ['amd', 'asahi', 'dlclose-skip', 'drm-shim', 'etnaviv', 'freedreno',
'glsl', 'imagination', 'intel', 'intel-ui', 'lima', 'nir', 'nouveau',
'panfrost', 'zink', 'all'],
'panfrost', 'rti', 'zink', 'all'],
description : 'List of tools to build. (Note: `intel-ui` selects `intel`)',
)

View file

@ -9,3 +9,24 @@ libimgui_core_dep = declare_dependency(
link_with : libimgui_core,
include_directories : include_directories('.', is_system : true)
)
if with_tools.contains('rti')
libimgui_sdl3_vulkan = static_library(
'imgui_sdl3_vulkan',
files(
'backends/imgui_impl_sdl3.cpp',
'backends/imgui_impl_vulkan.cpp',
'imgui.cpp',
'imgui_draw.cpp',
'imgui_tables.cpp',
'imgui_widgets.cpp',
),
cpp_args : cpp.get_supported_arguments('-w', '-Wno-error'),
install : false
)
libimgui_sdl3_vulkan_dep = declare_dependency(
link_with : libimgui_sdl3_vulkan,
include_directories : include_directories('.', is_system : true)
)
endif

View file

@ -9,3 +9,7 @@ endif
if with_tools.contains('dlclose-skip')
subdir('dlclose-skip')
endif
if with_tools.contains('rti')
subdir('rtinspector')
endif

View file

@ -0,0 +1,6 @@
BasedOnStyle: InheritParentConfig
DisableFormat: false
AccessModifierOffset: -3
ColumnLimit: 120

View file

@ -0,0 +1,153 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint>
#include "rti_app.h"
#include "rti_file_view.h"
#include "rti_util.h"
#include "backends/imgui_impl_sdl3.h"
#include "vulkan/vulkan_core.h"
int
main(int argc, char **argv)
{
rti_app app = rti_app();
int ret = rti_app_init(&app);
if (ret)
return ret;
for (int i = 1; i < argc; i++)
app.open_files.push_back(rti_create_file_view(&app, argv[i]));
VkResult result;
bool needs_resize = true;
bool done = false;
while (!done) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
ImGui_ImplSDL3_ProcessEvent(&event);
if (event.type == SDL_EVENT_QUIT)
done = true;
if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(app.window))
done = true;
}
if (SDL_GetWindowFlags(app.window) & SDL_WINDOW_MINIMIZED) {
SDL_Delay(10);
continue;
}
int width, height;
SDL_GetWindowSize(app.window, &width, &height);
if (app.imgui_window.Width != width || app.imgui_window.Height != height)
needs_resize = true;
if (needs_resize) {
rti_app_resize(&app, width, height);
needs_resize = false;
continue;
}
VkSemaphore image_acquired_semaphore =
app.imgui_window.FrameSemaphores[app.imgui_window.SemaphoreIndex].ImageAcquiredSemaphore;
VkSemaphore render_complete_semaphore =
app.imgui_window.FrameSemaphores[app.imgui_window.SemaphoreIndex].RenderCompleteSemaphore;
result = vkAcquireNextImageKHR(app.device, app.imgui_window.Swapchain, UINT64_MAX, image_acquired_semaphore,
VK_NULL_HANDLE, &app.imgui_window.FrameIndex);
if (result != VK_SUCCESS) {
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
needs_resize = true;
continue;
}
rti_check_vk_result(result);
}
ImGui_ImplVulkanH_Frame *fd = &app.imgui_window.Frames[app.imgui_window.FrameIndex];
rti_check_vk_result(vkWaitForFences(app.device, 1, &fd->Fence, VK_TRUE, UINT64_MAX));
rti_check_vk_result(vkResetFences(app.device, 1, &fd->Fence));
rti_check_vk_result(vkResetCommandPool(app.device, fd->CommandPool, 0));
VkCommandBufferBeginInfo command_buffer_begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
rti_check_vk_result(vkBeginCommandBuffer(fd->CommandBuffer, &command_buffer_begin_info));
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
app.command_buffer = fd->CommandBuffer;
if (rti_app_run(&app))
break;
ImGui::Render();
ImDrawData *draw_data = ImGui::GetDrawData();
VkRenderPassBeginInfo render_pass_begin_info = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = app.imgui_window.RenderPass,
.framebuffer = fd->Framebuffer,
.renderArea =
{
.extent =
{
.width = (uint32_t)app.imgui_window.Width,
.height = (uint32_t)app.imgui_window.Height,
},
},
.clearValueCount = 1,
.pClearValues = &app.imgui_window.ClearValue,
};
vkCmdBeginRenderPass(fd->CommandBuffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer);
vkCmdEndRenderPass(fd->CommandBuffer);
rti_check_vk_result(vkEndCommandBuffer(fd->CommandBuffer));
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &image_acquired_semaphore,
.pWaitDstStageMask = &wait_stage,
.commandBufferCount = 1,
.pCommandBuffers = &fd->CommandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &render_complete_semaphore,
};
rti_check_vk_result(vkQueueSubmit(app.queue, 1, &submit_info, fd->Fence));
VkPresentInfoKHR present_info = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &render_complete_semaphore,
.swapchainCount = 1,
.pSwapchains = &app.imgui_window.Swapchain,
.pImageIndices = &app.imgui_window.FrameIndex,
};
result = vkQueuePresentKHR(app.queue, &present_info);
if (result != VK_SUCCESS) {
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
needs_resize = true;
continue;
}
rti_check_vk_result(result);
}
app.imgui_window.SemaphoreIndex = (app.imgui_window.SemaphoreIndex + 1) % app.imgui_window.SemaphoreCount;
}
rti_app_finish(&app);
return 0;
}

View file

@ -0,0 +1,36 @@
# Copyright © 2026 Valve Corporation
#
# SPDX-License-Identifier: MIT
rti_files = files(
'radv/rti_file_view_radv_bvh4.cpp',
'radv/rti_file_view_radv_bvh8.cpp',
'radv/rti_file_view_radv.cpp',
'main.cpp',
'rti_app.cpp',
'rti_file_view.cpp',
'rti_util.cpp',
)
subdir('shaders')
dep_sdl3 = dependency('sdl3')
dep_vulkan = dependency('vulkan')
executable(
'rti',
[rti_files, spv_shaders],
cpp_args: [cpp_msvc_compat_args],
gnu_symbol_visibility: 'hidden',
link_with: [],
include_directories: [
inc_include,
inc_src,
inc_util,
inc_radv_bvh,
inc_vulkan_bvh,
inc_radv_tools,
],
dependencies: [idep_mesautil, libimgui_sdl3_vulkan_dep, dep_sdl3, dep_vulkan],
)

View file

@ -0,0 +1,850 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "rti_file_view_radv.h"
#include "rti_file_view.h"
#include <cinttypes>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <imgui.h>
#include <memory>
#include <stdint.h>
#include "imgui_internal.h"
#include "shaders/rti_shader_interface.h"
#include "util/macros.h"
#include "util/rti_format.h"
#include "vulkan/vulkan_core.h"
#include "compiler/spirv/spirv.h"
#ifdef HAVE_TBB
#include <oneapi/tbb/blocked_range.h>
#include <oneapi/tbb/parallel_for.h>
#endif
#include "bvh.h"
#include "radv_rti.h"
#include "rti_app.h"
#include "rti_util.h"
std::unique_ptr<rti_acceleration_structure>
rti_file_view_radv::create_acceleration_structure()
{
return std::make_unique<rti_acceleration_structure_radv>(this);
}
float
rti_file_view_radv::handle_mouse_click(rti_acceleration_structure *_acceleration_structure, rti_ray ray, bool select)
{
rti_acceleration_structure_radv *acceleration_structure = (rti_acceleration_structure_radv *)_acceleration_structure;
float tmax = INFINITY;
if (select)
acceleration_structure->radv_ui.selected_node_id = RADV_BVH_INVALID_NODE;
std::unordered_set<uint32_t> path;
if (trace_info->bvh8)
bvh8_traverse(acceleration_structure, ray, RADV_BVH_ROOT_NODE, &tmax, path, select);
else
bvh4_traverse(acceleration_structure, ray, RADV_BVH_ROOT_NODE, &tmax, path, select);
return tmax;
}
void
rti_file_view_radv::load(FILE *file, const rti_header *header)
{
rti_file_view::load(file, header);
uint32_t vertex_count = 0;
for (uint32_t i = 0; i < acceleration_structures.size(); i++) {
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)acceleration_structures[i].get();
uint32_t primitive_vertex_count = acceleration_structure->header.geometry_type == VK_GEOMETRY_TYPE_TRIANGLES_KHR
? 3
: RTI_FILLED_CUBE_VERTEX_COUNT;
if (acceleration_structure->header.geometry_type != VK_GEOMETRY_TYPE_INSTANCES_KHR) {
acceleration_structure->first_vertex = vertex_count;
acceleration_structure->wireframe_first_vertex = vertex_count;
vertex_count += primitive_vertex_count * acceleration_structure->primitive_count;
if (acceleration_structure->header.geometry_type == VK_GEOMETRY_TYPE_AABBS_KHR) {
acceleration_structure->wireframe_first_vertex = vertex_count;
vertex_count += RTI_CUBE_VERTEX_COUNT * acceleration_structure->primitive_count;
}
}
}
rti_vertex *vertices = nullptr;
std::shared_ptr<rti_backed_buffer> vertex_buffer = nullptr;
if (vertex_count) {
vertex_buffer =
rti_create_backed_buffer(app, vertex_count * sizeof(rti_vertex), rti_memory_type_device_local,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, false);
vertices = (rti_vertex *)app->upload_memory(vertex_buffer);
}
#ifdef HAVE_TBB
tbb::parallel_for(tbb::blocked_range<uint32_t>(0, acceleration_structures.size()),
[this, vertices, vertex_buffer](const tbb::blocked_range<uint32_t> &range) {
for (uint32_t i = range.begin(); i < range.end(); i++) {
#else
for (uint32_t i = 0; i < acceleration_structures.size(); i++) {
#endif
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)acceleration_structures[i].get();
acceleration_structure->bvh = (const uint8_t *)acceleration_structure->data;
if (trace_info->bvh8)
acceleration_structure->aabb = bvh8_scene_aabb(acceleration_structure);
else
acceleration_structure->aabb = bvh4_scene_aabb(acceleration_structure);
acceleration_structure->init_camera();
if (acceleration_structure->header.geometry_type != VK_GEOMETRY_TYPE_INSTANCES_KHR &&
acceleration_structure->primitive_count) {
acceleration_structure->ui.vertex_buffer = vertex_buffer;
if (trace_info->bvh8) {
bvh8_get_vertices(acceleration_structure, RADV_BVH_ROOT_NODE,
vertices + acceleration_structure->first_vertex,
vertices + acceleration_structure->wireframe_first_vertex);
} else {
bvh4_get_vertices(acceleration_structure, RADV_BVH_ROOT_NODE,
vertices + acceleration_structure->first_vertex,
vertices + acceleration_structure->wireframe_first_vertex);
}
}
}
#ifdef HAVE_TBB
});
#endif
app->flush_upload_memory();
bool is_first_tlas = true;
for (uint32_t i = 0; i < acceleration_structures.size(); i++) {
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)acceleration_structures[i].get();
if (acceleration_structure->header.geometry_type != VK_GEOMETRY_TYPE_INSTANCES_KHR)
continue;
if (is_first_tlas) {
acceleration_structure->ui.opened = true;
acceleration_structure->ui.request_focus = true;
is_first_tlas = false;
}
acceleration_structure->ui.instance_render_list = std::make_shared<rti_render_list>(app);
if (trace_info->bvh8)
bvh8_get_instances(acceleration_structure, RADV_BVH_ROOT_NODE);
acceleration_structure->ui.instance_render_list->build();
}
for (uint32_t i = 0; i < dispatch_count; i++) {
auto dispatch_view = std::make_shared<rti_dispatch_view>();
dispatch_view->ui.settings.width = dispatch_infos[i].dimensions[0];
dispatch_view->ui.settings.height = dispatch_infos[i].dimensions[1];
dispatch_view->ui.settings.depth = dispatch_infos[i].dimensions[2];
dispatch_view->info = dispatch_infos + i;
dispatch_view->invocation_count =
dispatch_infos[i].dimensions[0] * dispatch_infos[i].dimensions[1] * dispatch_infos[i].dimensions[2];
dispatch_view->invocations.resize(dispatch_view->invocation_count);
/* Stupid but fast enough */
for (uint32_t j = 0; j < dispatch_view->invocation_count; j++) {
if (dispatch_view->invocation_count % (j + 1))
continue;
std::map<uint32_t, uint32_t> sizes;
uint32_t div = dispatch_view->invocation_count / (j + 1);
for (uint32_t k = 0; k < div; k++) {
if ((div % (k + 1)) == 0)
sizes[k + 1] = div / (k + 1);
}
dispatch_view->valid_dispatch_sizes[j + 1] = sizes;
}
dispatch_views.push_back(dispatch_view);
}
uint64_t data_size =
ray_history_data_size - sizeof(radv_rti_ray_history_header) - sizeof(radv_rti_dispatch_info) * dispatch_count;
for (uint32_t offset = 0; offset < data_size;) {
const radv_packed_token_header *header = (const radv_packed_token_header *)(history_data + offset);
if (header->token_type == radv_packed_token_trace_ray) {
const radv_packed_trace_ray_token *token = (const radv_packed_trace_ray_token *)(history_data + offset);
dispatch_views[token->dispatch_index]->invocations[token->header.launch_index].offsets.push_back(offset);
offset += sizeof(radv_packed_trace_ray_token);
} else if (header->token_type == radv_packed_token_iteration) {
const radv_packed_iteration_token *token = (const radv_packed_iteration_token *)(history_data + offset);
dispatch_views[token->dispatch_index]->invocations[token->header.launch_index].offsets.push_back(offset);
rti_invocation_data *invocation =
&dispatch_views[token->dispatch_index]->invocations[token->header.launch_index];
uint32_t node_type = token->node_id & 0xf;
if (node_type == radv_bvh_node_box32)
invocation->box_iteration_count++;
else if (node_type == radv_bvh_node_instance)
invocation->instance_iteration_count++;
else
invocation->primitive_iteration_count++;
offset += sizeof(radv_packed_iteration_token);
} else if (header->token_type == radv_packed_token_accel_struct) {
const radv_packed_accel_struct_token *token = (const radv_packed_accel_struct_token *)(history_data + offset);
dispatch_views[token->dispatch_index]->invocations[token->header.launch_index].offsets.push_back(offset);
offset += sizeof(radv_packed_accel_struct_token);
} else if (header->token_type == radv_packed_token_trace_ray_hit) {
offset += sizeof(radv_packed_trace_ray_end_token);
} else if (header->token_type == radv_packed_token_trace_ray_miss) {
offset += offsetof(radv_packed_trace_ray_end_token, primitive_id);
}
}
for (uint32_t i = 0; i < dispatch_count; i++) {
rti_dispatch_view *dispatch_view = dispatch_views[i].get();
for (uint32_t j = 0; j < dispatch_view->invocation_count; j++) {
rti_invocation_data *invocation = &dispatch_views[i]->invocations[j];
uint32_t iteration_count = invocation->box_iteration_count + invocation->instance_iteration_count +
invocation->primitive_iteration_count;
dispatch_view->max_iteration_count = MAX2(dispatch_view->max_iteration_count, iteration_count);
}
dispatch_view->ui.settings.max_iteration_count = dispatch_view->max_iteration_count;
}
app->finish_upload_memory();
}
static const char *node_type_names[16] = {
"triangle0", "triangle1", "triangle2", "triangle3", "invalid4", "box", "instance", "invalid7",
"triangle4", "triangle5", "triangle6", "triangle7", "invalid12", "invalid13", "invalid14", "invalid15",
};
void
rti_file_view_radv::load_driver_specific(FILE *file, const rti_chunk_header *chunk_header, const rti_header *header)
{
if (chunk_header->type == rti_chunk_type_trace_info_radv) {
assert(chunk_header->size == sizeof(radv_rti_trace_info));
trace_info = (radv_rti_trace_info *)malloc(sizeof(radv_rti_trace_info));
fread(trace_info, sizeof(radv_rti_trace_info), 1, file);
return;
}
ray_history_data = malloc(chunk_header->size);
fread(ray_history_data, chunk_header->size, 1, file);
ray_history_data_size = chunk_header->size;
history_header = (const radv_rti_ray_history_header *)ray_history_data;
dispatch_infos = (const radv_rti_dispatch_info *)(history_header + 1);
history_data = (const uint8_t *)(dispatch_infos + history_header->dispatch_count);
dispatch_count = history_header->dispatch_count;
}
void
rti_file_view_radv::dock_driver_specific(uint32_t left_top, uint32_t left_bottom, uint32_t center, uint32_t right_top,
uint32_t right_bottom)
{
ImGui::DockBuilderDockWindow("dispatches", right_top);
for (uint32_t i = 0; i < dispatch_count; i++) {
char tmp[32];
sprintf(tmp, "dispatch[%u]", i);
ImGui::DockBuilderDockWindow(tmp, center);
}
ImGui::DockBuilderDockWindow("dispatch", right_bottom);
ImGui::DockBuilderDockWindow("ray history", left_top);
ImGui::DockBuilderDockWindow("invocation", left_bottom);
}
rti_invocation_data *
rti_file_view_radv::get_selected_invocation()
{
if (selected_dispatch && selected_dispatch->ui.selection_x != UINT32_MAX) {
uint32_t selection_index = selected_dispatch->ui.selection_x +
selected_dispatch->ui.selection_y * selected_dispatch->ui.settings.width +
selected_dispatch->ui.settings.z * selected_dispatch->ui.settings.width *
selected_dispatch->ui.settings.height;
return &selected_dispatch->invocations[selection_index];
}
return nullptr;
}
void
rti_file_view_radv::run()
{
begin();
rti_invocation_data *selected_invocation = get_selected_invocation();
for (auto &_acceleration_structure : acceleration_structures) {
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)_acceleration_structure.get();
if (begin_viewport(acceleration_structure)) {
uint32_t primitive_vertex_count =
acceleration_structure->header.geometry_type == VK_GEOMETRY_TYPE_TRIANGLES_KHR
? 3
: RTI_FILLED_CUBE_VERTEX_COUNT;
if (acceleration_structure->ui.vertex_buffer) {
rti_render_task solid_task;
solid_task.vertex_buffer = acceleration_structure->ui.vertex_buffer.get();
solid_task.first_vertex = acceleration_structure->first_vertex;
solid_task.vertex_count = acceleration_structure->primitive_count * primitive_vertex_count;
solid_task.flags =
rti_visualization_color_to_renderer_color(acceleration_structure->ui.visualization_color);
render(solid_task);
if (acceleration_structure->wireframe_first_vertex != acceleration_structure->first_vertex) {
rti_render_task wireframe_task;
wireframe_task.type = rti_render_task_type_lines;
wireframe_task.vertex_buffer = acceleration_structure->ui.vertex_buffer.get();
wireframe_task.first_vertex = acceleration_structure->wireframe_first_vertex;
wireframe_task.vertex_count = acceleration_structure->primitive_count * RTI_CUBE_VERTEX_COUNT;
wireframe_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
wireframe_task.wait_for_prev = true;
render(wireframe_task);
} else {
rti_render_task wireframe_task = solid_task;
wireframe_task.type = rti_render_task_type_wireframe;
wireframe_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
wireframe_task.wait_for_prev = true;
render(wireframe_task);
}
} else {
rti_render_task tlas_task;
tlas_task.type = rti_render_task_type_render_list;
tlas_task.render_list = acceleration_structure->ui.instance_render_list.get();
tlas_task.flags = rti_visualization_color_to_renderer_color(acceleration_structure->ui.visualization_color);
tlas_task.override_flags = true;
render(tlas_task);
}
rti_render_task scene_aabb_task;
scene_aabb_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
render_aabb(scene_aabb_task, acceleration_structure->aabb);
if (acceleration_structure->radv_ui.selected_node_id != RADV_BVH_INVALID_NODE) {
if (trace_info->bvh8)
bvh8_render_selection(acceleration_structure);
else
bvh4_render_selection(acceleration_structure);
}
render_viewport();
if (selected_invocation && !selected_invocation->offsets.empty()) {
const radv_packed_trace_ray_token *trace_ray =
(const radv_packed_trace_ray_token *)(history_data +
selected_invocation->offsets[selected_invocation->selected_ray]);
uint64_t accel_struct_addr = ((uint64_t)trace_ray->accel_struct_hi << 32) | trace_ray->accel_struct_lo;
rti_acceleration_structure_radv *trace_accel_struct =
(rti_acceleration_structure_radv *)addr_to_acceleration_structure(accel_struct_addr);
bool draw_ray = acceleration_structure == trace_accel_struct;
rti_mat4 transform;
for (uint32_t i = selected_invocation->selected_ray + 1; i < selected_invocation->offsets.size(); i++) {
const radv_packed_token_header *header =
(const radv_packed_token_header *)(history_data + selected_invocation->offsets[i]);
if (header->token_type == radv_packed_token_trace_ray)
break;
if (header->token_type == radv_packed_token_iteration) {
const radv_packed_iteration_token *token = (const radv_packed_iteration_token *)header;
uint32_t type = token->node_id & 0xf;
if (type == radv_bvh_node_instance) {
rti_mat4 instance_transform;
rti_acceleration_structure_radv *blas = nullptr;
if (trace_info->bvh8)
bvh8_get_instance_info(trace_accel_struct, token->node_id, &instance_transform, &blas);
else
bvh4_get_instance_info(trace_accel_struct, token->node_id, &instance_transform, &blas);
if (blas == acceleration_structure) {
draw_ray = true;
transform = instance_transform;
break;
}
}
}
}
if (draw_ray) {
rti_ray ray = {
.origin = {trace_ray->origin[0], trace_ray->origin[1], trace_ray->origin[2]},
.direction = {trace_ray->direction[0], trace_ray->direction[1], trace_ray->direction[2]},
};
ray = rti_ray::transform(ray, transform);
draw_point(ray.origin, app->selection_color, 4);
draw_line(ray.origin,
{trace_ray->origin[0] + trace_ray->direction[0] * trace_ray->tmax,
trace_ray->origin[1] + trace_ray->direction[1] * trace_ray->tmax,
trace_ray->origin[2] + trace_ray->direction[2] * trace_ray->tmax},
app->selection_color, 1);
}
}
}
end_viewport();
}
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)ui.focused_acceleration_structure;
begin_bvh_tree();
if (acceleration_structure) {
if (ImGui::BeginTable("BVH", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("node type");
ImGui::TableSetupColumn("offset");
ImGui::TableHeadersRow();
if (trace_info->bvh8)
bvh8_draw(acceleration_structure, RADV_BVH_ROOT_NODE);
else
bvh4_draw(acceleration_structure, RADV_BVH_ROOT_NODE);
ImGui::EndTable();
}
acceleration_structure->radv_ui.open_path.clear();
}
end_bvh_tree();
begin_node_info();
if (acceleration_structure && acceleration_structure->radv_ui.selected_node_id != RADV_BVH_INVALID_NODE) {
if (trace_info->bvh8)
bvh8_draw_node_info(acceleration_structure, acceleration_structure->radv_ui.selected_node_id);
else
bvh4_draw_node_info(acceleration_structure, acceleration_structure->radv_ui.selected_node_id);
}
end_node_info();
ImGui::Begin("dispatches", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
if (ImGui::BeginTable("dispatches", 5, ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable)) {
ImGui::TableSetupColumn("index");
ImGui::TableSetupColumn("type");
ImGui::TableSetupColumn("width");
ImGui::TableSetupColumn("height");
ImGui::TableSetupColumn("depth");
ImGui::TableHeadersRow();
for (uint32_t i = 0; i < dispatch_count; i++) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
char tmp[32];
sprintf(tmp, "%u", i);
if (ImGui::Selectable(tmp, false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) {
if (dispatch_views[i]->ui.open)
dispatch_views[i]->ui.request_focus = true;
dispatch_views[i]->ui.open = true;
}
ImGui::TableSetColumnIndex(1);
switch (dispatch_infos[i].type) {
case radv_rti_dispatch_type_trace_rays:
ImGui::Text("vkCmdTraceRays");
break;
case radv_rti_dispatch_type_trace_rays_indirect:
ImGui::Text("vkCmdTraceRaysIndirect");
break;
case radv_rti_dispatch_type_trace_rays_indirect2:
ImGui::Text("vkCmdTraceRaysIndirect2");
break;
}
ImGui::TableSetColumnIndex(2);
ImGui::Text("%u", dispatch_infos[i].dimensions[0]);
ImGui::TableSetColumnIndex(3);
ImGui::Text("%u", dispatch_infos[i].dimensions[1]);
ImGui::TableSetColumnIndex(4);
ImGui::Text("%u", dispatch_infos[i].dimensions[2]);
}
ImGui::EndTable();
}
ImGui::End();
ImGui::Begin("dispatch", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
if (selected_dispatch) {
ImGui::SeparatorText("reshape");
if (ImGui::Button("reset")) {
selected_dispatch->ui.settings.width = selected_dispatch->info->dimensions[0];
selected_dispatch->ui.settings.height = selected_dispatch->info->dimensions[1];
selected_dispatch->ui.settings.depth = selected_dispatch->info->dimensions[2];
selected_dispatch->ui.selection_x = UINT32_MAX;
selected_dispatch->ui.selection_y = UINT32_MAX;
selected_dispatch->ui.initialize_viewport = true;
}
ImGui::SameLine();
char tmp[32];
sprintf(tmp, "%ux%ux%u", selected_dispatch->ui.settings.width, selected_dispatch->ui.settings.height,
selected_dispatch->ui.settings.depth);
if (ImGui::BeginMenu(tmp)) {
for (const auto &valid_size : selected_dispatch->valid_dispatch_sizes) {
sprintf(tmp, "%ux", valid_size.first);
if (ImGui::BeginMenu(tmp)) {
for (const auto &valid_size2 : valid_size.second) {
sprintf(tmp, "%ux%ux%u", valid_size.first, valid_size2.first, valid_size2.second);
if (ImGui::MenuItem(tmp)) {
selected_dispatch->ui.settings.width = valid_size.first;
selected_dispatch->ui.settings.height = valid_size2.first;
selected_dispatch->ui.settings.depth = valid_size2.second;
selected_dispatch->ui.selection_x = UINT32_MAX;
selected_dispatch->ui.selection_y = UINT32_MAX;
selected_dispatch->ui.initialize_viewport = true;
}
}
ImGui::EndMenu();
}
}
ImGui::EndMenu();
}
ImGui::SeparatorText("slice");
int z = selected_dispatch->ui.settings.z;
ImGui::InputInt("z", &z, 1);
selected_dispatch->ui.settings.z = MIN2((uint32_t)z, selected_dispatch->ui.settings.depth - 1);
int max_iteration_count = selected_dispatch->ui.settings.max_iteration_count;
ImGui::SliderInt("max iteration count", &max_iteration_count, 0, selected_dispatch->max_iteration_count);
selected_dispatch->ui.settings.max_iteration_count = max_iteration_count;
ImGui::Checkbox("box_iteration_count", &selected_dispatch->ui.settings.show_box_iteration_count);
ImGui::Checkbox("instance_iteration_count", &selected_dispatch->ui.settings.show_instance_iteration_count);
ImGui::Checkbox("primitive_iteration_count", &selected_dispatch->ui.settings.show_primitive_iteration_count);
}
ImGui::End();
for (uint32_t i = 0; i < dispatch_count; i++) {
rti_dispatch_view *dispatch_view = dispatch_views[i].get();
if (!dispatch_view->ui.open)
continue;
char tmp[32];
sprintf(tmp, "dispatch[%u]", i);
if (!ImGui::Begin(tmp, nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
ImGui::End();
continue;
}
if (dispatch_view->ui.request_focus) {
ImGui::SetWindowFocus();
dispatch_view->ui.request_focus = false;
}
if (ImGui::IsWindowFocused())
selected_dispatch = dispatch_view;
if (memcmp(&dispatch_view->ui.settings, &dispatch_view->ui.prev_settings, sizeof(dispatch_view->ui.settings))) {
std::shared_ptr<ImTextureData> texture_data = std::make_shared<ImTextureData>();
texture_data->Create(ImTextureFormat_RGBA32, dispatch_view->ui.settings.width,
dispatch_view->ui.settings.height);
uint32_t base_index =
dispatch_view->ui.settings.z * dispatch_view->ui.settings.width * dispatch_view->ui.settings.height;
for (uint32_t y = 0; y < dispatch_view->ui.settings.height; y++) {
for (uint32_t x = 0; x < dispatch_view->ui.settings.width; x++) {
rti_invocation_data *invocation =
&dispatch_view->invocations[x + y * dispatch_view->ui.settings.width + base_index];
uint32_t iteration_count = 0;
if (dispatch_view->ui.settings.show_box_iteration_count)
iteration_count += invocation->box_iteration_count;
if (dispatch_view->ui.settings.show_instance_iteration_count)
iteration_count += invocation->instance_iteration_count;
if (dispatch_view->ui.settings.show_primitive_iteration_count)
iteration_count += invocation->primitive_iteration_count;
float brightness =
fmin((float)iteration_count / (float)dispatch_view->ui.settings.max_iteration_count, 1.0);
ImVec4 color = {brightness, brightness, brightness, 1};
ImU32 *pixel = (ImU32 *)texture_data->GetPixelsAt(x, y);
*pixel = ImGui::ColorConvertFloat4ToU32(color);
}
}
ImGui::RegisterUserTexture(texture_data.get());
dispatch_view->ui.texture_data.push_back(texture_data);
dispatch_view->ui.prev_settings = dispatch_view->ui.settings;
}
for (auto it = dispatch_view->ui.destroy_texture_data.begin();
it != dispatch_view->ui.destroy_texture_data.end();) {
(*it)->UnusedFrames++;
if ((*it)->Status == ImTextureStatus_Destroyed) {
ImGui::UnregisterUserTexture(it->get());
it = dispatch_view->ui.destroy_texture_data.erase(it);
} else {
it++;
}
}
while (dispatch_view->ui.texture_data.size() > 1) {
if (dispatch_view->ui.texture_data[1]->Status != ImTextureStatus_OK)
break;
if (dispatch_view->ui.texture_data[0]->Status != ImTextureStatus_OK)
break;
dispatch_view->ui.texture_data[0]->Status = ImTextureStatus_WantDestroy;
dispatch_view->ui.texture_data[0]->DestroyPixels();
dispatch_view->ui.destroy_texture_data.push_back(dispatch_view->ui.texture_data[0]);
dispatch_view->ui.texture_data.erase(dispatch_view->ui.texture_data.begin());
}
ImVec2 viewport_offset = ImGui::GetCursorScreenPos();
ImVec2 viewport_size = ImGui::GetContentRegionAvail();
ImVec2 image_size = ImVec2(dispatch_view->ui.settings.width, dispatch_view->ui.settings.height);
if (dispatch_view->ui.initialize_viewport) {
dispatch_view->ui.initialize_viewport = false;
dispatch_view->ui.image_scale = fmin(viewport_size.x / image_size.x, viewport_size.y / image_size.y);
dispatch_view->ui.viewport_center.x = image_size.x / 2;
dispatch_view->ui.viewport_center.y = image_size.y / 2;
}
ImVec2 mouse_position = ImGui::GetMousePos();
bool mouse_inside_viewport = mouse_position.x >= viewport_offset.x && mouse_position.y >= viewport_offset.y &&
mouse_position.x <= viewport_offset.x + viewport_size.x &&
mouse_position.y <= viewport_offset.y + viewport_size.y;
if (ImGui::IsWindowFocused()) {
bool right_mouse_button_pressed = ImGui::IsMouseDown(ImGuiMouseButton_Right);
/* Detect thet start/end of dragging inside the viewport. */
if (!dispatch_view->ui.is_dragging_viewport && right_mouse_button_pressed && mouse_inside_viewport) {
dispatch_view->ui.prev_mouse_pos = mouse_position;
dispatch_view->ui.is_dragging_viewport = true;
}
if (dispatch_view->ui.is_dragging_viewport && !right_mouse_button_pressed) {
dispatch_view->ui.is_dragging_viewport = false;
}
if (dispatch_view->ui.is_dragging_viewport) {
float drag_delta_x = mouse_position.x - dispatch_view->ui.prev_mouse_pos.x;
float drag_delta_y = mouse_position.y - dispatch_view->ui.prev_mouse_pos.y;
dispatch_view->ui.viewport_center.x -= drag_delta_x / dispatch_view->ui.image_scale;
dispatch_view->ui.viewport_center.y -= drag_delta_y / dispatch_view->ui.image_scale;
}
dispatch_view->ui.prev_mouse_pos = mouse_position;
if (mouse_inside_viewport) {
float mouse_wheel = ImGui::GetIO().MouseWheel;
dispatch_view->ui.image_scale *= pow(1.1, mouse_wheel);
}
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_None) && mouse_inside_viewport) {
float image_x =
(mouse_position.x - (viewport_offset.x + viewport_size.x / 2)) / dispatch_view->ui.image_scale +
dispatch_view->ui.viewport_center.x;
float image_y =
(mouse_position.y - (viewport_offset.y + viewport_size.y / 2)) / dispatch_view->ui.image_scale +
dispatch_view->ui.viewport_center.y;
if (image_x >= 0 && image_x < image_size.x && image_y >= 0 && image_y < image_size.y) {
dispatch_view->ui.selection_x = (uint32_t)image_x;
dispatch_view->ui.selection_y = (uint32_t)image_y;
}
}
}
if (dispatch_view->ui.texture_data.size() && dispatch_view->ui.texture_data[0]->Status == ImTextureStatus_OK) {
ImDrawList *draw_list = ImGui::GetWindowDrawList();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() -
dispatch_view->ui.viewport_center.x * dispatch_view->ui.image_scale +
viewport_size.x / 2);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() -
dispatch_view->ui.viewport_center.y * dispatch_view->ui.image_scale +
viewport_size.y / 2);
ImVec2 global_image_offset = ImGui::GetCursorScreenPos();
draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest);
ImGui::Image(
dispatch_view->ui.texture_data[0]->GetTexRef(),
ImVec2(image_size.x * dispatch_view->ui.image_scale, image_size.y * dispatch_view->ui.image_scale));
draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear);
if (dispatch_view->ui.selection_x != UINT32_MAX) {
ImU32 selection_color = ImGui::ColorConvertFloat4ToU32(
ImVec4(app->selection_color.x, app->selection_color.y, app->selection_color.z, 1));
ImVec2 tl = {
global_image_offset.x + (dispatch_view->ui.selection_x + 0) * dispatch_view->ui.image_scale,
global_image_offset.y + (dispatch_view->ui.selection_y + 0) * dispatch_view->ui.image_scale,
};
ImVec2 br = {
global_image_offset.x + (dispatch_view->ui.selection_x + 1) * dispatch_view->ui.image_scale,
global_image_offset.y + (dispatch_view->ui.selection_y + 1) * dispatch_view->ui.image_scale,
};
float thickness = fmax(1.0, 0.2 * dispatch_view->ui.image_scale);
draw_list->AddRect(tl, br, selection_color, thickness, 0, thickness);
}
}
ImGui::End();
}
ImGui::Begin("invocation", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
if (selected_invocation) {
for (uint32_t i = 0; i < selected_invocation->offsets.size(); i++) {
const radv_packed_token_header *header =
(const radv_packed_token_header *)(history_data + selected_invocation->offsets[i]);
if (header->token_type != radv_packed_token_trace_ray)
continue;
const radv_packed_trace_ray_token *token = (const radv_packed_trace_ray_token *)header;
char tmp[32];
sprintf(tmp, "trace_ray[%u]", i);
ImGui::Separator();
if (ImGui::Selectable(tmp, i == selected_invocation->selected_ray))
selected_invocation->selected_ray = i;
if (ImGui::BeginTable(tmp, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("accel_struct");
ImGui::TableNextColumn();
uint64_t addr = ((uint64_t)token->accel_struct_hi << 32) | token->accel_struct_lo;
sprintf(tmp, "0x%" PRIx64, addr);
if (ImGui::TextLink(tmp)) {
rti_acceleration_structure *acceleration_structure = addr_to_acceleration_structure(addr);
if (acceleration_structure->ui.opened)
acceleration_structure->ui.request_focus = true;
acceleration_structure->ui.opened = true;
}
ImGui::TableNextColumnText("origin");
ImGui::TableNextColumnText("(%f, %f, %f)", token->origin[0], token->origin[1], token->origin[2]);
ImGui::TableNextColumnText("direction");
ImGui::TableNextColumnText("(%f, %f, %f)", token->direction[0], token->direction[1], token->direction[2]);
ImGui::TableNextColumnText("tmin/tmax");
ImGui::TableNextColumnText("(%f, %f)", token->tmin, token->tmax);
ImGui::TableNextColumnText("sbt_offset");
ImGui::TableNextColumnText("%u", token->sbt_offset);
ImGui::TableNextColumnText("sbt_stride");
ImGui::TableNextColumnText("%u", token->sbt_stride);
ImGui::TableNextColumnText("miss_index");
ImGui::TableNextColumnText("%u", token->miss_index);
ImGui::TableNextColumnText("cull_mask");
ImGui::TableNextColumnText("0x%x", token->cull_mask);
ImGui::TableNextColumnText("flags");
ImGui::TableNextColumnText(
"%s%s%s%s%s%s%s%s%s%s", (token->flags & SpvRayFlagsOpaqueKHRMask) ? "Opaque " : "",
(token->flags & SpvRayFlagsNoOpaqueKHRMask) ? "NoOpaque " : "",
(token->flags & SpvRayFlagsTerminateOnFirstHitKHRMask) ? "TerminateOnFirstHit " : "",
(token->flags & SpvRayFlagsSkipClosestHitShaderKHRMask) ? "SkipClosestHitShader " : "",
(token->flags & SpvRayFlagsCullBackFacingTrianglesKHRMask) ? "CullBackFacingTriangles " : "",
(token->flags & SpvRayFlagsCullFrontFacingTrianglesKHRMask) ? "CullFrontFacingTriangles " : "",
(token->flags & SpvRayFlagsCullOpaqueKHRMask) ? "CullOpaque " : "",
(token->flags & SpvRayFlagsCullNoOpaqueKHRMask) ? "CullNoOpaque " : "",
(token->flags & SpvRayFlagsSkipTrianglesKHRMask) ? "SkipTriangles " : "",
(token->flags & SpvRayFlagsSkipAABBsKHRMask) ? "SkipAABBs " : "");
ImGui::EndTable();
}
}
}
ImGui::End();
ImGui::Begin("ray history", nullptr, ImGuiWindowFlags_NoFocusOnAppearing);
if (selected_invocation && !selected_invocation->offsets.empty()) {
const radv_packed_trace_ray_token *trace_ray =
(const radv_packed_trace_ray_token *)(history_data +
selected_invocation->offsets[selected_invocation->selected_ray]);
uint64_t accel_struct_addr = ((uint64_t)trace_ray->accel_struct_hi << 32) | trace_ray->accel_struct_lo;
if (ImGui::BeginTable("history", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg)) {
for (uint32_t i = selected_invocation->selected_ray + 1; i < selected_invocation->offsets.size(); i++) {
const radv_packed_token_header *header =
(const radv_packed_token_header *)(history_data + selected_invocation->offsets[i]);
if (header->token_type == radv_packed_token_trace_ray)
break;
if (header->token_type == radv_packed_token_iteration) {
const radv_packed_iteration_token *token = (const radv_packed_iteration_token *)header;
uint32_t offset = (token->node_id & (~0xf)) << 3;
uint32_t type = token->node_id & 0xf;
rti_acceleration_structure_radv *acceleration_structure =
(rti_acceleration_structure_radv *)addr_to_acceleration_structure(accel_struct_addr);
char selectable_label[32];
sprintf(selectable_label, "%s##%x", node_type_names[type], i);
ImGui::TableNextColumn();
if (ImGui::Selectable(selectable_label,
token->node_id == acceleration_structure->radv_ui.selected_node_id,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) {
if (acceleration_structure->ui.opened)
acceleration_structure->ui.request_focus = true;
acceleration_structure->ui.opened = true;
acceleration_structure->radv_ui.selected_node_id = token->node_id;
}
ImGui::TableNextColumnText("0x%x", offset);
} else if (header->token_type == radv_packed_token_accel_struct) {
const radv_packed_accel_struct_token *token = (const radv_packed_accel_struct_token *)header;
accel_struct_addr = token->accel_struct;
}
}
ImGui::EndTable();
}
}
ImGui::End();
end();
}
std::unique_ptr<rti_file_view>
rti_create_file_view_radv()
{
return std::make_unique<rti_file_view_radv>();
}

View file

@ -0,0 +1,177 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cstdint>
#include <imgui.h>
#include <map>
#include <memory>
#include <stdint.h>
#include "bvh.h"
#include "radv_rti.h"
#include "rti_file_view.h"
#include "rti_util.h"
#include <unordered_set>
struct rti_acceleration_structure_radv : public rti_acceleration_structure {
rti_acceleration_structure_radv(rti_file_view *view): rti_acceleration_structure(view)
{
}
struct {
uint32_t selected_node_id = RADV_BVH_INVALID_NODE;
bool scroll_to_selected_node = false;
std::unordered_set<uint32_t> open_path;
} radv_ui;
const uint8_t *bvh;
uint32_t first_vertex;
uint32_t wireframe_first_vertex;
};
struct rti_dispatch_view_settings {
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 0;
uint32_t z = 0;
uint32_t max_iteration_count = 0;
bool show_box_iteration_count = true;
bool show_instance_iteration_count = true;
bool show_primitive_iteration_count = true;
};
struct rti_invocation_data {
std::vector<uint32_t> offsets;
uint32_t selected_ray = 0;
uint32_t box_iteration_count = 0;
uint32_t instance_iteration_count = 0;
uint32_t primitive_iteration_count = 0;
};
struct rti_dispatch_view {
struct {
bool open = false;
bool request_focus = false;
bool initialize_viewport = true;
std::vector<std::shared_ptr<ImTextureData>> texture_data;
std::vector<std::shared_ptr<ImTextureData>> destroy_texture_data;
float image_scale;
rti_vec2 viewport_center;
bool is_dragging_viewport = false;
ImVec2 prev_mouse_pos;
uint32_t selection_x = UINT32_MAX;
uint32_t selection_y = UINT32_MAX;
rti_dispatch_view_settings prev_settings;
rti_dispatch_view_settings settings;
} ui;
const radv_rti_dispatch_info *info;
uint32_t invocation_count;
uint32_t max_iteration_count = 0;
std::vector<rti_invocation_data> invocations;
std::map<uint32_t, std::map<uint32_t, uint32_t>> valid_dispatch_sizes;
};
struct rti_file_view_radv : public rti_file_view {
std::vector<std::shared_ptr<rti_dispatch_view>> dispatch_views;
rti_dispatch_view *selected_dispatch = nullptr;
void *ray_history_data = nullptr;
const radv_rti_ray_history_header *history_header = nullptr;
const radv_rti_dispatch_info *dispatch_infos = nullptr;
const uint8_t *history_data = nullptr;
uint32_t dispatch_count = 0;
uint64_t ray_history_data_size;
radv_rti_trace_info *trace_info;
virtual std::unique_ptr<rti_acceleration_structure> create_acceleration_structure() override;
virtual void load(FILE *file, const rti_header *header) override;
virtual void load_driver_specific(FILE *file, const rti_chunk_header *chunk_header,
const rti_header *header) override;
virtual void dock_driver_specific(uint32_t left_top, uint32_t left_bottom, uint32_t center, uint32_t right_top,
uint32_t right_bottom) override;
virtual float handle_mouse_click(rti_acceleration_structure *acceleration_structure, rti_ray ray,
bool select) override;
virtual void run() override;
rti_invocation_data *get_selected_invocation();
/* bvh4 */
void bvh4_traverse(rti_acceleration_structure_radv *acceleration_structure, rti_ray ray, uint32_t id, float *tmax,
const std::unordered_set<uint32_t> &path, bool select);
rti_aabb bvh4_scene_aabb(rti_acceleration_structure_radv *acceleration_structure);
void bvh4_get_vertices(rti_acceleration_structure_radv *acceleration_structure, uint32_t id, rti_vertex *vertices,
rti_vertex *wireframe_vertices);
void bvh4_get_instances(rti_acceleration_structure_radv *tlas, uint32_t id);
void bvh4_render_selection(rti_acceleration_structure_radv *acceleration_structure);
void bvh4_draw(rti_acceleration_structure_radv *acceleration_structure, uint32_t id);
void bvh4_draw_node_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id);
void bvh4_get_instance_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_mat4 *transform, rti_acceleration_structure_radv **blas);
/* bvh8 */
void bvh8_traverse(rti_acceleration_structure_radv *acceleration_structure, rti_ray ray, uint32_t id, float *tmax,
const std::unordered_set<uint32_t> &path, bool select);
rti_aabb bvh8_scene_aabb(rti_acceleration_structure_radv *acceleration_structure);
void bvh8_get_vertices(rti_acceleration_structure_radv *acceleration_structure, uint32_t id, rti_vertex *vertices,
rti_vertex *wireframe_vertices);
void bvh8_get_instances(rti_acceleration_structure_radv *tlas, uint32_t id);
void bvh8_render_selection(rti_acceleration_structure_radv *acceleration_structure);
void bvh8_draw(rti_acceleration_structure_radv *acceleration_structure, uint32_t id);
void bvh8_draw_node_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id);
void bvh8_get_instance_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_mat4 *transform, rti_acceleration_structure_radv **blas);
};
static inline uint64_t
radv_node_to_addr(uint64_t node)
{
node &= ~7ull;
node <<= 19;
return ((int64_t)node) >> 16;
}
static inline rti_mat4
mat3x4_to_rti(mat3x4 m)
{
rti_mat4 result;
for (uint32_t i = 0; i < 3; i++) {
for (uint32_t j = 0; j < 4; j++) {
result.elements[i + j * 4] = m.values[i][j];
}
}
return result;
}

View file

@ -0,0 +1,490 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "rti_file_view.h"
#include "rti_file_view_radv.h"
#include <cinttypes>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <imgui.h>
#include <memory>
#include <stdint.h>
#include "shaders/rti_shader_interface.h"
#include "util/half_float.h"
#include "util/rti_format.h"
#include "vulkan/vulkan_core.h"
#include "bvh.h"
#include "rti_app.h"
#include "rti_util.h"
#include "vk_bvh.h"
static radv_bvh_box32_node
radv_get_box_node(const void *node, uint32_t type)
{
if (type == radv_bvh_node_box32)
return *(const radv_bvh_box32_node *)node;
const radv_bvh_box16_node *box16 = (const radv_bvh_box16_node *)node;
radv_bvh_box32_node box32;
memset(&box32, 0, sizeof(box32));
for (uint32_t i = 0; i < 4; i++) {
box32.children[i] = box16->children[i];
box32.coords[i] = {
.min =
{
.x = _mesa_half_to_float(box16->coords[i].min_x),
.y = _mesa_half_to_float(box16->coords[i].min_y),
.z = _mesa_half_to_float(box16->coords[i].min_z),
},
.max =
{
.x = _mesa_half_to_float(box16->coords[i].max_x),
.y = _mesa_half_to_float(box16->coords[i].max_y),
.z = _mesa_half_to_float(box16->coords[i].max_z),
},
};
}
return box32;
}
static rti_acceleration_structure_radv *
radv_bvh_instance_node_get_blas(rti_file_view *view, const radv_bvh_instance_node *node)
{
return (rti_acceleration_structure_radv *)view->addr_to_acceleration_structure(radv_node_to_addr(node->bvh_ptr));
}
static rti_aabb
vk_aabb_to_rti(vk_aabb aabb)
{
return {
.min = {aabb.min.x, aabb.min.y, aabb.min.z},
.max = {aabb.max.x, aabb.max.y, aabb.max.z},
};
}
void
rti_file_view_radv::bvh4_traverse(rti_acceleration_structure_radv *acceleration_structure, rti_ray ray, uint32_t id,
float *tmax, const std::unordered_set<uint32_t> &path, bool select)
{
uint32_t offset = (id & (~0x7)) << 3;
uint32_t type = id & 0x7;
std::unordered_set<uint32_t> path_copy = path;
if (select)
path_copy.insert(id);
radv_bvh_box32_node node = radv_get_box_node(acceleration_structure->bvh + offset, type);
for (uint32_t i = 0; i < 4; i++) {
if (node.children[i] == RADV_BVH_INVALID_NODE)
continue;
uint32_t child_offset = (node.children[i] & (~0x7)) << 3;
uint32_t child_type = node.children[i] & 0x7;
rti_aabb aabb = vk_aabb_to_rti(node.coords[i]);
float t = rti_aabb::intersect_ray(aabb, ray);
if (t >= *tmax)
continue;
if (child_type == radv_bvh_node_box32 || child_type == radv_bvh_node_box16) {
bvh4_traverse(acceleration_structure, ray, node.children[i], tmax, path_copy, select);
} else if (child_type == radv_bvh_node_triangle) {
const radv_bvh_triangle_node *triangle_node =
(const radv_bvh_triangle_node *)(acceleration_structure->bvh + child_offset);
rti_triangle triangle;
for (uint32_t j = 0; j < 3; j++) {
triangle.vertices[j] = {triangle_node->coords[j][0], triangle_node->coords[j][1],
triangle_node->coords[j][2]};
}
t = rti_triangle::intersect_ray(triangle, ray);
if (t < *tmax) {
*tmax = t;
if (select) {
acceleration_structure->radv_ui.selected_node_id = node.children[i];
acceleration_structure->radv_ui.open_path = path_copy;
acceleration_structure->radv_ui.scroll_to_selected_node = true;
}
}
} else if (child_type == radv_bvh_node_aabb) {
if (t < *tmax) {
*tmax = t;
if (select) {
acceleration_structure->radv_ui.selected_node_id = node.children[i];
acceleration_structure->radv_ui.open_path = path_copy;
acceleration_structure->radv_ui.scroll_to_selected_node = true;
}
}
} else {
const radv_bvh_instance_node *instance =
(const radv_bvh_instance_node *)(acceleration_structure->bvh + child_offset);
rti_ray object_ray = rti_ray::transform(ray, mat3x4_to_rti(instance->wto_matrix));
rti_acceleration_structure_radv *blas = radv_bvh_instance_node_get_blas(this, instance);
float blas_tmax = INFINITY;
std::unordered_set<uint32_t> blas_path;
bvh4_traverse(blas, object_ray, RADV_BVH_ROOT_NODE, &blas_tmax, blas_path, false);
if (blas_tmax < INFINITY) {
if (select) {
acceleration_structure->radv_ui.selected_node_id = node.children[i];
acceleration_structure->radv_ui.open_path = path_copy;
acceleration_structure->radv_ui.scroll_to_selected_node = true;
}
*tmax = blas_tmax;
}
}
}
}
rti_aabb
rti_file_view_radv::bvh4_scene_aabb(rti_acceleration_structure_radv *acceleration_structure)
{
rti_aabb aabb;
uint32_t root_offset = (RADV_BVH_ROOT_NODE & (~0x7)) << 3;
uint32_t root_type = RADV_BVH_ROOT_NODE & 0x7;
assert(root_type == radv_bvh_node_box32);
bool first = true;
const radv_bvh_box32_node *root_node = (const radv_bvh_box32_node *)(acceleration_structure->bvh + root_offset);
for (uint32_t i = 0; i < 4; i++) {
if (root_node->children[i] == RADV_BVH_INVALID_NODE)
continue;
rti_aabb child_aabb = vk_aabb_to_rti(root_node->coords[i]);
if (first) {
aabb = child_aabb;
first = false;
} else {
aabb = rti_aabb::combine(aabb, child_aabb);
}
}
return aabb;
}
void
rti_file_view_radv::bvh4_get_vertices(rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_vertex *vertices, rti_vertex *wireframe_vertices)
{
uint32_t offset = (id & (~0x7)) << 3;
uint32_t type = id & 0x7;
radv_bvh_box32_node box_node = radv_get_box_node(acceleration_structure->bvh + offset, type);
for (uint32_t i = 0; i < 4; i++) {
if (box_node.children[i] == RADV_BVH_INVALID_NODE)
continue;
uint32_t child_offset = (box_node.children[i] & (~0x7)) << 3;
uint32_t child_type = box_node.children[i] & 0x7;
if (child_type == radv_bvh_node_box32 || child_type == radv_bvh_node_box16) {
bvh4_get_vertices(acceleration_structure, box_node.children[i], vertices, wireframe_vertices);
} else if (child_type == radv_bvh_node_triangle) {
const radv_bvh_triangle_node *node =
(const radv_bvh_triangle_node *)(acceleration_structure->bvh + child_offset);
uint32_t geometry_index = node->geometry_id_and_flags & 0xffffff;
uint32_t dst_index =
acceleration_structure->primitive_counts_exclusive_sum[node->geometry_id_and_flags & 0xfffffff] +
node->triangle_id;
for (uint32_t j = 0; j < 3; j++) {
vertices[dst_index * 3 + j].position = {node->coords[j][0], node->coords[j][1], node->coords[j][2]};
vertices[dst_index * 3 + j].geometry_index = geometry_index;
vertices[dst_index * 3 + j].primitive_index = node->triangle_id;
}
} else if (child_type == radv_bvh_node_aabb) {
const radv_bvh_aabb_node *node = (const radv_bvh_aabb_node *)(acceleration_structure->bvh + child_offset);
uint32_t dst_index =
acceleration_structure->primitive_counts_exclusive_sum[node->geometry_id_and_flags & 0xfffffff] +
node->primitive_id;
rti_aabb aabb = vk_aabb_to_rti(box_node.coords[i]);
rti_generate_cube_vertices(wireframe_vertices + dst_index * RTI_CUBE_VERTEX_COUNT, aabb);
rti_generate_filled_cube_vertices(vertices + dst_index * RTI_FILLED_CUBE_VERTEX_COUNT, aabb,
node->geometry_id_and_flags & 0xffffff, node->primitive_id);
}
}
}
void
rti_file_view_radv::bvh4_get_instances(rti_acceleration_structure_radv *tlas, uint32_t id)
{
uint32_t offset = (id & (~0x7)) << 3;
uint32_t type = id & 0x7;
if (type == radv_bvh_node_box32 || type == radv_bvh_node_box16) {
radv_bvh_box32_node node = radv_get_box_node(tlas->bvh + offset, type);
for (uint32_t i = 0; i < 4; i++)
if (node.children[i] != RADV_BVH_INVALID_NODE)
bvh4_get_instances(tlas, node.children[i]);
} else if (type == radv_bvh_node_instance) {
const radv_bvh_instance_node *node = (const radv_bvh_instance_node *)(tlas->bvh + offset);
rti_acceleration_structure_radv *blas = radv_bvh_instance_node_get_blas(this, node);
uint32_t primitive_vertex_count =
blas->header.geometry_type == VK_GEOMETRY_TYPE_TRIANGLES_KHR ? 3 : RTI_FILLED_CUBE_VERTEX_COUNT;
rti_render_task solid_task;
solid_task.vertex_buffer = blas->ui.vertex_buffer.get();
solid_task.first_vertex = blas->first_vertex;
solid_task.vertex_count = blas->primitive_count * primitive_vertex_count;
solid_task.params.transform = mat3x4_to_rti(node->otw_matrix);
solid_task.params.color = rti_index_to_color(node->instance_id);
solid_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
tlas->ui.instance_render_list->tasks.push_back(solid_task);
}
}
static const char *node_type_names[8] = {
"triangle0", "triangle1", "triangle2", "triangle3", "box16", "box32", "instance", "aabb",
};
void
rti_file_view_radv::bvh4_draw(rti_acceleration_structure_radv *acceleration_structure, uint32_t id)
{
uint32_t offset = (id & (~0x7)) << 3;
uint32_t type = id & 0x7;
bool selected = acceleration_structure->radv_ui.selected_node_id == id;
ImGuiTreeNodeFlags node_flags =
ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (type != radv_bvh_node_box32 && type != radv_bvh_node_box16)
node_flags |= ImGuiTreeNodeFlags_Leaf;
if (selected)
node_flags |= ImGuiTreeNodeFlags_Selected;
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (acceleration_structure->radv_ui.open_path.find(id) != acceleration_structure->radv_ui.open_path.end())
ImGui::SetNextItemOpen(true);
char tree_node_id[32];
sprintf(tree_node_id, "%x %x", type, offset);
bool open = ImGui::TreeNodeEx(tree_node_id, node_flags, "%s", node_type_names[type]);
if (ImGui::IsItemClicked())
acceleration_structure->radv_ui.selected_node_id = id;
ImGui::TableNextColumnText("0x%x", offset);
if (open) {
if (type == radv_bvh_node_box32 || type == radv_bvh_node_box16) {
radv_bvh_box32_node box_node = radv_get_box_node(acceleration_structure->bvh + offset, type);
for (uint32_t i = 0; i < 4; i++) {
if (box_node.children[i] != RADV_BVH_INVALID_NODE)
bvh4_draw(acceleration_structure, box_node.children[i]);
}
}
ImGui::TreePop();
}
if (acceleration_structure->radv_ui.scroll_to_selected_node && selected) {
ImGui::SetScrollHereY();
acceleration_structure->radv_ui.scroll_to_selected_node = false;
}
}
void
rti_file_view_radv::bvh4_draw_node_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id)
{
uint32_t offset = (acceleration_structure->radv_ui.selected_node_id & (~0x7)) << 3;
uint32_t type = acceleration_structure->radv_ui.selected_node_id & 0x7;
if (type == radv_bvh_node_box32 || type == radv_bvh_node_box16) {
radv_bvh_box32_node node = radv_get_box_node(acceleration_structure->bvh + offset, type);
char tmp[64];
sprintf(tmp, "box%u node (0x%x)", type == radv_bvh_node_box32 ? 32 : 16, offset);
ImGui::SeparatorText(tmp);
for (uint32_t i = 0; i < 4; i++) {
sprintf(tmp, "child[%u]", i);
ImGui::SeparatorText(tmp);
sprintf(tmp, "child[%u] properties", i);
if (ImGui::BeginTable(tmp, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("id");
ImGui::TableNextColumnText("0x%x", node.children[i]);
ImGui::TableNextColumnText("aabb");
ImGui::TableNextColumnText("(%f, %f, %f) (%f, %f, %f)", node.coords[i].min.x, node.coords[i].min.y,
node.coords[i].min.z, node.coords[i].max.x, node.coords[i].max.y,
node.coords[i].max.z);
ImGui::EndTable();
}
}
} else if (type == radv_bvh_node_instance) {
const radv_bvh_instance_node *node = (const radv_bvh_instance_node *)(acceleration_structure->bvh + offset);
char tmp[64];
sprintf(tmp, "instance node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("instance node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("bvh_ptr");
ImGui::TableNextColumn();
sprintf(tmp, "0x%" PRIx64, node->bvh_ptr);
if (ImGui::TextLink(tmp)) {
rti_acceleration_structure_radv *blas = radv_bvh_instance_node_get_blas(this, node);
if (blas->ui.opened)
blas->ui.request_focus = true;
blas->ui.opened = true;
}
for (uint32_t i = 0; i < 3; i++) {
ImGui::TableNextColumnText("wto_matrix.row[%u]", i);
ImGui::TableNextColumnText("(%f, %f, %f, %f)", node->wto_matrix.values[i][0], node->wto_matrix.values[i][1],
node->wto_matrix.values[i][2], node->wto_matrix.values[i][3]);
}
for (uint32_t i = 0; i < 3; i++) {
ImGui::TableNextColumnText("otw_matrix.row[%u]", i);
ImGui::TableNextColumnText("(%f, %f, %f, %f)", node->otw_matrix.values[i][0], node->otw_matrix.values[i][1],
node->otw_matrix.values[i][2], node->otw_matrix.values[i][3]);
}
ImGui::TableNextColumnText("user_data");
ImGui::TableNextColumnText("0x%x", node->custom_instance_and_mask & 0xFFFFFF);
ImGui::TableNextColumnText("mask");
ImGui::TableNextColumnText("0x%x", node->custom_instance_and_mask >> 24);
ImGui::TableNextColumnText("sbt_offset");
ImGui::TableNextColumnText("0x%x", node->sbt_offset_and_flags & 0xFFFFFF);
ImGui::TableNextColumnText("flags");
ImGui::TableNextColumnText(
"%s%s%s%s", (node->sbt_offset_and_flags & RADV_INSTANCE_FORCE_OPAQUE) ? "force_opaque " : "",
(node->sbt_offset_and_flags & RADV_INSTANCE_NO_FORCE_NOT_OPAQUE) ? "no_force_not_opaque " : "",
(node->sbt_offset_and_flags & RADV_INSTANCE_TRIANGLE_FACING_CULL_DISABLE) ? "triangle_facing_cull_disable "
: "",
(node->sbt_offset_and_flags & RADV_INSTANCE_TRIANGLE_FLIP_FACING) ? "triangle_flip_facing " : "");
ImGui::TableNextColumnText("instance_id");
ImGui::TableNextColumnText("%u", node->instance_id);
ImGui::EndTable();
}
} else if (type == radv_bvh_node_triangle) {
const radv_bvh_triangle_node *node = (const radv_bvh_triangle_node *)(acceleration_structure->bvh + offset);
char tmp[64];
sprintf(tmp, "triangle node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("triangle node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
for (uint32_t i = 0; i < 3; i++) {
ImGui::TableNextColumnText("v%u", i);
ImGui::TableNextColumnText("(%f, %f, %f)", node->coords[i][0], node->coords[i][1], node->coords[i][2]);
}
ImGui::TableNextColumnText("triangle_id");
ImGui::TableNextColumnText("%u", node->triangle_id);
ImGui::TableNextColumnText("geometry_id");
ImGui::TableNextColumnText("%u", node->geometry_id_and_flags & 0xfffffff);
ImGui::TableNextColumnText("flags");
ImGui::TableNextColumnText("%s", (node->geometry_id_and_flags & RADV_GEOMETRY_OPAQUE) ? "opaque " : "");
ImGui::EndTable();
}
} else {
const radv_bvh_aabb_node *node = (const radv_bvh_aabb_node *)(acceleration_structure->bvh + offset);
char tmp[64];
sprintf(tmp, "aabb node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("aabb node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("primitive_id");
ImGui::TableNextColumnText("%u", node->primitive_id);
ImGui::TableNextColumnText("geometry_id");
ImGui::TableNextColumnText("%u", node->geometry_id_and_flags & 0xfffffff);
ImGui::TableNextColumnText("flags");
ImGui::TableNextColumnText("%s", (node->geometry_id_and_flags & RADV_GEOMETRY_OPAQUE) ? "opaque " : "");
ImGui::EndTable();
}
}
}
void
rti_file_view_radv::bvh4_render_selection(rti_acceleration_structure_radv *acceleration_structure)
{
rti_render_task selection_task;
selection_task.type = rti_render_task_type_thick_wireframe;
selection_task.vertex_buffer = acceleration_structure->ui.vertex_buffer.get();
selection_task.vertex_count = 3;
selection_task.params.color = app->selection_color;
selection_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
selection_task.wait_for_prev = true;
if (acceleration_structure->wireframe_first_vertex != acceleration_structure->first_vertex) {
selection_task.type = rti_render_task_type_thick_lines;
selection_task.vertex_count = RTI_CUBE_VERTEX_COUNT;
}
uint32_t offset = (acceleration_structure->radv_ui.selected_node_id & (~0x7)) << 3;
uint32_t type = acceleration_structure->radv_ui.selected_node_id & 0x7;
if (type == radv_bvh_node_box32 || type == radv_bvh_node_box16) {
radv_bvh_box32_node node = radv_get_box_node(acceleration_structure->bvh + offset, type);
for (uint32_t i = 0; i < 4; i++) {
if (node.children[i] != RADV_BVH_INVALID_NODE) {
render_aabb(selection_task, vk_aabb_to_rti(node.coords[i]));
selection_task.wait_for_prev = false;
}
}
} else if (type == radv_bvh_node_triangle) {
const radv_bvh_triangle_node *node = (const radv_bvh_triangle_node *)(acceleration_structure->bvh + offset);
uint32_t index = acceleration_structure->primitive_counts_exclusive_sum[node->geometry_id_and_flags & 0xfffffff] +
node->triangle_id;
selection_task.first_vertex = acceleration_structure->wireframe_first_vertex + index * 3;
render(selection_task);
selection_task.wait_for_prev = false;
} else if (type == radv_bvh_node_aabb) {
const radv_bvh_aabb_node *node = (const radv_bvh_aabb_node *)(acceleration_structure->bvh + offset);
uint32_t index = acceleration_structure->primitive_counts_exclusive_sum[node->geometry_id_and_flags & 0xfffffff] +
node->primitive_id;
selection_task.first_vertex = acceleration_structure->wireframe_first_vertex + index * RTI_CUBE_VERTEX_COUNT;
render(selection_task);
selection_task.wait_for_prev = false;
} else {
const radv_bvh_instance_node *node = (const radv_bvh_instance_node *)(acceleration_structure->bvh + offset);
selection_task.params.transform = mat3x4_to_rti(node->otw_matrix);
render_aabb(selection_task, radv_bvh_instance_node_get_blas(this, node)->aabb);
}
}
void
rti_file_view_radv::bvh4_get_instance_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_mat4 *transform, rti_acceleration_structure_radv **blas)
{
uint32_t offset = (id & (~0x7)) << 3;
const radv_bvh_instance_node *node = (const radv_bvh_instance_node *)(acceleration_structure->bvh + offset);
*blas = radv_bvh_instance_node_get_blas(this, node);
*transform = mat3x4_to_rti(node->wto_matrix);
}

View file

@ -0,0 +1,736 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "rti_file_view.h"
#include "rti_file_view_radv.h"
#include <cinttypes>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <imgui.h>
#include <memory>
#include <stdint.h>
#include "shaders/rti_shader_interface.h"
#include "util/bitset.h"
#include "util/macros.h"
#include "util/rti_format.h"
#include "util/u_math.h"
#include "vulkan/vulkan_core.h"
#include "bvh.h"
#include "rti_app.h"
#include "rti_util.h"
struct radv_bvh8_box_node_iterator {
const radv_gfx12_box_node *node;
uint32_t internal_id;
uint32_t primitive_id;
int32_t valid_child_count_minus_one;
int32_t index = -1;
radv_bvh8_box_node_iterator(const radv_gfx12_box_node *node): node(node)
{
internal_id = node->internal_base_id;
primitive_id = node->primitive_base_id;
valid_child_count_minus_one = node->child_count_exponents >> 28;
if (valid_child_count_minus_one == 0xF)
valid_child_count_minus_one = -1;
}
uint32_t next()
{
index++;
if (index > (int32_t)valid_child_count_minus_one)
return RADV_BVH_INVALID_NODE;
uint32_t child_type = (node->children[index].dword2 >> 24) & 0xf;
uint32_t child_size = node->children[index].dword2 >> 28;
uint32_t child_id;
if (child_type == radv_bvh_node_box32) {
child_id = internal_id;
internal_id += (child_size * RADV_GFX12_BVH_NODE_SIZE) >> 3;
} else {
child_id = primitive_id;
primitive_id += (child_size * RADV_GFX12_BVH_NODE_SIZE) >> 3;
}
return child_id | child_type;
}
};
static rti_aabb
radv_bvh8_box_child_get_aabb(radv_gfx12_box_child child, vec3 origin, uint32_t child_count_exponents)
{
rti_aabb aabb;
uint32_t exponents[3] = {
child_count_exponents & 0xff,
(child_count_exponents >> 8) & 0xff,
(child_count_exponents >> 16) & 0xff,
};
float extent[3] = {
uif(exponents[0] << 23),
uif(exponents[1] << 23),
uif(exponents[2] << 23),
};
aabb.min.x = (float)(child.dword0 & 0xfff) / 0x1000 * extent[0] + origin.x;
aabb.min.y = (float)((child.dword0 >> 12) & 0xfff) / 0x1000 * extent[1] + origin.y;
aabb.min.z = (float)(child.dword1 & 0xfff) / 0x1000 * extent[2] + origin.z;
aabb.max.x = (float)(((child.dword1 >> 12) & 0xfff) + 1) / 0x1000 * extent[0] + origin.x;
aabb.max.y = (float)((child.dword2 & 0xfff) + 1) / 0x1000 * extent[1] + origin.y;
aabb.max.z = (float)(((child.dword2 >> 12) & 0xfff) + 1) / 0x1000 * extent[2] + origin.z;
return aabb;
}
struct radv_bvh8_primitive_node_info {
uint32_t geometry_index_base_bits_div_2;
uint32_t geometry_index_bits_div_2;
uint32_t triangle_pair_count_minus_one;
uint32_t primitive_index_base_bits;
uint32_t primitive_index_bits;
uint32_t indices_midpoint;
uint32_t vertex_bits_minus_one[3];
uint32_t trailing_zero_bits;
rti_triangle triangles[16];
uint32_t procedural_mask = 0;
radv_bvh8_primitive_node_info(const BITSET_WORD *node)
{
geometry_index_base_bits_div_2 = BITSET_EXTRACT(node, 20, 4);
geometry_index_bits_div_2 = BITSET_EXTRACT(node, 24, 4);
triangle_pair_count_minus_one = BITSET_EXTRACT(node, 28, 3);
primitive_index_base_bits = BITSET_EXTRACT(node, 32, 5);
primitive_index_bits = BITSET_EXTRACT(node, 37, 5);
indices_midpoint = BITSET_EXTRACT(node, 42, 10);
vertex_bits_minus_one[0] = BITSET_EXTRACT(node, 0, 5);
vertex_bits_minus_one[1] = BITSET_EXTRACT(node, 5, 5);
vertex_bits_minus_one[2] = BITSET_EXTRACT(node, 10, 5);
trailing_zero_bits = BITSET_EXTRACT(node, 15, 5);
uint32_t geometry_id_base = BITSET_EXTRACT(node, indices_midpoint - geometry_index_base_bits_div_2 * 2,
geometry_index_base_bits_div_2 * 2);
uint32_t primitive_id_base = BITSET_EXTRACT(node, indices_midpoint, primitive_index_base_bits);
for (uint32_t i = 0; i < (triangle_pair_count_minus_one + 1) * 2; i++) {
uint32_t pair_offset = 1024 - (i / 2 + 1) * RADV_GFX12_PRIMITIVE_NODE_PAIR_DESC_SIZE;
uint32_t vertex_indices_offset = pair_offset + ((i % 2) == 0 ? 17 : 3);
uint32_t vertex_indices[] = {
BITSET_EXTRACT(node, vertex_indices_offset + 0, 4),
BITSET_EXTRACT(node, vertex_indices_offset + 4, 4),
BITSET_EXTRACT(node, vertex_indices_offset + 8, 4),
};
if (vertex_indices[0] == 0 && vertex_indices[1] == 0 && vertex_indices[2] == 0) {
triangles[i].primitive_index = RTI_PRIMITIVE_INDEX_INACTIVE;
continue;
}
bool is_procedural = vertex_indices[0] == 0xf && vertex_indices[1] == 0xf;
if (is_procedural)
procedural_mask |= BITFIELD_BIT(i);
uint32_t payload_size[3] = {
vertex_bits_minus_one[0] + 1,
vertex_bits_minus_one[1] + 1,
vertex_bits_minus_one[2] + 1,
};
uint32_t total_payload_size = payload_size[0] + payload_size[1] + payload_size[2];
uint32_t prefix_size[3] = {
32 - trailing_zero_bits - payload_size[0],
32 - trailing_zero_bits - payload_size[1],
32 - trailing_zero_bits - payload_size[2],
};
uint32_t total_prefix_size = prefix_size[0] + prefix_size[1] + prefix_size[2];
uint32_t prefix[3] = {
BITSET_EXTRACT(node, RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE, prefix_size[0]) << (32 - prefix_size[0]),
BITSET_EXTRACT(node, RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE + prefix_size[0], prefix_size[1])
<< (32 - prefix_size[1]),
BITSET_EXTRACT(node, RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE + prefix_size[0] + prefix_size[1], prefix_size[2])
<< (32 - prefix_size[2]),
};
for (uint32_t j = 0; j < (is_procedural ? 0 : 3); j++) {
uint32_t payload[3] = {
BITSET_EXTRACT(
node,
RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE + total_prefix_size + total_payload_size * vertex_indices[j],
payload_size[0])
<< trailing_zero_bits,
BITSET_EXTRACT(node,
RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE + total_prefix_size +
total_payload_size * vertex_indices[j] + payload_size[0],
payload_size[1])
<< trailing_zero_bits,
BITSET_EXTRACT(node,
RADV_GFX12_PRIMITIVE_NODE_HEADER_SIZE + total_prefix_size +
total_payload_size * vertex_indices[j] + payload_size[0] + payload_size[1],
payload_size[2])
<< trailing_zero_bits,
};
triangles[i].vertices[j].x = uif(prefix[0] + payload[0]);
triangles[i].vertices[j].y = uif(prefix[1] + payload[1]);
triangles[i].vertices[j].z = uif(prefix[2] + payload[2]);
}
uint32_t geometry_index = geometry_id_base;
uint32_t primitive_index = primitive_id_base;
if (i) {
geometry_index =
(geometry_id_base & ~BITFIELD64_MASK(geometry_index_bits_div_2 * 2)) |
BITSET_EXTRACT(node,
indices_midpoint - geometry_index_base_bits_div_2 * 2 - geometry_index_bits_div_2 * 2 * i,
geometry_index_bits_div_2 * 2);
primitive_index =
(primitive_id_base & ~BITFIELD64_MASK(primitive_index_bits)) |
BITSET_EXTRACT(node, indices_midpoint + primitive_index_base_bits + primitive_index_bits * (i - 1),
primitive_index_bits);
};
triangles[i].geometry_index = geometry_index;
triangles[i].primitive_index = primitive_index;
}
}
};
static rti_mat4
radv_bvh8_instance_node_get_transform(const radv_gfx12_instance_node *node)
{
rti_mat4 inv_transform = mat3x4_to_rti(node->wto_matrix);
rti_mat4 transform;
util_invert_mat4x4(transform.elements, inv_transform.elements);
return transform;
}
static rti_acceleration_structure_radv *
radv_bvh8_instance_node_get_blas(rti_file_view *view, const radv_gfx12_instance_node *node)
{
return (rti_acceleration_structure_radv *)view->addr_to_acceleration_structure(
radv_node_to_addr(node->pointer_flags_bvh_addr));
}
void
rti_file_view_radv::bvh8_traverse(rti_acceleration_structure_radv *acceleration_structure, rti_ray ray, uint32_t id,
float *tmax, const std::unordered_set<uint32_t> &path, bool select)
{
uint32_t offset = (id & (~0xf)) << 3;
std::unordered_set<uint32_t> path_copy = path;
if (select)
path_copy.insert(id);
const radv_gfx12_box_node *node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + offset);
radv_bvh8_box_node_iterator iterator = node;
for (uint32_t child_id = iterator.next(); child_id != RADV_BVH_INVALID_NODE; child_id = iterator.next()) {
uint32_t child_offset = (child_id & (~0xf)) << 3;
uint32_t child_type = child_id & 0xf;
rti_aabb aabb =
radv_bvh8_box_child_get_aabb(node->children[iterator.index], node->origin, node->child_count_exponents);
float t = rti_aabb::intersect_ray(aabb, ray);
if (t >= *tmax)
continue;
if (child_type == radv_bvh_node_box32) {
bvh8_traverse(acceleration_structure, ray, child_id, tmax, path_copy, select);
} else if (child_type != radv_bvh_node_instance) {
const BITSET_WORD *node = (const BITSET_WORD *)(acceleration_structure->bvh + child_offset);
radv_bvh8_primitive_node_info info(node);
for (uint32_t i = 0; i < (info.triangle_pair_count_minus_one + 1) * 2; i++) {
rti_triangle triangle = info.triangles[i];
if (triangle.primitive_index == RTI_PRIMITIVE_INDEX_INACTIVE)
continue;
if (!(info.procedural_mask & BITFIELD_BIT(i)))
t = rti_triangle::intersect_ray(triangle, ray);
if (t < *tmax) {
*tmax = t;
if (select) {
acceleration_structure->radv_ui.selected_node_id = child_id;
acceleration_structure->radv_ui.open_path = path_copy;
acceleration_structure->radv_ui.scroll_to_selected_node = true;
}
}
}
} else {
const radv_gfx12_instance_node *instance =
(const radv_gfx12_instance_node *)(acceleration_structure->bvh + child_offset);
rti_ray object_ray = rti_ray::transform(ray, mat3x4_to_rti(instance->wto_matrix));
rti_acceleration_structure_radv *blas = radv_bvh8_instance_node_get_blas(this, instance);
float blas_tmax = INFINITY;
std::unordered_set<uint32_t> blas_path;
bvh8_traverse(blas, object_ray, RADV_BVH_ROOT_NODE, &blas_tmax, blas_path, false);
if (blas_tmax < INFINITY) {
if (select) {
acceleration_structure->radv_ui.selected_node_id = child_id;
acceleration_structure->radv_ui.open_path = path_copy;
acceleration_structure->radv_ui.scroll_to_selected_node = true;
}
*tmax = blas_tmax;
}
}
}
}
rti_aabb
rti_file_view_radv::bvh8_scene_aabb(rti_acceleration_structure_radv *acceleration_structure)
{
rti_aabb aabb;
uint32_t root_offset = (RADV_BVH_ROOT_NODE & (~0xf)) << 3;
uint32_t root_type = RADV_BVH_ROOT_NODE & 0xf;
assert(root_type == radv_bvh_node_box32);
const radv_gfx12_box_node *root_node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + root_offset);
for (uint32_t i = 0; i <= root_node->child_count_exponents >> 28; i++) {
rti_aabb child_aabb =
radv_bvh8_box_child_get_aabb(root_node->children[i], root_node->origin, root_node->child_count_exponents);
if (i == 0) {
aabb = child_aabb;
} else {
aabb = rti_aabb::combine(aabb, child_aabb);
}
}
return aabb;
}
void
rti_file_view_radv::bvh8_get_vertices(rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_vertex *vertices, rti_vertex *wireframe_vertices)
{
uint32_t offset = (id & (~0xf)) << 3;
const radv_gfx12_box_node *box_node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + offset);
radv_bvh8_box_node_iterator iterator = box_node;
for (uint32_t child_id = iterator.next(); child_id != RADV_BVH_INVALID_NODE; child_id = iterator.next()) {
uint32_t child_offset = (child_id & (~0xf)) << 3;
uint32_t child_type = child_id & 0xf;
if (child_type == radv_bvh_node_box32) {
bvh8_get_vertices(acceleration_structure, child_id, vertices, wireframe_vertices);
} else if (child_type != radv_bvh_node_instance) {
const BITSET_WORD *node = (const BITSET_WORD *)(acceleration_structure->bvh + child_offset);
radv_bvh8_primitive_node_info info(node);
for (uint32_t i = 0; i < (info.triangle_pair_count_minus_one + 1) * 2; i++) {
rti_triangle triangle = info.triangles[i];
if (triangle.primitive_index == RTI_PRIMITIVE_INDEX_INACTIVE)
continue;
uint32_t dst_index = acceleration_structure->primitive_counts_exclusive_sum[triangle.geometry_index] +
triangle.primitive_index;
if (info.procedural_mask & BITFIELD_BIT(i)) {
rti_aabb aabb = radv_bvh8_box_child_get_aabb(box_node->children[iterator.index], box_node->origin,
box_node->child_count_exponents);
rti_generate_cube_vertices(wireframe_vertices + dst_index * RTI_CUBE_VERTEX_COUNT, aabb);
rti_generate_filled_cube_vertices(vertices + dst_index * RTI_FILLED_CUBE_VERTEX_COUNT, aabb,
triangle.geometry_index, triangle.primitive_index);
continue;
}
vertices[dst_index * 3 + 0].position = triangle.vertices[0];
vertices[dst_index * 3 + 0].geometry_index = triangle.geometry_index;
vertices[dst_index * 3 + 0].primitive_index = triangle.primitive_index;
vertices[dst_index * 3 + 1].position = triangle.vertices[1];
vertices[dst_index * 3 + 1].geometry_index = triangle.geometry_index;
vertices[dst_index * 3 + 1].primitive_index = triangle.primitive_index;
vertices[dst_index * 3 + 2].position = triangle.vertices[2];
vertices[dst_index * 3 + 2].geometry_index = triangle.geometry_index;
vertices[dst_index * 3 + 2].primitive_index = triangle.primitive_index;
}
}
}
}
void
rti_file_view_radv::bvh8_get_instances(rti_acceleration_structure_radv *tlas, uint32_t id)
{
uint32_t offset = (id & (~0xf)) << 3;
uint32_t type = id & 0xf;
if (type == radv_bvh_node_box32) {
const radv_gfx12_box_node *node = (const radv_gfx12_box_node *)(tlas->bvh + offset);
radv_bvh8_box_node_iterator iterator = node;
for (uint32_t child_id = iterator.next(); child_id != RADV_BVH_INVALID_NODE; child_id = iterator.next())
bvh8_get_instances(tlas, child_id);
} else if (type == radv_bvh_node_instance) {
const radv_gfx12_instance_node *node = (const radv_gfx12_instance_node *)(tlas->bvh + offset);
const radv_gfx12_instance_node_user_data *user_data = (const radv_gfx12_instance_node_user_data *)(node + 1);
rti_acceleration_structure_radv *blas = radv_bvh8_instance_node_get_blas(this, node);
uint32_t primitive_vertex_count =
blas->header.geometry_type == VK_GEOMETRY_TYPE_TRIANGLES_KHR ? 3 : RTI_FILLED_CUBE_VERTEX_COUNT;
rti_render_task solid_task;
solid_task.vertex_buffer = blas->ui.vertex_buffer.get();
solid_task.first_vertex = blas->first_vertex;
solid_task.vertex_count = blas->primitive_count * primitive_vertex_count;
solid_task.params.transform = radv_bvh8_instance_node_get_transform(node);
solid_task.params.color = rti_index_to_color(user_data->instance_index);
solid_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
tlas->ui.instance_render_list->tasks.push_back(solid_task);
}
}
static const char *node_type_names[16] = {
"triangle0", "triangle1", "triangle2", "triangle3", "invalid4", "box", "instance", "invalid7",
"triangle4", "triangle5", "triangle6", "triangle7", "invalid12", "invalid13", "invalid14", "invalid15",
};
void
rti_file_view_radv::bvh8_draw(rti_acceleration_structure_radv *acceleration_structure, uint32_t id)
{
uint32_t offset = (id & (~0xf)) << 3;
uint32_t type = id & 0xf;
bool selected = acceleration_structure->radv_ui.selected_node_id == id;
ImGuiTreeNodeFlags node_flags =
ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (type != radv_bvh_node_box32)
node_flags |= ImGuiTreeNodeFlags_Leaf;
if (selected)
node_flags |= ImGuiTreeNodeFlags_Selected;
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (acceleration_structure->radv_ui.open_path.find(id) != acceleration_structure->radv_ui.open_path.end())
ImGui::SetNextItemOpen(true);
char tree_node_id[32];
sprintf(tree_node_id, "%x %x", type, offset);
bool open = ImGui::TreeNodeEx(tree_node_id, node_flags, "%s", node_type_names[type]);
if (ImGui::IsItemClicked())
acceleration_structure->radv_ui.selected_node_id = id;
ImGui::TableNextColumnText("0x%x", offset);
if (open) {
if (type == radv_bvh_node_box32) {
const radv_gfx12_box_node *node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + offset);
radv_bvh8_box_node_iterator iterator = node;
for (uint32_t child_id = iterator.next(); child_id != RADV_BVH_INVALID_NODE; child_id = iterator.next())
bvh8_draw(acceleration_structure, child_id);
}
ImGui::TreePop();
}
if (acceleration_structure->radv_ui.scroll_to_selected_node && selected) {
ImGui::SetScrollHereY();
acceleration_structure->radv_ui.scroll_to_selected_node = false;
}
}
static void
rti_amd_draw_box8_child_info(const char *table_id, radv_gfx12_box_child child, vec3 origin,
uint32_t child_count_exponents)
{
if (ImGui::BeginTable(table_id, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("cull_flags");
ImGui::TableNextColumnText("0x%x", child.dword0 >> 24);
ImGui::TableNextColumnText("cull_mask");
ImGui::TableNextColumnText("0x%x", child.dword1 >> 24);
ImGui::TableNextColumnText("type");
ImGui::TableNextColumnText("%s", node_type_names[(child.dword2 >> 24) & 0xF]);
ImGui::TableNextColumnText("stride_128b");
ImGui::TableNextColumnText("0x%x", child.dword1 >> 28);
struct rti_aabb aabb = radv_bvh8_box_child_get_aabb(child, origin, child_count_exponents);
ImGui::TableNextColumn();
bool min_open = ImGui::TreeNodeEx("min");
ImGui::TableNextColumnText("(0x%x, 0x%x, 0x%x)", (child.dword0 >> 0) & 0xfff, (child.dword0 >> 12) & 0xfff,
(child.dword1 >> 0) & 0xfff);
if (min_open) {
ImGui::TableNextColumn();
ImGui::TableNextColumnText("(%f, %f, %f)", aabb.min.x, aabb.min.y, aabb.min.z);
ImGui::TreePop();
}
ImGui::TableNextColumn();
bool max_open = ImGui::TreeNodeEx("max");
ImGui::TableNextColumnText("(0x%x, 0x%x, 0x%x)", (child.dword1 >> 12) & 0xfff, (child.dword2 >> 0) & 0xfff,
(child.dword2 >> 12) & 0xfff);
if (max_open) {
ImGui::TableNextColumn();
ImGui::TableNextColumnText("(%f, %f, %f)", aabb.max.x, aabb.max.y, aabb.max.z);
ImGui::TreePop();
}
ImGui::EndTable();
}
}
void
rti_file_view_radv::bvh8_draw_node_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id)
{
uint32_t offset = (acceleration_structure->radv_ui.selected_node_id & (~0xf)) << 3;
uint32_t type = acceleration_structure->radv_ui.selected_node_id & 0xf;
if (type == radv_bvh_node_box32) {
const radv_gfx12_box_node *node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + offset);
char tmp[64];
sprintf(tmp, "box8 node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("box8 node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("internal_base_id");
ImGui::TableNextColumnText("0x%x", node->internal_base_id);
ImGui::TableNextColumnText("primitive_base_id");
ImGui::TableNextColumnText("0x%x", node->primitive_base_id);
ImGui::TableNextColumnText("origin");
ImGui::TableNextColumnText("(%f, %f, %f)", node->origin.x, node->origin.y, node->origin.z);
ImGui::TableNextColumnText("child_count - 1");
ImGui::TableNextColumnText("%u", node->child_count_exponents >> 28);
ImGui::TableNextColumnText("exponents");
ImGui::TableNextColumnText("(0x%x, 0x%x, 0x%x)", (node->child_count_exponents >> 0) & 0xFF,
(node->child_count_exponents >> 8) & 0xFF,
(node->child_count_exponents >> 16) & 0xFF);
ImGui::TableNextColumnText("obb_matrix_index");
ImGui::TableNextColumnText("0x%x", node->obb_matrix_index);
ImGui::EndTable();
}
for (uint32_t i = 0; i <= node->child_count_exponents >> 28; i++) {
sprintf(tmp, "child[%u]", i);
ImGui::SeparatorText(tmp);
sprintf(tmp, "child[%u] properties", i);
rti_amd_draw_box8_child_info(tmp, node->children[i], node->origin, node->child_count_exponents);
}
} else if (type == radv_bvh_node_instance) {
const radv_gfx12_instance_node *node = (const radv_gfx12_instance_node *)(acceleration_structure->bvh + offset);
char tmp[64];
sprintf(tmp, "instance node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("instance node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
for (uint32_t i = 0; i < 3; i++) {
ImGui::TableNextColumnText("wto_matrix.row[%u]", i);
ImGui::TableNextColumnText("(%f, %f, %f, %f)", node->wto_matrix.values[i][0], node->wto_matrix.values[i][1],
node->wto_matrix.values[i][2], node->wto_matrix.values[i][3]);
}
ImGui::TableNextColumnText("pointer_flags_bvh_addr");
ImGui::TableNextColumn();
sprintf(tmp, "0x%" PRIx64, node->pointer_flags_bvh_addr);
if (ImGui::TextLink(tmp)) {
rti_acceleration_structure_radv *blas = radv_bvh8_instance_node_get_blas(this, node);
if (blas->ui.opened)
blas->ui.request_focus = true;
blas->ui.opened = true;
}
ImGui::TableNextColumnText("pointer_flags");
ImGui::TableNextColumnText(
"%s%s%s%s%s%s", (node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_FORCE_OPAQUE) ? "force_opaque " : "",
(node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_FORCE_NON_OPAQUE) ? "force_non_opaque " : "",
(node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_DISABLE_TRI_CULL) ? "disable_tri_cull " : "",
(node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_FLIP_FACING) ? "flip_facing " : "",
(node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_SKIP_TRIANGLES) ? "skip_triangles " : "",
(node->pointer_flags_bvh_addr & RADV_BLAS_POINTER_SKIP_AABBS) ? "skip_aabbs " : "");
ImGui::TableNextColumnText("cull_mask");
ImGui::TableNextColumnText("0x%x", node->cull_mask_user_data >> 24);
ImGui::TableNextColumnText("user_data");
ImGui::TableNextColumnText("0x%x", node->cull_mask_user_data & 0xFFFFFF);
ImGui::TableNextColumnText("origin");
ImGui::TableNextColumnText("(%f, %f, %f)", node->origin.x, node->origin.y, node->origin.z);
ImGui::TableNextColumnText("child_count - 1");
ImGui::TableNextColumnText("%u", node->child_count_exponents >> 28);
ImGui::TableNextColumnText("exponents");
ImGui::TableNextColumnText("(0x%x, 0x%x, 0x%x)", (node->child_count_exponents >> 0) & 0xFF,
(node->child_count_exponents >> 8) & 0xFF,
(node->child_count_exponents >> 16) & 0xFF);
ImGui::EndTable();
}
for (uint32_t i = 0; i <= node->child_count_exponents >> 28; i++) {
sprintf(tmp, "child[%u]", i);
ImGui::SeparatorText(tmp);
sprintf(tmp, "child[%u] properties", i);
rti_amd_draw_box8_child_info(tmp, node->children[i], node->origin, node->child_count_exponents);
}
} else {
const BITSET_WORD *node = (const BITSET_WORD *)(acceleration_structure->bvh + offset);
radv_bvh8_primitive_node_info info(node);
char tmp[64];
sprintf(tmp, "primitive node (0x%x)", offset);
ImGui::SeparatorText(tmp);
if (ImGui::BeginTable("primitive node properties", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("vertex_bits - 1");
ImGui::TableNextColumnText("(%u, %u, %u)", info.vertex_bits_minus_one[0], info.vertex_bits_minus_one[1],
info.vertex_bits_minus_one[2]);
ImGui::TableNextColumnText("trailing_zero_bits");
ImGui::TableNextColumnText("%u", info.trailing_zero_bits);
ImGui::TableNextColumnText("geometry_index_base_bits / 2");
ImGui::TableNextColumnText("%u", info.geometry_index_base_bits_div_2);
ImGui::TableNextColumnText("geometry_index_bits / 2");
ImGui::TableNextColumnText("%u", info.geometry_index_bits_div_2);
ImGui::TableNextColumnText("triangle_pair_count - 1");
ImGui::TableNextColumnText("%u", info.triangle_pair_count_minus_one);
ImGui::TableNextColumnText("primitive_index_base_bits");
ImGui::TableNextColumnText("%u", info.primitive_index_base_bits);
ImGui::TableNextColumnText("primitive_index_bits");
ImGui::TableNextColumnText("%u", info.primitive_index_bits);
ImGui::EndTable();
}
for (uint32_t i = 0; i <= info.triangle_pair_count_minus_one; i++) {
sprintf(tmp, "pair_desc[%u]", i);
ImGui::SeparatorText(tmp);
uint32_t pair_offset = 1024 - (i + 1) * RADV_GFX12_PRIMITIVE_NODE_PAIR_DESC_SIZE;
sprintf(tmp, "pair_desc[%u] properties", i);
if (ImGui::BeginTable(tmp, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableNextColumnText("flags");
ImGui::TableNextColumnText("%s%s%s%s%s", BITSET_EXTRACT(node, pair_offset + 0, 1) ? "prim_range_stop " : "",
BITSET_EXTRACT(node, pair_offset + 15, 1) ? "tri0_double_sided " : "",
BITSET_EXTRACT(node, pair_offset + 16, 1) ? "tri0_opaque " : "",
BITSET_EXTRACT(node, pair_offset + 1, 1) ? "tri1_double_sided " : "",
BITSET_EXTRACT(node, pair_offset + 2, 1) ? "tri1_opaque " : "");
for (uint32_t j = 0; j < 2; j++) {
uint32_t triangle_index = i * 2 + j;
uint32_t vertex_indices_offset = pair_offset + (triangle_index == 0 ? 17 : 3);
sprintf(tmp, "tri%u vertices", j);
ImGui::TableNextColumn();
bool vertices_open = ImGui::TreeNodeEx(tmp);
uint32_t vertex_indices[] = {
BITSET_EXTRACT(node, vertex_indices_offset + 0, 4),
BITSET_EXTRACT(node, vertex_indices_offset + 4, 4),
BITSET_EXTRACT(node, vertex_indices_offset + 8, 4),
};
ImGui::TableNextColumnText("(%u, %u, %u)", vertex_indices[0], vertex_indices[1], vertex_indices[2]);
if (vertices_open) {
for (uint32_t k = 0; k < 3; k++) {
ImGui::TableNextColumn();
ImGui::TableNextColumnText("(%f, %f, %f)", info.triangles[triangle_index].vertices[k].x,
info.triangles[triangle_index].vertices[k].y,
info.triangles[triangle_index].vertices[k].z);
}
ImGui::TreePop();
}
ImGui::TableNextColumnText("tri%u geometry_index", j);
ImGui::TableNextColumnText("0x%x", info.triangles[triangle_index].geometry_index);
ImGui::TableNextColumnText("tri%u primitive_index", j);
ImGui::TableNextColumnText("0x%x", info.triangles[triangle_index].primitive_index);
}
ImGui::EndTable();
}
}
}
}
void
rti_file_view_radv::bvh8_render_selection(rti_acceleration_structure_radv *acceleration_structure)
{
rti_render_task selection_task;
selection_task.type = rti_render_task_type_thick_wireframe;
selection_task.vertex_buffer = acceleration_structure->ui.vertex_buffer.get();
selection_task.vertex_count = 3;
selection_task.params.color = app->selection_color;
selection_task.flags = RTI_RENDERER_COLOR_PUSH_CONSTANT;
selection_task.wait_for_prev = true;
if (acceleration_structure->wireframe_first_vertex != acceleration_structure->first_vertex) {
selection_task.type = rti_render_task_type_thick_lines;
selection_task.vertex_count = RTI_CUBE_VERTEX_COUNT;
}
uint32_t offset = (acceleration_structure->radv_ui.selected_node_id & (~0xf)) << 3;
uint32_t type = acceleration_structure->radv_ui.selected_node_id & 0xf;
if (type == radv_bvh_node_box32) {
const radv_gfx12_box_node *node = (const radv_gfx12_box_node *)(acceleration_structure->bvh + offset);
radv_bvh8_box_node_iterator iterator = node;
for (uint32_t child_id = iterator.next(); child_id != RADV_BVH_INVALID_NODE; child_id = iterator.next()) {
render_aabb(selection_task, radv_bvh8_box_child_get_aabb(node->children[iterator.index], node->origin,
node->child_count_exponents));
selection_task.wait_for_prev = false;
}
} else if (type != radv_bvh_node_instance) {
const BITSET_WORD *node = (const BITSET_WORD *)(acceleration_structure->bvh + offset);
radv_bvh8_primitive_node_info info(node);
for (uint32_t i = 0; i < (info.triangle_pair_count_minus_one + 1) * 2; i++) {
rti_triangle triangle = info.triangles[i];
if (triangle.primitive_index == RTI_PRIMITIVE_INDEX_INACTIVE)
continue;
uint32_t index =
acceleration_structure->primitive_counts_exclusive_sum[triangle.geometry_index] + triangle.primitive_index;
if (info.procedural_mask & BITFIELD_BIT(i))
selection_task.first_vertex = index * RTI_CUBE_VERTEX_COUNT;
else
selection_task.first_vertex = index * 3;
selection_task.first_vertex += acceleration_structure->wireframe_first_vertex;
render(selection_task);
selection_task.wait_for_prev = false;
}
} else {
const radv_gfx12_instance_node *node = (const radv_gfx12_instance_node *)(acceleration_structure->bvh + offset);
selection_task.params.transform = radv_bvh8_instance_node_get_transform(node);
render_aabb(selection_task, radv_bvh8_instance_node_get_blas(this, node)->aabb);
}
}
void
rti_file_view_radv::bvh8_get_instance_info(const rti_acceleration_structure_radv *acceleration_structure, uint32_t id,
rti_mat4 *transform, rti_acceleration_structure_radv **blas)
{
uint32_t offset = (id & (~0xf)) << 3;
const radv_gfx12_instance_node *node = (const radv_gfx12_instance_node *)(acceleration_structure->bvh + offset);
*blas = radv_bvh8_instance_node_get_blas(this, node);
*transform = mat3x4_to_rti(node->wto_matrix);
}

View file

@ -0,0 +1,791 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "rti_app.h"
#include "rti_file_view.h"
#include "rti_util.h"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
#include "shaders/rti_shader_interface.h"
#include "util/os_time.h"
#include "vulkan/vulkan_core.h"
#include <SDL3/SDL_dialog.h>
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_vulkan.h>
#include "backends/imgui_impl_sdl3.h"
#include "imgui.h"
#include "imgui_internal.h"
static const uint32_t renderer_vert_spv[] = {
#include "shaders/renderer_vert.spv.h"
};
static const uint32_t renderer_frag_spv[] = {
#include "shaders/renderer_frag.spv.h"
};
#include "util/macros.h"
int
rti_app_init(rti_app *app)
{
int64_t start_time = os_time_get_nano();
SDL_Init(SDL_INIT_VIDEO);
app->window = SDL_CreateWindow("Ray tracing inspector", 720, 400,
SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED);
if (app->window == NULL) {
fprintf(stderr, "rti: Failed to create window: %s\n", SDL_GetError());
rti_app_finish(app);
return 1;
}
uint32_t sdl_extensions_count = 0;
const char *const *sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extensions_count);
uint32_t api_version = VK_API_VERSION_1_0;
vkEnumerateInstanceVersion(&api_version);
if (api_version < VK_API_VERSION_1_4) {
fprintf(stderr, "rti: VK_API_VERSION_1_4 is required but not supported.\n");
rti_app_finish(app);
return 1;
}
uint32_t layer_property_count = 0;
vkEnumerateInstanceLayerProperties(&layer_property_count, nullptr);
std::vector<VkLayerProperties> layer_properties(layer_property_count);
vkEnumerateInstanceLayerProperties(&layer_property_count, layer_properties.data());
bool has_validation = false;
for (const VkLayerProperties &properties : layer_properties) {
if (!strcmp("VK_LAYER_KHRONOS_validation", properties.layerName)) {
has_validation = true;
break;
}
}
if (has_validation)
fprintf(stderr, "rti: Enabling VK_LAYER_KHRONOS_validation.\n");
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "mesa.rti",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "mesa.rti",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = api_version,
};
const char *layers[] = {"VK_LAYER_KHRONOS_validation"};
VkInstanceCreateInfo instance_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app_info,
.enabledLayerCount = (uint32_t)(has_validation ? ARRAY_SIZE(layers) : 0),
.ppEnabledLayerNames = layers,
.enabledExtensionCount = sdl_extensions_count,
.ppEnabledExtensionNames = sdl_extensions,
};
rti_check_vk_result(vkCreateInstance(&instance_info, nullptr, &app->instance));
VkSurfaceKHR surface;
if (!SDL_Vulkan_CreateSurface(app->window, app->instance, nullptr, &surface)) {
fprintf(stderr, "rti: Failed to create VkSurfaceKHR: %s\n", SDL_GetError());
rti_app_finish(app);
return 1;
}
uint32_t physical_device_count = 0;
rti_check_vk_result(vkEnumeratePhysicalDevices(app->instance, &physical_device_count, nullptr));
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
rti_check_vk_result(vkEnumeratePhysicalDevices(app->instance, &physical_device_count, physical_devices.data()));
for (VkPhysicalDevice pdev : physical_devices) {
uint32_t extension_property_count = 0;
vkEnumerateDeviceExtensionProperties(pdev, nullptr, &extension_property_count, nullptr);
std::vector<VkExtensionProperties> extension_properties(extension_property_count);
vkEnumerateDeviceExtensionProperties(pdev, nullptr, &extension_property_count, extension_properties.data());
bool has_multi_draw = false;
for (const VkExtensionProperties &properties : extension_properties) {
if (!strcmp(VK_EXT_MULTI_DRAW_EXTENSION_NAME, properties.extensionName)) {
has_multi_draw = true;
break;
}
}
if (!has_multi_draw)
continue;
VkPhysicalDeviceMultiDrawFeaturesEXT multi_draw_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT,
};
VkPhysicalDeviceVulkan11Features features11 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.pNext = &multi_draw_features,
};
VkPhysicalDeviceVulkan12Features features12 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = &features11,
};
VkPhysicalDeviceVulkan13Features features13 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = &features12,
};
VkPhysicalDeviceVulkan14Features features14 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES,
.pNext = &features13,
};
VkPhysicalDeviceFeatures2 features2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &features14,
};
vkGetPhysicalDeviceFeatures2(pdev, &features2);
if (!features2.features.fillModeNonSolid || !features2.features.wideLines || !features11.shaderDrawParameters ||
!features12.scalarBlockLayout || !features13.dynamicRendering || !features14.maintenance5 ||
!multi_draw_features.multiDraw)
continue;
uint32_t queue_family_property_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_property_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_property_count);
vkGetPhysicalDeviceQueueFamilyProperties(pdev, &queue_family_property_count, queue_family_properties.data());
for (uint32_t i = 0; i < queue_family_property_count; i++) {
if (queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
app->queue_family_index = i;
break;
}
}
app->pdev = pdev;
break;
}
if (!app->pdev) {
fprintf(stderr, "rti: Failed to find suitable VkPhysicalDevice\n");
rti_app_finish(app);
return 1;
}
VkPhysicalDeviceMultiDrawPropertiesEXT multi_draw_properties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_PROPERTIES_EXT,
};
VkPhysicalDeviceProperties2 properties2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = &multi_draw_properties,
};
vkGetPhysicalDeviceProperties2(app->pdev, &properties2);
VkSampleCountFlags sample_counts = properties2.properties.limits.framebufferColorSampleCounts &
properties2.properties.limits.framebufferDepthSampleCounts;
app->sample_count = 1;
if (sample_counts & VK_SAMPLE_COUNT_16_BIT)
app->sample_count = 16;
else if (sample_counts & VK_SAMPLE_COUNT_8_BIT)
app->sample_count = 8;
else if (sample_counts & VK_SAMPLE_COUNT_4_BIT)
app->sample_count = 4;
else if (sample_counts & VK_SAMPLE_COUNT_2_BIT)
app->sample_count = 2;
fprintf(stderr, "rti: Using GPU: %s\n", properties2.properties.deviceName);
float thick_line_width = fmin(3.0, properties2.properties.limits.lineWidthRange[1]);
app->max_multi_draw_count = multi_draw_properties.maxMultiDrawCount;
float priority = 1.0;
VkDeviceQueueCreateInfo queue_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = app->queue_family_index,
.queueCount = 1,
.pQueuePriorities = &priority,
};
const char *extensions[] = {VK_EXT_MULTI_DRAW_EXTENSION_NAME, VK_KHR_SWAPCHAIN_EXTENSION_NAME};
VkPhysicalDeviceFeatures features = {
.fillModeNonSolid = true,
.wideLines = true,
};
VkPhysicalDeviceVulkan11Features features11 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.shaderDrawParameters = true,
};
VkPhysicalDeviceVulkan12Features features12 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = &features11,
.scalarBlockLayout = true,
};
VkPhysicalDeviceVulkan13Features features13 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = &features12,
.dynamicRendering = true,
};
VkPhysicalDeviceVulkan14Features features14 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES,
.pNext = &features13,
.maintenance5 = true,
};
VkPhysicalDeviceMultiDrawFeaturesEXT multi_draw_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTI_DRAW_FEATURES_EXT,
.pNext = &features14,
.multiDraw = true,
};
VkDeviceCreateInfo device_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &multi_draw_features,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info,
.enabledExtensionCount = ARRAY_SIZE(extensions),
.ppEnabledExtensionNames = extensions,
.pEnabledFeatures = &features,
};
rti_check_vk_result(vkCreateDevice(app->pdev, &device_info, nullptr, &app->device));
vkGetDeviceQueue(app->device, app->queue_family_index, 0, &app->queue);
app->vkCmdDrawMultiEXT = (PFN_vkCmdDrawMultiEXT)vkGetDeviceProcAddr(app->device, "vkCmdDrawMultiEXT");
VkDescriptorPoolSize pool_sizes[] = {
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE + RTI_MAX_OPEN_VIEWS},
{VK_DESCRIPTOR_TYPE_SAMPLER, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE},
};
VkDescriptorPoolCreateInfo pool_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE + IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE +
RTI_MAX_OPEN_VIEWS,
.poolSizeCount = ARRAY_SIZE(pool_sizes),
.pPoolSizes = pool_sizes,
};
VkDescriptorPool descriptor_pool;
rti_check_vk_result(vkCreateDescriptorPool(app->device, &pool_info, nullptr, &descriptor_pool));
VkFormat request_formats[] = {VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM,
VK_FORMAT_R8G8B8_UNORM};
VkColorSpaceKHR color_space = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
VkClearColorValue clear_color_value = {
.float32 = {1, 1, 1, 1},
};
VkClearValue clear_value = {
.color = clear_color_value,
};
app->imgui_window.ClearValue = clear_value;
app->imgui_window.Surface = surface;
app->imgui_window.SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(app->pdev, surface, request_formats,
ARRAY_SIZE(request_formats), color_space);
VkPresentModeKHR present_modes[] = {VK_PRESENT_MODE_FIFO_KHR};
app->imgui_window.PresentMode =
ImGui_ImplVulkanH_SelectPresentMode(app->pdev, surface, present_modes, ARRAY_SIZE(present_modes));
int w, h;
SDL_GetWindowSize(app->window, &w, &h);
ImGui_ImplVulkanH_CreateOrResizeWindow(app->instance, app->pdev, app->device, &app->imgui_window,
app->queue_family_index, nullptr, w, h, 2, 0);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
// io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.Fonts->AddFontDefaultVector();
io.IniFilename = nullptr;
ImGui::StyleColorsLight();
ImGuiStyle *style = &ImGui::GetStyle();
style->WindowBorderSize = style->_MainScale;
style->FrameBorderSize = style->_MainScale;
style->PopupBorderSize = style->_MainScale;
style->WindowRounding = 8.0f;
style->ChildRounding = 8.0f;
style->FrameRounding = 8.0f;
style->PopupRounding = 8.0f;
style->GrabRounding = 8.0f;
style->ImageBorderSize = 0;
style->WindowPadding = ImVec2(8, 4);
style->ItemSpacing = ImVec2(8, 6);
style->DockingNodeHasCloseButton = false;
style->Colors[ImGuiCol_DockingEmptyBg] = ImVec4(1, 1, 1, 1);
ImGui_ImplSDL3_InitForVulkan(app->window);
ImGui_ImplVulkan_InitInfo init_info = {};
init_info.Instance = app->instance;
init_info.PhysicalDevice = app->pdev;
init_info.Device = app->device;
init_info.QueueFamily = app->queue_family_index;
init_info.Queue = app->queue;
init_info.PipelineCache = VK_NULL_HANDLE;
init_info.DescriptorPool = descriptor_pool;
init_info.MinImageCount = 2;
init_info.ImageCount = app->imgui_window.ImageCount;
init_info.Allocator = nullptr;
init_info.PipelineInfoMain.RenderPass = app->imgui_window.RenderPass;
init_info.PipelineInfoMain.Subpass = 0;
init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
init_info.CheckVkResultFn = nullptr;
ImGui_ImplVulkan_Init(&init_info);
VkCommandPoolCreateInfo command_pool_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.queueFamilyIndex = app->queue_family_index,
};
rti_check_vk_result(vkCreateCommandPool(app->device, &command_pool_info, nullptr, &app->staging_command_pool));
/* Start uploading data to the GPU before compiling pipelines to reduce startup time. */
app->cube_vertex_buffer =
rti_create_backed_buffer(app, RTI_CUBE_VERTEX_COUNT * sizeof(rti_vertex), rti_memory_type_device_local,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, false);
rti_vertex *cube_vertices = (rti_vertex *)app->upload_memory(app->cube_vertex_buffer);
rti_generate_cube_vertices(cube_vertices, {.min = {0, 0, 0}, .max = {1, 1, 1}});
app->filled_cube_vertex_buffer =
rti_create_backed_buffer(app, RTI_FILLED_CUBE_VERTEX_COUNT * sizeof(rti_vertex), rti_memory_type_device_local,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, false);
rti_vertex *filled_cube_vertices = (rti_vertex *)app->upload_memory(app->filled_cube_vertex_buffer);
rti_generate_filled_cube_vertices(filled_cube_vertices, {.min = {0, 0, 0}, .max = {1, 1, 1}}, 0, 0);
app->flush_upload_memory();
VkDescriptorSetLayoutBinding binding = {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
};
VkDescriptorSetLayoutCreateInfo set_layout_info = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &binding,
};
rti_check_vk_result(vkCreateDescriptorSetLayout(app->device, &set_layout_info, nullptr, &app->renderer_set_layout));
VkPushConstantRange push_constant_range = {
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
.size = sizeof(rti_push_constants),
};
VkPipelineLayoutCreateInfo pipeline_layout_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &app->renderer_set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &push_constant_range,
};
rti_check_vk_result(
vkCreatePipelineLayout(app->device, &pipeline_layout_info, nullptr, &app->renderer_pipeline_layout));
VkFormat color_attachment_format = VK_FORMAT_R8G8B8A8_UNORM;
VkPipelineRenderingCreateInfo rendering_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &color_attachment_format,
.depthAttachmentFormat = VK_FORMAT_D32_SFLOAT,
};
VkShaderModuleCreateInfo module_infos[2] = {
{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = sizeof(renderer_vert_spv),
.pCode = renderer_vert_spv,
},
{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = sizeof(renderer_frag_spv),
.pCode = renderer_frag_spv,
},
};
VkPipelineShaderStageCreateInfo stage_infos[2] = {
{.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = &module_infos[0],
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.pName = "main"},
{.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.pNext = &module_infos[1],
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.pName = "main"},
};
VkVertexInputBindingDescription vertex_binding_info = {
.binding = 0,
.stride = sizeof(rti_vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
VkVertexInputAttributeDescription vertex_attribute_infos[] = {
{
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(rti_vertex, position),
},
{
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32_UINT,
.offset = offsetof(rti_vertex, geometry_index),
},
{
.location = 2,
.binding = 0,
.format = VK_FORMAT_R32_UINT,
.offset = offsetof(rti_vertex, primitive_index),
},
};
VkPipelineVertexInputStateCreateInfo vertex_input_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &vertex_binding_info,
.vertexAttributeDescriptionCount = ARRAY_SIZE(vertex_attribute_infos),
.pVertexAttributeDescriptions = vertex_attribute_infos,
};
VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
};
VkPipelineViewportStateCreateInfo viewpoirt_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1,
};
VkPipelineRasterizationStateCreateInfo rasterization_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.lineWidth = 1,
};
VkPipelineMultisampleStateCreateInfo multisample_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = (VkSampleCountFlagBits)app->sample_count,
.minSampleShading = 1.0,
};
VkPipelineDepthStencilStateCreateInfo depth_stencil_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = true,
.depthWriteEnable = true,
.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL,
.minDepthBounds = 0.0,
.maxDepthBounds = 1.0,
};
VkPipelineColorBlendAttachmentState color_blend_attachment_info = {
.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo color_blend_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &color_blend_attachment_info,
};
VkDynamicState dynamic_states[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
VkPipelineDynamicStateCreateInfo dynamic_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = ARRAY_SIZE(dynamic_states),
.pDynamicStates = dynamic_states,
};
VkGraphicsPipelineCreateInfo graphics_pipeline_info = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &rendering_info,
.stageCount = ARRAY_SIZE(stage_infos),
.pStages = stage_infos,
.pVertexInputState = &vertex_input_info,
.pInputAssemblyState = &input_assembly_info,
.pViewportState = &viewpoirt_info,
.pRasterizationState = &rasterization_info,
.pMultisampleState = &multisample_info,
.pDepthStencilState = &depth_stencil_info,
.pColorBlendState = &color_blend_info,
.pDynamicState = &dynamic_info,
.layout = app->renderer_pipeline_layout,
};
rti_check_vk_result(
vkCreateGraphicsPipelines(app->device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr, &app->fill_pipeline));
rasterization_info.polygonMode = VK_POLYGON_MODE_LINE;
rti_check_vk_result(vkCreateGraphicsPipelines(app->device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr,
&app->wireframe_pipeline));
input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
rti_check_vk_result(vkCreateGraphicsPipelines(app->device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr,
&app->lines_pipeline));
rasterization_info.lineWidth = thick_line_width;
input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
rti_check_vk_result(vkCreateGraphicsPipelines(app->device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr,
&app->thick_wireframe_pipeline));
input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
rti_check_vk_result(vkCreateGraphicsPipelines(app->device, VK_NULL_HANDLE, 1, &graphics_pipeline_info, nullptr,
&app->thick_lines_pipeline));
app->finish_upload_memory();
int64_t end_time = os_time_get_nano();
fprintf(stderr, "rti: Creating window state took %.2fms\n", (end_time - start_time) / 1000000.0);
return 0;
}
void
rti_app_finish(rti_app *app)
{
vkDeviceWaitIdle(app->device);
vkDestroyPipeline(app->device, app->fill_pipeline, nullptr);
vkDestroyPipeline(app->device, app->wireframe_pipeline, nullptr);
vkDestroyPipelineLayout(app->device, app->renderer_pipeline_layout, nullptr);
vkDestroyDescriptorSetLayout(app->device, app->renderer_set_layout, nullptr);
vkDestroyCommandPool(app->device, app->staging_command_pool, nullptr);
/* Cleanup anything potentially using Vulkan first. */
app->open_files.clear();
ImGui_ImplVulkan_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
SDL_DestroyWindow(app->window);
SDL_Quit();
}
void
rti_app_resize(rti_app *app, uint32_t width, uint32_t height)
{
ImGui_ImplVulkan_SetMinImageCount(2);
ImGui_ImplVulkanH_CreateOrResizeWindow(app->instance, app->pdev, app->device, &app->imgui_window,
app->queue_family_index, nullptr, width, height, 2, 0);
}
static SDLCALL void
rti_open_file_cb(void *userdata, const char *const *filelist, int filter)
{
rti_app *app = (rti_app *)userdata;
if (!filelist)
return;
for (uint32_t i = 0; filelist[i]; i++) {
/* O(N^2) should be fine here. Focus the file instead of opening it again if it was already opened. */
bool already_opened = false;
for (const auto &view : app->open_files) {
if (view->path == filelist[i]) {
already_opened = true;
view->ui.request_focus = true;
break;
}
}
if (!already_opened)
app->open_files.push_back(rti_create_file_view(app, filelist[i]));
}
}
static const SDL_DialogFileFilter rti_file_filters[] = {
{"RTI file", "rti"},
};
bool
rti_app_run(rti_app *app)
{
if (!app->last_run_time_valid) {
app->last_run_time = std::chrono::high_resolution_clock::now();
app->last_run_time_valid = true;
}
std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now();
uint64_t dt_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now - app->last_run_time).count();
app->dt = dt_ns / 1000000000.0;
app->last_run_time = now;
rti_vec3 selection_colors[] = {
{228.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0}, {255.0 / 255.0, 140.0 / 255.0, 0.0 / 255.0},
{255.0 / 255.0, 237.0 / 255.0, 0.0 / 255.0}, {0.0 / 255.0, 128.0 / 255.0, 38.0 / 255.0},
{0.0 / 255.0, 76.0 / 255.0, 255.0 / 255.0}, {115.0 / 255.0, 41.0 / 255.0, 130.0 / 255.0},
};
float selection_color = app->t * 6.0;
float selection_color_fract = selection_color - (uint64_t)selection_color;
app->selection_color.x =
selection_colors[((uint64_t)selection_color + 0) % ARRAY_SIZE(selection_colors)].x * (1 - selection_color_fract) +
selection_colors[((uint64_t)selection_color + 1) % ARRAY_SIZE(selection_colors)].x * selection_color_fract;
app->selection_color.y =
selection_colors[((uint64_t)selection_color + 0) % ARRAY_SIZE(selection_colors)].y * (1 - selection_color_fract) +
selection_colors[((uint64_t)selection_color + 1) % ARRAY_SIZE(selection_colors)].y * selection_color_fract;
app->selection_color.z =
selection_colors[((uint64_t)selection_color + 0) % ARRAY_SIZE(selection_colors)].z * (1 - selection_color_fract) +
selection_colors[((uint64_t)selection_color + 1) % ARRAY_SIZE(selection_colors)].z * selection_color_fract;
ImGuiKeyChord exit_chord = ImGuiMod_Ctrl | ImGuiKey_Q;
if (ImGui::Shortcut(exit_chord, ImGuiInputFlags_RouteGlobal))
return true;
ImGuiKeyChord open_chord = ImGuiMod_Ctrl | ImGuiKey_O;
if (ImGui::Shortcut(open_chord, ImGuiInputFlags_RouteGlobal)) {
SDL_ShowOpenFileDialog(rti_open_file_cb, app, app->window, rti_file_filters, ARRAY_SIZE(rti_file_filters),
nullptr, false);
}
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", ImGui::GetKeyChordName(open_chord))) {
SDL_ShowOpenFileDialog(rti_open_file_cb, app, app->window, rti_file_filters, ARRAY_SIZE(rti_file_filters),
nullptr, false);
}
if (ImGui::MenuItem("Exit", ImGui::GetKeyChordName(exit_chord)))
return true;
ImGui::EndMenu();
}
uint32_t framerate = 0;
if (app->dt != 0)
framerate = (uint32_t)(1 / app->dt);
char tmp[32];
sprintf(tmp, "%u fps", framerate);
ImVec2 framerate_text_size = ImGui::CalcTextSize(tmp);
ImGui::SameLine(ImGui::GetWindowWidth() - framerate_text_size.x - 10);
ImGui::Text("%s", tmp);
ImGui::EndMainMenuBar();
}
ImGuiWindowClass top_level_class;
top_level_class.ClassId = ImHashStr("top_level_class");
top_level_class.DockingAllowUnclassed = false;
ImGuiID root_dockspace_id = ImGui::GetID("root_dockspace");
ImGui::DockSpaceOverViewport(root_dockspace_id, nullptr, ImGuiDockNodeFlags_NoWindowMenuButton, &top_level_class);
for (const auto &view : app->open_files) {
ImGui::SetNextWindowClass(&top_level_class);
ImGui::SetNextWindowDockID(root_dockspace_id, ImGuiCond_FirstUseEver);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::Begin(view->path.filename().c_str());
ImGui::PopStyleVar();
view->run();
ImGui::End();
}
app->t += app->dt;
return false;
}
void *
rti_app::upload_memory(std::shared_ptr<rti_backed_buffer> dst)
{
std::shared_ptr<rti_backed_buffer> staging_buffer =
rti_create_backed_buffer(this, dst->size, rti_memory_type_host_visible, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, true);
pending_staging_buffers.push_back({staging_buffer, dst});
return staging_buffer->map;
}
void
rti_app::flush_upload_memory()
{
if (pending_staging_buffers.empty())
return;
assert(!staging_command_buffer);
VkCommandBufferAllocateInfo command_buffer_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = staging_command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
rti_check_vk_result(vkAllocateCommandBuffers(device, &command_buffer_info, &staging_command_buffer));
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
};
rti_check_vk_result(vkBeginCommandBuffer(staging_command_buffer, &begin_info));
for (const auto &staging_buffer : pending_staging_buffers) {
VkBufferCopy copy = {
.size = staging_buffer.first->size,
};
vkCmdCopyBuffer(staging_command_buffer, staging_buffer.first->buffer, staging_buffer.second->buffer, 1, &copy);
}
rti_check_vk_result(vkEndCommandBuffer(staging_command_buffer));
VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &staging_command_buffer,
};
rti_check_vk_result(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
}
void
rti_app::finish_upload_memory()
{
if (!staging_command_buffer)
return;
rti_check_vk_result(vkQueueWaitIdle(queue));
vkFreeCommandBuffers(device, staging_command_pool, 1, &staging_command_buffer);
staging_command_buffer = VK_NULL_HANDLE;
pending_staging_buffers.clear();
}

View file

@ -0,0 +1,76 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <memory>
#include <utility>
#include "vulkan/vulkan_core.h"
#include <SDL3/SDL.h>
#include "backends/imgui_impl_vulkan.h"
#include "rti_file_view.h"
#include "rti_util.h"
#define RTI_MAX_OPEN_VIEWS 4096
#define RTI_CUBE_VERTEX_COUNT (2 * 4 * 2 + 4 * 2)
#define RTI_FILLED_CUBE_VERTEX_COUNT (3 * 2 * 6)
struct rti_app {
SDL_Window *window;
VkInstance instance;
VkPhysicalDevice pdev;
VkDevice device;
VkQueue queue;
uint32_t queue_family_index;
ImGui_ImplVulkanH_Window imgui_window;
std::vector<std::unique_ptr<rti_file_view>> open_files;
VkCommandBuffer command_buffer;
PFN_vkCmdDrawMultiEXT vkCmdDrawMultiEXT;
VkCommandPool staging_command_pool;
VkCommandBuffer staging_command_buffer = VK_NULL_HANDLE;
std::vector<std::pair<std::shared_ptr<rti_backed_buffer>, std::shared_ptr<rti_backed_buffer>>>
pending_staging_buffers;
VkDescriptorSetLayout renderer_set_layout;
VkPipelineLayout renderer_pipeline_layout;
VkPipeline fill_pipeline;
VkPipeline wireframe_pipeline;
VkPipeline thick_wireframe_pipeline;
VkPipeline lines_pipeline;
VkPipeline thick_lines_pipeline;
uint32_t sample_count;
uint32_t max_multi_draw_count;
std::shared_ptr<rti_backed_buffer> cube_vertex_buffer;
std::shared_ptr<rti_backed_buffer> filled_cube_vertex_buffer;
std::chrono::high_resolution_clock::time_point last_run_time;
bool last_run_time_valid = false;
float t = 0;
float dt;
rti_vec3 selection_color;
void *upload_memory(std::shared_ptr<rti_backed_buffer> dst);
void flush_upload_memory();
void finish_upload_memory();
};
int rti_app_init(rti_app *app);
void rti_app_finish(rti_app *app);
void rti_app_resize(rti_app *app, uint32_t width, uint32_t height);
bool rti_app_run(rti_app *app);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,259 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <map>
#include <memory>
#include <vector>
#include "imgui.h"
#include "rti_util.h"
#include "shaders/rti_shader_interface.h"
#include "util/rti_format.h"
#include "vulkan/vulkan_core.h"
struct rti_app;
enum rti_render_task_type {
rti_render_task_type_solid,
rti_render_task_type_wireframe,
rti_render_task_type_thick_wireframe,
rti_render_task_type_lines,
rti_render_task_type_thick_lines,
rti_render_task_type_render_list,
};
struct rti_render_list;
struct rti_render_task {
rti_render_task_type type = rti_render_task_type_solid;
union {
const rti_backed_buffer *vertex_buffer = nullptr;
rti_render_list *render_list;
};
uint32_t first_vertex = 0;
uint32_t vertex_count = 0;
rti_render_params params;
uint32_t flags = 0;
bool wait_for_prev = false;
bool override_flags = false;
};
struct rti_render_list {
rti_app *app;
std::vector<rti_render_task> tasks;
std::shared_ptr<rti_backed_buffer> constants = nullptr;
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
VkDescriptorSet descriptor_set = VK_NULL_HANDLE;
rti_render_list(rti_app *app): app(app)
{
}
~rti_render_list();
void build();
};
struct rti_imgui_texture {
VkDescriptorSet descriptor_set;
rti_imgui_texture(VkDescriptorSet descriptor_set): descriptor_set(descriptor_set)
{
}
~rti_imgui_texture();
};
struct rti_viewport_state {
std::shared_ptr<rti_imgui_texture> surface = nullptr;
std::shared_ptr<rti_backed_image> resolved_image = nullptr;
std::shared_ptr<rti_backed_image> cb = nullptr;
std::shared_ptr<rti_backed_image> db = nullptr;
uint32_t width = 0;
uint32_t height = 0;
std::shared_ptr<rti_render_list> render_list = nullptr;
};
enum rti_visualization_color {
rti_visualization_color_primitive_index,
rti_visualization_color_geometry_index,
rti_visualization_color_instance_index,
};
static inline uint32_t
rti_visualization_color_to_renderer_color(rti_visualization_color color)
{
switch (color) {
case rti_visualization_color_primitive_index:
return RTI_RENDERER_COLOR_PRIMITIVE_INDEX;
case rti_visualization_color_geometry_index:
return RTI_RENDERER_COLOR_GEOMETRY_INDEX;
case rti_visualization_color_instance_index:
return RTI_RENDERER_COLOR_PUSH_CONSTANT;
}
return RTI_RENDERER_COLOR_PRIMITIVE_INDEX;
}
static inline rti_vec3
rti_index_to_color(uint32_t index)
{
return {
.x = ((index * 0xd83f0930) >> 24) / 255.0f,
.y = ((index * 0x8fa9836b) >> 24) / 255.0f,
.z = ((index * 0x3037f8ad) >> 24) / 255.0f,
};
}
enum rti_camera_type {
rti_camera_type_first_person,
rti_camera_type_pivot,
};
struct rti_file_view;
struct rti_acceleration_structure {
rti_app *app = nullptr;
rti_file_view *view = nullptr;
rti_acceleration_structure_header header;
char *name = nullptr;
void *data = nullptr;
uint32_t *primitive_counts = nullptr;
uint32_t *primitive_counts_exclusive_sum = nullptr;
uint32_t primitive_count = 0;
rti_aabb aabb;
struct {
bool opened = false;
bool request_focus = false;
bool prev_focused = false;
rti_viewport_state viewport;
std::vector<rti_viewport_state> pending_viewports;
std::shared_ptr<rti_backed_buffer> vertex_buffer = nullptr;
std::shared_ptr<rti_render_list> instance_render_list = nullptr;
rti_camera_type camera_type = rti_camera_type_first_person;
float camera_phi = 0;
float camera_theta = 0;
rti_vec3 camera_pivot = {0, 0, 0};
float camera_pivot_distance = 0;
bool camera_pivot_valid = false;
rti_vec3 camera_position = {0, 0, 0};
float camera_speed = 0.3;
float camera_near = 0.0001;
float camera_far = 5.0;
float camera_fov = 60.0;
rti_visualization_color visualization_color = rti_visualization_color_primitive_index;
bool is_right_dragging_viewport;
bool is_middle_dragging_viewport;
ImVec2 prev_mouse_pos;
float middle_drag_start_z_distance;
} ui;
rti_acceleration_structure(rti_file_view *view);
virtual ~rti_acceleration_structure()
{
free(name);
free(data);
}
void init_camera();
void load(FILE *file, uint32_t chunk_size);
};
struct rti_file_view {
rti_app *app;
std::filesystem::path path;
std::vector<std::unique_ptr<rti_acceleration_structure>> acceleration_structures;
std::map<uint64_t, uint32_t> address_map;
uint32_t up_axis = 1 + 3; /* +x, +y, +z, -x, -y, -z */
struct {
ImGuiID dockspace_id;
ImGuiWindowClass dockspace_class;
bool request_focus = true;
bool initialized;
VkAccelerationStructureTypeKHR filter_acceleration_structure_type;
rti_acceleration_structure *focused_acceleration_structure = nullptr;
} ui;
struct {
rti_acceleration_structure *acceleration_structure;
rti_mat4 projection_matrix;
rti_mat4 inv_camera_rotation;
rti_mat4 view_projection_matrix;
ImVec2 viewport_offset;
ImVec2 viewport_size;
bool render;
rti_vec3 camera_pivot;
} rendering_state;
virtual void load(FILE *file, const rti_header *header);
virtual void load_driver_specific(FILE *file, const rti_chunk_header *chunk_header, const rti_header *header)
{
}
virtual std::unique_ptr<rti_acceleration_structure> create_acceleration_structure() = 0;
virtual void dock_driver_specific(uint32_t left_top, uint32_t left_bottom, uint32_t center, uint32_t right_top,
uint32_t right_bottom)
{
}
virtual float handle_mouse_click(rti_acceleration_structure *acceleration_structure, rti_ray ray, bool select)
{
return INFINITY;
}
virtual void run() = 0;
/* Helper functions for updating and drawing the UI */
void begin();
void end();
bool begin_viewport(rti_acceleration_structure *acceleration_structure);
void render(const rti_render_task &task);
void render_aabb(const rti_render_task &base_task, rti_aabb aabb, bool fill = false);
void render_viewport();
void draw_point(rti_vec3 pos, rti_vec3 color, float stroke);
void draw_line(rti_vec3 start, rti_vec3 end, rti_vec3 color, float stroke);
void end_viewport();
void begin_bvh_tree();
void end_bvh_tree();
void begin_node_info();
void end_node_info();
rti_acceleration_structure *addr_to_acceleration_structure(uint64_t address);
};
std::unique_ptr<rti_file_view> rti_create_file_view(rti_app *app, const char *file);
/* Create functions for rti_file_views specialized for drivers. */
std::unique_ptr<rti_file_view> rti_create_file_view_radv();

View file

@ -0,0 +1,428 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "rti_util.h"
#include "rti_app.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <vulkan/vulkan_core.h>
void
rti_check_vk_result_internal(VkResult result, const char *file, uint32_t line)
{
if (result != VK_SUCCESS) {
fprintf(stderr, "rti: VkResult(%i) at %s:%u\n", result, file, line);
exit(1);
}
}
static uint32_t
rti_find_memory_index(rti_app *app, VkMemoryPropertyFlags flags)
{
VkPhysicalDeviceMemoryProperties mem_properties;
vkGetPhysicalDeviceMemoryProperties(app->pdev, &mem_properties);
/* Try to find an exact match first. */
for (uint32_t i = 0; i < mem_properties.memoryTypeCount; ++i) {
if (mem_properties.memoryTypes[i].propertyFlags == flags)
return i;
}
for (uint32_t i = 0; i < mem_properties.memoryTypeCount; ++i) {
if ((mem_properties.memoryTypes[i].propertyFlags & flags) == flags)
return i;
}
fprintf(stderr, "rti: Unsupported memory properties\n");
return 0;
}
rti_backed_buffer::~rti_backed_buffer()
{
vkDestroyBuffer(app->device, buffer, NULL);
if (map) {
VkMemoryUnmapInfo unmap_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_UNMAP_INFO,
.memory = memory,
};
vkUnmapMemory2(app->device, &unmap_info);
}
vkFreeMemory(app->device, memory, NULL);
}
std::shared_ptr<rti_backed_buffer>
rti_create_backed_buffer(rti_app *app, uint64_t size, enum rti_memory_type memory_type, VkBufferUsageFlags usage,
bool map)
{
VkMemoryPropertyFlags memory_property_flags = 0;
switch (memory_type) {
case rti_memory_type_device_local:
memory_property_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
break;
case rti_memory_type_host_visible:
memory_property_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
break;
case rti_memory_type_host_visible_cached:
memory_property_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
break;
}
uint32_t memory_type_index = rti_find_memory_index(app, memory_property_flags);
std::shared_ptr<rti_backed_buffer> buffer = std::make_shared<rti_backed_buffer>();
buffer->app = app;
buffer->size = size;
VkBufferCreateInfo buffer_create_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = usage,
};
rti_check_vk_result(vkCreateBuffer(app->device, &buffer_create_info, NULL, &buffer->buffer));
VkDeviceBufferMemoryRequirements buffer_mem_req_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS,
.pCreateInfo = &buffer_create_info,
};
VkMemoryRequirements2 mem_reqs = {
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
};
vkGetDeviceBufferMemoryRequirements(app->device, &buffer_mem_req_info, &mem_reqs);
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = mem_reqs.memoryRequirements.size,
.memoryTypeIndex = memory_type_index,
};
rti_check_vk_result(vkAllocateMemory(app->device, &alloc_info, NULL, &buffer->memory));
VkBindBufferMemoryInfo bind_info = {
.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO,
.buffer = buffer->buffer,
.memory = buffer->memory,
};
rti_check_vk_result(vkBindBufferMemory2(app->device, 1, &bind_info));
if (map) {
VkMemoryMapInfo mem_map_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_MAP_INFO,
.memory = buffer->memory,
.size = VK_WHOLE_SIZE,
};
rti_check_vk_result(vkMapMemory2(app->device, &mem_map_info, &buffer->map));
}
return buffer;
}
rti_backed_image::~rti_backed_image()
{
vkDestroyImageView(app->device, image_view, nullptr);
vkDestroyImage(app->device, image, nullptr);
vkFreeMemory(app->device, memory, nullptr);
}
std::shared_ptr<rti_backed_image>
rti_create_backed_image(rti_app *app, VkExtent3D extent, const std::vector<VkFormat> &formats,
VkFormatFeatureFlags format_features, VkImageUsageFlags usage,
VkImageAspectFlagBits aspect_mask, uint32_t samples, uint32_t levels)
{
VkFormat format = formats[0];
for (VkFormat candidate_format : formats) {
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties(app->pdev, candidate_format, &format_properties);
if ((format_properties.optimalTilingFeatures & format_features) == format_features) {
format = candidate_format;
break;
}
}
std::shared_ptr<rti_backed_image> image = std::make_shared<rti_backed_image>();
image->app = app;
VkImageCreateInfo image_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = extent,
.mipLevels = levels,
.arrayLayers = 1,
.samples = (VkSampleCountFlagBits)samples,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &app->queue_family_index,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
rti_check_vk_result(vkCreateImage(app->device, &image_create_info, nullptr, &image->image));
VkDeviceImageMemoryRequirements memory_requirements_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS,
.pCreateInfo = &image_create_info,
.planeAspect = aspect_mask,
};
VkMemoryRequirements2 memory_requirements = {
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
};
vkGetDeviceImageMemoryRequirements(app->device, &memory_requirements_info, &memory_requirements);
uint32_t memory_type_index = rti_find_memory_index(app, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memory_requirements.memoryRequirements.size,
.memoryTypeIndex = memory_type_index,
};
rti_check_vk_result(vkAllocateMemory(app->device, &alloc_info, NULL, &image->memory));
rti_check_vk_result(vkBindImageMemory(app->device, image->image, image->memory, 0));
VkComponentMapping view_component_mapping = {
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
};
VkImageSubresourceRange subresource_range = {
.aspectMask = aspect_mask,
.levelCount = levels,
.layerCount = 1,
};
VkImageViewCreateInfo image_view_create_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = format,
.components = view_component_mapping,
.subresourceRange = subresource_range,
};
rti_check_vk_result(vkCreateImageView(app->device, &image_view_create_info, nullptr, &image->image_view));
return image;
}
void
rti_generate_cube_vertices(rti_vertex *vertices, rti_aabb aabb)
{
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.min.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.max.x, aabb.max.y, aabb.max.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.min.z}};
*(vertices++) = {.position = {aabb.min.x, aabb.max.y, aabb.max.z}};
}
void
rti_generate_filled_cube_vertices(rti_vertex *vertices, rti_aabb aabb, uint32_t geometry_index,
uint32_t primitive_index)
{
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.min.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.max.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.max.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
*(vertices++) = {
.position = {aabb.max.x, aabb.min.y, aabb.min.z},
.geometry_index = geometry_index,
.primitive_index = primitive_index,
};
}

View file

@ -0,0 +1,262 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <cmath>
#include <cstdarg>
#include <cstdint>
#include <imgui.h>
#include <memory>
#include <vector>
#include "util/macros.h"
#include <vulkan/vulkan_core.h>
void rti_check_vk_result_internal(VkResult result, const char *file, uint32_t line);
#define rti_check_vk_result(result) rti_check_vk_result_internal(result, __FILE__, __LINE__)
struct rti_app;
struct rti_backed_buffer {
rti_app *app;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkBuffer buffer = VK_NULL_HANDLE;
void *map = nullptr;
uint64_t size = 0;
~rti_backed_buffer();
};
enum rti_memory_type {
rti_memory_type_device_local,
rti_memory_type_host_visible,
rti_memory_type_host_visible_cached,
};
std::shared_ptr<rti_backed_buffer> rti_create_backed_buffer(rti_app *app, uint64_t size,
enum rti_memory_type memory_type, VkBufferUsageFlags usage,
bool map);
struct rti_backed_image {
rti_app *app = nullptr;
VkImage image = VK_NULL_HANDLE;
VkDeviceMemory memory = VK_NULL_HANDLE;
VkImageView image_view = VK_NULL_HANDLE;
~rti_backed_image();
};
std::shared_ptr<rti_backed_image> rti_create_backed_image(rti_app *app, VkExtent3D extent,
const std::vector<VkFormat> &formats,
VkFormatFeatureFlags format_features, VkImageUsageFlags usage,
VkImageAspectFlagBits aspect_mask, uint32_t samples,
uint32_t levels);
struct rti_vec2 {
float x = 0;
float y = 0;
};
struct rti_vec3 {
float x = 0;
float y = 0;
float z = 0;
static inline rti_vec3 sub(rti_vec3 a, rti_vec3 b)
{
return {
.x = a.x - b.x,
.y = a.y - b.y,
.z = a.z - b.z,
};
}
static inline rti_vec3 cross(rti_vec3 a, rti_vec3 b)
{
return {
.x = a.y * b.z - a.z * b.y,
.y = a.z * b.x - a.x * b.z,
.z = a.x * b.y - a.y * b.x,
};
}
static inline float dot(rti_vec3 a, rti_vec3 b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
};
struct rti_vec4 {
float x = 0;
float y = 0;
float z = 0;
float w = 0;
};
struct rti_mat4 {
float elements[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
static inline rti_mat4 zero()
{
return {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
}
static inline rti_mat4 mul(rti_mat4 a, rti_mat4 b)
{
rti_mat4 result = rti_mat4::zero();
for (uint32_t i = 0; i < 4; i++) {
for (uint32_t j = 0; j < 4; j++) {
for (uint32_t k = 0; k < 4; k++)
result.elements[i + j * 4] += a.elements[i + k * 4] * b.elements[k + j * 4];
}
}
return result;
}
static inline rti_mat4 transpose(rti_mat4 m)
{
rti_mat4 result;
for (uint32_t i = 0; i < 4; i++) {
for (uint32_t j = 0; j < 4; j++)
result.elements[i + j * 4] = m.elements[j + i * 4];
}
return result;
}
static inline rti_vec4 mul_vec4(rti_mat4 a, rti_vec4 v)
{
return (rti_vec4){
.x = a.elements[0 + 0 * 4] * v.x + a.elements[0 + 1 * 4] * v.y + a.elements[0 + 2 * 4] * v.z +
a.elements[0 + 3 * 4] * v.w,
.y = a.elements[1 + 0 * 4] * v.x + a.elements[1 + 1 * 4] * v.y + a.elements[1 + 2 * 4] * v.z +
a.elements[1 + 3 * 4] * v.w,
.z = a.elements[2 + 0 * 4] * v.x + a.elements[2 + 1 * 4] * v.y + a.elements[2 + 2 * 4] * v.z +
a.elements[2 + 3 * 4] * v.w,
.w = a.elements[3 + 0 * 4] * v.x + a.elements[3 + 1 * 4] * v.y + a.elements[3 + 2 * 4] * v.z +
a.elements[3 + 3 * 4] * v.w,
};
}
};
struct rti_ray {
rti_vec3 origin;
rti_vec3 direction;
static inline rti_ray transform(rti_ray ray, rti_mat4 transform)
{
rti_vec4 origin = {ray.origin.x, ray.origin.y, ray.origin.z, 1};
origin = rti_mat4::mul_vec4(transform, origin);
rti_vec4 direction = {ray.direction.x, ray.direction.y, ray.direction.z, 0};
direction = rti_mat4::mul_vec4(transform, direction);
return {
.origin = {origin.x, origin.y, origin.z},
.direction = {direction.x, direction.y, direction.z},
};
}
};
struct rti_aabb {
rti_vec3 min;
rti_vec3 max;
static inline rti_aabb combine(rti_aabb a, rti_aabb b)
{
return {
.min =
{
.x = MIN2(a.min.x, b.min.x),
.y = MIN2(a.min.y, b.min.y),
.z = MIN2(a.min.z, b.min.z),
},
.max =
{
.x = MAX2(a.max.x, b.max.x),
.y = MAX2(a.max.y, b.max.y),
.z = MAX2(a.max.z, b.max.z),
},
};
}
static inline float intersect_ray(rti_aabb aabb, rti_ray ray)
{
float t1 = (aabb.min.x - ray.origin.x) / ray.direction.x;
float t2 = (aabb.max.x - ray.origin.x) / ray.direction.x;
float t3 = (aabb.min.y - ray.origin.y) / ray.direction.y;
float t4 = (aabb.max.y - ray.origin.y) / ray.direction.y;
float t5 = (aabb.min.z - ray.origin.z) / ray.direction.z;
float t6 = (aabb.max.z - ray.origin.z) / ray.direction.z;
float tmin = fmax(fmax(fmin(t1, t2), fmin(t3, t4)), fmin(t5, t6));
float tmax = fmin(fmin(fmax(t1, t2), fmax(t3, t4)), fmax(t5, t6));
if (tmax < 0 || tmax < tmin)
return INFINITY;
return tmin;
}
};
struct rti_triangle {
rti_vec3 vertices[3];
uint32_t geometry_index = 0;
uint32_t primitive_index = 0;
static inline float intersect_ray(rti_triangle triangle, rti_ray ray)
{
rti_vec3 edge1 = rti_vec3::sub(triangle.vertices[1], triangle.vertices[0]);
rti_vec3 edge2 = rti_vec3::sub(triangle.vertices[2], triangle.vertices[0]);
rti_vec3 direction_cross_edge2 = rti_vec3::cross(ray.direction, edge2);
float det = rti_vec3::dot(edge1, direction_cross_edge2);
rti_vec3 s = rti_vec3::sub(ray.origin, triangle.vertices[0]);
float u = rti_vec3::dot(s, direction_cross_edge2) / det;
rti_vec3 s_cross_edge1 = rti_vec3::cross(s, edge1);
float v = rti_vec3::dot(ray.direction, s_cross_edge1) / det;
if (u < 0 || v < 0 || u + v > 1)
return INFINITY;
float t = rti_vec3::dot(edge2, s_cross_edge1) / det;
if (t < 0)
t = INFINITY;
return t;
}
};
#define RTI_PRIMITIVE_INDEX_INACTIVE 0xffffffff
struct rti_vertex {
rti_vec3 position;
uint32_t geometry_index;
uint32_t primitive_index;
};
void rti_generate_cube_vertices(rti_vertex *vertices, rti_aabb aabb);
void rti_generate_filled_cube_vertices(rti_vertex *vertices, rti_aabb aabb, uint32_t geometry_index,
uint32_t primitive_index);
/* Helpers to remove some imgui boilerplate. */
namespace ImGui {
static inline void
TableNextColumnText(const char *fmt, ...)
{
TableNextColumn();
va_list args;
va_start(args, fmt);
TextV(fmt, args);
va_end(args);
}
}; // namespace ImGui

View file

@ -0,0 +1,35 @@
# Copyright © 2026 Valve Corporation
#
# SPDX-License-Identifier: MIT
shaders = [
[
'renderer.vert',
'renderer_vert',
],
[
'renderer.frag',
'renderer_frag',
],
]
shader_includes = files(
)
spv_shaders = []
foreach s : shaders
command = [
prog_glslang, '-V', '--target-env', 'spirv1.5',
'-x', '-o', '@OUTPUT@', '@INPUT@', glslang_depfile, glslang_quiet,
]
_spv_name = '@0@.spv.h'.format(s[1])
spv_shaders += custom_target(
_spv_name,
input : s[0],
output : _spv_name,
command : command,
depfile : f'@_spv_name@.d',
depend_files: shader_includes
)
endforeach

View file

@ -0,0 +1,52 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int32 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_scalar_block_layout : require
#include "rti_shader_interface.h"
layout(location = 0) out vec4 out_color;
layout(location = 0) flat in uint32_t index;
layout(location = 1) flat in uint32_t param_index;
layout(push_constant) uniform block
{
rti_push_constants consts;
};
layout(scalar, set = 0, binding = 0) readonly buffer SSBO
{
rti_render_params params[];
}
state;
void
main()
{
rti_render_params params = state.params[param_index];
out_color = vec4(0.0, 0.0, 0.0, 1.0);
uint32_t color_calculation = consts.flags & RTI_RENDERER_FLAG_COLOR_MASK;
if (color_calculation == RTI_RENDERER_COLOR_PUSH_CONSTANT) {
out_color.rgb = params.color;
} else if (color_calculation == RTI_RENDERER_COLOR_GEOMETRY_INDEX ||
color_calculation == RTI_RENDERER_COLOR_PRIMITIVE_INDEX) {
out_color.r = ((index * 0xd83f0930) >> 24) / 255.0;
out_color.g = ((index * 0x8fa9836b) >> 24) / 255.0;
out_color.b = ((index * 0x3037f8ad) >> 24) / 255.0;
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#version 460
#extension GL_GOOGLE_include_directive : require
#extension GL_EXT_shader_explicit_arithmetic_types_int8 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int32 : require
#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
#extension GL_EXT_scalar_block_layout : require
#include "rti_shader_interface.h"
layout(location = 0) in vec3 position;
layout(location = 1) in uint32_t geometry_index;
layout(location = 2) in uint32_t primitive_index;
layout(location = 0) flat out uint32_t out_index;
layout(location = 1) flat out uint32_t out_param_index;
layout(push_constant) uniform block
{
rti_push_constants consts;
};
layout(scalar, set = 0, binding = 0) readonly buffer SSBO
{
rti_render_params params[];
}
state;
void
main()
{
uint32_t param_index = consts.first_param + gl_DrawID;
rti_render_params params = state.params[param_index];
gl_Position = consts.transform * (params.transform * vec4(position, 1.0));
uint32_t color_calculation = consts.flags & RTI_RENDERER_FLAG_COLOR_MASK;
if (color_calculation == RTI_RENDERER_COLOR_GEOMETRY_INDEX)
out_index = geometry_index;
else
out_index = primitive_index;
out_param_index = param_index;
}

View file

@ -0,0 +1,41 @@
/*
* Copyright © 2026 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#ifndef RTI_SHADER_INTERFACE_H
#define RTI_SHADER_INTERFACE_H
#ifdef VULKAN
#else
#include "rti_util.h"
#define mat4 rti_mat4
#define vec3 rti_vec3
#endif
struct rti_render_params {
mat4 transform;
vec3 color;
};
struct rti_push_constants {
mat4 transform;
uint32_t first_param;
uint32_t flags;
};
#define RTI_RENDERER_COLOR_PUSH_CONSTANT 0
#define RTI_RENDERER_COLOR_PRIMITIVE_INDEX 1
#define RTI_RENDERER_COLOR_GEOMETRY_INDEX 2
#define RTI_RENDERER_COLOR_BIT_COUNT 2
#define RTI_RENDERER_FLAG_COLOR_MASK ((1u << RTI_RENDERER_COLOR_BIT_COUNT) - 1)
#ifdef VULKAN
#else
#undef mat4
#undef vec3
#endif
#endif