From 87e2a5d683b9dc932be42dd8691b7452aadcc66e Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Wed, 26 Nov 2025 21:40:46 +0900 Subject: [PATCH] ADD: multi light system docs --- docs/Compute.md | 290 +++++++++++++++++++++++--- docs/MultiLighting.md | 153 ++++++++++++++ src/render/vk_renderpass_background.h | 2 +- 3 files changed, 414 insertions(+), 31 deletions(-) create mode 100644 docs/MultiLighting.md diff --git a/docs/Compute.md b/docs/Compute.md index e6227ed..a7928d3 100644 --- a/docs/Compute.md +++ b/docs/Compute.md @@ -4,70 +4,300 @@ Standalone compute subsystem with a small, explicit API. Used by passes (e.g., B ### 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. +- **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`. +#### `ComputeBinding` — Descriptor Binding Abstraction + +Unified wrapper for all descriptor types. Use static factory methods: + +```c++ +struct ComputeBinding { + uint32_t binding; + VkDescriptorType type; + // Union of buffer/image info + + static ComputeBinding uniformBuffer(uint32_t binding, VkBuffer buffer, VkDeviceSize size, VkDeviceSize offset = 0); + static ComputeBinding storageBuffer(uint32_t binding, VkBuffer buffer, VkDeviceSize size, VkDeviceSize offset = 0); + static ComputeBinding sampledImage(uint32_t binding, VkImageView view, VkSampler sampler, + VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + static ComputeBinding storeImage(uint32_t binding, VkImageView view, + VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL); +}; +``` + +#### `ComputePipelineCreateInfo` — Pipeline Configuration + +```c++ +struct ComputePipelineCreateInfo { + std::string shaderPath; // Path to .comp.spv file + std::vector descriptorTypes; // Descriptor layout (binding order) + uint32_t pushConstantSize = 0; + VkShaderStageFlags pushConstantStages = VK_SHADER_STAGE_COMPUTE_BIT; + + // Optional: shader specialization constants + std::vector specializationEntries; + std::vector specializationData; +}; +``` + +#### `ComputeDispatchInfo` — Dispatch Configuration + +```c++ +struct ComputeDispatchInfo { + uint32_t groupCountX = 1; // Workgroup counts + uint32_t groupCountY = 1; + uint32_t groupCountZ = 1; + + std::vector bindings; // Per-dispatch bindings (transient) + + const void *pushConstants = nullptr; // Push constant data + uint32_t pushConstantSize = 0; + + // Optional synchronization barriers (inserted after dispatch) + std::vector memoryBarriers; + std::vector bufferBarriers; + std::vector imageBarriers; +}; +``` ### API Surface -- Register/Destroy - - `bool ComputeManager::registerPipeline(name, ComputePipelineCreateInfo)` - - `void ComputeManager::unregisterPipeline(name)` - - Query: `bool ComputeManager::hasPipeline(name)` +#### Register/Destroy -- 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])`. +| Method | Description | +|--------|-------------| +| `bool registerPipeline(name, ComputePipelineCreateInfo)` | Create and register a named compute pipeline | +| `void unregisterPipeline(name)` | Destroy and unregister a pipeline | +| `bool hasPipeline(name)` | Check if a pipeline exists | -- Instances - - `bool ComputeManager::createInstance(instanceName, pipelineName)` / `destroyInstance(instanceName)` - - `setInstanceStorageImage`, `setInstanceSampledImage`, `setInstanceBuffer` - - `AllocatedImage createAndBindStorageImage(...)`, `AllocatedBuffer createAndBindStorageBuffer(...)` - - `void dispatchInstance(cmd, instanceName, info)` +#### Dispatch + +| Method | Description | +|--------|-------------| +| `void dispatch(cmd, name, ComputeDispatchInfo)` | Record dispatch to command buffer | +| `void dispatchImmediate(name, ComputeDispatchInfo)` | Execute immediately (GPU-blocking) | + +#### Dispatch Helpers + +```c++ +static uint32_t calculateGroupCount(uint32_t workItems, uint32_t localSize); +static ComputeDispatchInfo createDispatch2D(uint32_t w, uint32_t h, uint32_t lsX = 16, uint32_t lsY = 16); +static ComputeDispatchInfo createDispatch3D(uint32_t w, uint32_t h, uint32_t d, + uint32_t lsX = 8, uint32_t lsY = 8, uint32_t lsZ = 8); +``` + +#### Instances + +| Method | Description | +|--------|-------------| +| `bool createInstance(instanceName, pipelineName)` | Create persistent instance | +| `void destroyInstance(instanceName)` | Destroy instance and owned resources | +| `void setInstanceStorageImage(name, binding, imageView)` | Bind storage image | +| `void setInstanceSampledImage(name, binding, imageView, sampler)` | Bind sampled image | +| `void setInstanceBuffer(name, binding, buffer, size, type)` | Bind buffer | +| `AllocatedImage createAndBindStorageImage(...)` | Create and bind owned image | +| `AllocatedBuffer createAndBindStorageBuffer(...)` | Create and bind owned buffer | +| `void dispatchInstance(cmd, instanceName, info)` | Dispatch using instance bindings | + +#### Utility Operations + +```c++ +void clearImage(VkCommandBuffer cmd, VkImageView imageView, const glm::vec4 &clearColor); +void copyBuffer(VkCommandBuffer cmd, VkBuffer src, VkBuffer dst, VkDeviceSize size, + VkDeviceSize srcOffset = 0, VkDeviceSize dstOffset = 0); +``` ### Quick Start — One‑Shot Dispatch +For ad-hoc compute work where bindings change each dispatch: + ```c++ +// 1. Register pipeline (once, e.g., in init) 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); +ci.descriptorTypes = { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER }; +ci.pushConstantSize = sizeof(BlurPushConstants); context->compute->registerPipeline("blur", ci); -ComputeDispatchInfo di = ComputeManager::createDispatch2D(draw.w, draw.h); +// 2. Create dispatch info with bindings +ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height, 16, 16); 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); + +BlurPushConstants pc{ .radius = 5.0f }; +di.pushConstants = &pc; +di.pushConstantSize = sizeof(pc); + +// 3. Dispatch context->compute->dispatch(cmd, "blur", di); ``` ### Quick Start — Persistent Instance +For effects that reuse the same bindings across frames: + ```c++ +// 1. Create instance (once, e.g., in init) context->compute->createInstance("background.sky", "sky"); context->compute->setInstanceStorageImage("background.sky", 0, ctx->getSwapchain()->drawImage().imageView); +// 2. Dispatch each frame (bindings persist) ComputeDispatchInfo di = ComputeManager::createDispatch2D(ctx->getDrawExtent().width, ctx->getDrawExtent().height); -di.pushConstants = &effect.data; di.pushConstantSize = sizeof(ComputePushConstants); +di.pushConstants = &effect.data; +di.pushConstantSize = sizeof(ComputePushConstants); context->compute->dispatchInstance(cmd, "background.sky", di); ``` +### Quick Start — Immediate Execution + +For GPU-blocking operations (uploads, preprocessing): + +```c++ +ComputeDispatchInfo di = ComputeManager::createDispatch2D(width, height); +di.bindings.push_back(ComputeBinding::storeImage(0, imageView)); +di.pushConstants = &data; +di.pushConstantSize = sizeof(data); + +// Blocks CPU until GPU completes +context->compute->dispatchImmediate("preprocess", di); +// Safe to read results here +``` + ### 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`. +Use `RGPassType::Compute` for graph-managed compute passes: -### Sync Notes +```c++ +graph->add_pass( + "MyComputePass", + RGPassType::Compute, + // Build: declare resource usage + [drawHandle](RGPassBuilder &builder, EngineContext *) { + builder.write(drawHandle, RGImageUsage::ComputeWrite); // Storage image write + }, + // Record: issue dispatch + [this, drawHandle](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx) { + VkImageView drawView = res.image_view(drawHandle); -- 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`. + // Update binding to current frame's target + ctx->pipelines->setComputeInstanceStorageImage("myeffect", 0, drawView); + + ComputeDispatchInfo di = ComputeManager::createDispatch2D( + ctx->getDrawExtent().width, ctx->getDrawExtent().height, 16, 16); + di.pushConstants = ¶ms; + di.pushConstantSize = sizeof(params); + + ctx->pipelines->dispatchComputeInstance(cmd, "myeffect", di); + } +); +``` + +Benefits: +- Automatic layout transitions to `VK_IMAGE_LAYOUT_GENERAL` for storage images. +- Integrated barrier management with other passes. +- Per-frame synchronization tracking. + +Reference: `src/render/vk_renderpass_background.cpp`. + +### Synchronization + +#### Automatic (Render Graph) + +When using the render graph, declare resource usage and let the graph handle barriers: +- `builder.write(image, RGImageUsage::ComputeWrite)` — storage image write +- `builder.read(image, RGImageUsage::SampledCompute)` — sampled image read in compute + +#### Manual (Dispatch Barriers) + +For standalone dispatches, add barriers to `ComputeDispatchInfo`: + +```c++ +ComputeDispatchInfo di{...}; + +// Image layout transition after compute write +VkImageMemoryBarrier2 barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, + .dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + .image = image, + .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } +}; +di.imageBarriers.push_back(barrier); + +context->compute->dispatch(cmd, "mycompute", di); +``` + +#### Frame-in-Flight Safety + +- Descriptor sets use `VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT` for safe concurrent updates. +- `dispatchInstance()` uses per-frame descriptor allocator when available, falling back to instance-owned sets. + +### Shader Example + +```glsl +// myeffect.comp +#version 450 + +layout(local_size_x = 16, local_size_y = 16) in; + +layout(set = 0, binding = 0, rgba16f) uniform image2D outImage; + +layout(push_constant) uniform PushConstants { + vec4 params; +} pc; + +void main() { + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(outImage); + + if (texelCoord.x >= size.x || texelCoord.y >= size.y) return; + + vec4 color = vec4(float(texelCoord.x) / size.x, float(texelCoord.y) / size.y, 0.0, 1.0); + imageStore(outImage, texelCoord, color * pc.params); +} +``` + +### File Structure + +``` +src/compute/ +├── vk_compute.h # Core types and ComputeManager interface +└── vk_compute.cpp # Implementation + +src/core/ +├── vk_pipeline_manager.h/cpp # Unified graphics+compute wrapper +├── engine_context.h # Contains ComputeManager* pointer +└── vk_resource.h/cpp # immediate_submit() for GPU-blocking + +src/render/ +├── vk_renderpass_background.cpp # Example: gradient/sky compute passes +├── rg_types.h # RGPassType::Compute, RGImageUsage::ComputeWrite +└── rg_graph.h # Render graph integration + +shaders/ +├── gradient_color.comp # Example: gradient background +└── sky.comp # Example: procedural starfield +``` + +### Notes & Limits + +- Compute pipelines are **not** hot-reloaded. Re-create via `registerPipeline()` if shader changes. +- `dispatchImmediate()` blocks the CPU; use sparingly for interactive workloads. +- Descriptor pool uses `UPDATE_AFTER_BIND` flag; ensure driver support (Vulkan 1.2+). +- Instance-owned resources are destroyed with the instance; external resources must be managed separately. + +### Debugging + +- Use Vulkan validation layers to catch descriptor/barrier issues. +- Each dispatch records debug labels when validation is enabled. +- Check `ComputeManager::hasPipeline()` before dispatch to avoid silent failures. diff --git a/docs/MultiLighting.md b/docs/MultiLighting.md new file mode 100644 index 0000000..596d96a --- /dev/null +++ b/docs/MultiLighting.md @@ -0,0 +1,153 @@ +## Multi-Light System: Punctual Point Lights + +Extends the rendering pipeline to support multiple dynamic point lights alongside the existing directional sun light. All lighting calculations use a shared PBR (Cook-Torrance) BRDF implementation. + +- Files: `src/scene/vk_scene.h/.cpp`, `src/core/vk_types.h`, `shaders/lighting_common.glsl` + +### Data Structures + +**CPU-side (C++)** + +```cpp +// src/scene/vk_scene.h +struct PointLight { + glm::vec3 position; + float radius; // falloff radius + glm::vec3 color; + float intensity; +}; +``` + +**GPU-side (GLSL)** + +```glsl +// shaders/input_structures.glsl +#define MAX_PUNCTUAL_LIGHTS 64 + +struct GPUPunctualLight { + vec4 position_radius; // xyz: world position, w: radius + vec4 color_intensity; // rgb: color, a: intensity +}; +``` + +The `GPUSceneData` uniform buffer includes: +- `punctualLights[MAX_PUNCTUAL_LIGHTS]`: array of packed light data +- `lightCounts.x`: number of active point lights + +### Scene API + +`SceneManager` exposes a simple API to manage point lights at runtime: + +| Method | Description | +|--------|-------------| +| `addPointLight(const PointLight& light)` | Appends a point light to the scene | +| `clearPointLights()` | Removes all point lights | +| `getPointLights()` | Returns const reference to current lights | + +Example usage: +```cpp +SceneManager::PointLight light{}; +light.position = glm::vec3(5.0f, 3.0f, 0.0f); +light.radius = 15.0f; +light.color = glm::vec3(1.0f, 0.9f, 0.7f); +light.intensity = 20.0f; +sceneManager->addPointLight(light); +``` + +### GPU Upload + +During `SceneManager::update_scene()`, point lights are packed into `GPUSceneData`: + +1. Iterate over `pointLights` vector (capped at `kMaxPunctualLights = 64`) +2. Pack each light into `GPUPunctualLight` format +3. Zero-fill unused array slots +4. Set `lightCounts.x` to active count + +This data is uploaded to the per-frame UBO and bound via `DescriptorManager::gpuSceneDataLayout()` at set 0, binding 0. + +### Shader Implementation + +**lighting_common.glsl** + +Shared PBR functions extracted for reuse across all lighting passes: + +| Function | Purpose | +|----------|---------| +| `fresnelSchlick()` | Fresnel term (Schlick approximation) | +| `DistributionGGX()` | Normal distribution function (GGX/Trowbridge-Reitz) | +| `GeometrySchlickGGX()` | Geometry term (Schlick-GGX) | +| `GeometrySmith()` | Combined geometry attenuation | +| `evaluate_brdf(N, V, L, albedo, roughness, metallic)` | Full Cook-Torrance BRDF evaluation | +| `eval_point_light(light, pos, N, V, albedo, roughness, metallic)` | Point light contribution with falloff | + +**Point Light Falloff** + +Uses smooth inverse-square attenuation with radius-based cutoff: +```glsl +float att = 1.0 / max(dist * dist, 0.0001); +float x = clamp(dist / radius, 0.0, 1.0); +float smth = (1.0 - x * x); +smth *= smth; +float falloff = att * smth; +``` + +This provides physically-based falloff that smoothly reaches zero at the specified radius. + +### Render Path Integration + +**Deferred Lighting** (`deferred_lighting.frag`, `deferred_lighting_nort.frag`) + +```glsl +// Directional sun +vec3 Lsun = normalize(-sceneData.sunlightDirection.xyz); +float sunVis = calcShadowVisibility(pos, N, Lsun); +vec3 sunBRDF = evaluate_brdf(N, V, Lsun, albedo, roughness, metallic); +vec3 direct = sunBRDF * sceneData.sunlightColor.rgb * sceneData.sunlightColor.a * sunVis; + +// Accumulate point lights +uint pointCount = sceneData.lightCounts.x; +for (uint i = 0u; i < pointCount; ++i) { + direct += eval_point_light(sceneData.punctualLights[i], pos, N, V, albedo, roughness, metallic); +} +``` + +**Forward Pass** (`mesh.frag`) + +Same accumulation loop, but without shadow visibility (used for transparent objects). + +### Ray-Traced Point Light Shadows + +When ray tracing is enabled (hybrid mode), the first 4 point lights receive RT shadows: + +```glsl +if (sceneData.rtOptions.x == 1u && i < 4u) { + // Cast shadow ray from surface toward light + vec3 toL = light.position_radius.xyz - pos; + // ... rayQueryInitializeEXT / rayQueryProceedEXT ... + if (hit) contrib = vec3(0.0); +} +``` + +This provides accurate point light shadows without additional shadow maps. + +### Performance Considerations + +- **Light Count**: Up to 64 point lights supported; practical limit depends on scene complexity +- **Loop Unrolling**: Shader compilers typically unroll small loops; consider dynamic branching for very high light counts +- **Shadow Maps**: Point light shadows use RT only; traditional cubemap shadows are not implemented +- **Bandwidth**: Each light adds 32 bytes to the UBO; full array is 2KB + +### Default Lights + +`SceneManager::init()` creates two default point lights for testing: + +| Light | Position | Radius | Color | Intensity | +|-------|----------|--------|-------|-----------| +| Warm Key | (0, 0, 0) | 25 | (1.0, 0.95, 0.8) | 15 | +| Cool Fill | (-10, 4, 10) | 20 | (0.6, 0.7, 1.0) | 10 | + +Call `clearPointLights()` before adding your own lights to remove these defaults. + +### Future Extensions + +- Spot lights (add cone angle to `GPUPunctualLight`) diff --git a/src/render/vk_renderpass_background.h b/src/render/vk_renderpass_background.h index 841eed4..10e7aa6 100644 --- a/src/render/vk_renderpass_background.h +++ b/src/render/vk_renderpass_background.h @@ -19,7 +19,7 @@ public: std::vector &getEffects() { return _backgroundEffects; } std::vector _backgroundEffects; - int _currentEffect = 0; + int _currentEffect = 2; private: EngineContext *_context = nullptr;