Use getAssetPath() instead of ASSET_PATH to support broader range of platforms. Multisampling example determines sample rate from device at runtime. Move example wrapper code from DemoViewController.mm to dedicated MVKExample.cpp file. Remove AssImp libraries for iOS and macOS from repo, and add instructions for generating them from AssImp source files. Update general README.md file to mention support for iOS and macOS platforms. Add Apple logo for README.md. Update Vulkan logo to current registered TM logo. Update copyright notice of MoltenVK example files to MIT license. Examples use +/- on main keyboard, instead of numpad.
750 lines
26 KiB
C++
750 lines
26 KiB
C++
/*
|
|
* Vulkan Example - Multisampling using resolve attachments
|
|
*
|
|
* Copyright (C) 2016 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>
|
|
#include <algorithm>
|
|
|
|
#define GLM_FORCE_RADIANS
|
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include <vulkan/vulkan.h>
|
|
#include "vulkanexamplebase.h"
|
|
#include "VulkanBuffer.hpp"
|
|
#include "VulkanTexture.hpp"
|
|
#include "VulkanModel.hpp"
|
|
|
|
#define VERTEX_BUFFER_BIND_ID 0
|
|
#define ENABLE_VALIDATION false
|
|
|
|
struct {
|
|
struct {
|
|
VkImage image;
|
|
VkImageView view;
|
|
VkDeviceMemory memory;
|
|
} color;
|
|
struct {
|
|
VkImage image;
|
|
VkImageView view;
|
|
VkDeviceMemory memory;
|
|
} depth;
|
|
} multisampleTarget;
|
|
|
|
class VulkanExample : public VulkanExampleBase
|
|
{
|
|
public:
|
|
bool useSampleShading = false;
|
|
VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
struct {
|
|
vks::Texture2D colorMap;
|
|
} textures;
|
|
|
|
// Vertex layout for the models
|
|
vks::VertexLayout vertexLayout = vks::VertexLayout({
|
|
vks::VERTEX_COMPONENT_POSITION,
|
|
vks::VERTEX_COMPONENT_NORMAL,
|
|
vks::VERTEX_COMPONENT_UV,
|
|
vks::VERTEX_COMPONENT_COLOR,
|
|
});
|
|
|
|
struct {
|
|
vks::Model example;
|
|
} models;
|
|
|
|
struct {
|
|
VkPipelineVertexInputStateCreateInfo inputState;
|
|
std::vector<VkVertexInputBindingDescription> bindingDescriptions;
|
|
std::vector<VkVertexInputAttributeDescription> attributeDescriptions;
|
|
} vertices;
|
|
|
|
vks::Buffer uniformBuffer;
|
|
|
|
struct UBOVS {
|
|
glm::mat4 projection;
|
|
glm::mat4 model;
|
|
glm::vec4 lightPos = glm::vec4(5.0f, 5.0f, 5.0f, 1.0f);
|
|
} uboVS;
|
|
|
|
struct {
|
|
VkPipeline MSAA;
|
|
VkPipeline MSAASampleShading;
|
|
} pipelines;
|
|
|
|
VkPipelineLayout pipelineLayout;
|
|
VkDescriptorSet descriptorSet;
|
|
VkDescriptorSetLayout descriptorSetLayout;
|
|
|
|
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
|
|
{
|
|
zoom = -7.5f;
|
|
zoomSpeed = 2.5f;
|
|
rotation = { 0.0f, -90.0f, 0.0f };
|
|
cameraPos = glm::vec3(2.5f, 2.5f, 0.0f);
|
|
title = "Vulkan Example - Multisampling";
|
|
}
|
|
|
|
~VulkanExample()
|
|
{
|
|
// Clean up used Vulkan resources
|
|
// Note : Inherited destructor cleans up resources stored in base class
|
|
vkDestroyPipeline(device, pipelines.MSAA, nullptr);
|
|
vkDestroyPipeline(device, pipelines.MSAASampleShading, nullptr);
|
|
|
|
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
|
|
|
models.example.destroy();
|
|
|
|
// Destroy MSAA target
|
|
vkDestroyImage(device, multisampleTarget.color.image, nullptr);
|
|
vkDestroyImageView(device, multisampleTarget.color.view, nullptr);
|
|
vkFreeMemory(device, multisampleTarget.color.memory, nullptr);
|
|
vkDestroyImage(device, multisampleTarget.depth.image, nullptr);
|
|
vkDestroyImageView(device, multisampleTarget.depth.view, nullptr);
|
|
vkFreeMemory(device, multisampleTarget.depth.memory, nullptr);
|
|
|
|
textures.colorMap.destroy();
|
|
|
|
uniformBuffer.destroy();
|
|
}
|
|
|
|
// Creates a multi sample render target (image and view) that is used to resolve
|
|
// into the visible frame buffer target in the render pass
|
|
void setupMultisampleTarget()
|
|
{
|
|
// Check if device supports requested sample count for color and depth frame buffer
|
|
assert((deviceProperties.limits.framebufferColorSampleCounts >= sampleCount) && (deviceProperties.limits.framebufferDepthSampleCounts >= sampleCount));
|
|
|
|
// Color target
|
|
VkImageCreateInfo info = vks::initializers::imageCreateInfo();
|
|
info.imageType = VK_IMAGE_TYPE_2D;
|
|
info.format = swapChain.colorFormat;
|
|
info.extent.width = width;
|
|
info.extent.height = height;
|
|
info.extent.depth = 1;
|
|
info.mipLevels = 1;
|
|
info.arrayLayers = 1;
|
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
info.samples = sampleCount;
|
|
// Image will only be used as a transient target
|
|
info.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
VK_CHECK_RESULT(vkCreateImage(device, &info, nullptr, &multisampleTarget.color.image));
|
|
|
|
VkMemoryRequirements memReqs;
|
|
vkGetImageMemoryRequirements(device, multisampleTarget.color.image, &memReqs);
|
|
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
|
memAlloc.allocationSize = memReqs.size;
|
|
// We prefer a lazily allocated memory type
|
|
// This means that the memory gets allocated when the implementation sees fit, e.g. when first using the images
|
|
VkBool32 lazyMemTypePresent;
|
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, &lazyMemTypePresent);
|
|
if (!lazyMemTypePresent)
|
|
{
|
|
// If this is not available, fall back to device local memory
|
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &multisampleTarget.color.memory));
|
|
vkBindImageMemory(device, multisampleTarget.color.image, multisampleTarget.color.memory, 0);
|
|
|
|
// Create image view for the MSAA target
|
|
VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo();
|
|
viewInfo.image = multisampleTarget.color.image;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = swapChain.colorFormat;
|
|
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
viewInfo.subresourceRange.levelCount = 1;
|
|
viewInfo.subresourceRange.layerCount = 1;
|
|
|
|
VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &multisampleTarget.color.view));
|
|
|
|
// Depth target
|
|
info.imageType = VK_IMAGE_TYPE_2D;
|
|
info.format = depthFormat;
|
|
info.extent.width = width;
|
|
info.extent.height = height;
|
|
info.extent.depth = 1;
|
|
info.mipLevels = 1;
|
|
info.arrayLayers = 1;
|
|
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
info.samples = sampleCount;
|
|
// Image will only be used as a transient target
|
|
info.usage = VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
VK_CHECK_RESULT(vkCreateImage(device, &info, nullptr, &multisampleTarget.depth.image));
|
|
|
|
vkGetImageMemoryRequirements(device, multisampleTarget.depth.image, &memReqs);
|
|
memAlloc = vks::initializers::memoryAllocateInfo();
|
|
memAlloc.allocationSize = memReqs.size;
|
|
|
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, &lazyMemTypePresent);
|
|
if (!lazyMemTypePresent)
|
|
{
|
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
}
|
|
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &multisampleTarget.depth.memory));
|
|
vkBindImageMemory(device, multisampleTarget.depth.image, multisampleTarget.depth.memory, 0);
|
|
|
|
// Create image view for the MSAA target
|
|
viewInfo.image = multisampleTarget.depth.image;
|
|
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
viewInfo.format = depthFormat;
|
|
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
viewInfo.subresourceRange.levelCount = 1;
|
|
viewInfo.subresourceRange.layerCount = 1;
|
|
|
|
VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &multisampleTarget.depth.view));
|
|
}
|
|
|
|
// Setup a render pass for using a multi sampled attachment
|
|
// and a resolve attachment that the msaa image is resolved
|
|
// to at the end of the render pass
|
|
void setupRenderPass()
|
|
{
|
|
// Overrides the virtual function of the base class
|
|
|
|
std::array<VkAttachmentDescription, 4> attachments = {};
|
|
|
|
// Multisampled attachment that we render to
|
|
attachments[0].format = swapChain.colorFormat;
|
|
attachments[0].samples = sampleCount;
|
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
// No longer required after resolve, this may save some bandwidth on certain GPUs
|
|
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
// This is the frame buffer attachment to where the multisampled image
|
|
// will be resolved to and which will be presented to the swapchain
|
|
attachments[1].format = swapChain.colorFormat;
|
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
// Multisampled depth attachment we render to
|
|
attachments[2].format = depthFormat;
|
|
attachments[2].samples = sampleCount;
|
|
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[2].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[2].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[2].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
// Depth resolve attachment
|
|
attachments[3].format = depthFormat;
|
|
attachments[3].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[3].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[3].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[3].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[3].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[3].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[3].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference colorReference = {};
|
|
colorReference.attachment = 0;
|
|
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference depthReference = {};
|
|
depthReference.attachment = 2;
|
|
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
// Resolve attachment reference for the color attachment
|
|
VkAttachmentReference resolveReference = {};
|
|
resolveReference.attachment = 1;
|
|
resolveReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
VkSubpassDescription subpass = {};
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpass.colorAttachmentCount = 1;
|
|
subpass.pColorAttachments = &colorReference;
|
|
// Pass our resolve attachments to the sub pass
|
|
subpass.pResolveAttachments = &resolveReference;
|
|
subpass.pDepthStencilAttachment = &depthReference;
|
|
|
|
std::array<VkSubpassDependency, 2> dependencies;
|
|
|
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[0].dstSubpass = 0;
|
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
dependencies[1].srcSubpass = 0;
|
|
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
VkRenderPassCreateInfo renderPassInfo = vks::initializers::renderPassCreateInfo();
|
|
renderPassInfo.attachmentCount = attachments.size();
|
|
renderPassInfo.pAttachments = attachments.data();
|
|
renderPassInfo.subpassCount = 1;
|
|
renderPassInfo.pSubpasses = &subpass;
|
|
renderPassInfo.dependencyCount = 2;
|
|
renderPassInfo.pDependencies = dependencies.data();
|
|
|
|
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
|
|
}
|
|
|
|
// Frame buffer attachments must match with render pass setup,
|
|
// so we need to adjust frame buffer creation to cover our
|
|
// multisample target
|
|
void setupFrameBuffer()
|
|
{
|
|
// Overrides the virtual function of the base class
|
|
|
|
std::array<VkImageView, 4> attachments;
|
|
|
|
setupMultisampleTarget();
|
|
|
|
attachments[0] = multisampleTarget.color.view;
|
|
// attachment[1] = swapchain image
|
|
attachments[2] = multisampleTarget.depth.view;
|
|
attachments[3] = depthStencil.view;
|
|
|
|
VkFramebufferCreateInfo frameBufferCreateInfo = {};
|
|
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
frameBufferCreateInfo.pNext = NULL;
|
|
frameBufferCreateInfo.renderPass = renderPass;
|
|
frameBufferCreateInfo.attachmentCount = attachments.size();
|
|
frameBufferCreateInfo.pAttachments = attachments.data();
|
|
frameBufferCreateInfo.width = width;
|
|
frameBufferCreateInfo.height = height;
|
|
frameBufferCreateInfo.layers = 1;
|
|
|
|
// Create frame buffers for every swap chain image
|
|
frameBuffers.resize(swapChain.imageCount);
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
attachments[1] = swapChain.buffers[i].view;
|
|
VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
|
|
}
|
|
}
|
|
|
|
void reBuildCommandBuffers()
|
|
{
|
|
if (!checkCommandBuffers())
|
|
{
|
|
destroyCommandBuffers();
|
|
createCommandBuffers();
|
|
}
|
|
buildCommandBuffers();
|
|
}
|
|
|
|
void buildCommandBuffers()
|
|
{
|
|
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
|
|
|
|
VkClearValue clearValues[3];
|
|
// Clear to a white background for higher contrast
|
|
clearValues[0].color = { { 1.0f, 1.0f, 1.0f, 1.0f } };
|
|
clearValues[1].color = { { 1.0f, 1.0f, 1.0f, 1.0f } };
|
|
clearValues[2].depthStencil = { 1.0f, 0 };
|
|
|
|
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
|
|
renderPassBeginInfo.renderPass = renderPass;
|
|
renderPassBeginInfo.renderArea.extent.width = width;
|
|
renderPassBeginInfo.renderArea.extent.height = height;
|
|
renderPassBeginInfo.clearValueCount = 3;
|
|
renderPassBeginInfo.pClearValues = clearValues;
|
|
|
|
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
|
|
{
|
|
// Set target frame buffer
|
|
renderPassBeginInfo.framebuffer = frameBuffers[i];
|
|
|
|
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
|
|
|
|
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
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);
|
|
|
|
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
|
|
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, useSampleShading ? pipelines.MSAASampleShading : pipelines.MSAA);
|
|
|
|
VkDeviceSize offsets[1] = { 0 };
|
|
vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.example.vertices.buffer, offsets);
|
|
vkCmdBindIndexBuffer(drawCmdBuffers[i], models.example.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
|
|
vkCmdDrawIndexed(drawCmdBuffers[i], models.example.indexCount, 1, 0, 0, 0);
|
|
|
|
vkCmdEndRenderPass(drawCmdBuffers[i]);
|
|
|
|
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
|
|
}
|
|
}
|
|
|
|
void loadAssets()
|
|
{
|
|
models.example.loadFromFile(getAssetPath() + "models/voyager/voyager.dae", vertexLayout, 1.0f, vulkanDevice, queue);
|
|
if (deviceFeatures.textureCompressionBC) {
|
|
textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_bc3_unorm.ktx", VK_FORMAT_BC3_UNORM_BLOCK, vulkanDevice, queue);
|
|
}
|
|
else if (deviceFeatures.textureCompressionASTC_LDR) {
|
|
textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_astc_8x8_unorm.ktx", VK_FORMAT_ASTC_8x8_UNORM_BLOCK, vulkanDevice, queue);
|
|
}
|
|
else if (deviceFeatures.textureCompressionETC2) {
|
|
textures.colorMap.loadFromFile(getAssetPath() + "models/voyager/voyager_etc2_unorm.ktx", VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, vulkanDevice, queue);
|
|
}
|
|
else {
|
|
vks::tools::exitFatal("Device does not support any compressed texture format!", "Error");
|
|
}
|
|
}
|
|
|
|
void setupVertexDescriptions()
|
|
{
|
|
// Binding description
|
|
vertices.bindingDescriptions.resize(1);
|
|
vertices.bindingDescriptions[0] =
|
|
vks::initializers::vertexInputBindingDescription(
|
|
VERTEX_BUFFER_BIND_ID,
|
|
vertexLayout.stride(),
|
|
VK_VERTEX_INPUT_RATE_VERTEX);
|
|
|
|
// Attribute descriptions
|
|
vertices.attributeDescriptions.resize(4);
|
|
// Location 0 : Position
|
|
vertices.attributeDescriptions[0] =
|
|
vks::initializers::vertexInputAttributeDescription(
|
|
VERTEX_BUFFER_BIND_ID,
|
|
0,
|
|
VK_FORMAT_R32G32B32_SFLOAT,
|
|
0);
|
|
// Location 1 : Normal
|
|
vertices.attributeDescriptions[1] =
|
|
vks::initializers::vertexInputAttributeDescription(
|
|
VERTEX_BUFFER_BIND_ID,
|
|
1,
|
|
VK_FORMAT_R32G32B32_SFLOAT,
|
|
sizeof(float) * 3);
|
|
// Location 2 : Texture coordinates
|
|
vertices.attributeDescriptions[2] =
|
|
vks::initializers::vertexInputAttributeDescription(
|
|
VERTEX_BUFFER_BIND_ID,
|
|
2,
|
|
VK_FORMAT_R32G32_SFLOAT,
|
|
sizeof(float) * 6);
|
|
// Location 3 : Color
|
|
vertices.attributeDescriptions[3] =
|
|
vks::initializers::vertexInputAttributeDescription(
|
|
VERTEX_BUFFER_BIND_ID,
|
|
3,
|
|
VK_FORMAT_R32G32B32_SFLOAT,
|
|
sizeof(float) * 8);
|
|
|
|
vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo();
|
|
vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size();
|
|
vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data();
|
|
vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size();
|
|
vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data();
|
|
}
|
|
|
|
void setupDescriptorPool()
|
|
{
|
|
// Example uses one ubo and one combined image sampler
|
|
std::vector<VkDescriptorPoolSize> poolSizes =
|
|
{
|
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
|
|
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1),
|
|
};
|
|
|
|
VkDescriptorPoolCreateInfo descriptorPoolInfo =
|
|
vks::initializers::descriptorPoolCreateInfo(
|
|
poolSizes.size(),
|
|
poolSizes.data(),
|
|
2);
|
|
|
|
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
|
|
}
|
|
|
|
void setupDescriptorSetLayout()
|
|
{
|
|
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
|
|
{
|
|
// Binding 0 : Vertex shader uniform buffer
|
|
vks::initializers::descriptorSetLayoutBinding(
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
VK_SHADER_STAGE_VERTEX_BIT,
|
|
0),
|
|
// Binding 1 : Fragment shader combined sampler
|
|
vks::initializers::descriptorSetLayoutBinding(
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
1),
|
|
};
|
|
|
|
VkDescriptorSetLayoutCreateInfo descriptorLayout =
|
|
vks::initializers::descriptorSetLayoutCreateInfo(
|
|
setLayoutBindings.data(),
|
|
setLayoutBindings.size());
|
|
|
|
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
|
|
|
|
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
|
|
vks::initializers::pipelineLayoutCreateInfo(
|
|
&descriptorSetLayout,
|
|
1);
|
|
|
|
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
|
|
}
|
|
|
|
void setupDescriptorSet()
|
|
{
|
|
VkDescriptorSetAllocateInfo allocInfo =
|
|
vks::initializers::descriptorSetAllocateInfo(
|
|
descriptorPool,
|
|
&descriptorSetLayout,
|
|
1);
|
|
|
|
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
|
|
|
|
VkDescriptorImageInfo texDescriptor =
|
|
vks::initializers::descriptorImageInfo(
|
|
textures.colorMap.sampler,
|
|
textures.colorMap.view,
|
|
VK_IMAGE_LAYOUT_GENERAL);
|
|
|
|
std::vector<VkWriteDescriptorSet> writeDescriptorSets =
|
|
{
|
|
// Binding 0 : Vertex shader uniform buffer
|
|
vks::initializers::writeDescriptorSet(
|
|
descriptorSet,
|
|
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
|
0,
|
|
&uniformBuffer.descriptor),
|
|
// Binding 1 : Color map
|
|
vks::initializers::writeDescriptorSet(
|
|
descriptorSet,
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
|
1,
|
|
&texDescriptor)
|
|
};
|
|
|
|
vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL);
|
|
}
|
|
|
|
void preparePipelines()
|
|
{
|
|
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
|
|
vks::initializers::pipelineInputAssemblyStateCreateInfo(
|
|
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
|
0,
|
|
VK_FALSE);
|
|
|
|
VkPipelineRasterizationStateCreateInfo rasterizationState =
|
|
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 colorBlendState =
|
|
vks::initializers::pipelineColorBlendStateCreateInfo(
|
|
1,
|
|
&blendAttachmentState);
|
|
|
|
VkPipelineDepthStencilStateCreateInfo depthStencilState =
|
|
vks::initializers::pipelineDepthStencilStateCreateInfo(
|
|
VK_TRUE,
|
|
VK_TRUE,
|
|
VK_COMPARE_OP_LESS_OR_EQUAL);
|
|
|
|
VkPipelineViewportStateCreateInfo viewportState =
|
|
vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
|
|
|
|
std::vector<VkDynamicState> dynamicStateEnables = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
VkPipelineDynamicStateCreateInfo dynamicState =
|
|
vks::initializers::pipelineDynamicStateCreateInfo(
|
|
dynamicStateEnables.data(),
|
|
dynamicStateEnables.size(),
|
|
0);
|
|
|
|
VkGraphicsPipelineCreateInfo pipelineCreateInfo =
|
|
vks::initializers::pipelineCreateInfo(
|
|
pipelineLayout,
|
|
renderPass,
|
|
0);
|
|
|
|
VkPipelineMultisampleStateCreateInfo multisampleState{};
|
|
multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
|
|
|
std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages;
|
|
|
|
pipelineCreateInfo.pVertexInputState = &vertices.inputState;
|
|
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
|
pipelineCreateInfo.pRasterizationState = &rasterizationState;
|
|
pipelineCreateInfo.pColorBlendState = &colorBlendState;
|
|
pipelineCreateInfo.pMultisampleState = &multisampleState;
|
|
pipelineCreateInfo.pViewportState = &viewportState;
|
|
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
|
|
pipelineCreateInfo.pDynamicState = &dynamicState;
|
|
pipelineCreateInfo.stageCount = shaderStages.size();
|
|
pipelineCreateInfo.pStages = shaderStages.data();
|
|
|
|
// MSAA rendering pipeline
|
|
shaderStages[0] = loadShader(getAssetPath() + "shaders/mesh/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
|
|
shaderStages[1] = loadShader(getAssetPath() + "shaders/mesh/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
// Setup multi sampling
|
|
multisampleState.rasterizationSamples = sampleCount; // Number of samples to use for rasterization
|
|
|
|
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.MSAA));
|
|
|
|
// MSAA with sample shading pipeline
|
|
// Sample shading enables per-sample shading to avoid shader aliasing and smooth out e.g. high frequency texture maps
|
|
// Note: This will trade performance for are more stable image
|
|
multisampleState.sampleShadingEnable = VK_TRUE; // Enable per-sample shading (instead of per-fragment)
|
|
multisampleState.minSampleShading = 0.25f; // Minimum fraction for sample shading
|
|
|
|
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.MSAASampleShading));
|
|
}
|
|
|
|
// Prepare and initialize uniform buffer containing shader uniforms
|
|
void prepareUniformBuffers()
|
|
{
|
|
// Vertex shader uniform buffer block
|
|
VK_CHECK_RESULT(vulkanDevice->createBuffer(
|
|
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
&uniformBuffer,
|
|
sizeof(uboVS)));
|
|
|
|
// Map persistent
|
|
VK_CHECK_RESULT(uniformBuffer.map());
|
|
|
|
updateUniformBuffers();
|
|
}
|
|
|
|
void updateUniformBuffers()
|
|
{
|
|
// Vertex shader
|
|
glm::mat4 viewMatrix = glm::mat4();
|
|
uboVS.projection = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f);
|
|
viewMatrix = glm::translate(viewMatrix, glm::vec3(0.0f, 0.0f, zoom));
|
|
|
|
float offset = 0.5f;
|
|
int uboIndex = 1;
|
|
uboVS.model = glm::mat4();
|
|
uboVS.model = viewMatrix * glm::translate(uboVS.model, cameraPos);
|
|
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
|
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
|
|
uboVS.model = glm::rotate(uboVS.model, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
|
|
|
|
memcpy(uniformBuffer.mapped, &uboVS, sizeof(uboVS));
|
|
}
|
|
|
|
void draw()
|
|
{
|
|
VulkanExampleBase::prepareFrame();
|
|
|
|
// Command buffer to be sumitted to the queue
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
|
|
|
|
// Submit to queue
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
|
|
VulkanExampleBase::submitFrame();
|
|
}
|
|
|
|
void prepare()
|
|
{
|
|
setSampleCount();
|
|
VulkanExampleBase::prepare();
|
|
loadAssets();
|
|
setupVertexDescriptions();
|
|
prepareUniformBuffers();
|
|
setupDescriptorSetLayout();
|
|
preparePipelines();
|
|
setupDescriptorPool();
|
|
setupDescriptorSet();
|
|
buildCommandBuffers();
|
|
prepared = true;
|
|
}
|
|
|
|
virtual void render()
|
|
{
|
|
if (!prepared)
|
|
return;
|
|
draw();
|
|
}
|
|
|
|
virtual void viewChanged()
|
|
{
|
|
updateUniformBuffers();
|
|
}
|
|
|
|
void toggleSampleShading()
|
|
{
|
|
useSampleShading = !useSampleShading;
|
|
reBuildCommandBuffers();
|
|
}
|
|
|
|
virtual void keyPressed(uint32_t keyCode)
|
|
{
|
|
switch (keyCode)
|
|
{
|
|
case KEY_S:
|
|
case GAMEPAD_BUTTON_A:
|
|
toggleSampleShading();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Determine the maximum sample count usable by the platform
|
|
void setSampleCount()
|
|
{
|
|
VkSampleCountFlags flags = std::min(deviceProperties.limits.framebufferColorSampleCounts,
|
|
deviceProperties.limits.framebufferDepthSampleCounts);
|
|
// Extract the value of the high-order bit of the flags
|
|
sampleCount = (VkSampleCountFlagBits)(flags ? (1 << (fls(flags) - 1)) : 0);
|
|
}
|
|
|
|
};
|
|
|
|
VULKAN_EXAMPLE_MAIN()
|