You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
26 KiB
C++
547 lines
26 KiB
C++
//------------------------------------------------------------------------------
|
|
// Copyright (c) 2025 Michele Morrone
|
|
// All rights reserved.
|
|
//
|
|
// https://michelemorrone.eu - https://brutpitt.com
|
|
//
|
|
// X: https://x.com/BrutPitt - GitHub: https://github.com/BrutPitt
|
|
//
|
|
// direct mail: brutpitt(at)gmail.com - me(at)michelemorrone.eu
|
|
//
|
|
// This software is distributed under the terms of the BSD 2-Clause license
|
|
//------------------------------------------------------------------------------
|
|
#include <vulkan/vulkan.hpp>
|
|
#include <shaderc/shaderc.hpp>
|
|
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <set>
|
|
#include <limits>
|
|
#include <array>
|
|
#include <cfloat>
|
|
|
|
#include "vkCube.h"
|
|
|
|
// passed from CMake: SPIR_V_EXT is different for DEBUG | RELEASE
|
|
|
|
#ifndef APP_SHADERS_DIR
|
|
#define APP_SHADERS_DIR Shaders
|
|
#endif
|
|
|
|
#ifdef NDEBUG
|
|
#define SPIR_V_EXT ".spirv"
|
|
#else
|
|
#define SPIR_V_EXT ".dbg.spirv"
|
|
#endif
|
|
|
|
#define FRAG_NAME "vkLightCube.frag" SPIR_V_EXT
|
|
#define VERT_NAME "vkLightCube.vert" SPIR_V_EXT
|
|
|
|
#define _STRING(x) #x
|
|
#define STRING(x) _STRING(x)
|
|
|
|
static std::vector<uint32_t> readFile(const std::string& filename)
|
|
{
|
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
|
|
|
if (!file.is_open()) { throw std::runtime_error("failed to open file"); }
|
|
|
|
const auto fileSize = file.tellg();
|
|
std::vector<uint32_t> buffer(fileSize);
|
|
|
|
file.seekg(0);
|
|
file.read((char *)buffer.data(), fileSize);
|
|
file.close();
|
|
|
|
return buffer;
|
|
}
|
|
|
|
uint32_t getGraphicsIndex(const vk::PhysicalDevice &physicalDevice)
|
|
{
|
|
auto qFamProp = physicalDevice.getQueueFamilyProperties();
|
|
return std::distance(qFamProp.begin(), std::find_if(qFamProp.begin(), qFamProp.end(), [](vk::QueueFamilyProperties const& prop) { return prop.queueFlags & vk::QueueFlagBits::eGraphics; }));
|
|
}
|
|
|
|
uint32_t getPresentIndex(const vk::PhysicalDevice &physicalDevice, const vk::SurfaceKHR &surface)
|
|
{
|
|
uint32_t qFamPropSize = physicalDevice.getQueueFamilyProperties().size();
|
|
for(uint32_t i = 0; i < qFamPropSize; i++)
|
|
if(physicalDevice.getSurfaceSupportKHR(i, surface)) return i;
|
|
throw std::runtime_error("No present surface supported\n");
|
|
}
|
|
|
|
void vkAppBase::createInstance()
|
|
{
|
|
vk::ApplicationInfo appInfo(appName, 1, nullptr, 0, VK_API_VERSION_1_1);
|
|
vk::InstanceCreateInfo instInfo({}, &appInfo, 0, nullptr, uint32_t(framework.getExtensions().size()), framework.getExtensions().data());
|
|
|
|
instance = vk::createInstance(instInfo); // create a Instance
|
|
#if defined(ENABLE_VALIDATION_LAYER) && defined ADDITIONAL_INFO
|
|
std::cout << "Available extensions:" << std::endl;
|
|
for(const auto& extension : vk::enumerateInstanceExtensionProperties()) std::cout << "\t" << extension.extensionName << std::endl;
|
|
#endif
|
|
}
|
|
|
|
void vkAppBase::selectPhysicalDevice()
|
|
{
|
|
const auto physicalDevices = instance.enumeratePhysicalDevices();
|
|
if(physicalDevices.empty()) { throw std::runtime_error("no GPUs with Vulkan support!"); }
|
|
|
|
PRINT_AVAILABLE_DEVICES(physicalDevices) // print all devices aviable in debug mode
|
|
physicalDevice = physicalDevices.front(); // Select only first device
|
|
}
|
|
|
|
void vkAppBase::initLogicalDevice()
|
|
{
|
|
graphQueueFamilyIdx = getGraphicsIndex(physicalDevice);
|
|
const uint32_t presentQueueFamilyIdx = getPresentIndex(physicalDevice, surface);
|
|
|
|
std::set uniqueQueueFamilyIndices = { graphQueueFamilyIdx, presentQueueFamilyIdx };
|
|
std::vector<uint32_t> familyIndices = { uniqueQueueFamilyIndices.begin(), uniqueQueueFamilyIndices.end() };
|
|
|
|
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
|
|
|
constexpr float queuePriority = 0.0f;
|
|
for(uint32_t queueFamilyIndex : uniqueQueueFamilyIndices)
|
|
queueCreateInfos.push_back(vk::DeviceQueueCreateInfo({}, uint32_t(queueFamilyIndex), 1, &queuePriority));
|
|
|
|
const std::vector deviceExtensions { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
|
|
|
|
const vk::DeviceCreateInfo deviceInfo({}, queueCreateInfos.size(), queueCreateInfos.data(),
|
|
debug.validationLayers.size(), nullptr /* deprecated debug.validationLayers.data()*/, // only if Debug enabled -> check class
|
|
deviceExtensions.size(), deviceExtensions.data());
|
|
logicalDevice = physicalDevice.createDevice(deviceInfo);
|
|
|
|
sm = graphQueueFamilyIdx == presentQueueFamilyIdx ? sharingModeStruct({ vk::SharingMode::eExclusive , 0u, nullptr }) :
|
|
sharingModeStruct({ vk::SharingMode::eConcurrent, 2u, familyIndices.data() });
|
|
|
|
commandPool = logicalDevice.createCommandPool(vk::CommandPoolCreateInfo(vk::CommandPoolCreateFlagBits::eResetCommandBuffer, uint32_t(graphQueueFamilyIdx)));
|
|
|
|
queueGraphics = logicalDevice.getQueue(graphQueueFamilyIdx, 0);
|
|
queuePresent = logicalDevice.getQueue(presentQueueFamilyIdx, 0);
|
|
}
|
|
|
|
void vkAppBase::builSwapchain(vk::SwapchainKHR oldSwapChain)
|
|
{
|
|
swapChainExtent = vk::Extent2D(width, height);
|
|
const vk::SwapchainCreateInfoKHR swapChainInfo({}, surface, minImageCount, surfaceFormat, vk::ColorSpaceKHR::eSrgbNonlinear, swapChainExtent, 1, vk::ImageUsageFlagBits::eColorAttachment,
|
|
sm.sharingMode, sm.idxCount, sm.idxDataPtr, vk::SurfaceTransformFlagBitsKHR::eIdentity, vk::CompositeAlphaFlagBitsKHR::eOpaque, pmType, true, oldSwapChain);
|
|
|
|
swapChain = logicalDevice.createSwapchainKHR(swapChainInfo);
|
|
swapChainImages = logicalDevice.getSwapchainImagesKHR(swapChain);
|
|
}
|
|
|
|
void vkAppBase::buildDepthBuffer()
|
|
{
|
|
depthBuffer.init(physicalDevice, logicalDevice, vk::Format::eD32Sfloat, vk::Extent2D(width,height));
|
|
}
|
|
|
|
void vkAppBase::buildImagesView()
|
|
{
|
|
scImageView.clear();
|
|
constexpr vk::ComponentMapping componentMapping(vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA);
|
|
constexpr vk::ImageSubresourceRange imgSubresourceRange(vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1) ;
|
|
for (const auto image : swapChainImages) {
|
|
const vk::ImageViewCreateInfo imageViewInfo({}, image, vk::ImageViewType::e2D, surfaceFormat, componentMapping, imgSubresourceRange);
|
|
scImageView.push_back(logicalDevice.createImageView(imageViewInfo));
|
|
}
|
|
}
|
|
|
|
void vkAppBase::loadSpirVShaders()
|
|
{
|
|
const auto vertCode = readFile(STRING(APP_SHADERS_DIR) "/" VERT_NAME);
|
|
const auto fragCode = readFile(STRING(APP_SHADERS_DIR) "/" FRAG_NAME);
|
|
|
|
vertShaderMod = logicalDevice.createShaderModule(vk::ShaderModuleCreateInfo({}, vertCode.size(), vertCode.data()));
|
|
fragShaderMod = logicalDevice.createShaderModule(vk::ShaderModuleCreateInfo({}, fragCode.size(), fragCode.data()));
|
|
}
|
|
|
|
// Load and compile shaders, like in WebGL/OpenGL
|
|
/////////////////////////////////////////////
|
|
void vkAppBase::compileShaders()
|
|
{
|
|
const auto vertCode = readFile(VERT_NAME);
|
|
const auto fragCode = readFile(FRAG_NAME);
|
|
|
|
shaderc::Compiler compiler;
|
|
shaderc::CompileOptions options;
|
|
#ifdef NDEBUG
|
|
options.SetOptimizationLevel(shaderc_optimization_level_performance);
|
|
#else
|
|
options.SetOptimizationLevel(shaderc_optimization_level_zero);
|
|
#endif
|
|
shaderc::SpvCompilationResult vertShaderModule = compiler.CompileGlslToSpv((char *)vertCode.data(), shaderc_glsl_vertex_shader, "vertex shader", options);
|
|
if(vertShaderModule.GetCompilationStatus() != shaderc_compilation_status_success) std::cerr << vertShaderModule.GetErrorMessage();
|
|
|
|
const auto vertShaderCode = std::vector<uint32_t>{ vertShaderModule.cbegin(), vertShaderModule.cend() };
|
|
const vk::ShaderModuleCreateInfo vertShaderCreateInfo({}, vertShaderCode.size()*sizeof(uint32_t), vertShaderCode.data());
|
|
vertShaderMod = logicalDevice.createShaderModule(vertShaderCreateInfo);
|
|
|
|
const shaderc::SpvCompilationResult fragShaderModule = compiler.CompileGlslToSpv((char *)fragCode.data(), shaderc_glsl_fragment_shader, "fragment shader", options);
|
|
if (fragShaderModule.GetCompilationStatus() != shaderc_compilation_status_success) std::cerr << fragShaderModule.GetErrorMessage();
|
|
|
|
const auto fragShaderCode = std::vector<uint32_t>{ fragShaderModule.cbegin(), fragShaderModule.cend() };
|
|
vk::ShaderModuleCreateInfo fragShaderCreateInfo({}, fragShaderCode.size()*sizeof(uint32_t), fragShaderCode.data());
|
|
fragShaderMod = logicalDevice.createShaderModule(fragShaderCreateInfo);
|
|
}
|
|
|
|
void vkAppBase::buildRenderPass()
|
|
{
|
|
static std::vector<vk::AttachmentDescription> attachDescriptions;
|
|
|
|
attachDescriptions.emplace_back(vk::AttachmentDescriptionFlags(), surfaceFormat,
|
|
vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore,
|
|
vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::ePresentSrcKHR);
|
|
|
|
if(depthBuffer.format != vk::Format::eUndefined)
|
|
attachDescriptions.emplace_back(vk::AttachmentDescriptionFlags(), depthBuffer.format,
|
|
vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore,
|
|
vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
|
|
|
vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
|
|
vk::AttachmentReference depthAttachment( 1, vk::ImageLayout::eDepthStencilAttachmentOptimal );
|
|
|
|
vk::SubpassDescription subpassDescription({}, vk::PipelineBindPoint::eGraphics, {}, colorAttachmentRef, {}, depthBuffer.format != vk::Format::eUndefined ? &depthAttachment : nullptr);
|
|
renderPass = logicalDevice.createRenderPass(vk::RenderPassCreateInfo({}, attachDescriptions, subpassDescription));
|
|
}
|
|
|
|
void vkAppBase::createPipelineCache()
|
|
{
|
|
vk::PipelineCacheCreateInfo pipelineCacheCreateInfo;
|
|
pipelineCache = logicalDevice.createPipelineCache(pipelineCacheCreateInfo);
|
|
}
|
|
|
|
void vkAppBase::buildGraphPipeline()
|
|
{
|
|
vk::PipelineInputAssemblyStateCreateInfo assemblyInfo({}, vk::PrimitiveTopology::eTriangleList);
|
|
|
|
vk::PipelineRasterizationStateCreateInfo rastering { {}, {}, {}, vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack, vk::FrontFace::eClockwise, {}, {}, {}, {}, 1.0f };
|
|
|
|
vk::ColorComponentFlags colorComponentFlags( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );
|
|
vk::PipelineColorBlendAttachmentState cbAttachment( false, vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
|
|
vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd, colorComponentFlags );
|
|
|
|
vk::PipelineColorBlendStateCreateInfo colorBlending({}, false, vk::LogicOp::eNoOp, cbAttachment, { { 1.0f, 1.0f, 1.0f, 1.0f } } );
|
|
|
|
vk::PipelineViewportStateCreateInfo viewportState( {}, 1, {}, 1, {} );
|
|
|
|
std::vector dynamicStateFlags { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
|
|
vk::PipelineDynamicStateCreateInfo dynamicState({}, dynamicStateFlags.size(), dynamicStateFlags.data());
|
|
|
|
bool depthBuffered = true;
|
|
vk::StencilOpState stencilOpState( vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::CompareOp::eAlways );
|
|
vk::PipelineDepthStencilStateCreateInfo depthStencil({}, depthBuffered, depthBuffered, depthBufferCompareOp, false, false, stencilOpState, stencilOpState );
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisampling { {}, vk::SampleCountFlagBits::e1 };
|
|
|
|
|
|
vk::PipelineShaderStageCreateInfo vertShaderStageInfo({}, vk::ShaderStageFlagBits::eVertex , vertShaderMod, "main");
|
|
vk::PipelineShaderStageCreateInfo fragShaderStageInfo({}, vk::ShaderStageFlagBits::eFragment, fragShaderMod, "main");
|
|
std::array pipelineShaderStages = { vertShaderStageInfo, fragShaderStageInfo };
|
|
|
|
std::vector<vk::VertexInputAttributeDescription> vertexInputAttributeDescriptions;
|
|
vk::PipelineVertexInputStateCreateInfo vtxStateInfo;
|
|
vk::VertexInputBindingDescription vertexInputBindingDescription( 0, vertexStride );
|
|
|
|
if ( 0 < vertexStride ) {
|
|
vertexInputAttributeDescriptions.reserve( vertexInputAttributeFormatOffset.size() );
|
|
for ( uint32_t i = 0; i < vertexInputAttributeFormatOffset.size(); i++ )
|
|
vertexInputAttributeDescriptions.emplace_back( i, 0, vertexInputAttributeFormatOffset[i].first, vertexInputAttributeFormatOffset[i].second );
|
|
vtxStateInfo.setVertexBindingDescriptions( vertexInputBindingDescription );
|
|
vtxStateInfo.setVertexAttributeDescriptions( vertexInputAttributeDescriptions );
|
|
}
|
|
|
|
vk::GraphicsPipelineCreateInfo pipelineCreateInfo({}, pipelineShaderStages, &vtxStateInfo, &assemblyInfo, nullptr,
|
|
&viewportState, &rastering, &multisampling, &depthStencil, &colorBlending, &dynamicState, pipelineLayout, renderPass );
|
|
|
|
pipeline = logicalDevice.createGraphicsPipeline(pipelineCache, pipelineCreateInfo).value;
|
|
}
|
|
|
|
void vkAppBase::createSyncItems()
|
|
{
|
|
fence.resize(minImageCount);
|
|
vk::SemaphoreCreateInfo semaphoreCreateInfo;
|
|
for(size_t i = 0; i < minImageCount; i++)
|
|
fence[i] = logicalDevice.createFence({vk::FenceCreateFlagBits::eSignaled});
|
|
semaphoreImageAvailable = logicalDevice.createSemaphore(semaphoreCreateInfo);
|
|
semaphoreEndRendering = logicalDevice.createSemaphore(semaphoreCreateInfo);
|
|
}
|
|
|
|
void vkAppBase::buildFramebuffer()
|
|
{
|
|
vk::ImageView imgviewBuffers[2];
|
|
imgviewBuffers[1] = depthBuffer.imageView;
|
|
|
|
bool isDepthPresent = true;
|
|
|
|
scFrameBuffers.resize(minImageCount);
|
|
for(size_t i = 0; i < minImageCount; i++) {
|
|
imgviewBuffers[0] = scImageView[i];
|
|
scFrameBuffers[i] = logicalDevice.createFramebuffer(vk::FramebufferCreateInfo({}, renderPass, isDepthPresent ? 2 : 1, imgviewBuffers, width, height, 1));
|
|
}
|
|
}
|
|
|
|
void vkAppBase::createDescriptorsPool()
|
|
{
|
|
vk::DescriptorPoolSize poolSize(vk::DescriptorType::eUniformBuffer, 2);
|
|
descriptorPool = logicalDevice.createDescriptorPool(vk::DescriptorPoolCreateInfo({}, 1, poolSize));
|
|
}
|
|
|
|
void vkAppBase::setupDescriptorsSetLayout()
|
|
{
|
|
std::vector<vk::DescriptorSetLayoutBinding> layoutBindings {
|
|
{ 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex },
|
|
{ 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment} };
|
|
|
|
descriptorSetLayout = logicalDevice.createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, layoutBindings));
|
|
pipelineLayout = logicalDevice.createPipelineLayout( vk::PipelineLayoutCreateInfo({}, descriptorSetLayout));
|
|
}
|
|
|
|
void vkAppBase::updateDescriptorsSets(std::vector<bufferSet> &buffer)
|
|
{
|
|
descriptorSets = logicalDevice.allocateDescriptorSets(vk::DescriptorSetAllocateInfo(descriptorPool, descriptorSetLayout));
|
|
std::vector<vk::DescriptorBufferInfo> desc;
|
|
for( auto &b : buffer) desc.emplace_back(b.buffer, 0, b.bufferSize);
|
|
|
|
vk::DescriptorSet descriptorSet = descriptorSets.front();
|
|
std::vector<vk::WriteDescriptorSet> writeDescriptorSet {
|
|
{descriptorSet, 0, 0, 1, vk::DescriptorType::eUniformBuffer, {}, &desc[0]},
|
|
{descriptorSet, 1, 0, 1, vk::DescriptorType::eUniformBuffer, {}, &desc[1]} };
|
|
logicalDevice.updateDescriptorSets(writeDescriptorSet, {});
|
|
}
|
|
|
|
void vkAppBase::buildCommandBuffers()
|
|
{
|
|
commandBuffer.resize(swapChainImages.size());
|
|
commandBuffer = logicalDevice.allocateCommandBuffers(vk::CommandBufferAllocateInfo(commandPool, vk::CommandBufferLevel::ePrimary, commandBuffer.size()));
|
|
}
|
|
|
|
void vkAppBase::setCommandBuffer(uint32_t currentFrame)
|
|
{
|
|
std::array<vk::ClearValue, 2> clearValues;
|
|
clearValues[0].color = vk::ClearColorValue( 0.07f, 0.07f, 0.07f, 0.07f );
|
|
clearValues[1].depthStencil = vk::ClearDepthStencilValue( depthBufferClearValue, 0 );
|
|
const vk::Rect2D rect(vk::Offset2D( 0, 0 ), swapChainExtent);
|
|
const vk::Rect2D scissor({ 0, 0 }, swapChainExtent);
|
|
const vk::Viewport viewport(0.0f, 0.0f, swapChainExtent.width, swapChainExtent.height, 0.0f, 1.0f);
|
|
|
|
logicalDevice.resetCommandPool(commandPool);
|
|
commandBuffer[currentFrame].reset();
|
|
commandBuffer[currentFrame].begin(vk::CommandBufferBeginInfo());
|
|
commandBuffer[currentFrame].beginRenderPass(vk::RenderPassBeginInfo(renderPass, scFrameBuffers[currentBufferIdx], rect, clearValues), vk::SubpassContents::eInline);
|
|
commandBuffer[currentFrame].setViewport(0, 1, &viewport);
|
|
commandBuffer[currentFrame].setScissor(0, 1, &scissor);
|
|
commandBuffer[currentFrame].bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline);
|
|
commandBuffer[currentFrame].bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, 1, &descriptorSets[0], 0, nullptr );
|
|
commandBuffer[currentFrame].bindVertexBuffers(0, vtxCubeData.buffer, {0 } );
|
|
|
|
commandBuffer[currentFrame].draw( 12 * 3, 2, 0, 0 ); //
|
|
|
|
commandBuffer[currentFrame].endRenderPass();
|
|
commandBuffer[currentFrame].end();
|
|
}
|
|
|
|
void vkAppBase::rebuildAllSwapchain()
|
|
{
|
|
// wait GPU to idle before destroy resources
|
|
logicalDevice.waitIdle();
|
|
|
|
// Rebuil swapchain
|
|
vk::SwapchainKHR oldSwapChain = swapChain;
|
|
builSwapchain(oldSwapChain);
|
|
logicalDevice.destroySwapchainKHR(oldSwapChain);
|
|
|
|
// rebuild: depthBuffer, frameBuffer, commandBuffer and syncObj
|
|
destroySwapChainComponents();
|
|
buildSwapChainComponents();
|
|
}
|
|
|
|
void vkApp::setPerspective()
|
|
{
|
|
float aspectRatio = float(height) / float(width); // Set "camera" position and perspective
|
|
float fov = radians( 45.0f ) * aspectRatio;
|
|
// PERSPECTIVE: depends from clipMatrix (+Z or -Z) ==> view in the end of vkCube.h
|
|
projMatrix = PERSPECTIVE( fov, 1/aspectRatio, 0.1f, 100.0f );
|
|
|
|
}
|
|
|
|
// light model
|
|
vec3 lightPos(2, 2.5, 3); // Light Position
|
|
void vkApp::setScene()
|
|
{
|
|
// LOOK_AT: depends from clipMatrix (+Z or -Z) ==> view in the end of vkCube.h
|
|
viewMatrix = LOOK_AT( { 12.0f, 6.0f, 4.0f }, // From / EyePos / PoV
|
|
{ 0.0f, 0.0f, 0.0f }, // To / Tgt
|
|
{ 3.0f, 1.0f, .0f } ); // Up
|
|
|
|
// Now scale cube to better view light position
|
|
cubeObj = mat4(1); // nothing to do ... scale( vec3(.5));
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
/// vGizmo3D initialize:
|
|
/// set/associate mouse BUTTON IDs and KEY modifier IDs to vGizmo3D functionalities <br><br>
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
framework.initVGizmo3D(vgTrackball);
|
|
|
|
/// imGuIZMO / vGizmo3D
|
|
/// Set initial rotation
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//vgTrackball.setRotation(quat(1,0,0,0)); // vGizmo3D with NO initial rotation (DEFAULT)
|
|
//vgTrackball.setRotation(eulerAngleXYZ(vec3(radians(45),
|
|
// radians( 0),
|
|
// radians( 0)))); // vGizmo3D with rotation of 45 degrees on X axis
|
|
vgTrackball.setRotation(viewMatrix); // vGizmo3D with ViewMatrix (lookAt) rotation
|
|
|
|
// for Pan & Dolly always bounded on screen coords (x = left/right, y = up/douw, z = in/out) we remove viewMatrix rotation
|
|
// otherwise Pan & Dolly have as reference the Cartesian axes
|
|
compensateView = inverse(mat4_cast(quat(viewMatrix)));
|
|
|
|
|
|
// acquiring rotation for the light pos
|
|
const float len = length(lightPos);
|
|
//if(len<1.0 && len>= FLT_EPSILON) { normalize(lightPos); len = 1.0; } // controls are not necessary: lightPos is known
|
|
//else if(len > FLT_EPSILON)
|
|
quat q = angleAxis(acosf(-lightPos.x/len), normalize(vec3(FLT_EPSILON, lightPos.z, -lightPos.y)));
|
|
vgTrackball.setSecondRot(q); // store secondary rotation for the Light
|
|
|
|
lightObj = translate(mat4(1), lightPos);
|
|
lightObj = inverse(static_cast<mat4>(vgTrackball.getSecondRot())) * lightObj ;
|
|
|
|
setPerspective();
|
|
}
|
|
|
|
void vkApp::draw()
|
|
{
|
|
if(!prepareFrame()) { resizeWnd(); return; } // acquireNextImageKHR ==> resizeWnd on vk::OutOfDateKHRError
|
|
|
|
const uint32_t currentFrame = currentBufferIdx; // used to differentiate in example: in reality they do not always coincide
|
|
|
|
vk::detail::resultCheck(logicalDevice.waitForFences(1, &fence[currentFrame], VK_TRUE, std::numeric_limits<uint64_t>::max()), "waitForFences...");
|
|
vk::detail::resultCheck(logicalDevice.resetFences (1, &fence[currentFrame]), "resetFences...");
|
|
|
|
setCommandBuffer(currentFrame);
|
|
|
|
constexpr vk::PipelineStageFlags waitStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
const vk::SubmitInfo submitInfo(1, &semaphoreImageAvailable, &waitStageMask,
|
|
1, &commandBuffer[currentBufferIdx],
|
|
1, &semaphoreEndRendering);
|
|
queueGraphics.submit(submitInfo, fence[currentFrame]);
|
|
submitFrame(); //presentKHR ==> resizeWnd on vk::OutOfDateKHRError and also on vk::Result::eSuboptimalKHR with stderr message
|
|
}
|
|
|
|
void vkApp::resizeWnd()
|
|
{
|
|
// get new surface size
|
|
framework.getFramebufferSize((int *)&width, (int *)&height); // e.g. glfwGetFramebufferSize / SDL_GetWindowSize | SDL_Vulkan_GetDrawableSize(
|
|
rebuildAllSwapchain(); // rebuild all swapchain components
|
|
setPerspective(); // recalibrate perspective aspect ratio
|
|
|
|
// vGizmo3D: is necessary to call when resize window/surface: re-calibrate drag rotation & auto-set mouse sensitivity
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
vgTrackball.viewportSize(width, height);
|
|
}
|
|
|
|
vec3 getLightPosFromQuat(quat &q, float centerDistance) { return (q * vec3(-1.0f, 0.0f, 0.0f)) * centerDistance ;}
|
|
quat getQuatRotFromVec3(vec3 &lPos) {
|
|
return normalize(angleAxis(acosf(-lPos.x/length(lPos)), normalize(vec3(FLT_EPSILON, lPos.z, -lPos.y))));
|
|
}
|
|
|
|
void vkApp::run()
|
|
{
|
|
while(framework.pollEvents()) {
|
|
framework.checkVGizmo3DMouseEvent(vgTrackball);
|
|
|
|
// vGizmo3D: call it every rendering loop if you want a continue rotation until you do not click on screen
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
vgTrackball.idle(); // set continuous rotation on Idle: the slow rotation depends on speed of last mouse movement
|
|
// It can be adjusted from setIdleRotSpeed(1.0) > more speed, < less
|
|
// It can be stopped by click on screen (without mouse movement)
|
|
vgTrackball.idleSecond();
|
|
|
|
// transferring the rotation to cube model matrix...
|
|
mat4 modelMatrix = cubeObj * mat4_cast(vgTrackball.getRotation());
|
|
|
|
// Build a "translation" matrix
|
|
mat4 translationMatrix = translate(mat4(1), vgTrackball.getPosition()); // add translations (pan/dolly) to an identity matrix
|
|
|
|
// old transformations used in "easy_examples": I comment and leave them to make less difficult the reading of the next steps
|
|
//uboMat.mvpMatrix = clipMatrix * projMatrix * viewMatrix * compensateView * translationMatrix * modelMatrix ;
|
|
//uboMat.lightMatrix = clipMatrix * projMatrix * viewMatrix * compensateView * translationMatrix * (static_cast<mat4>(vgTrackball.getSecondRot())) * lightObj;
|
|
|
|
// Decomposition of the various transformations to use (in different way) with normal, vtx position and light
|
|
uboMat.projMatrix = clipMatrix * projMatrix ;
|
|
uboMat.viewMatrix = viewMatrix ;
|
|
uboMat.compMatrix = compensateView;
|
|
uboMat.modelMatrix = translationMatrix * modelMatrix;
|
|
uboMat.lightMatrix = translationMatrix * static_cast<mat4>(vgTrackball.getSecondRot()) * lightObj;
|
|
|
|
// fill fragment uniforms: get PointOfView (camera position) from viewMatrix...
|
|
uboFrag.PoV = viewMatrix[3]; // in this example PoV does not change, so it would be useless to update it anytime...
|
|
|
|
// some way to get light position:
|
|
uboFrag.lightPos = uboMat.lightMatrix * vec4(1); // from LightMatrix
|
|
// another way to get lightPos:
|
|
// light has orbit invariant around cube, of ray always length(lightPos), so...
|
|
// uboFrag.lightPos = getLightPosFromQuat(vgTrackball.refSecondRot(),length(lightPos)) + vgTrackball.getPosition();
|
|
|
|
// update uniform buffers to GPU
|
|
for(auto &ubo : uboSceneMat) ubo.update();
|
|
|
|
// draw the cube, passing matrices to the vtx shader
|
|
draw(); // Render framebuffer
|
|
}
|
|
}
|
|
|
|
void vkApp::onInit() // called from constructor @ startup
|
|
{
|
|
// specific App Vulkan Initializations
|
|
|
|
// Send cube Vertex to GPU
|
|
vtxCubeData.init(physicalDevice, logicalDevice, vk::BufferUsageFlagBits::eVertexBuffer);
|
|
vtxCubeData.copyToDevice();
|
|
|
|
for(auto &ubo : uboSceneMat) { // uniform buffers for Vtx & Frag shaders
|
|
ubo.init(physicalDevice, logicalDevice, vk::BufferUsageFlagBits::eUniformBuffer);
|
|
ubo.update();
|
|
}
|
|
updateDescriptorsSets(uboSceneMat);
|
|
|
|
/// vGizmo3D initialize:
|
|
// Set Scene, vGizmo3D init and set starting rotations (primary & secondary)
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
setScene();
|
|
|
|
// If you need to flip "mouse movements"
|
|
// you can set they in the code or set as default values in vGizmo3D_config.h
|
|
// view in the end of vkApp header file
|
|
APP_FLIP_ROT_X
|
|
APP_FLIP_ROT_Y
|
|
APP_FLIP_ROT_Z
|
|
APP_FLIP_PAN_X
|
|
APP_FLIP_PAN_Y
|
|
APP_FLIP_ROT_X
|
|
APP_FLIP_DOLLY
|
|
}
|
|
|
|
void vkApp::onExit() // called from destructor @ exit
|
|
{
|
|
// wait GPU to idle before destroy resources
|
|
logicalDevice.waitIdle();
|
|
// Cleanup
|
|
// Vulkan App specific cleanup
|
|
vtxCubeData.destroy();
|
|
for(auto ubo : uboSceneMat) ubo.destroy();
|
|
}
|
|
|
|
vkApp* vkApp::theMainApp = nullptr;
|
|
int main() {
|
|
theApp = new vkApp; // theApp ==> vkApp::theMainApp
|
|
try { theApp->run(); } catch(const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; }
|
|
|
|
delete theApp;
|
|
return EXIT_SUCCESS;
|
|
} |