Compare commits

...

5 Commits

Author SHA1 Message Date
burkkyy 17fe9de756 using engine types 2026-10-25 22:46:35 -07:00
burkkyy 2f5268fef6 shader reflect testing 2026-10-22 07:12:25 +00:00
burkkyy 09a6c0283a sandbox updates 2026-10-07 11:51:14 -07:00
burkkyy 3784d0fdec engine backend updates to create buffer to include the vma info 2026-10-07 11:51:01 -07:00
burkkyy bfc96bac2b added readme's 2026-10-06 01:24:16 -07:00
17 changed files with 468 additions and 23 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ namespace sophia {
struct TagComponent {
std::string tag; // entt::hashed_string ?
uint32_t mask = 0;
u32 mask = 0;
};
struct TransformComponent {
@@ -100,7 +100,7 @@ struct CameraComponent {
float farPlane = 100.f;
};
enum class Projection { Orthographic, Perspective };
enum class Projection : u8 { Orthographic, Perspective };
Projection projection = Projection::Orthographic;
Orthographic ortho;
+5 -2
View File
@@ -27,8 +27,8 @@ Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
memoryPropertyFlags{memoryPropertyFlags} {
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
bufferSize = alignmentSize * instanceCount;
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
allocation);
device.createBuffer(bufferSize, this->usageFlags, this->memoryPropertyFlags,
this->buffer, this->allocation, this->info);
}
Buffer::~Buffer() {
@@ -62,6 +62,9 @@ void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
if (size == VK_WHOLE_SIZE) {
memcpy(mapped, data, bufferSize);
} else {
SOPHIA_ASSERT(offset + size <= bufferSize,
"write of {} bytes at offset {} overflows buffer of {} bytes",
size, offset, bufferSize);
char* memOffset = (char*)mapped;
memOffset += offset;
memcpy(memOffset, data, size);
+10 -4
View File
@@ -36,9 +36,10 @@ class Buffer {
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
vk::Result invalidateIndex(int index);
vk::Buffer getBuffer() const { return buffer; }
void* getMappedMemory() const { return mapped; }
uint32_t getInstanceCount() const { return instanceCount; }
void* getMappedMemory() const { return this->mapped; }
vk::Buffer getBuffer() const { return this->buffer; }
const VmaAllocationInfo& getAllocationInfo() const { return this->info; }
u32 getInstanceCount() const { return instanceCount; }
vk::DeviceSize getInstanceSize() const { return instanceSize; }
vk::DeviceSize getAlignmentSize() const { return alignmentSize; }
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
@@ -47,6 +48,10 @@ class Buffer {
}
vk::DeviceSize getBufferSize() const { return bufferSize; }
vk::DeviceAddress getDeviceAddress() {
return this->device.get()->getBufferAddress(this->buffer);
}
private:
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
vk::DeviceSize minOffsetAlignment);
@@ -55,9 +60,10 @@ class Buffer {
void* mapped = nullptr;
vk::Buffer buffer = VK_NULL_HANDLE;
VmaAllocation allocation = VK_NULL_HANDLE;
VmaAllocationInfo info;
vk::DeviceSize bufferSize;
uint32_t instanceCount;
u32 instanceCount;
vk::DeviceSize instanceSize;
vk::DeviceSize alignmentSize;
vk::BufferUsageFlags usageFlags;
+3 -2
View File
@@ -184,7 +184,8 @@ vk::Format Device::findSupportedFormat(
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags properties,
vk::Buffer& buffer, VmaAllocation& allocation) {
vk::Buffer& buffer, VmaAllocation& allocation,
VmaAllocationInfo& info) {
vk::BufferCreateInfo bufferInfo = {};
bufferInfo.size = size;
bufferInfo.usage = usage;
@@ -196,7 +197,7 @@ void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
VkBuffer rawBuffer = VK_NULL_HANDLE;
VkResult result = vmaCreateBuffer(
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
&allocInfo, &rawBuffer, &allocation, nullptr);
&allocInfo, &rawBuffer, &allocation, &info);
if (result != VK_SUCCESS) {
log::fatal("failed to create buffer. Error: {}",
vk::to_string(static_cast<vk::Result>(result)));
+1 -1
View File
@@ -69,7 +69,7 @@ class Device {
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
VmaAllocation& allocation);
VmaAllocation& allocation, VmaAllocationInfo& info);
vk::CommandBuffer beginSingleTimeCommands();
+6 -5
View File
@@ -226,11 +226,12 @@ std::unique_ptr<Image> Image::Builder::build() {
vk::Buffer stagingBuffer;
VmaAllocation stagingAllocation;
this->device.createBuffer(this->initialDataSize,
vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
stagingBuffer, stagingAllocation);
VmaAllocationInfo stagingAllocationInfo;
this->device.createBuffer(
this->initialDataSize, vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent,
stagingBuffer, stagingAllocation, stagingAllocationInfo);
void* mapped = nullptr;
vmaMapMemory(allocator, stagingAllocation, &mapped);
+15
View File
@@ -0,0 +1,15 @@
# sandbox
A playground for working out the implementation and architecture of new engine features. It can include the engine's internal backend headers (`engine/src`) as well as the public API, so you can use classes such as `Device`, `RenderContext`, `RenderGraph` and `SceneRenderer` directly.
Prototype a feature here first. Once its design settles, move it into the engine and expose it through the public API, which [testbed](../testbed/README.md) exercises.
Any `.cpp` file in this directory is compiled in automatically.
## Running
```sh
dev sandbox [args...]
```
This builds the `debug` preset and runs `build/debug/bin/sandbox`. Shaders are loaded from `shaders/` (`SANDBOX_SHADER_DIR`).
+151 -7
View File
@@ -1,8 +1,11 @@
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <spirv_reflect.h>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <sophia/components.hpp>
#include <sophia/scene_registry.hpp>
#include <sophia/scene_view.hpp>
#include <tracy/Tracy.hpp>
@@ -12,14 +15,26 @@
#include "renderer/render_graph.hpp"
#include "renderer/scene_renderer.hpp"
#include "resources/resource_registry.hpp"
#include "vulkan/buffer.hpp"
#include "vulkan/device.hpp"
#include "vulkan/shader.hpp"
#include "window.hpp"
// local includes
#include "material.hpp"
constexpr uint32_t WIDTH = 1280;
constexpr uint32_t HEIGHT = 720;
constexpr const char* NAME = "sandbox";
int main() {
void immediate_submit(sophia::Device& device,
std::function<void(VkCommandBuffer cmd)>&& function) {
auto command = device.beginSingleTimeCommands();
function(command);
device.endSingleTimeCommands(command);
}
void triangle_test() {
sophia::Window window{WIDTH, HEIGHT, NAME};
sophia::Device device{&window};
sophia::RenderContext renderContext{window, device};
@@ -30,10 +45,54 @@ int main() {
sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device,
renderContext.getImageFormat()};
sophia::SceneHandle sceneHandle = sceneRegistry.create();
sophia::Scene& scene = sceneRegistry.get(sceneHandle);
sophia::Entity camera = scene.createCamera();
// {
// auto& transform = camera.getComponent<sophia::TransformComponent>();
// transform.position.z += 60;
// }
sophia::SceneView view;
view.scene = sceneRegistry.create();
view.scene = sceneHandle;
view.clearColor = {0.01f, 0.01f, 0.02f, 1.0f};
view.camera = sceneRegistry.get(view.scene).createCamera();
view.camera = camera;
auto vertexShader = sophia::Shader::Builder()
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.vert")
.setStage(sophia::ShaderStage::VERTEX)
.build();
auto fragmentShader = sophia::Shader::Builder()
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.frag")
.setStage(sophia::ShaderStage::FRAGMENT)
.build();
vk::Format depthFormat = resourceRegistry.renderTargetPool.getDepthFormat();
sophia::Material material{device, std::move(vertexShader),
std::move(fragmentShader),
renderContext.getImageFormat(), depthFormat};
struct TriangleVertex {
sophia::vec4 vertex;
sophia::vec4 color;
};
TriangleVertex verticies[] = {
{{-1.f, 1.f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f, 0.0f}},
{{1.f, 1.f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}},
{{0.f, -1.f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}}};
sophia::u32 triangleVerticiesCount = 3;
sophia::Buffer triangleBuffer{device, sizeof(TriangleVertex),
triangleVerticiesCount,
vk::BufferUsageFlagBits::eShaderDeviceAddress,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent};
triangleBuffer.map();
triangleBuffer.writeToBuffer(verticies);
vk::DeviceAddress address = triangleBuffer.getDeviceAddress();
while (!window.shouldClose()) {
glfwPollEvents();
@@ -52,7 +111,7 @@ int main() {
renderContext.getCurrentDepthImage(), sophia::Access::DepthWrite);
graph.addPass(
"scene",
"sandbox",
[&](sophia::RenderGraph::PassBuilder& pass) {
const sophia::vec4& c = view.clearColor;
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
@@ -60,11 +119,33 @@ int main() {
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
},
[&](vk::CommandBuffer commandBuffer) {
ZoneScopedN("scene pass");
sceneRenderer.render(commandBuffer, view, view.camera,
renderContext.getExtent());
ZoneScopedN("main sandbox pass");
material.bind(commandBuffer);
sophia::Material::PushConstants push;
push.data0 = address;
commandBuffer.pushConstants(
material.getLayout(), vk::ShaderStageFlagBits::eVertex, 0,
sizeof(sophia::Material::PushConstants), &push);
commandBuffer.draw(3, 1, 0, 0);
});
/*
graph.addPass(
"scene",
[&](sophia::RenderGraph::PassBuilder& pass) {
const sophia::vec4& c = view.clearColor;
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
{c.r, c.g, c.b, c.a});
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
},
[&](vk::CommandBuffer commandBuffer) {
ZoneScopedN("scene pass");
sceneRenderer.render(commandBuffer, view, view.camera,
renderContext.getExtent());
});
*/
graph.execute(commandBuffer);
renderContext.endFrame();
@@ -72,6 +153,69 @@ int main() {
}
device.waitIdle();
}
int main() {
{
using namespace sophia;
auto shader = Shader::Builder()
.fromGLSL(SANDBOX_SHADER_DIR "/shader.comp")
.setStage(ShaderStage::VERTEX)
.build();
std::vector<u32> spirv = shader.get()->getSpirv();
size_t spirv_size = spirv.size() * sizeof(u32);
void* spirv_code = spirv.data();
SpvReflectShaderModule module;
SpvReflectResult result =
spvReflectCreateShaderModule(spirv_size, spirv_code, &module);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
{
log::print();
log::info("Reflecting Input Variables");
uint32_t count = 0;
result = spvReflectEnumerateInputVariables(&module, &count, NULL);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
SpvReflectInterfaceVariable** vars =
(SpvReflectInterfaceVariable**)malloc(
count * sizeof(SpvReflectInterfaceVariable*));
result = spvReflectEnumerateInputVariables(&module, &count, vars);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
for (size_t i = 0; i < count; i++) {
SpvReflectInterfaceVariable* var = vars[i];
assert(var != nullptr);
std::cout << "{" << std::endl;
log::print("\tname: {},", var->name);
std::cout << "}" << std::endl;
std::cout << std::endl;
}
}
{
log::print();
log::info("Reflecting Output Variables");
u32 count = 0;
result = spvReflectEnumerateOutputVariables(&module, &count, NULL);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
SpvReflectInterfaceVariable** vars =
(SpvReflectInterfaceVariable**)malloc(
count * sizeof(SpvReflectInterfaceVariable*));
result = spvReflectEnumerateOutputVariables(&module, &count, vars);
assert(result == SPV_REFLECT_RESULT_SUCCESS);
}
spvReflectDestroyShaderModule(&module);
}
// triangle_test();
return EXIT_SUCCESS;
}
+45
View File
@@ -0,0 +1,45 @@
#include "material.hpp"
#include <sophia/util/logger.hpp>
namespace sophia {
Material::Material(Device& device, std::unique_ptr<Shader> vertexShader,
std::unique_ptr<Shader> fragmentShader,
vk::Format colorFormat, vk::Format depthFormat) {
createPipelineLayout(device);
this->pipeline = GraphicsPipeline::Builder(device)
.setVertexShader(*vertexShader)
.setFragmentShader(*fragmentShader)
.setPipelineLayout(this->pipelineLayout.get())
.setColorAttachmentFormats({colorFormat})
.setDepthAttachmentFormat(depthFormat)
.setDepthTest(true, true)
.setCullMode(vk::CullModeFlagBits::eBack)
.build();
}
Material::~Material() { log::trace("destroyed sophia::Material"); }
void Material::createPipelineLayout(Device& device) {
vk::PushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(PushConstants);
vk::PipelineLayoutCreateInfo pipelineLayoutInfo = {};
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
try {
this->pipelineLayout =
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");
}
}
} // namespace sophia
+58
View File
@@ -0,0 +1,58 @@
#pragma once
#include <memory>
#include <sophia/math.hpp>
#include <vulkan/vulkan.hpp>
#include "vulkan/device.hpp"
#include "vulkan/pipelines/graphics_pipeline.hpp"
#include "vulkan/shader.hpp"
namespace sophia {
class Material {
public:
struct PushConstants {
vk::DeviceAddress data0;
u64 padding;
vec4 data1;
vec4 data2;
vec4 data3;
};
Material(const Material&) = delete;
Material& operator=(const Material&) = delete;
Material(Device& device, std::unique_ptr<Shader> vertexShader,
std::unique_ptr<Shader> fragmentShader, vk::Format colorFormat,
vk::Format depthFormat);
~Material();
void bind(vk::CommandBuffer commandBuffer) const {
pipeline->bind(commandBuffer);
}
vk::PipelineLayout getLayout() const { return pipelineLayout.get(); }
private:
void createPipelineLayout(Device& device);
vk::UniquePipelineLayout pipelineLayout;
std::unique_ptr<GraphicsPipeline> pipeline;
};
class MaterialInstance {
public:
void setBaseColor();
private:
Material* material;
vec3 base_color = {};
};
// !NOTE: eventually add ecs support for ecs
// struct MaterialComponent {
// MaterialInstanceHandle handle;
// };
} // namespace sophia
+3
View File
@@ -0,0 +1,3 @@
#include "renderer.hpp"
namespace sophia {} // namespace sophia
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <sophia/math.hpp>
#include <vulkan/vulkan.hpp>
#include "material.hpp"
namespace sophia {
struct RenderObject {
uint32_t indexCount;
uint32_t firstIndex;
vk::Buffer indexBuffer;
MaterialInstance* material;
mat4 transform;
VkDeviceAddress vertexBufferAddress;
};
class Renderer {
public:
private:
};
} // namespace sophia
+45
View File
@@ -0,0 +1,45 @@
#version 450
#extension GL_EXT_buffer_reference : require
struct TriangleVertex {
vec4 vertex;
vec4 color;
};
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
TriangleVertex vertices[];
};
layout( push_constant ) uniform constants {
TriangleVertexBuffer triangleBuffer;
vec2 padding;
vec4 data1;
vec4 data2;
vec4 data3;
} PushConstants;
layout (location = 0) out vec3 outColor;
void main()
{
//const array of positions for the triangle
const vec3 positions[3] = vec3[3](
vec3(-1.f,1.f, 0.0f),
vec3(1.f,1.f, 0.0f),
vec3(0.f,-1.f, 0.0f)
);
//const array of colors for the triangle
const vec3 colors[3] = vec3[3](
vec3(1.0f, 0.0f, 0.0f), //red
vec3(0.0f, 1.0f, 0.0f), //green
vec3(00.f, 0.0f, 1.0f) //blue
);
// Load vertex data from device address
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
// output the position of each vertex
gl_Position = vec4(v.vertex.xyz, 1.0);
outColor = vec3(v.color.xyz);
}
+13
View File
@@ -0,0 +1,13 @@
#version 450
//shader input
layout (location = 0) in vec3 inColor;
//output write
layout (location = 0) out vec4 outFragColor;
void main()
{
//return red
outFragColor = vec4(inColor,1.0f);
}
+45
View File
@@ -0,0 +1,45 @@
#version 450
#extension GL_EXT_buffer_reference : require
struct TriangleVertex {
vec4 vertex;
vec4 color;
};
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
TriangleVertex vertices[];
};
layout( push_constant ) uniform constants {
TriangleVertexBuffer triangleBuffer;
vec2 padding;
vec4 data1;
vec4 data2;
vec4 data3;
} PushConstants;
layout (location = 0) out vec3 outColor;
void main()
{
//const array of positions for the triangle
const vec3 positions[3] = vec3[3](
vec3(-1.f,1.f, 0.0f),
vec3(1.f,1.f, 0.0f),
vec3(0.f,-1.f, 0.0f)
);
//const array of colors for the triangle
const vec3 colors[3] = vec3[3](
vec3(1.0f, 0.0f, 0.0f), //red
vec3(0.0f, 1.0f, 0.0f), //green
vec3(00.f, 0.0f, 1.0f) //blue
);
// Load vertex data from device address
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
// output the position of each vertex
gl_Position = vec4(v.vertex.xyz, 1.0);
outColor = vec3(v.color.xyz);
}
+27
View File
@@ -0,0 +1,27 @@
#version 460
//size of a workgroup for compute
layout (local_size_x = 16, local_size_y = 16) in;
//descriptor bindings for the pipeline
layout(rgba16f,set = 0, binding = 0) uniform image2D image;
void main()
{
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(image);
if(texelCoord.x < size.x && texelCoord.y < size.y)
{
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
if(gl_LocalInvocationID.x != 0 && gl_LocalInvocationID.y != 0)
{
color.x = float(texelCoord.x)/(size.x);
color.y = float(texelCoord.y)/(size.y);
}
imageStore(image, texelCoord, color);
}
}
+13
View File
@@ -0,0 +1,13 @@
# testbed
Exercises the engine's frontend API. It includes only the public headers in `engine/include/sophia/`, so it uses the engine the way a game would.
Use it to check that public API changes work and feel right from the caller's side. It cannot include engine internals (`engine/src`). If something needs them, it belongs in [sandbox](../sandbox/README.md) instead.
## Running
```sh
dev testbed [args...]
```
This builds the `debug` preset and runs `build/debug/bin/testbed`. Shaders are loaded from `shaders/` (`TESTBED_SHADER_DIR`).