Program Listing for File CascadedShadowMap.cpp#
↰ Return to documentation for file (Src/GraphicsEngineVulkan/scene/light/directional_light/CascadedShadowMap.cpp)
module;
#include <vector>
#include <unordered_map>
#include <memory>
#include <filesystem>
#include <sstream>
#include <vulkan/vulkan.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include "common/FormatHelper.hpp"
#include "common/Utilities.hpp"
module kataglyphis.vulkan.cascaded_shadow_map;
import kataglyphis.vulkan.device;
import kataglyphis.vulkan.texture;
import kataglyphis.vulkan.file;
import kataglyphis.vulkan.shader_helper;
import kataglyphis.vulkan.scene;
import kataglyphis.vulkan.vertex;
import kataglyphis.vulkan.buffer;
import kataglyphis.vulkan.buffer_manager;
namespace Kataglyphis {
// Global map to store per-layer image views
static std::unordered_map<CascadedShadowMap*, std::vector<vk::ImageView>> g_layerViewsMap;
void CascadedShadowMap::init(std::shared_ptr<VulkanDevice>in_device, uint32_t width, uint32_t height, uint32_t num_cascades)
{
this->device = in_device;
this->shadowWidth = width;
this->shadowHeight = height;
this->numCascades = num_cascades;
cascadeData.resize(numCascades);
vk::Format depthFormat = Kataglyphis::choose_supported_format(device->getPhysicalDevice(), { vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint }, vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment);
// Create 2D Texture Array for Cascades
shadowMapArray = std::make_unique<Texture>();
shadowMapArray->createImage(device, shadowWidth, shadowHeight, 1, depthFormat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, numCascades);
// Create a view for the entire array (used in descriptor set for reading later)
shadowMapArray->createImageView(device, depthFormat, vk::ImageAspectFlagBits::eDepth, 1, vk::ImageViewType::e2DArray, numCascades);
createRenderPass();
createFramebuffers();
}
std::vector<glm::vec4> CascadedShadowMap::getFrustumCornersWorldSpace(const glm::mat4& proj, const glm::mat4& view)
{
const auto inv = glm::inverse(proj * view);
std::vector<glm::vec4> frustumCorners;
for (unsigned int x = 0; x < 2; ++x) {
for (unsigned int y = 0; y < 2; ++y) {
for (unsigned int z = 0; z < 2; ++z) {
const glm::vec4 pt = inv * glm::vec4((2.0F * x) - 1.0F, (2.0F * y) - 1.0F, (2.0F * z) - 1.0F, 1.0F);
frustumCorners.push_back(pt / pt.w);
}
}
}
return frustumCorners;
}
void CascadedShadowMap::updateCascades(const glm::mat4& cameraView, float cameraFov, float aspect, float nearPlane, float farPlane, const glm::vec3& lightDir)
{
std::vector<float> cascadeSplits(numCascades + 1);
for (uint32_t i = 0; i < numCascades + 1; i++) {
if (i == 0) {
cascadeSplits[i] = nearPlane;
} else {
// Using a simple uniform split for now; could be changed to practical split scheme
cascadeSplits[i] = farPlane * (static_cast<float>(i) / static_cast<float>(numCascades));
}
}
for (uint32_t i = 0; i < numCascades; i++) {
glm::mat4 const curr_cascade_proj = glm::perspective(glm::radians(cameraFov), aspect, cascadeSplits[i], cascadeSplits[i + 1]);
std::vector<glm::vec4> frustumCornerWorldSpace = getFrustumCornersWorldSpace(curr_cascade_proj, cameraView);
glm::vec3 center = glm::vec3(0, 0, 0);
for (const auto &v : frustumCornerWorldSpace) { center += glm::vec3(v); }
center /= frustumCornerWorldSpace.size();
glm::mat4 const light_view_matrix = glm::lookAt(center - lightDir, center, glm::vec3(0.0F, 1.0F, 0.0F));
float minX = std::numeric_limits<float>::max();
float maxX = std::numeric_limits<float>::lowest();
float minY = std::numeric_limits<float>::max();
float maxY = std::numeric_limits<float>::lowest();
float minZ = std::numeric_limits<float>::max();
float maxZ = std::numeric_limits<float>::lowest();
for (const auto& m : frustumCornerWorldSpace) {
glm::vec4 const v_light_view = light_view_matrix * m;
minX = std::min(minX, v_light_view.x);
maxX = std::max(maxX, v_light_view.x);
minY = std::min(minY, v_light_view.y);
maxY = std::max(maxY, v_light_view.y);
minZ = std::min(minZ, v_light_view.z);
maxZ = std::max(maxZ, v_light_view.z);
}
constexpr float zMult = 10.0F;
if (minZ < 0) minZ *= zMult; else minZ /= zMult;
if (maxZ < 0) maxZ /= zMult; else maxZ *= zMult;
glm::mat4 const light_projection = glm::ortho(minX, maxX, minY, maxY, minZ, maxZ);
cascadeData[i].viewProjMatrix = light_projection * light_view_matrix;
// The split depth is the far plane of this cascade frustum, but measured in view space depth
// A simple way is to pass the positive distance
cascadeData[i].splitDepth = cascadeSplits[i + 1];
}
}
void CascadedShadowMap::createRenderPass()
{
vk::AttachmentDescription depthAttachment{};
depthAttachment.format = vk::Format::eD32Sfloat;
depthAttachment.samples = vk::SampleCountFlagBits::e1;
depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
depthAttachment.storeOp = vk::AttachmentStoreOp::eStore;
depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
depthAttachment.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::AttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 0;
depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
vk::SubpassDescription subpass{};
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.pDepthStencilAttachment = &depthAttachmentRef;
std::array<vk::SubpassDependency, 2> dependencies;
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
dependencies[0].dstSubpass = 0;
dependencies[0].srcStageMask = vk::PipelineStageFlagBits::eFragmentShader;
dependencies[0].dstStageMask = vk::PipelineStageFlagBits::eEarlyFragmentTests;
dependencies[0].srcAccessMask = vk::AccessFlagBits::eShaderRead;
dependencies[0].dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite;
dependencies[0].dependencyFlags = vk::DependencyFlagBits::eByRegion;
dependencies[1].srcSubpass = 0;
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
dependencies[1].srcStageMask = vk::PipelineStageFlagBits::eLateFragmentTests;
dependencies[1].dstStageMask = vk::PipelineStageFlagBits::eFragmentShader;
dependencies[1].srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite;
dependencies[1].dstAccessMask = vk::AccessFlagBits::eShaderRead;
dependencies[1].dependencyFlags = vk::DependencyFlagBits::eByRegion;
vk::RenderPassCreateInfo renderPassInfo{};
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &depthAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
renderPassInfo.pDependencies = dependencies.data();
auto result = device->getLogicalDevice().createRenderPass(renderPassInfo);
ASSERT_VULKAN(VkResult(result.result), "Failed to create cascaded shadow map render pass!");
renderPass = result.value;
}
void CascadedShadowMap::createFramebuffers()
{
framebuffers.resize(numCascades);
// Create layer views for rendering to individual layers
std::vector<vk::ImageView> layerViews(numCascades);
for (uint32_t i = 0; i < numCascades; i++) {
vk::ImageViewCreateInfo viewInfo{};
viewInfo.image = shadowMapArray->getImage();
viewInfo.viewType = vk::ImageViewType::e2DArray;
viewInfo.format = vk::Format::eD32Sfloat;
viewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = i; // Point to the specific layer
viewInfo.subresourceRange.layerCount = 1;
auto viewResult = device->getLogicalDevice().createImageView(viewInfo);
ASSERT_VULKAN(VkResult(viewResult.result), "Failed to create shadow map layer view!");
layerViews[i] = viewResult.value;
vk::FramebufferCreateInfo framebufferInfo{};
framebufferInfo.renderPass = renderPass;
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = &layerViews[i];
framebufferInfo.width = shadowWidth;
framebufferInfo.height = shadowHeight;
framebufferInfo.layers = 1;
auto fbResult = device->getLogicalDevice().createFramebuffer(framebufferInfo);
ASSERT_VULKAN(VkResult(fbResult.result), "Failed to create shadow map framebuffer!");
framebuffers[i] = fbResult.value;
}
// Store layer views to clean them up later
g_layerViewsMap[this] = std::move(layerViews);
}
void CascadedShadowMap::cleanUp()
{
if (device) {
spdlog::info("CascadedShadowMap: Destroying pipeline handle: 0x{:x}", (uint64_t)(VkPipeline)graphicsPipeline);
auto it = g_layerViewsMap.find(this);
if (it != g_layerViewsMap.end()) {
for (auto view : it->second) {
device->getLogicalDevice().destroyImageView(view);
}
g_layerViewsMap.erase(it);
}
for (auto fb : framebuffers) {
device->getLogicalDevice().destroyFramebuffer(fb);
}
framebuffers.clear();
device->getLogicalDevice().destroyRenderPass(renderPass);
if (graphicsPipeline) { device->getLogicalDevice().destroyPipeline(graphicsPipeline); }
if (pipelineLayout) { device->getLogicalDevice().destroyPipelineLayout(pipelineLayout); }
if (descriptorSetLayout) { device->getLogicalDevice().destroyDescriptorSetLayout(descriptorSetLayout); }
if (descriptorPool) { device->getLogicalDevice().destroyDescriptorPool(descriptorPool); }
if (shadowMapArray) {
shadowMapArray->cleanUp();
shadowMapArray.reset();
}
lightMatricesBuffer.cleanUp();
}
}
void CascadedShadowMap::createDescriptorSetAndPipeline()
{
vk::DescriptorSetLayoutBinding lightMatricesBinding{};
lightMatricesBinding.binding = 1; // UNIFORM_LIGHT_MATRICES_BINDING = 1
lightMatricesBinding.descriptorCount = 1;
lightMatricesBinding.descriptorType = vk::DescriptorType::eUniformBuffer;
lightMatricesBinding.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eGeometry;
vk::DescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &lightMatricesBinding;
auto layoutRes = device->getLogicalDevice().createDescriptorSetLayout(layoutInfo);
ASSERT_VULKAN(VkResult(layoutRes.result), "Failed to create shadow map descriptor set layout!");
descriptorSetLayout = layoutRes.value;
vk::DescriptorPoolSize poolSize{};
poolSize.type = vk::DescriptorType::eUniformBuffer;
poolSize.descriptorCount = 1;
vk::DescriptorPoolCreateInfo poolInfo{};
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = 1;
auto poolRes = device->getLogicalDevice().createDescriptorPool(poolInfo);
ASSERT_VULKAN(VkResult(poolRes.result), "Failed to create shadow map descriptor pool!");
descriptorPool = poolRes.value;
vk::DescriptorSetAllocateInfo allocInfo{};
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = &descriptorSetLayout;
auto allocRes = device->getLogicalDevice().allocateDescriptorSets(allocInfo);
ASSERT_VULKAN(VkResult(allocRes.result), "Failed to allocate shadow map descriptor set!");
descriptorSet = allocRes.value[0];
std::vector<glm::mat4> lightMatrices(numCascades);
for (size_t i = 0; i < lightMatrices.size(); i++) {
lightMatrices[i] = cascadeData[i].viewProjMatrix;
}
vk::CommandPool transferCommandPool{};
vk::CommandPoolCreateInfo poolCreateInfo{};
poolCreateInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
poolCreateInfo.queueFamilyIndex = static_cast<uint32_t>(device->getQueueFamilies().graphics_family);
auto poolResult = device->getLogicalDevice().createCommandPool(poolCreateInfo);
if (poolResult.result != vk::Result::eSuccess) {
spdlog::error("Failed to create transfer command pool for cascaded shadow map! Error: {}", static_cast<int>(poolResult.result));
std::abort();
}
transferCommandPool = poolResult.value;
VulkanBufferManager vbm;
vbm.createBufferAndUploadVectorOnDevice(device, transferCommandPool, lightMatricesBuffer,
vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent,
lightMatrices);
device->getLogicalDevice().destroyCommandPool(transferCommandPool);
vk::DescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = lightMatricesBuffer.getBuffer();
bufferInfo.offset = 0;
bufferInfo.range = sizeof(glm::mat4) * numCascades;
vk::WriteDescriptorSet write{};
write.dstSet = descriptorSet;
write.dstBinding = 1; // UNIFORM_LIGHT_MATRICES_BINDING = 1
write.dstArrayElement = 0;
write.descriptorType = vk::DescriptorType::eUniformBuffer;
write.descriptorCount = 1;
write.pBufferInfo = &bufferInfo;
device->getLogicalDevice().updateDescriptorSets(1, &write, 0, nullptr);
}
void CascadedShadowMap::createGraphicsPipeline()
{
createDescriptorSetAndPipeline();
std::stringstream shadow_shader_dir;
std::filesystem::path const cwd = std::filesystem::current_path();
shadow_shader_dir << cwd.string() << RELATIVE_RESOURCE_PATH << "Shaders/rasterizer/shadows/";
ShaderHelper shaderHelper;
shaderHelper.compileShader(shadow_shader_dir.str(), "directional_shadow_map.vert");
shaderHelper.compileShader(shadow_shader_dir.str(), "directional_shadow_map.geom");
shaderHelper.compileShader(shadow_shader_dir.str(), "directional_shadow_map.frag");
File vertFile(shaderHelper.getShaderSpvDir(shadow_shader_dir.str(), "directional_shadow_map.vert"));
File geomFile(shaderHelper.getShaderSpvDir(shadow_shader_dir.str(), "directional_shadow_map.geom"));
File fragFile(shaderHelper.getShaderSpvDir(shadow_shader_dir.str(), "directional_shadow_map.frag"));
vk::ShaderModule vertModule = shaderHelper.createShaderModule(device, vertFile.readCharSequence());
vk::ShaderModule geomModule = shaderHelper.createShaderModule(device, geomFile.readCharSequence());
vk::ShaderModule fragModule = shaderHelper.createShaderModule(device, fragFile.readCharSequence());
vk::PipelineShaderStageCreateInfo vertStageInfo{};
vertStageInfo.stage = vk::ShaderStageFlagBits::eVertex;
vertStageInfo.module = vertModule;
vertStageInfo.pName = "main";
vk::PipelineShaderStageCreateInfo geomStageInfo{};
geomStageInfo.stage = vk::ShaderStageFlagBits::eGeometry;
geomStageInfo.module = geomModule;
geomStageInfo.pName = "main";
vk::PipelineShaderStageCreateInfo fragStageInfo{};
fragStageInfo.stage = vk::ShaderStageFlagBits::eFragment;
fragStageInfo.module = fragModule;
fragStageInfo.pName = "main";
std::array skyStages = {vertStageInfo, geomStageInfo, fragStageInfo};
vk::VertexInputBindingDescription bindingDesc{};
bindingDesc.binding = 0;
bindingDesc.stride = sizeof(Vertex);
bindingDesc.inputRate = vk::VertexInputRate::eVertex;
vk::VertexInputAttributeDescription posAttr{};
posAttr.location = 0;
posAttr.binding = 0;
posAttr.format = vk::Format::eR32G32B32Sfloat;
posAttr.offset = 0;
std::array vertexBindingDescs = {bindingDesc};
std::array vertexAttrDescs = {posAttr};
vk::PipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexBindingDescs.size());
vertexInputInfo.pVertexBindingDescriptions = vertexBindingDescs.data();
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexAttrDescs.size());
vertexInputInfo.pVertexAttributeDescriptions = vertexAttrDescs.data();
vk::PipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;
inputAssembly.primitiveRestartEnable = VK_FALSE;
vk::PipelineViewportStateCreateInfo viewportState{};
viewportState.viewportCount = 1;
viewportState.scissorCount = 1;
std::vector<vk::DynamicState> dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamicState{};
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
dynamicState.pDynamicStates = dynamicStates.data();
vk::PipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = vk::PolygonMode::eFill;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = vk::CullModeFlagBits::eBack;
rasterizer.frontFace = vk::FrontFace::eCounterClockwise;
rasterizer.depthBiasEnable = VK_FALSE;
vk::PipelineMultisampleStateCreateInfo multisampling{};
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;
vk::PipelineDepthStencilStateCreateInfo depthStencil{};
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = vk::CompareOp::eLess;
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.stencilTestEnable = VK_FALSE;
vk::PushConstantRange pushConstantRange{};
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
pushConstantRange.offset = 0;
pushConstantRange.size = sizeof(glm::mat4);
vk::PipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
pipelineLayoutInfo.pushConstantRangeCount = 1;
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
auto layoutRes = device->getLogicalDevice().createPipelineLayout(pipelineLayoutInfo);
ASSERT_VULKAN(VkResult(layoutRes.result), "Failed to create shadow map pipeline layout!");
pipelineLayout = layoutRes.value;
vk::GraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.stageCount = static_cast<uint32_t>(skyStages.size());
pipelineInfo.pStages = skyStages.data();
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
auto pipelineRes = device->getLogicalDevice().createGraphicsPipeline(nullptr, pipelineInfo);
ASSERT_VULKAN(VkResult(pipelineRes.result), "Failed to create shadow map graphics pipeline!");
graphicsPipeline = pipelineRes.value;
spdlog::info("CascadedShadowMap: Created pipeline handle: 0x{:x}", (uint64_t)(VkPipeline)graphicsPipeline);
device->getLogicalDevice().destroyShaderModule(vertModule);
device->getLogicalDevice().destroyShaderModule(geomModule);
device->getLogicalDevice().destroyShaderModule(fragModule);
}
void CascadedShadowMap::recordCommands(vk::CommandBuffer &commandBuffer, uint32_t image_index, Scene *scene, const std::vector<vk::DescriptorSet> &descriptorSets)
{
for (uint32_t cascade = 0; cascade < numCascades; cascade++) {
vk::RenderPassBeginInfo renderPassInfo{};
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = framebuffers[cascade];
renderPassInfo.renderArea.offset = vk::Offset2D{0, 0};
renderPassInfo.renderArea.extent = vk::Extent2D{shadowWidth, shadowHeight};
vk::ClearValue clearValue{};
clearValue.depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearValue;
commandBuffer.beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);
commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline);
vk::Viewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast<float>(shadowWidth);
viewport.height = static_cast<float>(shadowHeight);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
commandBuffer.setViewport(0, viewport);
vk::Rect2D scissor{};
scissor.offset = vk::Offset2D{0, 0};
scissor.extent = vk::Extent2D{shadowWidth, shadowHeight};
commandBuffer.setScissor(0, scissor);
std::vector<vk::DescriptorSet> shadowDescriptorSets = {descriptorSet};
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, shadowDescriptorSets, nullptr);
glm::mat4 modelMatrix = glm::mat4(1.0f);
commandBuffer.pushConstants(pipelineLayout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &modelMatrix);
for (uint32_t m = 0; m < scene->getModelCount(); m++) {
for (uint32_t k = 0; k < scene->getMeshCount(m); k++) {
std::vector<vk::Buffer> const vertex_buffers = { scene->getVertexBuffer(m, k) };
vk::DeviceSize offsets[] = { 0 };
commandBuffer.bindVertexBuffers(0, vertex_buffers, offsets);
commandBuffer.bindIndexBuffer(scene->getIndexBuffer(m, k), 0, vk::IndexType::eUint32);
commandBuffer.drawIndexed(scene->getIndexCount(m, k), 1, 0, 0, 0);
}
}
commandBuffer.endRenderPass();
}
}
}