From 0b09aeb5d9408b891d0940939ea797a8acc03b6f Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Sat, 26 Sep 2026 02:03:11 -0700 Subject: [PATCH] More changes --- engine/include/sophia/components.hpp | 17 +++++++ engine/include/sophia/scene_registry.hpp | 2 + engine/include/sophia/scene_view_registry.hpp | 4 ++ engine/src/application.cpp | 31 +++++++++---- engine/src/rendering/scene_renderer.cpp | 46 ++++++------------- engine/src/rendering/scene_renderer.hpp | 9 ++-- 6 files changed, 64 insertions(+), 45 deletions(-) diff --git a/engine/include/sophia/components.hpp b/engine/include/sophia/components.hpp index eae6ebd..ab44893 100644 --- a/engine/include/sophia/components.hpp +++ b/engine/include/sophia/components.hpp @@ -66,6 +66,14 @@ struct SpriteComponent { // Higher draws on top. Ties are broken by the transform's position.z. s32 sortOrder = 0; + + // Maps the unit quad (0, 0)-(1, 1) to world space: to the sprite's + // rectangle around its pivot, then by `transform`. + glm::mat4 getModel(const TransformComponent& transform) const { + return transform.mat4() * + glm::translate(glm::mat4{1.f}, glm::vec3{-pivot * size, 0.f}) * + glm::scale(glm::mat4{1.f}, glm::vec3{size, 1.f}); + } }; struct CameraComponent { @@ -98,6 +106,15 @@ struct CameraComponent { return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, ortho.nearPlane, ortho.farPlane); } + + // World -> clip space for this camera placed by `transform`. The view + // ignores the transform's scale; zoom is the projection's job. + glm::mat4 getViewProjection(const TransformComponent& transform, + float aspect) const { + glm::mat4 world = glm::translate(glm::mat4{1.f}, transform.position) * + glm::mat4_cast(transform.rotation); + return getProjection(aspect) * glm::inverse(world); + } }; } // namespace sophia diff --git a/engine/include/sophia/scene_registry.hpp b/engine/include/sophia/scene_registry.hpp index 2403b79..4e923ed 100644 --- a/engine/include/sophia/scene_registry.hpp +++ b/engine/include/sophia/scene_registry.hpp @@ -19,6 +19,8 @@ class SceneRegistry { const Scene& get(SceneHandle handle) const { return pool.get(handle); } Scene& get(SceneHandle handle) { return pool.get(handle); } + const Scene& peek(SceneHandle handle) const { return pool.get(handle); } + void release(SceneHandle handle) { pool.release(handle); } bool isAlive(SceneHandle handle) const { return pool.isAlive(handle); } diff --git a/engine/include/sophia/scene_view_registry.hpp b/engine/include/sophia/scene_view_registry.hpp index fc2ef54..7a6fd6d 100644 --- a/engine/include/sophia/scene_view_registry.hpp +++ b/engine/include/sophia/scene_view_registry.hpp @@ -35,6 +35,10 @@ class SceneViewRegistry { return pool.get(handle); } + const SceneView& peek(SceneViewHandle handle) const { + return pool.get(handle); + } + void release(SceneViewHandle handle) { isRenderQueueDirty = true; pool.release(handle); diff --git a/engine/src/application.cpp b/engine/src/application.cpp index dadc490..c265566 100644 --- a/engine/src/application.cpp +++ b/engine/src/application.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -87,11 +88,12 @@ class Application::Impl { PanelRegistry& panelRegistry = this->application.panelRegistry; SceneRegistry& sceneRegistry = this->application.sceneRegistry; SceneViewRegistry& sceneViewRegistry = this->application.sceneViewRegistry; - const SceneViewRegistry& sceneViewRegistryConst = - this->application.sceneViewRegistry; imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout()); + // Views already warned about having no camera, so each warns once. + std::set viewsWithoutCamera; + auto startTime = std::chrono::high_resolution_clock::now(); auto currentTime = startTime; u32 frameIndex = 0; @@ -148,8 +150,8 @@ class Application::Impl { graph.importImage(renderContext.getCurrentImage(), Access::Present); std::map viewTargets; - for (SceneViewHandle handle : sceneViewRegistryConst.GetRenderQueue()) { - const SceneView& view = sceneViewRegistryConst.get(handle); + for (SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) { + const SceneView& view = sceneViewRegistry.peek(handle); if (!sceneRegistry.isAlive(view.scene) || !renderTargetPool.isAlive(view.target)) { continue; @@ -172,12 +174,23 @@ class Application::Impl { : vk::AttachmentLoadOp::eLoad, {color.r, color.g, color.b, color.a}); }, - [&sceneRenderer, &sceneViewRegistryConst, - handle](vk::CommandBuffer commandBuffer) { - const SceneView& view = sceneViewRegistryConst.get(handle); - if (view.camera) { - sceneRenderer.render(commandBuffer, view); + [&sceneRenderer, &sceneRegistry, &sceneViewRegistry, + &viewsWithoutCamera, handle](vk::CommandBuffer commandBuffer) { + const SceneView& view = sceneViewRegistry.peek(handle); + 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) { + 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); }); } diff --git a/engine/src/rendering/scene_renderer.cpp b/engine/src/rendering/scene_renderer.cpp index 71d6ee6..5a631dd 100644 --- a/engine/src/rendering/scene_renderer.cpp +++ b/engine/src/rendering/scene_renderer.cpp @@ -14,19 +14,13 @@ SceneRenderer::SceneRenderer(const SceneRegistry& sceneRegistry, resources{resources}, spriteRenderSystem{device, colorFormat, resources} {} -SceneRenderer::~SceneRenderer() {} - void SceneRenderer::beginFrame(u32 frameIndex) { this->spriteRenderSystem.beginFrame(frameIndex); } void SceneRenderer::render(vk::CommandBuffer commandBuffer, - const SceneView& sceneView) { - const Scene& scene = this->sceneRegistry.get(sceneView.scene); - const entt::registry& registry = scene.registry; - - entt::entity camera = sceneView.camera; - SOPHIA_ASSERT(registry.all_of(camera), + const SceneView& sceneView, Entity camera) { + SOPHIA_ASSERT(camera.hasComponent(), "camera does not have camera component!"); vk::Extent2D extent = @@ -34,17 +28,12 @@ void SceneRenderer::render(vk::CommandBuffer commandBuffer, float aspect = static_cast(extent.width) / static_cast(extent.height); - // The view matrix ignores the camera's scale; zoom is the projection's job. - glm::mat4 cameraWorld{1.f}; - if (const auto* transform = registry.try_get(camera)) { - cameraWorld = glm::translate(glm::mat4{1.f}, transform->position) * - glm::mat4_cast(transform->rotation); - } glm::mat4 viewProjection = - registry.get(camera).getProjection(aspect) * - glm::inverse(cameraWorld); + camera.getComponent().getViewProjection( + camera.getComponent(), aspect); - renderSprites(commandBuffer, scene, viewProjection); + renderSprites(commandBuffer, this->sceneRegistry.get(sceneView.scene), + viewProjection); // TODO: meshes. Group MeshComponent entities by mesh and draw each group // (instanced), as sprites are grouped by texture. @@ -53,8 +42,7 @@ void SceneRenderer::render(vk::CommandBuffer commandBuffer, void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer, const Scene& scene, const glm::mat4& viewProjection) { - std::vector& items = this->spriteDrawItems; - items.clear(); + this->spriteDrawItems.clear(); auto view = scene.registry.view(); @@ -63,28 +51,22 @@ void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer, ? sprite.texture : this->resources.whiteTexture; - // Unit quad -> sprite rectangle around its pivot -> world. - glm::mat4 model = - transform.mat4() * - glm::translate(glm::mat4{1.f}, - glm::vec3{-sprite.pivot * sprite.size, 0.f}) * - glm::scale(glm::mat4{1.f}, glm::vec3{sprite.size, 1.f}); - - items.push_back({sprite.sortOrder, - transform.position.z, - texture, - {model, sprite.color, sprite.uvRect}}); + this->spriteDrawItems.push_back( + {sprite.sortOrder, + transform.position.z, + texture, + {sprite.getModel(transform), sprite.color, sprite.uvRect}}); } // Texture is the last key so that sprites whose order doesn't matter // relative to each other still end up in as few batches as possible. - std::ranges::sort(items, {}, [](const SpriteDrawItem& item) { + std::ranges::sort(this->spriteDrawItems, {}, [](const SpriteDrawItem& item) { return std::tuple{item.sortOrder, item.depth, item.texture}; }); this->spriteInstances.clear(); this->spriteBatches.clear(); - for (const SpriteDrawItem& item : items) { + for (const SpriteDrawItem& item : this->spriteDrawItems) { auto index = static_cast(this->spriteInstances.size()); this->spriteInstances.push_back(item.instance); diff --git a/engine/src/rendering/scene_renderer.hpp b/engine/src/rendering/scene_renderer.hpp index e7c50a4..fe6cf32 100644 --- a/engine/src/rendering/scene_renderer.hpp +++ b/engine/src/rendering/scene_renderer.hpp @@ -21,14 +21,15 @@ class SceneRenderer { SceneRenderer(const SceneRegistry& sceneRegistry, ResourceRegistry& resources, Device& device, vk::Format colorFormat); - ~SceneRenderer(); // Call once per frame, before any render(). void beginFrame(u32 frameIndex); - // Records `sceneView` into `commandBuffer`. Rendering into the view's - // target must already have begun. - void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView); + // Records `sceneView` as seen through `camera` (an entity of the view's + // scene with a CameraComponent) into `commandBuffer`. Rendering into the + // view's target must already have begun. + void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView, + Entity camera); private: struct SpriteDrawItem {