#include #ifdef AURORA_ENABLE_GX #include "gfx/common.hpp" #include "gx/fifo.hpp" #include "imgui.hpp" #include "webgpu/gpu.hpp" #include #endif #include "input.hpp" #include "internal.hpp" #include "window.hpp" #include #include #include "tracy/Tracy.hpp" namespace aurora { AuroraConfig g_config; uint32_t g_sdlCustomEventsStart; namespace { Module Log("aurora"); #ifdef AURORA_ENABLE_GX // GPU using webgpu::g_device; using webgpu::g_queue; using webgpu::g_surface; #endif #ifdef AURORA_ENABLE_GX constexpr std::array PreferredBackendOrder{ #ifdef ENABLE_BACKEND_WEBGPU BACKEND_WEBGPU, #endif #ifdef DAWN_ENABLE_BACKEND_D3D12 BACKEND_D3D12, #endif #ifdef DAWN_ENABLE_BACKEND_METAL BACKEND_METAL, #endif #ifdef DAWN_ENABLE_BACKEND_VULKAN BACKEND_VULKAN, #endif #ifdef DAWN_ENABLE_BACKEND_D3D11 BACKEND_D3D11, #endif // #ifdef DAWN_ENABLE_BACKEND_DESKTOP_GL // BACKEND_OPENGL, // #endif // #ifdef DAWN_ENABLE_BACKEND_OPENGLES // BACKEND_OPENGLES, // #endif #ifdef DAWN_ENABLE_BACKEND_NULL BACKEND_NULL, #endif }; #else constexpr std::array PreferredBackendOrder{}; #endif bool g_initialFrame = false; #ifdef AURORA_ENABLE_GX struct PresentViewport { float x; float y; float width; float height; }; PresentViewport calculate_present_viewport(uint32_t surface_width, uint32_t surface_height, uint32_t content_width, uint32_t content_height) { if (surface_width == 0 || surface_height == 0 || content_width == 0 || content_height == 0) { return {}; } uint32_t viewport_width = surface_width; uint32_t viewport_height = std::min( surface_height, std::max(1u, static_cast(std::lround(static_cast(viewport_width) * static_cast(content_height) / static_cast(content_width))))); if (viewport_height == surface_height) { viewport_width = std::min( surface_width, std::max(1u, static_cast(std::lround(static_cast(viewport_height) * static_cast(content_width) / static_cast(content_height))))); } return { .x = static_cast((surface_width - viewport_width) / 2), .y = static_cast((surface_height - viewport_height) / 2), .width = static_cast(viewport_width), .height = static_cast(viewport_height), }; } #endif AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexcept { g_config = config; Log.info("Aurora initializing"); if (g_config.appName == nullptr) { g_config.appName = "Aurora"; } else { g_config.appName = strdup(g_config.appName); } if (g_config.configPath == nullptr) { g_config.configPath = SDL_GetPrefPath(nullptr, g_config.appName); } else { g_config.configPath = strdup(g_config.configPath); } if (g_config.msaa == 0) { g_config.msaa = 1; } if (g_config.maxTextureAnisotropy == 0) { g_config.maxTextureAnisotropy = 16; } ASSERT(window::initialize(), "Error initializing window"); g_sdlCustomEventsStart = SDL_RegisterEvents(2); ASSERT(g_sdlCustomEventsStart, "Failed to allocate user events: {}", SDL_GetError()); #ifdef AURORA_ENABLE_GX /* Attempt to create a window using the calling application's desired backend */ AuroraBackend selectedBackend = config.desiredBackend; bool windowCreated = false; if (selectedBackend != BACKEND_AUTO && window::create_window(selectedBackend)) { if (webgpu::initialize(selectedBackend)) { windowCreated = true; } else { window::destroy_window(); } } if (!windowCreated) { for (const auto backendType : PreferredBackendOrder) { selectedBackend = backendType; if (!window::create_window(selectedBackend)) { continue; } if (webgpu::initialize(selectedBackend)) { windowCreated = true; break; } else { window::destroy_window(); } } } ASSERT(windowCreated, "Error creating window: {}", SDL_GetError()); // Initialize SDL_Renderer for ImGui when we can't use a Dawn backend if (webgpu::g_backendType == wgpu::BackendType::Null) { ASSERT(window::create_renderer(), "Failed to initialize SDL renderer: {}", SDL_GetError()); } #else AuroraBackend selectedBackend = BACKEND_NULL; ASSERT(window::create_window(BACKEND_NULL), "Error creating window: {}", SDL_GetError()); ASSERT(window::create_renderer(), "Failed to initialize SDL renderer: {}", SDL_GetError()); #endif window::show_window(); #ifdef AURORA_ENABLE_GX gfx::initialize(); imgui::create_context(); #endif const auto size = window::get_window_size(); Log.info("Using framebuffer size {}x{} scale {}", size.fb_width, size.fb_height, size.scale); #ifdef AURORA_ENABLE_GX if (g_config.imGuiInitCallback != nullptr) { g_config.imGuiInitCallback(&size); } imgui::initialize(); #endif g_initialFrame = true; g_config.desiredBackend = selectedBackend; return { .backend = selectedBackend, .configPath = g_config.configPath, .window = window::get_sdl_window(), .windowSize = size, }; } void aurora_set_log_level(AuroraLogLevel level) noexcept { g_config.logLevel = level; } #ifdef AURORA_ENABLE_GX wgpu::TextureView g_currentView; #endif void shutdown() noexcept { #ifdef AURORA_ENABLE_GX g_currentView = {}; imgui::shutdown(); gfx::shutdown(); webgpu::shutdown(); #endif input::shutdown(); window::shutdown(); } const AuroraEvent* update() noexcept { ZoneScoped; if (g_initialFrame) { g_initialFrame = false; input::initialize(); } return window::poll_events(); } bool begin_frame() noexcept { ZoneScoped; #ifdef AURORA_ENABLE_GX if (!g_surface) { webgpu::refresh_surface(true); if (!g_surface) { return false; } } wgpu::SurfaceTexture surfaceTexture; g_surface.GetCurrentTexture(&surfaceTexture); switch (surfaceTexture.status) { case wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal: g_currentView = surfaceTexture.texture.CreateView(); break; case wgpu::SurfaceGetCurrentTextureStatus::Timeout: Log.warn("Surface texture acquisition timed out"); return false; case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal: case wgpu::SurfaceGetCurrentTextureStatus::Outdated: Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceTexture.status)); webgpu::refresh_surface(false); return false; case wgpu::SurfaceGetCurrentTextureStatus::Lost: case wgpu::SurfaceGetCurrentTextureStatus::Error: Log.warn("Surface texture is {}, recreating surface", magic_enum::enum_name(surfaceTexture.status)); webgpu::refresh_surface(true); return false; default: Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceTexture.status)); return false; } imgui::new_frame(window::get_window_size()); gfx::begin_frame(); #endif return true; } void end_frame() noexcept { ZoneScoped; #ifdef AURORA_ENABLE_GX gx::fifo::drain(); const auto encoderDescriptor = wgpu::CommandEncoderDescriptor{ .label = "Redraw encoder", }; auto encoder = g_device.CreateCommandEncoder(&encoderDescriptor); gfx::end_frame(encoder); gfx::render(encoder); { const std::array attachments{ wgpu::RenderPassColorAttachment{ .view = g_currentView, .loadOp = wgpu::LoadOp::Clear, .storeOp = wgpu::StoreOp::Store, }, }; const wgpu::RenderPassDescriptor renderPassDescriptor{ .label = "Post render pass", .colorAttachmentCount = attachments.size(), .colorAttachments = attachments.data(), }; auto pass = encoder.BeginRenderPass(&renderPassDescriptor); // Copy EFB -> XFB (swapchain) pass.SetPipeline(webgpu::g_CopyPipeline); pass.SetBindGroup(0, webgpu::g_CopyBindGroup, 0, nullptr); const auto viewport = calculate_present_viewport( webgpu::g_graphicsConfig.surfaceConfiguration.width, webgpu::g_graphicsConfig.surfaceConfiguration.height, webgpu::g_frameBuffer.size.width, webgpu::g_frameBuffer.size.height); pass.SetViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1); pass.Draw(3); imgui::render(pass); pass.End(); } const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"}; const auto buffer = encoder.Finish(&cmdBufDescriptor); g_queue.Submit(1, &buffer); auto presentStatus = g_surface.Present(); if (presentStatus != wgpu::Status::Success) { Log.warn("Surface present failed: {}", static_cast(presentStatus)); } g_currentView = {}; TracyPlotConfig("aurora: lastVertSize", tracy::PlotFormatType::Memory, false, true, 0); TracyPlotConfig("aurora: lastUniformSize", tracy::PlotFormatType::Memory, false, true, 0); TracyPlotConfig("aurora: lastIndexSize", tracy::PlotFormatType::Memory, false, true, 0); TracyPlotConfig("aurora: lastStorageSize", tracy::PlotFormatType::Memory, false, true, 0); TracyPlotConfig("aurora: lastTextureUploadSize", tracy::PlotFormatType::Memory, false, true, 0); TracyPlot("aurora: queuedPipelines", static_cast(gfx::g_stats.queuedPipelines)); TracyPlot("aurora: createdPipelines", static_cast(gfx::g_stats.createdPipelines)); TracyPlot("aurora: drawCallCount", static_cast(gfx::g_stats.drawCallCount)); TracyPlot("aurora: mergedDrawCallCount", static_cast(gfx::g_stats.mergedDrawCallCount)); TracyPlot("aurora: lastVertSize", static_cast(gfx::g_stats.lastVertSize)); TracyPlot("aurora: lastUniformSize", static_cast(gfx::g_stats.lastUniformSize)); TracyPlot("aurora: lastIndexSize", static_cast(gfx::g_stats.lastIndexSize)); TracyPlot("aurora: lastStorageSize", static_cast(gfx::g_stats.lastStorageSize)); TracyPlot("aurora: lastTextureUploadSize", static_cast(gfx::g_stats.lastTextureUploadSize)); #endif } } // namespace } // namespace aurora // C API bindings AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config) { return aurora::initialize(argc, argv, *config); } void aurora_shutdown() { aurora::shutdown(); } const AuroraEvent* aurora_update() { return aurora::update(); } bool aurora_begin_frame() { return aurora::begin_frame(); } void aurora_end_frame() { aurora::end_frame(); } AuroraBackend aurora_get_backend() { return aurora::g_config.desiredBackend; } const AuroraBackend* aurora_get_available_backends(size_t* count) { *count = aurora::PreferredBackendOrder.size(); return aurora::PreferredBackendOrder.data(); }