More changes
This commit is contained in:
@@ -66,6 +66,14 @@ struct SpriteComponent {
|
|||||||
|
|
||||||
// Higher draws on top. Ties are broken by the transform's position.z.
|
// Higher draws on top. Ties are broken by the transform's position.z.
|
||||||
s32 sortOrder = 0;
|
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 {
|
struct CameraComponent {
|
||||||
@@ -98,6 +106,15 @@ struct CameraComponent {
|
|||||||
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
|
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
|
||||||
ortho.nearPlane, ortho.farPlane);
|
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
|
} // namespace sophia
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class SceneRegistry {
|
|||||||
const Scene& get(SceneHandle handle) const { return pool.get(handle); }
|
const Scene& get(SceneHandle handle) const { return pool.get(handle); }
|
||||||
Scene& get(SceneHandle handle) { 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); }
|
void release(SceneHandle handle) { pool.release(handle); }
|
||||||
bool isAlive(SceneHandle handle) const { return pool.isAlive(handle); }
|
bool isAlive(SceneHandle handle) const { return pool.isAlive(handle); }
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class SceneViewRegistry {
|
|||||||
return pool.get(handle);
|
return pool.get(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SceneView& peek(SceneViewHandle handle) const {
|
||||||
|
return pool.get(handle);
|
||||||
|
}
|
||||||
|
|
||||||
void release(SceneViewHandle handle) {
|
void release(SceneViewHandle handle) {
|
||||||
isRenderQueueDirty = true;
|
isRenderQueueDirty = true;
|
||||||
pool.release(handle);
|
pool.release(handle);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
#include <sophia/application.hpp>
|
#include <sophia/application.hpp>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -87,11 +88,12 @@ class Application::Impl {
|
|||||||
PanelRegistry& panelRegistry = this->application.panelRegistry;
|
PanelRegistry& panelRegistry = this->application.panelRegistry;
|
||||||
SceneRegistry& sceneRegistry = this->application.sceneRegistry;
|
SceneRegistry& sceneRegistry = this->application.sceneRegistry;
|
||||||
SceneViewRegistry& sceneViewRegistry = this->application.sceneViewRegistry;
|
SceneViewRegistry& sceneViewRegistry = this->application.sceneViewRegistry;
|
||||||
const SceneViewRegistry& sceneViewRegistryConst =
|
|
||||||
this->application.sceneViewRegistry;
|
|
||||||
|
|
||||||
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
|
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
|
||||||
|
|
||||||
|
// Views already warned about having no camera, so each warns once.
|
||||||
|
std::set<SceneViewHandle> viewsWithoutCamera;
|
||||||
|
|
||||||
auto startTime = std::chrono::high_resolution_clock::now();
|
auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
auto currentTime = startTime;
|
auto currentTime = startTime;
|
||||||
u32 frameIndex = 0;
|
u32 frameIndex = 0;
|
||||||
@@ -148,8 +150,8 @@ class Application::Impl {
|
|||||||
graph.importImage(renderContext.getCurrentImage(), Access::Present);
|
graph.importImage(renderContext.getCurrentImage(), Access::Present);
|
||||||
|
|
||||||
std::map<RenderTargetHandle, ImageRef> viewTargets;
|
std::map<RenderTargetHandle, ImageRef> viewTargets;
|
||||||
for (SceneViewHandle handle : sceneViewRegistryConst.GetRenderQueue()) {
|
for (SceneViewHandle handle : sceneViewRegistry.GetRenderQueue()) {
|
||||||
const SceneView& view = sceneViewRegistryConst.get(handle);
|
const SceneView& view = sceneViewRegistry.peek(handle);
|
||||||
if (!sceneRegistry.isAlive(view.scene) ||
|
if (!sceneRegistry.isAlive(view.scene) ||
|
||||||
!renderTargetPool.isAlive(view.target)) {
|
!renderTargetPool.isAlive(view.target)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -172,12 +174,23 @@ class Application::Impl {
|
|||||||
: vk::AttachmentLoadOp::eLoad,
|
: vk::AttachmentLoadOp::eLoad,
|
||||||
{color.r, color.g, color.b, color.a});
|
{color.r, color.g, color.b, color.a});
|
||||||
},
|
},
|
||||||
[&sceneRenderer, &sceneViewRegistryConst,
|
[&sceneRenderer, &sceneRegistry, &sceneViewRegistry,
|
||||||
handle](vk::CommandBuffer commandBuffer) {
|
&viewsWithoutCamera, handle](vk::CommandBuffer commandBuffer) {
|
||||||
const SceneView& view = sceneViewRegistryConst.get(handle);
|
const SceneView& view = sceneViewRegistry.peek(handle);
|
||||||
if (view.camera) {
|
Entity camera =
|
||||||
sceneRenderer.render(commandBuffer, view);
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,19 +14,13 @@ SceneRenderer::SceneRenderer(const SceneRegistry& sceneRegistry,
|
|||||||
resources{resources},
|
resources{resources},
|
||||||
spriteRenderSystem{device, colorFormat, resources} {}
|
spriteRenderSystem{device, colorFormat, resources} {}
|
||||||
|
|
||||||
SceneRenderer::~SceneRenderer() {}
|
|
||||||
|
|
||||||
void SceneRenderer::beginFrame(u32 frameIndex) {
|
void SceneRenderer::beginFrame(u32 frameIndex) {
|
||||||
this->spriteRenderSystem.beginFrame(frameIndex);
|
this->spriteRenderSystem.beginFrame(frameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneRenderer::render(vk::CommandBuffer commandBuffer,
|
void SceneRenderer::render(vk::CommandBuffer commandBuffer,
|
||||||
const SceneView& sceneView) {
|
const SceneView& sceneView, Entity camera) {
|
||||||
const Scene& scene = this->sceneRegistry.get(sceneView.scene);
|
SOPHIA_ASSERT(camera.hasComponent<CameraComponent>(),
|
||||||
const entt::registry& registry = scene.registry;
|
|
||||||
|
|
||||||
entt::entity camera = sceneView.camera;
|
|
||||||
SOPHIA_ASSERT(registry.all_of<CameraComponent>(camera),
|
|
||||||
"camera does not have camera component!");
|
"camera does not have camera component!");
|
||||||
|
|
||||||
vk::Extent2D extent =
|
vk::Extent2D extent =
|
||||||
@@ -34,17 +28,12 @@ void SceneRenderer::render(vk::CommandBuffer commandBuffer,
|
|||||||
float aspect =
|
float aspect =
|
||||||
static_cast<float>(extent.width) / static_cast<float>(extent.height);
|
static_cast<float>(extent.width) / static_cast<float>(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<TransformComponent>(camera)) {
|
|
||||||
cameraWorld = glm::translate(glm::mat4{1.f}, transform->position) *
|
|
||||||
glm::mat4_cast(transform->rotation);
|
|
||||||
}
|
|
||||||
glm::mat4 viewProjection =
|
glm::mat4 viewProjection =
|
||||||
registry.get<CameraComponent>(camera).getProjection(aspect) *
|
camera.getComponent<CameraComponent>().getViewProjection(
|
||||||
glm::inverse(cameraWorld);
|
camera.getComponent<TransformComponent>(), aspect);
|
||||||
|
|
||||||
renderSprites(commandBuffer, scene, viewProjection);
|
renderSprites(commandBuffer, this->sceneRegistry.get(sceneView.scene),
|
||||||
|
viewProjection);
|
||||||
|
|
||||||
// TODO: meshes. Group MeshComponent entities by mesh and draw each group
|
// TODO: meshes. Group MeshComponent entities by mesh and draw each group
|
||||||
// (instanced), as sprites are grouped by texture.
|
// (instanced), as sprites are grouped by texture.
|
||||||
@@ -53,8 +42,7 @@ void SceneRenderer::render(vk::CommandBuffer commandBuffer,
|
|||||||
void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer,
|
void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer,
|
||||||
const Scene& scene,
|
const Scene& scene,
|
||||||
const glm::mat4& viewProjection) {
|
const glm::mat4& viewProjection) {
|
||||||
std::vector<SpriteDrawItem>& items = this->spriteDrawItems;
|
this->spriteDrawItems.clear();
|
||||||
items.clear();
|
|
||||||
|
|
||||||
auto view =
|
auto view =
|
||||||
scene.registry.view<const TransformComponent, const SpriteComponent>();
|
scene.registry.view<const TransformComponent, const SpriteComponent>();
|
||||||
@@ -63,28 +51,22 @@ void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer,
|
|||||||
? sprite.texture
|
? sprite.texture
|
||||||
: this->resources.whiteTexture;
|
: this->resources.whiteTexture;
|
||||||
|
|
||||||
// Unit quad -> sprite rectangle around its pivot -> world.
|
this->spriteDrawItems.push_back(
|
||||||
glm::mat4 model =
|
{sprite.sortOrder,
|
||||||
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,
|
transform.position.z,
|
||||||
texture,
|
texture,
|
||||||
{model, sprite.color, sprite.uvRect}});
|
{sprite.getModel(transform), sprite.color, sprite.uvRect}});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Texture is the last key so that sprites whose order doesn't matter
|
// 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.
|
// 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};
|
return std::tuple{item.sortOrder, item.depth, item.texture};
|
||||||
});
|
});
|
||||||
|
|
||||||
this->spriteInstances.clear();
|
this->spriteInstances.clear();
|
||||||
this->spriteBatches.clear();
|
this->spriteBatches.clear();
|
||||||
for (const SpriteDrawItem& item : items) {
|
for (const SpriteDrawItem& item : this->spriteDrawItems) {
|
||||||
auto index = static_cast<u32>(this->spriteInstances.size());
|
auto index = static_cast<u32>(this->spriteInstances.size());
|
||||||
this->spriteInstances.push_back(item.instance);
|
this->spriteInstances.push_back(item.instance);
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ class SceneRenderer {
|
|||||||
|
|
||||||
SceneRenderer(const SceneRegistry& sceneRegistry, ResourceRegistry& resources,
|
SceneRenderer(const SceneRegistry& sceneRegistry, ResourceRegistry& resources,
|
||||||
Device& device, vk::Format colorFormat);
|
Device& device, vk::Format colorFormat);
|
||||||
~SceneRenderer();
|
|
||||||
|
|
||||||
// Call once per frame, before any render().
|
// Call once per frame, before any render().
|
||||||
void beginFrame(u32 frameIndex);
|
void beginFrame(u32 frameIndex);
|
||||||
|
|
||||||
// Records `sceneView` into `commandBuffer`. Rendering into the view's
|
// Records `sceneView` as seen through `camera` (an entity of the view's
|
||||||
// target must already have begun.
|
// scene with a CameraComponent) into `commandBuffer`. Rendering into the
|
||||||
void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView);
|
// view's target must already have begun.
|
||||||
|
void render(vk::CommandBuffer commandBuffer, const SceneView& sceneView,
|
||||||
|
Entity camera);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SpriteDrawItem {
|
struct SpriteDrawItem {
|
||||||
|
|||||||
Reference in New Issue
Block a user