diff --git a/editor/main.cpp b/editor/main.cpp index b673768..7e0eb49 100644 --- a/editor/main.cpp +++ b/editor/main.cpp @@ -20,7 +20,7 @@ class Editor : public sophia::Application { panels().emplace(); sophia::RenderTargetHandle sceneTarget = resources().create(); - setSceneTarget(sceneTarget); + views().create({.scene = scenes().create(), .target = sceneTarget}); panels().emplace(ui(), sceneTarget); panels().setDefaultLayout( diff --git a/engine/include/sophia/application.hpp b/engine/include/sophia/application.hpp index f2a2542..a1e5c6c 100644 --- a/engine/include/sophia/application.hpp +++ b/engine/include/sophia/application.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ class Application { ResourceRegistryRef resources(); PanelRegistry& panels() { return this->panelRegistry; } SceneRegistry& scenes() { return this->sceneRegistry; } + SceneViewRegistry& views() { return this->sceneViewRegistry; } UI& ui() { return this->_ui; } DebugUI& debugUI() { return this->_debugUI; } @@ -58,16 +60,6 @@ class Application { void drawMesh(MeshHandle handle, const glm::mat4& transform); - /** - * !TEMP !TODO remove this func eventually - * Chooses where the scene is rendered. An invalid handle (the default) means - * the swapchain; a target created with - * ResourceRegistryRef::create() redirects it offscreen, and the - * swapchain then only receives the UI. FrameInfo::currentFramebufferExtent - * and the camera projection follow the target's size. - */ - void setSceneTarget(RenderTargetHandle handle); - private: friend class Canvas; @@ -79,6 +71,7 @@ class Application { PanelRegistry panelRegistry; SceneRegistry sceneRegistry; + SceneViewRegistry sceneViewRegistry; UI _ui; DebugUI _debugUI; diff --git a/engine/include/sophia/handles.hpp b/engine/include/sophia/handles.hpp index 242535f..298b51b 100644 --- a/engine/include/sophia/handles.hpp +++ b/engine/include/sophia/handles.hpp @@ -37,10 +37,12 @@ class Mesh; class Panel; class RenderTarget; class Scene; +struct SceneView; using MeshHandle = Handle; using PanelHandle = Handle; using RenderTargetHandle = Handle; using SceneHandle = Handle; +using SceneViewHandle = Handle; } // namespace sophia diff --git a/engine/include/sophia/scene_view.hpp b/engine/include/sophia/scene_view.hpp new file mode 100644 index 0000000..7d83da9 --- /dev/null +++ b/engine/include/sophia/scene_view.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +namespace sophia { + +enum class ClearFlags : u8 { + None = 0, + Color = 1 << 0, + Depth = 1 << 1, + Stencil = 1 << 2, + All = Color | Depth | Stencil +}; + +struct SceneView { + SceneHandle scene = INVALID_HANDLE; + RenderTargetHandle target = INVALID_HANDLE; + Entity camera; + + vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f}; + + vec4 clearColor{0.f, 0.f, 0.f, 1.f}; + ClearFlags clearFlags = ClearFlags::All; + + u32 priority = 0; // Lower priority renders first + u32 layerMask = 0xFFFFFFFF; + + bool enabled = true; // Disabled views are kept but not rendered +}; + +} // namespace sophia diff --git a/engine/include/sophia/scene_view_registry.hpp b/engine/include/sophia/scene_view_registry.hpp new file mode 100644 index 0000000..fc2ef54 --- /dev/null +++ b/engine/include/sophia/scene_view_registry.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace sophia { + +/** + * Every live SceneView. The application renders all enabled views each frame, + * in ascending priority order, before the UI. + */ +class SceneViewRegistry { + public: + SceneViewRegistry(const SceneViewRegistry&) = delete; + SceneViewRegistry& operator=(const SceneViewRegistry&) = delete; + + SceneViewRegistry() = default; + ~SceneViewRegistry() = default; + + SceneViewHandle create(const SceneView& view = {}) { + isRenderQueueDirty = true; + return pool.create(view); + } + + const SceneView& get(SceneViewHandle handle) const { + return pool.get(handle); + } + + SceneView& get(SceneViewHandle handle) { + isRenderQueueDirty = true; + return pool.get(handle); + } + + void release(SceneViewHandle handle) { + isRenderQueueDirty = true; + pool.release(handle); + } + + bool isAlive(SceneViewHandle handle) const { return pool.isAlive(handle); } + + u32 size() const { return pool.size(); } + + template + void forEach(Fn&& fn) { + isRenderQueueDirty = true; + pool.forEach(std::forward(fn)); + } + + template + void forEach(Fn&& fn) const { + pool.forEach(std::forward(fn)); + } + + const std::vector& GetRenderQueue() const { + if (isRenderQueueDirty) { + rebuildRenderQueue(); + } + return renderQueue; + } + + private: + void rebuildRenderQueue() const { + renderQueue.clear(); + pool.forEachWithHandle([&](SceneViewHandle handle, const SceneView& view) { + if (view.enabled) { + renderQueue.push_back(handle); + } + }); + + std::ranges::stable_sort(renderQueue, {}, [this](SceneViewHandle handle) { + return pool.get(handle).priority; + }); + isRenderQueueDirty = false; + } + + Pool pool{"SceneView"}; + + mutable std::vector renderQueue; + mutable bool isRenderQueueDirty = true; +}; + +} // namespace sophia diff --git a/engine/src/application.cpp b/engine/src/application.cpp index cf7f12f..8891820 100644 --- a/engine/src/application.cpp +++ b/engine/src/application.cpp @@ -1,9 +1,11 @@ #include +#include #include #include #include #include +#include #include #include #include @@ -15,6 +17,7 @@ #include "rendering/model_render_system.hpp" #include "rendering/render_context.hpp" #include "rendering/render_graph.hpp" +#include "rendering/scene_renderer.hpp" #include "resources/mesh.hpp" #include "resources/render_target.hpp" #include "resources/resource_registry.hpp" @@ -43,35 +46,8 @@ std::vector commandLineArgs; constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f}; -void beginColorPass(vk::CommandBuffer commandBuffer, vk::ImageView view, - vk::Extent2D extent) { - vk::ClearValue clearValue{}; - clearValue.color = vk::ClearColorValue{CLEAR_COLOR}; - - vk::RenderingAttachmentInfo colorAttachment{}; - colorAttachment.imageView = view; - colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal; - colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; - colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; - colorAttachment.clearValue = clearValue; - - vk::RenderingInfo renderingInfo{}; - renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent}; - renderingInfo.layerCount = 1; - renderingInfo.colorAttachmentCount = 1; - renderingInfo.pColorAttachments = &colorAttachment; - - commandBuffer.beginRendering(renderingInfo); - - vk::Viewport viewport{0.0f, - 0.0f, - static_cast(extent.width), - static_cast(extent.height), - 0.0f, - 1.0f}; - vk::Rect2D scissor{{0, 0}, extent}; - commandBuffer.setViewport(0, viewport); - commandBuffer.setScissor(0, scissor); +bool hasFlag(ClearFlags flags, ClearFlags flag) { + return (static_cast(flags) & static_cast(flag)) != 0; } } // namespace @@ -88,6 +64,8 @@ class Application::Impl { void run() { ModelRenderSystem modelRenderSystem{device, renderContext}; + SceneRenderer sceneRenderer{this->application.sceneRegistry, + this->resourceRegistry}; ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(), renderContext.getImageCount(), this->resourceRegistry}; @@ -106,6 +84,7 @@ class Application::Impl { auto& renderTargetPool = this->resourceRegistry.renderTargetPool; PanelRegistry& panelRegistry = this->application.panelRegistry; + const SceneViewRegistry& sceneViews = this->application.sceneViewRegistry; imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); @@ -128,17 +107,10 @@ class Application::Impl { // frame's work (or ImGui's references to the targets) is recorded. renderTargetPool.applyPendingResizes(); - RenderTarget* sceneTarget = - this->sceneTargetHandle.isValid() - ? &renderTargetPool.get(this->sceneTargetHandle) - : nullptr; - vk::Extent2D extent = renderContext.getExtent(); - vk::Extent2D sceneExtent = - sceneTarget ? sceneTarget->getExtent() : extent; FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime, - glm::vec2(static_cast(sceneExtent.width), - static_cast(sceneExtent.height)), + glm::vec2(static_cast(extent.width), + static_cast(extent.height)), this->application.camera}; this->application.onUpdate(frameInfo); this->application.camera.setProjection( @@ -166,31 +138,54 @@ class Application::Impl { this->drawCalls.end()); this->drawCalls.clear(); - if (sceneTarget) { - Image& targetImage = sceneTarget->getImage(); - targetImage.transitionLayout(commandBuffer, - vk::ImageLayout::eColorAttachmentOptimal); - beginColorPass(commandBuffer, targetImage.getView(), sceneExtent); - modelRenderSystem.render(commandBuffer, instances, frameInfo); - commandBuffer.endRendering(); - targetImage.transitionLayout(commandBuffer, - vk::ImageLayout::eShaderReadOnlyOptimal); - } - RenderGraph graph; ImageRef backbuffer = graph.importImage(renderContext.getCurrentImage(), Access::Present); + std::map viewTargets; + for (SceneViewHandle handle : sceneViews.GetRenderQueue()) { + const SceneView& view = sceneViews.get(handle); + if (!this->application.sceneRegistry.isAlive(view.scene) || + !renderTargetPool.isAlive(view.target)) { + continue; + } + + auto [it, inserted] = viewTargets.try_emplace(view.target); + if (inserted) { + it->second = + graph.importImage(renderTargetPool.get(view.target).getImage(), + Access::SampledRead); + } + + const vec4& color = view.clearColor; + graph.addPass( + "scene view", + [&](RenderGraph::PassBuilder& pass) { + pass.writeColor(it->second, + hasFlag(view.clearFlags, ClearFlags::Color) + ? vk::AttachmentLoadOp::eClear + : vk::AttachmentLoadOp::eLoad, + {color.r, color.g, color.b, color.a}); + }, + [&sceneRenderer, &sceneViews, handle](vk::CommandBuffer) { + const SceneView& view = sceneViews.get(handle); + if (view.camera) { + sceneRenderer.render(view); + } + }); + } + graph.addPass( "main", [&](RenderGraph::PassBuilder& pass) { pass.writeColor(backbuffer, vk::AttachmentLoadOp::eClear, CLEAR_COLOR); + for (const auto& [handle, target] : viewTargets) { + pass.sample(target); + } }, [&](vk::CommandBuffer commandBuffer) { - if (!sceneTarget) { - modelRenderSystem.render(commandBuffer, instances, frameInfo); - } + modelRenderSystem.render(commandBuffer, instances, frameInfo); imguiRenderer.endFrame(commandBuffer); }); @@ -237,10 +232,6 @@ class Application::Impl { this->drawCalls.push_back({&mesh, transform}); } - void setSceneTarget(RenderTargetHandle handle) { - this->sceneTargetHandle = handle; - } - ResourceRegistry& getResourceRegistry() { return resourceRegistry; } private: @@ -253,8 +244,6 @@ class Application::Impl { std::vector> meshes; std::vector drawCalls; - - RenderTargetHandle sceneTargetHandle = RenderTargetHandle::invalid(); }; void Application::setCommandLineArgs(int argc, const char* const* argv) { @@ -281,10 +270,6 @@ void Application::pushMesh(std::vector vertices, impl->pushMesh(std::move(vertices), std::move(indices)); } -void Application::setSceneTarget(RenderTargetHandle handle) { - impl->setSceneTarget(handle); -} - void Application::clearMeshes() { impl->clearMeshes(); } void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {