2018-11-10 00:51:15 +00:00
/*
* Vulkan Example - Multi threaded command buffer generation and rendering
*
* 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 <thread>
# include <random>
2019-01-16 00:31:19 +00:00
# include <fstream>
2018-11-10 00:51:15 +00:00
# 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 "threadpool.hpp"
# include "frustum.hpp"
# include "VulkanModel.hpp"
# define ENABLE_VALIDATION false
2019-01-16 00:31:19 +00:00
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
std : : wstring g_ScreenshotRequest ;
bool g_TakingScreenshot = false ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2019-03-24 21:31:54 +00:00
bool OnOptickStateChanged ( Optick : : State : : Type state )
2019-01-16 00:31:19 +00:00
{
2019-03-24 21:31:54 +00:00
if ( state = = Optick : : State : : STOP_CAPTURE )
2019-01-16 00:31:19 +00:00
{
wchar_t tempPath [ MAX_PATH ] = { 0 } ;
GetTempPath ( MAX_PATH , tempPath ) ;
std : : wstring fullPath ( tempPath ) ;
2019-03-24 21:31:54 +00:00
g_ScreenshotRequest = fullPath + L " OptickScreenshot.bmp " ;
2019-01-16 00:31:19 +00:00
g_TakingScreenshot = true ;
}
2019-03-24 21:31:54 +00:00
if ( state = = Optick : : State : : DUMP_CAPTURE )
2019-01-16 00:31:19 +00:00
{
if ( g_TakingScreenshot )
return false ;
// Attach screenshot
2019-03-24 21:31:54 +00:00
Optick : : AttachFile ( Optick : : File : : OPTICK_IMAGE , " Screenshot.bmp " , g_ScreenshotRequest . c_str ( ) ) ;
2019-01-16 00:31:19 +00:00
// Remove temp file
_wremove ( g_ScreenshotRequest . c_str ( ) ) ;
g_ScreenshotRequest . clear ( ) ;
}
return true ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2018-11-10 00:51:15 +00:00
class VulkanExample : public VulkanExampleBase
{
public :
bool displaySkybox = true ;
// Vertex layout for the models
vks : : VertexLayout vertexLayout = vks : : VertexLayout ( {
vks : : VERTEX_COMPONENT_POSITION ,
vks : : VERTEX_COMPONENT_NORMAL ,
vks : : VERTEX_COMPONENT_COLOR ,
} ) ;
struct {
vks : : Model ufo ;
vks : : Model skysphere ;
} models ;
// Shared matrices used for thread push constant blocks
struct {
glm : : mat4 projection ;
glm : : mat4 view ;
} matrices ;
struct {
VkPipeline phong ;
VkPipeline starsphere ;
} pipelines ;
VkPipelineLayout pipelineLayout ;
VkCommandBuffer primaryCommandBuffer ;
// Secondary scene command buffers used to store backgdrop and user interface
struct SecondaryCommandBuffers {
VkCommandBuffer background ;
VkCommandBuffer ui ;
} secondaryCommandBuffers ;
// Number of animated objects to be renderer
// by using threads and secondary command buffers
uint32_t numObjectsPerThread ;
// Multi threaded stuff
// Max. number of concurrent threads
uint32_t numThreads ;
// Use push constants to update shader
// parameters on a per-thread base
struct ThreadPushConstantBlock {
glm : : mat4 mvp ;
glm : : vec3 color ;
} ;
struct ObjectData {
glm : : mat4 model ;
glm : : vec3 pos ;
glm : : vec3 rotation ;
float rotationDir ;
float rotationSpeed ;
float scale ;
float deltaT ;
float stateT = 0 ;
bool visible = true ;
} ;
struct ThreadData {
VkCommandPool commandPool ;
// One command buffer per render object
std : : vector < VkCommandBuffer > commandBuffer ;
// One push constant block per render object
std : : vector < ThreadPushConstantBlock > pushConstBlock ;
// Per object information (position, rotation, etc.)
std : : vector < ObjectData > objectData ;
} ;
std : : vector < ThreadData > threadData ;
vks : : ThreadPool threadPool ;
// Fence to wait for all command buffers to finish before
// presenting to the swap chain
VkFence renderFence = { } ;
// Max. dimension of the ufo mesh for use as the sphere
// radius for frustum culling
float objectSphereDim ;
// View frustum for culling invisible objects
vks : : Frustum frustum ;
std : : default_random_engine rndEngine ;
VulkanExample ( ) : VulkanExampleBase ( ENABLE_VALIDATION )
{
zoom = - 32.5f ;
zoomSpeed = 2.5f ;
rotationSpeed = 0.5f ;
rotation = { 0.0f , 37.5f , 0.0f } ;
title = " Multi threaded command buffer " ;
settings . overlay = true ;
// Get number of max. concurrrent threads
2019-03-30 19:45:27 +00:00
numThreads = std : : thread : : hardware_concurrency ( ) / 2 ;
2018-11-10 00:51:15 +00:00
assert ( numThreads > 0 ) ;
# if defined(__ANDROID__)
LOGD ( " numThreads = %d " , numThreads ) ;
# else
std : : cout < < " numThreads = " < < numThreads < < std : : endl ;
# endif
threadPool . setThreadCount ( numThreads ) ;
numObjectsPerThread = 512 / numThreads ;
rndEngine . seed ( benchmark . active ? 0 : ( unsigned ) time ( nullptr ) ) ;
2019-01-16 00:31:19 +00:00
2019-03-24 21:31:54 +00:00
OPTICK_SET_STATE_CHANGED_CALLBACK ( OnOptickStateChanged ) ;
2018-11-10 00:51:15 +00:00
}
~ VulkanExample ( )
{
// Clean up used Vulkan resources
// Note : Inherited destructor cleans up resources stored in base class
vkDestroyPipeline ( device , pipelines . phong , nullptr ) ;
vkDestroyPipeline ( device , pipelines . starsphere , nullptr ) ;
vkDestroyPipelineLayout ( device , pipelineLayout , nullptr ) ;
models . ufo . destroy ( ) ;
models . skysphere . destroy ( ) ;
for ( auto & thread : threadData ) {
vkFreeCommandBuffers ( device , thread . commandPool , thread . commandBuffer . size ( ) , thread . commandBuffer . data ( ) ) ;
vkDestroyCommandPool ( device , thread . commandPool , nullptr ) ;
}
vkDestroyFence ( device , renderFence , nullptr ) ;
}
float rnd ( float range )
{
std : : uniform_real_distribution < float > rndDist ( 0.0f , range ) ;
return rndDist ( rndEngine ) ;
}
// Create all threads and initialize shader push constants
void prepareMultiThreadedRenderer ( )
{
// Since this demo updates the command buffers on each frame
// we don't use the per-framebuffer command buffers from the
// base class, and create a single primary command buffer instead
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
vks : : initializers : : commandBufferAllocateInfo (
cmdPool ,
VK_COMMAND_BUFFER_LEVEL_PRIMARY ,
1 ) ;
VK_CHECK_RESULT ( vkAllocateCommandBuffers ( device , & cmdBufAllocateInfo , & primaryCommandBuffer ) ) ;
// Create additional secondary CBs for background and ui
cmdBufAllocateInfo . level = VK_COMMAND_BUFFER_LEVEL_SECONDARY ;
VK_CHECK_RESULT ( vkAllocateCommandBuffers ( device , & cmdBufAllocateInfo , & secondaryCommandBuffers . background ) ) ;
VK_CHECK_RESULT ( vkAllocateCommandBuffers ( device , & cmdBufAllocateInfo , & secondaryCommandBuffers . ui ) ) ;
threadData . resize ( numThreads ) ;
float maxX = std : : floor ( std : : sqrt ( numThreads * numObjectsPerThread ) ) ;
uint32_t posX = 0 ;
uint32_t posZ = 0 ;
for ( uint32_t i = 0 ; i < numThreads ; i + + ) {
ThreadData * thread = & threadData [ i ] ;
// Create one command pool for each thread
VkCommandPoolCreateInfo cmdPoolInfo = vks : : initializers : : commandPoolCreateInfo ( ) ;
cmdPoolInfo . queueFamilyIndex = swapChain . queueNodeIndex ;
cmdPoolInfo . flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT ;
VK_CHECK_RESULT ( vkCreateCommandPool ( device , & cmdPoolInfo , nullptr , & thread - > commandPool ) ) ;
// One secondary command buffer per object that is updated by this thread
thread - > commandBuffer . resize ( numObjectsPerThread ) ;
// Generate secondary command buffers for each thread
VkCommandBufferAllocateInfo secondaryCmdBufAllocateInfo =
vks : : initializers : : commandBufferAllocateInfo (
thread - > commandPool ,
VK_COMMAND_BUFFER_LEVEL_SECONDARY ,
thread - > commandBuffer . size ( ) ) ;
VK_CHECK_RESULT ( vkAllocateCommandBuffers ( device , & secondaryCmdBufAllocateInfo , thread - > commandBuffer . data ( ) ) ) ;
thread - > pushConstBlock . resize ( numObjectsPerThread ) ;
thread - > objectData . resize ( numObjectsPerThread ) ;
for ( uint32_t j = 0 ; j < numObjectsPerThread ; j + + ) {
float theta = 2.0f * float ( M_PI ) * rnd ( 1.0f ) ;
float phi = acos ( 1.0f - 2.0f * rnd ( 1.0f ) ) ;
thread - > objectData [ j ] . pos = glm : : vec3 ( sin ( phi ) * cos ( theta ) , 0.0f , cos ( phi ) ) * 35.0f ;
thread - > objectData [ j ] . rotation = glm : : vec3 ( 0.0f , rnd ( 360.0f ) , 0.0f ) ;
thread - > objectData [ j ] . deltaT = rnd ( 1.0f ) ;
thread - > objectData [ j ] . rotationDir = ( rnd ( 100.0f ) < 50.0f ) ? 1.0f : - 1.0f ;
thread - > objectData [ j ] . rotationSpeed = ( 2.0f + rnd ( 4.0f ) ) * thread - > objectData [ j ] . rotationDir ;
thread - > objectData [ j ] . scale = 0.75f + rnd ( 0.5f ) ;
thread - > pushConstBlock [ j ] . color = glm : : vec3 ( rnd ( 1.0f ) , rnd ( 1.0f ) , rnd ( 1.0f ) ) ;
}
}
}
// Builds the secondary command buffer for each thread
void threadRenderCode ( uint32_t threadIndex , uint32_t cmdBufferIndex , VkCommandBufferInheritanceInfo inheritanceInfo )
{
2019-03-30 19:45:27 +00:00
OPTICK_CATEGORY ( " Render " , Optick : : Category : : Rendering ) ;
2018-11-27 20:55:40 +00:00
2018-11-10 00:51:15 +00:00
ThreadData * thread = & threadData [ threadIndex ] ;
ObjectData * objectData = & thread - > objectData [ cmdBufferIndex ] ;
// Check visibility against view frustum
objectData - > visible = frustum . checkSphere ( objectData - > pos , objectSphereDim * 0.5f ) ;
if ( ! objectData - > visible )
{
return ;
}
VkCommandBufferBeginInfo commandBufferBeginInfo = vks : : initializers : : commandBufferBeginInfo ( ) ;
commandBufferBeginInfo . flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT ;
commandBufferBeginInfo . pInheritanceInfo = & inheritanceInfo ;
VkCommandBuffer cmdBuffer = thread - > commandBuffer [ cmdBufferIndex ] ;
VK_CHECK_RESULT ( vkBeginCommandBuffer ( cmdBuffer , & commandBufferBeginInfo ) ) ;
2018-11-27 20:55:40 +00:00
{
2019-03-24 21:31:54 +00:00
OPTICK_GPU_CONTEXT ( cmdBuffer ) ;
OPTICK_GPU_EVENT ( " DrawUFO " ) ;
2018-11-27 20:55:40 +00:00
VkViewport viewport = vks : : initializers : : viewport ( ( float ) width , ( float ) height , 0.0f , 1.0f ) ;
vkCmdSetViewport ( cmdBuffer , 0 , 1 , & viewport ) ;
2018-11-10 00:51:15 +00:00
2018-11-27 20:55:40 +00:00
VkRect2D scissor = vks : : initializers : : rect2D ( width , height , 0 , 0 ) ;
vkCmdSetScissor ( cmdBuffer , 0 , 1 , & scissor ) ;
2018-11-10 00:51:15 +00:00
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdBindPipeline " ) ;
2019-03-30 19:45:27 +00:00
vkCmdBindPipeline ( cmdBuffer , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelines . phong ) ;
}
2018-11-10 00:51:15 +00:00
2018-11-27 20:55:40 +00:00
// Update
if ( ! paused ) {
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " UpdateUFO " ) ;
2018-11-27 20:55:40 +00:00
objectData - > rotation . y + = 2.5f * objectData - > rotationSpeed * frameTimer ;
if ( objectData - > rotation . y > 360.0f ) {
objectData - > rotation . y - = 360.0f ;
}
objectData - > deltaT + = 0.15f * frameTimer ;
if ( objectData - > deltaT > 1.0f )
objectData - > deltaT - = 1.0f ;
objectData - > pos . y = sin ( glm : : radians ( objectData - > deltaT * 360.0f ) ) * 2.5f ;
2018-11-10 00:51:15 +00:00
}
2018-11-27 20:55:40 +00:00
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " UpdateMVP " ) ;
2019-03-30 19:45:27 +00:00
objectData - > model = glm : : translate ( glm : : mat4 ( 1.0f ) , objectData - > pos ) ;
objectData - > model = glm : : rotate ( objectData - > model , - sinf ( glm : : radians ( objectData - > deltaT * 360.0f ) ) * 0.25f , glm : : vec3 ( objectData - > rotationDir , 0.0f , 0.0f ) ) ;
objectData - > model = glm : : rotate ( objectData - > model , glm : : radians ( objectData - > rotation . y ) , glm : : vec3 ( 0.0f , objectData - > rotationDir , 0.0f ) ) ;
objectData - > model = glm : : rotate ( objectData - > model , glm : : radians ( objectData - > deltaT * 360.0f ) , glm : : vec3 ( 0.0f , objectData - > rotationDir , 0.0f ) ) ;
objectData - > model = glm : : scale ( objectData - > model , glm : : vec3 ( objectData - > scale ) ) ;
2018-11-27 20:55:40 +00:00
2019-03-30 19:45:27 +00:00
thread - > pushConstBlock [ cmdBufferIndex ] . mvp = matrices . projection * matrices . view * objectData - > model ;
}
2018-11-27 20:55:40 +00:00
// Update shader push constant block
// Contains model view matrix
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdPushConstants " ) ;
2019-03-30 19:45:27 +00:00
vkCmdPushConstants (
cmdBuffer ,
pipelineLayout ,
VK_SHADER_STAGE_VERTEX_BIT ,
0 ,
sizeof ( ThreadPushConstantBlock ) ,
& thread - > pushConstBlock [ cmdBufferIndex ] ) ;
}
2018-11-27 20:55:40 +00:00
VkDeviceSize offsets [ 1 ] = { 0 } ;
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdBindVertexBuffers " ) ;
2019-03-30 19:45:27 +00:00
vkCmdBindVertexBuffers ( cmdBuffer , 0 , 1 , & models . ufo . vertices . buffer , offsets ) ;
}
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdBindIndexBuffer " ) ;
2019-03-30 19:45:27 +00:00
vkCmdBindIndexBuffer ( cmdBuffer , models . ufo . indices . buffer , 0 , VK_INDEX_TYPE_UINT32 ) ;
}
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdDrawIndexed " ) ;
2019-03-30 19:45:27 +00:00
vkCmdDrawIndexed ( cmdBuffer , models . ufo . indexCount , 1 , 0 , 0 , 0 ) ;
}
2018-11-10 00:51:15 +00:00
}
VK_CHECK_RESULT ( vkEndCommandBuffer ( cmdBuffer ) ) ;
}
void updateSecondaryCommandBuffers ( VkCommandBufferInheritanceInfo inheritanceInfo )
{
// Secondary command buffer for the sky sphere
VkCommandBufferBeginInfo commandBufferBeginInfo = vks : : initializers : : commandBufferBeginInfo ( ) ;
commandBufferBeginInfo . flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT ;
commandBufferBeginInfo . pInheritanceInfo = & inheritanceInfo ;
VkViewport viewport = vks : : initializers : : viewport ( ( float ) width , ( float ) height , 0.0f , 1.0f ) ;
VkRect2D scissor = vks : : initializers : : rect2D ( width , height , 0 , 0 ) ;
/*
Background
*/
VK_CHECK_RESULT ( vkBeginCommandBuffer ( secondaryCommandBuffers . background , & commandBufferBeginInfo ) ) ;
vkCmdSetViewport ( secondaryCommandBuffers . background , 0 , 1 , & viewport ) ;
vkCmdSetScissor ( secondaryCommandBuffers . background , 0 , 1 , & scissor ) ;
vkCmdBindPipeline ( secondaryCommandBuffers . background , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelines . starsphere ) ;
glm : : mat4 view = glm : : mat4 ( 1.0f ) ;
view = glm : : rotate ( view , glm : : radians ( rotation . x ) , glm : : vec3 ( 1.0f , 0.0f , 0.0f ) ) ;
view = glm : : rotate ( view , glm : : radians ( rotation . y ) , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
view = glm : : rotate ( view , glm : : radians ( rotation . z ) , glm : : vec3 ( 0.0f , 0.0f , 1.0f ) ) ;
glm : : mat4 mvp = matrices . projection * view ;
vkCmdPushConstants (
secondaryCommandBuffers . background ,
pipelineLayout ,
VK_SHADER_STAGE_VERTEX_BIT ,
0 ,
sizeof ( mvp ) ,
& mvp ) ;
VkDeviceSize offsets [ 1 ] = { 0 } ;
vkCmdBindVertexBuffers ( secondaryCommandBuffers . background , 0 , 1 , & models . skysphere . vertices . buffer , offsets ) ;
vkCmdBindIndexBuffer ( secondaryCommandBuffers . background , models . skysphere . indices . buffer , 0 , VK_INDEX_TYPE_UINT32 ) ;
vkCmdDrawIndexed ( secondaryCommandBuffers . background , models . skysphere . indexCount , 1 , 0 , 0 , 0 ) ;
VK_CHECK_RESULT ( vkEndCommandBuffer ( secondaryCommandBuffers . background ) ) ;
/*
User interface
With VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS, the primary command buffer's content has to be defined
by secondary command buffers, which also applies to the UI overlay command buffer
*/
VK_CHECK_RESULT ( vkBeginCommandBuffer ( secondaryCommandBuffers . ui , & commandBufferBeginInfo ) ) ;
vkCmdSetViewport ( secondaryCommandBuffers . ui , 0 , 1 , & viewport ) ;
vkCmdSetScissor ( secondaryCommandBuffers . ui , 0 , 1 , & scissor ) ;
vkCmdBindPipeline ( secondaryCommandBuffers . ui , VK_PIPELINE_BIND_POINT_GRAPHICS , pipelines . starsphere ) ;
if ( settings . overlay ) {
drawUI ( secondaryCommandBuffers . ui ) ;
}
VK_CHECK_RESULT ( vkEndCommandBuffer ( secondaryCommandBuffers . ui ) ) ;
}
// Updates the secondary command buffers using a thread pool
// and puts them into the primary command buffer that's
// lat submitted to the queue for rendering
void updateCommandBuffers ( VkFramebuffer frameBuffer )
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( ) ;
2018-11-10 00:51:15 +00:00
// Contains the list of secondary command buffers to be submitted
std : : vector < VkCommandBuffer > commandBuffers ;
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 ;
renderPassBeginInfo . framebuffer = frameBuffer ;
// Set target frame buffer
VK_CHECK_RESULT ( vkBeginCommandBuffer ( primaryCommandBuffer , & cmdBufInfo ) ) ;
// The primary command buffer does not contain any rendering commands
// These are stored (and retrieved) from the secondary command buffers
vkCmdBeginRenderPass ( primaryCommandBuffer , & renderPassBeginInfo , VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS ) ;
// Inheritance info for the secondary command buffers
VkCommandBufferInheritanceInfo inheritanceInfo = vks : : initializers : : commandBufferInheritanceInfo ( ) ;
inheritanceInfo . renderPass = renderPass ;
// Secondary command buffer also use the currently active framebuffer
inheritanceInfo . framebuffer = frameBuffer ;
// Update secondary sene command buffers
updateSecondaryCommandBuffers ( inheritanceInfo ) ;
if ( displaySkybox ) {
commandBuffers . push_back ( secondaryCommandBuffers . background ) ;
}
// Add a job to the thread's queue for each object to be rendered
for ( uint32_t t = 0 ; t < numThreads ; t + + )
{
for ( uint32_t i = 0 ; i < numObjectsPerThread ; i + + )
{
threadPool . threads [ t ] - > addJob ( [ = ] { threadRenderCode ( t , i , inheritanceInfo ) ; } ) ;
}
}
threadPool . wait ( ) ;
// Only submit if object is within the current view frustum
for ( uint32_t t = 0 ; t < numThreads ; t + + )
{
for ( uint32_t i = 0 ; i < numObjectsPerThread ; i + + )
{
if ( threadData [ t ] . objectData [ i ] . visible )
{
commandBuffers . push_back ( threadData [ t ] . commandBuffer [ i ] ) ;
}
}
}
// Render ui last
if ( UIOverlay . visible ) {
commandBuffers . push_back ( secondaryCommandBuffers . ui ) ;
}
// Execute render commands from the secondary command buffer
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdExecuteCommands " ) ;
2019-03-30 19:45:27 +00:00
vkCmdExecuteCommands ( primaryCommandBuffer , commandBuffers . size ( ) , commandBuffers . data ( ) ) ;
}
2018-11-10 00:51:15 +00:00
2019-03-30 19:45:27 +00:00
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkCmdEndRenderPass " ) ;
2019-03-30 19:45:27 +00:00
vkCmdEndRenderPass ( primaryCommandBuffer ) ;
}
2018-11-10 00:51:15 +00:00
VK_CHECK_RESULT ( vkEndCommandBuffer ( primaryCommandBuffer ) ) ;
}
void loadAssets ( )
{
models . ufo . loadFromFile ( getAssetPath ( ) + " models/retroufo_red.dae " , vertexLayout , 0.12f , vulkanDevice , queue ) ;
models . skysphere . loadFromFile ( getAssetPath ( ) + " models/sphere.obj " , vertexLayout , 1.0f , vulkanDevice , queue ) ;
objectSphereDim = std : : max ( std : : max ( models . ufo . dim . size . x , models . ufo . dim . size . y ) , models . ufo . dim . size . z ) ;
}
void setupPipelineLayout ( )
{
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo =
vks : : initializers : : pipelineLayoutCreateInfo ( nullptr , 0 ) ;
// Push constants for model matrices
VkPushConstantRange pushConstantRange =
vks : : initializers : : pushConstantRange (
VK_SHADER_STAGE_VERTEX_BIT ,
sizeof ( ThreadPushConstantBlock ) ,
0 ) ;
// Push constant ranges are part of the pipeline layout
pPipelineLayoutCreateInfo . pushConstantRangeCount = 1 ;
pPipelineLayoutCreateInfo . pPushConstantRanges = & pushConstantRange ;
VK_CHECK_RESULT ( vkCreatePipelineLayout ( device , & pPipelineLayoutCreateInfo , nullptr , & pipelineLayout ) ) ;
}
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 ) ;
VkPipelineMultisampleStateCreateInfo multisampleState =
vks : : initializers : : pipelineMultisampleStateCreateInfo (
VK_SAMPLE_COUNT_1_BIT ,
0 ) ;
std : : vector < VkDynamicState > dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT ,
VK_DYNAMIC_STATE_SCISSOR
} ;
VkPipelineDynamicStateCreateInfo dynamicState =
vks : : initializers : : pipelineDynamicStateCreateInfo (
dynamicStateEnables . data ( ) ,
dynamicStateEnables . size ( ) ,
0 ) ;
std : : array < VkPipelineShaderStageCreateInfo , 2 > shaderStages ;
VkGraphicsPipelineCreateInfo pipelineCI = vks : : initializers : : pipelineCreateInfo ( pipelineLayout , renderPass , 0 ) ;
pipelineCI . pInputAssemblyState = & inputAssemblyState ;
pipelineCI . pRasterizationState = & rasterizationState ;
pipelineCI . pColorBlendState = & colorBlendState ;
pipelineCI . pMultisampleState = & multisampleState ;
pipelineCI . pViewportState = & viewportState ;
pipelineCI . pDepthStencilState = & depthStencilState ;
pipelineCI . pDynamicState = & dynamicState ;
pipelineCI . stageCount = shaderStages . size ( ) ;
pipelineCI . pStages = shaderStages . data ( ) ;
// 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_R32G32B32_SFLOAT , sizeof ( float ) * 6 ) , // Location 2: Color
} ;
VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks : : initializers : : pipelineVertexInputStateCreateInfo ( ) ;
vertexInputStateCI . vertexBindingDescriptionCount = static_cast < uint32_t > ( vertexInputBindings . size ( ) ) ;
vertexInputStateCI . pVertexBindingDescriptions = vertexInputBindings . data ( ) ;
vertexInputStateCI . vertexAttributeDescriptionCount = static_cast < uint32_t > ( vertexInputAttributes . size ( ) ) ;
vertexInputStateCI . pVertexAttributeDescriptions = vertexInputAttributes . data ( ) ;
pipelineCI . pVertexInputState = & vertexInputStateCI ;
// Object rendering pipeline
shaderStages [ 0 ] = loadShader ( getAssetPath ( ) + " shaders/multithreading/phong.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ;
shaderStages [ 1 ] = loadShader ( getAssetPath ( ) + " shaders/multithreading/phong.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT ) ;
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device , pipelineCache , 1 , & pipelineCI , nullptr , & pipelines . phong ) ) ;
// Star sphere rendering pipeline
rasterizationState . cullMode = VK_CULL_MODE_FRONT_BIT ;
depthStencilState . depthWriteEnable = VK_FALSE ;
shaderStages [ 0 ] = loadShader ( getAssetPath ( ) + " shaders/multithreading/starsphere.vert.spv " , VK_SHADER_STAGE_VERTEX_BIT ) ;
shaderStages [ 1 ] = loadShader ( getAssetPath ( ) + " shaders/multithreading/starsphere.frag.spv " , VK_SHADER_STAGE_FRAGMENT_BIT ) ;
VK_CHECK_RESULT ( vkCreateGraphicsPipelines ( device , pipelineCache , 1 , & pipelineCI , nullptr , & pipelines . starsphere ) ) ;
}
void updateMatrices ( )
{
matrices . projection = glm : : perspective ( glm : : radians ( 60.0f ) , ( float ) width / ( float ) height , 0.1f , 256.0f ) ;
matrices . view = glm : : translate ( glm : : mat4 ( 1.0f ) , glm : : vec3 ( 0.0f , 0.0f , zoom ) ) ;
matrices . view = glm : : rotate ( matrices . view , glm : : radians ( rotation . x ) , glm : : vec3 ( 1.0f , 0.0f , 0.0f ) ) ;
matrices . view = glm : : rotate ( matrices . view , glm : : radians ( rotation . y ) , glm : : vec3 ( 0.0f , 1.0f , 0.0f ) ) ;
matrices . view = glm : : rotate ( matrices . view , glm : : radians ( rotation . z ) , glm : : vec3 ( 0.0f , 0.0f , 1.0f ) ) ;
frustum . update ( matrices . projection * matrices . view ) ;
}
void draw ( )
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( ) ;
2019-03-30 19:45:27 +00:00
2018-11-10 00:51:15 +00:00
// Wait for fence to signal that all command buffers are ready
VkResult fenceRes ;
do {
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( " vkWaitForFences " ) ;
2018-11-10 00:51:15 +00:00
fenceRes = vkWaitForFences ( device , 1 , & renderFence , VK_TRUE , 100000000 ) ;
} while ( fenceRes = = VK_TIMEOUT ) ;
VK_CHECK_RESULT ( fenceRes ) ;
vkResetFences ( device , 1 , & renderFence ) ;
VulkanExampleBase : : prepareFrame ( ) ;
updateCommandBuffers ( frameBuffers [ currentBuffer ] ) ;
submitInfo . commandBufferCount = 1 ;
submitInfo . pCommandBuffers = & primaryCommandBuffer ;
VK_CHECK_RESULT ( vkQueueSubmit ( queue , 1 , & submitInfo , renderFence ) ) ;
2019-01-16 00:31:19 +00:00
if ( g_TakingScreenshot ) {
saveScreenshot ( g_ScreenshotRequest . c_str ( ) ) ;
g_TakingScreenshot = false ;
}
2018-11-10 00:51:15 +00:00
VulkanExampleBase : : submitFrame ( ) ;
}
void prepare ( )
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( ) ;
2018-11-10 00:51:15 +00:00
VulkanExampleBase : : prepare ( ) ;
// Create a fence for synchronization
VkFenceCreateInfo fenceCreateInfo = vks : : initializers : : fenceCreateInfo ( VK_FENCE_CREATE_SIGNALED_BIT ) ;
vkCreateFence ( device , & fenceCreateInfo , nullptr , & renderFence ) ;
loadAssets ( ) ;
setupPipelineLayout ( ) ;
preparePipelines ( ) ;
prepareMultiThreadedRenderer ( ) ;
updateMatrices ( ) ;
prepared = true ;
}
virtual void render ( )
{
2019-03-31 23:53:36 +01:00
OPTICK_EVENT ( ) ;
2018-11-10 00:51:15 +00:00
if ( ! prepared )
return ;
draw ( ) ;
}
virtual void viewChanged ( )
{
updateMatrices ( ) ;
}
virtual void OnUpdateUIOverlay ( vks : : UIOverlay * overlay )
{
if ( overlay - > header ( " Statistics " ) ) {
overlay - > text ( " Active threads: %d " , numThreads ) ;
}
if ( overlay - > header ( " Settings " ) ) {
overlay - > checkBox ( " Skybox " , & displaySkybox ) ;
}
}
2019-01-16 00:31:19 +00:00
// szPathName : Specifies the pathname -> the file path to save the image
// lpBits : Specifies the bitmap bits -> the buffer (content of the) image
// w : Specifies the image width
// h : Specifies the image height
bool saveBitmap ( std : : ofstream & stream , void * lpBits , int w , int h ) {
BITMAPINFOHEADER BMIH ; // BMP header
BMIH . biSize = sizeof ( BITMAPINFOHEADER ) ;
BMIH . biSizeImage = w * h * 3 ;
// Create the bitmap for this OpenGL context
BMIH . biSize = sizeof ( BITMAPINFOHEADER ) ;
BMIH . biWidth = w ;
BMIH . biHeight = h ;
BMIH . biPlanes = 1 ;
BMIH . biBitCount = 24 ;
BMIH . biCompression = BI_RGB ;
BMIH . biSizeImage = w * h * 3 ;
BITMAPFILEHEADER bmfh ; // Other BMP header
int nBitsOffset = sizeof ( BITMAPFILEHEADER ) + BMIH . biSize ;
LONG lImageSize = BMIH . biSizeImage ;
LONG lFileSize = nBitsOffset + lImageSize ;
bmfh . bfType = ' B ' + ( ' M ' < < 8 ) ;
bmfh . bfOffBits = nBitsOffset ;
bmfh . bfSize = lFileSize ;
bmfh . bfReserved1 = bmfh . bfReserved2 = 0 ;
// Write the bitmap file header // Saving the first header to file
stream . write ( ( const char * ) & bmfh , sizeof ( BITMAPFILEHEADER ) ) ;
// And then the bitmap info header // Saving the second header to file
stream . write ( ( const char * ) & BMIH , sizeof ( BITMAPINFOHEADER ) ) ;
// Finally, write the image data itself
//-- the data represents our drawing // Saving the file content in lpBits to file
stream . write ( ( const char * ) lpBits , lImageSize ) ;
stream . flush ( ) ;
return true ;
}
// Take a screenshot from the current swapchain image
// This is done using a blit from the swapchain image to a linear image whose memory content is then saved as a ppm image
// Getting the image date directly from a swapchain image wouldn't work as they're usually stored in an implementation dependant optimal tiling format
// Note: This requires the swapchain images to be created with the VK_IMAGE_USAGE_TRANSFER_SRC_BIT flag (see VulkanSwapChain::create)
void saveScreenshot ( const wchar_t * filename )
{
bool supportsBlit = true ;
// Check blit support for source and destination
VkFormatProperties formatProps ;
// Check if the device supports blitting from optimal images (the swapchain images are in optimal format)
vkGetPhysicalDeviceFormatProperties ( physicalDevice , swapChain . colorFormat , & formatProps ) ;
if ( ! ( formatProps . optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT ) ) {
std : : cerr < < " Device does not support blitting from optimal tiled images, using copy instead of blit! " < < std : : endl ;
supportsBlit = false ;
}
// Check if the device supports blitting to linear images
vkGetPhysicalDeviceFormatProperties ( physicalDevice , VK_FORMAT_R8G8B8A8_UNORM , & formatProps ) ;
if ( ! ( formatProps . linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT ) ) {
std : : cerr < < " Device does not support blitting to linear tiled images, using copy instead of blit! " < < std : : endl ;
supportsBlit = false ;
}
// Source for the copy is the last rendered swapchain image
VkImage srcImage = swapChain . images [ currentBuffer ] ;
// Create the linear tiled destination image to copy to and to read the memory from
VkImageCreateInfo imageCreateCI ( vks : : initializers : : imageCreateInfo ( ) ) ;
imageCreateCI . imageType = VK_IMAGE_TYPE_2D ;
// Note that vkCmdBlitImage (if supported) will also do format conversions if the swapchain color format would differ
imageCreateCI . format = VK_FORMAT_R8G8B8A8_UNORM ;
imageCreateCI . extent . width = width ;
imageCreateCI . extent . height = height ;
imageCreateCI . extent . depth = 1 ;
imageCreateCI . arrayLayers = 1 ;
imageCreateCI . mipLevels = 1 ;
imageCreateCI . initialLayout = VK_IMAGE_LAYOUT_UNDEFINED ;
imageCreateCI . samples = VK_SAMPLE_COUNT_1_BIT ;
imageCreateCI . tiling = VK_IMAGE_TILING_LINEAR ;
imageCreateCI . usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT ;
// Create the image
VkImage dstImage ;
VK_CHECK_RESULT ( vkCreateImage ( device , & imageCreateCI , nullptr , & dstImage ) ) ;
// Create memory to back up the image
VkMemoryRequirements memRequirements ;
VkMemoryAllocateInfo memAllocInfo ( vks : : initializers : : memoryAllocateInfo ( ) ) ;
VkDeviceMemory dstImageMemory ;
vkGetImageMemoryRequirements ( device , dstImage , & memRequirements ) ;
memAllocInfo . allocationSize = memRequirements . size ;
// Memory must be host visible to copy from
memAllocInfo . memoryTypeIndex = vulkanDevice - > getMemoryType ( memRequirements . memoryTypeBits , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ) ;
VK_CHECK_RESULT ( vkAllocateMemory ( device , & memAllocInfo , nullptr , & dstImageMemory ) ) ;
VK_CHECK_RESULT ( vkBindImageMemory ( device , dstImage , dstImageMemory , 0 ) ) ;
// Do the actual blit from the swapchain image to our host visible destination image
VkCommandBuffer copyCmd = vulkanDevice - > createCommandBuffer ( VK_COMMAND_BUFFER_LEVEL_PRIMARY , true ) ;
// Transition destination image to transfer destination layout
vks : : tools : : insertImageMemoryBarrier (
copyCmd ,
dstImage ,
0 ,
VK_ACCESS_TRANSFER_WRITE_BIT ,
VK_IMAGE_LAYOUT_UNDEFINED ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
// Transition swapchain image from present to transfer source layout
vks : : tools : : insertImageMemoryBarrier (
copyCmd ,
srcImage ,
VK_ACCESS_MEMORY_READ_BIT ,
VK_ACCESS_TRANSFER_READ_BIT ,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
// If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB)
if ( supportsBlit )
{
// Define the region to blit (we will blit the whole swapchain image)
VkOffset3D blitSize ;
blitSize . x = width ;
blitSize . y = height ;
blitSize . z = 1 ;
VkImageBlit imageBlitRegion { } ;
imageBlitRegion . srcSubresource . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
imageBlitRegion . srcSubresource . layerCount = 1 ;
imageBlitRegion . srcOffsets [ 1 ] = blitSize ;
imageBlitRegion . dstSubresource . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
imageBlitRegion . dstSubresource . layerCount = 1 ;
imageBlitRegion . dstOffsets [ 1 ] = blitSize ;
// Issue the blit command
vkCmdBlitImage (
copyCmd ,
srcImage , VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
dstImage , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
1 ,
& imageBlitRegion ,
VK_FILTER_NEAREST ) ;
}
else
{
// Otherwise use image copy (requires us to manually flip components)
VkImageCopy imageCopyRegion { } ;
imageCopyRegion . srcSubresource . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
imageCopyRegion . srcSubresource . layerCount = 1 ;
imageCopyRegion . dstSubresource . aspectMask = VK_IMAGE_ASPECT_COLOR_BIT ;
imageCopyRegion . dstSubresource . layerCount = 1 ;
imageCopyRegion . extent . width = width ;
imageCopyRegion . extent . height = height ;
imageCopyRegion . extent . depth = 1 ;
// Issue the copy command
vkCmdCopyImage (
copyCmd ,
srcImage , VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
dstImage , VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
1 ,
& imageCopyRegion ) ;
}
// Transition destination image to general layout, which is the required layout for mapping the image memory later on
vks : : tools : : insertImageMemoryBarrier (
copyCmd ,
dstImage ,
VK_ACCESS_TRANSFER_WRITE_BIT ,
VK_ACCESS_MEMORY_READ_BIT ,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ,
VK_IMAGE_LAYOUT_GENERAL ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
// Transition back the swap chain image after the blit is done
vks : : tools : : insertImageMemoryBarrier (
copyCmd ,
srcImage ,
VK_ACCESS_TRANSFER_READ_BIT ,
VK_ACCESS_MEMORY_READ_BIT ,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VK_PIPELINE_STAGE_TRANSFER_BIT ,
VkImageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 1 , 0 , 1 } ) ;
vulkanDevice - > flushCommandBuffer ( copyCmd , queue ) ;
// Get layout of the image (including row pitch)
VkImageSubresource subResource { VK_IMAGE_ASPECT_COLOR_BIT , 0 , 0 } ;
VkSubresourceLayout subResourceLayout ;
vkGetImageSubresourceLayout ( device , dstImage , & subResource , & subResourceLayout ) ;
// Map image memory so we can start copying from it
const char * data ;
vkMapMemory ( device , dstImageMemory , 0 , VK_WHOLE_SIZE , 0 , ( void * * ) & data ) ;
data + = subResourceLayout . offset ;
// If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components
bool isBGR = false ;
// Check if source is BGR
// Note: Not complete, only contains most common and basic BGR surface formats for demonstation purposes
if ( ! supportsBlit )
{
std : : vector < VkFormat > formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB , VK_FORMAT_B8G8R8A8_UNORM , VK_FORMAT_B8G8R8A8_SNORM } ;
isBGR = ( std : : find ( formatsBGR . begin ( ) , formatsBGR . end ( ) , swapChain . colorFormat ) ! = formatsBGR . end ( ) ) ;
}
std : : vector < char > buffer ;
buffer . resize ( width * height * 3 ) ;
const char * pImg = data ;
for ( uint32_t y = 0 ; y < height ; y + + )
{
const char * row = ( const char * ) pImg ;
for ( uint32_t x = 0 ; x < width ; x + + )
{
char * out = & buffer [ ( ( height - 1 - y ) * width + x ) * 3 ] ;
out [ 0 ] = row [ isBGR ? 0 : 2 ] ;
out [ 1 ] = row [ 1 ] ;
out [ 2 ] = row [ isBGR ? 2 : 0 ] ;
row + = 4 ;
}
pImg + = subResourceLayout . rowPitch ;
}
std : : ofstream file ( filename , std : : ios : : out | std : : ios : : binary ) ;
saveBitmap ( file , buffer . data ( ) , width , height ) ;
std : : cout < < " Screenshot saved to disk " < < std : : endl ;
// Clean up resources
vkUnmapMemory ( device , dstImageMemory ) ;
vkFreeMemory ( device , dstImageMemory , nullptr ) ;
vkDestroyImage ( device , dstImage , nullptr ) ;
}
2018-11-10 00:51:15 +00:00
} ;
VULKAN_EXAMPLE_MAIN ( )