|
SleakEngine 1.0.0
C++23 multi-backend game engine
|
SleakEngine does not mandate a fixed vertex format. It keeps one built-in default vertex layout internally (VertexFormatHandle 0), and downstream applications can register their own custom layouts at runtime instead of using it.
| Type | Role |
|---|---|
| Sleak::VertexLayoutDesc | Stride, attribute list, and up to four shader stems describing one layout. |
| Sleak::VertexAttribute | One attribute: shader location, component format, byte offset. |
| Sleak::VertexAttribFormat | The component types an attribute can have. |
| Sleak::VertexFormatRegistry | All-static registry; Register returns a handle, Get reads a desc back. |
| Sleak::VertexFormatHandle | The uint32_t key everything downstream is addressed by; 0 is the engine default. |
| Sleak::MeshBatch | Builds GPU buffers for a handle and draws the result. |
| Sleak::MeshHandle | Vertex buffer, index buffer, index count, and the format tag that picks the pipeline. |
Register once at startup, then every mesh and every pipeline is addressed by the handle you got back.
Defined in include/public/Runtime/VertexLayout.hpp:
Register appends desc to an internal list and returns its index as the handle; it performs no validation of its own (no offset or stride checks happen at registration time). The only defensive check (stride == 0 || attributes.empty()) happens later, at Vulkan pipeline creation, in VulkanRenderer::CreateCustomFormatPipelines.
A downstream game defining its own vertex struct registers a matching VertexLayoutDesc once at startup:
and creates GPU buffers for a mesh built from that struct through the raw-bytes overload of MeshBatch::CreateMesh (include/public/Runtime/MeshBatch.hpp):
The returned MeshHandle carries vertexBuffer, indexBuffer, indexCount, and a vertexFormat field (0 means the engine default) so a renderer knows which pipeline the mesh needs. Drawing goes through MeshBatch::BeginBatch(material) / MeshBatch::Draw(mesh, castsShadow) / MeshBatch::EndBatch(), or through RenderCommandQueue::SubmitDrawIndexed directly for components that manage their own constant buffers (as MeshComponent does).
There is also a VertexGroup&/IndexGroup& overload of CreateMesh for building meshes from the engine's own CPU mesh data types (include/public/Runtime/MeshData.hpp), used for the engine-default vertex format and primitives loaded through ModelLoader.
Vulkan (VulkanRenderer::CreateCustomFormatPipelines, src/Graphics/Vulkan/VulkanPipelines.cpp) builds up to four pipeline variants from the four shader stems, each gated independently. An empty main shaderStem logs an error and marks that variant failed; a shader compile failure or a failed vkCreateGraphicsPipelines call logs an error and marks that specific variant failed. No fallback shader is ever substituted; a failed or absent variant simply causes that pass to be skipped for that vertex format on every frame.
OpenGL (OpenGLRenderer::BindVertexBuffer, src/Graphics/OpenGL/OpenGLRenderer.cpp) looks up the registered VertexLayoutDesc and emits glVertexAttribPointer / glVertexAttribIPointer calls generically from its attributes and stride, falling back to the engine's hardcoded default vertex layout when vertexFormat is 0. It never reads any of the four shader stems; shader program selection for OpenGL draws is entirely independent of the registered format and uses fixed, hardcoded programs per pass. This is why custom vertex formats that only add the four stems (main/shadow/gbuffer/ transparent) do not need an OpenGL-specific shader variant.
Register neither validates nor deduplicates. Registering the same descriptor twice yields two distinct handles and two full sets of backend pipelines, so keep the call in one place and hand the resulting handle around rather than rebuilding the descriptor at each use site.
An empty shader stem is a deliberate opt-out, not an error. Leave transparentShaderStem empty for an opaque-only format and that pass is skipped for the format, with no cost and no log noise. An empty main shaderStem is different: it fails pipeline creation and every pass for that format goes dark.
Handles are process-lifetime values held by the registry. Shutdown clears it, so any MeshHandle still carrying a handle after that point refers to nothing.
| Question | File |
|---|---|
| The descriptor, handle, and registry API | include/public/Runtime/VertexLayout.hpp |
| Mesh creation and drawing | include/public/Runtime/MeshBatch.hpp, src/Graphics/Common/MeshBatch.cpp |
| How the four stems become Vulkan pipelines | src/Graphics/Vulkan/VulkanPipelines.cpp |
| How OpenGL binds attributes generically | src/Graphics/OpenGL/OpenGLRenderer.cpp |
| The engine's own default vertex type | include/public/Runtime/MeshData.hpp |
| A real registration in a shipped game | SleakCraft, Game/include/World/VoxelVertex.hpp |