Skip to content
Snippets Groups Projects
main.cpp 23.59 KiB
#include <iostream>
#include <vkcv/Buffer.hpp>
#include <vkcv/Core.hpp>
#include <vkcv/Pass.hpp>
#include <vkcv/Sampler.hpp>
#include <vkcv/Image.hpp>
#include <vkcv/camera/CameraManager.hpp>
#include <vkcv/gui/GUI.hpp>
#include <vkcv/asset/asset_loader.hpp>
#include <vkcv/shader/GLSLCompiler.hpp>

struct Plane {
    glm::vec3   pointOnPlane;
    float       padding0;
    glm::vec3   normal;
    float       padding1;
};

struct CameraPlanes {
    Plane planes[6];
};

CameraPlanes computeCameraPlanes(const vkcv::camera::Camera& camera) {
    const float     fov     = camera.getFov();
    const glm::vec3 pos     = camera.getPosition();
    const float     ratio   = camera.getRatio();
    const glm::vec3 forward = glm::normalize(camera.getFront());
    float near;
    float far;
    camera.getNearFar(near, far);

    glm::vec3 up    = glm::vec3(0, -1, 0);
    glm::vec3 right = glm::normalize(glm::cross(forward, up));
    up              = glm::cross(forward, right);

    const glm::vec3 nearCenter      = pos + forward * near;
    const glm::vec3 farCenter       = pos + forward * far;

    const float tanFovHalf          = glm::tan(fov / 2);

    const glm::vec3 nearUpCenter    = nearCenter + up    * tanFovHalf * near;
    const glm::vec3 nearDownCenter  = nearCenter - up    * tanFovHalf * near;
    const glm::vec3 nearRightCenter = nearCenter + right * tanFovHalf * near * ratio;
    const glm::vec3 nearLeftCenter  = nearCenter - right * tanFovHalf * near * ratio;

    const glm::vec3 farUpCenter     = farCenter + up    * tanFovHalf * far;
    const glm::vec3 farDownCenter   = farCenter - up    * tanFovHalf * far;
    const glm::vec3 farRightCenter  = farCenter + right * tanFovHalf * far * ratio;
    const glm::vec3 farLeftCenter   = farCenter - right * tanFovHalf * far * ratio;

    CameraPlanes cameraPlanes;
    // near
    cameraPlanes.planes[0].pointOnPlane = nearCenter;
    cameraPlanes.planes[0].normal       = -forward;
    // far
    cameraPlanes.planes[1].pointOnPlane = farCenter;
    cameraPlanes.planes[1].normal       = forward;

    // top
    cameraPlanes.planes[2].pointOnPlane = nearUpCenter;
    cameraPlanes.planes[2].normal       = glm::normalize(glm::cross(farUpCenter - nearUpCenter, right));
    // bot
    cameraPlanes.planes[3].pointOnPlane = nearDownCenter;
    cameraPlanes.planes[3].normal       = glm::normalize(glm::cross(right, farDownCenter - nearDownCenter));

    // right
    cameraPlanes.planes[4].pointOnPlane = nearRightCenter;
    cameraPlanes.planes[4].normal       = glm::normalize(glm::cross(up, farRightCenter - nearRightCenter));
    // left
    cameraPlanes.planes[5].pointOnPlane = nearLeftCenter;
    cameraPlanes.planes[5].normal       = glm::normalize(glm::cross(farLeftCenter - nearLeftCenter, up));

    return cameraPlanes;
}

struct Vertex
{
	glm::vec3 position;
	glm::vec3 normal;
	glm::vec2 uv;
};

struct CompiledMaterial
{
    std::vector<vkcv::Image> baseColor;
    std::vector<vkcv::Image> metalRough;
    std::vector<vkcv::Image> normal;
    std::vector<vkcv::Image> occlusion;
};

void interleaveScene(vkcv::asset::Scene scene,
                     std::vector<std::vector<Vertex>> &interleavedVertexBuffers,
                     std::vector<glm::vec4> &boundingBoxBuffers) {
	
    for(const auto &mesh : scene.meshes) {
        for(auto vertexGroupIndex : mesh.vertexGroups) {
            const auto &vertexGroup = scene.vertexGroups[vertexGroupIndex];

            const vkcv::asset::VertexAttribute* positionAttribute = nullptr;
            const vkcv::asset::VertexAttribute* normalAttribute   = nullptr;
            const vkcv::asset::VertexAttribute* uvAttribute       = nullptr;
			
			for (const auto& attribute : vertexGroup.vertexBuffer.attributes) {
				switch (attribute.type) {
					case vkcv::asset::PrimitiveType::POSITION:
						positionAttribute = &attribute;
						break;
					case vkcv::asset::PrimitiveType::NORMAL:
						normalAttribute = &attribute;
						break;
					case vkcv::asset::PrimitiveType::TEXCOORD_0:
						uvAttribute = &attribute;
						break;
					default:
						break;
				}
			}
	
			assert(positionAttribute && normalAttribute && uvAttribute);

            const uint64_t &verticesCount          = vertexGroup.numVertices;
            const std::vector<uint8_t> &vertexData = vertexGroup.vertexBuffer.data;

            std::vector<Vertex> vertices;
            vertices.reserve(verticesCount);

            const size_t positionStride = positionAttribute->stride == 0 ? sizeof(glm::vec3) : positionAttribute->stride;
            const size_t normalStride   = normalAttribute->stride   == 0 ? sizeof(glm::vec3) : normalAttribute->stride;
            const size_t uvStride       = uvAttribute->stride       == 0 ? sizeof(glm::vec2) : uvAttribute->stride;

            glm::vec3 max_pos(-std::numeric_limits<float>::max());
            glm::vec3 min_pos(std::numeric_limits<float>::max());

            for(size_t i = 0; i < verticesCount; i++)
            {
                const size_t positionOffset = positionAttribute->offset + positionStride * i;
                const size_t normalOffset   = normalAttribute->offset   + normalStride * i;
                const size_t uvOffset       = uvAttribute->offset       + uvStride * i;

                Vertex v;

                v.position = *reinterpret_cast<const glm::vec3*>(&(vertexData[positionOffset]));
                v.normal   = *reinterpret_cast<const glm::vec3*>(&(vertexData[normalOffset]));
                v.uv       = *reinterpret_cast<const glm::vec3*>(&(vertexData[uvOffset]));

                glm::vec3 posWorld = glm::make_mat4(mesh.modelMatrix.data()) * glm::vec4(v.position, 1);

                max_pos.x = glm::max(max_pos.x, posWorld.x);
                max_pos.y = glm::max(max_pos.y, posWorld.y);
                max_pos.z = glm::max(max_pos.z, posWorld.z);

                min_pos.x = glm::min(min_pos.x, posWorld.x);
                min_pos.y = glm::min(min_pos.y, posWorld.y);
                min_pos.z = glm::min(min_pos.z, posWorld.z);

                vertices.push_back(v);
            }

            const glm::vec3 boundingPosition = (max_pos + min_pos) / 2.0f;
            const float radius = glm::distance(max_pos, min_pos) / 2.0f;

            boundingBoxBuffers.emplace_back(boundingPosition.x,
                                            boundingPosition.y,
                                            boundingPosition.z,
                                            radius);

            interleavedVertexBuffers.push_back(vertices);
        }
    }
}

// Assumes the meshes use index buffers
void compileMeshForIndirectDraw(vkcv::Core &core,
                                const vkcv::asset::Scene &scene,
                                std::vector<uint8_t> &compiledVertexBuffer,
                                std::vector<uint8_t> &compiledIndexBuffer,
                                CompiledMaterial &compiledMat,
                                std::vector<vk::DrawIndexedIndirectCommand> &indexedIndirectCommands)
{
    vkcv::Image pseudoImg = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, 2, 2);
    std::vector<uint8_t> pseudoData = {0, 0, 0, 0};
    pseudoImg.fill(pseudoData.data());
	
	auto mipStream = core.createCommandStream(vkcv::QueueType::Graphics);
	pseudoImg.recordMipChainGeneration(mipStream, core.getDownsampler());

	uint32_t vertexOffset = 0;
    for (const auto &mesh : scene.meshes)
    {
        for(auto &vertexGroupIndex : mesh.vertexGroups)
        {
            auto &vertexGroup = scene.vertexGroups[vertexGroupIndex];

            auto &material      = scene.materials[vertexGroup.materialIndex];

            if(material.baseColor == -1)
            {
                std::cout << "baseColor is -1! Pushing pseudo-texture!" << std::endl;
                compiledMat.baseColor.push_back(pseudoImg);
            }
            else
            {
                auto &baseColor     = scene.textures[material.baseColor];

                vkcv::Image baseColorImg = vkcv::image(core, vk::Format::eR8G8B8A8Srgb, baseColor.w, baseColor.h);
                baseColorImg.fill(baseColor.data.data());
				baseColorImg.recordMipChainGeneration(mipStream, core.getDownsampler());

                compiledMat.baseColor.push_back(baseColorImg);
            }

            indexedIndirectCommands.emplace_back(static_cast<uint32_t>(vertexGroup.numIndices),
                                                 1,
                                                 static_cast<uint32_t>(compiledIndexBuffer.size() / 4),
                                                 vertexOffset,
                                                 static_cast<uint32_t>(indexedIndirectCommands.size()));

            vertexOffset += vertexGroup.numVertices;

            compiledVertexBuffer.insert(compiledVertexBuffer.end(),
                                        vertexGroup.vertexBuffer.data.begin(),
                                        vertexGroup.vertexBuffer.data.end());

			if(vertexGroup.indexBuffer.type == vkcv::asset::IndexType::UINT8)
            {
				for(auto index : vertexGroup.indexBuffer.data)
				{
                    uint32_t index32 = static_cast<uint32_t>(index);
                    uint8_t firstByte = index32;
                    uint8_t secondByte = index32 >> 8;
                    uint8_t thirdByte = index32 >> 16;
                    uint8_t fourthByte = index32 >> 24;

                    compiledIndexBuffer.push_back(firstByte);
                    compiledIndexBuffer.push_back(secondByte);
                    compiledIndexBuffer.push_back(thirdByte);
                    compiledIndexBuffer.push_back(fourthByte);
                }
			}
            else if(vertexGroup.indexBuffer.type == vkcv::asset::IndexType::UINT16)
            {
                for (size_t i = 0; i < vertexGroup.indexBuffer.data.size(); i += 2)
                {
                    uint16_t index16 = *reinterpret_cast<const uint16_t*>(&vertexGroup.indexBuffer.data[i]);
                    uint32_t index32 = static_cast<uint32_t>(index16);

                    uint8_t firstByte = index32;
                    uint8_t secondByte = index32 >> 8;
                    uint8_t thirdByte = index32 >> 16;
                    uint8_t fourthByte = index32 >> 24;

                    compiledIndexBuffer.push_back(firstByte);
                    compiledIndexBuffer.push_back(secondByte);
                    compiledIndexBuffer.push_back(thirdByte);
                    compiledIndexBuffer.push_back(fourthByte);
                }
			}
			else
			{
				compiledIndexBuffer.insert(compiledIndexBuffer.end(),
										   vertexGroup.indexBuffer.data.begin(),
										   vertexGroup.indexBuffer.data.end());
			}
        }
    }
	
	core.submitCommandStream(mipStream, false);
}

int main(int argc, const char** argv) {
	const std::string applicationName = "Indirect draw";

    vkcv_log(vkcv::LogLevel::TIME, "Startup");

	vkcv::Features features;
	features.requireExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
    features.requireFeature([](vk::PhysicalDeviceFeatures &features){
        features.setMultiDrawIndirect(true);
    });
	features.requireExtension(VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME);
    features.requireExtension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
    features.requireExtensionFeature<vk::PhysicalDeviceDescriptorIndexingFeatures>(
            VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, [](vk::PhysicalDeviceDescriptorIndexingFeatures &features) {
                // features.setShaderInputAttachmentArrayDynamicIndexing(true);
                features.setShaderUniformTexelBufferArrayDynamicIndexing(true);
                features.setShaderStorageTexelBufferArrayDynamicIndexing(true);
                features.setShaderUniformBufferArrayNonUniformIndexing(true);
                features.setShaderSampledImageArrayNonUniformIndexing(true);
                features.setShaderStorageBufferArrayNonUniformIndexing(true);
                features.setShaderStorageImageArrayNonUniformIndexing(true);
                // features.setShaderInputAttachmentArrayNonUniformIndexing(true);
                features.setShaderUniformTexelBufferArrayNonUniformIndexing(true);
                features.setShaderStorageTexelBufferArrayNonUniformIndexing(true);

                features.setDescriptorBindingUniformBufferUpdateAfterBind(true);
                features.setDescriptorBindingSampledImageUpdateAfterBind(true);
                features.setDescriptorBindingStorageImageUpdateAfterBind(true);
                features.setDescriptorBindingStorageBufferUpdateAfterBind(true);
                features.setDescriptorBindingUniformTexelBufferUpdateAfterBind(true);
                features.setDescriptorBindingStorageTexelBufferUpdateAfterBind(true);

                features.setDescriptorBindingUpdateUnusedWhilePending(true);
                features.setDescriptorBindingPartiallyBound(true);
                features.setDescriptorBindingVariableDescriptorCount(true);
                features.setRuntimeDescriptorArray(true);
            }
    );

    features.tryExtensionFeature<vk::PhysicalDeviceHostImageCopyFeaturesEXT>(
        VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME, [](vk::PhysicalDeviceHostImageCopyFeaturesEXT& features) {
				features.setHostImageCopy(true);
			}
    );

    vkcv_log(vkcv::LogLevel::TIME, "Features configured");

	vkcv::Core core = vkcv::Core::create(
		applicationName,
		VK_MAKE_VERSION(0, 0, 1),
		{ vk::QueueFlagBits::eGraphics ,vk::QueueFlagBits::eCompute , vk::QueueFlagBits::eTransfer },
		features
	);

    vkcv_log(vkcv::LogLevel::TIME, "Core created");

	vkcv::WindowHandle windowHandle = core.createWindow(applicationName,800,600,true);
	vkcv::Window& window = core.getWindow(windowHandle);

    vkcv_log(vkcv::LogLevel::TIME, "Window created");

    vkcv::gui::GUI gui (core, windowHandle);

    vkcv_log(vkcv::LogLevel::TIME, "GUI initialized");

    vkcv::asset::Scene asset_scene;
	const char* path = argc > 1 ? argv[1] : "resources/Sponza/Sponza.gltf";
	int result = vkcv::asset::loadScene(path, asset_scene);

    vkcv_log(vkcv::LogLevel::TIME, "Scene loaded");

	if (result == 1) {
		std::cout << "Loading Sponza successful!" << std::endl;
	} else {
		std::cerr << "Loading Sponza failed: " << result << std::endl;
		return 1;
	}
	assert(!asset_scene.vertexGroups.empty());
    if (asset_scene.textures.empty())
    {
        std::cerr << "Error. No textures found. Exiting." << std::endl;
        return EXIT_FAILURE;
    }

    vkcv::PassHandle passHandle = vkcv::passSwapchain(
			core,
			window.getSwapchain(),
			{ vk::Format::eUndefined, vk::Format::eD32Sfloat }
	);
	
	if (!passHandle) {
		std::cerr << "Error. Could not create renderpass. Exiting." << std::endl;
		return EXIT_FAILURE;
	}

    vkcv_log(vkcv::LogLevel::TIME, "Scene verified");

	vkcv::ShaderProgram sponzaProgram;
	vkcv::shader::GLSLCompiler compiler;
    compiler.compileProgram(sponzaProgram, {
        { vkcv::ShaderStage::VERTEX, std::filesystem::path("resources/shaders/shader.vert") },
        { vkcv::ShaderStage::FRAGMENT, std::filesystem::path("resources/shaders/shader.frag") },
    }, nullptr);

    vkcv::ShaderProgram cullingProgram;
    compiler.compileProgram(cullingProgram, {
        { vkcv::ShaderStage::COMPUTE, std::filesystem::path("resources/shaders/culling.comp") },
    }, nullptr);

    vkcv_log(vkcv::LogLevel::TIME, "Shaders compiled");

    // vertex layout for the pipeline. (assumed to be) used by all sponza meshes.
    const std::vector<vkcv::VertexAttachment> vertexAttachments = sponzaProgram.getVertexAttachments();
	const vkcv::VertexLayout sponzaVertexLayout {
		{ vkcv::createVertexBinding(0, { vertexAttachments }) }
	};

    vkcv_log(vkcv::LogLevel::TIME, "Vertex layout configured");

    std::vector<uint8_t> compiledVertexBuffer; // IGNORED, since the vertex buffer is not interleaved!

    std::vector<uint8_t> compiledIndexBuffer;
    CompiledMaterial compiledMaterial;
    std::vector<vk::DrawIndexedIndirectCommand> indexedIndirectCommands;

    compileMeshForIndirectDraw(core,
                               asset_scene,
                               compiledVertexBuffer,
                               compiledIndexBuffer,
                               compiledMaterial,
                               indexedIndirectCommands);
    
    vkcv_log(vkcv::LogLevel::TIME, "Mesh compiled");

	std::vector<std::vector<Vertex>> interleavedVertices;
    std::vector<glm::vec4> compiledBoundingBoxBuffer;
    interleaveScene(asset_scene,
                    interleavedVertices,
                    compiledBoundingBoxBuffer);
    
    vkcv_log(vkcv::LogLevel::TIME, "Scene interleaved");

	std::vector<Vertex> compiledInterleavedVertexBuffer;
	for(auto& vertexGroup : interleavedVertices )
	{
        compiledInterleavedVertexBuffer.insert(compiledInterleavedVertexBuffer.end(),vertexGroup.begin(),vertexGroup.end());
	}

	auto vkCompiledVertexBuffer = vkcv::buffer<Vertex>(
			core,
			vkcv::BufferType::VERTEX,
            compiledInterleavedVertexBuffer.size(),
			vkcv::BufferMemoryType::DEVICE_LOCAL);
    vkCompiledVertexBuffer.fill(compiledInterleavedVertexBuffer.data());

    auto vkCompiledIndexBuffer = vkcv::buffer<uint8_t>(
			core,
            vkcv::BufferType::INDEX,
            compiledIndexBuffer.size(),
            vkcv::BufferMemoryType::DEVICE_LOCAL);
    vkCompiledIndexBuffer.fill(compiledIndexBuffer.data());

    vkcv::Buffer<vk::DrawIndexedIndirectCommand> indirectBuffer = vkcv::buffer<vk::DrawIndexedIndirectCommand>(
			core,
            vkcv::BufferType::INDIRECT,
            indexedIndirectCommands.size(),
            vkcv::BufferMemoryType::DEVICE_LOCAL);
    indirectBuffer.fill(indexedIndirectCommands);

    auto boundingBoxBuffer = vkcv::buffer<glm::vec4>(
			core,
            vkcv::BufferType::STORAGE,
            compiledBoundingBoxBuffer.size());
    boundingBoxBuffer.fill(compiledBoundingBoxBuffer);

    std::vector<glm::mat4> modelMatrix;
	for( auto& mesh : asset_scene.meshes)
	{
		modelMatrix.push_back(glm::make_mat4(mesh.modelMatrix.data()));
	}
	vkcv::Buffer<glm::mat4> modelBuffer = vkcv::buffer<glm::mat4>(
			core,
			vkcv::BufferType::STORAGE,
			modelMatrix.size(),
			vkcv::BufferMemoryType::DEVICE_LOCAL
			);
	modelBuffer.fill(modelMatrix);

    vkcv_log(vkcv::LogLevel::TIME, "Buffers filled");

	const std::vector<vkcv::VertexBufferBinding> vertexBufferBindings = {
			vkcv::vertexBufferBinding(vkCompiledVertexBuffer)
	};
	
	vkcv::VertexData vertexData (vertexBufferBindings);
	vertexData.setIndexBuffer(vkCompiledIndexBuffer.getHandle(), vkcv::IndexBitCount::Bit32);

    //assert(compiledMaterial.baseColor.size() == compiledMaterial.metalRough.size());

	vkcv::DescriptorBindings descriptorBindings = sponzaProgram.getReflectedDescriptors().at(0);
    descriptorBindings[2].descriptorCount = compiledMaterial.baseColor.size();

	vkcv::DescriptorSetLayoutHandle descriptorSetLayout = core.createDescriptorSetLayout(descriptorBindings);
	vkcv::DescriptorSetHandle descriptorSet = core.createDescriptorSet(descriptorSetLayout);

    vkcv::SamplerHandle standardSampler = vkcv::samplerLinear(core);
	
	vkcv::DescriptorWrites setWrites;
	
    std::vector<vkcv::SampledImageDescriptorWrite> textureArrayWrites;
    for(uint32_t i = 0; i < compiledMaterial.baseColor.size(); i++)
    {
		setWrites.writeSampledImage(2, compiledMaterial.baseColor[i].getHandle(), 0, false, i);
    }
    
    setWrites.writeSampler(0, standardSampler);
	setWrites.writeStorageBuffer(1, modelBuffer.getHandle());
    core.writeDescriptorSet(descriptorSet, setWrites);

    vkcv_log(vkcv::LogLevel::TIME, "DescriptorSets written");

	vkcv::GraphicsPipelineHandle sponzaPipelineHandle = core.createGraphicsPipeline(
			vkcv::GraphicsPipelineConfig(
					sponzaProgram,
					passHandle,
					{ sponzaVertexLayout },
					{ descriptorSetLayout }
			)
	);
	
	if (!sponzaPipelineHandle) {
		std::cerr << "Error. Could not create graphics pipeline. Exiting." << std::endl;
		return EXIT_FAILURE;
	}

    vkcv_log(vkcv::LogLevel::TIME, "Graphics pipeline created");

    vkcv::DescriptorBindings cullingBindings = cullingProgram.getReflectedDescriptors().at(0);
    vkcv::DescriptorSetLayoutHandle cullingSetLayout = core.createDescriptorSetLayout(cullingBindings);
    vkcv::DescriptorSetHandle cullingDescSet = core.createDescriptorSet(cullingSetLayout);

    vkcv::Buffer<CameraPlanes> cameraPlaneBuffer = vkcv::buffer<CameraPlanes>(
			core,
            vkcv::BufferType::UNIFORM,
            1);

    vkcv::DescriptorWrites cullingWrites;
	cullingWrites.writeStorageBuffer(1, indirectBuffer.getHandle());
	cullingWrites.writeStorageBuffer(2, boundingBoxBuffer.getHandle());
    cullingWrites.writeUniformBuffer(0, cameraPlaneBuffer.getHandle());
    core.writeDescriptorSet(cullingDescSet, cullingWrites);

    vkcv_log(vkcv::LogLevel::TIME, "Culling descriptor set written");

    const vkcv::ComputePipelineConfig computeCullingConfig {
        cullingProgram,
        {cullingSetLayout}
    };
    vkcv::ComputePipelineHandle cullingPipelineHandle = core.createComputePipeline(computeCullingConfig);
    if (!cullingPipelineHandle) {
        std::cerr << "Error. Could not create culling pipeline. Exiting." << std::endl;
        return EXIT_FAILURE;
    }

    vkcv_log(vkcv::LogLevel::TIME, "Compute pipeline created");

    vkcv::camera::CameraManager cameraManager (window);
    auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
	
	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -3));
	cameraManager.getCamera(camHandle).setNearFar(0.1f, 20.f);

    vkcv::ImageHandle depthBuffer;
	
    const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();

    float ceiledDispatchCount = static_cast<float>(indexedIndirectCommands.size()) / 64.0f;
    ceiledDispatchCount = std::ceil(ceiledDispatchCount);
    const vkcv::DispatchSize dispatchCount = static_cast<uint32_t>(ceiledDispatchCount);

    vkcv::DescriptorSetUsage cullingUsage = vkcv::useDescriptorSet(0, cullingDescSet);
    vkcv::PushConstants emptyPushConstant(0);

    bool updateFrustumPlanes    = true;
	
	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
		if ((!depthBuffer) ||
			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
			(swapchainHeight != core.getImageHeight(depthBuffer))) {
			depthBuffer = core.createImage(
					vk::Format::eD32Sfloat,
					vkcv::ImageConfig(
							swapchainWidth,
							swapchainHeight
					)
			);
		}
  
		cameraManager.update(dt);
		
        vkcv::camera::Camera cam = cameraManager.getActiveCamera();
		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
		pushConstants.appendDrawcall(cam.getProjection() * cam.getView());

        if (updateFrustumPlanes) {
            const CameraPlanes cameraPlanes = computeCameraPlanes(cam);
            cameraPlaneBuffer.fill({ cameraPlanes });
        }

		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);

        core.recordComputeDispatchToCmdStream(
				cmdStream,
				cullingPipelineHandle,
				dispatchCount,
				{cullingUsage},
				emptyPushConstant
		);

        core.recordBufferMemoryBarrier(cmdStream, indirectBuffer.getHandle());

		vkcv::IndirectDrawcall drawcall (
				indirectBuffer.getHandle(),
				vertexData,
				indexedIndirectCommands.size()
		);
		
		drawcall.useDescriptorSet(0, descriptorSet);
		
		core.recordIndirectDrawcallsToCmdStream(
			cmdStream,
            sponzaPipelineHandle,
            pushConstants,
			{ drawcall },
			renderTargets,
			windowHandle
		);

		core.prepareSwapchainImageForPresent(cmdStream);
		core.submitCommandStream(cmdStream);
		
        gui.beginGUI();

        ImGui::Begin("Settings");
        ImGui::Checkbox("Update frustum culling", &updateFrustumPlanes);
		ImGui::Text("Deltatime %fms, %f", dt * 1000, 1/dt);

        ImGui::End();

        gui.endGUI();
	});
	
	return 0;
}