From 9ecf98a26d60647c591a8d24c98d9025a4f196d3 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 20 May 2025 20:36:21 +0200 Subject: [PATCH] VkInstance: Refactor Validation and Extensions selection --- .../fr/mrdev023/vulkan_java/vk/Instance.java | 88 ++-------- .../vk/utils/InstanceExtensions.java | 154 ++++++++++++++++++ .../vk/utils/InstanceValidationLayers.java | 146 +++++++++++++++++ .../InstanceValidationLayersSelector.java | 94 ----------- 4 files changed, 315 insertions(+), 167 deletions(-) create mode 100644 src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceExtensions.java create mode 100644 src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayers.java delete mode 100644 src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayersSelector.java diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java b/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java index ae0f6e9..3434ae7 100644 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java @@ -1,19 +1,17 @@ package fr.mrdev023.vulkan_java.vk; import org.lwjgl.PointerBuffer; -import org.lwjgl.glfw.GLFWVulkan; import org.lwjgl.system.*; import org.lwjgl.vulkan.*; import org.tinylog.Logger; -import fr.mrdev023.vulkan_java.vk.utils.InstanceValidationLayersSelector; +import fr.mrdev023.vulkan_java.vk.utils.InstanceExtensions; +import fr.mrdev023.vulkan_java.vk.utils.InstanceValidationLayers; import java.nio.*; -import java.util.*; import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck; import static org.lwjgl.vulkan.EXTDebugUtils.*; -import static org.lwjgl.vulkan.KHRPortabilityEnumeration.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; import static org.lwjgl.vulkan.VK11.*; public class Instance { @@ -23,7 +21,6 @@ public class Instance { public static final int MESSAGE_TYPE_BITMASK = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; - private static final String PORTABILITY_EXTENSION = "VK_KHR_portability_enumeration"; private final VkInstance vkInstance; @@ -31,7 +28,6 @@ public class Instance { private long vkDebugHandle; public Instance(boolean validate) throws VulkanError { - Logger.debug("Creating Vulkan instance"); try (MemoryStack stack = MemoryStack.stackPush()) { // Create application information ByteBuffer appShortName = stack.UTF8("VulkanBook"); @@ -43,52 +39,19 @@ public class Instance { .engineVersion(0) .apiVersion(VK_API_VERSION_1_1); - // Validation layers - var validationLayers = new InstanceValidationLayersSelector() - .withValidationLayers(validate) + InstanceValidationLayers validationLayers = new InstanceValidationLayers.Selector() + .includeValidationLayers(validate) .selectValidationLayers(); - // Set required layers - PointerBuffer requiredLayers = null; - if (!validationLayers.isEmpty()) { - requiredLayers = stack.mallocPointer(validationLayers.size()); - for (String layer : validationLayers) { - requiredLayers.put(stack.ASCII(layer)); - } - } + InstanceExtensions instanceExtensions = new InstanceExtensions.Selector() + .withValidationLayers(validationLayers) + .selectInstanceExtensions(); - Set instanceExtensions = getInstanceExtensions(); - - // GLFW Extension - PointerBuffer glfwExtensions = GLFWVulkan.glfwGetRequiredInstanceExtensions(); - if (glfwExtensions == null) { - throw new RuntimeException("Failed to find the GLFW platform surface extensions"); - } - - PointerBuffer requiredExtensions; - - boolean usePortability = instanceExtensions.contains(PORTABILITY_EXTENSION) && - VulkanUtils.getOS() == VulkanUtils.OSType.MACOS; - if (!validationLayers.isEmpty()) { - ByteBuffer vkDebugUtilsExtension = stack.UTF8(EXTDebugUtils.VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - int numExtensions = usePortability ? glfwExtensions.remaining() + 2 : glfwExtensions.remaining() + 1; - requiredExtensions = stack.mallocPointer(numExtensions); - requiredExtensions.put(glfwExtensions).put(vkDebugUtilsExtension); - if (usePortability) { - requiredExtensions.put(stack.UTF8(PORTABILITY_EXTENSION)); - } - } else { - int numExtensions = usePortability ? glfwExtensions.remaining() + 1 : glfwExtensions.remaining(); - requiredExtensions = stack.mallocPointer(numExtensions); - requiredExtensions.put(glfwExtensions); - if (usePortability) { - requiredExtensions.put(stack.UTF8(KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)); - } - } - requiredExtensions.flip(); + PointerBuffer instanceExtensionsBuffer = instanceExtensions.writeToStack(stack); + PointerBuffer validationLayersBuffer = validationLayers.writeToStack(stack); long extension = MemoryUtil.NULL; - if (!validationLayers.isEmpty()) { + if (validationLayers.hasValidationLayers()) { debugUtils = createDebugCallBack(); extension = debugUtils.address(); } @@ -98,24 +61,23 @@ public class Instance { .sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO) .pNext(extension) .pApplicationInfo(appInfo) - .ppEnabledLayerNames(requiredLayers) - .ppEnabledExtensionNames(requiredExtensions); - if (usePortability) { - instanceInfo.flags(VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR); - } + .ppEnabledLayerNames(validationLayersBuffer) + .ppEnabledExtensionNames(instanceExtensionsBuffer); + instanceExtensions.writeInstanceCreateInfoFlags(instanceInfo); PointerBuffer pInstance = stack.mallocPointer(1); vkCheck(vkCreateInstance(instanceInfo, null, pInstance), "Error creating instance"); vkInstance = new VkInstance(pInstance.get(0), instanceInfo); vkDebugHandle = VK_NULL_HANDLE; - if (!validationLayers.isEmpty()) { + if (validationLayers.hasValidationLayers()) { LongBuffer longBuff = stack.mallocLong(1); vkCheck(vkCreateDebugUtilsMessengerEXT(vkInstance, debugUtils, null, longBuff), "Error creating debug utils"); vkDebugHandle = longBuff.get(0); } } + Logger.debug("Vulkan instance created"); } private static VkDebugUtilsMessengerCreateInfoEXT createDebugCallBack() { @@ -152,26 +114,6 @@ public class Instance { } } - private Set getInstanceExtensions() { - Set instanceExtensions = new HashSet<>(); - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer numExtensionsBuf = stack.callocInt(1); - vkEnumerateInstanceExtensionProperties((String) null, numExtensionsBuf, null); - int numExtensions = numExtensionsBuf.get(0); - Logger.debug("Instance supports [{}] extensions", numExtensions); - - VkExtensionProperties.Buffer instanceExtensionsProps = VkExtensionProperties.calloc(numExtensions, stack); - vkEnumerateInstanceExtensionProperties((String) null, numExtensionsBuf, instanceExtensionsProps); - for (int i = 0; i < numExtensions; i++) { - VkExtensionProperties props = instanceExtensionsProps.get(i); - String extensionName = props.extensionNameString(); - instanceExtensions.add(extensionName); - Logger.debug("Supported instance extension [{}]", extensionName); - } - } - return instanceExtensions; - } - public VkInstance getVkInstance() { return vkInstance; } diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceExtensions.java b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceExtensions.java new file mode 100644 index 0000000..582526c --- /dev/null +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceExtensions.java @@ -0,0 +1,154 @@ +package fr.mrdev023.vulkan_java.vk.utils; + +import java.nio.IntBuffer; +import java.util.HashSet; +import java.util.Set; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.glfw.GLFWVulkan; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.EXTDebugUtils; +import org.lwjgl.vulkan.KHRPortabilityEnumeration; +import org.lwjgl.vulkan.VK10; +import org.lwjgl.vulkan.VkExtensionProperties; +import org.lwjgl.vulkan.VkInstanceCreateInfo; +import org.tinylog.Logger; + +import fr.mrdev023.vulkan_java.vk.VulkanUtils; + +/** + * This class is used to store the instance extensions to use to create the Vulkan instance. + * + * @see InstanceExtensions.Selector to select the instance extensions + */ +public class InstanceExtensions { + private Set instanceExtensions; + private PointerBuffer glfwRequiredExtensions; + + private InstanceExtensions(Set instanceExtensions, PointerBuffer glfwRequiredExtensions) { + this.instanceExtensions = instanceExtensions; + this.glfwRequiredExtensions = glfwRequiredExtensions; + } + + public boolean hasInstanceExtensions() { + return !instanceExtensions.isEmpty(); + } + + public Set getInstanceExtensions() { + return instanceExtensions; + } + + public PointerBuffer writeToStack(MemoryStack stack) { + int numExtensions = instanceExtensions.size() + glfwRequiredExtensions.remaining(); + PointerBuffer requiredExtensions = stack.mallocPointer(numExtensions); + + requiredExtensions.put(glfwRequiredExtensions); + for (String extension : instanceExtensions) { + requiredExtensions.put(stack.UTF8(extension)); + } + requiredExtensions.flip(); + + return requiredExtensions; + } + + public void writeInstanceCreateInfoFlags(VkInstanceCreateInfo instanceInfo) { + if (instanceExtensions.contains(KHRPortabilityEnumeration.VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { + instanceInfo.flags(KHRPortabilityEnumeration.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR); + } + } + + /** + * This class is used to automatically select the instance extensions + * + *
Specification
+ *
    + *
  • Get the GLFW required extensions to create the Window Surface
  • + *
  • Get the portability extensions if the OS needs it
  • + *
  • Add Debug Utils extension if validation layers are enabled
  • + *
+ * + *
See Also
+ *
    + *
  • {@link #selectInstanceExtensions()}
  • + *
  • {@link #withValidationLayers(InstanceValidationLayers)}
  • + *
+ */ + public static class Selector { + private InstanceValidationLayers validationLayers; + + public Selector withValidationLayers(InstanceValidationLayers validationLayers) { + this.validationLayers = validationLayers; + return this; + } + + /** + * Selects the instance extensions. + * + * @return the set of instance extensions + */ + public InstanceExtensions selectInstanceExtensions() { + Set instanceExtensions = getInstanceExtensions(); + log("Supported instance extensions", instanceExtensions); + + // GLFW Extension + PointerBuffer glfwExtensions = GLFWVulkan.glfwGetRequiredInstanceExtensions(); + if (glfwExtensions == null) { + throw new RuntimeException("Failed to find the GLFW platform surface extensions"); + } + + Set portabilityExtensions = getPortabilityExtensions(instanceExtensions); + log("Portability extensions used", portabilityExtensions); + + Set selectedExtensions = new HashSet<>(); + selectedExtensions.addAll(portabilityExtensions); + + if (validationLayers != null && validationLayers.hasValidationLayers()) { + selectedExtensions.add(EXTDebugUtils.VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + log("Selected instance extensions", selectedExtensions); + + return new InstanceExtensions(selectedExtensions, glfwExtensions); + } + + private Set getInstanceExtensions() { + Set instanceExtensions = new HashSet<>(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + IntBuffer numExtensionsBuf = stack.callocInt(1); + VK10.vkEnumerateInstanceExtensionProperties((String) null, numExtensionsBuf, null); + int numExtensions = numExtensionsBuf.get(0); + + VkExtensionProperties.Buffer instanceExtensionsProps = VkExtensionProperties.calloc(numExtensions, stack); + VK10.vkEnumerateInstanceExtensionProperties((String) null, numExtensionsBuf, instanceExtensionsProps); + for (int i = 0; i < numExtensions; i++) { + VkExtensionProperties props = instanceExtensionsProps.get(i); + String extensionName = props.extensionNameString(); + instanceExtensions.add(extensionName); + } + } + + return instanceExtensions; + } + + private Set getPortabilityExtensions(Set instanceExtensions) { + Set portabilityExtensions = new HashSet<>(); + + var osType = VulkanUtils.getOS(); + if (osType == VulkanUtils.OSType.MACOS) { + if (!instanceExtensions.contains(KHRPortabilityEnumeration.VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) { + throw new RuntimeException("Vulkan instance does not support portability enumeration extension but it's required for MacOS"); + } + portabilityExtensions.add(KHRPortabilityEnumeration.VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + } + + return portabilityExtensions; + } + + private void log(String title, Set layers) { + Logger.debug("{} ({})", title, layers.size()); + for (String layer : layers) { + Logger.debug(" - {}", layer); + } + } + } +} diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayers.java b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayers.java new file mode 100644 index 0000000..851b9ba --- /dev/null +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayers.java @@ -0,0 +1,146 @@ +package fr.mrdev023.vulkan_java.vk.utils; + +import java.nio.IntBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VK10; +import org.lwjgl.vulkan.VkLayerProperties; +import org.tinylog.Logger; + +public class InstanceValidationLayers { + private Set validationLayers; + + private InstanceValidationLayers(Set validationLayers) { + this.validationLayers = validationLayers; + } + + public boolean hasValidationLayers() { + return !validationLayers.isEmpty(); + } + + public Set getValidationLayers() { + return validationLayers; + } + + public PointerBuffer writeToStack(MemoryStack stack) { + if (validationLayers.isEmpty()) { + return null; + } + + PointerBuffer requiredLayers = stack.mallocPointer(validationLayers.size()); + for (String layer : validationLayers) { + requiredLayers.put(stack.UTF8(layer)); + } + requiredLayers.flip(); + + return requiredLayers; + } + + /** + * This class is used to select the validation layers and create an InstanceValidationLayers object. + * + *
Specification
+ *
    + *
  • Include the validation layers if the user wants them
  • + *
  • Select the validation layers from the supported validation layers
  • + *
  • Return an InstanceValidationLayers object with the selected validation layers
  • + *
+ * + *
See Also
+ *
    + *
  • {@link #includeValidationLayers(boolean)}
  • + *
  • {@link #selectValidationLayers()}
  • + *
+ */ + public static class Selector { + private boolean includeValidationLayers = false; + + public Selector includeValidationLayers(boolean includeValidationLayers) { + this.includeValidationLayers = includeValidationLayers; + return this; + } + + /** + * Selects the validation layers. + * + * @return the set of validation layers + */ + public InstanceValidationLayers selectValidationLayers() { + var supportedLayers = getSupportedValidationLayers(); + log("Supported validation layers", supportedLayers); + + Set layersToUse = includeValidationLayers + ? selectValidationLayersFromSupportedLayers(supportedLayers) + : Collections.emptySet(); + log("Selected validation layers", layersToUse); + + if (layersToUse.isEmpty() && includeValidationLayers) { + Logger.warn("Request validation but no supported validation layers found. Falling back to no validation"); + } + + return new InstanceValidationLayers(layersToUse); + } + + private Set getSupportedValidationLayers() { + Set supportedLayers = new HashSet<>(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + // Get the number of supported validation layers by the current vulkan instance + IntBuffer numLayersArr = stack.callocInt(1); + VK10.vkEnumerateInstanceLayerProperties(numLayersArr, null); + int numLayers = numLayersArr.get(0); + + // Get the supported validation layers by the current vulkan instance + VkLayerProperties.Buffer propsBuf = VkLayerProperties.calloc(numLayers, stack); + VK10.vkEnumerateInstanceLayerProperties(numLayersArr, propsBuf); + + // Convert the supported validation layers to a list of strings + for (int i = 0; i < numLayers; i++) { + VkLayerProperties props = propsBuf.get(i); + String layerName = props.layerNameString(); + supportedLayers.add(layerName); + } + } + + return supportedLayers; + } + + private Set selectValidationLayersFromSupportedLayers(Set supportedLayers) { + Set layersToUse = new HashSet<>(); + + // Main validation layer + if (supportedLayers.contains("VK_LAYER_KHRONOS_validation")) { + layersToUse.add("VK_LAYER_KHRONOS_validation"); + return layersToUse; + } + + // Fallback 1 + if (supportedLayers.contains("VK_LAYER_LUNARG_standard_validation")) { + layersToUse.add("VK_LAYER_LUNARG_standard_validation"); + return layersToUse; + } + + // Fallback 2 (set) + Set requestedLayers = new HashSet<>(); + requestedLayers.add("VK_LAYER_GOOGLE_threading"); + requestedLayers.add("VK_LAYER_LUNARG_parameter_validation"); + requestedLayers.add("VK_LAYER_LUNARG_object_tracker"); + requestedLayers.add("VK_LAYER_LUNARG_core_validation"); + requestedLayers.add("VK_LAYER_GOOGLE_unique_objects"); + + return requestedLayers.stream().filter(supportedLayers::contains).collect(Collectors.toSet()); + } + + private void log(String title, Set layers) { + Logger.debug("{} ({})", title, layers.size()); + for (String layer : layers) { + Logger.debug(" - {}", layer); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayersSelector.java b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayersSelector.java deleted file mode 100644 index 7e8f04e..0000000 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/utils/InstanceValidationLayersSelector.java +++ /dev/null @@ -1,94 +0,0 @@ -package fr.mrdev023.vulkan_java.vk.utils; - -import java.nio.IntBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.lwjgl.system.MemoryStack; -import org.lwjgl.vulkan.VK10; -import org.lwjgl.vulkan.VkLayerProperties; -import org.tinylog.Logger; - -public class InstanceValidationLayersSelector { - private boolean withValidationLayers = false; - - public InstanceValidationLayersSelector withValidationLayers(boolean withValidationLayers) { - this.withValidationLayers = withValidationLayers; - return this; - } - - public List selectValidationLayers() { - if (!withValidationLayers) { - return Collections.emptyList(); - } - - var supportedLayers = getSupportedValidationLayers(); - log("Supported validation layers", supportedLayers); - - var layersToUse = selectValidationLayersFromSupportedLayers(supportedLayers); - log("Selected validation layers", layersToUse); - - if (layersToUse.isEmpty() && withValidationLayers) { - Logger.warn("Request validation but no supported validation layers found. Falling back to no validation"); - } - - return layersToUse; - } - - private List getSupportedValidationLayers() { - try (MemoryStack stack = MemoryStack.stackPush()) { - // Get the number of supported validation layers by the current vulkan instance - IntBuffer numLayersArr = stack.callocInt(1); - VK10.vkEnumerateInstanceLayerProperties(numLayersArr, null); - int numLayers = numLayersArr.get(0); - - // Get the supported validation layers by the current vulkan instance - VkLayerProperties.Buffer propsBuf = VkLayerProperties.calloc(numLayers, stack); - VK10.vkEnumerateInstanceLayerProperties(numLayersArr, propsBuf); - - // Convert the supported validation layers to a list of strings - List supportedLayers = new ArrayList<>(); - for (int i = 0; i < numLayers; i++) { - VkLayerProperties props = propsBuf.get(i); - String layerName = props.layerNameString(); - supportedLayers.add(layerName); - } - - return supportedLayers; - } - } - - private List selectValidationLayersFromSupportedLayers(List supportedLayers) { - List layersToUse = new ArrayList<>(); - - // Main validation layer - if (supportedLayers.contains("VK_LAYER_KHRONOS_validation")) { - layersToUse.add("VK_LAYER_KHRONOS_validation"); - return layersToUse; - } - - // Fallback 1 - if (supportedLayers.contains("VK_LAYER_LUNARG_standard_validation")) { - layersToUse.add("VK_LAYER_LUNARG_standard_validation"); - return layersToUse; - } - - // Fallback 2 (set) - List requestedLayers = new ArrayList<>(); - requestedLayers.add("VK_LAYER_GOOGLE_threading"); - requestedLayers.add("VK_LAYER_LUNARG_parameter_validation"); - requestedLayers.add("VK_LAYER_LUNARG_object_tracker"); - requestedLayers.add("VK_LAYER_LUNARG_core_validation"); - requestedLayers.add("VK_LAYER_GOOGLE_unique_objects"); - - return requestedLayers.stream().filter(supportedLayers::contains).toList(); - } - - private void log(String title, List layers) { - Logger.debug("{} ({})", title, layers.size()); - for (String layer : layers) { - Logger.debug(" - {}", layer); - } - } -} \ No newline at end of file