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 3434ae7..8f84875 100644 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/Instance.java @@ -35,9 +35,7 @@ public class Instance { .sType(VK_STRUCTURE_TYPE_APPLICATION_INFO) .pApplicationName(appShortName) .applicationVersion(1) - .pEngineName(appShortName) - .engineVersion(0) - .apiVersion(VK_API_VERSION_1_1); + .pEngineName(appShortName); InstanceValidationLayers validationLayers = new InstanceValidationLayers.Selector() .includeValidationLayers(validate) diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/PhysicalDevice.java b/src/main/java/fr/mrdev023/vulkan_java/vk/PhysicalDevice.java index 1b67ced..bd4425d 100644 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/PhysicalDevice.java +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/PhysicalDevice.java @@ -52,58 +52,7 @@ public class PhysicalDevice { } } - public static PhysicalDevice createPhysicalDevice(Instance instance, Optional preferredDeviceName) - throws VulkanError { - Logger.debug("Selecting physical devices"); - PhysicalDevice selectedPhysicalDevice = null; - try (MemoryStack stack = MemoryStack.stackPush()) { - // Get available devices - PointerBuffer pPhysicalDevices = getPhysicalDevices(instance, stack); - int numDevices = pPhysicalDevices.capacity(); - if (numDevices <= 0) { - throw new RuntimeException("No physical devices found"); - } - - // Populate available devices - List devices = new ArrayList<>(); - for (int i = 0; i < numDevices; i++) { - VkPhysicalDevice vkPhysicalDevice = new VkPhysicalDevice(pPhysicalDevices.get(i), - instance.getVkInstance()); - PhysicalDevice physicalDevice = new PhysicalDevice(vkPhysicalDevice); - - String deviceName = physicalDevice.getDeviceName(); - if (physicalDevice.hasGraphicsQueueFamily() && physicalDevice.hasKHRSwapChainExtension()) { - Logger.debug("Device [{}] supports required extensions", deviceName); - if (preferredDeviceName.isPresent() && preferredDeviceName.get().equals(deviceName)) { - selectedPhysicalDevice = physicalDevice; - break; - } - devices.add(physicalDevice); - } else { - Logger.debug("Device [{}] does not support required extensions", deviceName); - physicalDevice.destroy(); - } - } - - // No preferred device or it does not meet requirements, just pick the first one - selectedPhysicalDevice = selectedPhysicalDevice == null && !devices.isEmpty() ? devices.remove(0) - : selectedPhysicalDevice; - - // Clean up non-selected devices - for (PhysicalDevice physicalDevice : devices) { - physicalDevice.destroy(); - } - - if (selectedPhysicalDevice == null) { - throw new RuntimeException("No suitable physical devices found"); - } - Logger.debug("Selected device: [{}]", selectedPhysicalDevice.getDeviceName()); - } - - return selectedPhysicalDevice; - } - - protected static PointerBuffer getPhysicalDevices(Instance instance, MemoryStack stack) throws VulkanError { + public static List getPhysicalDevices(Instance instance, MemoryStack stack) throws VulkanError { PointerBuffer pPhysicalDevices; // Get number of physical devices IntBuffer intBuffer = stack.mallocInt(1); @@ -116,7 +65,14 @@ public class PhysicalDevice { pPhysicalDevices = stack.mallocPointer(numDevices); vkCheck(vkEnumeratePhysicalDevices(instance.getVkInstance(), intBuffer, pPhysicalDevices), "Failed to get physical devices"); - return pPhysicalDevices; + + List physicalDevices = new ArrayList<>(); + for (int i = 0; i < numDevices; i++) { + VkPhysicalDevice physicalDevice = new VkPhysicalDevice(pPhysicalDevices.get(i), instance.getVkInstance()); + physicalDevices.add(new PhysicalDevice(physicalDevice)); + } + + return physicalDevices; } public void destroy() { @@ -154,29 +110,7 @@ public class PhysicalDevice { return vkQueueFamilyProps; } - private boolean hasGraphicsQueueFamily() { - boolean result = false; - int numQueueFamilies = vkQueueFamilyProps != null ? vkQueueFamilyProps.capacity() : 0; - for (int i = 0; i < numQueueFamilies; i++) { - VkQueueFamilyProperties familyProps = vkQueueFamilyProps.get(i); - if ((familyProps.queueFlags() & VK_QUEUE_GRAPHICS_BIT) != 0) { - result = true; - break; - } - } - return result; - } - - private boolean hasKHRSwapChainExtension() { - boolean result = false; - int numExtensions = vkDeviceExtensions != null ? vkDeviceExtensions.capacity() : 0; - for (int i = 0; i < numExtensions; i++) { - String extensionName = vkDeviceExtensions.get(i).extensionNameString(); - if (KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME.equals(extensionName)) { - result = true; - break; - } - } - return result; + public VkExtensionProperties.Buffer getVkDeviceExtensions() { + return vkDeviceExtensions; } } \ No newline at end of file diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/Surface.java b/src/main/java/fr/mrdev023/vulkan_java/vk/Surface.java index 05e5f68..34f2a57 100644 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/Surface.java +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/Surface.java @@ -11,16 +11,15 @@ import java.nio.LongBuffer; public class Surface { - private final PhysicalDevice physicalDevice; + private final Instance instance; private final long vkSurface; - public Surface(PhysicalDevice physicalDevice, long windowHandle) throws VulkanError { - this.physicalDevice = physicalDevice; - + public Surface(Instance instance, long windowHandle) throws VulkanError { + this.instance = instance; try (MemoryStack stack = MemoryStack.stackPush()) { LongBuffer pSurface = stack.mallocLong(1); - var result = GLFWVulkan.glfwCreateWindowSurface(this.physicalDevice.getVkPhysicalDevice().getInstance(), + var result = GLFWVulkan.glfwCreateWindowSurface(instance.getVkInstance(), windowHandle, null, pSurface); vkCheck(result, "Failed to create Vulkan Surface"); @@ -32,7 +31,7 @@ public class Surface { } public void destroy() { - KHRSurface.vkDestroySurfaceKHR(physicalDevice.getVkPhysicalDevice().getInstance(), vkSurface, null); + KHRSurface.vkDestroySurfaceKHR(instance.getVkInstance(), vkSurface, null); Logger.debug("Vulkan surface destroyed"); } diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/Vulkan.java b/src/main/java/fr/mrdev023/vulkan_java/vk/Vulkan.java index b5a04ad..15873b8 100644 --- a/src/main/java/fr/mrdev023/vulkan_java/vk/Vulkan.java +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/Vulkan.java @@ -1,8 +1,11 @@ package fr.mrdev023.vulkan_java.vk; +import fr.mrdev023.vulkan_java.vk.utils.SuitablePhysicalDeviceFinder; import fr.mrdev023.vulkan_java.window.Display; -import java.util.Optional; +import java.util.Set; + +import org.lwjgl.vulkan.KHRDynamicRendering; import static org.lwjgl.glfw.GLFWVulkan.glfwVulkanSupported; @@ -20,9 +23,22 @@ public class Vulkan { } instance = new Instance(true); - physicalDevice = PhysicalDevice.createPhysicalDevice(instance, Optional.empty()); + surface = new Surface(instance, Display.getWindow()); + + var criteria = new SuitablePhysicalDeviceFinder.Criteria() + .withGraphicsQueue(true) + .withComputeQueue(true) + .withTransferQueue(true) + .withSurfaceSupport(surface) + .withExtensions(Set.of(KHRDynamicRendering.VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)); + + var physicalDeviceMatch = SuitablePhysicalDeviceFinder.findBestPhysicalDevice(instance, criteria); + if (physicalDeviceMatch == null) { + throw new RuntimeException("No suitable physical device found"); + } + + physicalDevice = physicalDeviceMatch.physicalDevice; device = new Device(physicalDevice); - surface = new Surface(physicalDevice, Display.getWindow()); graphicsQueue = new Queue.GraphicsQueue(device, 0); } diff --git a/src/main/java/fr/mrdev023/vulkan_java/vk/utils/SuitablePhysicalDeviceFinder.java b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/SuitablePhysicalDeviceFinder.java new file mode 100644 index 0000000..4d25d67 --- /dev/null +++ b/src/main/java/fr/mrdev023/vulkan_java/vk/utils/SuitablePhysicalDeviceFinder.java @@ -0,0 +1,192 @@ +package fr.mrdev023.vulkan_java.vk.utils; + +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.KHRSurface; +import org.lwjgl.vulkan.VK10; +import org.tinylog.Logger; + +import fr.mrdev023.vulkan_java.vk.Instance; +import fr.mrdev023.vulkan_java.vk.PhysicalDevice; +import fr.mrdev023.vulkan_java.vk.Surface; +import fr.mrdev023.vulkan_java.vk.VulkanError; + +public class SuitablePhysicalDeviceFinder { + + public static MatchResult findBestPhysicalDevice(Instance instance, Criteria criteria) throws VulkanError { + List matchedPhysicalDevices = new ArrayList<>(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + List physicalDevices = PhysicalDevice.getPhysicalDevices(instance, stack); + for (PhysicalDevice physicalDevice : physicalDevices) { + var matchResult = checkPhysicalDevice(stack, physicalDevice, criteria); + if (matchResult.isSuitable(criteria)) { + matchedPhysicalDevices.add(matchResult); + } + } + } + + return matchedPhysicalDevices.stream().min(Comparator.comparingInt(MatchResult::getScore)).orElse(null); + } + + private static MatchResult checkPhysicalDevice(MemoryStack stack, PhysicalDevice physicalDevice, Criteria criteria) { + int graphicsQueueFamilyIndex = -1; + int computeQueueFamilyIndex = -1; + int transferQueueFamilyIndex = -1; + boolean surfaceSupport = false; + + + IntBuffer presentSupport = stack.ints(VK10.VK_FALSE); + var vkQueueFamilyProps = physicalDevice.getVkQueueFamilyProps(); + for (int i = 0; i < vkQueueFamilyProps.capacity(); i++) { + var vkQueueFamilyProp = vkQueueFamilyProps.get(i); + + if ((vkQueueFamilyProp.queueFlags() & VK10.VK_QUEUE_GRAPHICS_BIT) != 0) { + graphicsQueueFamilyIndex = i; + } + + if ((vkQueueFamilyProp.queueFlags() & VK10.VK_QUEUE_COMPUTE_BIT) != 0) { + computeQueueFamilyIndex = i; + } + + if ((vkQueueFamilyProp.queueFlags() & VK10.VK_QUEUE_TRANSFER_BIT) != 0) { + transferQueueFamilyIndex = i; + } + + KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice.getVkPhysicalDevice(), i, criteria.withSurfaceSupport.getVkSurface(), presentSupport); + if (presentSupport.get(0) == VK10.VK_TRUE) { + surfaceSupport = true; + } + } + + return new MatchResult(physicalDevice, graphicsQueueFamilyIndex, computeQueueFamilyIndex, transferQueueFamilyIndex, surfaceSupport); + } + + public static class MatchResult { + public final PhysicalDevice physicalDevice; + public final boolean surfaceSupport; + public final int graphicsQueueFamilyIndex; + public final int computeQueueFamilyIndex; + public final int transferQueueFamilyIndex; + + public MatchResult(PhysicalDevice physicalDevice, int graphicsQueueFamilyIndex, int computeQueueFamilyIndex, int transferQueueFamilyIndex, boolean surfaceSupport) { + this.physicalDevice = physicalDevice; + this.graphicsQueueFamilyIndex = graphicsQueueFamilyIndex; + this.computeQueueFamilyIndex = computeQueueFamilyIndex; + this.transferQueueFamilyIndex = transferQueueFamilyIndex; + this.surfaceSupport = surfaceSupport; + } + + public boolean isSuitable(Criteria criteria) { + Logger.debug("Checking physical device"); + Logger.debug("\tProperties"); + Logger.debug("\t\tName: {}", physicalDevice.getVkPhysicalDeviceProperties().deviceNameString()); + int apiVersion = physicalDevice.getVkPhysicalDeviceProperties().apiVersion(); + Logger.debug("\t\tAPI version: {}.{}.{}", + VK10.VK_API_VERSION_MAJOR(apiVersion), + VK10.VK_API_VERSION_MINOR(apiVersion), + VK10.VK_API_VERSION_PATCH(apiVersion)); + Logger.debug("\t\tDevice type: {}", physicalDevice.getVkPhysicalDeviceProperties().deviceType()); + Logger.debug("\tRequired supports checking report"); + + if (!criteria.withGraphicsQueue) { + Logger.debug("\t\t[SKIPPED] Graphics queue is not required"); + } else if (graphicsQueueFamilyIndex == -1) { + Logger.debug("\t\t[FAILED] Graphics queue is not supported"); + return false; + } else { + Logger.debug("\t\t[OK] Graphics queue is supported"); + } + + if (!criteria.withComputeQueue) { + Logger.debug("\t\t[SKIPPED] Compute queue is not required"); + } else if (computeQueueFamilyIndex == -1) { + Logger.debug("\t\t[FAILED] Compute queue is not supported"); + return false; + } else { + Logger.debug("\t\t[OK] Compute queue is supported"); + } + + if (!criteria.withTransferQueue) { + Logger.debug("\t\t[SKIPPED] Transfer queue is not required"); + } else if (transferQueueFamilyIndex == -1) { + Logger.debug("\t\t[FAILED] Transfer queue is not supported"); + return false; + } else { + Logger.debug("\t\t[OK] Transfer queue is supported"); + } + + if (criteria.withSurfaceSupport == null) { + Logger.debug("\t\t[SKIPPED] Surface support is not required"); + } else if (!surfaceSupport) { + Logger.debug("\t\t[FAILED] Surface support is not supported"); + return false; + } else { + Logger.debug("\t\t[OK] Surface support is supported"); + } + + if (criteria.extensions == null) { + Logger.debug("\t\t[SKIPPED] Required extensions is empty"); + } else if (physicalDevice.getVkDeviceExtensions().stream().allMatch(extension -> criteria.extensions.contains(extension.extensionNameString()))) { + Logger.debug("\t\t[FAILED] Required extensions are not supported [{}]", String.join(", ", criteria.extensions)); + return false; + } else { + Logger.debug("\t\t[OK] Required extensions are supported [{}]", String.join(", ", criteria.extensions)); + } + + return true; + } + + public int getScore() { + int deviceType = physicalDevice.getVkPhysicalDeviceProperties().deviceType(); + + return switch (deviceType) { + case VK10.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU -> 0; + case VK10.VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU -> 1; + case VK10.VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU -> 2; + case VK10.VK_PHYSICAL_DEVICE_TYPE_CPU -> 3; + case VK10.VK_PHYSICAL_DEVICE_TYPE_OTHER -> 4; + default -> 5; + }; + } + } + + public static class Criteria { + private boolean withGraphicsQueue = false; + private boolean withComputeQueue = false; + private boolean withTransferQueue = false; + private Surface withSurfaceSupport = null; + private Set extensions = new HashSet<>(); + + public Criteria withGraphicsQueue(boolean withGraphicsQueue) { + this.withGraphicsQueue = withGraphicsQueue; + return this; + } + + public Criteria withSurfaceSupport(Surface withSurfaceSupport) { + this.withSurfaceSupport = withSurfaceSupport; + return this; + } + + public Criteria withComputeQueue(boolean withComputeQueue) { + this.withComputeQueue = withComputeQueue; + return this; + } + + public Criteria withTransferQueue(boolean withTransferQueue) { + this.withTransferQueue = withTransferQueue; + return this; + } + + public Criteria withExtensions(Set extensions) { + this.extensions = extensions; + return this; + } + } +}