diff --git a/sandbox/main.cpp b/sandbox/main.cpp index af1b5ce..8ab5895 100644 --- a/sandbox/main.cpp +++ b/sandbox/main.cpp @@ -1,18 +1,295 @@ #define GLFW_INCLUDE_VULKAN #include +#include #include #include +#include #include +#include #include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include "renderer/imgui_renderer.hpp" +#include "renderer/model_render_system.hpp" +#include "renderer/render_context.hpp" +#include "renderer/render_graph.hpp" +#include "renderer/scene_renderer.hpp" +#include "resources/mesh.hpp" +#include "resources/render_target.hpp" +#include "resources/resource_registry.hpp" +#include "vulkan/buffer.hpp" +#include "vulkan/device.hpp" +#include "vulkan/pipelines/graphics_pipeline.hpp" +#include "vulkan/shader.hpp" +#include "window.hpp" constexpr uint32_t WIDTH = 1280; constexpr uint32_t HEIGHT = 720; -constexpr const char* NAME = ""; +constexpr const char* NAME = "sandbox"; -int main() { return EXIT_SUCCESS; } +constexpr sophia::ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; + +bool hasFlag(sophia::ClearFlags flags, sophia::ClearFlags flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +int main(int argc, const char** argv) { + std::vector args; + args.assign(argv, argv + argc); + + std::cout << __FILE__ << "::" << __LINE__ << '\n'; + + sophia::Window window{WIDTH, HEIGHT, NAME}; + sophia::Device device{&window}; + sophia::RenderContext renderContext{window, device}; + sophia::ResourceRegistry resourceRegistry{ + device, renderContext.getImageFormat()}; // remove? + + // remove? + std::vector> meshes; + + // remove? + std::vector drawCalls; + + // todo: figure out which to remove + sophia::PanelRegistry panelRegistry; + sophia::SceneRegistry sceneRegistry; + sophia::SceneViewRegistry sceneViewRegistry; + sophia::ConsoleCommandRegistry consoleCommandRegistry; + + sophia::DebugUI debugUI{panelRegistry, consoleCommandRegistry}; + debugUI.showFrameStats(true); + + sophia::ModelRenderSystem modelRenderSystem{device, renderContext}; + sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device, + renderContext.getImageFormat()}; + sophia::ImGuiRenderer imguiRenderer{ + window, device, renderContext.getImageFormat(), + renderContext.getImageCount(), resourceRegistry}; + + consoleCommandRegistry.registerCommand( + "reset_layout", "Restore the default panel layout", + [&imguiRenderer](const std::vector&) { + imguiRenderer.resetLayout(); + return std::string{"Layout reset"}; + }); + + auto& meshPool = resourceRegistry.meshPool; + auto& renderTargetPool = resourceRegistry.renderTargetPool; + + imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); + + // Views already warned about, so each warns once. + std::set viewsWithoutCamera; + std::set viewsOverwritingOthers; + + auto startTime = std::chrono::high_resolution_clock::now(); + auto currentTime = startTime; + sophia::u32 frameIndex = 0; + + while (!window.shouldClose()) { + glfwPollEvents(); + + auto newTime = std::chrono::high_resolution_clock::now(); + double deltaTime = + std::chrono::duration(newTime - currentTime).count(); + currentTime = newTime; + + double elapsedTime = + std::chrono::duration(currentTime - startTime).count(); + + // Resizing waits for the GPU, so it has to happen before any of this + // frame's work (or ImGui's references to the targets) is recorded. + renderTargetPool.applyPendingResizes(); + + vk::Extent2D extent = renderContext.getExtent(); + sophia::FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime, + glm::vec2(static_cast(extent.width), + static_cast(extent.height))}; + + // Canvas draws in window pixels, origin at the center. + // TODO: replace with Debug/Gizmos drawn into scene views through their + // cameras. + glm::mat4 canvasProjection = + glm::ortho(-frameInfo.currentFramebufferExtent.x / 2.f, + frameInfo.currentFramebufferExtent.x / 2.f, + -frameInfo.currentFramebufferExtent.y / 2.f, + frameInfo.currentFramebufferExtent.y / 2.f); + + vk::CommandBuffer commandBuffer = renderContext.beginFrame(); + if (!commandBuffer) { + continue; + } + + imguiRenderer.beginFrame(); + sceneRenderer.beginFrame(renderContext.getFrameIndex()); + + panelRegistry.update(frameInfo); + + std::vector instances; + instances.reserve(meshes.size() + drawCalls.size()); + for (const std::unique_ptr& mesh : meshes) { + instances.push_back({mesh.get(), glm::mat4{1.f}}); + } + instances.insert(instances.end(), drawCalls.begin(), drawCalls.end()); + drawCalls.clear(); + + sophia::RenderGraph graph{renderContext.getTracyContext()}; + sophia::ImageRef backbuffer = graph.importImage( + renderContext.getCurrentImage(), sophia::Access::Present); + + struct ViewTarget { + sophia::ImageRef color; + sophia::ImageRef depth; + }; + std::map viewTargets; + // Views without a target render straight into the backbuffer. + std::optional swapchainTarget; + for (sophia::SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) { + const sophia::SceneView& view = sceneViewRegistry.peek(handle); + bool toSwapchain = !view.target.isValid(); + if (!sceneRegistry.isAlive(view.scene) || + !sceneRegistry.get(view.scene).isVisible() || + (!toSwapchain && !renderTargetPool.isAlive(view.target))) { + continue; + } + + ViewTarget target; + vk::Extent2D targetExtent; + // Whether this is the first view this frame to render to its target. + bool firstOnTarget = false; + if (toSwapchain) { + firstOnTarget = !swapchainTarget; + if (!swapchainTarget) { + swapchainTarget = { + backbuffer, + graph.importImage(renderContext.getCurrentDepthImage(), + sophia::Access::DepthWrite)}; + } + target = *swapchainTarget; + targetExtent = renderContext.getExtent(); + } else { + sophia::RenderTarget& renderTarget = renderTargetPool.get(view.target); + auto [it, inserted] = viewTargets.try_emplace(view.target); + firstOnTarget = inserted; + if (inserted) { + it->second = {graph.importImage(renderTarget.getImage(), + sophia::Access::SampledRead), + graph.importImage(renderTarget.getDepthImage(), + sophia::Access::DepthWrite)}; + } + target = it->second; + targetExtent = renderTarget.getExtent(); + } + + if (!firstOnTarget && + hasFlag(view.clearFlags, sophia::ClearFlags::Color) && + viewsOverwritingOthers.insert(handle).second) { + sophia::log::warning( + "scene view clears its target after another view already " + "rendered to it this frame, hiding that view (give it clearFlags " + "without Color, or render it first with a lower priority)"); + } + + const sophia::vec4& color = view.clearColor; + graph.addPass( + "scene view " + std::to_string(handle.index), + [&](sophia::RenderGraph::PassBuilder& pass) { + pass.writeColor(target.color, + hasFlag(view.clearFlags, sophia::ClearFlags::Color) + ? vk::AttachmentLoadOp::eClear + : vk::AttachmentLoadOp::eLoad, + {color.r, color.g, color.b, color.a}); + pass.writeDepth(target.depth, + hasFlag(view.clearFlags, sophia::ClearFlags::Depth) + ? vk::AttachmentLoadOp::eClear + : vk::AttachmentLoadOp::eLoad); + }, + [&sceneRenderer, &sceneRegistry, &sceneViewRegistry, + &viewsWithoutCamera, handle, + targetExtent](vk::CommandBuffer commandBuffer) { + ZoneScopedN("scene view pass"); + const sophia::SceneView& view = sceneViewRegistry.peek(handle); + sophia::Entity camera = + view.camera ? view.camera + : sceneRegistry.get(view.scene).getActiveCamera(); + // No camera to look through: the target is just cleared. + if (!camera) { + if (viewsWithoutCamera.insert(handle).second) { + sophia::log::warning( + "scene view has no camera and its scene has no active " + "camera; it will only be cleared (see " + "Scene::createCamera)"); + } + return; + } + sceneRenderer.render(commandBuffer, view, camera, targetExtent); + }); + } + + graph.addPass( + "main", + [&](sophia::RenderGraph::PassBuilder& pass) { + // Keep what scene views drew into the backbuffer. + pass.writeColor(backbuffer, + swapchainTarget ? vk::AttachmentLoadOp::eLoad + : vk::AttachmentLoadOp::eClear, + CLEAR_COLOR); + for (const auto& [handle, target] : viewTargets) { + pass.sample(target.color); + } + }, + [&](vk::CommandBuffer commandBuffer) { + ZoneScopedN("main pass"); + modelRenderSystem.render(commandBuffer, instances, canvasProjection); + + imguiRenderer.endFrame(commandBuffer); + }); + + graph.execute(commandBuffer); + + renderContext.endFrame(); + FrameMark; + + frameIndex++; + } + + device.waitIdle(); + + auto endTime = std::chrono::high_resolution_clock::now(); + double totalRuntime = + std::chrono::duration(endTime - startTime).count(); + sophia::log::info("Application ran for {:.2f}s", totalRuntime); + + return EXIT_SUCCESS; +}