Added basic descriptor sets usage example

This commit is contained in:
saschawillems 2018-04-08 13:12:16 +02:00
parent f5ba1e8939
commit 3c578a065f
7 changed files with 486 additions and 12 deletions

View file

@ -42,51 +42,55 @@ Basic and verbose example for getting a colored triangle rendered to the screen
Using pipeline state objects (pso) that bake state information (rasterization states, culling modes, etc.) along with the shaders into a single object, making it easy for an implementation to optimize usage (compared to OpenGL's dynamic state machine). Also demonstrates the use of pipeline derivatives.
#### [03 - Dynamic uniform buffers](examples/dynamicuniformbuffer/)
#### [03 - Descriptor sets](examples/descriptorsets)
Descriptors are used to pass data to shader binding points. Sets up descriptor sets, layouts, pools, creates a single pipeline based on the set layout and renders multiple objects with different descriptor sets.
#### [04 - Dynamic uniform buffers](examples/dynamicuniformbuffer/)
Dynamic uniform buffers are used for rendering multiple objects with multiple matrices stored in a single uniform buffer object. Individual matrices are dynamically addressed upon descriptor binding time, minimizing the number of required descriptor sets.
#### [04 - Push constants](examples/pushconstants/)
#### [05 - Push constants](examples/pushconstants/)
Uses push constants, small blocks of uniform data stored within a command buffer, to pass data to a shader without the need for uniform buffers.
#### [05 - Specialization constants](examples/specializationconstants/)
#### [06 - Specialization constants](examples/specializationconstants/)
Uses SPIR-V specialization constants to create multiple pipelines with different lighting paths from a single "uber" shader.
#### [06 - Texture mapping](examples/texture/)
#### [07 - Texture mapping](examples/texture/)
Loads a 2D texture from disk (including all mip levels), uses staging to upload it into video memory and samples from it using combined image samplers.
#### [07 - Cube map textures](examples/texturecubemap/)
#### [08 - Cube map textures](examples/texturecubemap/)
Loads a cube map texture from disk containing six different faces. All faces and mip levels are uploaded into video memory and the cubemap is sampled once as a skybox (for the background) and as a source for reflections (for a 3D model).
#### [08 - Texture arrays](examples/texturearray/)
#### [09 - Texture arrays](examples/texturearray/)
Loads a 2D texture array containing multiple 2D texture slices (each with it's own mip chain) and renders multiple meshes each sampling from a different layer of the texture. 2D texture arrays don't do any interpolation between the slices.
#### [09 - 3D textures](examples/texture3d/)
#### [10 - 3D textures](examples/texture3d/)
Generates a 3D texture on the cpu (using perlin noise), uploads it to the device and samples it to render an animation. 3D textures store volumetric data and interpolate in all three dimensions.
#### [10 - Model rendering](examples/mesh/)
#### [11 - Model rendering](examples/mesh/)
Loads a 3D model and texture maps from a common file format (using [assimp](https://github.com/assimp/assimp)), uploads the vertex and index buffer data to video memory, sets up a matching vertex layout and renders the 3D model.
#### [11 - Sub passes](examples/subpasses/)
#### [12 - Sub passes](examples/subpasses/)
Uses sub passes and input attachments to write and read back data from framebuffer attachments (same location only) in single render pass. This is used to implement deferred render composition with added forward transparency in a single pass.
#### [12 - Offscreen rendering](examples/offscreen/)
#### [13 - Offscreen rendering](examples/offscreen/)
Basic offscreen rendering in two passes. First pass renders the mirrored scene to a separate framebuffer with color and depth attachments, second pass samples from that color attachment for rendering a mirror surface.
#### [13 - CPU particle system](examples/particlefire/)
#### [14 - CPU particle system](examples/particlefire/)
Implements a simple CPU based particle system. Particle data is stored in host memory, updated on the CPU per-frame and synchronized with the device before it's rendered using pre-multiplied alpha.
#### [14 - Stencil buffer](examples/stencilbuffer/)
#### [15 - Stencil buffer](examples/stencilbuffer/)
Uses the stencil buffer and it's compare functionality for rendering a 3D model with dynamic outlines.

View file

@ -0,0 +1,14 @@
#version 450
layout (set = 0, binding = 1) uniform sampler2D samplerColorMap;
layout (location = 0) in vec3 inNormal;
layout (location = 1) in vec3 inColor;
layout (location = 2) in vec2 inUV;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = texture(samplerColorMap, inUV) * vec4(inColor, 1.0);
}

Binary file not shown.

View file

@ -0,0 +1,28 @@
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inColor;
layout (set = 0, binding = 0) uniform UBOMatrices {
mat4 projection;
mat4 view;
mat4 model;
} uboMatrices;
layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec2 outUV;
out gl_PerVertex {
vec4 gl_Position;
};
void main()
{
outNormal = inNormal;
outColor = inColor;
outUV = inUV;
gl_Position = uboMatrices.projection * uboMatrices.view * uboMatrices.model * vec4(inPos.xyz, 1.0);
}

Binary file not shown.

View file

@ -50,6 +50,7 @@ set(EXAMPLES
deferred
deferredmultisampling
deferredshadows
descriptorsets
displacement
distancefieldfonts
dynamicuniformbuffer

View file

@ -0,0 +1,427 @@
/*
* Vulkan Example - Using descriptor sets for passing data to shader stages
*
* Relevant code parts are marked with [POI]
*
* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <vulkan/vulkan.h>
#include "vulkanexamplebase.h"
#include "VulkanTexture.hpp"
#include "VulkanModel.hpp"
#define ENABLE_VALIDATION false
class VulkanExample : public VulkanExampleBase
{
public:
bool animate = true;
vks::VertexLayout vertexLayout = vks::VertexLayout({
vks::VERTEX_COMPONENT_POSITION,
vks::VERTEX_COMPONENT_NORMAL,
vks::VERTEX_COMPONENT_UV,
vks::VERTEX_COMPONENT_COLOR,
});
struct Cube {
struct Matrices {
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model;
} matrices;
VkDescriptorSet descriptorSet;
vks::Texture2D texture;
vks::Buffer uniformBuffer;
glm::vec3 rotation;
};
std::array<Cube, 2> cubes;
struct Models {
vks::Model cube;
} models;
VkPipeline pipeline;
VkPipelineLayout pipelineLayout;
VkDescriptorSetLayout descriptorSetLayout;
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
{
title = "Using descriptor Sets";
settings.overlay = true;
camera.type = Camera::CameraType::lookat;
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f);
camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f));
camera.setTranslation(glm::vec3(0.0f, 0.0f, -5.0f));
}
~VulkanExample()
{
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
models.cube.destroy();
for (auto cube : cubes) {
cube.uniformBuffer.destroy();
cube.texture.destroy();
}
}
virtual void getEnabledFeatures()
{
if (deviceFeatures.samplerAnisotropy) {
enabledFeatures.samplerAnisotropy = VK_TRUE;
};
}
void buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.offset.x = 0;
renderPassBeginInfo.renderArea.offset.y = 0;
renderPassBeginInfo.renderArea.extent.width = width;
renderPassBeginInfo.renderArea.extent.height = height;
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues;
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) {
renderPassBeginInfo.framebuffer = frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.cube.vertices.buffer, offsets);
vkCmdBindIndexBuffer(drawCmdBuffers[i], models.cube.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
/*
[POI] Render cubes with separate descriptor sets
*/
for (auto cube : cubes) {
// Bind the cube's descriptor set. This tells the command buffer to use the uniform buffer and image set for this cube
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &cube.descriptorSet, 0, nullptr);
vkCmdDrawIndexed(drawCmdBuffers[i], models.cube.indexCount, 1, 0, 0, 0);
}
vkCmdEndRenderPass(drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
void loadAssets()
{
models.cube.loadFromFile(getAssetPath() + "models/cube.dae", vertexLayout, 1.0f, vulkanDevice, queue);
cubes[0].texture.loadFromFile(getAssetPath() + "textures/crate01_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
cubes[1].texture.loadFromFile(getAssetPath() + "textures/crate02_color_height_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, vulkanDevice, queue);
}
/*
[POI] Set up descriptor sets and set layout
*/
void setupDescriptors()
{
/*
Descriptor set layout
The layout describes the shader bindings and types used for a certain descriptor layout and as such must match the shader bindings
Shader bindings used in this example:
VS:
layout (set = 0, binding = 0) uniform UBOMatrices ...
FS :
layout (set = 0, binding = 1) uniform sampler2D ...;
*/
std::array<VkDescriptorSetLayoutBinding,2> setLayoutBindings{};
/*
Binding 0: Uniform buffers (used to pass matrices matrices)
*/
setLayoutBindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
// Shader binding point
setLayoutBindings[0].binding = 0;
// Accessible from the vertex shader only (flags can be combined to make it accessible to multiple shader stages)
setLayoutBindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
// Binding contains one element (can be used for array bindings)
setLayoutBindings[0].descriptorCount = 1;
/*
Binding 1: Combined image sampler (used to pass per object texture information)
*/
setLayoutBindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
setLayoutBindings[1].binding = 1;
// Accessible from the fragment shader only
setLayoutBindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
setLayoutBindings[1].descriptorCount = 1;
// Create the descriptor set layout
VkDescriptorSetLayoutCreateInfo descriptorLayoutCI{};
descriptorLayoutCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptorLayoutCI.bindingCount = static_cast<uint32_t>(setLayoutBindings.size());
descriptorLayoutCI.pBindings = setLayoutBindings.data();
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayoutCI, nullptr, &descriptorSetLayout));
/*
Descriptor pool
Actual descriptors are allocated from a descriptor pool telling the driver what types and how many
descriptors this application will use
An application can have multiple pools (e.g. for multiple threads) with any number of descriptor types
as long as device limits are not surpassed
It's good practice to allocate pools with actually required descriptor types and counts
*/
std::array<VkDescriptorPoolSize, 2> descriptorPoolSizes{};
// Uniform buffers : 1 for scene and 1 per object (scene and local matrices)
descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSizes[0].descriptorCount = 1 + static_cast<uint32_t>(cubes.size());
// Combined image samples : 1 per mesh texture
descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorPoolSizes[1].descriptorCount = static_cast<uint32_t>(cubes.size());
// Create the global descriptor pool
VkDescriptorPoolCreateInfo descriptorPoolCI = {};
descriptorPoolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptorPoolCI.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());
descriptorPoolCI.pPoolSizes = descriptorPoolSizes.data();
// Max. number of descriptor sets that can be allocted from this pool (one per object)
descriptorPoolCI.maxSets = static_cast<uint32_t>(descriptorPoolSizes.size());
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCI, nullptr, &descriptorPool));
/*
Descriptor sets
Using the shared descriptor set layout and the descriptor pool we will now allocate the descriptor sets.
Descriptor sets contain the actual descriptor fo the objects (buffers, images) used at render time.
*/
for (auto &cube: cubes) {
// Allocates an empty descriptor set without actual descriptors from the pool using the set layout
VkDescriptorSetAllocateInfo allocateInfo{};
allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocateInfo.descriptorPool = descriptorPool;
allocateInfo.descriptorSetCount = 1;
allocateInfo.pSetLayouts = &descriptorSetLayout;
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocateInfo, &cube.descriptorSet));
// Update the descriptor set with the actual descriptors matching shader bindings set in the layout
std::array<VkWriteDescriptorSet, 2> writeDescriptorSets{};
/*
Binding 0: Object matrices Uniform buffer
*/
writeDescriptorSets[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSets[0].dstSet = cube.descriptorSet;
writeDescriptorSets[0].dstBinding = 0;
writeDescriptorSets[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
writeDescriptorSets[0].pBufferInfo = &cube.uniformBuffer.descriptor;
writeDescriptorSets[0].descriptorCount = 1;
/*
Binding 1: Object texture
*/
writeDescriptorSets[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptorSets[1].dstSet = cube.descriptorSet;
writeDescriptorSets[1].dstBinding = 1;
writeDescriptorSets[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
// Images use a different descriptor strucutre, so we use pImageInfo instead of pBufferInfo
writeDescriptorSets[1].pImageInfo = &cube.texture.descriptor;
writeDescriptorSets[1].descriptorCount = 1;
// Execute the writes to update descriptors for this set
// Note that it's also possible to gather all writes and only run updates once, even for multiple sets
// This is possible because each VkWriteDescriptorSet also contains the destination set to be updated
// For simplicity we will update once per set instead
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
}
}
void preparePipelines()
{
/*
[POI] Create a pipeline layout used for our graphics pipeline
*/
VkPipelineLayoutCreateInfo pipelineLayoutCI{};
pipelineLayoutCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
// The pipeline layout is based on the descriptor set layout we created above
pipelineLayoutCI.setLayoutCount = 1;
pipelineLayoutCI.pSetLayouts = &descriptorSetLayout;
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout));
const std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCI = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationStateCI = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendStateCI = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilStateCI = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportStateCI = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleStateCI = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
VkPipelineDynamicStateCreateInfo dynamicStateCI = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables.data(), static_cast<uint32_t>(dynamicStateEnables.size()),0);
// Vertex bindings and attributes
const std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX),
};
const std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3), // Location 1: Normal
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6), // Location 2: UV
vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8), // Location 3: Color
};
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfoCI = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
pipelineCreateInfoCI.pVertexInputState = &vertexInputState;
pipelineCreateInfoCI.pInputAssemblyState = &inputAssemblyStateCI;
pipelineCreateInfoCI.pRasterizationState = &rasterizationStateCI;
pipelineCreateInfoCI.pColorBlendState = &colorBlendStateCI;
pipelineCreateInfoCI.pMultisampleState = &multisampleStateCI;
pipelineCreateInfoCI.pViewportState = &viewportStateCI;
pipelineCreateInfoCI.pDepthStencilState = &depthStencilStateCI;
pipelineCreateInfoCI.pDynamicState = &dynamicStateCI;
const std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages = {
loadShader(getAssetPath() + "shaders/descriptorsets/cube.vert.spv", VK_SHADER_STAGE_VERTEX_BIT),
loadShader(getAssetPath() + "shaders/descriptorsets/cube.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)
};
pipelineCreateInfoCI.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfoCI.pStages = shaderStages.data();
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfoCI, nullptr, &pipeline));
}
void prepareUniformBuffers()
{
// Vertex shader matrix uniform buffer block
for (auto& cube : cubes) {
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&cube.uniformBuffer,
sizeof(glm::mat4)));
VK_CHECK_RESULT(cube.uniformBuffer.map());
}
updateUniformBuffers();
}
void updateUniformBuffers()
{
cubes[0].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3(-2.0f, 0.0f, 0.0f));
cubes[1].matrices.model = glm::translate(glm::mat4(1.0f), glm::vec3( 1.5f, 0.5f, 0.0f));
for (auto& cube : cubes) {
cube.matrices.projection = camera.matrices.perspective;
cube.matrices.view = camera.matrices.view;
cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
cube.matrices.model = glm::rotate(cube.matrices.model, glm::radians(cube.rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
memcpy(cube.uniformBuffer.mapped, &cube.matrices, sizeof(cube.matrices));
}
}
void draw()
{
VulkanExampleBase::prepareFrame();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanExampleBase::submitFrame();
}
void prepare()
{
VulkanExampleBase::prepare();
loadAssets();
prepareUniformBuffers();
setupDescriptors();
preparePipelines();
buildCommandBuffers();
prepared = true;
}
virtual void render()
{
if (!prepared)
return;
draw();
if (animate) {
cubes[0].rotation.x += 2.5f * frameTimer;
if (cubes[0].rotation.x > 360.0f)
cubes[0].rotation.x -= 360.0f;
cubes[1].rotation.y += 2.0f * frameTimer;
if (cubes[1].rotation.x > 360.0f)
cubes[1].rotation.x -= 360.0f;
}
if ((camera.updated) || (animate)) {
updateUniformBuffers();
}
}
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
{
if (overlay->header("Settings")) {
overlay->checkBox("Animate", &animate);
}
}
};
VULKAN_EXAMPLE_MAIN()