Device.kt
src/jvmMain/kotlin/afield/graphics/vulkan/device/Device.kt · 987 lines
package afield.graphics.vulkan.device
The logical Vulkan device: Device wraps a PhysicalDevice and enables the extensions and features the renderer uses.
It also reports optional capabilities such as ray tracing limits and Metal object export, and offers Device.calibratedHostGpuTimeNS for lining GPU timestamps up with host time.
import afield.graphics.vulkan.util.OS
import afield.graphics.vulkan.util.OSType
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.vkCheck
import afield.app.diagnostics.logging.lprint
import afield.app.diagnostics.logging.lprintln
import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil.memPutAddress
import org.lwjgl.vulkan.*
import org.lwjgl.vulkan.EXTCalibratedTimestamps.VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT
import org.lwjgl.vulkan.EXTCalibratedTimestamps.VK_TIME_DOMAIN_DEVICE_EXT
import org.lwjgl.vulkan.VK10.*
import org.lwjgl.vulkan.VK11
import org.lwjgl.vulkan.VK11.vkGetPhysicalDeviceProperties2
class Device(val physicalDevice: PhysicalDevice, private val instance: Instance)
class Device(val physicalDevice: PhysicalDevice, private val instance: Instance) {
private val ngxDelegate = lazy { afield.media.reconstruction.NgxRuntime.open(this, instance) }
internal val ngxRuntime by ngxDelegate
data class RayTracingLimits
The device's ray-tracing and acceleration-structure limits, read off it once when the acceleration path is usable. Shader-binding-table layout needs the three handle sizes and alignments; geometry building needs the scratch alignment and the geometry, instance and primitive ceilings.
data class RayTracingLimits(
val shaderGroupHandleSize: Int,
val shaderGroupHandleAlignment: Int,
val shaderGroupBaseAlignment: Int,
val maxRayRecursionDepth: Int,
val maxRayDispatchInvocationCount: Int,
val maxRayHitAttributeSize: Int,
val minAccelerationStructureScratchOffsetAlignment: Int,
val maxGeometryCount: Long,
val maxInstanceCount: Long,
val maxPrimitiveCount: Long,
val maxPerStageDescriptorAccelerationStructures: Int,
val maxDescriptorSetAccelerationStructures: Int
)
val openDlssNrFeatureFailure: String?
Why DLSS ray reconstruction could not be prepared on this device, or null when it was. The extensions it asked for are already in enabledExtensions by then, so this is the message to show rather than a reason to re-probe.
val vkDevice: VkDevice
The raw VkDevice handle, which every LWJGL call in the engine takes as its first argument.
val supportsPushDescriptors: Boolean
VK_KHR_push_descriptor is available. A descriptor set layout built for push descriptors
requires this, and refuses by name without it rather than failing inside Vulkan.
val pipelineStatisticsQueryEnabled: Boolean
The query features GPU profiling can use, enabled whenever the device has them so that
Options.profiling can be switched on a running renderer. Performance counters cost something
to enable, so they are only there when profiling was on at startup.
val performanceQueryEnabled: Boolean
VK_KHR_performance_query is available and profiling was on at startup. Unlike
pipelineStatisticsQueryEnabled this cannot be switched later, because the counters cost
something to enable. The renderer reads it together with a Windows-only test.
val pushDescriptorMax: Int
The device's maxPushDescriptors, or 0 when push descriptors are unavailable. A layout
asking for more than this is refused with both numbers named.
val supportsExtendedDynamicState3: Boolean
VK_EXT_extended_dynamic_state3 is available and offers dynamic depth-clamp. Field sets
only that one EDS3 state, so that one feature decides support; polygon mode is asked for
separately where offered, since KosmicKrisp has EDS3 without it and requesting it anyway
fails device creation.
val supportsExtendedDynamicState: Boolean
VK_EXT_extended_dynamic_state is available. Unlike EDS3 this needs no feature beyond the extension.
val supportsMaintenance7: Boolean
VK_KHR_maintenance7 is available and its feature enabled. Secondary graphics command
buffers are used only where this and the option asking for them both hold.
val supportsMaintenance11: Boolean
VK_KHR_maintenance11 is available and its feature enabled. It is what lets a single array
layer of an image be aliased as its own descriptor.
val supportsDynamicRenderingUnusedAttachments: Boolean
VK_EXT_dynamic_rendering_unused_attachments is available and its feature enabled, which is
also the condition under which Field asks for it. Nothing in the engine branches on it: it
relaxes which attachment formats a pipeline may declare rather than adding a call to make.
val supportsUnifiedImageLayouts: Boolean
The device offers unifiedImageLayouts; usesUnifiedImageLayouts says whether Field turned it on.
val usesUnifiedImageLayouts: Boolean
VK_KHR_unified_image_layouts is enabled and ImageLayouts sends GENERAL.
val supportsMultiview: Boolean
VK_KHR_multiview is available, which is how one draw reaches both eyes. A render-to-texture
given a multiview view mask requires it, as does the multiview side-by-side stereo target;
both refuse by name without it.
val raySupport: RayDeviceSupport
What the hardware offers for ray tracing, before any of Field's own opt-in. The
supports… flags beside this are this probe and the ray-tracing option together.
val enabledExtensions: Set<String>
Exactly the extensions vkCreateDevice was given, deduplicated. This is what Field asked
for; PhysicalDevice.hasDeviceExtension is what the device offers.
val supportsAccelerationStructures: Boolean
Acceleration structures can be built: ray tracing is opted into and the hardware path is complete.
val supportsRayTracing: Boolean
Ray-tracing pipelines — raygen, hit and miss stages — are available and opted into.
val supportsRayQuery: Boolean
Ray queries from an ordinary compute or fragment shader are available and opted into.
val rayTracingLimits: RayTracingLimits?
The device's ray-tracing limits, or null when the acceleration path is unusable.
val supportsMultiviewMeshShader: Boolean
Mesh-stage multiview is a separate feature from ordinary multiview.
data class MeshShaderLimits
data class MeshShaderLimits(
val outputVertices: Int,
val outputPrimitives: Int,
val taskPayloadBytes: Int,
val multiviewViews: Int,
val meshInvocations: Int,
val taskInvocations: Int,
)
val meshShaderLimits: MeshShaderLimits?
val supportsOpticalFlow: Boolean
Optional shader/vision features. SDKs are not required to enable these Vulkan features.
val supportsCooperativeVector: Boolean
In-shader cooperative vectors, which learned shaders infer with. It takes three features
together — cooperative vectors, replicated composites and the Vulkan memory model — and can
be turned off with the system property afield.cooperativeVector.disabled.
val supportsCooperativeVectorTraining: Boolean
Cooperative-vector training as well as inference. Offline shader training stays available without it.
val supportsClusterAccelerationStructure: Boolean
VK_NV_cluster_acceleration_structure, which needs acceleration structures underneath it.
Switched off by the system property afield.clusterAccelerationStructure.disabled.
var usePresentIndexing = !Options.isMac
Presents are tagged with an id and waited on, which is Windows-only and needs both
VK_KHR_present_id and VK_KHR_present_wait. Construction answers this for the real device,
so the value here is only what holds until then.
var enableMeshShading = !Options.isMac
Mesh and task shader stages are enabled, which is Windows-only and needs VK_EXT_mesh_shader.
Construction overwrites the value here the same way usePresentIndexing does.
var enableRayTracing = !Options.isMac
Whether ray-tracing pipelines were enabled at device creation. supportsRayTracing is the same answer as a read-only property and is what the engine reads; construction overwrites the value here as it does for usePresentIndexing.
var usesPresentTiming = false
True when VK_EXT_present_timing was successfully enabled on the device. Never true on macOS/MoltenVK.
Cache the device's supported extensions once.
private val supportedExtensions: Set<String> by lazy { enumerateDeviceExtensions() }
val supportsMetalObjects: Boolean
VK_EXT_metal_objects (MoltenVK) — required to export an RTT color image's backing IOSurface.
val supportsMetalObjects: Boolean
get() = supportedExtensions.contains(org.lwjgl.vulkan.EXTMetalObjects.VK_EXT_METAL_OBJECTS_EXTENSION_NAME)
val supportsExternalMemoryHost: Boolean
VK_EXT_external_memory_host, enabled on macOS: an IOSurface's own pages imported as a
VkBuffer (afield.graphics.texture.IOSurfaceHostBuffer). That is how IOSurfaces reach Vulkan on
KosmicKrisp, which imports image memory only as an MTLHeap — never an IOSurface-backed
MTLTexture — and has no VK_EXT_metal_objects.
val supportsExternalMemoryHost: Boolean
get() = OS == OSType.MACOS &&
supportedExtensions.contains(EXTExternalMemoryHost.VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME)
val minImportedHostPointerAlignment: Long by lazy
The alignment an imported host pointer and its size must have (16 KiB on Apple silicon).
val minImportedHostPointerAlignment: Long by lazy {
if (!supportsExternalMemoryHost) 0L else MemoryStack.stackPush().use { stack ->
val host = VkPhysicalDeviceExternalMemoryHostPropertiesEXT.calloc(stack).`sType$Default`()
VK11.vkGetPhysicalDeviceProperties2(
physicalDevice.vkPhysicalDevice,
VkPhysicalDeviceProperties2.calloc(stack).`sType$Default`().pNext(host.address())
)
host.minImportedHostPointerAlignment()
}
}
fun requireMetalObjects(what: String)
Refuse, naming the way out, when supportsMetalObjects is false — which is the case on KosmicKrisp, the driver Field picks on Apple silicon with macOS 26 and a recent SDK. Field's two live IOSurface paths go through supportsExternalMemoryHost and work there; image-level IOSurface import and export is what needs this extension.
fun requireMetalObjects(what: String) = check(supportsMetalObjects) {
"$what needs VK_EXT_metal_objects, which the ${physicalDevice.driverName.ifEmpty { "current" }} Vulkan driver " +
"does not have. Run Field on MoltenVK for it: AFIELD_VULKAN_DRIVER=moltenvk ./field.sh"
}
val supportsExternalMemoryFd: Boolean
VK_KHR_external_memory_fd (Linux) — required to export an RTT color image's backing memory as an opaque fd for CUDA/NVENC import (see VulkanExternalMemory.kt / field-encode-linux).
val supportsExternalMemoryFd: Boolean
get() = supportedExtensions.contains(org.lwjgl.vulkan.KHRExternalMemoryFd.VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)
val supportsExternalSemaphoreFd: Boolean
Linux CUDA interop synchronization, negotiated independently of external memory.
val supportsExternalSemaphoreFd: Boolean
get() = supportedExtensions.contains(org.lwjgl.vulkan.KHRExternalSemaphoreFd.VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME)
val supportsDmaBufImport: Boolean
Extensions needed to import Electron nativePixmap dma-bufs with explicit DRM modifiers.
val supportsDmaBufImport: Boolean
get() = supportsExternalMemoryFd &&
supportedExtensions.contains(EXTExternalMemoryDmaBuf.VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME) &&
supportedExtensions.contains(EXTImageDrmFormatModifier.VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME) &&
supportedExtensions.contains(EXTQueueFamilyForeign.VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)
val supportsExternalMemoryWin32: Boolean
VK_KHR_external_memory_win32, used for duplicated Electron D3D11 NT handles.
val supportsExternalMemoryWin32: Boolean
get() = supportedExtensions.contains(KHRExternalMemoryWin32.VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME)
init
body — 586 lines, from line 257
init {
MemoryStack.stackPush().use { stack ->
val hasExt = { name: String -> supportedExtensions.contains(name) }
val ascii = { name: String -> stack.ASCII(name) }
val usePortability =
hasExt(KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) && OS == OSType.MACOS
val useExternalMemory = OS == OSType.WINDOWS
usePresentIndexing = OS == OSType.WINDOWS &&
hasExt(KHRPresentId.VK_KHR_PRESENT_ID_EXTENSION_NAME) &&
hasExt(KHRPresentWait.VK_KHR_PRESENT_WAIT_EXTENSION_NAME)
enableMeshShading =
OS != OSType.MACOS && hasExt(EXTMeshShader.VK_EXT_MESH_SHADER_EXTENSION_NAME)
// VK_EXT_present_timing has a strict dependency bundle. Enabling the extension
// without every member is invalid even if presentTiming itself is supported.
val hasPresentTimingBundle = OS != OSType.MACOS &&
hasExt(EXTPresentTiming.VK_EXT_PRESENT_TIMING_EXTENSION_NAME) &&
hasExt(KHRPresentId2.VK_KHR_PRESENT_ID_2_EXTENSION_NAME) &&
hasExt(KHRCalibratedTimestamps.VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME) &&
KHRGetSurfaceCapabilities2.VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME in instance.enabledExtensions
usesPresentTiming = if (hasPresentTimingBundle) {
val ptFeatures = VkPhysicalDevicePresentTimingFeaturesEXT.calloc(stack)
.`sType$Default`()
val presentId2Features = VkPhysicalDevicePresentId2FeaturesKHR.calloc(stack)
.`sType$Default`()
ptFeatures.pNext(presentId2Features.address())
val probe = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
pNext(ptFeatures.address())
}
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice, probe)
ptFeatures.presentTiming() && presentId2Features.presentId2()
} else false
val profilingEnabled = Options.profiling
val hasPerfQuery = hasExt(KHRPerformanceQuery.VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME)
val hasHostQueryReset = hasExt(EXTHostQueryReset.VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME)
val hasCalibrated = hasExt(EXTCalibratedTimestamps.VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)
val hasPushDescriptor = hasExt(KHRPushDescriptor.VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME)
supportsPushDescriptors = hasPushDescriptor
val supportedCoreFeatures = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
}
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice, supportedCoreFeatures)
val enablePipelineStatisticsQuery = supportedCoreFeatures.features().pipelineStatisticsQuery()
pipelineStatisticsQueryEnabled = enablePipelineStatisticsQuery
performanceQueryEnabled = profilingEnabled && hasPerfQuery
if (profilingEnabled && !enablePipelineStatisticsQuery) {
lprint(
"info,graphics",
"profiling enabled without pipelineStatisticsQuery; using timestamp timing only"
)
}
val hasExtDynamicState =
hasExt(org.lwjgl.vulkan.EXTExtendedDynamicState.VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)
val hasExtDynamicState3 =
hasExt(org.lwjgl.vulkan.EXTExtendedDynamicState3.VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
val hasMaintenance7 = hasExt(KHRMaintenance7.VK_KHR_MAINTENANCE_7_EXTENSION_NAME)
supportsMaintenance7 = if (hasMaintenance7) {
val m7Features = VkPhysicalDeviceMaintenance7FeaturesKHR.calloc(stack)
.sType(KHRMaintenance7.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_7_FEATURES_KHR)
val m7FeatureProbe = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
pNext(m7Features.address())
}
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice, m7FeatureProbe)
m7Features.maintenance7()
} else false
val hasMaintenance11 = hasExt(KHRMaintenance11.VK_KHR_MAINTENANCE_11_EXTENSION_NAME)
supportsMaintenance11 = if (hasMaintenance11) {
val m11Features = VkPhysicalDeviceMaintenance11FeaturesKHR.calloc(stack)
.sType(KHRMaintenance11.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_11_FEATURES_KHR)
val m11FeatureProbe = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
pNext(m11Features.address())
}
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice, m11FeatureProbe)
m11Features.maintenance11()
} else false
val hasDynamicRenderingUnusedAttachments =
hasExt(EXTDynamicRenderingUnusedAttachments.VK_EXT_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_EXTENSION_NAME)
supportsDynamicRenderingUnusedAttachments = if (hasDynamicRenderingUnusedAttachments) {
val unusedAttachmentFeatures = VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT.calloc(stack)
.sType(EXTDynamicRenderingUnusedAttachments.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT)
val unusedAttachmentFeatureProbe = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
pNext(unusedAttachmentFeatures.address())
}
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice, unusedAttachmentFeatureProbe)
unusedAttachmentFeatures.dynamicRenderingUnusedAttachments()
} else false
val unifiedLayoutFeatures = VkPhysicalDeviceUnifiedImageLayoutsFeaturesKHR.calloc(stack).`sType$Default`()
if (hasExt(KHRUnifiedImageLayouts.VK_KHR_UNIFIED_IMAGE_LAYOUTS_EXTENSION_NAME)) probeFeatures(stack, unifiedLayoutFeatures.address())
supportsUnifiedImageLayouts = unifiedLayoutFeatures.unifiedImageLayouts()
usesUnifiedImageLayouts = supportsUnifiedImageLayouts && Options.unifiedImageLayouts && !Options.enableVR
// Field sets only the depth-clamp dynamic state, so that is the EDS3 feature that decides
// support. polygonMode is requested only where offered: KosmicKrisp has EDS3 without it
// (and without fillModeNonSolid), and asking for it anyway fails vkCreateDevice.
val eds3Supported = VkPhysicalDeviceExtendedDynamicState3FeaturesEXT.calloc(stack).`sType$Default`()
if (hasExtDynamicState3) probeFeatures(stack, eds3Supported.address())
supportsExtendedDynamicState = hasExtDynamicState
supportsExtendedDynamicState3 = hasExtDynamicState3 && eds3Supported.extendedDynamicState3DepthClampEnable()
val eds3PolygonMode = eds3Supported.extendedDynamicState3PolygonMode()
val multiviewSupported = VkPhysicalDeviceMultiviewFeatures.calloc(stack).`sType$Default`()
probeFeatures(stack, multiviewSupported.address())
supportsMultiview = multiviewSupported.multiview()
val meshSupported = VkPhysicalDeviceMeshShaderFeaturesEXT.calloc(stack).`sType$Default`()
if (enableMeshShading) {
VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice,
VkPhysicalDeviceFeatures2.calloc(stack).`sType$Default`().pNext(meshSupported.address()))
enableMeshShading = meshSupported.meshShader() && meshSupported.taskShader()
}
supportsMultiviewMeshShader = enableMeshShading && supportsMultiview && meshSupported.multiviewMeshShader()
meshShaderLimits = if (enableMeshShading) {
val limits = VkPhysicalDeviceMeshShaderPropertiesEXT.calloc(stack).`sType$Default`()
vkGetPhysicalDeviceProperties2(physicalDevice.vkPhysicalDevice,
VkPhysicalDeviceProperties2.calloc(stack).`sType$Default`().pNext(limits.address()))
MeshShaderLimits(limits.maxMeshOutputVertices(), limits.maxMeshOutputPrimitives(),
limits.maxTaskPayloadSize(), limits.maxMeshMultiviewViewCount(),
limits.maxMeshWorkGroupInvocations(), limits.maxTaskWorkGroupInvocations())
} else null
raySupport = RayDeviceSupport.probe(physicalDevice.vkPhysicalDevice, supportedExtensions)
supportsAccelerationStructures = Options.enableRayTracing && raySupport.accelerationUsable
enableRayTracing = Options.enableRayTracing && raySupport.pipelineUsable
supportsRayTracing = enableRayTracing
supportsRayQuery = Options.enableRayTracing && raySupport.queryUsable
rayTracingLimits = if (raySupport.accelerationUsable) queryRayTracingLimits(stack) else null
val clusterFeatures=VkPhysicalDeviceClusterAccelerationStructureFeaturesNV.calloc(stack).`sType$Default`()
val opticalFeatures=VkPhysicalDeviceOpticalFlowFeaturesNV.calloc(stack).`sType$Default`()
val vectorFeatures=VkPhysicalDeviceCooperativeVectorFeaturesNV.calloc(stack).`sType$Default`()
val replicatedFeatures=VkPhysicalDeviceShaderReplicatedCompositesFeaturesEXT.calloc(stack).`sType$Default`()
val memoryModelFeatures=VkPhysicalDeviceVulkanMemoryModelFeatures.calloc(stack).`sType$Default`()
fun probeExtra(address: Long) = VK11.vkGetPhysicalDeviceFeatures2(physicalDevice.vkPhysicalDevice,
VkPhysicalDeviceFeatures2.calloc(stack).`sType$Default`().pNext(address))
if(hasExt(NVOpticalFlow.VK_NV_OPTICAL_FLOW_EXTENSION_NAME))probeExtra(opticalFeatures.address())
if(hasExt(NVCooperativeVector.VK_NV_COOPERATIVE_VECTOR_EXTENSION_NAME))probeExtra(vectorFeatures.address())
if(hasExt(EXTShaderReplicatedComposites.VK_EXT_SHADER_REPLICATED_COMPOSITES_EXTENSION_NAME))probeExtra(replicatedFeatures.address())
probeExtra(memoryModelFeatures.address())
val supportedMemoryModelDeviceScope = memoryModelFeatures.vulkanMemoryModelDeviceScope()
if(hasExt(NVClusterAccelerationStructure.VK_NV_CLUSTER_ACCELERATION_STRUCTURE_EXTENSION_NAME))probeExtra(clusterFeatures.address())
supportsClusterAccelerationStructure=supportsAccelerationStructures && clusterFeatures.clusterAccelerationStructure() &&
!java.lang.Boolean.getBoolean("afield.clusterAccelerationStructure.disabled")
supportsOpticalFlow=opticalFeatures.opticalFlow() && !java.lang.Boolean.getBoolean("afield.opticalFlow.disabled")
supportsCooperativeVector=vectorFeatures.cooperativeVector() && replicatedFeatures.shaderReplicatedComposites() &&
memoryModelFeatures.vulkanMemoryModel() && !java.lang.Boolean.getBoolean("afield.cooperativeVector.disabled")
supportsCooperativeVectorTraining=supportsCooperativeVector && vectorFeatures.cooperativeVectorTraining() &&
!java.lang.Boolean.getBoolean("afield.cooperativeVectorTraining.disabled")
// SteamVR's Linux XR_KHR_vulkan_enable2 implementation appends a
// VkPhysicalDeviceTimelineSemaphoreFeatures while creating the device. Vulkan forbids
// that promoted struct alongside VkPhysicalDeviceVulkan12Features, so the Linux XR
// path enables the other promoted features individually below.
val useSplitVulkan12Features =
OS == OSType.LINUX && Options.enableVR && Instance.hasOpenXRBootstrap()
// Build extension list defensively.
val extNames = ArrayList<String>(12).apply {
add(KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME) // not optional
if(supportsOpticalFlow)add(NVOpticalFlow.VK_NV_OPTICAL_FLOW_EXTENSION_NAME)
if(supportsCooperativeVector){add(NVCooperativeVector.VK_NV_COOPERATIVE_VECTOR_EXTENSION_NAME);add(EXTShaderReplicatedComposites.VK_EXT_SHADER_REPLICATED_COMPOSITES_EXTENSION_NAME)}
addIfSupported(KHRDynamicRendering.VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)
addIfSupported(KHRDepthStencilResolve.VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)
addIfSupported(KHRCreateRenderpass2.VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)
addIfSupported(KHRSeparateDepthStencilLayouts.VK_KHR_SEPARATE_DEPTH_STENCIL_LAYOUTS_EXTENSION_NAME)
addIfSupported(KHRPushDescriptor.VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME)
if (supportsMaintenance7) add(KHRMaintenance7.VK_KHR_MAINTENANCE_7_EXTENSION_NAME)
if (supportsMaintenance11) add(KHRMaintenance11.VK_KHR_MAINTENANCE_11_EXTENSION_NAME)
if (supportsDynamicRenderingUnusedAttachments) {
add(EXTDynamicRenderingUnusedAttachments.VK_EXT_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_EXTENSION_NAME)
}
if (usesUnifiedImageLayouts) add(KHRUnifiedImageLayouts.VK_KHR_UNIFIED_IMAGE_LAYOUTS_EXTENSION_NAME)
addIfSupported(KHR16bitStorage.VK_KHR_16BIT_STORAGE_EXTENSION_NAME)
// shaderOutputLayer/shaderOutputViewportIndex normally come from
// VkPhysicalDeviceVulkan12Features. The split Linux XR feature chain cannot carry
// that aggregate struct, so expose the equivalent pre-promotion capability.
if (useSplitVulkan12Features) {
check(hasExt(EXTShaderViewportIndexLayer.VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME)) {
"Linux OpenXR requires ${EXTShaderViewportIndexLayer.VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME}"
}
add(EXTShaderViewportIndexLayer.VK_EXT_SHADER_VIEWPORT_INDEX_LAYER_EXTENSION_NAME)
}
if (enableMeshShading) {
add(EXTMeshShader.VK_EXT_MESH_SHADER_EXTENSION_NAME)
}
if (supportsAccelerationStructures) {
add(KHRDeferredHostOperations.VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME)
add(KHRAccelerationStructure.VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)
if(supportsClusterAccelerationStructure)add(NVClusterAccelerationStructure.VK_NV_CLUSTER_ACCELERATION_STRUCTURE_EXTENSION_NAME)
if (supportsRayTracing) add(KHRRayTracingPipeline.VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME)
addIfSupported(KHRSpirv14.VK_KHR_SPIRV_1_4_EXTENSION_NAME)
addIfSupported(KHRShaderFloatControls.VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME)
if (supportsRayQuery) {
add(KHRRayQuery.VK_KHR_RAY_QUERY_EXTENSION_NAME)
}
}
if (OS == OSType.WINDOWS) {
addIfSupported(KHRExternalMemory.VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME)
addIfSupported(KHRExternalMemoryWin32.VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME)
addIfSupported(KHRPresentId.VK_KHR_PRESENT_ID_EXTENSION_NAME)
addIfSupported(KHRPresentWait.VK_KHR_PRESENT_WAIT_EXTENSION_NAME)
}
if (OS == OSType.MACOS) {
addIfSupported(KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)
// Lets an RTT color image export its backing IOSurface for GPU video streaming.
addIfSupported(org.lwjgl.vulkan.EXTMetalObjects.VK_EXT_METAL_OBJECTS_EXTENSION_NAME)
// IOSurface pages as a VkBuffer: how streaming and browser surfaces reach
// KosmicKrisp, which has no metal_objects (see supportsExternalMemoryHost).
addIfSupported(EXTExternalMemoryHost.VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME)
}
if (OS == OSType.LINUX) {
// Lets an RTT color image export its backing memory as an opaque fd, for
// zero-copy CUDA/NVENC video streaming (field-encode-linux). VK_KHR_external_memory
// is the base capability VK_KHR_external_memory_fd builds on.
addIfSupported(KHRExternalMemory.VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME)
addIfSupported(org.lwjgl.vulkan.KHRExternalMemoryFd.VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME)
addIfSupported(org.lwjgl.vulkan.KHRExternalSemaphoreFd.VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME)
addIfSupported(EXTExternalMemoryDmaBuf.VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME)
addIfSupported(EXTImageDrmFormatModifier.VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME)
addIfSupported(EXTQueueFamilyForeign.VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)
}
// VK_EXT_present_timing: scanout-jitter logging via actual display timestamps.
// Explicitly excluded from macOS/MoltenVK where the extension is absent/broken.
if (usesPresentTiming) {
add(EXTPresentTiming.VK_EXT_PRESENT_TIMING_EXTENSION_NAME)
add(KHRPresentId2.VK_KHR_PRESENT_ID_2_EXTENSION_NAME)
add(KHRCalibratedTimestamps.VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)
}
// Host query reset whatever Options.profiling says now: profiling can be switched on later.
addIfSupported(EXTHostQueryReset.VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME)
if (Options.profiling) {
addIfSupported(KHRPerformanceQuery.VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME)
addIfSupported(EXTCalibratedTimestamps.VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)
}
addIfSupported(KHRShaderNonSemanticInfo.VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME)
addIfSupported(org.lwjgl.vulkan.EXTExtendedDynamicState.VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME)
if (supportsExtendedDynamicState3) add(org.lwjgl.vulkan.EXTExtendedDynamicState3.VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME)
if (Options.enableVR) {
val d = Instance.getOpenXRRequiredDeviceExts(physicalDevice.vkPhysicalDevice.address())
lprint("info,openxr", "required device extensions are $d")
addAll(d)
}
// this is useful outside of the context of VR as well
addIfSupported(KHRMultiview.VK_KHR_MULTIVIEW_EXTENSION_NAME)
// VK_EXT_hdr_metadata lets us advertise mastering-display primaries to the OS/compositor
// for HDR10 swap chains. Opt-in only; safe to skip when unsupported.
if (Options.enableHDR) {
addIfSupported(EXTHdrMetadata.VK_EXT_HDR_METADATA_EXTENSION_NAME)
}
}
val nrPlanResult=afield.graphics.render.neural.OpenDlssNrSupport.prepare(instance.vkInstance.address(),physicalDevice.vkPhysicalDevice.address())
val nrPlan=nrPlanResult.getOrNull()
openDlssNrFeatureFailure=nrPlanResult.exceptionOrNull()?.message
nrPlan?.let { extNames.addAll(it.native.required()) }
extNames.addAll(afield.media.reconstruction.NgxSupport.deviceExtensions(instance, physicalDevice.vkPhysicalDevice.address(), supportedExtensions))
val dedupedExtNames = extNames.distinct()
enabledExtensions = dedupedExtNames.toSet()
val requiredExtensions = stack.mallocPointer(dedupedExtNames.size).apply {
dedupedExtNames.forEach { put(ascii(it)) }
flip()
}
// --- Core features via VkPhysicalDeviceFeatures2 + Vulkan12/13
val features = VkPhysicalDeviceFeatures2.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
// Requested where offered, and named when not, so a driver missing one reports it here
// rather than failing vkCreateDevice with VK_ERROR_FEATURE_NOT_PRESENT. KosmicKrisp
// before SDK 1.4.357 lacked vertexPipelineStoresAndAtomics.
val core = supportedCoreFeatures.features()
val missing = listOfNotNull(
"fragmentStoresAndAtomics".takeUnless { core.fragmentStoresAndAtomics() },
"vertexPipelineStoresAndAtomics".takeUnless { core.vertexPipelineStoresAndAtomics() },
"shaderInt64".takeUnless { core.shaderInt64() },
)
if (missing.isNotEmpty()) {
lprintln("warning,vulkan", "${physicalDevice.driverName} lacks $missing; shaders that need them will fail to build")
}
features().fragmentStoresAndAtomics(core.fragmentStoresAndAtomics())
features().vertexPipelineStoresAndAtomics(core.vertexPipelineStoresAndAtomics())
features().shaderInt64(core.shaderInt64())
features().shaderInt16(supportedCoreFeatures.features().shaderInt16())
// Guard: geometryShader is unsupported on Apple Silicon / MoltenVK.
features().geometryShader(physicalDevice.vkPhysicalDeviceFeatures.geometryShader())
features().robustBufferAccess(Options.robustness)
features().pipelineStatisticsQuery(enablePipelineStatisticsQuery)
}
val features11 = VkPhysicalDeviceVulkan11Features.calloc(stack).apply {
`sType$Default`()
multiview(supportsMultiview)
shaderDrawParameters(true)
storagePushConstant16(true)
}
val features12 = VkPhysicalDeviceVulkan12Features.calloc(stack).apply {
sType(VK12.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES)
shaderOutputLayer(true)
shaderOutputViewportIndex(true)
shaderFloat16(true)
bufferDeviceAddress(true)
timelineSemaphore(true)
// Core in 1.2; GPU profiling resets its query pools from the host (see timing.kt).
hostQueryReset(true)
vulkanMemoryModel(supportsCooperativeVector)
// Ordinary atomic shaders also use Device scope when this memory model is active.
vulkanMemoryModelDeviceScope(supportsCooperativeVector && supportedMemoryModelDeviceScope)
// Descriptor indexing (promoted to 1.2) — bindless-friendly bits
descriptorIndexing(true)
runtimeDescriptorArray(true)
descriptorBindingPartiallyBound(true)
descriptorBindingVariableDescriptorCount(true)
// Non-uniform indexing (use as needed)
shaderSampledImageArrayNonUniformIndexing(true)
shaderStorageImageArrayNonUniformIndexing(true)
shaderUniformBufferArrayNonUniformIndexing(true)
shaderStorageBufferArrayNonUniformIndexing(true)
// If you use texel buffers with non-uniform indexing, enable these too:
// shaderUniformTexelBufferArrayNonUniformIndexing(true)
// shaderStorageTexelBufferArrayNonUniformIndexing(true)
// Leave all *UpdateAfterBind* = false unless your pool/layouts use the corresponding flags.
// descriptorBindingSampledImageUpdateAfterBind(true)
// descriptorBindingStorageImageUpdateAfterBind(true)
// descriptorBindingUniformBufferUpdateAfterBind(true)
// descriptorBindingStorageBufferUpdateAfterBind(true)
// descriptorBindingUniformTexelBufferUpdateAfterBind(true)
// descriptorBindingStorageTexelBufferUpdateAfterBind(true)
// descriptorBindingUpdateUnusedWhilePending(true)
}
val features13 = VkPhysicalDeviceVulkan13Features.calloc(stack).apply {
sType(VK13.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES)
dynamicRendering(true)
synchronization2(true)
}
val eds3 = VkPhysicalDeviceExtendedDynamicState3FeaturesEXT.calloc(stack)
.sType(org.lwjgl.vulkan.EXTExtendedDynamicState3.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT)
.extendedDynamicState3PolygonMode(eds3PolygonMode)
.extendedDynamicState3DepthClampEnable(true)
val eds = VkPhysicalDeviceExtendedDynamicStateFeaturesEXT.calloc(stack)
.sType(org.lwjgl.vulkan.EXTExtendedDynamicState.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT)
.extendedDynamicState(true)
features.pNext(features11.address())
var featureTail = features11.address()
if (useSplitVulkan12Features) {
val shaderFloat16 = VkPhysicalDeviceShaderFloat16Int8Features.calloc(stack)
.`sType$Default`()
.shaderFloat16(true)
memPutAddress(featureTail + VkBaseOutStructure.PNEXT, shaderFloat16.address())
featureTail = shaderFloat16.address()
val bufferDeviceAddress = VkPhysicalDeviceBufferDeviceAddressFeatures.calloc(stack)
.`sType$Default`()
.bufferDeviceAddress(true)
memPutAddress(featureTail + VkBaseOutStructure.PNEXT, bufferDeviceAddress.address())
featureTail = bufferDeviceAddress.address()
val descriptorIndexing = VkPhysicalDeviceDescriptorIndexingFeatures.calloc(stack)
.`sType$Default`()
.runtimeDescriptorArray(true)
.descriptorBindingPartiallyBound(true)
.descriptorBindingVariableDescriptorCount(true)
.shaderSampledImageArrayNonUniformIndexing(true)
.shaderStorageImageArrayNonUniformIndexing(true)
.shaderUniformBufferArrayNonUniformIndexing(true)
.shaderStorageBufferArrayNonUniformIndexing(true)
memPutAddress(featureTail + VkBaseOutStructure.PNEXT, descriptorIndexing.address())
featureTail = descriptorIndexing.address()
if(supportsCooperativeVector) {
memoryModelFeatures.vulkanMemoryModelDeviceScope(supportedMemoryModelDeviceScope).vulkanMemoryModelAvailabilityVisibilityChains(false).pNext(0L)
memPutAddress(featureTail+VkBaseOutStructure.PNEXT,memoryModelFeatures.address());featureTail=memoryModelFeatures.address()
}
} else {
memPutAddress(featureTail + VkBaseOutStructure.PNEXT, features12.address())
featureTail = features12.address()
}
memPutAddress(featureTail + VkBaseOutStructure.PNEXT, features13.address())
var tail = features13.address()
if(supportsOpticalFlow) {
opticalFeatures.pNext(0L);memPutAddress(tail+VkBaseOutStructure.PNEXT,opticalFeatures.address());tail=opticalFeatures.address()
}
if(supportsCooperativeVector) {
vectorFeatures.cooperativeVectorTraining(supportsCooperativeVectorTraining).pNext(0L)
memPutAddress(tail+VkBaseOutStructure.PNEXT,vectorFeatures.address());tail=vectorFeatures.address()
replicatedFeatures.pNext(0L);memPutAddress(tail+VkBaseOutStructure.PNEXT,replicatedFeatures.address());tail=replicatedFeatures.address()
}
if(supportsClusterAccelerationStructure) {
clusterFeatures.pNext(0L);memPutAddress(tail+VkBaseOutStructure.PNEXT,clusterFeatures.address());tail=clusterFeatures.address()
}
if (supportsExtendedDynamicState) {
memPutAddress(tail + VkBaseOutStructure.PNEXT, eds.address()); tail = eds.address()
}
if (supportsExtendedDynamicState3) {
memPutAddress(tail + VkBaseOutStructure.PNEXT, eds3.address()); tail = eds3.address()
}
if (supportsMaintenance7) {
val m7 = VkPhysicalDeviceMaintenance7FeaturesKHR.calloc(stack)
.sType(KHRMaintenance7.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_7_FEATURES_KHR)
.maintenance7(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, m7.address()); tail = m7.address()
}
if (supportsMaintenance11) {
val m11 = VkPhysicalDeviceMaintenance11FeaturesKHR.calloc(stack)
.sType(KHRMaintenance11.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_11_FEATURES_KHR)
.maintenance11(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, m11.address()); tail = m11.address()
}
if (supportsDynamicRenderingUnusedAttachments) {
val unusedAttachments = VkPhysicalDeviceDynamicRenderingUnusedAttachmentsFeaturesEXT.calloc(stack)
.sType(EXTDynamicRenderingUnusedAttachments.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_UNUSED_ATTACHMENTS_FEATURES_EXT)
.dynamicRenderingUnusedAttachments(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, unusedAttachments.address()); tail = unusedAttachments.address()
}
if (usesUnifiedImageLayouts) {
val unified = VkPhysicalDeviceUnifiedImageLayoutsFeaturesKHR.calloc(stack).`sType$Default`()
.unifiedImageLayouts(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, unified.address()); tail = unified.address()
}
// Mesh/task features are queried independently; Linux is a first-class mesh path.
if (enableMeshShading) {
val mesh = VkPhysicalDeviceMeshShaderFeaturesEXT.calloc(stack)
.sType(EXTMeshShader.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT)
.meshShader(true).taskShader(true).multiviewMeshShader(supportsMultiviewMeshShader)
memPutAddress(tail + VkBaseOutStructure.PNEXT, mesh.address()); tail = mesh.address()
}
if (usePresentIndexing) {
val presentId = VkPhysicalDevicePresentIdFeaturesKHR.calloc(stack)
.sType(KHRPresentId.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR)
.presentId(true)
val presentWait = VkPhysicalDevicePresentWaitFeaturesKHR.calloc(stack)
.sType(KHRPresentWait.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR)
.presentWait(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, presentId.address()); tail = presentId.address()
memPutAddress(tail + VkBaseOutStructure.PNEXT, presentWait.address()); tail = presentWait.address()
}
// VK_EXT_present_timing feature enable — never on macOS/MoltenVK.
if (usesPresentTiming) {
val presentTiming = VkPhysicalDevicePresentTimingFeaturesEXT.calloc(stack)
.`sType$Default`()
.presentTiming(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, presentTiming.address()); tail = presentTiming.address()
val presentId2 = VkPhysicalDevicePresentId2FeaturesKHR.calloc(stack)
.`sType$Default`()
.presentId2(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, presentId2.address()); tail = presentId2.address()
}
if (supportsAccelerationStructures) {
val accelerationStructure = VkPhysicalDeviceAccelerationStructureFeaturesKHR.calloc(stack)
.sType(KHRAccelerationStructure.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR)
.accelerationStructure(true)
val rayTracingPipeline = VkPhysicalDeviceRayTracingPipelineFeaturesKHR.calloc(stack)
.sType(KHRRayTracingPipeline.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR)
.rayTracingPipeline(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, accelerationStructure.address()); tail =
accelerationStructure.address()
if (supportsRayTracing) {
memPutAddress(tail + VkBaseOutStructure.PNEXT, rayTracingPipeline.address())
tail = rayTracingPipeline.address()
}
if (supportsRayQuery) {
val rayQuery = VkPhysicalDeviceRayQueryFeaturesKHR.calloc(stack)
.sType(KHRRayQuery.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR)
.rayQuery(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, rayQuery.address()); tail = rayQuery.address()
}
}
// Profiling features (conditionally).
if (profilingEnabled && hasPerfQuery) {
val perf = VkPhysicalDevicePerformanceQueryFeaturesKHR.calloc(stack)
.sType(KHRPerformanceQuery.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR)
.performanceCounterQueryPools(true)
.performanceCounterMultipleQueryPools(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, perf.address()); tail = perf.address()
}
// Only the split chain needs the extension struct: alongside VkPhysicalDeviceVulkan12Features
// it is invalid (VUID-VkDeviceCreateInfo-pNext-02830), and that struct carries the bit itself.
if (hasHostQueryReset && useSplitVulkan12Features) {
val hostReset = VkPhysicalDeviceHostQueryResetFeatures.calloc(stack)
.sType(EXTHostQueryReset.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT)
.hostQueryReset(true)
memPutAddress(tail + VkBaseOutStructure.PNEXT, hostReset.address()); tail = hostReset.address()
}
nrPlan?.apply(features.address())
// Enable all queue families.
val qProps = physicalDevice.vkQueueFamilyProps
val qInfos = VkDeviceQueueCreateInfo.calloc(qProps.capacity(), stack)
for (i in 0 until qProps.capacity()) {
val priorities = stack.callocFloat(qProps[i].queueCount())
qInfos[i]
.sType(VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO)
.queueFamilyIndex(i)
.pQueuePriorities(priorities)
}
val ci = VkDeviceCreateInfo.calloc(stack)
.sType(VK12.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO)
.ppEnabledExtensionNames(requiredExtensions)
.pQueueCreateInfos(qInfos)
.pNext(features.address())
try {
if (Options.enableVR && Instance.hasOpenXRBootstrap()) {
vkDevice = Instance.createVkDeviceViaXR(ci, instance.vkInstance, physicalDevice.vkPhysicalDevice)
} else {
val pDev = stack.mallocPointer(1)
vkCheck(vkCreateDevice(physicalDevice.vkPhysicalDevice, ci, null, pDev), "Failed to create device")
vkDevice = VkDevice(pDev[0], physicalDevice.vkPhysicalDevice, ci)
}
} finally { nrPlan?.close() }
// Query push-descriptor limits (0 if unsupported)
pushDescriptorMax = if (hasPushDescriptor) {
val props2 = VkPhysicalDeviceProperties2.calloc(stack)
.sType(VK11.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2)
val pushProps = VkPhysicalDevicePushDescriptorPropertiesKHR.calloc(stack)
.sType(KHRPushDescriptor.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR)
props2.pNext(pushProps.address())
vkGetPhysicalDeviceProperties2(physicalDevice.vkPhysicalDevice, props2)
pushProps.maxPushDescriptors()
} else 0
lprintln(
"info,device",
"Device push descriptors: supported=$supportsPushDescriptors, maxPushDescriptors=$pushDescriptorMax"
)
lprintln(
"info,device",
"Device ray tracing: pipeline=$supportsRayTracing, rayQuery=$supportsRayQuery, limits=$rayTracingLimits"
)
lprintln(
"info,device",
"Device maintenance: m7=$supportsMaintenance7, m11=$supportsMaintenance11, dynamicRenderingUnusedAttachments=$supportsDynamicRenderingUnusedAttachments"
)
lprintln(
"info,device",
"Device present timing: usesPresentTiming=$usesPresentTiming"
)
// Only now: an image created before the device existed could not have used it anyway.
ImageLayouts.unified = usesUnifiedImageLayouts
lprintln(
"info,device",
"Device unified image layouts: supported=$supportsUnifiedImageLayouts, used=$usesUnifiedImageLayouts"
)
}
}
fun waitIdle()
Block until every queue on this device is idle. The blunt instrument teardown and swapchain rebuilds use; a per-queue or per-fence wait is what a frame should use.
fun waitIdle() {
vkDeviceWaitIdle(vkDevice)
}
var acceptingSubmissions
False once the owning Renderer's teardown has begun (closeSubmissions); Queue.submit
then refuses by name instead of queuing work against buffers that are about to be freed.
@Volatile
var acceptingSubmissions = true
private set
fun closeSubmissions()
Refuse every later Queue.submit; taken under Queue.globalQueueLock so none is mid-flight.
fun closeSubmissions() {
synchronized(Queue.globalQueueLock) { acceptingSubmissions = false }
}
fun cleanup()
Close the reconstruction runtime if one was ever opened, then destroy the device. Everything allocated from the device has to be gone first; closeSubmissions and waitIdle are how a renderer gets there.
fun cleanup() {
if (ngxDelegate.isInitialized()) ngxRuntime.getOrNull()?.close()
vkDestroyDevice(vkDevice, null)
}
Fill the feature struct at address (its sType set, pNext 0) with what the device supports.
source — 50 lines, from line 876
private fun probeFeatures(stack: MemoryStack, address: Long) {
VK11.vkGetPhysicalDeviceFeatures2(
physicalDevice.vkPhysicalDevice,
VkPhysicalDeviceFeatures2.calloc(stack).`sType$Default`().pNext(address)
)
}
private fun MutableList<String>.addIfSupported(name: String) {
if (name in supportedExtensions) add(name)
}
private fun enumerateDeviceExtensions(): Set<String> {
val out = HashSet<String>()
MemoryStack.stackPush().use { stack ->
val countBuf = stack.callocInt(1)
vkEnumerateDeviceExtensionProperties(physicalDevice.vkPhysicalDevice, null as String?, countBuf, null)
val props = VkExtensionProperties.calloc(countBuf[0], stack)
vkEnumerateDeviceExtensionProperties(physicalDevice.vkPhysicalDevice, null as String?, countBuf, props)
repeat(props.capacity()) { i -> out += props[i].extensionNameString() }
}
return out
}
private fun queryRayTracingLimits(stack: MemoryStack): RayTracingLimits {
val props2 = VkPhysicalDeviceProperties2.calloc(stack)
.sType(VK11.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2)
val rtProps = VkPhysicalDeviceRayTracingPipelinePropertiesKHR.calloc(stack)
.sType(KHRRayTracingPipeline.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR)
val asProps = VkPhysicalDeviceAccelerationStructurePropertiesKHR.calloc(stack)
.sType(KHRAccelerationStructure.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR)
props2.pNext(asProps.address())
if (raySupport.pipelineExtension) asProps.pNext(rtProps.address())
VK11.vkGetPhysicalDeviceProperties2(physicalDevice.vkPhysicalDevice, props2)
return RayTracingLimits(
shaderGroupHandleSize = rtProps.shaderGroupHandleSize(),
shaderGroupHandleAlignment = rtProps.shaderGroupHandleAlignment(),
shaderGroupBaseAlignment = rtProps.shaderGroupBaseAlignment(),
maxRayRecursionDepth = rtProps.maxRayRecursionDepth(),
maxRayDispatchInvocationCount = rtProps.maxRayDispatchInvocationCount(),
maxRayHitAttributeSize = rtProps.maxRayHitAttributeSize(),
minAccelerationStructureScratchOffsetAlignment = asProps.minAccelerationStructureScratchOffsetAlignment(),
maxGeometryCount = asProps.maxGeometryCount(),
maxInstanceCount = asProps.maxInstanceCount(),
maxPrimitiveCount = asProps.maxPrimitiveCount(),
maxPerStageDescriptorAccelerationStructures = asProps.maxPerStageDescriptorAccelerationStructures(),
maxDescriptorSetAccelerationStructures = asProps.maxDescriptorSetAccelerationStructures()
)
}
fun hasCalibrateableDomains(): Boolean
True if DEVICE and CLOCK_MONOTONIC time domains are calibrateable (guarded by extension presence).
fun hasCalibrateableDomains(): Boolean {
if (!supportedExtensions.contains(EXTCalibratedTimestamps.VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) {
return false
}
MemoryStack.stackPush().use { stack ->
val countBuf = stack.mallocInt(1)
EXTCalibratedTimestamps.vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(
physicalDevice.vkPhysicalDevice, countBuf, null as java.nio.IntBuffer?
)
val count = countBuf[0]
if (count == 0) return false
val domains = stack.mallocInt(count)
EXTCalibratedTimestamps.vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(
physicalDevice.vkPhysicalDevice, countBuf, domains
)
var hasDevice = false
var hasMono = false
for (i in 0 until count) {
when (domains[i]) {
VK_TIME_DOMAIN_DEVICE_EXT -> hasDevice = true
VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT -> hasMono = true
}
}
return hasDevice && hasMono
}
}
fun calibratedHostGpuTimeNS(): Pair<Long, Long>
Returns (hostMonotonicNS, gpuNS). Requires VK_EXT_calibrated_timestamps. Callers should gate on hasCalibrateableDomains.
fun calibratedHostGpuTimeNS(): Pair<Long, Long> {
require(supportedExtensions.contains(EXTCalibratedTimestamps.VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) {
"VK_EXT_calibrated_timestamps not enabled"
}
MemoryStack.stackPush().use { stack ->
val infos = VkCalibratedTimestampInfoEXT.calloc(2, stack)
infos[0]
.sType(EXTCalibratedTimestamps.VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT)
.timeDomain(VK_TIME_DOMAIN_DEVICE_EXT)
infos[1]
.sType(EXTCalibratedTimestamps.VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT)
.timeDomain(VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT)
val timestamps = stack.mallocLong(2)
val maxDeviation = stack.mallocLong(1)
val res = EXTCalibratedTimestamps.vkGetCalibratedTimestampsEXT(
vkDevice, infos, timestamps, maxDeviation
)
vkCheck(res, "vkGetCalibratedTimestampsEXT failed")
val gpuNS = timestamps[0]
val hostNS = timestamps[1]
return hostNS to gpuNS
}
}
}