Files
Claude f7ca5ecbd6 Keep debug labels out of the ImGui passes in normal builds
Aurora's own debug groups only exist when AURORA_GFX_DEBUG_GROUPS is
defined, but the three ImGui passes (desktop overlay, host-frame overlay
and the headset settings panel) pushed and popped theirs in every build.
An Adreno 740 Android driver (512.676) faults in
vkCmdEndDebugUtilsLabelEXT on the first overlay pass (KartPad #321); the
Quest 3 uses the same GPU. Gate all three like the rest of Aurora.

Ported from chrissotraidis/wiicompiled (KartPad) 420b828, extended to the
two extra passes this fork has.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rLZ24cFcqTNBmb4w2dpSq
2026-10-05 15:55:43 +00:00

386 lines
12 KiB
C++

#include "imgui.hpp"
#include <cstddef>
#include <cmath>
#include <filesystem>
#include <mutex>
#include <string>
#include <vector>
#include <webgpu/webgpu_cpp.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_render.h>
#include "fs_helper.hpp"
#include "internal.hpp"
#include "stereo_overlay.hpp"
#include "webgpu/gpu.hpp"
#include "window.hpp"
#define IMGUI_IMPL_WEBGPU_BACKEND_DAWN
#include "backends/imgui_impl_sdl3.h"
#include "backends/imgui_impl_sdlrenderer3.h"
#include "backends/imgui_impl_wgpu.h"
#include "tracy/Tracy.hpp"
namespace aurora::imgui {
static float g_scale;
static std::string g_imguiLog{};
static bool g_useSdlRenderer = false;
// Set once ImGui::Render() has produced this frame's draw data. Interpolation encodes up to four
// ImGui passes per frame, and every one of them used to rebuild the draw lists from scratch.
static bool g_frameDataBuilt = true;
// Host-owned frames (see imgui.hpp). Once the host begins one, aurora never calls new_frame() or
// ImGui::Render() itself; the sealed frame carries the host's copy of the draw data instead.
static bool g_hostFrames = false;
static bool g_hostFrameOpen = false;
static std::vector<SDL_Texture*> g_sdlTextures;
static std::vector<wgpu::Texture> g_wgpuTextures;
// Set by the producer, latched by the seal.
static std::mutex g_stereoOverlayMutex;
static StereoOverlay g_stereoOverlay;
void remove_legacy_ini_file(const char* basePath) noexcept {
if (basePath == nullptr || *basePath == '\0') {
return;
}
std::error_code ec;
std::filesystem::remove(fs_path_from_string(basePath) / "imgui.ini", ec);
}
void create_context() noexcept {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
remove_legacy_ini_file(g_config.userPath);
remove_legacy_ini_file(g_config.cachePath);
g_imguiLog = std::string{g_config.cachePath} + "/imgui.log";
io.IniFilename = nullptr;
io.LogFilename = g_imguiLog.c_str();
ImGui::LoadIniSettingsFromMemory("", 0);
io.WantSaveIniSettings = false;
}
void initialize() noexcept {
ZoneScoped;
SDL_Renderer* renderer = window::get_sdl_renderer();
ImGui_ImplSDL3_InitForSDLRenderer(window::get_sdl_window(), renderer);
g_useSdlRenderer = renderer != nullptr;
if (g_useSdlRenderer) {
ImGui_ImplSDLRenderer3_Init(renderer);
} else {
ImGui_ImplWGPU_InitInfo info;
info.Device = webgpu::g_device.Get();
info.RenderTargetFormat = static_cast<WGPUTextureFormat>(webgpu::g_graphicsConfig.surfaceConfiguration.format);
// Interpolation records up to four ImGui passes in one command buffer, so keep three logical
// frames of renderer resources to stop them overwriting each other's vertex/index buffers.
info.NumFramesInFlight = 12;
ImGui_ImplWGPU_Init(&info);
}
}
void shutdown() noexcept {
ZoneScoped;
// Startup can fail before either backend initializes. A context alone does
// not mean its renderer/platform backend owns resources to release.
if (ImGui::GetCurrentContext() != nullptr) {
ImGuiIO& io = ImGui::GetIO();
if (io.BackendRendererUserData != nullptr) {
if (g_useSdlRenderer) {
ImGui_ImplSDLRenderer3_Shutdown();
} else {
ImGui_ImplWGPU_Shutdown();
}
}
if (io.BackendPlatformUserData != nullptr) {
ImGui_ImplSDL3_Shutdown();
}
ImGui::DestroyContext();
}
for (const auto& texture : g_sdlTextures) {
SDL_DestroyTexture(texture);
}
g_sdlTextures.clear();
g_wgpuTextures.clear();
g_useSdlRenderer = false;
g_scale = 0.f;
g_frameDataBuilt = true;
}
void process_event(const SDL_Event& event) noexcept {
auto renderEvent = event;
if (g_useSdlRenderer) {
if (SDL_Renderer* renderer = window::get_sdl_renderer()) {
SDL_ConvertEventToRenderCoordinates(renderer, &renderEvent);
}
}
ImGui_ImplSDL3_ProcessEvent(&renderEvent);
}
bool wants_capture_event(const SDL_Event& event) noexcept {
if (ImGui::GetCurrentContext() == nullptr) {
return false;
}
const ImGuiIO& io = ImGui::GetIO();
switch (event.type) {
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_UP:
case SDL_EVENT_FINGER_CANCELED:
return io.WantCaptureMouse;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
case SDL_EVENT_TEXT_INPUT:
return io.WantCaptureKeyboard || io.WantTextInput;
default:
return false;
}
}
void new_frame(const AuroraWindowSize& size) noexcept {
ZoneScoped;
ImVec2 framebufferScale{
size.width > 0 ? static_cast<float>(size.native_fb_width) / static_cast<float>(size.width) : 1.0f,
size.height > 0 ? static_cast<float>(size.native_fb_height) / static_cast<float>(size.height) : 1.0f,
};
ImVec2 displaySize{static_cast<float>(size.width), static_cast<float>(size.height)};
if (g_useSdlRenderer) {
if (SDL_Renderer* renderer = window::get_sdl_renderer()) {
float renderScaleX = 1.0f;
float renderScaleY = 1.0f;
SDL_GetRenderScale(renderer, &renderScaleX, &renderScaleY);
if (renderScaleX > 0.0f && renderScaleY > 0.0f &&
(std::fabs(renderScaleX - 1.0f) > 0.0001f || std::fabs(renderScaleY - 1.0f) > 0.0001f)) {
int outputWidth = static_cast<int>(size.native_fb_width);
int outputHeight = static_cast<int>(size.native_fb_height);
SDL_GetRenderOutputSize(renderer, &outputWidth, &outputHeight);
displaySize = {
static_cast<float>(outputWidth) / renderScaleX,
static_cast<float>(outputHeight) / renderScaleY,
};
framebufferScale = {renderScaleX, renderScaleY};
}
}
ImGui_ImplSDLRenderer3_NewFrame();
g_scale = size.scale;
} else {
if (g_scale != size.scale) {
if (g_scale > 0.f) {
ImGui_ImplWGPU_CreateDeviceObjects();
}
g_scale = size.scale;
}
if (!ImGui::GetIO().Fonts->IsBuilt()) {
ImGui_ImplWGPU_CreateDeviceObjects();
}
ImGui_ImplWGPU_NewFrame();
}
ImGui_ImplSDL3_NewFrame();
ImGuiIO& io = ImGui::GetIO();
io.DisplayFramebufferScale = framebufferScale;
ImGui::GetIO().DisplaySize = displaySize;
ImGui::NewFrame();
g_frameDataBuilt = false;
}
void render_frame_data() noexcept {
ZoneScoped;
if (g_frameDataBuilt || g_hostFrames) {
return;
}
ImGui::Render();
auto* data = ImGui::GetDrawData();
data->FramebufferScale = ImGui::GetIO().DisplayFramebufferScale;
g_frameDataBuilt = true;
}
void render(const wgpu::RenderPassEncoder& pass) noexcept {
ZoneScoped;
if (g_hostFrames) {
// The shared context's draw data belongs to the host's current frame now;
// a sealed frame without a host copy has nothing safe to draw.
return;
}
render_frame_data();
auto* data = ImGui::GetDrawData();
if (g_useSdlRenderer) {
SDL_Renderer* renderer = window::get_sdl_renderer();
SDL_RenderClear(renderer);
ImGui_ImplSDLRenderer3_RenderDrawData(data, renderer);
SDL_RenderPresent(renderer);
} else {
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PushDebugGroup("Aurora: Dear Imgui");
#endif
ImGui_ImplWGPU_RenderDrawData(data, pass.Get());
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PopDebugGroup();
#endif
}
}
struct HostFrame {
ImDrawData data{};
std::vector<ImDrawList*> lists;
~HostFrame() {
for (ImDrawList* list : lists) {
IM_DELETE(list);
}
}
};
void host_frame_begin(const AuroraWindowSize& size) noexcept {
g_hostFrames = true;
if (g_hostFrameOpen) {
return;
}
if (!g_frameDataBuilt) {
// aurora started this frame itself before the host took over: adopt it.
g_hostFrameOpen = true;
return;
}
new_frame(size);
g_hostFrameOpen = true;
}
HostFramePtr host_frame_end() noexcept {
ZoneScoped;
if (!g_hostFrameOpen) {
host_frame_begin(window::get_window_size());
}
ImGui::Render();
ImDrawData* source = ImGui::GetDrawData();
source->FramebufferScale = ImGui::GetIO().DisplayFramebufferScale;
auto frame = std::make_shared<HostFrame>();
frame->data = *source;
frame->data.CmdLists.clear();
frame->lists.reserve(static_cast<size_t>(source->CmdListsCount));
for (int i = 0; i < source->CmdListsCount; ++i) {
const ImDrawList* src = source->CmdLists[i];
ImDrawList* copy = IM_NEW(ImDrawList)(src->_Data);
copy->CmdBuffer = src->CmdBuffer;
copy->IdxBuffer = src->IdxBuffer;
copy->VtxBuffer = src->VtxBuffer;
copy->Flags = src->Flags;
frame->lists.push_back(copy);
frame->data.CmdLists.push_back(copy);
}
g_hostFrameOpen = false;
g_frameDataBuilt = true;
return frame;
}
bool host_frames_active() noexcept { return g_hostFrames; }
const ImDrawData* host_frame_draw_data(const HostFrame& frame) noexcept { return &frame.data; }
void render(const wgpu::RenderPassEncoder& pass, const ImDrawData* data) noexcept {
ZoneScoped;
if (g_useSdlRenderer || data == nullptr) {
return;
}
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PushDebugGroup("Aurora: Dear Imgui");
#endif
ImGui_ImplWGPU_RenderDrawData(const_cast<ImDrawData*>(data), pass.Get());
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PopDebugGroup();
#endif
}
StereoOverlay latch_stereo_overlay() noexcept {
std::lock_guard lock(g_stereoOverlayMutex);
return g_stereoOverlay;
}
bool render_draw_data(const wgpu::RenderPassEncoder& pass, ImDrawData* data) noexcept {
ZoneScoped;
// The SDL renderer fallback has no render passes to draw into.
if (g_useSdlRenderer || data == nullptr || ImGui::GetCurrentContext() == nullptr) {
return false;
}
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PushDebugGroup("Aurora: Dear Imgui headset panel");
#endif
ImGui_ImplWGPU_RenderDrawData(data, pass.Get());
#ifdef AURORA_GFX_DEBUG_GROUPS
pass.PopDebugGroup();
#endif
return true;
}
ImTextureID add_texture(uint32_t width, uint32_t height, const uint8_t* data) noexcept {
if (SDL_Renderer* renderer = window::get_sdl_renderer()) {
SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height);
SDL_UpdateTexture(texture, nullptr, data, width * 4);
SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_LINEAR);
g_sdlTextures.push_back(texture);
return reinterpret_cast<ImTextureID>(texture);
}
const wgpu::Extent3D size{
.width = width,
.height = height,
.depthOrArrayLayers = 1,
};
const wgpu::TextureDescriptor textureDescriptor{
.label = "imgui texture",
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst,
.dimension = wgpu::TextureDimension::e2D,
.size = size,
.format = wgpu::TextureFormat::RGBA8Unorm,
.mipLevelCount = 1,
.sampleCount = 1,
};
const wgpu::TextureViewDescriptor textureViewDescriptor{
.label = "imgui texture view",
.format = wgpu::TextureFormat::RGBA8Unorm,
.dimension = wgpu::TextureViewDimension::e2D,
.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED,
.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED,
};
auto texture = webgpu::g_device.CreateTexture(&textureDescriptor);
auto textureView = texture.CreateView(&textureViewDescriptor);
{
const wgpu::TexelCopyTextureInfo dstView{
.texture = texture,
};
const wgpu::TexelCopyBufferLayout dataLayout{
.bytesPerRow = 4 * width,
.rowsPerImage = height,
};
webgpu::g_queue.WriteTexture(&dstView, data, width * height * 4, &dataLayout, &size);
}
g_wgpuTextures.push_back(texture);
return reinterpret_cast<ImTextureID>(textureView.MoveToCHandle());
}
} // namespace aurora::imgui
// C bindings
extern "C" {
ImTextureID aurora_imgui_add_texture(uint32_t width, uint32_t height, const void* rgba8) {
return aurora::imgui::add_texture(width, height, static_cast<const uint8_t*>(rgba8));
}
void aurora_set_stereo_panel_layer(bool enabled) { aurora::stereo_overlay::set_layer_mode(enabled); }
void aurora_imgui_set_stereo_overlay(ImDrawData* drawData, float widthFraction) {
std::lock_guard lock(aurora::imgui::g_stereoOverlayMutex);
aurora::imgui::g_stereoOverlay = {
.drawData = drawData,
.widthFraction = drawData != nullptr ? widthFraction : 0.f,
};
}
}