{.experimental: "codeReordering".}
{.deadCodeElim: on.}
import vulkan
, buffer
, swapchain
, depth_stencil
, render_pass
, render_passes
, uniform_buffer
, vulkan_utils
import std/os
import glm
#import ../drawable/shape_types
type
Render_Update_Stage* = enum
Never, Always, Pre, Normal, Post, Render
Vertex* = object
position*: array[3,float32]
color*: array[3, float32]
#texture_coordinates*: array[3, float32]
Graphics_Pipeline_Obj = object of RootObj
#Mode mode
#Shader* shader
pipeline_stage*: Render_Update_Stage
shader_stages*: seq[string]
#shader_defines*: Shader_Define
#depth*: Depth
pushDescriptors*: bool
dynamic_states*: seq[VkDynamicState]
shader_modules*: seq[VkShaderModule]
shaders*: seq[string]
pipeline*: VkPipeline
pipeline_layout*: VkPipelineLayout
pipeline_layout_create_info*: VkPipelineLayoutCreateInfo
pipelineBindPoint*: VkPipelineBindPoint
descriptor_pool*: VkDescriptorPool
descriptor_set*: VkDescriptorSet
descriptor_set_layout*: VkDescriptorSetLayout
depth_stencil*: Depth_Stencil
topology*: VkPrimitiveTopology
polygonMode*: VkPolygonMode
cullMode*: VkCullModeFlags
frontFace*: VkFrontFace
render_pass*: Render_Pass
blend_attachment_states*: array[1, VkPipelineColorBlendAttachmentState]
pipeline_shader_stages*: seq[VkPipelineShaderStageCreateInfo]
vertexInputStateCreateInfo*: VkPipelineVertexInputStateCreateInfo
inputAssemblyState*: VkPipelineInputAssemblyStateCreateInfo
rasterizationState*: VkPipelineRasterizationStateCreateInfo
colourBlendState*: VkPipelineColorBlendStateCreateInfo
depthStencilState*: VkPipelineDepthStencilStateCreateInfo
viewportState*: VkPipelineViewportStateCreateInfo
multisampleState*: VkPipelineMultisampleStateCreateInfo
dynamicState*: VkPipelineDynamicStateCreateInfo
tessellationState*: VkPipelineTessellationStateCreateInfo
render_pass_begin_info *: VkRenderPassBeginInfo
Graphics_Pipeline* = ref object of Graphics_Pipeline_Obj
proc a_graphics_pipeline*( vk_device: VkDevice
, swapchain: Swapchain
, descriptor_buffer_info: VkDescriptorBufferInfo
, pipeline_cache: var VkPipelineCache
, gpu_memory_properties: VkPhysicalDeviceMemoryProperties
, shaders: seq[string] = @[]
, hollow: bool = false
, make_depth_stencil: bool = false
): Graphics_Pipeline =
var shader_stages: seq[VkPipelineShaderStageCreateInfo]
shader_stages.setLen shaders.len
result = Graphics_Pipeline( descriptor_pool: create_descriptor_pool( vk_device )
, descriptor_set_layout: create_descriptor_set_layout( vk_device )
, render_pass: create_basic_shape_render_pass( vk_device
, swapchain.color_format
, swapchain.depth_format
)
)
if make_depth_stencil:
result.depth_stencil = create_depth_stencil( vk_device
, swapchain.color_format
, swapchain.depth_format
, swapchain.current_extent
, gpu_memory_properties
)
result.pipeline_layout = create_pipeline_layout( vk_device
, result.descriptor_set_layout
, 0
)
result.descriptor_set = create_descriptor_sets( vk_device
, result.descriptor_set_layout
, result.descriptor_pool
, descriptor_buffer_info
)
var
topo: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
pMode: VkPolygonMode = VK_POLYGON_MODE_FILL
var
pipeline_info = VkGraphicsPipelineCreateInfo(
sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
, layout: result.pipeline_layout
, render_pass: result.render_pass.vk_handle
#subpass: 0,
#basePipelineHandle: VkPipeline(VK_NULL_HANDLE), # optional
#basePipelineIndex: -1, # optional
)
#Input assembly state describes how primitives are assembled
#This pipeline will assemble vertex data as a triangle lists (though we only use one triangle)
inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
, topology: topo
#primitiveRestartEnable: VkBool32(VK_FALSE),
)
rasterizer = VkPipelineRasterizationStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
, depthClampEnable: VkBool32(VK_FALSE)
, rasterizerDiscardEnable: VkBool32(VK_FALSE)
, polygonMode: pMode
, lineWidth: 1.float32
, cullMode: VkCullModeFlags VK_CULL_MODE_NONE
, frontFace: VK_FRONT_FACE_COUNTER_CLOCKWISE
, depthBiasEnable: VkBool32(VK_FALSE)
# , depthBiasConstantFactor: 0f # optional
# , depthBiasClamp: 0f # optional
# , depthBiasSlopeFactor: 0f # optional
)
#[
VK_BLEND_FACTOR_ZERO = 0
VK_BLEND_FACTOR_ONE = 1
VK_BLEND_FACTOR_SRC_COLOR = 2
VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR = 3
VK_BLEND_FACTOR_DST_COLOR = 4
VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR = 5
VK_BLEND_FACTOR_SRC_ALPHA = 6
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA = 7
VK_BLEND_FACTOR_DST_ALPHA = 8
VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA = 9
VK_BLEND_FACTOR_CONSTANT_COLOR = 10
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR = 11
VK_BLEND_FACTOR_CONSTANT_ALPHA = 12
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA = 13
VK_BLEND_FACTOR_SRC_ALPHA_SATURATE = 14
VK_BLEND_FACTOR_SRC1_COLOR = 15
VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR = 16
VK_BLEND_FACTOR_SRC1_ALPHA = 17
VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA = 18
]#
# Assume Alpha Blending
colorBlendAttachment = VkPipelineColorBlendAttachmentState(
blendEnable: VKBool32 VK_TRUE
, srcColorBlendFactor: VkBlendFactor VK_BLEND_FACTOR_SRC_ALPHA
, dstColorBlendFactor: VkBlendFactor VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA
, colorBlendOp: VkBlendOp VK_BLEND_OP_ADD
, srcAlphaBlendFactor: VkBlendFactor VK_BLEND_FACTOR_ONE
, dstAlphaBlendFactor: VkBlendFactor VK_BLEND_FACTOR_ZERO
, alphaBlendOp: VkBlendOp VK_BLEND_OP_ADD
, colorWriteMask: VkColorComponentFlags VK_COLOR_COMPONENT_R_BIT.ord or
VK_COLOR_COMPONENT_G_BIT.ord or
VK_COLOR_COMPONENT_B_BIT.ord or
VK_COLOR_COMPONENT_A_BIT.ord
)
colorBlending = VkPipelineColorBlendStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO
, logicOpEnable: VkBool32(VK_FALSE)
, logicOp: VK_LOGIC_OP_COPY # optional
, attachmentCount: 1
, pAttachments: colorBlendAttachment.addr
, blendConstants: [0f, 0f, 0f, 0f] # optional
)
# Enable dynamic states
# Most states are baked into the pipeline, but there are still a few dynamic states that can be changed within a command buffer
# To be able to change these we need do specify which dynamic states VkPipelineViewportStateCreateInfoVkPipelineViewportStateCreateInfo be changed using this pipeline. Their actual states are set later on in the command buffer.
# For this example we will set the viewport and scissor using dynamic states
dynamicStateEnables: seq[VkDynamicSTate] = @[ VK_DYNAMIC_STATE_VIEWPORT
, VK_DYNAMIC_STATE_SCISSOR
, VK_DYNAMIC_STATE_LINE_WIDTH
]
dynamicState = VkPipelineDynamicStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
, pDynamicStates: addr dynamicStateEnables[0]
, dynamicStateCount: dynamicStateEnables.len.uint32
)
# Depth and stencil state containing depth and stencil compare and test operations
# We only use depth tests and want depth tests and writes to be enabled and compare with less or equal
depthStencilState = VkPipelineDepthStencilStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
, depthTestEnable: VkBool32 VK_TRUE
, depthWriteEnable: VkBool32 VK_TRUE
, depthCompareOp: VK_COMPARE_OP_LESS_OR_EQUAL
, depthBoundsTestEnable: VkBool32 VK_FALSE
, back: VkStencilOpState( failOp: VK_STENCIL_OP_KEEP
, passOp: VK_STENCIL_OP_KEEP
, compareOp: VK_COMPARE_OP_ALWAYS
)
, front: VkStencilOpState( failOp: VK_STENCIL_OP_KEEP
, passOp: VK_STENCIL_OP_KEEP
, compareOp: VK_COMPARE_OP_ALWAYS
)
, stencilTestEnable: VkBool32 VK_FALSE
)
# Multi sampling state
# This example does not make use of multi sampling (for anti-aliasing), the state must still be set and passed to the pipeline
multisampleState = VkPipelineMultisampleStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
, rasterizationSamples: VK_SAMPLE_COUNT_1_BIT
, pSampleMask: nil
)
# Vertex input descriptions
# Specifies the vertex input parameters for a pipeline
# Vertex input binding
# This example uses a single vertex input binding at binding point 0 (see vkCmdBindVertexBuffers)
vertexInputBinding = VkVertexInputBindingDescription(
binding: 0
, stride: uint32 sizeof(Vertex)
, inputRate: VK_VERTEX_INPUT_RATE_VERTEX
)
# Input attribute bindings describe shader attribute locations and memory layouts
vertex_input_attributes: array[2,VkVertexInputAttributeDescription]
# Attribute location 0: Position
vertex_input_attributes[0].binding = 0
vertex_input_attributes[0].location = 0
vertex_input_attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT
vertex_input_attributes[0].offset = uint32 offsetof(Vertex, position)
# Attribute location 1: Color
vertex_input_attributes[1].binding = 0
vertex_input_attributes[1].location = 1
vertex_input_attributes[1].format = VK_FORMAT_R32G32B32_SFLOAT
vertex_input_attributes[1].offset = offsetof(Vertex, color).uint32
#[
vertex_input_attributes[2].location = 2
vertex_input_attributes[2].format = VK_FORMAT_R32G32B32_SFLOAT
vertex_input_attributes[2].offset = offsetof(Vertex, texture_coordinates).uint32
]#
var
vertex_input_state =
VkPipelineVertexInputStateCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
, vertexBindingDescriptionCount: 1
, pVertexBindingDescriptions: addr vertexInputBinding
, vertexAttributeDescriptionCount: uint32 vertex_input_attributes.len
, pVertexAttributeDescriptions: addr vertex_input_attributes[0]
)
viewport = VkViewport(
x: 0f, y: 0
, width: swapchain.current_extent.width.float32
, height: swapchain.current_extent.height.float32
, minDepth: 0f
, maxDepth: 1f
)
scissor = VkRect2D( offset: VkOffset2D(x: 0, y: 0)
, extent: swapchain.current_extent,
)
viewportState = VkPipelineViewportStateCreateInfo(
sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
, viewportCount: 1
, pViewports: viewport.addr
, scissorCount: 1
, pScissors: scissor.addr
, flags: VkPipelineViewportStateCreateFlags 0
)
# Shaders
if shaders.len != 0:
var
vertShaderCode = readFile shaders[0]
fragShaderCode = readFile shaders[1]
# Vertex shader
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT
shaderStages[0].module = vk_device.a_shader_module vertShaderCode
# Main entry point for the shader
shaderStages[0].pName = "main"
# Fragment shader
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT
shaderStages[1].module = vk_device.a_shader_module fragShaderCode
# Main entry point for the shader
shaderStages[1].pName = "main"
# Set pipeline shader stage info
pipeline_info.stageCount = shaderStages.len.uint32
pipeline_info.pStages = addr shaderStages[0]
# Assign the pipeline states to the pipeline creation info structure
pipeline_info.pVertexInputState = addr vertex_input_state
pipeline_info.pInputAssemblyState = addr inputAssembly
pipeline_info.pRasterizationState = addr rasterizer
pipeline_info.pColorBlendState = addr colorBlending
pipeline_info.pMultisampleState = addr multisampleState
pipeline_info.pViewportState = addr viewportState
pipeline_info.pDepthStencilState = addr depthStencilState
pipeline_info.render_pass = result.render_pass.vk_handle
pipeline_info.pDynamicState = addr dynamicState
# Create rendering pipeline using the specified states
discard vkCreateGraphicsPipelines( vk_device
, pipelineCache
, 1
, addr pipeline_info
, nil
, addr result.pipeline
)
# Shader modules are no longer needed once the graphics pipeline has been created
if shaders.len != 0:
for shader_stage in shaderStages.mitems:
vkDestroyShaderModule( vk_device
, shader_stage.module
, nil
)
proc a_shader_module( device: VkDevice
, code: string
): VkShaderModule =
var createInfo = VkShaderModuleCreateInfo( sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO
, codeSize: cast[uint] (code.len)
, pCode: cast[ptr uint32] ( addr code[0])
)
assert vkCreateShaderModule( device
, addr createInfo
, nil
, addr result
) == VK_SUCCESS
result
# TODO: currently only assumes a single vertex <-> fragment pair. (total of 2 shaders)
proc the_shader_stages( vk_device: VkDevice
, shaders: seq[string]
): seq[VkPipelineShaderStageCreateInfo] =
for shader in shaders:
assert( shader.fileExists()
, "shader file path doesn't exist: " & shader
)
result = @[
VkPipelineShaderStageCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
, pNext: nil
, flags: VkPipelineShaderStageCreateFlags 0
, stage: VK_SHADER_STAGE_VERTEX_BIT
, module: a_shader_module( vk_device
, readFile shaders[0]
)
, pName: "main"
, pSpecializationInfo: nil
)
, VkPipelineShaderStageCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
, pNext: nil
, flags: VkPipelineShaderStageCreateFlags 0
, stage: VK_SHADER_STAGE_FRAGMENT_BIT
, module: a_shader_module( vk_device
, readFile shaders[1]
)
, pName: "main"
, pSpecializationInfo: nil
)
]
# TODO
# currently any shape > 6 sides is counted as an Ngon
# weird behavior when hollow is true, in the method used to render the verts
proc create_descriptor_set_layout*( vk_device: VkDevice
): VkDescriptorSetLayout =
# Setup layout of descriptors used in this example
# Basically connects the different shader stages to descriptors for binding uniform buffers, image samplers, etc.
# So every shader binding should map to one descriptor set layout binding
# Binding 0: Uniform buffer (Vertex shader)
var
layoutBinding: VkDescriptorSetLayoutBinding
descriptorLayout: VkDescriptorSetLayoutCreateInfo
layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
layoutBinding.descriptorCount = 1
layoutBinding.stageFlags = VkShaderStageFlags VK_SHADER_STAGE_VERTEX_BIT.ord or
VK_SHADER_STAGE_FRAGMENT_BIT.ord
layoutBinding.pImmutableSamplers = nil
descriptorLayout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO
descriptorLayout.pNext = nil
descriptorLayout.bindingCount = 1
descriptorLayout.pBindings = addr layoutBinding
discard vkCreateDescriptorSetLayout( vk_device
, addr descriptorLayout
, nil
, addr result
)
proc create_descriptor_sets*( vk_device: VkDevice
, descriptor_set_layout: VkDescriptorSetLayout
, descriptor_pool: VkDescriptorPool
, descriptor_buffer_info: VkDescriptorBufferInfo
): VkDescriptorSet =
var
allocInfo = VkDescriptorSetAllocateInfo( sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO
, descriptorPool: descriptor_pool
, descriptorSetCount: 1
, pSetLayouts: addr descriptor_set_layout
)
assert vkAllocateDescriptorSets( vk_device
, addr allocInfo
, addr result
) == VK_SUCCESS
# Update the descriptor set determining the shader binding points
# For every binding point used in a shader there needs to be one
# descriptor set matching that binding point
var write_descriptor_sets: array[1,VkWriteDescriptorSet] =
[ VkWriteDescriptorSet( sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET
, dstSet: result
, descriptorCount: 1
, descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
, pBufferInfo: cast[ptr ptr VkDescriptorBufferInfo] (addr descriptor_buffer_info)
, dstBinding: 0
)
]
vkUpdateDescriptorSets( vk_device
, 1
, addr write_descriptor_sets[0]
, 0
, nil
)
proc create_pipeline_layout( vk_device: VkDevice
, descriptor_set_layout: VkDescriptorSetLayout
, push_constant_size: uint32
, push_constant_range_count: uint32 = 0
): VkPipelineLayout =
var
pPipelineLayoutCreateInfo: VkPipelineLayoutCreateInfo
pushConstantRange = VkPushConstantRange( stageFlags: VkShaderStageFlags VK_SHADER_STAGE_VERTEX_BIT.ord or VK_SHADER_STAGE_FRAGMENT_BIT.ord
, offset: 0
, size : push_constant_size# uint32 sizeof Shape_Pushes
)
# Create the pipeline layout that is used to generate the rendering pipelines that are based on this descriptor set layout
# In a more complex scenario you would have different pipeline layouts for different descriptor set layouts that could be reused
pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
pPipelineLayoutCreateInfo.pNext = nil
pPipelineLayoutCreateInfo.setLayoutCount = 1
pPipelineLayoutCreateInfo.pSetLayouts = addr descriptor_set_layout
pPipelineLayoutCreateInfo.pushConstantRangeCount = push_constant_range_count
#pPipelineLayoutCreateInfo.pPushConstantRanges = addr pushConstantRange
assert vkCreatePipelineLayout( vk_device
, addr pPipelineLayoutCreateInfo
, nil
, addr result
) == VK_SUCCESS
result