diff --git a/.gitlab-ci/build/gitlab-ci.yml b/.gitlab-ci/build/gitlab-ci.yml index 867aab97c37..c2c81df7e0d 100644 --- a/.gitlab-ci/build/gitlab-ci.yml +++ b/.gitlab-ci/build/gitlab-ci.yml @@ -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 diff --git a/.gitlab-ci/container/debian/x86_64_build-base.sh b/.gitlab-ci/container/debian/x86_64_build-base.sh index 2b3db17d9fd..4cc2bfef847 100644 --- a/.gitlab-ci/container/debian/x86_64_build-base.sh +++ b/.gitlab-ci/container/debian/x86_64_build-base.sh @@ -53,6 +53,7 @@ DEPS=( libglfw3-dev "libllvm${LLVM_VERSION}" libpciaccess-dev + libsdl3-dev libunwind-dev libva-dev libvulkan-dev diff --git a/.gitlab-ci/container/fedora/x86_64_build.sh b/.gitlab-ci/container/fedora/x86_64_build.sh index 83c1d29daab..aff15d7ae95 100644 --- a/.gitlab-ci/container/fedora/x86_64_build.sh +++ b/.gitlab-ci/container/fedora/x86_64_build.sh @@ -79,6 +79,7 @@ DEPS=( python3-ply python3-pycparser python3-yaml + SDL3-devel spirv-tools-devel spirv-llvm-translator-devel vulkan-headers diff --git a/.gitlab-ci/image-tags.yml b/.gitlab-ci/image-tags.yml index dfcadc7a0da..b9deb1b5739 100644 --- a/.gitlab-ci/image-tags.yml +++ b/.gitlab-ci/image-tags.yml @@ -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" diff --git a/meson.build b/meson.build index 798a4b5efc9..64df3e8aa1e 100644 --- a/meson.build +++ b/meson.build @@ -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 == '' diff --git a/meson.options b/meson.options index 8f3552ca124..5229f1e958d 100644 --- a/meson.options +++ b/meson.options @@ -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`)', ) diff --git a/src/imgui/meson.build b/src/imgui/meson.build index 4078590d60f..f5f067aca3c 100644 --- a/src/imgui/meson.build +++ b/src/imgui/meson.build @@ -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 diff --git a/src/tool/meson.build b/src/tool/meson.build index 2d1738e41d9..4a529342382 100644 --- a/src/tool/meson.build +++ b/src/tool/meson.build @@ -9,3 +9,7 @@ endif if with_tools.contains('dlclose-skip') subdir('dlclose-skip') endif + +if with_tools.contains('rti') + subdir('rtinspector') +endif diff --git a/src/tool/rtinspector/.clang-format b/src/tool/rtinspector/.clang-format new file mode 100644 index 00000000000..e7de1f0f018 --- /dev/null +++ b/src/tool/rtinspector/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: InheritParentConfig +DisableFormat: false + +AccessModifierOffset: -3 + +ColumnLimit: 120 diff --git a/src/tool/rtinspector/main.cpp b/src/tool/rtinspector/main.cpp new file mode 100644 index 00000000000..80fbb3e5165 --- /dev/null +++ b/src/tool/rtinspector/main.cpp @@ -0,0 +1,153 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include + +#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; +} diff --git a/src/tool/rtinspector/meson.build b/src/tool/rtinspector/meson.build new file mode 100644 index 00000000000..d2f0cfdb3de --- /dev/null +++ b/src/tool/rtinspector/meson.build @@ -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], +) \ No newline at end of file diff --git a/src/tool/rtinspector/radv/rti_file_view_radv.cpp b/src/tool/rtinspector/radv/rti_file_view_radv.cpp new file mode 100644 index 00000000000..14907a28800 --- /dev/null +++ b/src/tool/rtinspector/radv/rti_file_view_radv.cpp @@ -0,0 +1,850 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "rti_file_view_radv.h" +#include "rti_file_view.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#endif + +#include "bvh.h" +#include "radv_rti.h" +#include "rti_app.h" +#include "rti_util.h" + +std::unique_ptr +rti_file_view_radv::create_acceleration_structure() +{ + return std::make_unique(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 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 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(0, acceleration_structures.size()), + [this, vertices, vertex_buffer](const tbb::blocked_range &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(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(); + + 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 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 texture_data = std::make_shared(); + 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_create_file_view_radv() +{ + return std::make_unique(); +} diff --git a/src/tool/rtinspector/radv/rti_file_view_radv.h b/src/tool/rtinspector/radv/rti_file_view_radv.h new file mode 100644 index 00000000000..ccaf69dea77 --- /dev/null +++ b/src/tool/rtinspector/radv/rti_file_view_radv.h @@ -0,0 +1,177 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "bvh.h" +#include "radv_rti.h" +#include "rti_file_view.h" +#include "rti_util.h" +#include + +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 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 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> texture_data; + std::vector> 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 invocations; + + std::map> valid_dispatch_sizes; +}; + +struct rti_file_view_radv : public rti_file_view { + std::vector> 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 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 &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 &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; +} diff --git a/src/tool/rtinspector/radv/rti_file_view_radv_bvh4.cpp b/src/tool/rtinspector/radv/rti_file_view_radv_bvh4.cpp new file mode 100644 index 00000000000..2d1585a6f63 --- /dev/null +++ b/src/tool/rtinspector/radv/rti_file_view_radv_bvh4.cpp @@ -0,0 +1,490 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "rti_file_view.h" +#include "rti_file_view_radv.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 &path, bool select) +{ + uint32_t offset = (id & (~0x7)) << 3; + uint32_t type = id & 0x7; + + std::unordered_set 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 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); +} diff --git a/src/tool/rtinspector/radv/rti_file_view_radv_bvh8.cpp b/src/tool/rtinspector/radv/rti_file_view_radv_bvh8.cpp new file mode 100644 index 00000000000..a20414f3975 --- /dev/null +++ b/src/tool/rtinspector/radv/rti_file_view_radv_bvh8.cpp @@ -0,0 +1,736 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "rti_file_view.h" +#include "rti_file_view_radv.h" + +#include +#include +#include +#include +#include +#include +#include + +#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 &path, bool select) +{ + uint32_t offset = (id & (~0xf)) << 3; + + std::unordered_set 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 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); +} diff --git a/src/tool/rtinspector/rti_app.cpp b/src/tool/rtinspector/rti_app.cpp new file mode 100644 index 00000000000..f49bf7d18df --- /dev/null +++ b/src/tool/rtinspector/rti_app.cpp @@ -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 +#include +#include +#include +#include + +#include "shaders/rti_shader_interface.h" +#include "util/os_time.h" +#include "vulkan/vulkan_core.h" +#include +#include +#include + +#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 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 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 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 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(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 dst) +{ + std::shared_ptr 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, ©); + } + + 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(); +} diff --git a/src/tool/rtinspector/rti_app.h b/src/tool/rtinspector/rti_app.h new file mode 100644 index 00000000000..cd96ced1260 --- /dev/null +++ b/src/tool/rtinspector/rti_app.h @@ -0,0 +1,76 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include + +#include "vulkan/vulkan_core.h" +#include + +#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> open_files; + VkCommandBuffer command_buffer; + + PFN_vkCmdDrawMultiEXT vkCmdDrawMultiEXT; + + VkCommandPool staging_command_pool; + VkCommandBuffer staging_command_buffer = VK_NULL_HANDLE; + std::vector, std::shared_ptr>> + 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 cube_vertex_buffer; + std::shared_ptr 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 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); diff --git a/src/tool/rtinspector/rti_file_view.cpp b/src/tool/rtinspector/rti_file_view.cpp new file mode 100644 index 00000000000..6cdebf8f48b --- /dev/null +++ b/src/tool/rtinspector/rti_file_view.cpp @@ -0,0 +1,1404 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "rti_file_view.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "shaders/rti_shader_interface.h" + +#include "util/macros.h" +#include "util/os_time.h" +#include "util/rti_format.h" +#include "util/u_math.h" + +#include "vulkan/vulkan_core.h" + +#include +#include "imgui.h" +#include "imgui_internal.h" + +#include "rti_app.h" +#include "rti_util.h" + +rti_render_list::~rti_render_list() +{ + vkDestroyDescriptorPool(app->device, descriptor_pool, nullptr); +} + +void +rti_render_list::build() +{ + if (tasks.empty()) + return; + + constants = rti_create_backed_buffer(app, tasks.size() * sizeof(rti_render_params), rti_memory_type_host_visible, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, true); + rti_render_params *consts = (rti_render_params *)constants->map; + for (uint32_t i = 0; i < tasks.size(); i++) + consts[i] = tasks[i].params; + + VkDescriptorPoolSize pool_sizes[] = { + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}, + }; + VkDescriptorPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + .maxSets = 1, + .poolSizeCount = ARRAY_SIZE(pool_sizes), + .pPoolSizes = pool_sizes, + }; + + rti_check_vk_result(vkCreateDescriptorPool(app->device, &pool_info, nullptr, &descriptor_pool)); + + VkDescriptorSetAllocateInfo set_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = descriptor_pool, + .descriptorSetCount = 1, + .pSetLayouts = &app->renderer_set_layout, + }; + + rti_check_vk_result(vkAllocateDescriptorSets(app->device, &set_info, &descriptor_set)); + + VkDescriptorBufferInfo constants_buffer_info = { + .buffer = constants->buffer, + .range = VK_WHOLE_SIZE, + }; + + VkWriteDescriptorSet constants_buffer_write = { + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptor_set, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .pBufferInfo = &constants_buffer_info, + }; + + vkUpdateDescriptorSets(app->device, 1, &constants_buffer_write, 0, nullptr); +} + +rti_imgui_texture::~rti_imgui_texture() +{ + if (descriptor_set) + ImGui_ImplVulkan_RemoveTexture(descriptor_set); +} + +rti_acceleration_structure::rti_acceleration_structure(rti_file_view *view): app(view->app), view(view) +{ +} + +void +rti_acceleration_structure::load(FILE *file, uint32_t chunk_size) +{ + fread(&header, sizeof(header), 1, file); + + primitive_counts = (uint32_t *)malloc(header.geometry_count * sizeof(uint32_t)); + primitive_counts_exclusive_sum = (uint32_t *)malloc(header.geometry_count * sizeof(uint32_t)); + fread(primitive_counts, header.geometry_count * sizeof(uint32_t), 1, file); + for (uint32_t i = 0; i < header.geometry_count; i++) { + primitive_counts_exclusive_sum[i] = primitive_count; + primitive_count += primitive_counts[i]; + } + + name = (char *)malloc(header.name_size + 1); + name[header.name_size] = 0; + fread(name, header.name_size, 1, file); + + uint64_t data_size = chunk_size - header.name_size - sizeof(rti_acceleration_structure_header); + data = malloc(data_size); + fread(data, data_size, 1, file); +} + +void +rti_acceleration_structure::init_camera() +{ + rti_vec3 center = { + .x = (aabb.max.x + aabb.min.x) / 2, + .y = (aabb.max.y + aabb.min.y) / 2, + .z = (aabb.max.z + aabb.min.z) / 2, + }; + rti_vec3 extent = { + .x = aabb.max.x - aabb.min.x, + .y = aabb.max.y - aabb.min.y, + .z = aabb.max.z - aabb.min.z, + }; + float max_extent = fmax(fmax(extent.x, extent.y), extent.z); + + ui.camera_position = center; + ui.camera_position.x -= max_extent; + ui.camera_position.y -= max_extent; + ui.camera_position.z -= max_extent; + ui.camera_theta = M_PI / 4; + ui.camera_phi = M_PI / 4; +} + +void +rti_file_view::load(FILE *file, const rti_header *header) +{ + for (uint32_t i = 0; i < header->chunk_count; i++) { + rti_chunk_header chunk_header; + fread(&chunk_header, sizeof(chunk_header), 1, file); + + if (chunk_header.type == rti_chunk_type_acceleration_structure) { + std::unique_ptr acceleration_structure = create_acceleration_structure(); + acceleration_structure->app = app; + acceleration_structure->load(file, chunk_header.size); + address_map[acceleration_structure->header.address] = acceleration_structures.size(); + acceleration_structures.push_back(std::move(acceleration_structure)); + } else if (chunk_header.type >= rti_chunk_type_driver_start) { + load_driver_specific(file, &chunk_header, header); + } else { + fprintf(stderr, "rti: Invalid rti_chunk_type\n"); + return; + } + } +} + +rti_acceleration_structure * +rti_file_view::addr_to_acceleration_structure(uint64_t address) +{ + if (address_map.empty()) + return nullptr; + + auto lower_bound = address_map.lower_bound(address); + + if (lower_bound == address_map.end() || lower_bound->first > address) + lower_bound--; + + rti_acceleration_structure *acceleration_structure = acceleration_structures[lower_bound->second].get(); + if (address >= acceleration_structure->header.address && + address < acceleration_structure->header.address + acceleration_structure->header.allocated_size) + return acceleration_structure; + + return nullptr; +} + +std::unique_ptr +rti_create_file_view(rti_app *app, const char *file_path) +{ + int64_t start_time = os_time_get_nano(); + + FILE *file = fopen(file_path, "rb"); + if (!file) + return nullptr; + + rti_header header; + fread(&header, sizeof(header), 1, file); + + std::unique_ptr view = nullptr; + if (header.driver == rti_driver_radv) + view = rti_create_file_view_radv(); + else + return nullptr; + + view->app = app; + view->path = file_path; + view->load(file, &header); + + fclose(file); + + int64_t end_time = os_time_get_nano(); + fprintf(stderr, "rti: Opening file \"%s\" took %.2fms\n", file_path, (end_time - start_time) / 1000000.0); + + return view; +} + +static const std::map acceleration_structure_type_filters = { + {VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, "TLAS"}, + {VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, "BLAS"}, + {VK_ACCELERATION_STRUCTURE_TYPE_GENERIC_KHR, "any"}, +}; + +static const std::map visualization_colors = { + {rti_visualization_color_primitive_index, "primitive_index"}, + {rti_visualization_color_geometry_index, "geometry_index"}, + {rti_visualization_color_instance_index, "instance_index"}, +}; + +static const std::map camera_types = { + {rti_camera_type_first_person, "first person"}, + {rti_camera_type_pivot, "pivot"}, +}; + +static const rti_mat4 basis_transforms[] = { + {.elements = {0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}, /* x -> -y, y -> x, z -> z */ + {.elements = {-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}, /* x -> -x, y -> -y, z -> z */ + {.elements = {1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1}}, /* x -> x, y -> z, z -> -y */ + {.elements = {0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}, /* x -> y, y -> -x, z -> z */ + {.elements = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}}, /* x -> x, y -> y, z -> z */ + {.elements = {1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1}}, /* x -> x, y -> -z, z -> y */ +}; + +struct rti_basis_guizmo_axis_description { + const char *label; + rti_vec4 end; + ImVec4 color; + rti_vec4 transformed_end; +}; + +static int +rti_basis_guizmo_axis_description_compare(const void *_a, const void *_b) +{ + const rti_basis_guizmo_axis_description *a = (const rti_basis_guizmo_axis_description *)_a; + const rti_basis_guizmo_axis_description *b = (const rti_basis_guizmo_axis_description *)_b; + /* End points are sorted in ascending order, large z values need to be rendered first. */ + return a->transformed_end.z > b->transformed_end.z ? -1 : 1; +} + +void +rti_file_view::begin() +{ + if (!ui.initialized) { + ImGuiViewport *viewport = ImGui::GetMainViewport(); + + ui.dockspace_id = ImGui::GetID(path.c_str()); + ImGui::DockBuilderAddNode(ui.dockspace_id, ImGuiDockNodeFlags_DockSpace); + ImGui::DockBuilderSetNodeSize(ui.dockspace_id, viewport->Size); + + ImGuiID left_and_center = 0; + ImGuiID right = 0; + ImGui::DockBuilderSplitNode(ui.dockspace_id, ImGuiDir_Right, 0.3, &right, &left_and_center); + ImGuiID left = 0; + ImGuiID center = 0; + ImGui::DockBuilderSplitNode(left_and_center, ImGuiDir_Left, 0.3 / 0.7, &left, ¢er); + ImGuiID left_top = 0; + ImGuiID left_bottom = 0; + ImGui::DockBuilderSplitNode(left, ImGuiDir_Up, 0.6, &left_top, &left_bottom); + ImGuiID right_top = 0; + ImGuiID right_bottom = 0; + ImGui::DockBuilderSplitNode(right, ImGuiDir_Up, 0.6, &right_top, &right_bottom); + + ImGui::DockBuilderDockWindow("acceleration structures", right_top); + ImGui::DockBuilderDockWindow("viewport", right_bottom); + ImGui::DockBuilderDockWindow("BVH", left_top); + ImGui::DockBuilderDockWindow("node info", left_bottom); + for (const auto &acceleration_structure : acceleration_structures) + ImGui::DockBuilderDockWindow(acceleration_structure->name, center); + + dock_driver_specific(left_top, left_bottom, center, right_top, right_bottom); + + ImGui::DockBuilderFinish(ui.dockspace_id); + + ui.dockspace_class.ClassId = ImHashStr(path.c_str()); + + ui.filter_acceleration_structure_type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + + ui.initialized = true; + } + + if (ui.request_focus) { + ImGui::SetWindowFocus(); + ui.request_focus = false; + } + + ImGui::DockSpace(ui.dockspace_id, ImVec2(0, 0), ImGuiDockNodeFlags_NoWindowMenuButton, &ui.dockspace_class); + + ImGui::SetNextWindowClass(&ui.dockspace_class); + + ImGui::Begin("acceleration structures"); + + if (ImGui::BeginCombo("type", acceleration_structure_type_filters.at(ui.filter_acceleration_structure_type))) { + for (const auto &filter : acceleration_structure_type_filters) { + if (ImGui::Selectable(filter.second)) + ui.filter_acceleration_structure_type = filter.first; + } + ImGui::EndCombo(); + } + + if (ImGui::BeginTable("acceleration structures", 4, ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable)) { + ImGui::TableSetupColumn("name"); + ImGui::TableSetupColumn("type"); + ImGui::TableSetupColumn("allocated size"); + ImGui::TableSetupColumn("compacted size"); + ImGui::TableHeadersRow(); + + for (const auto &acceleration_structure : acceleration_structures) { + if (ui.filter_acceleration_structure_type != VK_ACCELERATION_STRUCTURE_TYPE_GENERIC_KHR && + ui.filter_acceleration_structure_type != acceleration_structure->header.type) + continue; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + if (ImGui::Selectable(acceleration_structure->name, false, + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) { + if (acceleration_structure->ui.opened) + acceleration_structure->ui.request_focus = true; + acceleration_structure->ui.opened = true; + } + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", acceleration_structure_type_filters.at(acceleration_structure->header.type)); + ImGui::TableSetColumnIndex(2); + ImGui::Text("%.1f MiB", (double)acceleration_structure->header.allocated_size / (1024 * 1024)); + ImGui::TableSetColumnIndex(3); + ImGui::Text("%.1f MiB", (double)acceleration_structure->header.compacted_size / (1024 * 1024)); + } + + ImGui::EndTable(); + } + + ImGui::End(); + + ImGui::Begin("viewport"); + + const char *axis_names[] = {"+x", "+y", "+z", "-x", "-y", "-z"}; + + if (ImGui::BeginCombo("up axis", axis_names[up_axis])) { + for (uint32_t i = 0; i < ARRAY_SIZE(axis_names); i++) { + if (ImGui::Selectable(axis_names[i])) + up_axis = i; + } + ImGui::EndCombo(); + } + + if (ui.focused_acceleration_structure) { + ImGui::SeparatorText("camera"); + + if (ImGui::BeginCombo("type", camera_types.at(ui.focused_acceleration_structure->ui.camera_type))) { + for (const auto &type : camera_types) { + if (ImGui::Selectable(type.second)) + ui.focused_acceleration_structure->ui.camera_type = (rti_camera_type)type.first; + } + ImGui::EndCombo(); + } + + ImGui::SliderFloat("speed", &ui.focused_acceleration_structure->ui.camera_speed, 0, 1); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ImGui::SetTooltip("Speed in max scene extent per second"); + + ImGui::SliderFloat("near", &ui.focused_acceleration_structure->ui.camera_near, 0, 0.001); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ImGui::SetTooltip("Camera near plane in max scene extent"); + + ImGui::SliderFloat("far", &ui.focused_acceleration_structure->ui.camera_far, 0, 10); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ImGui::SetTooltip("Camera far plane in max scene extent"); + + ImGui::SliderFloat("fov", &ui.focused_acceleration_structure->ui.camera_fov, 0, 179); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ImGui::SetTooltip("Field of view in degrees"); + + ImGui::SeparatorText("visualization"); + + if (ImGui::BeginCombo("color", + visualization_colors.at(ui.focused_acceleration_structure->ui.visualization_color))) { + for (const auto &color : visualization_colors) { + if (ImGui::Selectable(color.second)) + ui.focused_acceleration_structure->ui.visualization_color = (rti_visualization_color)color.first; + } + ImGui::EndCombo(); + } + } + ImGui::End(); +} + +void +rti_file_view::end() +{ +} + +bool +rti_file_view::begin_viewport(rti_acceleration_structure *acceleration_structure) +{ + rendering_state.render = false; + if (!acceleration_structure->ui.opened) + return false; + + rendering_state.render = ImGui::Begin(acceleration_structure->name); + + if (acceleration_structure->ui.request_focus) { + ImGui::SetWindowFocus(); + rendering_state.render = true; + acceleration_structure->ui.request_focus = false; + } + + if (ImGui::IsWindowFocused()) { + ui.focused_acceleration_structure = acceleration_structure; + } + + if (!rendering_state.render) { + acceleration_structure->ui.prev_focused = ImGui::IsWindowFocused(); + ImGui::End(); + return false; + } + + rendering_state.acceleration_structure = acceleration_structure; + rendering_state.viewport_offset = ImGui::GetCursorScreenPos(); + rendering_state.viewport_size = ImGui::GetContentRegionAvail(); + + uint32_t viewport_width = rendering_state.viewport_size.x; + uint32_t viewport_height = rendering_state.viewport_size.y; + if ((viewport_width != acceleration_structure->ui.viewport.width || + viewport_height != acceleration_structure->ui.viewport.height) && + rendering_state.viewport_size.x > 0 && rendering_state.viewport_size.y > 0) { + printf("rti: Resizing viewport to (%u, %u)\n", viewport_width, viewport_height); + + VkExtent3D viewport_extent = { + .width = viewport_width, + .height = viewport_height, + .depth = 1, + }; + + acceleration_structure->ui.viewport.cb = + rti_create_backed_image(app, viewport_extent, {VK_FORMAT_R8G8B8A8_UNORM}, + VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, app->sample_count, 1); + acceleration_structure->ui.viewport.db = rti_create_backed_image( + app, viewport_extent, {VK_FORMAT_D32_SFLOAT}, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, app->sample_count, 1); + acceleration_structure->ui.viewport.resolved_image = rti_create_backed_image( + app, viewport_extent, {VK_FORMAT_R8G8B8A8_UNORM}, + VK_FORMAT_FEATURE_TRANSFER_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_ASPECT_COLOR_BIT, 1, 1); + + acceleration_structure->ui.viewport.width = viewport_width; + acceleration_structure->ui.viewport.height = viewport_height; + + acceleration_structure->ui.viewport.surface = std::make_shared(ImGui_ImplVulkan_AddTexture( + acceleration_structure->ui.viewport.resolved_image->image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)); + + VkImageMemoryBarrier initial_transitions[] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = acceleration_structure->ui.viewport.resolved_image->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = acceleration_structure->ui.viewport.cb->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = acceleration_structure->ui.viewport.db->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + + vkCmdPipelineBarrier(app->command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, ARRAY_SIZE(initial_transitions), initial_transitions); + } + + ImVec2 mouse_position = ImGui::GetMousePos(); + bool mouse_inside_viewport = + mouse_position.x >= rendering_state.viewport_offset.x && mouse_position.y >= rendering_state.viewport_offset.y && + mouse_position.x <= rendering_state.viewport_offset.x + rendering_state.viewport_size.x && + mouse_position.y <= rendering_state.viewport_offset.y + rendering_state.viewport_size.y; + + rti_vec3 extent = { + .x = acceleration_structure->aabb.max.x - acceleration_structure->aabb.min.x, + .y = acceleration_structure->aabb.max.y - acceleration_structure->aabb.min.y, + .z = acceleration_structure->aabb.max.z - acceleration_structure->aabb.min.z, + }; + float max_extent = fmax(fmax(extent.x, extent.y), extent.z); + + float near = max_extent * acceleration_structure->ui.camera_near; + float far = max_extent * acceleration_structure->ui.camera_far; + float fov = M_PI / 180 * acceleration_structure->ui.camera_fov; + float x_aspect_ratio = 1; + float y_aspect_ratio = 1; + if (viewport_width > viewport_height) { + y_aspect_ratio = (float)viewport_width / (float)viewport_height; + } else { + x_aspect_ratio = (float)viewport_height / (float)viewport_width; + } + rendering_state.projection_matrix = rti_mat4::zero(); + rendering_state.projection_matrix.elements[0 + 0 * 4] = x_aspect_ratio / tan(fov / 2); + rendering_state.projection_matrix.elements[1 + 1 * 4] = y_aspect_ratio / tan(fov / 2); + rendering_state.projection_matrix.elements[2 + 2 * 4] = far / (far - near); + rendering_state.projection_matrix.elements[3 + 2 * 4] = 1; + rendering_state.projection_matrix.elements[2 + 3 * 4] = -2 * far * near / (far - near); + + rendering_state.inv_camera_rotation = rti_mat4(); + float camera_x_phi = acceleration_structure->ui.camera_phi - M_PI_2; + float camera_x_theta = M_PI_2; + rendering_state.inv_camera_rotation.elements[0 * 4 + 0] = cos(camera_x_phi) * sin(camera_x_theta); + rendering_state.inv_camera_rotation.elements[1 * 4 + 0] = cos(camera_x_theta); + rendering_state.inv_camera_rotation.elements[2 * 4 + 0] = sin(camera_x_phi) * sin(camera_x_theta); + float camera_y_phi = acceleration_structure->ui.camera_phi; + float camera_y_theta = acceleration_structure->ui.camera_theta - M_PI_2; + rendering_state.inv_camera_rotation.elements[0 * 4 + 1] = cos(camera_y_phi) * sin(camera_y_theta); + rendering_state.inv_camera_rotation.elements[1 * 4 + 1] = cos(camera_y_theta); + rendering_state.inv_camera_rotation.elements[2 * 4 + 1] = sin(camera_y_phi) * sin(camera_y_theta); + float camera_z_phi = acceleration_structure->ui.camera_phi; + float camera_z_theta = acceleration_structure->ui.camera_theta; + rendering_state.inv_camera_rotation.elements[0 * 4 + 2] = cos(camera_z_phi) * sin(camera_z_theta); + rendering_state.inv_camera_rotation.elements[1 * 4 + 2] = cos(camera_z_theta); + rendering_state.inv_camera_rotation.elements[2 * 4 + 2] = sin(camera_z_phi) * sin(camera_z_theta); + + if (acceleration_structure->ui.camera_type == rti_camera_type_pivot && + acceleration_structure->ui.camera_pivot_valid) { + rti_vec3 camera_z = { + .x = rendering_state.inv_camera_rotation.elements[0 * 4 + 2], + .y = rendering_state.inv_camera_rotation.elements[1 * 4 + 2], + .z = rendering_state.inv_camera_rotation.elements[2 * 4 + 2], + }; + + acceleration_structure->ui.camera_position.x = + acceleration_structure->ui.camera_pivot.x - acceleration_structure->ui.camera_pivot_distance * camera_z.x; + acceleration_structure->ui.camera_position.y = + acceleration_structure->ui.camera_pivot.y - acceleration_structure->ui.camera_pivot_distance * camera_z.y; + acceleration_structure->ui.camera_position.z = + acceleration_structure->ui.camera_pivot.z - acceleration_structure->ui.camera_pivot_distance * camera_z.z; + } + + rti_mat4 inv_camera_position; + inv_camera_position.elements[0 + 3 * 4] = -acceleration_structure->ui.camera_position.x; + inv_camera_position.elements[1 + 3 * 4] = -acceleration_structure->ui.camera_position.y; + inv_camera_position.elements[2 + 3 * 4] = -acceleration_structure->ui.camera_position.z; + + rendering_state.view_projection_matrix = rti_mat4::mul( + rendering_state.projection_matrix, rti_mat4::mul(rendering_state.inv_camera_rotation, + rti_mat4::mul(inv_camera_position, basis_transforms[up_axis]))); + + rendering_state.camera_pivot = acceleration_structure->ui.camera_pivot; + + if (acceleration_structure->ui.camera_type != rti_camera_type_pivot) + acceleration_structure->ui.camera_pivot_valid = false; + + bool started_middle_dragging = false; + + bool right_mouse_button_pressed = ImGui::IsMouseDown(ImGuiMouseButton_Right); + bool middle_mouse_button_pressed = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + bool left_mouse_button_pressed = ImGui::IsMouseDown(ImGuiMouseButton_Left); + + if (ImGui::IsWindowFocused()) { + float ds = app->dt * acceleration_structure->ui.camera_speed * max_extent; + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) + ds *= 2; + + rti_vec3 dr; + if (ImGui::IsKeyDown(ImGuiKey_W)) { + dr.x += cos(acceleration_structure->ui.camera_phi) * ds; + dr.z += sin(acceleration_structure->ui.camera_phi) * ds; + } + if (ImGui::IsKeyDown(ImGuiKey_S)) { + dr.x -= cos(acceleration_structure->ui.camera_phi) * ds; + dr.z -= sin(acceleration_structure->ui.camera_phi) * ds; + } + if (ImGui::IsKeyDown(ImGuiKey_A)) { + dr.x += cos(acceleration_structure->ui.camera_phi + M_PI_2) * ds; + dr.z += sin(acceleration_structure->ui.camera_phi + M_PI_2) * ds; + } + if (ImGui::IsKeyDown(ImGuiKey_D)) { + dr.x -= cos(acceleration_structure->ui.camera_phi + M_PI_2) * ds; + dr.z -= sin(acceleration_structure->ui.camera_phi + M_PI_2) * ds; + } + if (ImGui::IsKeyDown(ImGuiKey_Space)) { + dr.y -= ds; + } + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + dr.y += ds; + } + + if (acceleration_structure->ui.camera_type == rti_camera_type_pivot) { + acceleration_structure->ui.camera_pivot.x += dr.x; + acceleration_structure->ui.camera_pivot.y += dr.y; + acceleration_structure->ui.camera_pivot.z += dr.z; + } else { + acceleration_structure->ui.camera_position.x += dr.x; + acceleration_structure->ui.camera_position.y += dr.y; + acceleration_structure->ui.camera_position.z += dr.z; + } + + /* Detect thet start/end of dragging inside the viewport. */ + if (!acceleration_structure->ui.is_middle_dragging_viewport && + !acceleration_structure->ui.is_right_dragging_viewport && mouse_inside_viewport) { + if (right_mouse_button_pressed) { + acceleration_structure->ui.prev_mouse_pos = mouse_position; + acceleration_structure->ui.is_right_dragging_viewport = true; + } else if (middle_mouse_button_pressed) { + acceleration_structure->ui.prev_mouse_pos = mouse_position; + acceleration_structure->ui.is_middle_dragging_viewport = true; + started_middle_dragging = true; + } + } + if (acceleration_structure->ui.is_right_dragging_viewport && !right_mouse_button_pressed) + acceleration_structure->ui.is_right_dragging_viewport = false; + if (acceleration_structure->ui.is_middle_dragging_viewport && !middle_mouse_button_pressed) + acceleration_structure->ui.is_middle_dragging_viewport = false; + + if (acceleration_structure->ui.is_right_dragging_viewport) { + float drag_delta_x = mouse_position.x - acceleration_structure->ui.prev_mouse_pos.x; + float drag_delta_y = mouse_position.y - acceleration_structure->ui.prev_mouse_pos.y; + acceleration_structure->ui.camera_phi += drag_delta_x / x_aspect_ratio * fov / viewport_width; + acceleration_structure->ui.camera_theta += drag_delta_y / y_aspect_ratio * fov / viewport_height; + } + + if (mouse_inside_viewport) { + rti_mat4 inv_view_projection_matrix; + util_invert_mat4x4(inv_view_projection_matrix.elements, rendering_state.view_projection_matrix.elements); + + rti_vec4 ndc_mouse_position = { + .x = (mouse_position.x - rendering_state.viewport_offset.x) / rendering_state.viewport_size.x * 2 - 1, + .y = (mouse_position.y - rendering_state.viewport_offset.y) / rendering_state.viewport_size.y * 2 - 1, + .z = 1, + .w = 1, + }; + rti_vec4 global_mouse_position4 = rti_mat4::mul_vec4(inv_view_projection_matrix, ndc_mouse_position); + rti_vec3 global_mouse_position = { + .x = global_mouse_position4.x / global_mouse_position4.w, + .y = global_mouse_position4.y / global_mouse_position4.w, + .z = global_mouse_position4.z / global_mouse_position4.w, + }; + rti_vec4 origin4 = { + .x = acceleration_structure->ui.camera_position.x, + .y = acceleration_structure->ui.camera_position.y, + .z = acceleration_structure->ui.camera_position.z, + }; + origin4 = rti_mat4::mul_vec4(rti_mat4::transpose(basis_transforms[up_axis]), origin4); + rti_vec3 origin = {origin4.x, origin4.y, origin4.z}; + rti_ray ray = { + .origin = origin, + .direction = rti_vec3::sub(global_mouse_position, origin), + }; + rti_vec4 camera_z = { + .x = rendering_state.inv_camera_rotation.elements[0 * 4 + 2], + .y = rendering_state.inv_camera_rotation.elements[1 * 4 + 2], + .z = rendering_state.inv_camera_rotation.elements[2 * 4 + 2], + }; + camera_z = rti_mat4::mul_vec4(rti_mat4::transpose(basis_transforms[up_axis]), camera_z); + float t = handle_mouse_click(acceleration_structure, ray, false); + + if (started_middle_dragging) { + acceleration_structure->ui.middle_drag_start_z_distance = + t * (ray.direction.x * camera_z.x + ray.direction.y * camera_z.y + ray.direction.z * camera_z.z); + if (t == INFINITY) + acceleration_structure->ui.middle_drag_start_z_distance = far; + } + + if (acceleration_structure->ui.camera_type == rti_camera_type_pivot && + !acceleration_structure->ui.camera_pivot_valid && + (left_mouse_button_pressed || middle_mouse_button_pressed || right_mouse_button_pressed) && t < INFINITY) { + acceleration_structure->ui.camera_pivot = ray.origin; + acceleration_structure->ui.camera_pivot.x += t * ray.direction.x; + acceleration_structure->ui.camera_pivot.y += t * ray.direction.y; + acceleration_structure->ui.camera_pivot.z += t * ray.direction.z; + acceleration_structure->ui.camera_pivot_distance = t * sqrt(rti_vec3::dot(ray.direction, ray.direction)); + acceleration_structure->ui.camera_pivot_valid = true; + } + } + + if (acceleration_structure->ui.is_middle_dragging_viewport) { + float drag_delta_x = mouse_position.x - acceleration_structure->ui.prev_mouse_pos.x; + float drag_delta_y = mouse_position.y - acceleration_structure->ui.prev_mouse_pos.y; + + float dx = -drag_delta_x * 2.0 / viewport_width / x_aspect_ratio * tan(fov / 2) * + acceleration_structure->ui.middle_drag_start_z_distance; + rti_vec3 dr = { + .x = rendering_state.inv_camera_rotation.elements[0 * 4 + 0] * dx, + .y = rendering_state.inv_camera_rotation.elements[1 * 4 + 0] * dx, + .z = rendering_state.inv_camera_rotation.elements[2 * 4 + 0] * dx, + }; + + float dy = -drag_delta_y * 2.0 / viewport_height / y_aspect_ratio * tan(fov / 2) * + acceleration_structure->ui.middle_drag_start_z_distance; + dr.x += rendering_state.inv_camera_rotation.elements[0 * 4 + 1] * dy; + dr.y += rendering_state.inv_camera_rotation.elements[1 * 4 + 1] * dy; + dr.z += rendering_state.inv_camera_rotation.elements[2 * 4 + 1] * dy; + + if (acceleration_structure->ui.camera_type == rti_camera_type_pivot) { + acceleration_structure->ui.camera_pivot.x += dr.x; + acceleration_structure->ui.camera_pivot.y += dr.y; + acceleration_structure->ui.camera_pivot.z += dr.z; + } else { + acceleration_structure->ui.camera_position.x += dr.x; + acceleration_structure->ui.camera_position.y += dr.y; + acceleration_structure->ui.camera_position.z += dr.z; + } + } + + acceleration_structure->ui.prev_mouse_pos = mouse_position; + + if (acceleration_structure->ui.camera_type == rti_camera_type_pivot && + acceleration_structure->ui.camera_pivot_valid && mouse_inside_viewport) { + float mouse_wheel = ImGui::GetIO().MouseWheel; + acceleration_structure->ui.camera_pivot_distance /= pow(1.1, mouse_wheel); + } + } + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_None) && mouse_inside_viewport && + acceleration_structure->ui.prev_focused) { + rti_mat4 inv_view_projection_matrix; + util_invert_mat4x4(inv_view_projection_matrix.elements, rendering_state.view_projection_matrix.elements); + + rti_vec4 ndc_mouse_position = { + .x = (mouse_position.x - rendering_state.viewport_offset.x) / rendering_state.viewport_size.x * 2 - 1, + .y = (mouse_position.y - rendering_state.viewport_offset.y) / rendering_state.viewport_size.y * 2 - 1, + .z = 1, + .w = 1, + }; + rti_vec4 global_mouse_position4 = rti_mat4::mul_vec4(inv_view_projection_matrix, ndc_mouse_position); + rti_vec3 global_mouse_position = { + .x = global_mouse_position4.x / global_mouse_position4.w, + .y = global_mouse_position4.y / global_mouse_position4.w, + .z = global_mouse_position4.z / global_mouse_position4.w, + }; + rti_vec4 origin4 = { + .x = acceleration_structure->ui.camera_position.x, + .y = acceleration_structure->ui.camera_position.y, + .z = acceleration_structure->ui.camera_position.z, + }; + origin4 = rti_mat4::mul_vec4(rti_mat4::transpose(basis_transforms[up_axis]), origin4); + rti_vec3 origin = {origin4.x, origin4.y, origin4.z}; + rti_ray ray = { + .origin = origin, + .direction = rti_vec3::sub(global_mouse_position, origin), + }; + handle_mouse_click(acceleration_structure, ray, true); + } + + acceleration_structure->ui.prev_focused = ImGui::IsWindowFocused(); + + acceleration_structure->ui.viewport.render_list = std::make_shared(app); + + return true; +} + +void +rti_file_view::render(const rti_render_task &task) +{ + rti_render_task task_copy = task; + task_copy.params.transform = rti_mat4::mul(rendering_state.view_projection_matrix, task_copy.params.transform); + rendering_state.acceleration_structure->ui.viewport.render_list->tasks.push_back(task_copy); +} + +void +rti_file_view::render_aabb(const rti_render_task &base_task, rti_aabb aabb, bool fill) +{ + rti_render_task task = base_task; + task.type = rti_render_task_type_lines; + task.vertex_buffer = fill ? app->filled_cube_vertex_buffer.get() : app->cube_vertex_buffer.get(); + task.first_vertex = 0; + task.vertex_count = fill ? RTI_FILLED_CUBE_VERTEX_COUNT : RTI_CUBE_VERTEX_COUNT; + + rti_mat4 aabb_matrix; + aabb_matrix.elements[0 + 0 * 4] = aabb.max.x - aabb.min.x; + aabb_matrix.elements[1 + 1 * 4] = aabb.max.y - aabb.min.y; + aabb_matrix.elements[2 + 2 * 4] = aabb.max.z - aabb.min.z; + aabb_matrix.elements[0 + 3 * 4] = aabb.min.x; + aabb_matrix.elements[1 + 3 * 4] = aabb.min.y; + aabb_matrix.elements[2 + 3 * 4] = aabb.min.z; + task.params.transform = rti_mat4::mul(task.params.transform, aabb_matrix); + + render(task); +} + +struct rti_tracked_render_state { + rti_render_task last_render_task; + bool first_task = true; + std::vector draws; + uint32_t first_param = 0; +}; + +static void +rti_flush_draws(rti_file_view *view, rti_tracked_render_state *state, const uint32_t *flags_override, + rti_mat4 transform, uint32_t task_index) +{ + if (state->draws.empty()) + return; + + rti_push_constants consts = { + .transform = transform, + .first_param = state->first_param, + .flags = flags_override ? *flags_override : state->last_render_task.flags, + }; + + vkCmdPushConstants(view->app->command_buffer, view->app->renderer_pipeline_layout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(consts), &consts); + + view->app->vkCmdDrawMultiEXT(view->app->command_buffer, state->draws.size(), state->draws.data(), 1, 0, + sizeof(VkMultiDrawInfoEXT)); + + state->draws.clear(); + state->first_param = task_index; +} + +static void +rti_process_render_list(rti_file_view *view, rti_render_list *list, rti_tracked_render_state *state, + const VkRenderingInfo *rendering_info, const uint32_t *flags_override, rti_mat4 transform) +{ + for (uint32_t i = 0; i < list->tasks.size(); i++) { + const rti_render_task &task = list->tasks[i]; + + if (task.wait_for_prev) { + rti_flush_draws(view, state, flags_override, transform, i); + + vkCmdEndRendering(view->app->command_buffer); + + VkImageMemoryBarrier barriers[] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = view->app->queue_family_index, + .dstQueueFamilyIndex = view->app->queue_family_index, + .image = view->rendering_state.acceleration_structure->ui.viewport.cb->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = view->app->queue_family_index, + .dstQueueFamilyIndex = view->app->queue_family_index, + .image = view->rendering_state.acceleration_structure->ui.viewport.db->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + + vkCmdPipelineBarrier(view->app->command_buffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + 0, 0, nullptr, 0, nullptr, ARRAY_SIZE(barriers), barriers); + + vkCmdBeginRendering(view->app->command_buffer, rendering_info); + } + + if (task.type == rti_render_task_type_render_list) { + rti_flush_draws(view, state, flags_override, transform, i); + state->first_task = true; + + rti_process_render_list(view, task.render_list, state, rendering_info, + task.override_flags ? &task.flags : nullptr, task.params.transform); + + state->first_param = i + 1; + state->first_task = true; + + continue; + } + + if (state->first_task || task.type != state->last_render_task.type) { + rti_flush_draws(view, state, flags_override, transform, i); + + VkPipeline pipeline = VK_NULL_HANDLE; + switch (task.type) { + case rti_render_task_type_solid: + pipeline = view->app->fill_pipeline; + break; + case rti_render_task_type_wireframe: + pipeline = view->app->wireframe_pipeline; + break; + case rti_render_task_type_thick_wireframe: + pipeline = view->app->thick_wireframe_pipeline; + break; + case rti_render_task_type_lines: + pipeline = view->app->lines_pipeline; + break; + case rti_render_task_type_thick_lines: + pipeline = view->app->thick_lines_pipeline; + break; + default: + assert(false); + } + vkCmdBindPipeline(view->app->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + vkCmdBindDescriptorSets(view->app->command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, + view->app->renderer_pipeline_layout, 0, 1, &list->descriptor_set, 0, nullptr); + } + + if (state->first_task || task.vertex_buffer != state->last_render_task.vertex_buffer) { + rti_flush_draws(view, state, flags_override, transform, i); + + VkDeviceSize vertex_buffer_offset = 0; + vkCmdBindVertexBuffers(view->app->command_buffer, 0, 1, &task.vertex_buffer->buffer, &vertex_buffer_offset); + } + + if (task.flags != state->last_render_task.flags) + rti_flush_draws(view, state, flags_override, transform, i); + + if (state->draws.size() >= view->app->max_multi_draw_count) + rti_flush_draws(view, state, flags_override, transform, i); + + VkMultiDrawInfoEXT draw = { + .firstVertex = task.first_vertex, + .vertexCount = task.vertex_count, + }; + state->draws.push_back(draw); + + state->last_render_task = task; + state->first_task = false; + } + + rti_flush_draws(view, state, flags_override, transform, list->tasks.size() - 1); +} + +void +rti_file_view::render_viewport() +{ + if (!rendering_state.render) + return; + + rendering_state.acceleration_structure->ui.viewport.render_list->build(); + + VkRenderingAttachmentInfo color_attachment = { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = rendering_state.acceleration_structure->ui.viewport.cb->image_view, + .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = + { + .color = + { + .float32 = {1, 1, 1, 1}, + }, + }, + }; + + VkRenderingAttachmentInfo depth_attachment = { + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = rendering_state.acceleration_structure->ui.viewport.db->image_view, + .imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = + { + .depthStencil = + { + .depth = 1, + .stencil = 0, + }, + }, + }; + + uint32_t viewport_width = rendering_state.viewport_size.x; + uint32_t viewport_height = rendering_state.viewport_size.y; + + VkRenderingInfo rendering_info = { + .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, + .renderArea = + { + .extent = + { + .width = viewport_width, + .height = viewport_height, + }, + }, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment, + .pDepthAttachment = &depth_attachment, + }; + + vkCmdBeginRendering(app->command_buffer, &rendering_info); + + VkViewport viewport = { + .width = (float)viewport_width, + .height = (float)viewport_height, + .minDepth = 0.0, + .maxDepth = 1.0, + }; + vkCmdSetViewport(app->command_buffer, 0, 1, &viewport); + + VkRect2D scissor = { + .extent = + { + .width = viewport_width, + .height = viewport_height, + }, + }; + vkCmdSetScissor(app->command_buffer, 0, 1, &scissor); + + /* Set load ops to load in case rendering needs to be restarted to sync. */ + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + rti_tracked_render_state state; + rti_process_render_list(this, rendering_state.acceleration_structure->ui.viewport.render_list.get(), &state, + &rendering_info, nullptr, rti_mat4()); + rendering_state.acceleration_structure->ui.viewport.render_list->tasks.clear(); + + vkCmdEndRendering(app->command_buffer); + + VkImageMemoryBarrier transitions[] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = rendering_state.acceleration_structure->ui.viewport.cb->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = rendering_state.acceleration_structure->ui.viewport.resolved_image->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + + vkCmdPipelineBarrier(app->command_buffer, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, ARRAY_SIZE(transitions), + transitions); + + VkImageResolve resolve_region = { + .srcSubresource = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1, + }, + .dstSubresource = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .layerCount = 1, + }, + .extent = + { + .width = viewport_width, + .height = viewport_height, + .depth = 1, + }, + }; + vkCmdResolveImage(app->command_buffer, rendering_state.acceleration_structure->ui.viewport.cb->image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + rendering_state.acceleration_structure->ui.viewport.resolved_image->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &resolve_region); + + VkImageMemoryBarrier transitions2[] = { + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = rendering_state.acceleration_structure->ui.viewport.resolved_image->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_NONE, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = app->queue_family_index, + .dstQueueFamilyIndex = app->queue_family_index, + .image = rendering_state.acceleration_structure->ui.viewport.cb->image, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + + vkCmdPipelineBarrier(app->command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, + nullptr, 0, nullptr, ARRAY_SIZE(transitions2), transitions2); + + ImTextureRef viewport_tex_ref = rendering_state.acceleration_structure->ui.viewport.surface->descriptor_set; + ImGui::Image(viewport_tex_ref, rendering_state.viewport_size); +} + +void +rti_file_view::end_viewport() +{ + if (!rendering_state.render) + return; + + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + + float basis_guizmo_radius = 60; + float axis_length = basis_guizmo_radius * 0.6; + float axis_thickness = 3; + float axis_label_radius = 8; + + rti_basis_guizmo_axis_description axis_descriptions[] = { + { + .label = "X", + .end = {1, 0, 0, 1}, + .color = ImVec4(0.8, 0.0, 0.0, 1.0), + }, + { + .label = "Y", + .end = {0, 1, 0, 1}, + .color = ImVec4(0.0, 0.8, 0.0, 1.0), + }, + { + .label = "Z", + .end = {0, 0, 1, 1}, + .color = ImVec4(0.0, 0.0, 0.8, 1.0), + }, + }; + + rti_mat4 view_projection_matrix = rti_mat4::mul( + rendering_state.projection_matrix, rti_mat4::mul(rendering_state.inv_camera_rotation, basis_transforms[up_axis])); + rti_mat4 inv_view_projection_matrix; + util_invert_mat4x4(inv_view_projection_matrix.elements, view_projection_matrix.elements); + + ImVec2 basis_guizmo_center = + ImVec2(rendering_state.viewport_offset.x + rendering_state.viewport_size.x - basis_guizmo_radius, + rendering_state.viewport_offset.y + basis_guizmo_radius); + + rti_vec4 ndc_basis_guizmo_center = { + .x = 1 - basis_guizmo_radius * 2 / rendering_state.viewport_size.x, + .y = -1 + basis_guizmo_radius * 2 / rendering_state.viewport_size.y, + .z = 1, + .w = 1, + }; + rti_vec4 global_basis_guizmo_center = rti_mat4::mul_vec4(inv_view_projection_matrix, ndc_basis_guizmo_center); + global_basis_guizmo_center.x /= global_basis_guizmo_center.w; + global_basis_guizmo_center.y /= global_basis_guizmo_center.w; + global_basis_guizmo_center.z /= global_basis_guizmo_center.w; + + rti_vec4 ndc_basis_guizmo_corner = { + .x = 1 + (axis_length - basis_guizmo_radius) * 2 / rendering_state.viewport_size.x, + .y = -1 + basis_guizmo_radius * 2 / rendering_state.viewport_size.y, + .z = 1, + .w = 1, + }; + rti_vec4 global_basis_guizmo_corner = rti_mat4::mul_vec4(inv_view_projection_matrix, ndc_basis_guizmo_corner); + global_basis_guizmo_corner.x /= global_basis_guizmo_corner.w; + global_basis_guizmo_corner.y /= global_basis_guizmo_corner.w; + global_basis_guizmo_corner.z /= global_basis_guizmo_corner.w; + + rti_vec3 global_test_axis = { + .x = global_basis_guizmo_corner.x - global_basis_guizmo_center.x, + .y = global_basis_guizmo_corner.y - global_basis_guizmo_center.y, + .z = global_basis_guizmo_corner.z - global_basis_guizmo_center.z, + }; + + float global_axis_length = sqrt(global_test_axis.x * global_test_axis.x + global_test_axis.y * global_test_axis.y + + global_test_axis.z * global_test_axis.z); + + for (uint32_t j = 0; j < ARRAY_SIZE(axis_descriptions); j++) { + rti_vec4 end = { + .x = global_basis_guizmo_center.x + axis_descriptions[j].end.x * global_axis_length, + .y = global_basis_guizmo_center.y + axis_descriptions[j].end.y * global_axis_length, + .z = global_basis_guizmo_center.z + axis_descriptions[j].end.z * global_axis_length, + .w = 1, + }; + end = rti_mat4::mul_vec4(view_projection_matrix, end); + end.x = (end.x / end.w + 1.0) / 2 * rendering_state.viewport_size.x + rendering_state.viewport_offset.x; + end.y = (end.y / end.w + 1.0) / 2 * rendering_state.viewport_size.y + rendering_state.viewport_offset.y; + end.z = end.z / end.w; + axis_descriptions[j].transformed_end = end; + } + + qsort(axis_descriptions, ARRAY_SIZE(axis_descriptions), sizeof(axis_descriptions[0]), + rti_basis_guizmo_axis_description_compare); + + ImGui::PushFont(nullptr, axis_label_radius * 2); + for (uint32_t j = 0; j < ARRAY_SIZE(axis_descriptions); j++) { + ImVec2 screen_space_end = ImVec2(axis_descriptions[j].transformed_end.x, axis_descriptions[j].transformed_end.y); + + ImU32 color = ImGui::ColorConvertFloat4ToU32(axis_descriptions[j].color); + draw_list->AddLine(basis_guizmo_center, screen_space_end, color, axis_thickness); + draw_list->AddCircleFilled(screen_space_end, axis_label_radius, color); + + const char *label = axis_descriptions[j].label; + ImVec2 x_label_size = ImGui::CalcTextSize(label); + draw_list->AddText(ImVec2(screen_space_end.x - x_label_size.x / 2, screen_space_end.y - x_label_size.y / 2), + ImGui::ColorConvertFloat4ToU32(ImVec4(1, 1, 1, 1)), label); + } + ImGui::PopFont(); + + if (rendering_state.acceleration_structure->ui.camera_type == rti_camera_type_pivot) { + ImVec2 pivot_center = ImGui::GetMousePos(); + if (rendering_state.acceleration_structure->ui.camera_pivot_valid) { + rti_vec4 pos4 = { + .x = rendering_state.camera_pivot.x, + .y = rendering_state.camera_pivot.y, + .z = rendering_state.camera_pivot.z, + .w = 1, + }; + pos4 = rti_mat4::mul_vec4(rti_mat4::transpose(basis_transforms[up_axis]), pos4); + pos4 = rti_mat4::mul_vec4(rendering_state.view_projection_matrix, pos4); + pivot_center.x = + ((pos4.x / pos4.w) + 1.0) / 2.0 * rendering_state.viewport_size.x + rendering_state.viewport_offset.x; + pivot_center.y = + ((pos4.y / pos4.w) + 1.0) / 2.0 * rendering_state.viewport_size.y + rendering_state.viewport_offset.y; + } + + float pivot_radius = 6; + float pivot_thickness = 1; + ImU32 pivot_color = ImGui::ColorConvertFloat4ToU32(ImVec4(0, 0, 0, pivot_thickness)); + + draw_list->AddCircle(pivot_center, pivot_radius, pivot_color, 0, 1); + draw_list->AddLine(ImVec2(pivot_center.x - pivot_radius / sqrt(2), pivot_center.y - pivot_radius / sqrt(2)), + ImVec2(pivot_center.x + pivot_radius / sqrt(2), pivot_center.y + pivot_radius / sqrt(2)), + pivot_color, pivot_thickness); + draw_list->AddLine(ImVec2(pivot_center.x - pivot_radius / sqrt(2), pivot_center.y + pivot_radius / sqrt(2)), + ImVec2(pivot_center.x + pivot_radius / sqrt(2), pivot_center.y - pivot_radius / sqrt(2)), + pivot_color, pivot_thickness); + } + + /* Take a reference of the viewport state so the GPU resources are kept alive while they are used. */ + rendering_state.acceleration_structure->ui.pending_viewports.resize(app->imgui_window.SemaphoreCount); + rendering_state.acceleration_structure->ui.pending_viewports[app->imgui_window.SemaphoreIndex] = + rendering_state.acceleration_structure->ui.viewport; + + ImGui::End(); +} + +void +rti_file_view::draw_point(rti_vec3 pos, rti_vec3 color, float stroke) +{ + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + + rti_vec4 pos4 = {pos.x, pos.y, pos.z, 1}; + pos4 = rti_mat4::mul_vec4(rendering_state.view_projection_matrix, pos4); + pos4.x = ((pos4.x / pos4.w) + 1.0) / 2.0 * rendering_state.viewport_size.x; + pos4.y = ((pos4.y / pos4.w) + 1.0) / 2.0 * rendering_state.viewport_size.y; + if (pos4.z > 0) { + draw_list->AddCircleFilled( + ImVec2(rendering_state.viewport_offset.x + pos4.x, rendering_state.viewport_offset.y + pos4.y), stroke, + ImGui::ColorConvertFloat4ToU32(ImVec4(color.x, color.y, color.z, 1))); + } +} + +void +rti_file_view::draw_line(rti_vec3 start, rti_vec3 end, rti_vec3 color, float stroke) +{ + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + + rti_vec4 start4 = {start.x, start.y, start.z, 1}; + start4 = rti_mat4::mul_vec4(rendering_state.view_projection_matrix, start4); + + rti_vec4 end4 = {end.x, end.y, end.z, 1}; + end4 = rti_mat4::mul_vec4(rendering_state.view_projection_matrix, end4); + + if (start4.z < 0 && end4.z < 0) + return; + + if (start4.z < 0) { + /* 0 = start.z + t * (end.z - start.z) => t = -start.z / (end.z - start.z) */ + float t = -start4.z / (end4.z - start4.z); + start4.x += (end4.x - start4.x) * t; + start4.y += (end4.y - start4.y) * t; + start4.z += (end4.z - start4.z) * t; + start4.w += (end4.w - start4.w) * t; + } + + if (end4.z < 0) { + /* 0 = end.z + t * (start.z - end.z) => t = -end.z / (start.z - end.z) */ + float t = -end4.z / (start4.z - end4.z); + end4.x += (start4.x - end4.x) * t; + end4.y += (start4.y - end4.y) * t; + end4.z += (start4.z - end4.z) * t; + end4.w += (start4.w - end4.w) * t; + } + + start4.x = ((start4.x / start4.w) + 1.0) / 2.0 * rendering_state.viewport_size.x; + start4.y = ((start4.y / start4.w) + 1.0) / 2.0 * rendering_state.viewport_size.y; + + end4.x = ((end4.x / end4.w) + 1.0) / 2.0 * rendering_state.viewport_size.x; + end4.y = ((end4.y / end4.w) + 1.0) / 2.0 * rendering_state.viewport_size.y; + + draw_list->AddLine( + ImVec2(rendering_state.viewport_offset.x + start4.x, rendering_state.viewport_offset.y + start4.y), + ImVec2(rendering_state.viewport_offset.x + end4.x, rendering_state.viewport_offset.y + end4.y), + ImGui::ColorConvertFloat4ToU32(ImVec4(color.x, color.y, color.z, 1)), stroke); +} + +void +rti_file_view::begin_bvh_tree() +{ + ImGui::Begin("BVH"); +} +void +rti_file_view::end_bvh_tree() +{ + ImGui::End(); +} + +void +rti_file_view::begin_node_info() +{ + ImGui::Begin("node info"); +} + +void +rti_file_view::end_node_info() +{ + ImGui::End(); +} diff --git a/src/tool/rtinspector/rti_file_view.h b/src/tool/rtinspector/rti_file_view.h new file mode 100644 index 00000000000..15f70622aa6 --- /dev/null +++ b/src/tool/rtinspector/rti_file_view.h @@ -0,0 +1,259 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#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 tasks; + std::shared_ptr 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 surface = nullptr; + std::shared_ptr resolved_image = nullptr; + std::shared_ptr cb = nullptr; + std::shared_ptr db = nullptr; + uint32_t width = 0; + uint32_t height = 0; + std::shared_ptr 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 pending_viewports; + + std::shared_ptr vertex_buffer = nullptr; + std::shared_ptr 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> acceleration_structures; + std::map 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 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_create_file_view(rti_app *app, const char *file); + +/* Create functions for rti_file_views specialized for drivers. */ + +std::unique_ptr rti_create_file_view_radv(); diff --git a/src/tool/rtinspector/rti_util.cpp b/src/tool/rtinspector/rti_util.cpp new file mode 100644 index 00000000000..4d35feeb9fd --- /dev/null +++ b/src/tool/rtinspector/rti_util.cpp @@ -0,0 +1,428 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "rti_util.h" +#include "rti_app.h" + +#include +#include +#include +#include +#include + +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_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 buffer = std::make_shared(); + 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_create_backed_image(rti_app *app, VkExtent3D extent, const std::vector &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 image = std::make_shared(); + 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, + }; +} diff --git a/src/tool/rtinspector/rti_util.h b/src/tool/rtinspector/rti_util.h new file mode 100644 index 00000000000..84f1583b04b --- /dev/null +++ b/src/tool/rtinspector/rti_util.h @@ -0,0 +1,262 @@ +/* + * Copyright © 2026 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "util/macros.h" +#include + +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_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_create_backed_image(rti_app *app, VkExtent3D extent, + const std::vector &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 diff --git a/src/tool/rtinspector/shaders/meson.build b/src/tool/rtinspector/shaders/meson.build new file mode 100644 index 00000000000..67dc437b029 --- /dev/null +++ b/src/tool/rtinspector/shaders/meson.build @@ -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 diff --git a/src/tool/rtinspector/shaders/renderer.frag b/src/tool/rtinspector/shaders/renderer.frag new file mode 100644 index 00000000000..bc94d099d2f --- /dev/null +++ b/src/tool/rtinspector/shaders/renderer.frag @@ -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; + } +} diff --git a/src/tool/rtinspector/shaders/renderer.vert b/src/tool/rtinspector/shaders/renderer.vert new file mode 100644 index 00000000000..c8292b7bdb7 --- /dev/null +++ b/src/tool/rtinspector/shaders/renderer.vert @@ -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; +} \ No newline at end of file diff --git a/src/tool/rtinspector/shaders/rti_shader_interface.h b/src/tool/rtinspector/shaders/rti_shader_interface.h new file mode 100644 index 00000000000..e8d71b5fa19 --- /dev/null +++ b/src/tool/rtinspector/shaders/rti_shader_interface.h @@ -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