VkInstance: Refactor Validation and Extensions selection

This commit is contained in:
Florian RICHER 2025-05-20 20:36:21 +02:00
parent c09b04dc81
commit 9ecf98a26d
Signed by: florian.richer
GPG key ID: C73D37CBED7BFC77
4 changed files with 315 additions and 167 deletions

View file

@ -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<String> 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<String> getInstanceExtensions() {
Set<String> 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;
}

View file

@ -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<String> instanceExtensions;
private PointerBuffer glfwRequiredExtensions;
private InstanceExtensions(Set<String> instanceExtensions, PointerBuffer glfwRequiredExtensions) {
this.instanceExtensions = instanceExtensions;
this.glfwRequiredExtensions = glfwRequiredExtensions;
}
public boolean hasInstanceExtensions() {
return !instanceExtensions.isEmpty();
}
public Set<String> 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
*
* <h5>Specification</h5>
* <ul>
* <li>Get the GLFW required extensions to create the Window Surface</li>
* <li>Get the portability extensions if the OS needs it</li>
* <li>Add Debug Utils extension if validation layers are enabled</li>
* </ul>
*
* <h5>See Also</h5>
* <ul>
* <li>{@link #selectInstanceExtensions()}</li>
* <li>{@link #withValidationLayers(InstanceValidationLayers)}</li>
* </ul>
*/
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<String> 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<String> portabilityExtensions = getPortabilityExtensions(instanceExtensions);
log("Portability extensions used", portabilityExtensions);
Set<String> 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<String> getInstanceExtensions() {
Set<String> 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<String> getPortabilityExtensions(Set<String> instanceExtensions) {
Set<String> 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<String> layers) {
Logger.debug("{} ({})", title, layers.size());
for (String layer : layers) {
Logger.debug(" - {}", layer);
}
}
}
}

View file

@ -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<String> validationLayers;
private InstanceValidationLayers(Set<String> validationLayers) {
this.validationLayers = validationLayers;
}
public boolean hasValidationLayers() {
return !validationLayers.isEmpty();
}
public Set<String> 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.
*
* <h5>Specification</h5>
* <ul>
* <li>Include the validation layers if the user wants them</li>
* <li>Select the validation layers from the supported validation layers</li>
* <li>Return an InstanceValidationLayers object with the selected validation layers</li>
* </ul>
*
* <h5>See Also</h5>
* <ul>
* <li>{@link #includeValidationLayers(boolean)}</li>
* <li>{@link #selectValidationLayers()}</li>
* </ul>
*/
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<String> 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<String> getSupportedValidationLayers() {
Set<String> 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<String> selectValidationLayersFromSupportedLayers(Set<String> supportedLayers) {
Set<String> 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<String> 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<String> layers) {
Logger.debug("{} ({})", title, layers.size());
for (String layer : layers) {
Logger.debug(" - {}", layer);
}
}
}
}

View file

@ -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<String> 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<String> 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<String> 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<String> selectValidationLayersFromSupportedLayers(List<String> supportedLayers) {
List<String> 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<String> 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<String> layers) {
Logger.debug("{} ({})", title, layers.size());
for (String layer : layers) {
Logger.debug(" - {}", layer);
}
}
}