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.
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:
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:
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
:
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.
@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])
}
}
...
Now we're ready to continue.
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:
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:
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")
}
imageBuffers.add(Pair(_imagesBuffer[i.toInt()]!!, imageView))
}
...
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:
@ExperimentalUnsignedTypes
class Vertex(position: Vec3, color: Vec3) {
var buffer: FloatArray = FloatArray(6) { 0f }
private set
var position: Vec3 ...
var color: Vec3 ...
...
companion object {
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 {
this[0].binding = 0u
this[0].location = 0u
this[0].format = VK_FORMAT_R32G32B32_SFLOAT
this[0].offset = 0u
}
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:
@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()
val vertexBufferInfo = alloc<VkBufferCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
size = vertexBufferSize.toULong()
usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
}
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
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 ->
platform.posix.memcpy(mapped.value, buffer.addressOf(0),
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")
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")
...
...
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:
@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")
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(
radians(60f),
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 ->
platform.posix.memcpy(data.value, buffer.addressOf(0),
UboVS.SIZE.toULong())
}
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:
@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 shaderStages = allocArray<VkPipelineShaderStageCreateInfo>(2)
val cwd = ByteArray(1024)
cwd.usePinned {
getcwd(it.addressOf(0), 1024)
}
var shaderFile = "${cwd.stringFromUtf8()}/assets/shaders/triangle.vert.spv"
if(access(shaderFile, F_OK) == -1){
shaderFile = "${cwd.stringFromUtf8()}
/build/bin/mingw/mainDebugExecutable/assets/shaders/triangle.vert.spv"
if(access(shaderFile, F_OK) == -1){
shaderFile = "${cwd.stringFromUtf8()}/
build/bin/linux/mainDebugExecutable/assets/shaders/triangle.vert.spv"
}
}
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT
shaderStages[0].module = _pipelineCache.loadShader(_device.device!!, shaderFile)
shaderStages[0].pName = "main".cstr.ptr
assert(shaderStages[0].module != null)
shaderFile = "${cwd.stringFromUtf8()}/assets/shaders/triangle.frag.spv"
if(access(shaderFile, F_OK) == -1){
shaderFile = "${cwd.stringFromUtf8()}/build/
bin/mingw/mainDebugExecutable/assets/shaders/triangle.frag.spv"
if(access(shaderFile, F_OK) == -1){
shaderFile = "${cwd.stringFromUtf8()}/build/bin/linux/
mainDebugExecutable/assets/shaders/triangle.frag.spv"
}
}
...
val pipelineVertexInputStateCreateInfo =
alloc<VkPipelineVertexInputStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
vertexBindingDescriptionCount = 1u
pVertexBindingDescriptions = vertexInputBindingDescription.ptr
vertexAttributeDescriptionCount = 2u
pVertexAttributeDescriptions = vertexInputAttributs
}
val pipelineInputAssemblyStateCreateInfo =
alloc<VkPipelineInputAssemblyStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
primitiveRestartEnable = 0u
}
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
}
val pipelineRasterizationStateCreateInfo =
alloc<VkPipelineRasterizationStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
depthClampEnable = VK_FALSE.toUInt()
rasterizerDiscardEnable = VK_FALSE.toUInt()
polygonMode = VK_POLYGON_MODE_FILL
cullMode = VK_CULL_MODE_BACK_BIT
lineWidth = 1.0f
frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE
depthBiasEnable = VK_FALSE.toUInt()
}
val pipelineMultisampleStateCreateInfo =
alloc<VkPipelineMultisampleStateCreateInfo>().apply {
sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
pSampleMask = null
sampleShadingEnable = 0u
}
val blendAttachmentState = allocArray<VkPipelineColorBlendAttachmentState>(1)
blendAttachmentState[0].apply {
colorWriteMask = VK_COLOR_COMPONENT_R_BIT or
VK_COLOR_COMPONENT_G_BIT or VK_COLOR_COMPONENT_B_BIT or
VK_COLOR_COMPONENT_A_BIT
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
layout = _pipelineLayout._pipelineLayout.value
renderPass = _renderPass.renderPass.value
}
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
}
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.pStages = shaderStages
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")
vkDestroyShaderModule(_device.device, shaderStages[0].module, null)
vkDestroyShaderModule(_device.device, shaderStages[1].module, null)
}
}
...
}
History
- Vulkan API with Kotlin Native - Project Setup
- Vulkan API with Kotlin Native - Platform's Windows
- Vulkan API with Kotlin Native - Instance
- Vulkan API with Kotlin Native - Surface, Devices
- Vulkan API with Kotlin Native - SwapChain, Pipeline
- Vulkan API with Kotlin Native - Draw