#include #ifdef AURORA_ENABLE_GX #include "gfx/common.hpp" #include "gx/fifo.hpp" #include "imgui.hpp" #include "webgpu/gpu.hpp" #include "webgpu/gpu_prof.hpp" #include #endif #ifdef AURORA_ENABLE_RMLUI #include "rmlui.hpp" #endif #include "input.hpp" #include "internal.hpp" #include "window.hpp" #include #include #include "system_info.hpp" #include "tracy/Tracy.hpp" namespace aurora { AuroraConfig g_config; uint32_t g_sdlCustomEventsStart; char g_gameName[4]; 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; AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexcept { g_config = config; Log.info("Aurora initializing"); log_system_information(); if (g_config.appName == nullptr) { g_config.appName = "Aurora"; } else { g_config.appName = strdup(g_config.appName); } if (g_config.userPath == nullptr) { g_config.userPath = SDL_GetPrefPath(nullptr, g_config.appName); } else { g_config.userPath = strdup(g_config.userPath); } if (g_config.cachePath == nullptr) { g_config.cachePath = SDL_GetPrefPath(nullptr, g_config.appName); } else { g_config.cachePath = strdup(g_config.cachePath); } if (g_config.resourcesPath == nullptr) { g_config.resourcesPath = SDL_GetBasePath(); } else { g_config.resourcesPath = strdup(g_config.resourcesPath); } 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()); ASSERT(window::initialize_event_watch(), "Error initializing SDL event watch"); #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, config.allowCpuAdapter)) { 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, config.allowCpuAdapter)) { 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 #ifdef AURORA_ENABLE_RMLUI rmlui::initialize(size); #endif g_initialFrame = true; g_config.desiredBackend = selectedBackend; return { .backend = selectedBackend, .userPath = g_config.userPath, .cachePath = g_config.cachePath, .window = window::get_sdl_window(), .windowSize = size, }; } void shutdown() noexcept { #ifdef AURORA_ENABLE_RMLUI rmlui::shutdown(); #endif #ifdef AURORA_ENABLE_GX 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 { window::SurfaceLock surfaceLock; if (!window::is_presentable()) { webgpu::release_surface(); return false; } if (window::is_paused()) { return false; } if (!g_surface) { webgpu::refresh_surface(true); if (!g_surface) { return false; } } } imgui::new_frame(window::get_window_size()); if (!gfx::begin_frame()) { return false; } #endif return true; } void end_frame() noexcept { ZoneScoped; #ifdef AURORA_ENABLE_GX gx::fifo::drain(); gfx::finish(); auto imguiDrawData = imgui::freeze(); const auto& presentSource = webgpu::present_source(); const auto viewport = webgpu::calculate_present_viewport(webgpu::g_graphicsConfig.surfaceConfiguration.width, webgpu::g_graphicsConfig.surfaceConfiguration.height, presentSource.size.width, presentSource.size.height); wgpu::BindGroup rmlBindGroup; #if AURORA_ENABLE_RMLUI if (rmlui::is_initialized()) { rmlBindGroup = rmlui::record_frame(viewport); } #endif gfx::end_frame([rmlBindGroup = std::move(rmlBindGroup), viewport, imguiDrawData = std::move(imguiDrawData)](wgpu::CommandEncoder& encoder) { window::SurfaceLock surfaceLock; wgpu::Texture currentTexture; wgpu::TextureView currentView; auto surfaceStatus = wgpu::SurfaceGetCurrentTextureStatus::Error; if (window::is_presentable() && g_surface) { ZoneScopedN("Acquire texture"); wgpu::SurfaceTexture surfaceTexture; g_surface.GetCurrentTexture(&surfaceTexture); surfaceStatus = surfaceTexture.status; if (surfaceStatus == wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal) { currentTexture = std::move(surfaceTexture.texture); currentView = currentTexture.CreateView(); } } const bool canPresent = currentTexture && currentView; if (canPresent) { wgpu::BindGroup presentBindGroup; if (rmlBindGroup) { presentBindGroup = rmlBindGroup; } else { const auto& resampledSource = webgpu::resample_present_source(encoder, viewport); presentBindGroup = webgpu::create_copy_bind_group(resampledSource); } { const std::array attachments{ wgpu::RenderPassColorAttachment{ .view = currentView, .loadOp = wgpu::LoadOp::Clear, .storeOp = wgpu::StoreOp::Store, }, }; const wgpu::RenderPassDescriptor renderPassDescriptor{ .label = "EFB copy render pass", .colorAttachmentCount = attachments.size(), .colorAttachments = attachments.data(), .timestampWrites = webgpu::gpu_prof::pass_writes("Present blit"), }; const auto pass = encoder.BeginRenderPass(&renderPassDescriptor); // Copy EFB -> XFB (swapchain) pass.SetPipeline(webgpu::g_CopyPipeline); pass.SetBindGroup(0, presentBindGroup, 0, nullptr); pass.SetViewport(viewport.left, viewport.top, viewport.width, viewport.height, viewport.znear, viewport.zfar); pass.Draw(3); pass.End(); } { const std::array attachments{ wgpu::RenderPassColorAttachment{ .view = currentView, .loadOp = wgpu::LoadOp::Load, .storeOp = wgpu::StoreOp::Store, }, }; const wgpu::RenderPassDescriptor renderPassDescriptor{ .label = "ImGui render pass", .colorAttachmentCount = attachments.size(), .colorAttachments = attachments.data(), .timestampWrites = webgpu::gpu_prof::pass_writes("ImGui"), }; const auto pass = encoder.BeginRenderPass(&renderPassDescriptor); pass.SetViewport(0.f, 0.f, static_cast(webgpu::g_graphicsConfig.surfaceConfiguration.width), static_cast(webgpu::g_graphicsConfig.surfaceConfiguration.height), 0.f, 1.f); imgui::render(pass, imguiDrawData); pass.End(); } } else { Log.info("Skipping present; window not presentable"); } webgpu::gpu_prof::frame_end(encoder); const wgpu::CommandBufferDescriptor cmdBufDescriptor{.label = "Redraw command buffer"}; const auto buffer = encoder.Finish(&cmdBufDescriptor); { ZoneScopedN("Queue Submit"); g_queue.Submit(1, &buffer); } webgpu::gpu_prof::after_submit(); if (canPresent && g_surface) { ZoneScopedN("Present"); auto presentStatus = g_surface.Present(); if (presentStatus != wgpu::Status::Success) { Log.warn("Surface present failed: {}", static_cast(presentStatus)); webgpu::release_surface(); } } else if (g_surface) { switch (surfaceStatus) { case wgpu::SurfaceGetCurrentTextureStatus::Timeout: Log.warn("Surface texture acquisition timed out"); break; case wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal: case wgpu::SurfaceGetCurrentTextureStatus::Outdated: Log.info("Surface texture is {}, reconfiguring swapchain", magic_enum::enum_name(surfaceStatus)); webgpu::refresh_surface(false); break; case wgpu::SurfaceGetCurrentTextureStatus::Lost: Log.warn("Surface texture is {}, releasing surface", magic_enum::enum_name(surfaceStatus)); webgpu::release_surface(); break; case wgpu::SurfaceGetCurrentTextureStatus::Error: Log.warn("Surface texture is {}, dropping surface", magic_enum::enum_name(surfaceStatus)); g_surface = {}; break; default: if (!window::is_presentable()) { webgpu::release_surface(); } else { Log.error("Failed to get surface texture: {}", magic_enum::enum_name(surfaceStatus)); } break; } } gfx::after_submit(); 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) { if (count != nullptr) { *count = aurora::PreferredBackendOrder.size(); } return aurora::PreferredBackendOrder.data(); } void aurora_set_log_level(AuroraLogLevel level) { aurora::g_config.logLevel = level; } void aurora_set_pause_on_focus_lost(bool value) { aurora::g_config.pauseOnFocusLost = value; } void aurora_set_background_input(bool value) { aurora::g_config.allowJoystickBackgroundEvents = value; aurora::window::set_background_input(value); } void aurora_set_resampler(AuroraSampler sampler) { #ifdef AURORA_ENABLE_GX aurora::webgpu::set_resampler(sampler); #else (void)sampler; #endif }