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
}
}