Skip to content

PhysicalDevice.kt

src/jvmMain/kotlin/afield/graphics/vulkan/device/PhysicalDevice.kt · 275 lines

package afield.graphics.vulkan.device

The GPU Field renders on: PhysicalDevice.createPhysicalDevice picks one, optionally by preferred name, and the object records its properties, features, memory types, queue families, extensions and driver identity.

import org.lwjgl.PointerBuffer
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.*
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.vkCheck

class PhysicalDevice private constructor(vkPhysicalDevice: VkPhysicalDevice)

class PhysicalDevice private constructor(vkPhysicalDevice: VkPhysicalDevice) {
    private var vkDeviceExtensions: VkExtensionProperties.Buffer

var vkMemoryProperties: VkPhysicalDeviceMemoryProperties

The device's memory heaps and types, as an allocator reads them to choose a memory type.

var vkPhysicalDevice: VkPhysicalDevice

The raw VkPhysicalDevice handle, for the LWJGL calls Field has no wrapper for.

var vkPhysicalDeviceFeatures: VkPhysicalDeviceFeatures

The Vulkan 1.0 core feature set. Later feature structs are queried where they are needed.

var vkPhysicalDeviceProperties: VkPhysicalDeviceProperties

Limits, device name, API version and driver version, queried once at construction.

var vkQueueFamilyProps: VkQueueFamilyProperties.Buffer

One entry per queue family, carrying its capability flags, queue count and timestamp-valid bits. This is the list Queue.GraphicsQueue, Queue.ComputeQueue and Queue.PresentQueue search for a family to take a queue from.

var driverId: Int

VkDriverId of the ICD behind this device, e.g. VK12.VK_DRIVER_ID_MOLTENVK; 0 if unknown.

    var driverId: Int = 0
        private set

var driverName: String

The driver's own name for itself, which is what Field prints for the device it picked. Empty on a device reporting less than Vulkan 1.2, where the driver properties are not queried at all.

    var driverName: String = ""
        private set

var driverInfo: String

The driver's version string, in whatever form it chooses. Empty on the same devices driverName is.

    var driverInfo: String = ""
        private set

val isMoltenVK: Boolean get() = driverId == VK12.VK_DRIVER_ID_MOLTENVK

driverId is MoltenVK's. Nothing in Field reads this; the driver choice is made on driverId itself.

val isKosmicKrisp: Boolean get() = driverId == VK12.VK_DRIVER_ID_MESA_KOSMICKRISP

driverId is Mesa's KosmicKrisp. Nothing in Field reads this; see isMoltenVK.

init

body — 48 lines, from line 56
    init {
        MemoryStack.stackPush().use { stack ->
            this.vkPhysicalDevice = vkPhysicalDevice
            val intBuffer = stack.mallocInt(1)

            // Get device properties
            vkPhysicalDeviceProperties = VkPhysicalDeviceProperties.calloc()
            VK10.vkGetPhysicalDeviceProperties(vkPhysicalDevice, vkPhysicalDeviceProperties)
            if (vkPhysicalDeviceProperties.apiVersion() >= VK12.VK_API_VERSION_1_2) {
                val driver = VkPhysicalDeviceDriverProperties.calloc(stack).`sType$Default`()
                VK11.vkGetPhysicalDeviceProperties2(
                    vkPhysicalDevice, VkPhysicalDeviceProperties2.calloc(stack).`sType$Default`().pNext(driver.address())
                )
                driverId = driver.driverID()
                driverName = driver.driverNameString()
                driverInfo = driver.driverInfoString()
            }

            // Get device extensions
            vkCheck(
                VK10.vkEnumerateDeviceExtensionProperties(vkPhysicalDevice, null as String?, intBuffer, null),
                "Failed to get number of device extension properties"
            )
            vkDeviceExtensions = VkExtensionProperties.calloc(intBuffer[0])
            vkCheck(
                VK10.vkEnumerateDeviceExtensionProperties(
                    vkPhysicalDevice,
                    null as String?,
                    intBuffer,
                    vkDeviceExtensions
                ),
                "Failed to get extension properties"
            )

            // Get Queue family properties
            VK10.vkGetPhysicalDeviceQueueFamilyProperties(vkPhysicalDevice, intBuffer, null)
            vkQueueFamilyProps = VkQueueFamilyProperties.calloc(intBuffer[0])
            VK10.vkGetPhysicalDeviceQueueFamilyProperties(vkPhysicalDevice, intBuffer, vkQueueFamilyProps)

            vkPhysicalDeviceFeatures = VkPhysicalDeviceFeatures.calloc()
            VK10.vkGetPhysicalDeviceFeatures(vkPhysicalDevice, vkPhysicalDeviceFeatures)


            // Get Memory information and properties
            vkMemoryProperties = VkPhysicalDeviceMemoryProperties.calloc()
            VK10.vkGetPhysicalDeviceMemoryProperties(vkPhysicalDevice, vkMemoryProperties)
        }
    }

fun cleanup()

Free the five native property structs. Called for every enumerated device that is not selected, as well as for the selected one when its renderer tears down.

    fun cleanup() {
        vkMemoryProperties.free()
        vkPhysicalDeviceFeatures.free()
        vkQueueFamilyProps.free()
        vkDeviceExtensions.free()
        vkPhysicalDeviceProperties.free()
    }

val deviceName: String

The name the device reports for itself, which is also what Options.preferredDevice is matched against when choosing between several GPUs.

    val deviceName: String
        get() = vkPhysicalDeviceProperties.deviceNameString()

    private fun hasGraphicsQueueFamily(): Boolean {
        var result = false
        val numQueueFamilies = vkQueueFamilyProps.capacity()
        for (i in 0 until numQueueFamilies) {
            val familyProps = vkQueueFamilyProps[i]
            if ((familyProps.queueFlags() and VK10.VK_QUEUE_GRAPHICS_BIT) != 0) {
                result = true
                break
            }
        }
        return result
    }

    private fun hasKHRSwapChainExtension(): Boolean {
        var result = false
        val numExtensions = vkDeviceExtensions.capacity()
        for (i in 0 until numExtensions) {
            val extensionName = vkDeviceExtensions[i].extensionNameString()
            if (KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME == extensionName) {
                result = true
                break
            }
        }
        return result
    }

companion object

fun createPhysicalDevice(instance: Instance, prefferredDeviceName: String?): PhysicalDevice

body — 66 lines, from line 151
        fun createPhysicalDevice(instance: Instance, prefferredDeviceName: String?): PhysicalDevice {
            var selectedPhysicalDevice: PhysicalDevice? = null
            val xrPreferredPhys = if (Options.enableVR) Instance.peekOpenXRBootstrapPhys()
                ?: Instance.getOpenXRRuntimePhysicalDevice(instance.vkInstance) else null
            MemoryStack.stackPush().use { stack ->
                // Get available devices
                val pPhysicalDevices = getPhysicalDevices(instance, stack)
                val numDevices = pPhysicalDevices.capacity()
                if (numDevices <= 0) {
                    throw RuntimeException("No physical devices found")
                }

                // Populate available devices
                val devices: MutableList<PhysicalDevice> = ArrayList()
                for (i in 0 until numDevices) {
                    val vkPhysicalDevice = VkPhysicalDevice(pPhysicalDevices[i], instance.vkInstance)
                    val physicalDevice = PhysicalDevice(vkPhysicalDevice)

                    val deviceName = physicalDevice.deviceName
                    if (physicalDevice.hasGraphicsQueueFamily() && physicalDevice.hasKHRSwapChainExtension()) {
                        if (xrPreferredPhys != null && vkPhysicalDevice.address() == xrPreferredPhys) {
                            selectedPhysicalDevice = physicalDevice
                            println("INFO: Using XR-selected physical device ${deviceName}")
                            break
                        }
                        if (prefferredDeviceName != null && prefferredDeviceName == deviceName) {
                            selectedPhysicalDevice = physicalDevice
                            break
                        }
                        devices.add(physicalDevice)
                    } else {
                        println("ERROR: Device $deviceName does not support required extensions")
                        physicalDevice.cleanup()
                    }
                }

                // No preferred device or it does not meet requirements: on macOS take the requested
                // driver's device if the loader offered more than one driver, else the first one.
                if (selectedPhysicalDevice == null && devices.isNotEmpty()) {
                    val wantedDriver = if (Options.isMac) Options.resolvedMacVulkanDriver.driverId else 0
                    val index = devices.indexOfFirst { it.driverId == wantedDriver }.takeIf { it >= 0 } ?: 0
                    if (wantedDriver != 0 && devices[index].driverId != wantedDriver) {
                        println("WARNING: requested Vulkan driver ${Options.resolvedMacVulkanDriver.name.lowercase()} was not enumerated; " +
                            "using ${devices[index].driverName.ifEmpty { "driver ${devices[index].driverId}" }}")
                    }
                    selectedPhysicalDevice = devices.removeAt(index)
                }
                selectedPhysicalDevice?.let {
                    println("INFO: Vulkan device ${it.deviceName}, driver ${it.driverName} (${it.driverInfo})")
                }

                if (selectedPhysicalDevice == null && xrPreferredPhys != null) {
                    println("ERROR: OpenXR-selected physical device not available or lacks required graphics/swapchain support")
                }

                // Clean up non-selected devices
                for (physicalDevice in devices) {
                    physicalDevice.cleanup()
                }

                if (selectedPhysicalDevice == null) {
                    throw RuntimeException("No suitable physical devices found")
                }
            }
            return selectedPhysicalDevice ?: throw IllegalStateException(" no device ")
        }

protected fun getPhysicalDevices(instance: Instance, stack: MemoryStack): PointerBuffer

        protected fun getPhysicalDevices(instance: Instance, stack: MemoryStack): PointerBuffer {
            val pPhysicalDevices: PointerBuffer
            // Get number of physical devices
            val intBuffer = stack.mallocInt(1)
            vkCheck(
                VK10.vkEnumeratePhysicalDevices(instance.vkInstance, intBuffer, null),
                "Failed to get number of physical devices"
            )
            val numDevices = intBuffer[0]
            println("Detected $numDevices physical device(s)")

            // Populate physical devices list pointer
            pPhysicalDevices = stack.mallocPointer(numDevices)
            vkCheck(
                VK10.vkEnumeratePhysicalDevices(instance.vkInstance, intBuffer, pPhysicalDevices),
                "Failed to get physical devices"
            )
            return pPhysicalDevices
        }
    }

fun hasDeviceExtension(name: String): Boolean

Whether the device enumerated name among its extensions. This asks what the device offers, not what Field enabled; Device.enabledExtensions is the second question.

    fun hasDeviceExtension(name: String): Boolean {
        val count = vkDeviceExtensions.capacity()
        for (i in 0 until count) {
            if (vkDeviceExtensions[i].extensionNameString() == name) return true
        }
        return false
    }

fun requireExtensionsIfProfiling(profilingEnabled: Boolean): Boolean

True unless profilingEnabled and the device has no VK_KHR_performance_query, printing what is missing either way. A missing VK_EXT_host_query_reset is reported as a warning and does not fail the check. Nothing in Field calls this; Device.performanceQueryEnabled is the answer the renderer actually reads.

    fun requireExtensionsIfProfiling(profilingEnabled: Boolean): Boolean {
        if (!profilingEnabled) return true
        // swapchain already required by your code
        val perfOK = hasDeviceExtension(KHRPerformanceQuery.VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME)
        val hostResetOK = hasDeviceExtension(EXTHostQueryReset.VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME) // optional
        if (!perfOK) {
            println("ERROR: Profiling requested, but device '${deviceName}' lacks VK_KHR_performance_query")
            return false
        }
        // Host query reset is optional; just report:
        if (!hostResetOK) {
            println("WARN: No VK_EXT_host_query_reset; will use cmd-side resets or avoid reuse.")
        }
        return true
    }


}