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.
581 lines
28 KiB
C++
581 lines
28 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"
|
|
|
|
#include <imgui/imgui.h>
|
|
#include <imgui/imgui_impl_vulkan.h>
|
|
#include <imgui/imgui_internal.h>
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// imGuIZMO: include imGuIZMOquat.h or imguizmo_quat.h
|
|
#include <imGuIZMOquat.h> // now also imguizmo_quat.h
|
|
|
|
void renderWidgets(vg::vGizmo3D &track, vec3& vLight, int width, int height);
|
|
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
// Load and compile shaders, like in WebGL/OpenGL
|
|
/////////////////////////////////////////////
|
|
void vkAppBase::compileShaders()
|
|
{
|
|
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, 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, 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, 1);
|
|
descriptorPool = logicalDevice.createDescriptorPool(vk::DescriptorPoolCreateInfo({}, 1, 1, &poolSize));
|
|
}
|
|
|
|
void vkAppBase::setupDescriptorsSetLayout()
|
|
{
|
|
vk::DescriptorSetLayoutBinding layoutBindings(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex);
|
|
|
|
descriptorSetLayout = logicalDevice.createDescriptorSetLayout(vk::DescriptorSetLayoutCreateInfo({}, 1, &layoutBindings));
|
|
pipelineLayout = logicalDevice.createPipelineLayout( vk::PipelineLayoutCreateInfo({}, 1, &descriptorSetLayout));
|
|
}
|
|
|
|
void vkAppBase::updateDescriptorsSets(vk::Buffer &buffer, size_t bufferSize)
|
|
{
|
|
descriptorSet = logicalDevice.allocateDescriptorSets(vk::DescriptorSetAllocateInfo(descriptorPool, 1, &descriptorSetLayout));
|
|
vk::DescriptorBufferInfo desc(buffer, 0, bufferSize);
|
|
vk::WriteDescriptorSet writeDescriptorSet(descriptorSet[0], 0, 0, 1, vk::DescriptorType::eUniformBuffer, {}, &desc);
|
|
logicalDevice.updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr);
|
|
}
|
|
|
|
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, &descriptorSet[0], 0, nullptr );
|
|
commandBuffer[currentFrame].bindVertexBuffers( 0, vtxCubeData.uniformBuffer, { 0 } );
|
|
|
|
commandBuffer[currentFrame].draw( 12 * 3, 2, 0, 0 ); //
|
|
|
|
// ImGui: just before endRenderPass insert ImGui CommandBuffer data
|
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffer[currentFrame]);
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
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))));
|
|
}
|
|
|
|
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(vec3( 12.0f, 6.0f, 4.0f ), // From / EyePos / PoV
|
|
vec3( 0.0f, 0.0f, 0.0f ), // To / Tgt
|
|
vec3( 3.0f, 1.0f, .0f)); // Up
|
|
|
|
// Now scale cube to better view light position
|
|
cubeObj = mat4(1); // nothing to do ... scale( vec3(.5));
|
|
|
|
mat4 clipMatrixA = mat4(1.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, -1.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f,-1.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f ); // vulkan clip space: -y & -z
|
|
|
|
/// imGuIZMO / vGizmo3D
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
vgTrackball.setRotation(quat(1,0,0,0)); // vGizmo3D with NO initial rotation (default initialization)
|
|
//track.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 = scale(lightObj, vec3(.1)); // using same cube vertex but with 10% size
|
|
|
|
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);
|
|
}
|
|
|
|
void vkApp::run()
|
|
{
|
|
// initialize scene: lookAt, perspective, objs: size & pos
|
|
setScene();
|
|
|
|
// ImGui Vulkan initialization
|
|
imguiInit();
|
|
|
|
/// vGizmo3D (3D screen manipulator) initialize:
|
|
/// set/associate mouse BUTTON IDs and KEY Modifier IDs to vGizmo3D functionalities
|
|
framework.initVGizmo3D(vgTrackball);
|
|
|
|
// imGuIZMO: set mouse feeling and key mods
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
imguiGizmo::setGizmoFeelingRot(.75f); // default 1.0, >1 more mouse sensitivity, <1 less mouse sensitivity
|
|
imguiGizmo::setPanScale(.5f); // default 1.0, >1 more, <1 less
|
|
imguiGizmo::setDollyScale(.5f); // default 1.0, >1 more, <1 less
|
|
imguiGizmo::setDollyWheelScale(.5f); // default 1.0, > more, < less ... (from v3.1 separate values)
|
|
imguiGizmo::setPanModifier(vg::evSuperModifier); // change KEY modifier: CTRL (default) ==> SUPER
|
|
imguiGizmo::setDollyModifier(vg::evControlModifier); // change KEY modifier: SHIFT (default) ==> CTRL
|
|
|
|
// Main render loop
|
|
while(framework.pollEvents()) { // glfwPollEvents | SDL_PollEvent ... with exit/quit check
|
|
|
|
/// vGizmo3D: check changing button state to activate/deactivate drag movements
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
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.idleSecondary(); // also for "secondary" rotation
|
|
|
|
|
|
// ImGUI: prepare ImGUI new frame
|
|
ImGui_ImplVulkan_NewFrame();
|
|
framework.imguiNewFrame(); // ImGui_ImplGlfw_NewFrame | ImGui_ImplSDL2_NewFrame | ImGui_ImplSDL3_NewFrame
|
|
ImGui::NewFrame();
|
|
|
|
// ImGui: Your windows here
|
|
|
|
|
|
// using vec3 (lightPos) is necessary sync with vGizmo3D : in next example (08) this will no longer be necessary
|
|
lightPos = getLightPosFromQuat(vgTrackball.getSecondRotRef() ,length(lightPos)); //to syncronize trackball & lightPos passed to the Widgets call
|
|
|
|
// Render ALL ImGuIZMO_quat widgets
|
|
renderWidgets(vgTrackball, lightPos, width, height); // in next example (08) we will use directly quaternions
|
|
|
|
// using vec3 (lightPos) is necessary re-sync with vGizmo3D: in next example (08) this will no longer be necessary
|
|
vgTrackball.setSecondRot(getQuatRotFromVec3(lightPos)); //to re-syncronize trackball & lightPos passed to the Widgets call
|
|
|
|
|
|
// 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
|
|
|
|
// build MVP matrix to pass to shader ==> view vkCube_05 and higher for better implementation to render independent Pan/Dolly from viewMatrix
|
|
uboMat.mvpMatrix = clipMatrix * projMatrix * viewMatrix * compensateView * translationMatrix * modelMatrix;
|
|
uboMat.lightMatrix = clipMatrix * projMatrix * viewMatrix * compensateView * translationMatrix * (static_cast<mat4>(vgTrackball.getSecondRot())) * lightObj;
|
|
|
|
uboSceneMat.update(); // update mat to vtx shader
|
|
|
|
// ImGui: Render/draw
|
|
ImGui::Render();
|
|
// draw the cube, passing matrices to the vtx shader
|
|
draw(); // Render framebuffer
|
|
}
|
|
|
|
// Imgui cleanup
|
|
imguiExit();
|
|
}
|
|
|
|
void vkApp::imguiInit()
|
|
{
|
|
// Initialize ImGui
|
|
const std::array pool_sizes { vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, 1) } ; // array declaration C++ 17
|
|
vk::DescriptorPoolCreateInfo pool_info(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, //flags
|
|
1 /* .maxSets */, pool_sizes.size(), pool_sizes.data() /*, .pNext = nullptr */ );
|
|
|
|
vk::detail::resultCheck(logicalDevice.createDescriptorPool( &pool_info, nullptr, &imguiPool), "ImGui: createDescripptorPool...");
|
|
|
|
// Setup/Initialize Dear ImGui context
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
|
|
|
ImGui::StyleColorsDark(); // ImGui style: or ImGui::StyleColorsLight();
|
|
|
|
// Setup Platform/Renderer backends
|
|
framework.imguiInitForVulkan(true); // ImGui_ImplSDL2_InitForVulkan | ImGui_ImplSDL3_InitForVulkan | ImGui_ImplGLFW_InitForVulkan
|
|
// callback ImGui active: you don't even need to handle mouse events
|
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
|
init_info.Instance = instance;
|
|
init_info.PhysicalDevice = physicalDevice;
|
|
init_info.Device = logicalDevice;
|
|
init_info.QueueFamily = graphQueueFamilyIdx;
|
|
init_info.Queue = queueGraphics;
|
|
init_info.PipelineCache = pipelineCache;
|
|
init_info.DescriptorPool = imguiPool;
|
|
init_info.RenderPass = renderPass; // Ignored if using dynamic rendering
|
|
init_info.UseDynamicRendering = false;
|
|
init_info.Subpass = 0;
|
|
init_info.MinImageCount = minImageCount;
|
|
init_info.ImageCount = swapChainImages.size();
|
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
|
init_info.Allocator = nullptr;
|
|
init_info.CheckVkResultFn = nullptr;
|
|
ImGui_ImplVulkan_Init(&init_info);
|
|
|
|
ImGui_ImplVulkan_SetMinImageCount(minImageCount);
|
|
}
|
|
|
|
void vkApp::imguiExit() {
|
|
ImGui_ImplVulkan_Shutdown();
|
|
framework.imguiShutdown(); // SDL: ImGui_ImplSDL2/3_Shutdown() | GLFW: ImGui_ImplGlfw_Shutdown()
|
|
ImGui::DestroyContext();
|
|
logicalDevice.destroyDescriptorPool( imguiPool ); // destroy ImGui DescriptorPool
|
|
|
|
}
|
|
|
|
void vkApp::onInit() // called from constructor @ startup
|
|
{
|
|
// specific App Vulkan Initializations
|
|
vtxCubeData.init(physicalDevice, logicalDevice, vk::BufferUsageFlagBits::eVertexBuffer);
|
|
vtxCubeData.copyToDevice(vtxCubeData.memoryUBO);
|
|
|
|
uboSceneMat.init(physicalDevice, logicalDevice, vk::BufferUsageFlagBits::eUniformBuffer);
|
|
uboSceneMat.update();
|
|
updateDescriptorsSets(uboSceneMat.uniformBuffer, uboSceneMat.bufferSize);
|
|
|
|
|
|
// If you need to flip "mouse movements"
|
|
// you can set they in the code or set as default values in vGizmo3D_config.h
|
|
// the values below are used only in Vulkan native space: look in the end of vkCube.h
|
|
|
|
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
|
|
|
|
APP_REVERSE_AXES
|
|
}
|
|
|
|
void vkApp::onExit() // called from destructor @ exit
|
|
{
|
|
// wait GPU to idle before destroy resources
|
|
logicalDevice.waitIdle();
|
|
|
|
// Vulkan App specific cleanup
|
|
vtxCubeData.destroy();
|
|
uboSceneMat.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;
|
|
} |