15,504,286 members
Articles / Mobile Apps / Android
Article
Posted 16 Apr 2019

3.9K views

# Vulkan API with Kotlin Native - Swapchain, Pipeline

Rate me:
Vulkan pipeline with Kotlin Native

## Introduction

In the previous part, we created a surface where to draw. Now, we will prepare what to draw. But before this, we should make some preparations. We will need vectors, matrices, buffers and so on. It would be possible to use `glm` libraries for our purposes but as we want to check Kotlin Native performance itself, I will implement something like a little math library by myself. At first, we'll create a base vector class with add, subtract, multiply, dot product and so on operations. Also, we will need a companion object to cast our base vector to a vector with right dimensions. For easy of use with `Vulkan` API, we will define coordinates as a byte array.

Kotlin
```open class Vec(vararg coordinates: Float) {

protected var buffer: FloatArray = FloatArray(coordinates.size).apply {
coordinates.copyInto(this)
}

val len by lazy {
sqrtf(this dot this)
}

val size by lazy {
coordinates.size
}

fun normalize() = len.let {
if (it != 0f)
for (i in 0 until buffer.size)
buffer[i] /= it
}

open fun normalized() = len.run {

val copyed = buffer.copyOf()

if (this != 0f)
for (i in 0 until copyed.size)
copyed[i] /= this

when (buffer.size) {
2 -> Vec.fromBuffer(*copyed) as Vec2
3 -> Vec.fromBuffer(*copyed) as Vec3
4 -> Vec.fromBuffer(*copyed) as Vec4
else -> throw IllegalArgumentException("not supported vector size")
}
}

infix fun dot(vec: Vec): Float = buffer.foldIndexed(0f) { index, sum, element ->
sum + element * vec.buffer[index]
}

open operator fun plus(scalar: Float): Vec = Vec.fromBuffer(*buffer.map {
it + scalar
}.toFloatArray())

....

companion object {

fun fromBuffer(vararg buffer: Float): Vec {
when (buffer.size) {
2 -> return Vec2(buffer[0], buffer[1])
3 -> return Vec3(buffer[0], buffer[1], buffer[2])
4 -> return Vec4(buffer[0], buffer[1], buffer[2], buffer[3])
else -> throw IllegalArgumentException("not supported vector size")
}
}
}
...
}```

And as an example, I will show an inheritance of the 4-dimensional vector from the base class:

Kotlin
```class Vec4(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) : Vec(x, y, z, w) {

var x: Float
get() = buffer[0]
set(value) {
buffer[0] = value
}
....

var w: Float
get() = buffer[3]
set(value) {
buffer[3] = value
}

override operator fun inc(): Vec4 = super.inc() as Vec4
...
override fun normalized(): Vec4 = super.normalized() as Vec4

companion object {

val Zero = Vec4()
}
}```

Also, we will need matrices. They are implemented in the same way - first, we'll create a base class and then will inherit from it:

The base class:

Kotlin
```open class Mat(vararg columns: Vec) {

private var _size = columns.size

protected var buffer = FloatArray(_size * _size).apply {
columns.forEachIndexed { index, vec ->
vec.toArray().forEachIndexed { idx, fl ->
this[index * columns.size + idx] = fl
}
}
}

...

open operator fun plus(v: Float): Mat {

val arr = this.toBuffer()
for (i in 0 until arr.size)
arr[i] += v

return when (size) {
2 -> Mat2.from(*arr)
3 -> Mat3.from(*arr)
4 -> Mat4.from(*arr)
else -> throw IllegalArgumentException("invalid dimensions")
}
}

...

fun toBuffer(): FloatArray = buffer.copyOfRange(0, buffer.size)
}```

And `Mat4`:

Kotlin
```class Mat4(x: Vec4 = Vec4(x = 1f), y: Vec4 = Vec4(y = 1f),
z: Vec4 = Vec4(z = 1f), w: Vec4 = Vec4(w = 1f)) : Mat(x, y, z, w) {

var X: Vec4
get() = Vec4(buffer[0], buffer[1], buffer[2], buffer[3])
set(value) {
buffer[0] = value[0]
buffer[1] = value[1]
buffer[2] = value[2]
buffer[3] = value[3]
}
...

var W: Vec4
get() = Vec4(buffer[12], buffer[13], buffer[14], buffer[15])
set(value) {
buffer[12] = value[0]
buffer[13] = value[1]
buffer[14] = value[2]
buffer[15] = value[3]
}

override operator fun inc(): Mat4 = super.inc() as Mat4
...
override operator fun minus(m: Mat): Mat4 = super.minus(m) as Mat4

companion object {

fun from(vararg a: Float): Mat4 {
assert(a.size == 16)
return Mat4(Vec4(a[0], a[1], a[2], a[3]), Vec4(a[4], a[5], a[6], a[7]),
Vec4(a[8], a[9], a[10], a[11]), Vec4(a[12], a[13], a[14], a[15]))
}
...
}
}```

Now we have the little math library, but it would be good to have some helpers to work with byte arrays as we will use them much while working with `Vulkan` API. Let's create a helper class for it that allows us to use iterator, `forEach`, etc.

Kotlin
```@ExperimentalUnsignedTypes
internal class VulkanArray<T : CVariable>
private constructor(internal val _size: UInt) : DisposableContainer() {

internal lateinit var _array: CArrayPointer<T>
private set

companion object {

inline fun <reified K : CVariable> Make(size: UInt): VulkanArray<K> {
val array = VulkanArray<K>(size)
array._array = with(array.arena) { allocArray(size.toInt()) }
return array
}
}
}

@ExperimentalUnsignedTypes
internal inline operator fun <reified T : CVariable> VulkanArray<T>.iterator(): Iterator<T> {

return object : Iterator<T> {

var cursor = 0
override fun hasNext() = cursor < _size.toInt()
override fun next(): T = _array.get(cursor++)
}
}

@ExperimentalUnsignedTypes
internal inline fun <reified T : CVariable> VulkanArray<T>
.forEach(callback: (it: T) -> Unit) {

for (i in 0 until _size.toInt()) {
callback(_array[i])
}
}

...```

## SwapChain. RenderPass.

As you know, only one image is presented to the surface at a time. But we can create a queue and render more images while one of them is being presented to a screen. So the swapchain is an array of such presentable images in a queue and it allows to show them on a screen. To create the swapchain, we first will get supported formats, check if we can use `VK_PRESENT_MODE_IMMEDIATE_KHR` or `VK_PRESENT_MODE_MAILBOX_KHR`, check if we can use composite alpha, create swapchain itself and create a buffer with images.
Let's get supported formats:

Kotlin
```val formatsCount = alloc<UIntVar>()
var result: VkResult

var buffer: CArrayPointer<VkSurfaceFormatKHR>? = null

do {

result = vkGetPhysicalDeviceSurfaceFormatsKHR(pDevice.device,
surface, formatsCount.ptr, null)
if (!VK_CHECK(result)) {
throw RuntimeException("Could not get surface formats.")
}

if (formatsCount.value == 0u) break

buffer?.let {
nativeHeap.free(it)
buffer = null
}

buffer = nativeHeap.allocArray(formatsCount.value.toInt())
result =
vkGetPhysicalDeviceSurfaceFormatsKHR(
pDevice.device,
surface,
formatsCount.ptr,
buffer!!.getPointer(memScope)
)
if (!VK_CHECK(result)) {
throw RuntimeException("Could not get surface formats.")
}

} while (result == VK_INCOMPLETE)

if (formatsCount.value == 1u) {
displayFormat = buffer!![0].format
_colorSpace = buffer!![0].colorSpace
} else {

var chosenFormat: UInt? = null

for (i in 0u until formatsCount.value) {
if (buffer!![i.toInt()].format == VK_FORMAT_R8G8B8A8_UNORM) {
chosenFormat = i
break
}
}

chosenFormat?.let {
displayFormat = buffer!![it.toInt()].format
_colorSpace = buffer!![it.toInt()].colorSpace
} ?: kotlin.run {
displayFormat = buffer!![0].format
_colorSpace = buffer!![0].colorSpace
}

}

nativeHeap.free(buffer!!)
```

Next, we'll define present mode. Earlier, in the `PhysicalDevice` class, we added `surfacePresentModes` property. In case we won't use `vsync`, we just check if it contains `VK_PRESENT_MODE_MAILBOX_KHR` otherwise if contains `VK_PRESENT_MODE_IMMEDIATE_KHR`, use it. And by default, it's `VK_PRESENT_MODE_FIFO_KHR`. Then in surface capabilities also, we should check max images count and if they contain any composite alpha bits. After all preparations, we can create swapchain itself and image:

Kotlin
```val swapchainCreateInfo: VkSwapchainCreateInfoKHR =
alloc<VkSwapchainCreateInfoKHR>().apply {
sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
...
}
...

if (!VK_CHECK(vkCreateSwapchainKHR(lDevice,
swapchainCreateInfo.ptr, null, _swapchain.ptr)))
throw RuntimeException("Failed to create swapchain")

val imagesCount: UIntVar = alloc()

if (!VK_CHECK(vkGetSwapchainImagesKHR(lDevice, _swapchain.value,
imagesCount.ptr, null)))
throw RuntimeException("Failed to initialize vulkan. No images")

_imagesBuffer = arena.allocArray(imagesCount.value.toInt())

if (!VK_CHECK(vkGetSwapchainImagesKHR(lDevice, _swapchain.value,
imagesCount.ptr, _imagesBuffer)))
throw RuntimeException("Failed to initialize vulkan. No images")

for (i in 0u until imagesCount.value) {

val imageViewCreateInfo: VkImageViewCreateInfo =
alloc<VkImageViewCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO
...
image = _imagesBuffer[i.toInt()]!!
}

val imageView = with(arena) { alloc<VkImageViewVar>() }
if (!VK_CHECK(vkCreateImageView(lDevice,
imageViewCreateInfo.ptr, null, imageView.ptr))) {
throw RuntimeException("Failed to create image views")
}

}
...
```

The `RenderPass` implementation is quite easy to do. It's the standard way with use of structures and structures. So I leave it to the reader to check the source code.

## Uniform Buffers

Uniform buffers are read-only memory areas allocated in the memory of the video card that can be used by shader programs. So we should prepare those areas in the host memory, then pass them to the video card memory. For this, yes, again some preparatory steps. First of all, let's create a `Vertex` class:

Kotlin
```@ExperimentalUnsignedTypes
class Vertex(position: Vec3, color: Vec3) {
var buffer: FloatArray = FloatArray(6) { 0f }
private set

var position: Vec3 ...

var color: Vec3 ...

...

companion object {

// Single vertex input binding at binding point 0

fun bindingDescription(scope: MemScope) =
scope.alloc<VkVertexInputBindingDescription>().apply {
binding = 0u
stride = Vertex.SIZE.toUInt()
inputRate = VK_VERTEX_INPUT_RATE_VERTEX
}

fun inputAtributes(scope: MemScope) =
scope.allocArray<VkVertexInputAttributeDescription>(2).apply {

// Input attribute bindings describe shader attribute locations and memory layouts

// These match the following shader layout (see triangle.vert):
//	layout (location = 0) in vec3 inPos;
//	layout (location = 1) in vec3 inColor;

// Attribute location 0: Position
this[0].binding = 0u
this[0].location = 0u
// Position attribute is three 32 bit signed (SFLOAT) floats (R32 G32 B32)
this[0].format = VK_FORMAT_R32G32B32_SFLOAT
this[0].offset = 0u

// Attribute location 1: Color

}

val SIZE = 6 * sizeOf<FloatVar>()
val BUFFER_SIZE = 6
}
}```

Now we can create a `vertex` buffer. As we will use staging buffers, let's create classes for them. `StagingBuffer `itself will be just a couple of properties, a buffer and a device memory of `VkBufferVar `and `VkDeviceMemoryVar `types. And `StagingBuffers `class also contains a couple of variables for a vertex staging buffer and an indices staging buffer. And the initialization of the vertex buffer class will include the following steps:

• create a mappable buffer visible to the host
• copy data to it
• create a buffer in video card memory with the same size as the host buffer
• with use of a command buffer, copy data from the host to the device
• delete the host buffer
• use the device buffer inside shaders

Here is the implementation:

Kotlin
```@ExperimentalUnsignedTypes
internal class VertexBuffer(
private val pDevice: PhysicalDevice,
private val lDevice: LogicalDevice,
vertices: Array<Vertex>,
indices: Array<UInt>
) : DisposableContainer() {

...
init {

var voffset = 0

vertices.forEach { v ->
v.buffer.copyInto(vertexBuffer, voffset)
voffset += Vertex.BUFFER_SIZE
}

indices.forEachIndexed { index, uInt ->
indexBuffer[index] = uInt
}

memScoped {

val memoryAllocateInfo = alloc<VkMemoryAllocateInfo>().apply {
sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
}

val memReqs: VkMemoryRequirements = alloc()

// Vertex buffer
val vertexBufferInfo = alloc<VkBufferCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
size = vertexBufferSize.toULong()
usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT //copy source
}

val stagingBuffers = StagingBuffers()

if (!VK_CHECK(
vkCreateBuffer(
lDevice.device,
vertexBufferInfo.ptr,
null,
stagingBuffers.vertices.buffer.ptr
)
)
)
throw RuntimeException("Failed to create buffer")

vkGetBufferMemoryRequirements(lDevice.device,
stagingBuffers.vertices.buffer.value, memReqs.ptr)

memoryAllocateInfo.allocationSize = memReqs.size

// host visible memory and coherent
memoryAllocateInfo.memoryTypeIndex = pDevice.getMemoryType(
memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
)

val mapped = alloc<COpaquePointerVar>()

if (!VK_CHECK(
vkAllocateMemory(
lDevice.device,
memoryAllocateInfo.ptr,
null,
stagingBuffers.vertices.memory.ptr
)
)
)
throw RuntimeException("Faild allocate memory")

if (!VK_CHECK(
vkMapMemory(
lDevice.device,
stagingBuffers.vertices.memory.value,
0u,
memoryAllocateInfo.allocationSize,
0u,
mapped.ptr
)
)
)
throw RuntimeException("Faild map memory")

vertexBuffer.usePinned { buffer ->
vertexBufferSize.toULong())
}

vkUnmapMemory(lDevice.device, stagingBuffers.vertices.memory.value)

if (!VK_CHECK(
vkBindBufferMemory(
lDevice.device,
stagingBuffers.vertices.buffer.value,
stagingBuffers.vertices.memory.value,
0u
)
)
)
throw RuntimeException("failed bind memory")

// Create a _device local buffer to accept data
vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT or
VK_BUFFER_USAGE_TRANSFER_DST_BIT

if (!VK_CHECK(vkCreateBuffer(lDevice.device, vertexBufferInfo.ptr,
null, _vertexBuffer.ptr)))
throw RuntimeException("Failed to create buffer")
vkGetBufferMemoryRequirements(lDevice.device, _vertexBuffer.value,
memReqs.ptr)

memoryAllocateInfo.allocationSize = memReqs.size
memoryAllocateInfo.memoryTypeIndex = pDevice.getMemoryType(
memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
)

if (!VK_CHECK(vkAllocateMemory(lDevice.device, memoryAllocateInfo.ptr,
null, _vertexMemory.ptr)))
throw RuntimeException("Faild allocate memory")

if (!VK_CHECK(vkBindBufferMemory(lDevice.device, _vertexBuffer.value,
_vertexMemory.value, 0u)))
throw RuntimeException("failed bind memory")

...
/* the same for index buffer */
...

// copy buffer
val copyCmd = lDevice.createCommandBuffers(VK_COMMAND_BUFFER_LEVEL_PRIMARY,
1u, true)

val copyRegion = alloc<VkBufferCopy>()

copyRegion.size = vertexBufferSize.toULong()
vkCmdCopyBuffer(
copyCmd._array[0],
stagingBuffers.vertices.buffer.value,
_vertexBuffer.value,
1u,
copyRegion.ptr
)

...

val cc = alloc<VkCommandBufferVar>()
cc.value = copyCmd._array[0]
lDevice.flushCommandBuffer(cc, false)

vkDestroyBuffer(lDevice.device, stagingBuffers.vertices.buffer.value, null)
vkFreeMemory(lDevice.device, stagingBuffers.vertices.memory.value, null)

...
}
}
}```

Note: It's critical to use `usePinned `otherwise you'll get a trash. It temporarily pins the native memory address of the byte array.

Next, we need `UboVS `class. It will just contain model, view and projection matrices in the byte array, so it would be simple to copy it to the buffer. `UniformBufferVars` with memory, buffer and descriptor properties.

And `UniformBuffers `class:

Kotlin
```//
@ExperimentalUnsignedTypes
internal class UniformBuffers(
private val pDevice: PhysicalDevice,
private val lDevice: LogicalDevice,
private val swapchain: SwapChain

) : DisposableContainer() {

val uniformBufferVS: UniformBufferVars = UniformBufferVars()
val uboVS: UboVS = UboVS(Mat4.ZERO, Mat4.ZERO, Mat4.ZERO)

init {

memScoped {

val memReqs = alloc<VkMemoryRequirements>()

uniformBufferVS.buffer = with(arena) { alloc() }
uniformBufferVS.memory = with(arena) { alloc() }

val bufferCreateInfo = alloc<VkBufferCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
size = UboVS.SIZE.toULong()
}

if (!VK_CHECK(vkCreateBuffer(lDevice.device, bufferCreateInfo.ptr,
null, uniformBufferVS.buffer!!.ptr)))
throw RuntimeException("failed to create buffer")

val memoryAllocateInfo = alloc<VkMemoryAllocateInfo>().apply {
sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
pNext = null
allocationSize = 0u
memoryTypeIndex = 0u
}

vkGetBufferMemoryRequirements(lDevice.device,
uniformBufferVS.buffer!!.value, memReqs.ptr)
memoryAllocateInfo.allocationSize = memReqs.size

memoryAllocateInfo.memoryTypeIndex = pDevice.getMemoryType(
memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
)

if (!VK_CHECK(vkAllocateMemory(lDevice.device,
memoryAllocateInfo.ptr, null, uniformBufferVS.memory!!.ptr)))
throw RuntimeException("failed allocate memory")

if (!VK_CHECK(
vkBindBufferMemory(
lDevice.device,
uniformBufferVS.buffer!!.value,
uniformBufferVS.memory!!.value,
0u
)
)
)
throw  RuntimeException("faied bind memory")

// Store information in the uniform's descriptor
// that is used by the descriptor set
uniformBufferVS.descriptor = with(arena) { alloc() }
uniformBufferVS.descriptor!!.offset = 0u
uniformBufferVS.descriptor!!.buffer = uniformBufferVS.buffer!!.value
uniformBufferVS.descriptor!!.range = UboVS.SIZE.toULong()
}

uboVS.modelMatrix = Mat4.identity

update()
}

fun update() {

uboVS.projectionMatrix = Mat4.perspective(
swapchain.width.toInt().toFloat() / swapchain.height.toInt().toFloat(),
0.1f, 256f
)

uboVS.viewMatrix = Mat4.translate(Mat4.identity, Vec3(0f, 0f, -5f))
uboVS.modelMatrix = Mat4.rotate(uboVS.modelMatrix, radians(1f), Vec3(z = 1f))

memScoped {

val data = alloc<COpaquePointerVar>()

if (!VK_CHECK(
vkMapMemory(
lDevice.device!!,
uniformBufferVS.memory!!.value,
0u,
UboVS.SIZE.toULong(),
0u,
data.ptr
)
)
)
throw RuntimeException("failed bind memory")

uboVS.buffer.usePinned { buffer ->
UboVS.SIZE.toULong())
}

// Note: Since we requested a host coherent memory type for
// the uniform buffer, the write is instantly visible to the GPU
vkUnmapMemory(lDevice.device, uniformBufferVS.memory!!.value)
}
}

...

}```

Ok, we're almost at the end. Next time, we'll add frame buffers, command buffers and the drawing loop.

## Pipeline

Before we will create the pipeline, we need for it the pipeline cache, the descriptor set layout, the pipeline layout, the descriptor pool and the descriptor set. They are also as the renderpass implemented in the standard way and you can find them in the source code. The only thing I would mention - loading of shaders. If you remember, we compiled them and copied to the assets folder. Here the one thing that should be kept in mind - `usePinned` when loading them from a file system to a buffer.

And now, we'll define all fixed states in the rendering pipeline:

Kotlin
```@ExperimentalUnsignedTypes
internal class Pipeline(
private val _device: LogicalDevice,
private val _pipelineLayout: PipelineLayout,
private val _renderPass: RenderPass,
private val _pipelineCache: PipelineCache,
private val _swapchain: SwapChain
) : DisposableContainer() {

...

init {

memScoped {

val cwd = ByteArray(1024)
cwd.usePinned {
}

}
}

}
}

...

// Vertex input state

val pipelineVertexInputStateCreateInfo =
alloc<VkPipelineVertexInputStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
vertexBindingDescriptionCount = 1u
pVertexBindingDescriptions = vertexInputBindingDescription.ptr
vertexAttributeDescriptionCount = 2u
pVertexAttributeDescriptions = vertexInputAttributs
}

// Input assembly state

val pipelineInputAssemblyStateCreateInfo =
alloc<VkPipelineInputAssemblyStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
primitiveRestartEnable = 0u
}

// Viewport state
val viewport = alloc<VkViewport>().apply {
x = 0.0f
y = 0.0f
width = _swapchain.width.toInt().toFloat()
height = _swapchain.height.toInt().toFloat()
minDepth = 0f
maxDepth = 1f
}

val scissor = alloc<VkRect2D>().apply {
offset.x = 0
offset.y = 0
extent.width = _swapchain.width
extent.height = _swapchain.height
}

val pipelineViewportStateCreateInfo =
alloc<VkPipelineViewportStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
viewportCount = 1u
scissorCount = 1u
pViewports = viewport.ptr
pScissors = scissor.ptr
}

// Rasterization state

val pipelineRasterizationStateCreateInfo =
alloc<VkPipelineRasterizationStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
depthClampEnable = VK_FALSE.toUInt()
polygonMode = VK_POLYGON_MODE_FILL
cullMode = VK_CULL_MODE_BACK_BIT //VK_CULL_MODE_NONE
lineWidth = 1.0f
frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE
depthBiasEnable = VK_FALSE.toUInt()
}

// Multi sampling state

val pipelineMultisampleStateCreateInfo =
alloc<VkPipelineMultisampleStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
}

// Color blend state

val blendAttachmentState = allocArray<VkPipelineColorBlendAttachmentState>(1)
blendAttachmentState[0].apply {
VK_COLOR_COMPONENT_G_BIT or VK_COLOR_COMPONENT_B_BIT or
VK_COLOR_COMPONENT_A_BIT //0xfu
blendEnable = VK_FALSE.toUInt()
}

val pipelineColorBlendStateCreateInfo =
alloc<VkPipelineColorBlendStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO
attachmentCount = 1u
pAttachments = blendAttachmentState
logicOpEnable = 0u
logicOp = VK_LOGIC_OP_COPY
blendConstants[0] = 0.0f
blendConstants[1] = 0.0f
blendConstants[2] = 0.0f
blendConstants[3] = 0.0f
}

val pipelineCreateInfo = alloc<VkGraphicsPipelineCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
// The layout used for this pipeline
// (can be shared among multiple pipelines using the same layout)
layout = _pipelineLayout._pipelineLayout.value
//Renderpass this pipeline is attached to
renderPass = _renderPass.renderPass.value
}

// Enable dynamic states

val dynamicStateEnables = allocArray<VkDynamicStateVar>(2)
dynamicStateEnables[0] = VK_DYNAMIC_STATE_VIEWPORT
dynamicStateEnables[1] = VK_DYNAMIC_STATE_SCISSOR

val pipelineDynamicStateCreateInfo =
alloc<VkPipelineDynamicStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
pDynamicStates = dynamicStateEnables
dynamicStateCount = 2u
}

// Depth and stencil state

val pipelineDepthStencilStateCreateInfo =
alloc<VkPipelineDepthStencilStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
depthTestEnable = VK_TRUE.toUInt()
depthWriteEnable = VK_TRUE.toUInt()
depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL
depthBoundsTestEnable = VK_FALSE.toUInt()
back.failOp = VK_STENCIL_OP_KEEP
back.passOp = VK_STENCIL_OP_KEEP
back.compareOp = VK_COMPARE_OP_ALWAYS
stencilTestEnable = VK_FALSE.toUInt()
front.failOp = VK_STENCIL_OP_KEEP
front.passOp = VK_STENCIL_OP_KEEP
front.compareOp = VK_COMPARE_OP_ALWAYS
}

pipelineCreateInfo.stageCount = 2u

pipelineCreateInfo.pVertexInputState = pipelineVertexInputStateCreateInfo.ptr
pipelineCreateInfo.pInputAssemblyState = pipelineInputAssemblyStateCreateInfo.ptr
pipelineCreateInfo.pRasterizationState = pipelineRasterizationStateCreateInfo.ptr
pipelineCreateInfo.pColorBlendState = pipelineColorBlendStateCreateInfo.ptr
pipelineCreateInfo.pMultisampleState = pipelineMultisampleStateCreateInfo.ptr
pipelineCreateInfo.pViewportState = pipelineViewportStateCreateInfo.ptr
pipelineCreateInfo.pDepthStencilState = pipelineDepthStencilStateCreateInfo.ptr
pipelineCreateInfo.renderPass = _renderPass.renderPass.value
pipelineCreateInfo.pDynamicState = pipelineDynamicStateCreateInfo.ptr

if (!VK_CHECK(
vkCreateGraphicsPipelines(
_device.device,
_pipelineCache.value,
1u,
pipelineCreateInfo.ptr,
null,
_pipeline.ptr
)
)
)
throw RuntimeException("failed create pipeline")

}
}

...

}```

## History

Written By
Software Developer
Russian Federation
• More than 10 years experience in software development
• 3 years experience in direction of automation department.
• software engineering: experience in the whole life cycle of software development
• languages: Kotlin, Swift,Objective-C, C++, C#, Java, ASP.NET, HTML, XML, JavaScript, Visual FoxPro, MS SQL, T-SQL
• Gathering, specification and the analysis of requirements of the customer to the software.
• The analysis of a subject area.
• Estimations of labour input of development.
And so on...