From 9251d7f7677fe4b3ad494af844145bdce6a35aab Mon Sep 17 00:00:00 2001 From: Modin Modin Date: Mon, 6 Apr 2026 13:00:11 -0400 Subject: [PATCH] Sample 2 --- commonUtils.hpp | 70 ++++ strandComponent.hpp | 784 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 854 insertions(+) create mode 100644 commonUtils.hpp create mode 100644 strandComponent.hpp diff --git a/commonUtils.hpp b/commonUtils.hpp new file mode 100644 index 0000000..68ccb61 --- /dev/null +++ b/commonUtils.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 Mykhailo Mamedov. All rights reserved. + * + * RESEARCH PREVIEW / REFERENCE ONLY: + * This source code is provided solely for the purpose of reviewing + * the author's research methods and implementation. + * + * NO LICENSE GRANTED: + * This code is NOT for distribution, modification, or use in any + * project (commercial or otherwise). Unauthorized copying or + * use of this code is strictly prohibited. + * + * For inquiries regarding use or licensing, contact: ua.modin@gmail.com + */ + + +#include + + +#ifndef TOOLS_HEADER +#define TOOLS_HEADER + + +namespace M_TOOLS { + + struct Sync { + // Barrier quick helper. + std::vector buffers; + std::vector images; + + Sync& buffer(VkBuffer buffer, VkPipelineStageFlags2 srcMask, VkAccessFlags2 srcAccessMask, + VkPipelineStageFlags2 dstMask, VkAccessFlags2 dstAccessMask, VkDeviceSize offset=0) { + VkBufferMemoryBarrier2 barrierInstance{ VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2 }; + barrierInstance.srcStageMask = srcMask; barrierInstance.srcAccessMask = srcAccessMask; + barrierInstance.dstStageMask = dstMask; barrierInstance.dstAccessMask = dstAccessMask; + barrierInstance.buffer = buffer; barrierInstance.size = VK_WHOLE_SIZE; + barrierInstance.offset = offset; + buffers.push_back(barrierInstance); + return *this; + } + + Sync& image(VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, + VkPipelineStageFlags2 srcMask, VkAccessFlags2 srcAccessMask, + VkPipelineStageFlags2 dstMask, VkAccessFlags2 dstAccessMask) { + VkImageMemoryBarrier2 barrierInstance{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2 }; + barrierInstance.srcStageMask = srcMask; barrierInstance.srcAccessMask = srcAccessMask; + barrierInstance.dstStageMask = dstMask; barrierInstance.dstAccessMask = dstAccessMask; + barrierInstance.oldLayout = oldLayout; barrierInstance.newLayout = newLayout; + barrierInstance.image = image; + barrierInstance.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + images.push_back(barrierInstance); + return *this; + } + + void apply(VkCommandBuffer cmd) { + VkDependencyInfo depInfo{ VK_STRUCTURE_TYPE_DEPENDENCY_INFO }; + depInfo.bufferMemoryBarrierCount = (uint32_t)buffers.size(); + depInfo.pBufferMemoryBarriers = buffers.data(); + depInfo.imageMemoryBarrierCount = (uint32_t)images.size(); + depInfo.pImageMemoryBarriers = images.data(); + vkCmdPipelineBarrier2(cmd, &depInfo); + } + }; + +} // namespace + +#endif // TOOLS_HEADER +// +#ifdef TOOLS_COMPONENT +#endif \ No newline at end of file diff --git a/strandComponent.hpp b/strandComponent.hpp new file mode 100644 index 0000000..de9005a --- /dev/null +++ b/strandComponent.hpp @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2026 Mykhailo Mamedov. All rights reserved. + * + * RESEARCH PREVIEW / REFERENCE ONLY: + * This source code is provided solely for the purpose of reviewing + * the author's research methods and implementation. + * + * NO LICENSE GRANTED: + * This code is NOT for distribution, modification, or use in any + * project (commercial or otherwise). Unauthorized copying or + * use of this code is strictly prohibited. + * + * For inquiries regarding use or licensing, contact: ua.modin@gmail.com + * + * + * Description: + * + * High-performance volumetric rendering engine-component + * optimized for massive strand geometries (30M+ vertices). + * Implements hardware-accelerated ray-marching + * and volume-image buffer based shadow casting + * to achieve real-time visualization of dense fiber structures. + * + */ + +#include +#include + +#ifndef STR_COMPONENT_HEADER +#define STR_COMPONENT_HEADER + +namespace M_STR { + + namespace cfg { + inline constexpr auto SMOOTH_STRAND_COUNT = 64U; + inline constexpr auto IS_NOT_CUBE_MAP = false; + inline constexpr auto IS_3D_IMAGE = true; + inline constexpr auto IS_LAYOUT_GENNERAL = true; + inline constexpr auto UNIFORM_BUFFER_SIZE = sizeof(glm::mat4); + } + + struct appInfo { + VkDevice Device = NULL; + u32 NumImages = 0; + VK::VulkanCore* vCore; + GLFWwindow* m_pWindow; + size_t uniformBufferSize = 0; + glm::ivec2 offscreenViewportSize{}; + }; + + struct PushConstants { + glm::mat4 viewProj; + glm::vec3 gridOrigin; + float gridExtent; + glm::vec3 lightDir; + float _pad; + }; + + struct PushConstantsComputeBase { + glm::mat4 viewProj; + glm::vec3 gridOrigin; + float gridCellSize; + float gridExtent; + u32 frameCount; + float _pad[2]; + }; + + struct CurveHeader { // Matches GLSL. + uint32_t poolOffset; + uint32_t pointCount; + uint32_t maxPoints; + uint32_t _padding; // 16-byte alignment + glm::vec4 color; + }; + + struct CurvePoint { + glm::vec4 pos; // xyz = position, w = radius + // ... + }; + + struct StrandData { // Master CPU record. + uint32_t slotIdx; // Reserved + std::vector points; + glm::vec4 color; + uint8_t dirtyCount = 0; // Match with m_numImages to update all frames in flight. + + // Static helper to ingest initial generated data. + static void IngestInitialPool( + std::vector&targetMasterRecord, + std::span srcHeaders, + std::span srcPointPool, + u32 imagesInFlight + ) { + // We assume target is already resized. + for (uint32_t sIdx = 0; sIdx < srcHeaders.size(); ++sIdx) { + StrandData& strandTarget = targetMasterRecord[sIdx]; + const CurveHeader& srcHeader = srcHeaders[sIdx]; + + strandTarget.slotIdx = sIdx; // Initial index matches slot index + strandTarget.color = srcHeader.color; + + // Extract points from the generated pool + strandTarget.points.clear(); + strandTarget.points.reserve(srcHeader.pointCount); + for (uint32_t pIdx = 0; pIdx < srcHeader.pointCount; ++pIdx) { + // Read the position+radius into master record. + strandTarget.points.push_back(srcPointPool[srcHeader.poolOffset + pIdx]); + } + } + } + }; + + struct VolumetricGrid { + glm::vec3 origin; // Min corner of the box + glm::vec3 extent; // Size of the box + }; + + VolumetricGrid calculateGrid(const std::vector& particles) { + glm::vec3 minBound(FLT_MAX); + glm::vec3 maxBound(-FLT_MAX); + + for (const auto& p : particles) { + minBound = glm::min(minBound, glm::vec3(p.pos)); + maxBound = glm::max(maxBound, glm::vec3(p.pos)); + } + + float padding = 1.0f; + minBound -= padding; + maxBound += padding; + + VolumetricGrid grid; + grid.origin = minBound; + grid.extent = maxBound - minBound; + return grid; + } + + + class Curves { + + public: + VK::BufferMemory m_indirectBuffer; + std::vector cpuStrands; + std::vector curvePointPoolBuffer; + std::vector curveHeadersBuffer; + std::vector uniform_buffers; + std::vector curveSmoothPointsBuffer; + std::vector curveHeaders; + std::vector curvePointPool; + std::vector curveHeadersBufferPtr; + std::vector curvePointPoolBufferPtr; + std::vector freeSlots; + std::vector activeStrands; + std::vector shaders; + + VK::VulkanTexture shadowBuffer; + PushConstantsComputeBase pcComp{}; + PushConstants pcDraw{}; + + M_PIPE::UPipelineV1* pipe = NULL; + M_PIPE::UPipelineV1* pipeCurvesCompute = NULL; + M_PIPE::UPipelineV1* pipeCurvesFadeCompute = NULL; + VkDevice m_device = NULL; + u32 m_numImages = 0; + GLFWwindow* m_pWindow = NULL; + VkShaderModule m_shader_strandCompute = VK_NULL_HANDLE; + VkShaderModule m_shader_strandFadeCompute = VK_NULL_HANDLE; + VkShaderModule m_shader_strandVertex = VK_NULL_HANDLE; + VkShaderModule m_shader_strandFragment = VK_NULL_HANDLE; + VK::VulkanCore* vkCore; + size_t m_uniformBufferSize = 0; + // TODO: Remove. + bool runNudges = false; + bool hasNudges = false; + bool nudgetAlready = false; + u32 maxStrands = 200000; + u32 maxStrandPoints = 100; + + Curves(appInfo& info); + void createCVComputePipeline(); + void createShadowMapPipeline(); + void createCurveGraphicPipeline(); + void createShaders(); + void CreateIndirectBuffer(); + void createUniformBuffers(); + void RecordCommandBufferCurves(VkCommandBuffer CmdBuf, int ImageIndex); + //void nudgeParticle(Particle* mappedVram, uint32_t index, glm::vec3 newPos); + void generateVines(std::vector& headers, std::vector& pool, uint32_t numStrands); + void generateClumps(std::vector& headers, std::vector& pool, uint32_t numStrands); + void createBuffers(); + void createShadowImage(); + void createFadeShadowPipeline(); + void writeComputeBuffers(VkCommandBuffer CmdBuf, int ImageIndex, u32 currentFrame); + void applyNudges(VkCommandBuffer CmdBuf, int ImageIndex, u32 currentFrame); + uint32_t findEmptySlot(); + void UpdateScene(u32 currentFrame); + + ~Curves(); + }; +} // namespace + +#endif // STR_COMPONENT_HEADER +// +// +// --- The Implementation Section --- +#ifdef STR_COMPONENT + +namespace M_STR { + + Curves::Curves(appInfo& info) { + m_device = info.Device; + m_numImages = info.NumImages; + vkCore = info.vCore; + m_pWindow = info.m_pWindow; + m_uniformBufferSize = info.uniformBufferSize; + + u32 strandsToGen = 100000; + curveHeaders.resize(strandsToGen); + curvePointPool.resize(strandsToGen * maxStrandPoints); + // generateVines(curveHeaders, curvePointPool, strandsToGen); + generateClumps(curveHeaders, curvePointPool, strandsToGen); + cpuStrands.resize(maxStrands); + StrandData::IngestInitialPool(cpuStrands, curveHeaders, curvePointPool, m_numImages); + freeSlots.clear(); + activeStrands.clear(); + for (uint32_t i = 0; i < maxStrands; ++i) { + if (i >= curveHeaders.size()) { + freeSlots.push_back(i); + cpuStrands[i].points.clear(); + } + else { + activeStrands.push_back(i); + } + } + + pcComp.gridCellSize = 128.0f; + VolumetricGrid boundingInfo = calculateGrid(curvePointPool); + pcComp.gridOrigin = boundingInfo.origin; + pcDraw.gridOrigin = pcComp.gridOrigin; + pcComp.gridExtent = boundingInfo.extent.x; + pcDraw.gridExtent = boundingInfo.extent.x; + pcDraw.lightDir = glm::vec3(-1.0f, 0.3f, -0.02f); + pcDraw._pad = 1.0f; + + CreateIndirectBuffer(); // Disabled for now. We need to adj draw pass after compute is finished. + createUniformBuffers(); + createBuffers(); + createShadowImage(); + createShaders(); + createCurveGraphicPipeline(); // Graphics. + createFadeShadowPipeline(); + createCVComputePipeline(); + + } + + void Curves::generateVines(std::vector& headers, std::vector& pool, uint32_t numStrands) { + const uint32_t maxPtsPerStrand = 100; + const uint32_t initPtsPerStrand = 64; + const float spacing = 0.02f; + + headers.resize(numStrands); + pool.resize(numStrands * maxPtsPerStrand); + + std::vector indices(numStrands); + std::iota(indices.begin(), indices.end(), 0); + + std::for_each(std::execution::par, indices.begin(), indices.end(), [&](uint32_t sIdx) { + std::mt19937 gen(sIdx); + std::uniform_real_distribution angleDist(-0.015f, 0.015f); + std::uniform_real_distribution posDist(-10.0f, 10.0f); + std::uniform_real_distribution colorDist(0.6f, 1.0f); + + headers[sIdx].poolOffset = sIdx * maxPtsPerStrand; + headers[sIdx].pointCount = initPtsPerStrand; + headers[sIdx].maxPoints = maxPtsPerStrand; + headers[sIdx].color = glm::vec4(colorDist(gen), colorDist(gen), colorDist(gen), 1.0f); + + glm::vec3 currPos(posDist(gen), 0.0f, posDist(gen)); + glm::vec3 growDir(0, -1, 0); + + for (uint32_t i = 0; i < initPtsPerStrand; i++) { + uint32_t pIdx = headers[sIdx].poolOffset + i; + + // Radius taper. + float radius = glm::mix(0.05f, 0.05f, (float)i / 9.0f); + pool[pIdx].pos = glm::vec4(currPos, radius); + + // Rotate growth + glm::mat4 rot = glm::rotate(glm::mat4(1.0f), angleDist(gen), glm::vec3(1, 0, 0)); + rot = glm::rotate(rot, angleDist(gen), glm::vec3(0, 0, 1)); + growDir = glm::vec3(rot * glm::vec4(growDir, 0.0f)); + + currPos += growDir * spacing; + } + }); + } + + void Curves::generateClumps(std::vector& headers, std::vector& pool, uint32_t numStrands) { + const uint32_t maxPtsPerStrand = 100; + const uint32_t initPtsPerStrand = 64; + const float spacing = 0.007f; + + // Attractors + const int numClumps = 3; + glm::vec3 clumps[numClumps] = { + glm::vec3(0, -3, 0), + glm::vec3(2.0f, -3.2f, 1.5f) * 2.0f, + glm::vec3(-1.5f, -3.7f, -2.0f) * 3.0f + }; + + std::vector indices(numStrands); + std::iota(indices.begin(), indices.end(), 0); + + std::for_each(std::execution::par, indices.begin(), indices.end(), [&](uint32_t sIdx) { + std::mt19937 gen(sIdx); + std::uniform_real_distribution jitter(-1.4f, 1.4f); + std::uniform_real_distribution noise(-0.05f, 0.05f); + std::uniform_real_distribution colorDist(0.6f, 1.0f); + + // Assign this strand to one of the clumps + glm::vec3 center = clumps[sIdx % numClumps]; + + headers[sIdx].poolOffset = sIdx * maxPtsPerStrand; + headers[sIdx].pointCount = initPtsPerStrand; + headers[sIdx].maxPoints = maxPtsPerStrand; + headers[sIdx].color = glm::vec4(colorDist(gen), colorDist(gen), colorDist(gen), 1.0f); + + glm::vec3 currPos = center + glm::vec3(jitter(gen), jitter(gen), jitter(gen))*2.0f; + glm::vec3 growDir = glm::normalize(glm::cross(currPos - center, glm::vec3(0, 1, 0))); + + for (uint32_t i = 0; i < initPtsPerStrand; i++) { + uint32_t pIdx = headers[sIdx].poolOffset + i; + pool[pIdx].pos = glm::vec4(currPos, 0.015f); + glm::vec3 toCenter = glm::normalize(center - currPos); + growDir = glm::normalize(growDir + toCenter * 0.07f + glm::vec3(noise(gen))); + currPos += growDir * spacing; + } + }); + } + + uint32_t Curves::findEmptySlot() { + if (freeSlots.empty()) return UINT32_MAX; + uint32_t slot = freeSlots.back(); + freeSlots.pop_back(); + activeStrands.push_back(slot); + return slot; + } + + void Curves::applyNudges(VkCommandBuffer CmdBuf, int ImageIndex, u32 currentFrame) { + if (!runNudges)return; + if (!nudgetAlready) { + UpdateScene(currentFrame); // Adding a strand, nudging the rest in master buffer. + nudgetAlready = true; + } + // Apply master changes to device. + std::span gpuHeaders{ static_cast(curveHeadersBufferPtr[ImageIndex]), maxStrands }; + std::span gpuPoints{ static_cast(curvePointPoolBufferPtr[ImageIndex]), maxStrands * maxStrandPoints }; + hasNudges = false; + for (uint32_t idx : activeStrands) { + auto& localStrandRef = cpuStrands[idx]; + + if (localStrandRef.dirtyCount > 0) { + // UPLOAD to the current buffer. + gpuHeaders[idx].pointCount = localStrandRef.points.size(); + gpuHeaders[idx].poolOffset = idx * maxStrandPoints; + gpuHeaders[idx].color = localStrandRef.color; + gpuHeaders[idx].maxPoints = maxStrandPoints; + + // Update point pool for the strand. + std::copy(localStrandRef.points.begin(), localStrandRef.points.end(), &gpuPoints[idx * maxStrandPoints]); + + // 2. One buffer is now up to date + localStrandRef.dirtyCount--; + hasNudges = true; + } + } + } + + void Curves::createUniformBuffers() { + assert(m_uniformBufferSize); + uniform_buffers = vkCore->CreateUniformBuffers(m_uniformBufferSize); + } + + void Curves::createBuffers() { + VkResult res; + VkBufferUsageFlags bufferUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + VkMemoryPropertyFlags hostDeviceVisible = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + VkDeviceSize bufferSize; + for (uint32_t i = 0; i < m_numImages; ++i) { + VK::BufferMemory curveHeader; + void* pBuffer = NULL; + void* pBufferPool = NULL; + + // Buffer for structural headers. + bufferSize = maxStrands * sizeof(CurveHeader); + curveHeader = vkCore->CreateBuffer( + bufferSize, + bufferUsage, + hostDeviceVisible, + VK_SHARING_MODE_CONCURRENT, + { vkCore->m_queueFamily, vkCore->m_queueFamilyCompute } + ); + res = vkMapMemory(m_device, curveHeader.m_mem, 0, bufferSize, 0, &pBuffer); + CHECK_VK_RESULT(res, "vkMapMemory curveHeader"); + memcpy(pBuffer, curveHeaders.data(), (size_t)curveHeaders.size() * sizeof(CurveHeader)); // The same data in each buffer. + curveHeadersBuffer.push_back(curveHeader); + curveHeadersBufferPtr.push_back(pBuffer); + + // Buffer for transit smooth curves to VTX. + bufferSize = maxStrands * sizeof(glm::vec4) * cfg::SMOOTH_STRAND_COUNT; + curveSmoothPointsBuffer.push_back( + vkCore->CreateBuffer( + bufferSize, + bufferUsage, + hostDeviceVisible, + VK_SHARING_MODE_CONCURRENT, + { vkCore->m_queueFamily, vkCore->m_queueFamilyCompute } + ) + ); + + // Point pool buffer, for each frame in flight. + bufferSize = maxStrands * maxStrandPoints * sizeof(CurvePoint); + VK::BufferMemory curvePool; + curvePool = vkCore->CreateBuffer( + bufferSize, + bufferUsage, + hostDeviceVisible, + VK_SHARING_MODE_CONCURRENT, + { vkCore->m_queueFamily, vkCore->m_queueFamilyCompute } + ); + res = vkMapMemory(m_device, curvePool.m_mem, 0, bufferSize, 0, &pBufferPool); + CHECK_VK_RESULT(res, "vkMapMemory PointPool"); + memcpy(pBufferPool, curvePointPool.data(), (size_t)curvePointPool.size() * sizeof(CurvePoint)); + curvePointPoolBuffer.push_back(curvePool); + curvePointPoolBufferPtr.push_back(pBufferPool); // For later updates. + } + } + + void Curves::createShadowImage() { + + VkResult res; + VkFormat imageFormat = VK_FORMAT_R32_SFLOAT; + + vkCore->CreateImage( + shadowBuffer, + pcComp.gridCellSize, // Width + pcComp.gridCellSize, // Heigh + imageFormat, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + cfg::IS_NOT_CUBE_MAP, // Cubemap + pcComp.gridCellSize, // Depths + cfg::IS_3D_IMAGE, // is 3d not 2d + VK_SHARING_MODE_CONCURRENT // Sharing mode. + ); + vkCore->setObjectName(m_device, shadowBuffer.m_image, std::format("shadowBufferImage").c_str()); + + shadowBuffer.m_view = VK::CreateImageView( + vkCore->GetDevice(), shadowBuffer.m_image, imageFormat, + VK_IMAGE_ASPECT_COLOR_BIT, + cfg::IS_NOT_CUBE_MAP, // Cubemap. + cfg::IS_3D_IMAGE // is 3d image. + ); + vkCore->setObjectName(m_device, shadowBuffer.m_view, std::format("shadowBufferView").c_str()); + + VkSamplerCreateInfo samplerInfo{}; + samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + // Preserved from previous. + samplerInfo.magFilter = VK_FILTER_LINEAR; // automatically blends the 8 surrounding voxels + samplerInfo.minFilter = VK_FILTER_LINEAR; // to avoid lego bricks + samplerInfo.anisotropyEnable = VK_FALSE; + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + + // Added for light depth. + samplerInfo.compareEnable = VK_FALSE; + samplerInfo.compareOp = VK_COMPARE_OP_LESS; + samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 1.0f; + res = vkCreateSampler(vkCore->GetDevice(), &samplerInfo, nullptr, &shadowBuffer.m_sampler); + CHECK_VK_RESULT(res, "vkCreateSampler"); + vkCore->setObjectName(m_device, shadowBuffer.m_sampler, std::format("shadowBufferSampler").c_str()); + } + + void Curves::CreateIndirectBuffer() { + std::vector DrawCommands(1); + VkDrawIndirectCommand cmd = { + .vertexCount = cfg::SMOOTH_STRAND_COUNT, + .instanceCount = maxStrands, + .firstVertex = 0, + .firstInstance = 0 + }; + DrawCommands[0] = cmd; + m_indirectBuffer = vkCore->CreateIndirectBuffer( + DrawCommands.data(), + ARRAY_SIZE_IN_BYTES(DrawCommands) + ); + } + + void Curves::createShaders() { // TODO: Refactor. + m_shader_strandCompute = VK::CreateShaderModuleFromText(m_device, "strandsBase.comp"); + m_shader_strandFadeCompute = VK::CreateShaderModuleFromText(m_device, "strandsFade.comp"); + m_shader_strandVertex = VK::CreateShaderModuleFromText(m_device, "crv.vert"); + m_shader_strandFragment = VK::CreateShaderModuleFromText(m_device, "crv.frag"); + shaders.push_back(m_shader_strandCompute); + shaders.push_back(m_shader_strandFadeCompute); + shaders.push_back(m_shader_strandVertex); + shaders.push_back(m_shader_strandFragment); + vkCore->setObjectName(m_device, m_shader_strandCompute, std::format("m_shader_strandCompute").c_str()); + vkCore->setObjectName(m_device, m_shader_strandFadeCompute, std::format("m_shader_strandFadeCompute").c_str()); + vkCore->setObjectName(m_device, m_shader_strandVertex, std::format("m_shader_strandVertex").c_str()); + vkCore->setObjectName(m_device, m_shader_strandFragment, std::format("m_shader_strandFragment").c_str()); + } + + void Curves::createCurveGraphicPipeline() { + M_PIPE::PipelineDescU1 pipeInfo{}; + pipeInfo.Device = m_device; + pipeInfo.vertex_shader = m_shader_strandVertex; + pipeInfo.fragment_shader = m_shader_strandFragment; + pipeInfo.numImages = m_numImages; + pipeInfo.pushSize = sizeof(pcDraw); + + std::vector smoothBuffers; + for (VK::BufferMemory compound : curveSmoothPointsBuffer) { + smoothBuffers.push_back(compound.m_buffer); + } + std::vector headBuffers; + for (VK::BufferMemory compound : curveHeadersBuffer) { + headBuffers.push_back(compound.m_buffer); + } + + M_PIPE::BindingInfo smoothPointsBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .multiBuffer = smoothBuffers, // Transit to graphics pipe. + .hasCustomBinding = true, + .hasSingleBufferPerSet = true, + .bindingOverride = 0 + }; + + M_PIPE::BindingInfo headersBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .multiBuffer = headBuffers, // Curve heads. + .hasCustomBinding = true, + .hasSingleBufferPerSet = true, + .bindingOverride = 1 + }; + + M_PIPE::BindingInfo shadowBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .samplers = {shadowBuffer.m_sampler}, + .imageViews = {shadowBuffer.m_view}, + .imageLayoutInit = VK_IMAGE_LAYOUT_GENERAL, + .hasCustomBinding = true, + .bindingOverride = 2 + }; + + pipeInfo.bindings.push_back(smoothPointsBinding); + pipeInfo.bindings.push_back(headersBinding); + pipeInfo.bindings.push_back(shadowBinding); + + std::vector colorFormats; + colorFormats.push_back(vkCore->GetSwapChainFormat()); + pipeInfo.prim_topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + pipeInfo.colorAttachmentFormats = colorFormats; + pipeInfo.depthAttachmentFormat = vkCore->GetDepthFormat(); + pipeInfo.hasDynamicState = true; + pipeInfo.pWindow = m_pWindow; + pipeInfo.hasViewport = true; + pipeInfo.hasRendering = true; + pipeInfo.hasRasterization = true; + pipeInfo.nameId = "strandGraphicsPipeline"; + pipeInfo.pCore = vkCore; + pipe = new M_PIPE::UPipelineV1(pipeInfo); + } + + void Curves::createFadeShadowPipeline() { + M_PIPE::PipelineDescU1 pipe_info{}; + pipe_info.Device = m_device; + pipe_info.numImages = m_numImages; + { + M_PIPE::BindingInfo shadowImageBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .imageViews = {shadowBuffer.m_view}, + .imageLayoutInit = VK_IMAGE_LAYOUT_GENERAL, + .hasCustomBinding = true, + .bindingOverride = 0 + }; + pipe_info.bindings.push_back(shadowImageBinding); + } + pipe_info.compute_shader = m_shader_strandFadeCompute; + pipe_info.isCompute = true; + pipe_info.nameId = "curvesComputeFadePipeline"; + pipe_info.pCore = vkCore; + pipeCurvesFadeCompute = new M_PIPE::UPipelineV1(pipe_info); + } + + void Curves::writeComputeBuffers(VkCommandBuffer CmdBuf, int ImageIndex, u32 currentFrame) { + + if (currentFrame == 0) { + M_TOOLS::Sync() + .image(shadowBuffer.m_image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, // From nothing to General + VK_PIPELINE_STAGE_2_NONE, 0, // Source: Nothing + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_WRITE_BIT) // Ready for use + .apply(CmdBuf); + } + + M_TOOLS::Sync() + .image(shadowBuffer.m_image, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_READ_BIT, // srcMask, srcAccess + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT) // dstMask, dstAccess + .apply(CmdBuf); + + // Shadow fade. + pipeCurvesFadeCompute->Bind(CmdBuf); + pipeCurvesFadeCompute->bindDescriptorSets(CmdBuf, ImageIndex); + vkCmdDispatch(CmdBuf, 16, 16, 16); // TODO: link size. + + if (hasNudges) { + // Push vk buffer changes to device. + M_TOOLS::Sync() + .buffer(curveHeadersBuffer[ImageIndex].m_buffer, + VK_PIPELINE_STAGE_2_HOST_BIT, VK_ACCESS_2_HOST_WRITE_BIT, // Source: CPU + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_READ_BIT) // Dest: GPU + .buffer(curvePointPoolBuffer[ImageIndex].m_buffer, + VK_PIPELINE_STAGE_2_HOST_BIT, VK_ACCESS_2_HOST_WRITE_BIT, + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_READ_BIT) + .apply(CmdBuf); + } + + // Compute catmullRom + shadows + pipeCurvesCompute->Bind(CmdBuf); + pipeCurvesCompute->bindDescriptorSets(CmdBuf, ImageIndex); + pipeCurvesCompute->PushConstants(CmdBuf, sizeof(pcComp), &pcComp); + vkCmdPushConstants( + CmdBuf, // VkCommandBuffer commandBuffer + pipeCurvesCompute->GetPipelineLayout(), // VkPipelineLayout layout + VK_SHADER_STAGE_COMPUTE_BIT, // VkShaderStageFlags stageFlags + 0, // offset + sizeof(pcComp), // size + &pcComp + ); + + vkCmdDispatch(CmdBuf, maxStrands, 1, 1); + + // hand off shadow and renderable curves to render pipes. + M_TOOLS::Sync() + .image(shadowBuffer.m_image, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, // srcMask, srcAccess + VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE) // dstMask, dstAccess + .buffer(curveSmoothPointsBuffer[ImageIndex].m_buffer, + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, // srcMask, srcAccess + VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT) // dstMask, dstAccess + .apply(CmdBuf); + // Note: Don't use NONE mask/access, it would only silence validation. + } + + void Curves::createCVComputePipeline() { + M_PIPE::PipelineDescU1 pipe_info{}; + pipe_info.Device = m_device; + pipe_info.numImages = m_numImages; + + std::vector smoothBuffers; // TODO: move in pipeline. + for (VK::BufferMemory compound : curveSmoothPointsBuffer) { + smoothBuffers.push_back(compound.m_buffer); + } + std::vector headBuffers; + for (VK::BufferMemory compound : curveHeadersBuffer) { + headBuffers.push_back(compound.m_buffer); + } + + std::vector poolBuffers; + for (VK::BufferMemory compound : curvePointPoolBuffer) { + poolBuffers.push_back(compound.m_buffer); + } + + { + M_PIPE::BindingInfo headersBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .multiBuffer = headBuffers, + .hasCustomBinding = true, + .hasSingleBufferPerSet = true, + .bindingOverride = 0 + }; + pipe_info.bindings.push_back(headersBinding); + + M_PIPE::BindingInfo pointPoolBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .multiBuffer = poolBuffers, // Point-pool for compute only. + .hasCustomBinding = true, + .hasSingleBufferPerSet = true, + .bindingOverride = 1 + }; + pipe_info.bindings.push_back(pointPoolBinding); + + M_PIPE::BindingInfo smoothPointsBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .multiBuffer = smoothBuffers, // Transit to graphics pipe. + .hasCustomBinding = true, + .hasSingleBufferPerSet = true, + .bindingOverride = 2 + }; + pipe_info.bindings.push_back(smoothPointsBinding); + + M_PIPE::BindingInfo shadowImageBinding{ + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .imageViews = {shadowBuffer.m_view}, + .imageLayoutInit = VK_IMAGE_LAYOUT_GENERAL, + .hasCustomBinding = true, + .bindingOverride = 3 + }; + pipe_info.bindings.push_back(shadowImageBinding); + } + pipe_info.pushSize = sizeof(PushConstantsComputeBase); + pipe_info.compute_shader = m_shader_strandCompute; + pipe_info.isCompute = true; + pipe_info.nameId = "curvesComputePipeline"; + pipe_info.pCore = vkCore; + pipeCurvesCompute = new M_PIPE::UPipelineV1(pipe_info); + } + + void Curves::RecordCommandBufferCurves(VkCommandBuffer CmdBuf, int ImageIndex) { + vkCore->pfnVkCmdSetPolygonModeEXT(CmdBuf, VkPolygonMode::VK_POLYGON_MODE_FILL); + vkCore->pfnVkCmdSetPrimitiveTopologyEXT(CmdBuf, VK_PRIMITIVE_TOPOLOGY_POINT_LIST); + pipe->Bind(CmdBuf); + pipe->PushConstants(CmdBuf, sizeof(pcDraw), &pcDraw); + pipe->bindDescriptorSets(CmdBuf, ImageIndex); + vkCmdDrawIndirect(CmdBuf, + m_indirectBuffer.m_buffer, + 0, // offset inside the indirect buffer + (u32)1, // number of VkDrawIndirectCommand elements + sizeof(VkDrawIndirectCommand)); + } + + Curves::~Curves() { + vkDeviceWaitIdle(m_device); + + for (auto& buffer : uniform_buffers) { + buffer.Destroy(m_device); + } + + for (auto shaderModule : shaders) { + vkDestroyShaderModule(m_device, shaderModule, NULL); + } + + for (auto buff : curveHeadersBuffer) { + vkUnmapMemory(m_device, buff.m_mem); + buff.Destroy(m_device); + } + for (auto buff : curveSmoothPointsBuffer) { + buff.Destroy(m_device); + } + m_indirectBuffer.Destroy(m_device); + + for (auto buff : curvePointPoolBuffer) { + vkUnmapMemory(m_device, buff.m_mem); + buff.Destroy(m_device); + } + + shadowBuffer.Destroy(m_device); + + delete pipe; + delete pipeCurvesCompute; + delete pipeCurvesFadeCompute; + + } + +} // namespace + +#endif +// --- The Implementation Section End --- \ No newline at end of file