initial commit-moved from vulkan_guide
This commit is contained in:
73
docs/Compute.md
Normal file
73
docs/Compute.md
Normal file
@@ -0,0 +1,73 @@
|
||||
## Compute System: Pipelines, Instances, and Dispatch
|
||||
|
||||
Standalone compute subsystem with a small, explicit API. Used by passes (e.g., Background) and tools. It lives under `src/compute` and is surfaced via `EngineContext::compute` and convenience wrappers on `PipelineManager`.
|
||||
|
||||
### Concepts
|
||||
|
||||
- Pipelines: Named compute pipelines created from a SPIR‑V module and a simple descriptor layout spec.
|
||||
- Instances: Persistently bound descriptor sets keyed by instance name; useful for effects that rebind images/buffers across frames without re‑creating pipelines.
|
||||
- Dispatch: Issue work with group counts, optional push constants, and ad‑hoc memory barriers.
|
||||
|
||||
### Key Types
|
||||
|
||||
- `ComputePipelineCreateInfo` — shader path, descriptor types, push constant size/stages, optional specialization (src/compute/vk_compute.h).
|
||||
- `ComputeDispatchInfo` — `groupCount{X,Y,Z}`, `bindings`, `pushConstants`, and `*_barriers` arrays for additional sync.
|
||||
- `ComputeBinding` — helpers for `uniformBuffer`, `storageBuffer`, `sampledImage`, `storeImage`.
|
||||
|
||||
### API Surface
|
||||
|
||||
- Register/Destroy
|
||||
- `bool ComputeManager::registerPipeline(name, ComputePipelineCreateInfo)`
|
||||
- `void ComputeManager::unregisterPipeline(name)`
|
||||
- Query: `bool ComputeManager::hasPipeline(name)`
|
||||
|
||||
- Dispatch
|
||||
- `void ComputeManager::dispatch(cmd, name, ComputeDispatchInfo)`
|
||||
- `void ComputeManager::dispatchImmediate(name, ComputeDispatchInfo)` — records on a transient command buffer and submits.
|
||||
- Helpers: `createDispatch2D(w,h[,lsX,lsY])`, `createDispatch3D(w,h,d[,lsX,lsY,lsZ])`.
|
||||
|
||||
- Instances
|
||||
- `bool ComputeManager::createInstance(instanceName, pipelineName)` / `destroyInstance(instanceName)`
|
||||
- `setInstanceStorageImage`, `setInstanceSampledImage`, `setInstanceBuffer`
|
||||
- `AllocatedImage createAndBindStorageImage(...)`, `AllocatedBuffer createAndBindStorageBuffer(...)`
|
||||
- `void dispatchInstance(cmd, instanceName, info)`
|
||||
|
||||
### Quick Start — One‑Shot Dispatch
|
||||
|
||||
```c++
|
||||
ComputePipelineCreateInfo ci{};
|
||||
ci.shaderPath = context->getAssets()->shaderPath("blur.comp.spv");
|
||||
ci.descriptorTypes = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE };
|
||||
ci.pushConstantSize = sizeof(ComputePushConstants);
|
||||
context->compute->registerPipeline("blur", ci);
|
||||
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(draw.w, draw.h);
|
||||
di.bindings.push_back(ComputeBinding::storeImage(0, outImageView));
|
||||
di.bindings.push_back(ComputeBinding::sampledImage(1, inImageView, context->getSamplers()->defaultLinear()));
|
||||
ComputePushConstants pc{}; /* fill */
|
||||
di.pushConstants = &pc; di.pushConstantSize = sizeof(pc);
|
||||
context->compute->dispatch(cmd, "blur", di);
|
||||
```
|
||||
|
||||
### Quick Start — Persistent Instance
|
||||
|
||||
```c++
|
||||
context->compute->createInstance("background.sky", "sky");
|
||||
context->compute->setInstanceStorageImage("background.sky", 0, ctx->getSwapchain()->drawImage().imageView);
|
||||
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(ctx->getDrawExtent().width,
|
||||
ctx->getDrawExtent().height);
|
||||
di.pushConstants = &effect.data; di.pushConstantSize = sizeof(ComputePushConstants);
|
||||
context->compute->dispatchInstance(cmd, "background.sky", di);
|
||||
```
|
||||
|
||||
### Integration With Render Graph
|
||||
|
||||
- Compute passes declare `write(image, RGImageUsage::ComputeWrite)` in their build callback; the graph inserts layout transitions to `GENERAL` and required barriers.
|
||||
- Background pass example: `src/render/vk_renderpass_background.cpp`.
|
||||
|
||||
### Sync Notes
|
||||
|
||||
- ComputeManager inserts minimal barriers needed for common cases; prefer using the Render Graph for cross‑pass synchronization.
|
||||
- For advanced cases, add `imageBarriers`/`bufferBarriers` to `ComputeDispatchInfo`.
|
||||
|
||||
77
docs/Descriptors.md
Normal file
77
docs/Descriptors.md
Normal file
@@ -0,0 +1,77 @@
|
||||
## Descriptors: Builders, Allocators, and Layouts
|
||||
|
||||
Utilities to define descriptor layouts, write descriptor sets, and efficiently allocate them per-frame or globally.
|
||||
|
||||
### Overview
|
||||
|
||||
- Layouts: `DescriptorLayoutBuilder` assembles `VkDescriptorSetLayout` with staged bindings.
|
||||
- Writing: `DescriptorWriter` collects buffer/image writes and updates a set in one call.
|
||||
- Pools: `DescriptorAllocatorGrowable` manages a growable pool-of-pools for resilient allocations; `FrameResources` keeps one per overlapping frame.
|
||||
- Common layouts: `DescriptorManager` pre-creates reusable layouts such as `gpuSceneDataLayout()` and `singleImageLayout()`.
|
||||
|
||||
### Quick Start — Transient Per-Frame Set
|
||||
|
||||
```c++
|
||||
// 1) Create/update a small uniform buffer for the frame
|
||||
AllocatedBuffer ubuf = context->getResources()->create_buffer(
|
||||
sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
context->currentFrame->_deletionQueue.push_function([=, &ctx=*context]{ ctx.getResources()->destroy_buffer(ubuf); });
|
||||
VmaAllocationInfo ai{}; vmaGetAllocationInfo(context->getDevice()->allocator(), ubuf.allocation, &ai);
|
||||
*static_cast<GPUSceneData*>(ai.pMappedData) = context->getSceneData();
|
||||
|
||||
// 2) Allocate a set from the frame allocator using a common layout
|
||||
VkDescriptorSet set = context->currentFrame->_frameDescriptors.allocate(
|
||||
context->getDevice()->device(), context->getDescriptorLayouts()->gpuSceneDataLayout());
|
||||
|
||||
// 3) Write the buffer binding
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, ubuf.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.update_set(context->getDevice()->device(), set);
|
||||
```
|
||||
|
||||
### Defining a Custom Layout
|
||||
|
||||
```c++
|
||||
DescriptorLayoutBuilder lb;
|
||||
lb.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
lb.add_binding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
|
||||
VkDescriptorSetLayout myLayout = lb.build(device, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
// ... remember to vkDestroyDescriptorSetLayout(device, myLayout, nullptr) in cleanup
|
||||
```
|
||||
|
||||
### Global Growable Allocator
|
||||
|
||||
For long-lived sets (e.g., materials, compute instances), use `EngineContext::descriptors` which wraps a growable allocator shared across modules.
|
||||
|
||||
```c++
|
||||
VkDescriptorSet persistent = context->getDescriptors()->allocate(device, myLayout);
|
||||
DescriptorWriter writer;
|
||||
writer.write_image(0, albedoView, sampler, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
||||
writer.update_set(device, persistent);
|
||||
// Freeing: call `context->getDescriptors()->destroy_pools(device)` at engine shutdown; sets die with their pools
|
||||
```
|
||||
|
||||
### Compute Integration
|
||||
|
||||
`ComputeManager` uses an internal `DescriptorAllocatorGrowable` and offers higher-level bindings:
|
||||
|
||||
- Build pipeline: specify `descriptorTypes` and `pushConstantSize` in `ComputePipelineCreateInfo`.
|
||||
- Ad-hoc dispatch: fill `ComputeDispatchInfo.bindings` with `ComputeBinding::{uniformBuffer, storageBuffer, sampledImage, storeImage}`.
|
||||
- Persistent instances: create via `createInstance()` and set bindings with `setInstance*()`; descriptor sets are auto-updated and reused across dispatches.
|
||||
|
||||
See `PipelineManager.md` for a full compute quick start using the unified API.
|
||||
|
||||
### API Summary
|
||||
|
||||
- `DescriptorLayoutBuilder`: `add_binding(binding, type)`, `build(device, stages[, pNext, flags])`, `clear()`.
|
||||
- `DescriptorWriter`: `write_buffer(binding, buffer, size, offset, type)`, `write_image(binding, view, sampler, layout, type)`, `update_set(device, set)`, `clear()`.
|
||||
- `DescriptorAllocatorGrowable`: `init(device, initialSets, ratios)`, `allocate(device, layout[, pNext])`, `clear_pools(device)`, `destroy_pools(device)`.
|
||||
- `DescriptorManager`: `gpuSceneDataLayout()`, `singleImageLayout()`.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Use per-frame allocator (`currentFrame->_frameDescriptors`) for transient sets to avoid lifetime pitfalls.
|
||||
- Keep `DescriptorLayoutBuilder` small and local; free custom layouts in your pass/module `cleanup()`.
|
||||
- Tune pool ratios to match workload; see how frames are initialized in `VulkanEngine::init_frame_resources()`.
|
||||
- For persistent compute resources, prefer `ComputeManager` instances over manual descriptor lifecycle.
|
||||
90
docs/EngineContext.md
Normal file
90
docs/EngineContext.md
Normal file
@@ -0,0 +1,90 @@
|
||||
## Engine Context: Access to Managers + Per‑Frame State
|
||||
|
||||
Central DI-style handle that modules use to access device/managers, per-frame state, and convenience data without depending directly on `VulkanEngine`.
|
||||
|
||||
### Overview
|
||||
|
||||
- Ownership: Holds shared owners for `DeviceManager`, `ResourceManager`, and a growable `DescriptorAllocatorGrowable` used across modules.
|
||||
- Global managers: Non-owning pointers to `SwapchainManager`, `DescriptorManager` (prebuilt layouts), `SamplerManager`, and `SceneManager`.
|
||||
- Per-frame state: `currentFrame` (command buffer, per-frame descriptor pool, deletion queue), `stats`, and `drawExtent`.
|
||||
- Subsystems: `compute` (`ComputeManager`) and `pipelines` (`PipelineManager`) exposed for unified graphics/compute API.
|
||||
- Window + content: `window` (SDL handle) and convenience meshes (`cubeMesh`, `sphereMesh`).
|
||||
|
||||
Context is wired in `VulkanEngine::init()` and refreshed each frame before passes execute.
|
||||
|
||||
### Render Graph Note
|
||||
|
||||
- Built‑in passes no longer call `vkCmdBeginRendering` or perform image layout transitions directly.
|
||||
- Use your pass’ `register_graph(graph, ...)` to declare attachments and resource accesses; the Render Graph inserts barriers and begins/ends dynamic rendering.
|
||||
- See `docs/RenderGraph.md` for the builder API and scheduling.
|
||||
|
||||
### Quick Start — In a Render Pass (essentials)
|
||||
|
||||
```c++
|
||||
void MyPass::init(EngineContext* context) {
|
||||
_context = context;
|
||||
|
||||
// Use common descriptor layouts provided by DescriptorManager
|
||||
VkDescriptorSetLayout sceneLayout = _context->getDescriptorLayouts()->gpuSceneDataLayout();
|
||||
|
||||
// Build a pipeline via PipelineManager (re-fetch on draw for hot reload)
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = "../shaders/fullscreen.vert.spv";
|
||||
info.fragmentShaderPath = "../shaders/my_pass.frag.spv";
|
||||
info.setLayouts = { sceneLayout };
|
||||
info.configure = [this](PipelineBuilder& b){
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.disable_depthtest();
|
||||
b.set_color_attachment_format(_context->getSwapchain()->drawImage().imageFormat);
|
||||
};
|
||||
_context->pipelines->createGraphicsPipeline("my_pass", info);
|
||||
}
|
||||
|
||||
void MyPass::execute(VkCommandBuffer cmd) {
|
||||
// Fetch latest pipeline in case of hot reload
|
||||
VkPipeline p{}; VkPipelineLayout l{};
|
||||
_context->pipelines->getGraphics("my_pass", p, l);
|
||||
|
||||
// Per-frame uniform buffer via currentFrame allocator
|
||||
AllocatedBuffer ubuf = _context->getResources()->create_buffer(
|
||||
sizeof(GPUSceneData), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
_context->currentFrame->_deletionQueue.push_function([=, this]{ _context->getResources()->destroy_buffer(ubuf); });
|
||||
|
||||
VmaAllocationInfo ai{}; vmaGetAllocationInfo(_context->getDevice()->allocator(), ubuf.allocation, &ai);
|
||||
*static_cast<GPUSceneData*>(ai.pMappedData) = _context->getSceneData();
|
||||
|
||||
VkDescriptorSet set = _context->currentFrame->_frameDescriptors.allocate(
|
||||
_context->getDevice()->device(), _context->getDescriptorLayouts()->gpuSceneDataLayout());
|
||||
DescriptorWriter writer;
|
||||
writer.write_buffer(0, ubuf.buffer, sizeof(GPUSceneData), 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER);
|
||||
writer.update_set(_context->getDevice()->device(), set);
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, p);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, l, 0, 1, &set, 0, nullptr);
|
||||
|
||||
// Viewport/scissor from context draw extent
|
||||
VkViewport vp{0,0,(float)_context->getDrawExtent().width,(float)_context->getDrawExtent().height,0.f,1.f};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
VkRect2D sc{{0,0},{_context->getDrawExtent().width,_context->getDrawExtent().height}};
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
}
|
||||
```
|
||||
|
||||
### Life Cycle
|
||||
|
||||
- Init: `VulkanEngine` constructs managers, initializes `_context`, then assigns `pipelines`, `compute`, `scene`, etc.
|
||||
- Per-frame: Engine sets `currentFrame` and `drawExtent`, optionally triggers `PipelineManager::hotReloadChanged()`.
|
||||
- Cleanup: Managers own their resources; modules should free layouts/sets they create and push per-frame deletions to `currentFrame->_deletionQueue`.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Prefer `EngineContext` accessors (`getDevice()`, `getResources()`, `getSwapchain()`) for clarity and testability.
|
||||
- Re-fetch pipeline/layout by key every frame if using hot reload.
|
||||
- Use `currentFrame->_frameDescriptors` for transient sets; use `context->descriptors` for longer-lived sets.
|
||||
- Push resource cleanup to the frame or pass deletion queues to match lifetime with usage.
|
||||
|
||||
141
docs/PipelineManager.md
Normal file
141
docs/PipelineManager.md
Normal file
@@ -0,0 +1,141 @@
|
||||
## Pipeline Manager: Graphics + Compute
|
||||
|
||||
Centralizes pipeline creation and access with a clean, uniform API. Avoids duplication, enables hot reload (graphics), and makes passes/materials simpler.
|
||||
|
||||
### Overview
|
||||
|
||||
- Graphics pipelines: Owned by `PipelineManager` with per-name registry, hot-reloaded when shaders change.
|
||||
- Compute pipelines: Created through `PipelineManager` but executed by `ComputeManager` under the hood.
|
||||
- Access from anywhere via `EngineContext` (`context->pipelines`).
|
||||
|
||||
### Quick Start — Graphics
|
||||
|
||||
```c++
|
||||
// In pass/material init
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = "../shaders/mesh.vert.spv";
|
||||
info.fragmentShaderPath = "../shaders/mesh.frag.spv";
|
||||
info.setLayouts = { context->getDescriptorLayouts()->gpuSceneDataLayout(), materialLayout };
|
||||
info.pushConstants = { VkPushConstantRange{ VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants) } };
|
||||
info.configure = [context](PipelineBuilder& b){
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none();
|
||||
b.disable_blending();
|
||||
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
b.set_color_attachment_format(context->getSwapchain()->drawImage().imageFormat);
|
||||
b.set_depth_format(context->getSwapchain()->depthImage().imageFormat);
|
||||
};
|
||||
context->pipelines->createGraphicsPipeline("mesh.opaque", info);
|
||||
|
||||
// Fetch for binding
|
||||
MaterialPipeline mp{};
|
||||
context->pipelines->getMaterialPipeline("mesh.opaque", mp);
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, mp.pipeline);
|
||||
// ... bind sets using mp.layout
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Graphics hot-reload runs each frame. If you cache pipeline handles, re-fetch with `getGraphics()` before use to pick up changes.
|
||||
|
||||
### Quick Start — Compute
|
||||
|
||||
Define and create a compute pipeline through the same manager:
|
||||
|
||||
```c++
|
||||
ComputePipelineCreateInfo c{};
|
||||
c.shaderPath = "../shaders/blur.comp.spv";
|
||||
c.descriptorTypes = {
|
||||
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, // out image
|
||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER // in image
|
||||
};
|
||||
c.pushConstantSize = sizeof(MyBlurPC);
|
||||
|
||||
context->pipelines->createComputePipeline("blur", c);
|
||||
```
|
||||
|
||||
Dispatch it when needed:
|
||||
|
||||
```c++
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height, 16, 16);
|
||||
di.bindings.push_back(ComputeBinding::storeImage(0, outView));
|
||||
di.bindings.push_back(ComputeBinding::sampledImage(1, inView, context->getSamplers()->defaultLinear()));
|
||||
di.pushConstants = &pc; // of type MyBlurPC
|
||||
di.pushConstantSize = sizeof(MyBlurPC);
|
||||
|
||||
// Insert barriers as needed (optional)
|
||||
// di.imageBarriers.push_back(...);
|
||||
|
||||
context->pipelines->dispatchCompute(cmd, "blur", di);
|
||||
```
|
||||
|
||||
Tips:
|
||||
- Use `dispatchComputeImmediate("name", di)` for one-off operations via an internal immediate command buffer.
|
||||
- For complex synchronization, populate `memoryBarriers`, `bufferBarriers`, and `imageBarriers` in `ComputeDispatchInfo`.
|
||||
- Compute pipelines are not hot-reloaded yet. If needed, re-create via `createComputePipeline(...)` and re-dispatch.
|
||||
|
||||
### When to Create vs. Use
|
||||
|
||||
- Create pipelines in pass/material `init()` and keep only the string keys around if you rely on hot reload.
|
||||
- Re-fetch handles right before binding each frame to pick up changes:
|
||||
```c++
|
||||
VkPipeline p; VkPipelineLayout l;
|
||||
if (context->pipelines->getGraphics("mesh.opaque", p, l)) { /* bind & draw */ }
|
||||
```
|
||||
|
||||
### API Summary
|
||||
|
||||
- Graphics
|
||||
- `createGraphicsPipeline(name, GraphicsPipelineCreateInfo)`
|
||||
- `getGraphics(name, VkPipeline&, VkPipelineLayout&)`
|
||||
- `getMaterialPipeline(name, MaterialPipeline&)`
|
||||
- Hot reload: `hotReloadChanged()` is called by the engine each frame.
|
||||
|
||||
- Compute
|
||||
- `createComputePipeline(name, ComputePipelineCreateInfo)`
|
||||
- `destroyComputePipeline(name)` / `hasComputePipeline(name)`
|
||||
- `dispatchCompute(cmd, name, ComputeDispatchInfo)`
|
||||
- `dispatchComputeImmediate(name, ComputeDispatchInfo)`
|
||||
|
||||
### Persistent Compute Resources (Instances)
|
||||
|
||||
For long-lived compute workloads, create a compute instance that owns its descriptor set and (optionally) its resources.
|
||||
|
||||
```c++
|
||||
// 1) Ensure the pipeline exists
|
||||
ComputePipelineCreateInfo c{}; c.shaderPath = "../shaders/work.comp.spv"; c.descriptorTypes = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE };
|
||||
context->pipelines->createComputePipeline("work", c);
|
||||
|
||||
// 2) Create an instance bound to the pipeline
|
||||
// You can go either via ComputeManager or PipelineManager
|
||||
context->pipelines->createComputeInstance("work.main", "work");
|
||||
|
||||
// 3) Allocate persistent resources and bind to instance
|
||||
auto img = context->pipelines->createAndBindComputeStorageImage("work.main", 0,
|
||||
VkExtent3D{width, height, 1}, VK_FORMAT_R8G8B8A8_UNORM);
|
||||
|
||||
// 4) Optionally add more bindings (buffers, sampled images, etc.)
|
||||
auto buf = context->pipelines->createAndBindComputeStorageBuffer("work.main", 1, size);
|
||||
// or reference external resources
|
||||
context->pipelines->setComputeInstanceStorageImage("work.main", 2, someView);
|
||||
|
||||
// 5) Update and dispatch repeatedly (bindings persist)
|
||||
ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height);
|
||||
di.pushConstants = &myPC; di.pushConstantSize = sizeof(myPC);
|
||||
context->pipelines->dispatchComputeInstance(cmd, "work.main", di);
|
||||
|
||||
// 6) Destroy when no longer needed
|
||||
context->pipelines->destroyComputeInstance("work.main");
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Instances keep their descriptor set and binding specification; you can modify bindings via `setInstance*` and call `dispatchInstance()` without respecifying them each frame.
|
||||
- Owned images/buffers created via `createAndBind*` are automatically destroyed when the instance is destroyed or on engine cleanup.
|
||||
- Descriptor sets are allocated from a growable pool and are freed when the compute manager is cleaned up.
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Keep descriptor set layouts owned by the module that defines resource interfaces (e.g., material or pass). Pipelines/layouts created by the manager are managed by the manager.
|
||||
- Prefer pipeline keys over cached handles to benefit from hot reload.
|
||||
- Encapsulate fixed-function state in `GraphicsPipelineCreateInfo::configure` lambdas to keep pass code tidy.
|
||||
127
docs/RenderGraph.md
Normal file
127
docs/RenderGraph.md
Normal file
@@ -0,0 +1,127 @@
|
||||
## Render Graph: Per‑Frame Scheduling, Barriers, and Dynamic Rendering
|
||||
|
||||
Lightweight render graph that builds a per‑frame DAG from pass declarations, computes the necessary resource barriers/layout transitions, and records passes with dynamic rendering when attachments are declared.
|
||||
|
||||
### Why
|
||||
|
||||
- Centralize synchronization and image layout transitions across passes.
|
||||
- Make passes declarative: author declares reads/writes; the graph inserts barriers and begins/ends rendering.
|
||||
- Keep existing pass classes (`IRenderPass`) while migrating execution to the graph.
|
||||
|
||||
### High‑Level Flow
|
||||
|
||||
- Engine creates the graph each frame and imports swapchain/G‑Buffer images: `src/core/vk_engine.cpp:303`.
|
||||
- Each pass registers its work by calling `register_graph(graph, ...)` and declaring resources via a builder.
|
||||
- The graph appends a present chain (copy HDR `drawImage` → swapchain, then transition to `PRESENT`), optionally inserting ImGui before present.
|
||||
- `compile()` topologically sorts passes by data dependencies (read/write) and computes per‑pass barriers.
|
||||
- `execute(cmd)` emits barriers, begins dynamic rendering if attachments were declared, calls the pass record lambda, and ends rendering.
|
||||
|
||||
### Core API
|
||||
|
||||
- `RenderGraph::add_pass(name, RGPassType type, BuildCallback build, RecordCallback record)`
|
||||
- Declare image/buffer accesses and attachments inside `build` using `RGPassBuilder`.
|
||||
- Do your actual rendering/copies in `record` using resolved Vulkan objects from `RGPassResources`.
|
||||
- See: `src/render/rg_graph.h:36`, `src/render/rg_graph.cpp:51`.
|
||||
|
||||
- `RenderGraph::compile()` → builds ordering and per‑pass `Vk*MemoryBarrier2` lists. See `src/render/rg_graph.cpp:83`.
|
||||
|
||||
- `RenderGraph::execute(cmd)` → emits barriers and dynamic rendering begin/end. See `src/render/rg_graph.cpp:592`.
|
||||
|
||||
- Import helpers for engine images: `import_draw_image()`, `import_depth_image()`, `import_gbuffer_*()`, `import_swapchain_image(index)`. See `src/render/rg_graph.cpp:740`.
|
||||
|
||||
- Present chain: `add_present_chain(draw, swapchain, appendExtra)` inserts Copy→Present passes and lets you inject extra passes (e.g., ImGui) in between. See `src/render/rg_graph.cpp:705`.
|
||||
|
||||
### Declaring a Pass
|
||||
|
||||
Use `register_graph(...)` on your pass to declare resources and record work. The graph handles transitions and dynamic rendering.
|
||||
|
||||
```c++
|
||||
void MyPass::register_graph(RenderGraph* graph,
|
||||
RGImageHandle draw,
|
||||
RGImageHandle depth) {
|
||||
graph->add_pass(
|
||||
"MyPass",
|
||||
RGPassType::Graphics,
|
||||
// Build: declare resources + attachments
|
||||
[draw, depth](RGPassBuilder& b, EngineContext*) {
|
||||
b.read(draw, RGImageUsage::SampledFragment); // example read
|
||||
b.write_color(draw); // render target
|
||||
b.write_depth(depth, /*clear*/ false); // depth test
|
||||
},
|
||||
// Record: issue Vulkan commands (no begin/end rendering needed)
|
||||
[this, draw](VkCommandBuffer cmd, const RGPassResources& res, EngineContext* ctx) {
|
||||
VkPipeline p{}; VkPipelineLayout l{};
|
||||
ctx->pipelines->getGraphics("my_pass", p, l); // hot‑reload safe
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, p);
|
||||
VkViewport vp{0,0,(float)ctx->getDrawExtent().width,(float)ctx->getDrawExtent().height,0,1};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
VkRect2D sc{{0,0}, ctx->getDrawExtent()};
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Builder Reference (`RGPassBuilder`)
|
||||
|
||||
- Images
|
||||
- `read(RGImageHandle, RGImageUsage)` → sample/read usage for this pass.
|
||||
- `write(RGImageHandle, RGImageUsage)` → write usage (compute/storage/transfer).
|
||||
- `write_color(RGImageHandle, bool clearOnLoad=false, VkClearValue clear={})` → declares a color attachment.
|
||||
- `write_depth(RGImageHandle, bool clearOnLoad=false, VkClearValue clear={})` → declares a depth attachment.
|
||||
|
||||
- Buffers
|
||||
- `read_buffer(RGBufferHandle, RGBufferUsage)` / `write_buffer(RGBufferHandle, RGBufferUsage)`.
|
||||
- Convenience import: `read_buffer(VkBuffer, RGBufferUsage, size, name)` and `write_buffer(VkBuffer, ...)` dedup by raw handle.
|
||||
|
||||
See `src/render/rg_builder.h:39` and impl in `src/render/rg_builder.cpp:20`.
|
||||
|
||||
### Resource Model (`RGResourceRegistry`)
|
||||
|
||||
- Imported vs transient resources are tracked uniformly with lifetime indices (`firstUse/lastUse`).
|
||||
- Imports are deduplicated by `VkImage`/`VkBuffer` and keep initial layout/stage/access as the starting state.
|
||||
- Transients are created via `ResourceManager` and auto‑destroyed at end of frame using the frame deletion queue.
|
||||
- See `src/render/rg_resources.h:11` and `src/render/rg_resources.cpp:1`.
|
||||
|
||||
### Synchronization and Layouts
|
||||
|
||||
- For each pass, `compile()` compares previous state with desired usage and, if needed, adds a pre‑pass barrier:
|
||||
- Images: `VkImageMemoryBarrier2` with stage/access/layout derived from `RGImageUsage`.
|
||||
- Buffers: `VkBufferMemoryBarrier2` with stage/access derived from `RGBufferUsage`.
|
||||
- Initial state comes from the imported descriptor; if unknown, buffers default to `TOP_OF_PIPE`.
|
||||
- Format/usage checks:
|
||||
- Warns if binding a depth format as color (and vice‑versa).
|
||||
- Warns if a transient resource is used with flags it wasn’t created with.
|
||||
|
||||
Image usage → layout/stage examples (subset):
|
||||
|
||||
- `SampledFragment` → `SHADER_READ_ONLY_OPTIMAL`, `FRAGMENT_SHADER`.
|
||||
- `ColorAttachment` → `COLOR_ATTACHMENT_OPTIMAL`, `COLOR_ATTACHMENT_OUTPUT` (read|write).
|
||||
- `DepthAttachment` → `DEPTH_ATTACHMENT_OPTIMAL`, `EARLY|LATE_FRAGMENT_TESTS`.
|
||||
- `TransferDst` → `TRANSFER_DST_OPTIMAL`, `TRANSFER`.
|
||||
- `Present` → `PRESENT_SRC_KHR`, `BOTTOM_OF_PIPE`.
|
||||
|
||||
Buffer usage → stage/access examples:
|
||||
|
||||
- `IndexRead` → `INDEX_INPUT`, `INDEX_READ`.
|
||||
- `VertexRead` → `VERTEX_INPUT`, `VERTEX_ATTRIBUTE_READ`.
|
||||
- `UniformRead` → `ALL_GRAPHICS|COMPUTE`, `UNIFORM_READ`.
|
||||
- `StorageReadWrite` → `COMPUTE|FRAGMENT`, `SHADER_STORAGE_READ|WRITE`.
|
||||
|
||||
### Built‑In Pass Wiring (Current)
|
||||
|
||||
- Resource uploads (if any) → Background (compute) → Geometry (G‑Buffer) → Lighting (deferred) → Transparent → CopyToSwapchain → ImGui → PreparePresent.
|
||||
- See registrations: `src/core/vk_engine.cpp:321`–`src/core/vk_engine.cpp:352`.
|
||||
|
||||
### Notes & Limits
|
||||
|
||||
- No aliasing or transient pooling yet; images created via `create_*` are released end‑of‑frame.
|
||||
- Graph scheduling uses a topological order by data dependency; it does not parallelize across queues.
|
||||
- Load/store control for attachments is minimal (`clearOnLoad`, `store` on `RGAttachmentInfo`).
|
||||
- Render area is the min of all declared attachment extents and `EngineContext::drawExtent`.
|
||||
|
||||
### Debugging
|
||||
|
||||
- Each pass is wrapped with a debug label (`RG: <name>`).
|
||||
- Compile prints warnings for suspicious usages or format mismatches.
|
||||
90
docs/RenderPasses.md
Normal file
90
docs/RenderPasses.md
Normal file
@@ -0,0 +1,90 @@
|
||||
## Render Passes: Background → Geometry → Lighting → Transparent → ImGui
|
||||
|
||||
Pass classes (`IRenderPass`) define initialization and recording logic, but execution is now driven by the Render Graph. Each pass exposes a `register_graph(...)` method to declare dependencies and render targets; the graph handles barriers, layouts, and dynamic rendering.
|
||||
|
||||
### Overview
|
||||
|
||||
- Interface: Each pass implements `IRenderPass { init(context); execute(cmd); cleanup(); getName(); }`. Today, `execute()` is unused for built-in passes; work is recorded via the Render Graph record callback.
|
||||
- Manager: `RenderPassManager::init()` creates and stores built-in passes: `BackgroundPass` (compute), `GeometryPass` (G-Buffer), `LightingPass` (deferred), `TransparentPass`, plus optional `ImGuiPass`.
|
||||
- Render graph: Passes call `register_graph(graph, ...)` to declare image/buffer access and attachments. The graph inserts barriers and begins/ends dynamic rendering.
|
||||
- Shared targets: Passes coordinate through `SwapchainManager` images: `drawImage`, `gBufferPosition/Normal/Albedo`, `depthImage` (imported into the graph each frame).
|
||||
- Hot reload: Fetch graphics pipeline/layout by key each frame through `PipelineManager` in the record callback.
|
||||
|
||||
### Quick Start — Add a New Pass (Render Graph)
|
||||
|
||||
```c++
|
||||
class MyPass : public IRenderPass {
|
||||
public:
|
||||
void init(EngineContext* ctx) override {
|
||||
_ctx = ctx;
|
||||
GraphicsPipelineCreateInfo info{};
|
||||
info.vertexShaderPath = _ctx->getAssets()->shaderPath("fullscreen.vert.spv");
|
||||
info.fragmentShaderPath = _ctx->getAssets()->shaderPath("my_pass.frag.spv");
|
||||
info.setLayouts = { _ctx->getDescriptorLayouts()->gpuSceneDataLayout() };
|
||||
info.configure = [this](PipelineBuilder& b){
|
||||
b.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
b.set_polygon_mode(VK_POLYGON_MODE_FILL);
|
||||
b.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
|
||||
b.set_multisampling_none(); b.disable_depthtest();
|
||||
b.set_color_attachment_format(_ctx->getSwapchain()->drawImage().imageFormat);
|
||||
};
|
||||
_ctx->pipelines->createGraphicsPipeline("my_pass", info);
|
||||
}
|
||||
|
||||
void register_graph(RenderGraph* graph, RGImageHandle draw, RGImageHandle depth) {
|
||||
graph->add_pass(
|
||||
"MyPass",
|
||||
RGPassType::Graphics,
|
||||
[draw, depth](RGPassBuilder& b, EngineContext*) {
|
||||
b.write_color(draw);
|
||||
b.write_depth(depth, false);
|
||||
},
|
||||
[this](VkCommandBuffer cmd, const RGPassResources&, EngineContext* ctx){
|
||||
VkPipeline p{}; VkPipelineLayout l{};
|
||||
ctx->pipelines->getGraphics("my_pass", p, l);
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, p);
|
||||
VkViewport vp{0,0,(float)ctx->getDrawExtent().width,(float)ctx->getDrawExtent().height,0,1};
|
||||
vkCmdSetViewport(cmd, 0, 1, &vp);
|
||||
VkRect2D sc{{0,0}, ctx->getDrawExtent()};
|
||||
vkCmdSetScissor(cmd, 0, 1, &sc);
|
||||
vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void execute(VkCommandBuffer) override {} // unused with Render Graph
|
||||
void cleanup() override {}
|
||||
const char* getName() const override { return "MyPass"; }
|
||||
private:
|
||||
EngineContext* _ctx{};
|
||||
};
|
||||
|
||||
// Register in RenderPassManager::init()
|
||||
auto myPass = std::make_unique<MyPass>();
|
||||
myPass->init(context);
|
||||
addPass(std::move(myPass));
|
||||
```
|
||||
|
||||
### Built-in Passes
|
||||
|
||||
- Background (compute): Declares `ComputeWrite(drawImage)` and dispatches a selected effect instance.
|
||||
- Geometry (G-Buffer): Declares 3 color attachments and `DepthAttachment`, plus buffer reads for shared index/vertex buffers.
|
||||
- Lighting (deferred): Reads G‑Buffer as sampled images and writes to `drawImage`.
|
||||
- Transparent (forward): Writes to `drawImage` with depth test against `depthImage` after lighting.
|
||||
- ImGui: Inserted just before present to draw on the swapchain image.
|
||||
|
||||
### API Summary
|
||||
|
||||
- `RenderPassManager::addPass(unique_ptr<IRenderPass>)`: Register a new pass (storage/ownership only).
|
||||
- `RenderPassManager::setImGuiPass(...)`: Configure the optional ImGui pass.
|
||||
- `IRenderPass::register_graph(...)` (per pass class): Declare resources and recording callbacks for the Render Graph.
|
||||
|
||||
### Tips
|
||||
|
||||
- Don’t call `vkCmdBeginRendering` or add manual transitions for declared attachments; the Render Graph handles it.
|
||||
- Re-fetch pipeline and layout by key each frame to pick up hot-reloaded shaders.
|
||||
- Allocate transient descriptor sets from `currentFrame->_frameDescriptors`; free pass-owned layouts in `cleanup()`.
|
||||
- Use `EngineContext::getDrawExtent()` for viewport/scissor.
|
||||
|
||||
See also: `docs/RenderGraph.md` for the builder API and synchronization details.
|
||||
|
||||
49
docs/ResourceManager.md
Normal file
49
docs/ResourceManager.md
Normal file
@@ -0,0 +1,49 @@
|
||||
## Resource Manager: Buffers, Images, Uploads, and Lifetime
|
||||
|
||||
Central allocator and uploader built on VMA. Provides creation helpers, an immediate submit path, and a deferred upload queue that is converted into a Render Graph transfer pass each frame.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- Create/destroy GPU buffers and images (with mapped memory for CPU‑to‑GPU when requested).
|
||||
- Stage and upload mesh/texture data either immediately or via a per‑frame deferred path.
|
||||
- Integrate with `FrameResources` deletion queues to match lifetimes to the frame.
|
||||
- Expose a Render Graph pass that batches all pending uploads.
|
||||
|
||||
### Key APIs (src/core/vk_resource.h)
|
||||
|
||||
- Creation
|
||||
- `AllocatedBuffer create_buffer(size, usage, memUsage)`
|
||||
- `AllocatedImage create_image(extent3D, format, usage[, mipmapped])`
|
||||
- `AllocatedImage create_image(data, extent3D, format, usage[, mipmapped])`
|
||||
- Destroy with `destroy_buffer`, `destroy_image`.
|
||||
|
||||
- Uploads
|
||||
- Deferred mode toggle: `set_deferred_uploads(bool)`; when true, mesh/texture uploads enqueue staging work.
|
||||
- Query queues: `pending_buffer_uploads()`, `pending_image_uploads()`; clear via `clear_pending_uploads()`.
|
||||
- Immediate path: `process_queued_uploads_immediate()` or `immediate_submit(lambda)` for custom commands.
|
||||
- Render Graph integration: `register_upload_pass(RenderGraph&, FrameResources&)` builds a single `Transfer` pass that:
|
||||
- Imports staging buffers and destination resources into the graph.
|
||||
- Inserts the appropriate `TransferSrc/Dst` declarations.
|
||||
- Records `vkCmdCopyBuffer` / `vkCmdCopyBufferToImage` and optional mip generation.
|
||||
- Schedules deletion of staging buffers at end of frame.
|
||||
|
||||
- Mesh upload convenience
|
||||
- `GPUMeshBuffers uploadMesh(span<uint32_t> indices, span<Vertex> vertices)` — returns device buffers and device address.
|
||||
|
||||
### Per‑Frame Lifetime
|
||||
|
||||
- `FrameResources::_deletionQueue` owns per‑frame cleanups for transient buffers/images created during rendering passes.
|
||||
- The upload pass registers cleanups for staging buffers on the frame queue.
|
||||
|
||||
### Render Graph Interaction
|
||||
|
||||
- `register_upload_pass` is called during frame build before other passes (see `src/core/vk_engine.cpp:315`).
|
||||
- It uses graph `import_buffer` / `import_image` to deduplicate external resources and attach initial stage/layout.
|
||||
- Barriers and final layouts for uploaded images are handled in the pass recording (`generate_mipmaps` path transitions to `SHADER_READ_ONLY_OPTIMAL`).
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Prefer deferred uploads (`set_deferred_uploads(true)`) for frame‑coherent synchronization under the Render Graph.
|
||||
- For tooling and one‑off setup, use `immediate_submit(lambda)` to avoid per‑frame queuing.
|
||||
- When creating transient images/buffers used only inside a pass, prefer the Render Graph’s `create_*` so destruction is automatic at frame end.
|
||||
|
||||
54
docs/Scene.md
Normal file
54
docs/Scene.md
Normal file
@@ -0,0 +1,54 @@
|
||||
## Scene System: Cameras, DrawContext, and Instances
|
||||
|
||||
Thin scene layer that produces `RenderObject`s for the renderer. It gathers opaque/transparent surfaces, maintains the main camera, and exposes simple runtime instance APIs.
|
||||
|
||||
### Components
|
||||
|
||||
- `SceneManager` (src/scene/vk_scene.h/.cpp)
|
||||
- Owns the main `Camera`, `GPUSceneData`, and `DrawContext`.
|
||||
- Loads GLTF scenes via `AssetManager`/`LoadedGLTF` and creates dynamic mesh/GLTF instances.
|
||||
- Updates per‑frame transforms, camera, and `GPUSceneData` (`view/proj/viewproj`, sun/ambient).
|
||||
|
||||
- `DrawContext`
|
||||
- Two lists: `OpaqueSurfaces` and `TransparentSurfaces` of `RenderObject`.
|
||||
- Populated by scene graph traversal and dynamic instances each frame.
|
||||
|
||||
- `RenderObject`
|
||||
- Geometry: `indexBuffer`, `vertexBuffer` (for RG tracking), `vertexBufferAddress` (device address used by shaders).
|
||||
- Material: `MaterialInstance* material` with bound set and pipeline.
|
||||
- Transform and bounds for optional culling.
|
||||
|
||||
### Frame Flow
|
||||
|
||||
1. `SceneManager::update_scene()` clears the draw lists and rebuilds them by drawing all active scene/instance nodes.
|
||||
2. Renderer consumes the lists:
|
||||
- Geometry pass sorts opaque by material and index buffer to improve locality.
|
||||
- Transparent pass sorts back‑to‑front against camera and blends to the HDR target.
|
||||
3. Uniforms: Passes allocate a small per‑frame UBO (`GPUSceneData`) and bind it via a shared layout.
|
||||
|
||||
### Sorting / Culling
|
||||
|
||||
- Opaque (geometry): stable sort by `material` then `indexBuffer` (see `src/render/vk_renderpass_geometry.cpp`).
|
||||
- Transparent: sort by camera‑space depth far→near (see `src/render/vk_renderpass_transparent.cpp`).
|
||||
- An example frustum test exists in `vk_renderpass_geometry.cpp` (`is_visible`) and can be enabled to cull meshes.
|
||||
|
||||
### Dynamic Instances
|
||||
|
||||
- Mesh instances
|
||||
- `addMeshInstance(name, mesh, transform)`, `removeMeshInstance(name)`, `clearMeshInstances()`.
|
||||
- Useful for spawning primitives or asset meshes at runtime.
|
||||
|
||||
- GLTF instances
|
||||
- `addGLTFInstance(name, LoadedGLTF, transform)`, `removeGLTFInstance(name)`, `clearGLTFInstances()`.
|
||||
|
||||
### GPU Scene Data
|
||||
|
||||
- `GPUSceneData` carries camera matrices and lighting constants for the frame.
|
||||
- Passes map and fill it into a per‑frame UBO, bindable with `DescriptorManager::gpuSceneDataLayout()`.
|
||||
|
||||
### Tips
|
||||
|
||||
- Treat `DrawContext` as immutable during rendering; build it fully in `update_scene()`.
|
||||
- Keep `RenderObject` small; use device addresses for vertex data to avoid per‑draw vertex buffer binds.
|
||||
- For custom sorting/culling, modify only the scene layer; render passes stay simple.
|
||||
|
||||
159
docs/asset_manager.md
Normal file
159
docs/asset_manager.md
Normal file
@@ -0,0 +1,159 @@
|
||||
## Asset Manager
|
||||
|
||||
Centralized asset path resolution, glTF loading, and runtime mesh creation (including simple materials and primitives). Avoids scattered relative paths and duplicates by resolving roots at runtime and caching results.
|
||||
|
||||
### Path Resolution
|
||||
|
||||
- Environment root: Honors `VKG_ASSET_ROOT` (expected to contain `assets/` and/or `shaders/`).
|
||||
- Upward search: If unset, searches upward from the current directory for folders named `assets` and `shaders`.
|
||||
- Fallbacks: Tries `./assets`, `../assets` and `./shaders`, `../shaders`.
|
||||
- Methods: `shaderPath(name)`, `assetPath(name)`, and `modelPath(name)` (alias of `assetPath`). Relative or absolute input is returned if already valid; otherwise resolution is attempted as above.
|
||||
|
||||
Access the manager anywhere via `EngineContext`:
|
||||
|
||||
```c++
|
||||
auto *assets = context->getAssets();
|
||||
auto spv = assets->shaderPath("mesh.vert.spv");
|
||||
auto chairPath = assets->modelPath("models/chair.glb");
|
||||
```
|
||||
|
||||
### API Summary
|
||||
|
||||
- Paths
|
||||
- `std::string shaderPath(std::string_view)`
|
||||
- `std::string assetPath(std::string_view)` / `modelPath(std::string_view)`
|
||||
- glTF
|
||||
- `std::optional<std::shared_ptr<LoadedGLTF>> loadGLTF(std::string_view nameOrPath)` — cached by canonical absolute path
|
||||
- Meshes
|
||||
- `std::shared_ptr<MeshAsset> createMesh(const MeshCreateInfo &info)`
|
||||
- `std::shared_ptr<MeshAsset> createMesh(const std::string &name, std::span<Vertex> v, std::span<uint32_t> i, std::shared_ptr<GLTFMaterial> material = {})`
|
||||
- `std::shared_ptr<MeshAsset> getMesh(const std::string &name) const`
|
||||
- `std::shared_ptr<MeshAsset> getPrimitive(std::string_view name) const` (returns existing default primitives if created)
|
||||
- `bool removeMesh(const std::string &name)`
|
||||
- `void cleanup()` — releases meshes, material buffers, and any images owned by the manager
|
||||
|
||||
### Mesh Creation Model
|
||||
|
||||
Use either the convenience descriptor (`MeshCreateInfo`) or the direct overload with vertex/index spans.
|
||||
|
||||
```c++
|
||||
struct AssetManager::MaterialOptions {
|
||||
std::string albedoPath; // resolved through AssetManager
|
||||
std::string metalRoughPath; // resolved through AssetManager
|
||||
bool albedoSRGB = true; // VK_FORMAT_R8G8B8A8_SRGB when true
|
||||
bool metalRoughSRGB = false; // VK_FORMAT_R8G8B8A8_UNORM when false
|
||||
GLTFMetallic_Roughness::MaterialConstants constants{};
|
||||
MaterialPass pass = MaterialPass::MainColor; // or Transparent
|
||||
};
|
||||
|
||||
struct AssetManager::MeshGeometryDesc {
|
||||
enum class Type { Provided, Cube, Sphere };
|
||||
Type type = Type::Provided;
|
||||
std::span<Vertex> vertices{}; // when Provided
|
||||
std::span<uint32_t> indices{}; // when Provided
|
||||
int sectors = 16; // for Sphere
|
||||
int stacks = 16; // for Sphere
|
||||
};
|
||||
|
||||
struct AssetManager::MeshMaterialDesc {
|
||||
enum class Kind { Default, Textured };
|
||||
Kind kind = Kind::Default;
|
||||
MaterialOptions options{}; // used when Textured
|
||||
};
|
||||
|
||||
struct AssetManager::MeshCreateInfo {
|
||||
std::string name; // cache key; reused if already created
|
||||
MeshGeometryDesc geometry; // Provided / Cube / Sphere
|
||||
MeshMaterialDesc material; // Default or Textured
|
||||
};
|
||||
```
|
||||
|
||||
Behavior and lifetime:
|
||||
- Default material: If no material is given, a white material is created (2× white textures, per-mesh UBO with sane defaults).
|
||||
- Textured material: When `MeshMaterialDesc::Textured`, images are loaded via `stb_image` and uploaded; per-mesh UBO is allocated and filled from `constants`.
|
||||
- Ownership: Material buffers and any images created by the AssetManager are tracked and destroyed on `removeMesh(name)` or `cleanup()`.
|
||||
- Caching: Meshes are cached by `name`. Re-creating with the same name returns the existing mesh (no new uploads).
|
||||
|
||||
### Examples
|
||||
|
||||
Create a simple plane and render it (default material):
|
||||
|
||||
```c++
|
||||
std::vector<Vertex> v = {
|
||||
{{-0.5f, 0.0f, -0.5f}, 0.0f, {0,1,0}, 0.0f, {1,1,1,1}},
|
||||
{{ 0.5f, 0.0f, -0.5f}, 1.0f, {0,1,0}, 0.0f, {1,1,1,1}},
|
||||
{{-0.5f, 0.0f, 0.5f}, 0.0f, {0,1,0}, 1.0f, {1,1,1,1}},
|
||||
{{ 0.5f, 0.0f, 0.5f}, 1.0f, {0,1,0}, 1.0f, {1,1,1,1}},
|
||||
};
|
||||
std::vector<uint32_t> i = { 0,1,2, 2,1,3 };
|
||||
|
||||
auto plane = ctx->getAssets()->createMesh("plane", v, i); // default white material
|
||||
glm::mat4 xform = glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
|
||||
ctx->scene->addMeshInstance("ground", plane, xform);
|
||||
```
|
||||
|
||||
Generate primitives via `MeshCreateInfo`:
|
||||
|
||||
```c++
|
||||
AssetManager::MeshCreateInfo ci{};
|
||||
ci.name = "cubeA";
|
||||
ci.geometry.type = AssetManager::MeshGeometryDesc::Type::Cube;
|
||||
ci.material.kind = AssetManager::MeshMaterialDesc::Kind::Default;
|
||||
auto cube = ctx->getAssets()->createMesh(ci);
|
||||
ctx->scene->addMeshInstance("cube.instance", cube,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(-2.f, 0.f, -2.f)));
|
||||
|
||||
AssetManager::MeshCreateInfo si{};
|
||||
si.name = "sphere48x24";
|
||||
si.geometry.type = AssetManager::MeshGeometryDesc::Type::Sphere;
|
||||
si.geometry.sectors = 48; si.geometry.stacks = 24;
|
||||
si.material.kind = AssetManager::MeshMaterialDesc::Kind::Default;
|
||||
auto sphere = ctx->getAssets()->createMesh(si);
|
||||
ctx->scene->addMeshInstance("sphere.instance", sphere,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(2.f, 0.f, -2.f)));
|
||||
```
|
||||
|
||||
Textured primitive (albedo + metal-rough):
|
||||
|
||||
```c++
|
||||
AssetManager::MeshCreateInfo ti{};
|
||||
ti.name = "ground.textured";
|
||||
// provide vertices/indices for a plane (see first example)
|
||||
ti.geometry.type = AssetManager::MeshGeometryDesc::Type::Provided;
|
||||
ti.geometry.vertices = std::span<Vertex>(v.data(), v.size());
|
||||
ti.geometry.indices = std::span<uint32_t>(i.data(), i.size());
|
||||
ti.material.kind = AssetManager::MeshMaterialDesc::Kind::Textured;
|
||||
ti.material.options.albedoPath = "textures/ground_albedo.png"; // sRGB
|
||||
ti.material.options.metalRoughPath = "textures/ground_mr.png"; // UNORM
|
||||
// ti.material.options.pass = MaterialPass::Transparent; // optional
|
||||
|
||||
auto texturedPlane = ctx->getAssets()->createMesh(ti);
|
||||
glm::mat4 tx = glm::scale(glm::mat4(1.f), glm::vec3(10.f, 1.f, 10.f));
|
||||
ctx->scene->addMeshInstance("ground.textured", texturedPlane, tx);
|
||||
```
|
||||
|
||||
Textured cube/sphere via options is analogous — set `geometry.type` to `Cube` or `Sphere` and fill `material.options`.
|
||||
|
||||
Runtime glTF spawning:
|
||||
|
||||
```c++
|
||||
auto chair = ctx->getAssets()->loadGLTF("models/chair.glb");
|
||||
if (chair)
|
||||
{
|
||||
glm::mat4 t = glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -3.f));
|
||||
ctx->scene->addGLTFInstance("chair01", *chair, t);
|
||||
}
|
||||
// Move / overwrite
|
||||
ctx->scene->addGLTFInstance("chair01", *chair,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.5f, -3.f)));
|
||||
// Remove
|
||||
ctx->scene->removeGLTFInstance("chair01");
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Default primitives: The engine creates default Cube/Sphere meshes via `AssetManager` and registers them as dynamic scene instances.
|
||||
- Reuse by name: `createMesh("name", ...)` returns the cached mesh if it already exists. Use a unique name or call `removeMesh(name)` to replace.
|
||||
- sRGB/UNORM: Albedo is sRGB by default, metal-rough is UNORM by default. Adjust via `MaterialOptions`.
|
||||
- Hot reload: Shaders are resolved via `shaderPath()`; pipeline hot reload is handled by the pipeline manager, not the AssetManager.
|
||||
- Normal maps: Not wired into the default GLTF PBR material in this branch. Adding them would require descriptor and shader updates.
|
||||
Reference in New Issue
Block a user