Vulkan Renderer

Project Description:

This was a project developed to help me understand how to integrate the Vulkan API into my personal C++ engine. In turn, it helped me understand advanced concepts like job systems, memory management and synchronization. My end product was a model viewer with a vulkan wrapper that is similar to our previous OpenGL renderer.

Development Specifications:

  • Engine: Personal Engine (C++)
  • Development Time: 2 Months
  • Rendering API: Vulkan / GLSL

Summary of Work Done

  • Created an OpenGL style wrapper for Vulkan.
  • Integrated “shaderc”, a shader compiler developed by Google.
  • Integrated “spirv-cross”, a shader reflection engine by Khronos Group.
  • Current supported features:
    • Mesh drawing with vertex and index buffers.
    • Materials for specification of textures, uniform buffers, shader.
    • GLSL shaders that are compiled run-time.
    • Automatic shader reflection to identify uniform slots and create descriptor sets from that.

Class Architecture

Legend:

  • Names starting with ‘VK’ are my wrapper classes. 
  • Names starting with ‘Vk” are Vulkan’s internal structs.
  • The diagram includes only the dependency on Vulkan related classes. This does not include the model viewer code. 

What I Started With

Initially, to first understand what goes into setting up Vulkan and getting that first colored-triangle on the screen, I made these immediate functions that would first get a triangle on the screen. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//-----------------------------------------------------------------------------------------------
class VkRenderer
{
public:
	//-----------------------------------------------------------------------------------------------
	// Constructors/Destructors
	VkRenderer( const char* appName );
	~VkRenderer();
	
	//-----------------------------------------------------------------------------------------------
	// Accessors/Mutators
	
	//-----------------------------------------------------------------------------------------------
	// Vulkan Initialization Operations
private:
			bool				CheckValidationLayerSupport();
			bool				CheckExtensionsSupport();
			void				SetupDebugCallback();
			void				InitializeVulkanInstance( const char* appName );
			bool				CheckDeviceExtensionsSupport( VkPhysicalDevice device );
			QueueFamilyIndices		GetQueueFamilyIndices( VkPhysicalDevice device );
			SwapChainDetails		GetSwapChainDetails( VkPhysicalDevice device );
			bool				IsDeviceSuitable( VkPhysicalDevice device );
			void				CreateSurface();
			void				PickPhysicalDevice();
			void				CreateLogicalDevice();
			VkSurfaceFormatKHR		PickSurfaceFormat( const std::vector<VkSurfaceFormatKHR>& formats );
			VkPresentModeKHR		PickPresentMode( const std::vector<VkPresentModeKHR>& presentModes );
			VkExtent2D			PickSwapExtent( const VkSurfaceCapabilitiesKHR& capabilities );
			void				CreateSwapChain();
			void				CreateImageViews();
			VkShaderModule			CreateShaderModule( void* byteCode, size_t size );
			void				CreateRenderPass();
			void				CreateGraphicsPipeline();
			void				CreateFrameBuffers();
			void				CreateCommandPool();
			void				CreateCommandBuffers();
			void				CreateSemaphores();
			

	//-----------------------------------------------------------------------------------------------
	// Methods
public:
			void				PostStartup();
			void				BeginFrame();
			void				DrawFrame();
			void				EndFrame();
	
	//-----------------------------------------------------------------------------------------------
	// Static methods
	static		VkRenderer*			CreateInstance( const char* appName );
	static		void				DestroyInstance();
	static		VkRenderer*			GetInstance();

	// Debug callback
	static	VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugReportFlagsEXT flags,
		VkDebugReportObjectTypeEXT objType,
		uint64_t obj,
		size_t location,
		int32_t code,
		const char* layerPrefix,
		const char* msg,
		void* userData);

	//-----------------------------------------------------------------------------------------------
	// Members

private:
			VkDebugReportCallbackEXT	m_debugCallback;
			VkInstance			m_vkInstance;
			VkPhysicalDevice		m_physicalDevice = VK_NULL_HANDLE;
			VkDevice			m_logicalDevice = VK_NULL_HANDLE;
			VkQueue				m_graphicsQueue;
			VkSurfaceKHR			m_surface;
			VkQueue				m_presentQueue;
			VkSwapchainKHR			m_swapChain;
			VkExtent2D			m_swapChainExtent;
			VkFormat			m_swapChainImageFormat;
			std::vector<VkImage>	        m_swapChainImages;
			std::vector<VkImageView>	m_swapChainImageViews;
			VkRenderPass		        m_renderPass;
			VkPipelineLayout	        m_pipelineLayout;
			VkPipeline		        m_graphicsPipeline;
			std::vector<VkFramebuffer>      m_framebuffers;
			VkCommandPool		        m_commandPool;
			std::vector<VkCommandBuffer>	m_commandBuffers;
			VkSemaphore			m_imageAvailableSemaphore;
			VkSemaphore			m_renderFinishedSemaphore;
};

//-----------------------------------------------------------------------------------------------
// Standalone functions
void	VkRenderStartup();
void	VkShutdown();

What It Became

Once I started understanding the basic pipelines and the inner workings, I began to wrap Vulkan and finally ended with the result below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//-----------------------------------------------------------------------------------------------
class VKRenderer
{
public:
	//-----------------------------------------------------------------------------------------------
	// Constructors/Destructors
	VKRenderer( const char* appName );
	~VKRenderer();
	
	//-----------------------------------------------------------------------------------------------
	// Accessors/Mutators
			VkDevice			GetLogicalDevice() const { return m_logicalDevice; }
			VkPhysicalDevice		GetPhysicalDevice() const { return m_physicalDevice; }
			VKTexture*			GetDefaultColorTarget() const { return m_defaultColorTarget; }
			VKTexture*			GetDefaultDepthTarget() const { return m_defaultDepthTarget; }
	
	//-----------------------------------------------------------------------------------------------
	// Vulkan Initialization Operations
private:
			bool				CheckValidationLayerSupport();
			bool				CheckExtensionsSupport();
			void				SetupDebugCallback();
			void				InitializeVulkanInstance( const char* appName );
			bool				CheckDeviceExtensionsSupport( VkPhysicalDevice device );
			QueueFamilyIndices		GetQueueFamilyIndices( VkPhysicalDevice device );
			SwapChainDetails		GetSwapChainDetails( VkPhysicalDevice device );
			bool				IsDeviceSuitable( VkPhysicalDevice device );
			void				CreateSurface();
			void				PickPhysicalDevice();
			void				CreateLogicalDevice();
			VkSurfaceFormatKHR		PickSurfaceFormat( const std::vector<VkSurfaceFormatKHR>& formats );
			VkPresentModeKHR		PickPresentMode( const std::vector<VkPresentModeKHR>& presentModes );
			VkExtent2D			PickSwapExtent( const VkSurfaceCapabilitiesKHR& capabilities );
			void				CreateSwapChain();
			void				CreateImageViews();
			void				CreateCommandPool();
			void				CreateVertexBuffer();
			void				CreateIndexBuffer();
			void				CreateSyncStuff();
			void				CleanupSwapchain();
			void				RecreateSwapchain();

	//-----------------------------------------------------------------------------------------------
	// Methods
public:
			void				PostStartup();
			void				BeginFrame();
			void				EndFrame();

	//-----------------------------------------------------------------------------------------------
	// Draw commands
			void				DrawMeshImmediate(const Vertex_3DPCU* vertices, int numVerts, DrawPrimitiveType mode, const Matrix44& modelMatrix);
			void				DrawMesh( const VKMesh& mesh, const Matrix44& modelMatrix = Matrix44::IDENTITY );

	//-----------------------------------------------------------------------------------------------
	// Mesh functions
			VKMesh*				CreateOrGetMesh( const std::string& path );
			void				InitializeDefaultMeshes();
	//-----------------------------------------------------------------------------------------------
	// Command Buffer ops
			VkCommandBuffer			BeginTemporaryCommandBuffer(); // Begins a command buffer for temp usage and returns the handle
			void				EndTemporaryCommandBuffer( VkCommandBuffer tempBuffer ); 

	//-----------------------------------------------------------------------------------------------
	// Texture Ops
			void				CreateAndGetImage( VkImage* out_image, VkDeviceMemory* out_devMem, uint32_t width, uint32_t height, VkFormat format, VkImageUsageFlags usage, VkImageTiling tiling, VkMemoryPropertyFlags props );
			VkImageView			CreateAndGetImageView( VkImage image, VkFormat format, VkImageAspectFlags aspectFlags );
			void				TransitionImageLayout( VkImage image, VkImageAspectFlags aspectFlags, VkImageLayout oldLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStage, VkPipelineStageFlags dstStage, VkAccessFlags srcMask, VkAccessFlags dstMask );
			void				CopyBufferToImage( VkBuffer buffer, VkImage image, uint32_t width, uint32_t height );
			void				CopyImages( VkImage dst, VkImageLayout dstLayout, VkImage src, VkImageLayout srcLayout, VkImageCopy copyInfo, VkSemaphore* waitSemaphore = nullptr, uint32_t waitCount = 0, VkSemaphore* signalSemaphores = nullptr, uint32_t signalCount = 0 );

	//-----------------------------------------------------------------------------------------------
	// Texture Helpers
			VKTexture*			GetDefaultTexture() const { return m_defaultTexture; }
			void				SetTexture( VKTexture* texture );
			void				SetTexture( unsigned int index, VKTexture* texture, VKTexSampler* sampler = nullptr );
			void				BindTexture2D( const VKTexture* texture );
			void				BindTexture2D( unsigned int index, const VKTexture* texture );
			VKTexture*			CreateOrGetTexture(const std::string& path, bool genMipmaps = true);
			VKTexture*			CreateOrGetTexture(const Image& image, bool genMipmaps = true);
			bool				IsTextureLoaded(const std::string& path) const;
			void				SetDefaultTexture();
			void				CopyTexture2D( VKTexture* dst, VKTexture* src, VkSemaphore* waitSemaphore = nullptr, uint32_t waitCount = 0, VkSemaphore* signalSemaphores = nullptr, uint32_t signalCount = 0 );
			VKTexture*			CreateRenderTarget(unsigned int width, unsigned int height, eTextureFormat fmt = TEXTURE_FORMAT_RGBA8);
			VKTexture*			CreateDepthStencilTarget( unsigned int width, unsigned int height );
			VKTexture*			CreateColorTarget( unsigned int width, unsigned int height );

	//-----------------------------------------------------------------------------------------------
	// Shader functions
			void				SetShader( VKShader* shader = nullptr );
			VKShaderProgram*		CreateOrGetShaderProgram(const std::string& path, const char* defines = nullptr);
			void				UseShaderProgram(const VKShaderProgram* shaderProgram);
			void				SetDefaultShader();
			void				BindMeshToProgram( const VKMesh* mesh );
			void				BindRenderState( RenderState state );
			void				AlphaBlendFunction(BlendFactor sfactor, BlendFactor dfactor );
			void				ColorBlendFunction(BlendFactor sfactor, BlendFactor dfactor);
			void				SetDepthTestMode(DepthTestOp mode, bool flag);
			void				DisableDepth();
			void				SetFillMode(FillMode fillMode);
			void				SetCullMode(CullMode cullMode);

	//-----------------------------------------------------------------------------------------------
	// Material Functions
			void				BindMaterial( const VKMaterial* material = nullptr );
			void				SetMaterial( const VKMaterial* material = nullptr );
			VKMaterial*			CreateOrGetMaterial( const std::string& path );
			void				SetDefaultMaterial();
			void				ResetDefaultMaterial();

	//-----------------------------------------------------------------------------------------------
	// Setting uniforms on shaders
			void				BindUBO( int bindPoint, const VKUniformBuffer* ubo );
			void				SetUniform( const char* name, float value );
			void				SetUniform( const char* name, int value );
			void				SetUniform( const char* name, const Rgba& color );
			void				SetUniform( const char* name, const Matrix44& matrix, bool transpose = false );
			void				SetUniform( const char* name, const Vector3& value );

	//-----------------------------------------------------------------------------------------------
	// Camera Functions
			void				SetCamera(VKCamera* cam);

	//-----------------------------------------------------------------------------------------------
	// Buffer operations
			uint32_t			FindMemoryType( uint32_t requiredTypes, uint32_t requiredProps ) const;
			void				CreateAndGetBuffer( VkBuffer* out_buffer, VkDeviceMemory* out_deviceMem, 
									   VkDeviceSize size, VkBufferUsageFlags usage, 
									   VkMemoryPropertyFlags props );
			void				CopyBuffers( VkBuffer dstBuffer, VkBuffer srcBuffer, VkDeviceSize byteCount );
	
	//-----------------------------------------------------------------------------------------------
	// Static methods
	static		VKRenderer*			CreateInstance( const char* appName );
	static		void				DestroyInstance();
	static		VKRenderer*			GetInstance();

	// Debug callback
	static	VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugReportFlagsEXT flags,
		VkDebugReportObjectTypeEXT objType,
		uint64_t obj,
		size_t location,
		int32_t code,
		const char* layerPrefix,
		const char* msg,
		void* userData);

	//-----------------------------------------------------------------------------------------------
	// Vulkan Members
private:
			uint32_t					m_currentFrame = 0;
			VkDebugReportCallbackEXT			m_debugCallback;
			VkInstance					m_vkInstance;
			VkPhysicalDevice				m_physicalDevice = VK_NULL_HANDLE;
			VkDevice					m_logicalDevice = VK_NULL_HANDLE;
			VkQueue						m_graphicsQueue;
			VkSurfaceKHR					m_surface;
			VkQueue						m_presentQueue;
			VkSwapchainKHR					m_swapChain;
			VkExtent2D					m_swapChainExtent;
			VkFormat					m_swapChainImageFormat;
			std::vector<VkImage>				m_swapChainImages;
			std::vector<VkImageView>			m_swapChainImageViews;
			VkRenderPass					m_renderPass;
			VkPipelineLayout				m_pipelineLayout;
			VkPipeline					m_graphicsPipeline;
			std::vector<VkFramebuffer>			m_framebuffers;
			VkCommandPool					m_commandPool;
			std::vector<VkCommandBuffer>			m_commandBuffers;
			std::vector<VkSemaphore>			m_imageAvailableSemaphore;
			std::vector<VkSemaphore>			m_renderFinishedSemaphore;
			std::vector<VkSemaphore>			m_colorTargetAvailableSemaphore;
			std::vector<VkFence>				m_fences;
	
	//-----------------------------------------------------------------------------------------------
	// Data Members
			std::map<std::string,VKTexture*>		m_loadedTextures; 
			std::map<std::string,VKShaderProgram*>		m_loadedShaderPrograms;
			std::map<std::string,VKMesh*>			m_loadedMeshes;
			std::map<std::string,VKMaterial*>		m_loadedMaterials;
			VKVertexBuffer*					m_immediateVBO;
			VKIndexBuffer*					m_immediateIBO;
			VKTexture*					m_immediateTexture = nullptr;
			VKTexture*					m_defaultTexture = nullptr;
			VkDescriptorSetLayout				m_descriptorSetLayout;
			VkDescriptorPool				m_descriptorPool;
			VKCamera*					m_defaultCamera = nullptr;
			VKCamera*					m_defaultPerspectiveCamera = nullptr;
			VKCamera*					m_currentCamera = nullptr;
			VKTexture*					m_defaultColorTarget = nullptr;
			VKTexture*					m_defaultDepthTarget = nullptr;
			VKShader*					m_defaultShader = nullptr;
			VKMaterial*					m_defaultMaterial = nullptr;
			VKMaterial*					m_defaultMaterialShared = nullptr;
			VKMaterial*					m_activeMaterial = nullptr;
			VKPipeline*					m_defaultPipeline = nullptr;
			VKUniformBuffer*				m_testBuffer = nullptr;
			VKUniformBuffer*				m_modelBuffer = nullptr;
			uint32_t					m_swapImageIndex = 0;
			VkBuffer					m_ubo;
			VkDeviceMemory					m_uboMemory;
			VkDescriptorSet					m_descriptorSet;
};

//-----------------------------------------------------------------------------------------------
// Standalone functions
void				VkRenderStartup();
void				VkShutdown();
VkFormat			GetVKDataType( VKRenderType type );
VkFormat			GetVkFormat( eTextureFormat format );
VkShaderStageFlagBits		GetVKShaderStageFlag( ShaderStageSlot stage );
VkPrimitiveTopology		GetVKDrawType( DrawPrimitiveType type );
VkPolygonMode			GetVKPolygonMode( FillMode mode );
VkCullModeFlags			GetVKCullMode( CullMode mode );
VkFrontFace			GetVKWindOrder( WindOrder order );
VkCompareOp			GetVKDepthOp( DepthTestOp compare );
VkBlendOp			GetVKBlendOp( BlendOp op );
VkBlendFactor			GetVKBlendFactor( BlendFactor factor );

Postmortem