/* * 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 ---