2D scene rendering + image loading #15

Merged
burkkyy merged 18 commits from issue-14/2d-scene-support-spritecomponent-2d-render-system into main 2026-09-30 15:56:33 -07:00
9 changed files with 40 additions and 19 deletions
Showing only changes of commit b24945ec9d - Show all commits
+4 -5
View File
@@ -13,18 +13,17 @@ class Scene {
Scene(); Scene();
~Scene(); ~Scene();
// Every entity gets a TagComponent (named `name`) and a TransformComponent.
Entity createEntity(const std::string& name = {}); Entity createEntity(const std::string& name = {});
void destroyEntity(Entity entity); void destroyEntity(Entity entity);
Entity createCamera(const std::string& name = "Camera");
/** Calls `fn(Entity)` for every live entity. Defined in entity.hpp. */ /** Calls `fn(Entity)` for every live entity. Defined in entity.hpp. */
template <typename Fn> template <typename Fn>
void forEachEntity(Fn&& fn); void forEachEntity(Fn&& fn);
// The camera a render request uses when it doesn't name one. Null until Entity getActiveCamera();
// set. void setActiveCamera(Entity camera);
entt::entity getActiveCamera() const { return activeCamera; }
void setActiveCamera(entt::entity camera) { activeCamera = camera; }
private: private:
friend class Entity; friend class Entity;
+3 -2
View File
@@ -17,8 +17,9 @@ enum class ClearFlags : u8 {
struct SceneView { struct SceneView {
SceneHandle scene = INVALID_HANDLE; SceneHandle scene = INVALID_HANDLE;
RenderTargetHandle target = INVALID_HANDLE; RenderTargetHandle target =
Entity camera; INVALID_HANDLE; // null implies render to swapchain
Entity camera; // null implies use scene's active camera
vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f}; vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f};
+7 -5
View File
@@ -91,12 +91,13 @@ void ImGuiRenderer::beginFrame() {
} }
ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) { ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
vk::ImageView view = const RenderTarget& target = resources.renderTargetPool.get(handle);
resources.renderTargetPool.get(handle).getImage().getView(); vk::ImageView view = target.getImage().getView();
auto it = textures.find(handle.index); auto it = textures.find(handle.index);
if (it != textures.end()) { if (it != textures.end()) {
if (it->second.generation == handle.generation && it->second.view == view) { if (it->second.generation == handle.generation &&
it->second.version == target.getVersion()) {
return (ImTextureID)it->second.descriptorSet; return (ImTextureID)it->second.descriptorSet;
} }
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet); ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
@@ -107,7 +108,8 @@ ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
// left in once the scene pass has finished. // left in once the scene pass has finished.
VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture( VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture(
static_cast<VkImageView>(view), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); static_cast<VkImageView>(view), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
textures[handle.index] = {handle.generation, view, descriptorSet}; textures[handle.index] = {handle.generation, target.getVersion(),
descriptorSet};
return (ImTextureID)descriptorSet; return (ImTextureID)descriptorSet;
} }
@@ -120,7 +122,7 @@ void ImGuiRenderer::releaseStaleTextures() {
bool stale = bool stale =
!renderTargetPool.isAlive(handle) || !renderTargetPool.isAlive(handle) ||
renderTargetPool.get(handle).getImage().getView() != it->second.view; renderTargetPool.get(handle).getVersion() != it->second.version;
if (stale) { if (stale) {
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet); ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
it = textures.erase(it); it = textures.erase(it);
+1 -1
View File
@@ -37,7 +37,7 @@ class ImGuiRenderer {
struct Texture { struct Texture {
u32 generation; u32 generation;
vk::ImageView view; u32 version;
VkDescriptorSet descriptorSet; VkDescriptorSet descriptorSet;
}; };
+1
View File
@@ -39,6 +39,7 @@ void RenderTarget::applyPendingResize() {
} }
void RenderTarget::create(u32 width, u32 height) { void RenderTarget::create(u32 width, u32 height) {
this->version++;
this->image = Image::Builder(this->device) this->image = Image::Builder(this->device)
.setExtent(width, height) .setExtent(width, height)
.setFormat(this->format) .setFormat(this->format)
+2
View File
@@ -27,6 +27,7 @@ class RenderTarget {
Image& getImage() const { return *this->image; } Image& getImage() const { return *this->image; }
vk::Extent2D getExtent() const; vk::Extent2D getExtent() const;
u32 getVersion() const { return this->version; }
void requestResize(u32 width, u32 height); void requestResize(u32 width, u32 height);
@@ -46,6 +47,7 @@ class RenderTarget {
std::unique_ptr<Image> image; std::unique_ptr<Image> image;
std::optional<vk::Extent2D> pendingExtent; std::optional<vk::Extent2D> pendingExtent;
u32 version = 0;
}; };
} // namespace sophia } // namespace sophia
+18
View File
@@ -16,6 +16,24 @@ Entity Scene::createEntity(const std::string& name) {
return entity; return entity;
} }
Entity Scene::createCamera(const std::string& name) {
Entity camera = createEntity(name);
camera.addComponent<CameraComponent>();
if (this->activeCamera == entt::null) {
this->activeCamera = camera;
}
return camera;
}
Entity Scene::getActiveCamera() {
if (this->activeCamera == entt::null) {
return {};
}
return {this->activeCamera, this};
}
void Scene::setActiveCamera(Entity camera) { this->activeCamera = camera; }
void Scene::destroyEntity(Entity entity) { void Scene::destroyEntity(Entity entity) {
if (this->activeCamera == entity) { if (this->activeCamera == entity) {
this->activeCamera = entt::null; this->activeCamera = entt::null;
+3 -3
View File
@@ -87,6 +87,8 @@ class Boids2D : public sophia::Application {
[this](sophia::KeyReleasedEvent& e) { [this](sophia::KeyReleasedEvent& e) {
setScrollKeyHeld(e.getKeyCode(), false); setScrollKeyHeld(e.getKeyCode(), false);
}); });
debugUI().show();
} }
void onCreate() override { void onCreate() override {
@@ -110,8 +112,7 @@ class Boids2D : public sophia::Application {
sceneHandle = scenes().create(); sceneHandle = scenes().create();
sophia::Scene& scene = scenes().get(sceneHandle); sophia::Scene& scene = scenes().get(sceneHandle);
cameraEntity = scene.createEntity("Camera"); cameraEntity = scene.createCamera();
cameraEntity.addComponent<sophia::CameraComponent>();
std::uniform_real_distribution<float> tint{0.0f, 1.0f}; std::uniform_real_distribution<float> tint{0.0f, 1.0f};
for (Boid& boid : boids) { for (Boid& boid : boids) {
@@ -127,7 +128,6 @@ class Boids2D : public sophia::Application {
target = resources().create<sophia::RenderTarget>(); target = resources().create<sophia::RenderTarget>();
views().create({.scene = sceneHandle, views().create({.scene = sceneHandle,
.target = target, .target = target,
.camera = cameraEntity,
.clearColor = {0.01f, 0.01f, 0.02f, 1.0f}}); .clearColor = {0.01f, 0.01f, 0.02f, 1.0f}});
panels().emplace<ViewportPanel>(ui(), target); panels().emplace<ViewportPanel>(ui(), target);
+1 -3
View File
@@ -40,8 +40,7 @@ class ImageViewer : public sophia::Application {
sophia::SceneHandle sceneHandle = scenes().create(); sophia::SceneHandle sceneHandle = scenes().create();
sophia::Scene& scene = scenes().get(sceneHandle); sophia::Scene& scene = scenes().get(sceneHandle);
camera = scene.createEntity("Camera"); camera = scene.createCamera();
camera.addComponent<sophia::CameraComponent>();
sophia::TextureHandle texture = resources().load<sophia::Texture>(path); sophia::TextureHandle texture = resources().load<sophia::Texture>(path);
imageSize = resources().getExtent(texture); imageSize = resources().getExtent(texture);
@@ -54,7 +53,6 @@ class ImageViewer : public sophia::Application {
target = resources().create<sophia::RenderTarget>(); target = resources().create<sophia::RenderTarget>();
views().create({.scene = sceneHandle, views().create({.scene = sceneHandle,
.target = target, .target = target,
.camera = camera,
.clearColor = {0.1f, 0.1f, 0.15f, 1.f}}); .clearColor = {0.1f, 0.1f, 0.15f, 1.f}});
panels().emplace<ViewportPanel>(ui(), target); panels().emplace<ViewportPanel>(ui(), target);