Files
samples/strandComponent.hpp
2026-04-06 13:00:11 -04:00

784 lines
31 KiB
C++

/*
* 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 <stdio.h>
#include <span>
#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<CurvePoint> 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<StrandData>&targetMasterRecord,
std::span<const CurveHeader> srcHeaders,
std::span<const CurvePoint> 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<CurvePoint>& 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<StrandData> cpuStrands;
std::vector <VK::BufferMemory> curvePointPoolBuffer;
std::vector <VK::BufferMemory> curveHeadersBuffer;
std::vector<VK::BufferMemory> uniform_buffers;
std::vector <VK::BufferMemory> curveSmoothPointsBuffer;
std::vector<CurveHeader> curveHeaders;
std::vector<CurvePoint> curvePointPool;
std::vector <void*> curveHeadersBufferPtr;
std::vector <void*> curvePointPoolBufferPtr;
std::vector<uint32_t> freeSlots;
std::vector<uint32_t> activeStrands;
std::vector <VkShaderModule> 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<CurveHeader>& headers, std::vector<CurvePoint>& pool, uint32_t numStrands);
void generateClumps(std::vector<CurveHeader>& headers, std::vector<CurvePoint>& 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<CurveHeader>& headers, std::vector<CurvePoint>& 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<uint32_t> 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<float> angleDist(-0.015f, 0.015f);
std::uniform_real_distribution<float> posDist(-10.0f, 10.0f);
std::uniform_real_distribution<float> 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<CurveHeader>& headers, std::vector<CurvePoint>& 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<uint32_t> 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<float> jitter(-1.4f, 1.4f);
std::uniform_real_distribution<float> noise(-0.05f, 0.05f);
std::uniform_real_distribution<float> 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<CurveHeader> gpuHeaders{ static_cast<CurveHeader*>(curveHeadersBufferPtr[ImageIndex]), maxStrands };
std::span<CurvePoint> gpuPoints{ static_cast<CurvePoint*>(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<VkDrawIndirectCommand> 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<VkBuffer> smoothBuffers;
for (VK::BufferMemory compound : curveSmoothPointsBuffer) {
smoothBuffers.push_back(compound.m_buffer);
}
std::vector<VkBuffer> 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<VkFormat> 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<VkBuffer> smoothBuffers; // TODO: move in pipeline.
for (VK::BufferMemory compound : curveSmoothPointsBuffer) {
smoothBuffers.push_back(compound.m_buffer);
}
std::vector<VkBuffer> headBuffers;
for (VK::BufferMemory compound : curveHeadersBuffer) {
headBuffers.push_back(compound.m_buffer);
}
std::vector<VkBuffer> 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 ---