Skip to content

ImageView.kt

src/jvmMain/kotlin/afield/graphics/texture/ImageView.kt · 177 lines

ImageView wraps a VkImageView over an image, and the small interfaces Field's shader bindings use to find views and samplers.

HasImageView and HasImageViewArray are what a texture binding accepts; PreparesSampledImages lets a source record its layout transitions before sampling, and HasDefaultTextureSampler carries the sampler a binding uses when none is given.

package afield.graphics.texture

import afield.graphics.vulkan.device.CommandBuffer
import afield.graphics.vulkan.device.nameObject
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.VK10
import org.lwjgl.vulkan.VkImageSubresourceRange
import org.lwjgl.vulkan.VkImageViewCreateInfo
import afield.graphics.vulkan.device.Device
import afield.graphics.vulkan.util.AutoDisposer
import afield.graphics.vulkan.util.vkCheck

interface HasImageView

interface HasImageView {

val imageView: ImageView

The view to bind. Implementations that follow a ring or a ping-pong swap return a different view on each read, which is what lets a binding made once keep pointing at the current image.

interface HasImageViewArray

interface HasImageViewArray {

val imageViewArray: List<HasImageView>

The views to bind, in the order the shader's array takes them. Every one is written, so the list's length is the descriptor count the array binding receives; an empty list is refused.

interface PreparesSampledImages

fun prepareSampledImagesFor(command: CommandBuffer)

interface HasDefaultTextureSampler

interface HasDefaultTextureSampler {

var defaultSampler: TextureSampler?

The sampler a texture binding uses when the module does not name one, or null to leave that to the binding. The ping-pong targets forward this to the target itself, so setting it once covers every view they hand out.

class ImageView

class ImageView(
    private val device: Device,
    /** The `VkImage` this is a view of. The constructor taking an [Image] passes its handle and records the image in [underlyingImage]; this one is for a handle whose owner is elsewhere, a swapchain image among them. */
    val image: Long,
    /** The view's format, aspect, layer range, mip count and debug name, read once here and kept for anything that needs to know what the view covers. */
    val imageViewData: ImageViewData
) : HasImageView {

val aspectMask: Int

Which aspects of the image the view covers, from imageViewData — colour, or depth with or without stencil. A combined depth+stencil view cannot be sampled, which is why a sampled depth attachment gets a second, depth-only view.

val mipLevels: Int

How many mip levels the view covers, starting at level 0. A view of one level over a mipped image is how a render pass writes a single level.

var vkImageView: Long = 0

The VkImageView handle, 0 until the constructor has created it.

var underlyingImage : Image? = null

The Image this views, when the view was made from one. It stays null for a view built over a bare handle, so a caller that needs the image's layout or memory has to hold it separately.

    private val autoDisposeToken = AutoDisposer.register(
        this,
        "ImageView(fmt=${imageViewData.format}, layers=${imageViewData.layerCount}, mips=${imageViewData.mipLevels})",
        owner = device,
    ) { /* set after init */ }

constructor(device: Device, image: Image, imageViewData: ImageViewData) : this(device, image.vkImage, imageViewData)

    constructor(device: Device, image: Image, imageViewData: ImageViewData) : this(device, image.vkImage, imageViewData) {
        source(image)
    }

init

    init {
        this.aspectMask = imageViewData.aspectMask
        this.mipLevels = imageViewData.mipLevels
        MemoryStack.stackPush().use { stack ->
            val lp = stack.mallocLong(1)
            val viewCreateInfo = VkImageViewCreateInfo.calloc(stack)
                .sType(VK10.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO)
                .pNext(0)
                .flags(imageViewData.flags)
                .image(image)
                .viewType(imageViewData.viewType)
                .format(imageViewData.format)
                .subresourceRange { it: VkImageSubresourceRange ->
                    it
                        .aspectMask(aspectMask)
                        .baseMipLevel(0)
                        .levelCount(mipLevels)
                        .baseArrayLayer(imageViewData.baseArrayLayer)
                        .layerCount(imageViewData.layerCount)
                }

            vkCheck(
                VK10.vkCreateImageView(device.vkDevice, viewCreateInfo, null, lp),
                "Failed to create image view"
            )
            vkImageView = lp[0]
        }
        imageViewData.debugName.takeIf { it.isNotBlank() }?.let {
            device.nameObject(vkImageView, VK10.VK_OBJECT_TYPE_IMAGE_VIEW, it)
        }
        autoDisposeToken.updateCleanup(snapshotCleanup())
    }

fun cleanup()

    fun cleanup() {
        if (!autoDisposeToken.disable()) return
        VK10.vkDestroyImageView(device.vkDevice, vkImageView, null)
    }

fun source(i : Image) : ImageView

    fun source(i : Image) : ImageView {
        underlyingImage = i
        return this
    }

    private fun snapshotCleanup(): () -> Unit {
        val dev = device.vkDevice
        val view = vkImageView
        return { VK10.vkDestroyImageView(dev, view, null) }
    }

override val imageView: ImageView

    override val imageView: ImageView
        get() = this

class ImageViewData

var aspectMask: Int = 0

var baseArrayLayer: Int = 0

var format: Int = 0

var layerCount: Int = 1

var mipLevels: Int = 1

var viewType: Int

var flags: Int = 0

var debugName: String = ""

init

        init {
            this.viewType = VK10.VK_IMAGE_VIEW_TYPE_2D
        }

fun aspectMask(aspectMask: Int): ImageViewData

        fun aspectMask(aspectMask: Int): ImageViewData {
            this.aspectMask = aspectMask
            return this
        }

fun baseArrayLayer(baseArrayLayer: Int): ImageViewData

        fun baseArrayLayer(baseArrayLayer: Int): ImageViewData {
            this.baseArrayLayer = baseArrayLayer
            return this
        }

fun format(format: Int): ImageViewData

        fun format(format: Int): ImageViewData {
            this.format = format
            return this
        }

fun layerCount(layerCount: Int): ImageViewData

        fun layerCount(layerCount: Int): ImageViewData {
            this.layerCount = layerCount
            return this
        }

fun flags(flags: Int): ImageViewData

        fun flags(flags: Int): ImageViewData {
            this.flags = flags
            return this
        }

fun debugName(debugName: String): ImageViewData

        fun debugName(debugName: String): ImageViewData {
            this.debugName = debugName
            return this
        }

fun mipLevels(mipLevels: Int): ImageViewData

        fun mipLevels(mipLevels: Int): ImageViewData {
            this.mipLevels = mipLevels
            return this
        }

fun viewType(viewType: Int): ImageViewData

        fun viewType(viewType: Int): ImageViewData {
            this.viewType = viewType
            return this
        }
    }


}