Add SuitablePhysicalDeviceFinder

This commit is contained in:
Florian RICHER 2025-05-20 22:40:42 +02:00
parent 9ecf98a26d
commit 422b2825ad
Signed by: florian.richer
GPG key ID: C73D37CBED7BFC77
5 changed files with 228 additions and 89 deletions

View file

@ -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)

View file

@ -52,58 +52,7 @@ public class PhysicalDevice {
}
}
public static PhysicalDevice createPhysicalDevice(Instance instance, Optional<String> 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<PhysicalDevice> 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<PhysicalDevice> 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<PhysicalDevice> 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;
}
}

View file

@ -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");
}

View file

@ -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);
}

View file

@ -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<MatchResult> matchedPhysicalDevices = new ArrayList<>();
try (MemoryStack stack = MemoryStack.stackPush()) {
List<PhysicalDevice> 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<String> 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<String> extensions) {
this.extensions = extensions;
return this;
}
}
}