Initial claude dump

This commit is contained in:
2026-09-25 22:59:01 -07:00
parent 5644782a1f
commit a21a842b5b
26 changed files with 1101 additions and 158 deletions
+21
View File
@@ -47,6 +47,27 @@ struct MeshComponent {
MeshHandle handle;
};
/**
* A textured quad in the XY plane, placed by the entity's TransformComponent.
* Only rotation around Z is meaningful.
*/
struct SpriteComponent {
// Invalid draws a solid quad in `color`.
TextureHandle texture = INVALID_HANDLE;
glm::vec4 color{1.f, 1.f, 1.f, 1.f};
// Size in world units, before the transform's scale.
glm::vec2 size{1.f, 1.f};
// Point on the sprite, from (0, 0) to (1, 1) across its size, that sits at
// the transform's position and that it rotates around.
glm::vec2 pivot{0.5f, 0.5f};
// Region of the texture to draw, as normalized (u0, v0, u1, v1).
glm::vec4 uvRect{0.f, 0.f, 1.f, 1.f};
// Higher draws on top. Ties are broken by the transform's position.z.
s32 sortOrder = 0;
};
struct CameraComponent {
struct Orthographic {
float height = 10.f;
+7
View File
@@ -59,4 +59,11 @@ class Entity {
Scene* scene = nullptr;
};
template <typename Fn>
void Scene::forEachEntity(Fn&& fn) {
for (auto [handle] : registry.storage<entt::entity>().each()) {
fn(Entity{handle, this});
}
}
} // namespace sophia
+2
View File
@@ -38,11 +38,13 @@ class Panel;
class RenderTarget;
class Scene;
struct SceneView;
class Texture;
using MeshHandle = Handle<Mesh>;
using PanelHandle = Handle<Panel>;
using RenderTargetHandle = Handle<RenderTarget>;
using SceneHandle = Handle<Scene>;
using SceneViewHandle = Handle<SceneView>;
using TextureHandle = Handle<Texture>;
} // namespace sophia
@@ -26,8 +26,10 @@ class ResourceRegistryRef {
void requestResize(RenderTargetHandle handle, u32 width, u32 height);
glm::vec2 getExtent(RenderTargetHandle handle) const;
glm::vec2 getExtent(TextureHandle handle) const;
bool isAlive(RenderTargetHandle handle) const;
bool isAlive(TextureHandle handle) const;
template <typename T>
void release(Handle<T> handle);
@@ -45,16 +47,25 @@ class ResourceRegistryRef {
RenderTargetHandle createResource(std::type_identity<RenderTarget>,
u32 width = 1, u32 height = 1);
TextureHandle createResource(std::type_identity<Texture>, u32 width,
u32 height, std::vector<u8> pixels);
ResourceRegistry* registry;
};
template <>
MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path);
template <>
TextureHandle ResourceRegistryRef::load<Texture>(const std::string& path);
template <>
void ResourceRegistryRef::release<Mesh>(MeshHandle handle);
template <>
void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle);
template <>
void ResourceRegistryRef::release<Texture>(TextureHandle handle);
} // namespace sophia
+6 -1
View File
@@ -13,9 +13,14 @@ class Scene {
Scene();
~Scene();
void createEntity(const std::string& name = {});
// Every entity gets a TagComponent (named `name`) and a TransformComponent.
Entity createEntity(const std::string& name = {});
void destroyEntity(Entity entity);
/** Calls `fn(Entity)` for every live entity. Defined in entity.hpp. */
template <typename Fn>
void forEachEntity(Fn&& fn);
// The camera a render request uses when it doesn't name one. Null until
// set.
entt::entity getActiveCamera() const { return activeCamera; }
+7 -4
View File
@@ -65,7 +65,8 @@ class Application::Impl {
void run() {
ModelRenderSystem modelRenderSystem{device, renderContext};
SceneRenderer sceneRenderer{this->application.sceneRegistry,
this->resourceRegistry};
this->resourceRegistry, device,
renderContext.getImageFormat()};
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
renderContext.getImageCount(),
this->resourceRegistry};
@@ -128,6 +129,7 @@ class Application::Impl {
}
imguiRenderer.beginFrame();
sceneRenderer.beginFrame(renderContext.getFrameIndex());
panelRegistry.update(frameInfo);
this->application.onRender();
@@ -170,10 +172,11 @@ class Application::Impl {
: vk::AttachmentLoadOp::eLoad,
{color.r, color.g, color.b, color.a});
},
[&sceneRenderer, &sceneViewRegistry, handle](vk::CommandBuffer) {
const SceneView& view = sceneViewRegistry.get(handle);
[&sceneRenderer, &sceneViewRegistryConst,
handle](vk::CommandBuffer commandBuffer) {
const SceneView& view = sceneViewRegistryConst.get(handle);
if (view.camera) {
sceneRenderer.render(view);
sceneRenderer.render(commandBuffer, view);
}
});
}
+1
View File
@@ -39,6 +39,7 @@ class RenderContext {
vk::Format getImageFormat() const {
return this->swapchain->getImageFormat();
}
u32 getFrameIndex() const { return this->currentFrameIndex; }
u32 getImageCount() const {
return static_cast<u32>(this->swapchain->imageCount());
}
+78 -50
View File
@@ -1,74 +1,102 @@
#include "rendering/scene_renderer.hpp"
#include <algorithm>
#include <sophia/components.hpp>
#include <sophia/util/assert.hpp>
#include <tuple>
namespace sophia {
namespace {
struct PushConstantData {
glm::mat4 transform{1.f};
glm::vec4 color{1.f};
};
} // namespace
SceneRenderer::SceneRenderer(const SceneRegistry& sceneRegistry,
ResourceRegistry& resources)
: sceneRegistry{sceneRegistry}, resources{resources} {}
ResourceRegistry& resources, Device& device,
vk::Format colorFormat)
: sceneRegistry{sceneRegistry},
resources{resources},
spriteRenderSystem{device, colorFormat, resources} {}
SceneRenderer::~SceneRenderer() {}
void SceneRenderer::render(SceneView sceneView) {
auto& handle = sceneView.scene;
auto& scene = this->sceneRegistry.get(handle);
void SceneRenderer::beginFrame(u32 frameIndex) {
this->spriteRenderSystem.beginFrame(frameIndex);
}
auto& camera = sceneView.camera;
SOPHIA_ASSERT(camera.hasComponent<CameraComponent>(),
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<CameraComponent>(camera),
"camera does not have camera component!");
auto& registry = scene.registry;
vk::Extent2D extent =
this->resources.renderTargetPool.get(sceneView.target).getExtent();
float aspect =
static_cast<float>(extent.width) / static_cast<float>(extent.height);
auto view = registry.view<MeshComponent, TransformComponent>();
// 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 =
registry.get<CameraComponent>(camera).getProjection(aspect) *
glm::inverse(cameraWorld);
for (auto entity : view) {
auto& transform = view.get<TransformComponent>(entity);
log::info(transform);
renderSprites(commandBuffer, scene, viewProjection);
// TODO: meshes. Group MeshComponent entities by mesh and draw each group
// (instanced), as sprites are grouped by texture.
}
void SceneRenderer::renderSprites(vk::CommandBuffer commandBuffer,
const Scene& scene,
const glm::mat4& viewProjection) {
std::vector<SpriteDrawItem>& items = this->spriteDrawItems;
items.clear();
auto view =
scene.registry.view<const TransformComponent, const SpriteComponent>();
for (auto [entity, transform, sprite] : view.each()) {
TextureHandle texture = this->resources.texturePool.isAlive(sprite.texture)
? 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}});
}
// Get all mesh
// For each mesh, get all entities(ES) that have a transform and that mesh
// For e in ES: add e's transform component to a list
// 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) {
return std::tuple{item.sortOrder, item.depth, item.texture};
});
// We now have
/*
M = mesh
T = transform
this->spriteInstances.clear();
this->spriteBatches.clear();
for (const SpriteDrawItem& item : items) {
auto index = static_cast<u32>(this->spriteInstances.size());
this->spriteInstances.push_back(item.instance);
Work Batch
|- M0
| |- T0
| \- T2
|
|- M1
| |- T1
| \- T5
|
\- M2
|- T3
\- T4
if (this->spriteBatches.empty() ||
this->spriteBatches.back().texture != item.texture) {
this->spriteBatches.push_back({item.texture, index, 0});
}
this->spriteBatches.back().instanceCount++;
}
*/
/*
for batch in Work Batch:
batch.mesh.bind()
for transform in batch.transforms:
commandBuffer.pushConstants(transform)
batch.mesh.draw(commandBuffer)
NOTE: Use instanced rendering?
*/
this->spriteRenderSystem.render(commandBuffer, viewProjection,
this->spriteInstances, this->spriteBatches);
}
} // namespace sophia
+29 -3
View File
@@ -2,28 +2,54 @@
#include <sophia/scene_registry.hpp>
#include <sophia/scene_view.hpp>
#include <vector>
#include "rendering/sprite_render_system.hpp"
#include "resources/resource_registry.hpp"
namespace sophia {
/**
* !TEMP to be decoupled once scene batching and rendering impl
*
* Turns a scene's components into draw calls for a SceneView.
*/
class SceneRenderer {
public:
SceneRenderer(const SceneRenderer&) = delete;
SceneRenderer& operator=(const SceneRenderer&) = delete;
SceneRenderer(const SceneRegistry& sceneRegistry,
ResourceRegistry& resources);
SceneRenderer(const SceneRegistry& sceneRegistry, ResourceRegistry& resources,
Device& device, vk::Format colorFormat);
~SceneRenderer();
void render(SceneView sceneView);
// 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);
private:
struct SpriteDrawItem {
s32 sortOrder;
float depth;
TextureHandle texture;
SpriteRenderSystem::Instance instance;
};
void renderSprites(vk::CommandBuffer commandBuffer, const Scene& scene,
const glm::mat4& viewProjection);
const SceneRegistry& sceneRegistry;
ResourceRegistry& resources;
SpriteRenderSystem spriteRenderSystem;
// Reused across frames to avoid reallocating.
std::vector<SpriteDrawItem> spriteDrawItems;
std::vector<SpriteRenderSystem::Instance> spriteInstances;
std::vector<SpriteRenderSystem::Batch> spriteBatches;
};
} // namespace sophia
@@ -0,0 +1,194 @@
#include "rendering/sprite_render_system.hpp"
#include <sophia/util/assert.hpp>
#include <sophia/util/logger.hpp>
#include "shaders/sprite.frag.spv.h"
#include "shaders/sprite.vert.spv.h"
#include "vulkan/descriptors/descriptor_writer.hpp"
namespace sophia {
namespace {
constexpr u32 MAX_TEXTURES = 1024;
constexpr u32 VERTICES_PER_SPRITE = 6;
} // namespace
SpriteRenderSystem::SpriteRenderSystem(Device& device, vk::Format colorFormat,
ResourceRegistry& resources)
: device{device}, resources{resources} {
createDescriptors();
createPipelineLayout();
createPipeline(colorFormat);
createInstanceBuffers();
}
SpriteRenderSystem::~SpriteRenderSystem() {
log::trace("destroyed sophia::SpriteRenderSystem");
}
void SpriteRenderSystem::beginFrame(u32 frameIndex) {
this->frameIndex = frameIndex;
this->instanceCursor = 0;
}
void SpriteRenderSystem::render(vk::CommandBuffer commandBuffer,
const glm::mat4& viewProjection,
std::span<const Instance> instances,
std::span<const Batch> batches) {
if (instances.empty()) {
return;
}
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
if (instances.size() > available) {
log::warning("SpriteRenderSystem: frame instance buffer full, dropping",
instances.size() - available, "sprites");
}
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
u32 base = this->instanceCursor;
u32 count = std::min(static_cast<u32>(instances.size()), available);
buffer.writeToBuffer(const_cast<Instance*>(instances.data()),
count * sizeof(Instance), base * sizeof(Instance));
this->instanceCursor += count;
this->graphicsPipeline->bind(commandBuffer);
PushConstantData push{viewProjection};
commandBuffer.pushConstants(this->pipelineLayout.get(),
vk::ShaderStageFlagBits::eVertex, 0,
sizeof(PushConstantData), &push);
vk::Buffer vertexBuffer = buffer.getBuffer();
vk::DeviceSize offset = 0;
commandBuffer.bindVertexBuffers(0, 1, &vertexBuffer, &offset);
for (const Batch& batch : batches) {
if (batch.firstInstance >= count) {
break;
}
u32 batchCount = std::min(batch.instanceCount, count - batch.firstInstance);
vk::DescriptorSet set = getDescriptorSet(batch.texture);
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
this->pipelineLayout.get(), 0, 1, &set, 0,
nullptr);
commandBuffer.draw(VERTICES_PER_SPRITE, batchCount, 0,
base + batch.firstInstance);
}
}
vk::DescriptorSet SpriteRenderSystem::getDescriptorSet(TextureHandle handle) {
auto it = this->descriptorSets.find(handle.index);
if (it != this->descriptorSets.end() &&
it->second.generation == handle.generation) {
return it->second.set;
}
const Texture& texture = this->resources.texturePool.get(handle);
vk::DescriptorImageInfo imageInfo = texture.descriptorInfo();
DescriptorWriter writer{*this->setLayout, *this->descriptorPool};
writer.writeImage(0, &imageInfo);
if (it == this->descriptorSets.end()) {
vk::DescriptorSet set;
bool allocated = writer.build(set);
SOPHIA_ASSERT(allocated, "SpriteRenderSystem: out of texture descriptors");
this->descriptorSets.emplace(static_cast<u32>(handle.index),
CachedDescriptorSet{handle.generation, set});
return set;
}
// The slot was reused by a new texture. Releasing a texture waits for the
// device to go idle, so no in-flight frame can still be using this set.
writer.overwrite(it->second.set);
it->second.generation = handle.generation;
return it->second.set;
}
void SpriteRenderSystem::createDescriptors() {
this->setLayout =
DescriptorSetLayout::Builder(this->device)
.addBinding(0, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eFragment)
.build();
this->descriptorPool =
DescriptorPool::Builder(this->device)
.setMaxSets(MAX_TEXTURES)
.addPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_TEXTURES)
.build();
}
void SpriteRenderSystem::createPipelineLayout() {
vk::PushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(PushConstantData);
vk::DescriptorSetLayout setLayout = this->setLayout->getDescriptorSetLayout();
vk::PipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &setLayout;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
try {
this->pipelineLayout =
this->device.get()->createPipelineLayoutUnique(pipelineLayoutInfo);
log::trace("created vk::PipelineLayout");
} catch (const vk::SystemError& err) {
log::fatal("failed to create vk::PipelineLayout");
throw std::runtime_error("failed to create vk::PipelineLayout");
}
}
void SpriteRenderSystem::createPipeline(vk::Format colorFormat) {
auto vertexShader = Shader::Builder()
.fromSPIRV(shaders::sprite_vert_spv)
.setStage(ShaderStage::VERTEX)
.build();
auto fragmentShader = Shader::Builder()
.fromSPIRV(shaders::sprite_frag_spv)
.setStage(ShaderStage::FRAGMENT)
.build();
std::vector<vk::VertexInputBindingDescription> bindings{
{0, sizeof(Instance), vk::VertexInputRate::eInstance}};
std::vector<vk::VertexInputAttributeDescription> attributes;
// A mat4 attribute takes four consecutive locations, one per column.
for (u32 column = 0; column < 4; column++) {
attributes.push_back({column, 0, vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, model) +
column * sizeof(glm::vec4))});
}
attributes.push_back({4, 0, vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, color))});
attributes.push_back({5, 0, vk::Format::eR32G32B32A32Sfloat,
static_cast<u32>(offsetof(Instance, uvRect))});
this->graphicsPipeline = GraphicsPipeline::Builder(this->device)
.setVertexShader(*vertexShader)
.setFragmentShader(*fragmentShader)
.setPipelineLayout(this->pipelineLayout.get())
.setColorAttachmentFormats({colorFormat})
.setVertexInput(bindings, attributes)
.setAlphaBlending()
.build();
}
void SpriteRenderSystem::createInstanceBuffers() {
for (std::unique_ptr<Buffer>& buffer : this->instanceBuffers) {
buffer = std::make_unique<Buffer>(
this->device, sizeof(Instance), MAX_INSTANCES_PER_FRAME,
vk::BufferUsageFlagBits::eVertexBuffer,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent);
buffer->map();
}
}
} // namespace sophia
@@ -0,0 +1,94 @@
#pragma once
#include <array>
#include <memory>
#include <sophia/handles.hpp>
#include <sophia/math.hpp>
#include <span>
#include <unordered_map>
#include "rendering/render_context.hpp"
#include "resources/resource_registry.hpp"
#include "vulkan/buffer.hpp"
#include "vulkan/descriptors/descriptor_pool.hpp"
#include "vulkan/descriptors/descriptor_set_layout.hpp"
#include "vulkan/device.hpp"
#include "vulkan/pipelines/graphics_pipeline.hpp"
namespace sophia {
/**
* Draws batches of textured quads. Knows nothing about scenes: callers hand
* it instances already in draw order, grouped into runs that share a texture.
*
* Instances are written to a per-frame host-visible buffer, so every
* render() call within one frame appends to it; call beginFrame() once per
* frame before any render().
*/
class SpriteRenderSystem {
public:
// Layout must match the per-instance inputs in sprite.vert.
struct Instance {
glm::mat4 model{1.f};
glm::vec4 color{1.f};
glm::vec4 uvRect{0.f, 0.f, 1.f, 1.f};
};
// A run of consecutive instances drawn with the same texture.
struct Batch {
TextureHandle texture;
u32 firstInstance;
u32 instanceCount;
};
static constexpr u32 MAX_INSTANCES_PER_FRAME = 32768;
SpriteRenderSystem(const SpriteRenderSystem&) = delete;
SpriteRenderSystem& operator=(const SpriteRenderSystem&) = delete;
SpriteRenderSystem(Device& device, vk::Format colorFormat,
ResourceRegistry& resources);
~SpriteRenderSystem();
void beginFrame(u32 frameIndex);
void render(vk::CommandBuffer commandBuffer, const glm::mat4& viewProjection,
std::span<const Instance> instances,
std::span<const Batch> batches);
private:
struct PushConstantData {
glm::mat4 viewProjection{1.f};
};
struct CachedDescriptorSet {
u32 generation;
vk::DescriptorSet set;
};
void createDescriptors();
void createPipelineLayout();
void createPipeline(vk::Format colorFormat);
void createInstanceBuffers();
vk::DescriptorSet getDescriptorSet(TextureHandle handle);
Device& device;
ResourceRegistry& resources;
std::unique_ptr<DescriptorSetLayout> setLayout;
std::unique_ptr<DescriptorPool> descriptorPool;
// One set per texture slot, keyed by handle index. A generation mismatch
// means the slot was reused by a new texture, so the set is rewritten.
std::unordered_map<u32, CachedDescriptorSet> descriptorSets;
vk::UniquePipelineLayout pipelineLayout;
std::unique_ptr<GraphicsPipeline> graphicsPipeline;
std::array<std::unique_ptr<Buffer>, RenderContext::MAX_FRAMES_IN_FLIGHT>
instanceBuffers;
u32 frameIndex = 0;
u32 instanceCursor = 0;
};
} // namespace sophia
+11 -2
View File
@@ -1,10 +1,12 @@
#pragma once
#include <array>
#include <sophia/handles.hpp>
#include <sophia/util/pool.hpp>
#include "resources/mesh.hpp"
#include "resources/pools/render_target_pool.hpp"
#include "resources/texture.hpp"
#include "vulkan/device.hpp"
namespace sophia {
@@ -15,13 +17,20 @@ class ResourceRegistry {
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
ResourceRegistry(Device& device, vk::Format renderTargetFormat)
: device{device}, renderTargetPool{device, renderTargetFormat} {}
: device{device}, renderTargetPool{device, renderTargetFormat} {
constexpr std::array<u8, 4> WHITE{0xFF, 0xFF, 0xFF, 0xFF};
whiteTexture = texturePool.create(device, 1, 1, WHITE);
}
Device& device;
Pool<Mesh> meshPool{"Mesh"};
RenderTargetPool renderTargetPool;
// TexturePool textures;
Pool<Texture> texturePool{"Texture"};
// MaterialPool materials;
// 1x1 white. Stands in for sprites without a texture, so they draw as a
// solid quad in their tint color.
TextureHandle whiteTexture = INVALID_HANDLE;
};
} // namespace sophia
@@ -1,5 +1,9 @@
#include <stb_image.h>
#include <sophia/resource_registry_ref.hpp>
#include <sophia/util/assert.hpp>
#include <sophia/util/logger.hpp>
#include <stdexcept>
#include "resources/resource_registry.hpp"
@@ -14,6 +18,26 @@ MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path) {
return MeshHandle{};
}
template <>
TextureHandle ResourceRegistryRef::load<Texture>(const std::string& path) {
int width = 0;
int height = 0;
int channels = 0;
// Always decode to RGBA8, whatever the file has.
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4);
if (pixels == nullptr) {
log::fatal("failed to load texture", path, "-", stbi_failure_reason());
throw std::runtime_error("failed to load texture: " + path);
}
auto size = static_cast<size_t>(width) * static_cast<size_t>(height) * 4;
TextureHandle handle = registry->texturePool.create(
registry->device, static_cast<u32>(width), static_cast<u32>(height),
std::span<const u8>{pixels, size});
stbi_image_free(pixels);
return handle;
}
template <>
void ResourceRegistryRef::release<Mesh>(MeshHandle handle) {
registry->meshPool.release(handle);
@@ -24,6 +48,15 @@ void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle) {
registry->renderTargetPool.release(handle);
}
template <>
void ResourceRegistryRef::release<Texture>(TextureHandle handle) {
SOPHIA_ASSERT(handle != registry->whiteTexture,
"the built-in white texture cannot be released");
// Frames in flight may still be sampling it.
registry->device.waitIdle();
registry->texturePool.release(handle);
}
MeshHandle ResourceRegistryRef::createResource(
std::type_identity<Mesh>, std::vector<CanvasVertex> vertices,
std::vector<u32> indices) {
@@ -42,6 +75,13 @@ RenderTargetHandle ResourceRegistryRef::createResource(
return registry->renderTargetPool.create(width, height);
}
TextureHandle ResourceRegistryRef::createResource(std::type_identity<Texture>,
u32 width, u32 height,
std::vector<u8> pixels) {
return registry->texturePool.create(registry->device, width, height,
std::span<const u8>{pixels});
}
void ResourceRegistryRef::requestResize(RenderTargetHandle handle, u32 width,
u32 height) {
registry->renderTargetPool.get(handle).requestResize(width, height);
@@ -56,4 +96,14 @@ bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const {
return registry->renderTargetPool.isAlive(handle);
}
glm::vec2 ResourceRegistryRef::getExtent(TextureHandle handle) const {
const Texture& texture = registry->texturePool.get(handle);
return {static_cast<float>(texture.getWidth()),
static_cast<float>(texture.getHeight())};
}
bool ResourceRegistryRef::isAlive(TextureHandle handle) const {
return registry->texturePool.isAlive(handle);
}
} // namespace sophia
+32
View File
@@ -0,0 +1,32 @@
#include "resources/texture.hpp"
#include <sophia/util/assert.hpp>
#include <sophia/util/logger.hpp>
namespace sophia {
Texture::Texture(Device& device, u32 width, u32 height,
std::span<const u8> pixels) {
SOPHIA_ASSERT(width > 0 && height > 0, "texture must not be empty");
SOPHIA_ASSERT(pixels.size() == static_cast<size_t>(width) * height * 4,
"texture pixel data must be width * height RGBA8 texels");
this->image = Image::Builder(device)
.setExtent(width, height)
.setFormat(vk::Format::eR8G8B8A8Srgb)
.setUsage(vk::ImageUsageFlagBits::eSampled)
.setInitialData(pixels.data(), pixels.size())
.withView()
.withSampler(vk::Filter::eLinear,
vk::SamplerAddressMode::eClampToEdge)
.build();
}
Texture::~Texture() { log::trace("destroyed sophia::Texture"); }
vk::DescriptorImageInfo Texture::descriptorInfo() const {
return {this->image->getSampler(), this->image->getView(),
vk::ImageLayout::eShaderReadOnlyOptimal};
}
} // namespace sophia
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <memory>
#include <sophia/types.hpp>
#include <span>
#include <vulkan/vulkan.hpp>
#include "vulkan/device.hpp"
#include "vulkan/image.hpp"
namespace sophia {
/**
* A sampled, read-only RGBA8 (sRGB) image.
*
* Owned by the ResourceRegistry and addressed through a TextureHandle.
*/
class Texture {
public:
/** `pixels` is width * height tightly packed RGBA8 texels, top row first. */
Texture(Device& device, u32 width, u32 height, std::span<const u8> pixels);
~Texture();
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
const Image& getImage() const { return *this->image; }
u32 getWidth() const { return this->image->getExtent().width; }
u32 getHeight() const { return this->image->getExtent().height; }
vk::DescriptorImageInfo descriptorInfo() const;
private:
std::unique_ptr<Image> image;
};
} // namespace sophia
+16 -6
View File
@@ -1,16 +1,26 @@
#include <sophia/components.hpp>
#include <sophia/entity.hpp>
#include <sophia/scene.hpp>
#include <sophia/util/logger.hpp>
namespace sophia {
Scene::Scene() {
entt::entity entity = this->registry.create();
auto& a = this->registry.emplace<TransformComponent>(entity);
auto& b = this->registry.get<TransformComponent>(entity);
}
Scene::Scene() {}
Scene::~Scene() { log::trace("destroying sophia::Scene"); }
Entity Scene::createEntity(const std::string& name) {
Entity entity{this->registry.create(), this};
entity.addComponent<TagComponent>(name.empty() ? "Entity" : name);
entity.addComponent<TransformComponent>();
return entity;
}
void Scene::destroyEntity(Entity entity) {
if (this->activeCamera == entity) {
this->activeCamera = entt::null;
}
this->registry.destroy(entity);
}
} // namespace sophia
+12
View File
@@ -0,0 +1,12 @@
#version 450
layout (location = 0) in vec4 fragColor;
layout (location = 1) in vec2 fragUv;
layout (location = 0) out vec4 outColor;
layout (set = 0, binding = 0) uniform sampler2D spriteTexture;
void main() {
outColor = texture(spriteTexture, fragUv) * fragColor;
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <array>
#include <cstdint>
namespace sophia::shaders {
inline constexpr std::array<uint32_t, 186> sprite_frag_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000018, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0008000f, 0x00000004,
0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x00000011, 0x00000015,
0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2,
0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79,
0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47,
0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669,
0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009,
0x4374756f, 0x726f6c6f, 0x00000000, 0x00060005, 0x0000000d, 0x69727073,
0x65546574, 0x72757478, 0x00000065, 0x00040005, 0x00000011, 0x67617266,
0x00007655, 0x00050005, 0x00000015, 0x67617266, 0x6f6c6f43, 0x00000072,
0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, 0x0000000d,
0x00000021, 0x00000000, 0x00040047, 0x0000000d, 0x00000022, 0x00000000,
0x00040047, 0x00000011, 0x0000001e, 0x00000001, 0x00040047, 0x00000015,
0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007,
0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007,
0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00090019, 0x0000000a,
0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, 0x00040020, 0x0000000c,
0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000,
0x00040017, 0x0000000f, 0x00000006, 0x00000002, 0x00040020, 0x00000010,
0x00000001, 0x0000000f, 0x0004003b, 0x00000010, 0x00000011, 0x00000001,
0x00040020, 0x00000014, 0x00000001, 0x00000007, 0x0004003b, 0x00000014,
0x00000015, 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000,
0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000b, 0x0000000e,
0x0000000d, 0x0004003d, 0x0000000f, 0x00000012, 0x00000011, 0x00050057,
0x00000007, 0x00000013, 0x0000000e, 0x00000012, 0x0004003d, 0x00000007,
0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013,
0x00000016, 0x0003003e, 0x00000009, 0x00000017, 0x000100fd, 0x00010038,
};
} // namespace sophia::shaders
+31
View File
@@ -0,0 +1,31 @@
#version 450
// Per instance; layout must match SpriteRenderSystem::Instance.
layout (location = 0) in mat4 model; // Maps the unit quad to world space.
layout (location = 4) in vec4 color;
layout (location = 5) in vec4 uvRect;
layout (location = 0) out vec4 fragColor;
layout (location = 1) out vec2 fragUv;
layout (push_constant) uniform Push {
mat4 viewProjection;
} push;
// Two triangles covering the unit quad (0,0)-(1,1).
const vec2 CORNERS[6] = vec2[](
vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
vec2(1.0, 1.0), vec2(0.0, 1.0), vec2(0.0, 0.0)
);
void main() {
vec2 corner = CORNERS[gl_VertexIndex];
gl_Position = push.viewProjection * model * vec4(corner, 0.0, 1.0);
// Draw order comes from the CPU-side sort, so flatten depth; sprites should
// never be clipped by the camera's near/far planes.
gl_Position.z = 0.0;
fragColor = color;
fragUv = mix(uvRect.xy, uvRect.zw, corner);
}
+98
View File
@@ -0,0 +1,98 @@
#pragma once
#include <array>
#include <cstdint>
namespace sophia::shaders {
inline constexpr std::array<uint32_t, 519> sprite_vert_spv = {
0x07230203, 0x00010000, 0x000d000b, 0x00000045, 0x00000000, 0x00020011,
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x000c000f, 0x00000000,
0x00000004, 0x6e69616d, 0x00000000, 0x00000016, 0x00000021, 0x0000002b,
0x00000038, 0x0000003a, 0x0000003d, 0x0000003e, 0x00030003, 0x00000002,
0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004,
0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572,
0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005,
0x00000009, 0x6e726f63, 0x00007265, 0x00060005, 0x00000016, 0x565f6c67,
0x65747265, 0x646e4978, 0x00007865, 0x00050005, 0x00000019, 0x65646e69,
0x6c626178, 0x00000065, 0x00060005, 0x0000001f, 0x505f6c67, 0x65567265,
0x78657472, 0x00000000, 0x00060006, 0x0000001f, 0x00000000, 0x505f6c67,
0x7469736f, 0x006e6f69, 0x00070006, 0x0000001f, 0x00000001, 0x505f6c67,
0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000001f, 0x00000002,
0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000001f,
0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005,
0x00000021, 0x00000000, 0x00040005, 0x00000024, 0x68737550, 0x00000000,
0x00070006, 0x00000024, 0x00000000, 0x77656976, 0x6a6f7250, 0x69746365,
0x00006e6f, 0x00040005, 0x00000026, 0x68737570, 0x00000000, 0x00040005,
0x0000002b, 0x65646f6d, 0x0000006c, 0x00050005, 0x00000038, 0x67617266,
0x6f6c6f43, 0x00000072, 0x00040005, 0x0000003a, 0x6f6c6f63, 0x00000072,
0x00040005, 0x0000003d, 0x67617266, 0x00007655, 0x00040005, 0x0000003e,
0x65527675, 0x00007463, 0x00040047, 0x00000016, 0x0000000b, 0x0000002a,
0x00030047, 0x0000001f, 0x00000002, 0x00050048, 0x0000001f, 0x00000000,
0x0000000b, 0x00000000, 0x00050048, 0x0000001f, 0x00000001, 0x0000000b,
0x00000001, 0x00050048, 0x0000001f, 0x00000002, 0x0000000b, 0x00000003,
0x00050048, 0x0000001f, 0x00000003, 0x0000000b, 0x00000004, 0x00030047,
0x00000024, 0x00000002, 0x00040048, 0x00000024, 0x00000000, 0x00000005,
0x00050048, 0x00000024, 0x00000000, 0x00000007, 0x00000010, 0x00050048,
0x00000024, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x0000002b,
0x0000001e, 0x00000000, 0x00040047, 0x00000038, 0x0000001e, 0x00000000,
0x00040047, 0x0000003a, 0x0000001e, 0x00000004, 0x00040047, 0x0000003d,
0x0000001e, 0x00000001, 0x00040047, 0x0000003e, 0x0000001e, 0x00000005,
0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002,
0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00040015, 0x0000000a,
0x00000020, 0x00000000, 0x0004002b, 0x0000000a, 0x0000000b, 0x00000006,
0x0004001c, 0x0000000c, 0x00000007, 0x0000000b, 0x0004002b, 0x00000006,
0x0000000d, 0x00000000, 0x0005002c, 0x00000007, 0x0000000e, 0x0000000d,
0x0000000d, 0x0004002b, 0x00000006, 0x0000000f, 0x3f800000, 0x0005002c,
0x00000007, 0x00000010, 0x0000000f, 0x0000000d, 0x0005002c, 0x00000007,
0x00000011, 0x0000000f, 0x0000000f, 0x0005002c, 0x00000007, 0x00000012,
0x0000000d, 0x0000000f, 0x0009002c, 0x0000000c, 0x00000013, 0x0000000e,
0x00000010, 0x00000011, 0x00000011, 0x00000012, 0x0000000e, 0x00040015,
0x00000014, 0x00000020, 0x00000001, 0x00040020, 0x00000015, 0x00000001,
0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000001, 0x00040020,
0x00000018, 0x00000007, 0x0000000c, 0x00040017, 0x0000001c, 0x00000006,
0x00000004, 0x0004002b, 0x0000000a, 0x0000001d, 0x00000001, 0x0004001c,
0x0000001e, 0x00000006, 0x0000001d, 0x0006001e, 0x0000001f, 0x0000001c,
0x00000006, 0x0000001e, 0x0000001e, 0x00040020, 0x00000020, 0x00000003,
0x0000001f, 0x0004003b, 0x00000020, 0x00000021, 0x00000003, 0x0004002b,
0x00000014, 0x00000022, 0x00000000, 0x00040018, 0x00000023, 0x0000001c,
0x00000004, 0x0003001e, 0x00000024, 0x00000023, 0x00040020, 0x00000025,
0x00000009, 0x00000024, 0x0004003b, 0x00000025, 0x00000026, 0x00000009,
0x00040020, 0x00000027, 0x00000009, 0x00000023, 0x00040020, 0x0000002a,
0x00000001, 0x00000023, 0x0004003b, 0x0000002a, 0x0000002b, 0x00000001,
0x00040020, 0x00000033, 0x00000003, 0x0000001c, 0x0004002b, 0x0000000a,
0x00000035, 0x00000002, 0x00040020, 0x00000036, 0x00000003, 0x00000006,
0x0004003b, 0x00000033, 0x00000038, 0x00000003, 0x00040020, 0x00000039,
0x00000001, 0x0000001c, 0x0004003b, 0x00000039, 0x0000003a, 0x00000001,
0x00040020, 0x0000003c, 0x00000003, 0x00000007, 0x0004003b, 0x0000003c,
0x0000003d, 0x00000003, 0x0004003b, 0x00000039, 0x0000003e, 0x00000001,
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8,
0x00000005, 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b,
0x00000018, 0x00000019, 0x00000007, 0x0004003d, 0x00000014, 0x00000017,
0x00000016, 0x0003003e, 0x00000019, 0x00000013, 0x00050041, 0x00000008,
0x0000001a, 0x00000019, 0x00000017, 0x0004003d, 0x00000007, 0x0000001b,
0x0000001a, 0x0003003e, 0x00000009, 0x0000001b, 0x00050041, 0x00000027,
0x00000028, 0x00000026, 0x00000022, 0x0004003d, 0x00000023, 0x00000029,
0x00000028, 0x0004003d, 0x00000023, 0x0000002c, 0x0000002b, 0x00050092,
0x00000023, 0x0000002d, 0x00000029, 0x0000002c, 0x0004003d, 0x00000007,
0x0000002e, 0x00000009, 0x00050051, 0x00000006, 0x0000002f, 0x0000002e,
0x00000000, 0x00050051, 0x00000006, 0x00000030, 0x0000002e, 0x00000001,
0x00070050, 0x0000001c, 0x00000031, 0x0000002f, 0x00000030, 0x0000000d,
0x0000000f, 0x00050091, 0x0000001c, 0x00000032, 0x0000002d, 0x00000031,
0x00050041, 0x00000033, 0x00000034, 0x00000021, 0x00000022, 0x0003003e,
0x00000034, 0x00000032, 0x00060041, 0x00000036, 0x00000037, 0x00000021,
0x00000022, 0x00000035, 0x0003003e, 0x00000037, 0x0000000d, 0x0004003d,
0x0000001c, 0x0000003b, 0x0000003a, 0x0003003e, 0x00000038, 0x0000003b,
0x0004003d, 0x0000001c, 0x0000003f, 0x0000003e, 0x0007004f, 0x00000007,
0x00000040, 0x0000003f, 0x0000003f, 0x00000000, 0x00000001, 0x0004003d,
0x0000001c, 0x00000041, 0x0000003e, 0x0007004f, 0x00000007, 0x00000042,
0x00000041, 0x00000041, 0x00000002, 0x00000003, 0x0004003d, 0x00000007,
0x00000043, 0x00000009, 0x0008000c, 0x00000007, 0x00000044, 0x00000001,
0x0000002e, 0x00000040, 0x00000042, 0x00000043, 0x0003003e, 0x0000003d,
0x00000044, 0x000100fd, 0x00010038,
};
} // namespace sophia::shaders