Refactor + Add swapchain

This commit is contained in:
Florian RICHER 2025-05-25 16:18:57 +02:00
parent 4ce051b0f5
commit f8703fb47c
Signed by: florian.richer
GPG key ID: C73D37CBED7BFC77
15 changed files with 612 additions and 375 deletions

3
.gitignore vendored
View file

@ -3,4 +3,5 @@
target
.idea/
*.iml
dependency-reduced-pom.xml
dependency-reduced-pom.xml
hs_err_pid*.log

View file

@ -15,8 +15,6 @@ import org.lwjgl.vulkan.VkDeviceCreateInfo;
import org.lwjgl.vulkan.VkPhysicalDeviceFeatures;
import org.tinylog.Logger;
import fr.mrdev023.vulkan_java.vk.utils.SuitablePhysicalDeviceFinder;
public class Device {
private final PhysicalDevice physicalDevice;

View file

@ -1,18 +1,34 @@
package fr.mrdev023.vulkan_java.vk;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.*;
import org.lwjgl.vulkan.*;
import org.tinylog.Logger;
import fr.mrdev023.vulkan_java.vk.utils.InstanceExtensions;
import fr.mrdev023.vulkan_java.vk.utils.InstanceValidationLayers;
import java.nio.*;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import static org.lwjgl.vulkan.EXTDebugUtils.*;
import static org.lwjgl.vulkan.VK11.*;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
import static org.lwjgl.vulkan.EXTDebugUtils.vkCreateDebugUtilsMessengerEXT;
import static org.lwjgl.vulkan.EXTDebugUtils.vkDestroyDebugUtilsMessengerEXT;
import static org.lwjgl.vulkan.VK10.VK_FALSE;
import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE;
import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_APPLICATION_INFO;
import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
import static org.lwjgl.vulkan.VK10.vkCreateInstance;
import static org.lwjgl.vulkan.VK10.vkDestroyInstance;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.vulkan.VkApplicationInfo;
import org.lwjgl.vulkan.VkDebugUtilsMessengerCallbackDataEXT;
import org.lwjgl.vulkan.VkDebugUtilsMessengerCreateInfoEXT;
import org.lwjgl.vulkan.VkInstance;
import org.lwjgl.vulkan.VkInstanceCreateInfo;
import org.tinylog.Logger;
public class Instance {
@ -23,6 +39,8 @@ public class Instance {
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
private final VkInstance vkInstance;
private final InstanceValidationLayers validationLayers;
private final InstanceExtensions instanceExtensions;
private VkDebugUtilsMessengerCreateInfoEXT debugUtils;
private long vkDebugHandle;
@ -37,13 +55,8 @@ public class Instance {
.applicationVersion(1)
.pEngineName(appShortName);
InstanceValidationLayers validationLayers = new InstanceValidationLayers.Selector()
.includeValidationLayers(validate)
.selectValidationLayers();
InstanceExtensions instanceExtensions = new InstanceExtensions.Selector()
.withValidationLayers(validationLayers)
.selectInstanceExtensions();
validationLayers = new InstanceValidationLayers();
instanceExtensions = new InstanceExtensions(true);
PointerBuffer instanceExtensionsBuffer = instanceExtensions.writeToStack(stack);
PointerBuffer validationLayersBuffer = validationLayers.writeToStack(stack);

View file

@ -0,0 +1,128 @@
package fr.mrdev023.vulkan_java.vk;
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;
/**
* 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 Set<String> glfwExtensions;
private Set<String> portabilityExtensions;
private Set<String> selectedExtensions;
public InstanceExtensions(boolean withValidationLayers) {
this.instanceExtensions = queryVkInstanceExtensions();
log("Supported instance extensions", instanceExtensions);
this.glfwExtensions = getGLFWRequiredExtensions();
this.portabilityExtensions = getVkPortabilityExtensions(instanceExtensions);
selectedExtensions = new HashSet<>();
selectedExtensions.addAll(glfwExtensions);
selectedExtensions.addAll(portabilityExtensions);
if (withValidationLayers) {
selectedExtensions.add(EXTDebugUtils.VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
log("Selected instance extensions", selectedExtensions);
}
public boolean hasInstanceExtensions() {
return !instanceExtensions.isEmpty();
}
public Set<String> getInstanceExtensions() {
return instanceExtensions;
}
public PointerBuffer writeToStack(MemoryStack stack) {
int numExtensions = instanceExtensions.size();
PointerBuffer requiredExtensions = stack.mallocPointer(numExtensions);
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);
}
}
private static Set<String> queryVkInstanceExtensions() {
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 static Set<String> getVkPortabilityExtensions(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 static Set<String> getGLFWRequiredExtensions() {
PointerBuffer glfwExtensionsBuffer = GLFWVulkan.glfwGetRequiredInstanceExtensions();
if (glfwExtensionsBuffer == null) {
throw new RuntimeException("Failed to find the GLFW platform surface extensions");
}
Set<String> glfwExtensions = new HashSet<>();
for (int i = 0; i < glfwExtensionsBuffer.remaining(); i++) {
glfwExtensions.add(glfwExtensionsBuffer.getStringUTF8(i));
}
return glfwExtensions;
}
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,119 @@
package fr.mrdev023.vulkan_java.vk;
import java.nio.IntBuffer;
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;
public InstanceValidationLayers() {
this.validationLayers = selectValidationLayers();
}
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;
}
/**
* Selects the validation layers.
*
* @return the set of validation layers
*/
private static Set<String> selectValidationLayers() {
var supportedLayers = queryVkValidationLayers();
log("Supported validation layers", supportedLayers);
Set<String> layersToUse = getValidationLayersFromSupportedLayers(supportedLayers);
log("Selected validation layers", layersToUse);
if (layersToUse.isEmpty()) {
Logger.warn(
"Request validation but no supported validation layers found. Falling back to no validation");
}
return layersToUse;
}
private static Set<String> queryVkValidationLayers() {
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 static Set<String> getValidationLayersFromSupportedLayers(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 static void log(String title, Set<String> layers) {
Logger.debug("{} ({})", title, layers.size());
for (String layer : layers) {
Logger.debug(" - {}", layer);
}
}
}

View file

@ -1,16 +1,27 @@
package fr.mrdev023.vulkan_java.vk;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import static org.lwjgl.vulkan.VK10.vkEnumerateDeviceExtensionProperties;
import static org.lwjgl.vulkan.VK10.vkEnumeratePhysicalDevices;
import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceFeatures;
import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceMemoryProperties;
import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceProperties;
import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceQueueFamilyProperties;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;
import org.lwjgl.vulkan.VkExtensionProperties;
import org.lwjgl.vulkan.VkPhysicalDevice;
import org.lwjgl.vulkan.VkPhysicalDeviceFeatures;
import org.lwjgl.vulkan.VkPhysicalDeviceMemoryProperties;
import org.lwjgl.vulkan.VkPhysicalDeviceProperties;
import org.lwjgl.vulkan.VkQueueFamilyProperties;
import org.tinylog.Logger;
import java.nio.IntBuffer;
import java.util.*;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import static org.lwjgl.vulkan.VK11.*;
public class PhysicalDevice {
private final VkExtensionProperties.Buffer vkDeviceExtensions;
@ -28,8 +39,8 @@ public class PhysicalDevice {
vkPhysicalDeviceProperties = VkPhysicalDeviceProperties.calloc();
vkGetPhysicalDeviceProperties(vkPhysicalDevice, vkPhysicalDeviceProperties);
vkDeviceExtensions = getVulkanPhysicalDeviceExtensions(stack, vkPhysicalDevice);
vkQueueFamilyProps = getVulkanQueueFamilyProperties(stack, vkPhysicalDevice);
vkDeviceExtensions = queryVkPhysicalDeviceExtensions(stack, vkPhysicalDevice);
vkQueueFamilyProps = queryVkQueueFamilyProperties(stack, vkPhysicalDevice);
vkPhysicalDeviceFeatures = VkPhysicalDeviceFeatures.calloc();
vkGetPhysicalDeviceFeatures(vkPhysicalDevice, vkPhysicalDeviceFeatures);
@ -41,25 +52,11 @@ public class PhysicalDevice {
}
public static List<PhysicalDevice> getPhysicalDevices(Instance instance, MemoryStack stack) throws VulkanError {
PointerBuffer pPhysicalDevices;
// Get number of physical devices
IntBuffer intBuffer = stack.mallocInt(1);
vkCheck(vkEnumeratePhysicalDevices(instance.getVkInstance(), intBuffer, null),
"Failed to get number of physical devices");
int numDevices = intBuffer.get(0);
Logger.debug("Detected {} physical device(s)", numDevices);
// Populate physical devices list pointer
pPhysicalDevices = stack.mallocPointer(numDevices);
vkCheck(vkEnumeratePhysicalDevices(instance.getVkInstance(), intBuffer, pPhysicalDevices),
"Failed to get physical devices");
// Streams mapping is not possible because it not propagates the VulkanError
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));
for (VkPhysicalDevice vkPhysicalDevice : queryVkPhysicalDevices(instance, stack)) {
physicalDevices.add(new PhysicalDevice(vkPhysicalDevice));
}
return physicalDevices;
}
@ -102,7 +99,30 @@ public class PhysicalDevice {
return vkDeviceExtensions;
}
private static VkExtensionProperties.Buffer getVulkanPhysicalDeviceExtensions(MemoryStack stack,
private static List<VkPhysicalDevice> queryVkPhysicalDevices(Instance instance, MemoryStack stack)
throws VulkanError {
PointerBuffer pPhysicalDevices;
// Get number of physical devices
IntBuffer intBuffer = stack.mallocInt(1);
vkCheck(vkEnumeratePhysicalDevices(instance.getVkInstance(), intBuffer, null),
"Failed to get number of physical devices");
int numDevices = intBuffer.get(0);
Logger.debug("Detected {} physical device(s)", numDevices);
// Populate physical devices list pointer
pPhysicalDevices = stack.mallocPointer(numDevices);
vkCheck(vkEnumeratePhysicalDevices(instance.getVkInstance(), intBuffer, pPhysicalDevices),
"Failed to get physical devices");
List<VkPhysicalDevice> physicalDevices = new ArrayList<>();
for (int i = 0; i < numDevices; i++) {
physicalDevices.add(new VkPhysicalDevice(pPhysicalDevices.get(i), instance.getVkInstance()));
}
return physicalDevices;
}
private static VkExtensionProperties.Buffer queryVkPhysicalDeviceExtensions(MemoryStack stack,
VkPhysicalDevice physicalDevice)
throws VulkanError {
IntBuffer numExtensions = stack.mallocInt(1);
@ -117,7 +137,7 @@ public class PhysicalDevice {
return deviceExtensions;
}
private static VkQueueFamilyProperties.Buffer getVulkanQueueFamilyProperties(MemoryStack stack,
private static VkQueueFamilyProperties.Buffer queryVkQueueFamilyProperties(MemoryStack stack,
VkPhysicalDevice physicalDevice)
throws VulkanError {
IntBuffer numQueueFamilies = stack.mallocInt(1);

View file

@ -1,18 +1,22 @@
package fr.mrdev023.vulkan_java.vk;
import static org.lwjgl.vulkan.VK10.vkGetDeviceQueue;
import static org.lwjgl.vulkan.VK10.vkQueueWaitIdle;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;
import org.lwjgl.vulkan.VkQueue;
import org.tinylog.Logger;
import static org.lwjgl.vulkan.VK11.*;
public class Queue {
private final VkQueue vkQueue;
private final int familyIndex;
private final int queueIndex;
public Queue(Device device, int queueFamilyIndex, int queueIndex) {
Logger.debug("Creating queue");
this.familyIndex = queueFamilyIndex;
this.queueIndex = queueIndex;
try (MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer pQueue = stack.mallocPointer(1);
@ -20,12 +24,22 @@ public class Queue {
long queue = pQueue.get(0);
vkQueue = new VkQueue(queue, device.getVkDevice());
}
Logger.debug("Queue created on family index {} and queue index {}", familyIndex, queueIndex);
}
public VkQueue getVkQueue() {
return vkQueue;
}
public int getFamilyIndex() {
return familyIndex;
}
public int getQueueIndex() {
return queueIndex;
}
public void waitIdle() {
vkQueueWaitIdle(vkQueue);
}

View file

@ -1,4 +1,4 @@
package fr.mrdev023.vulkan_java.vk.utils;
package fr.mrdev023.vulkan_java.vk;
import java.util.ArrayList;
import java.util.Comparator;
@ -7,9 +7,6 @@ import java.util.List;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VK10;
import fr.mrdev023.vulkan_java.vk.Instance;
import fr.mrdev023.vulkan_java.vk.PhysicalDevice;
import fr.mrdev023.vulkan_java.vk.VulkanError;
import fr.mrdev023.vulkan_java.vk.loggers.PhysicalDeviceCompatibilityLogger;
import fr.mrdev023.vulkan_java.vk.validators.PhysicalDeviceCompatibilityValidator;

View file

@ -1,21 +1,24 @@
package fr.mrdev023.vulkan_java.vk;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import java.nio.LongBuffer;
import org.lwjgl.glfw.GLFWVulkan;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.KHRSurface;
import org.tinylog.Logger;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import java.nio.LongBuffer;
public class Surface {
private final Instance instance;
private final long windowHandle;
private final long vkSurface;
public Surface(Instance instance, long windowHandle) throws VulkanError {
this.instance = instance;
this.windowHandle = windowHandle;
try (MemoryStack stack = MemoryStack.stackPush()) {
LongBuffer pSurface = stack.mallocLong(1);
@ -35,6 +38,10 @@ public class Surface {
Logger.debug("Vulkan surface destroyed");
}
public long getWindowHandle() {
return windowHandle;
}
public long getVkSurface() {
return vkSurface;
}

View file

@ -0,0 +1,232 @@
package fr.mrdev023.vulkan_java.vk;
import static fr.mrdev023.vulkan_java.vk.VulkanError.vkCheck;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.KHRSurface;
import org.lwjgl.vulkan.KHRSwapchain;
import org.lwjgl.vulkan.VK10;
import org.lwjgl.vulkan.VkExtent2D;
import org.lwjgl.vulkan.VkImageViewCreateInfo;
import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR;
import org.lwjgl.vulkan.VkSurfaceFormatKHR;
import org.lwjgl.vulkan.VkSwapchainCreateInfoKHR;
import org.tinylog.Logger;
import fr.mrdev023.vulkan_java.window.Display;
import fr.mrdev023.vulkan_java.window.DisplayMode;
public class Swapchain {
private final Device device;
private final Surface surface;
private final long vkSwapchain;
private final List<Long> vkImages;
private final List<Long> vkImageViews;
private final VkSurfaceCapabilitiesKHR vkSurfaceCapabilities;
private final VkSurfaceFormatKHR.Buffer vkSurfaceFormats;
private final IntBuffer vkPresentModes;
private final IntBuffer chosenImageCount;
private final VkSurfaceFormatKHR chosenSurfaceFormat;
private final int chosenPresentMode;
private final VkExtent2D chosenExtent;
public Swapchain(Device device, Surface surface) throws VulkanError {
this.device = device;
this.surface = surface;
try (MemoryStack stack = MemoryStack.stackPush()) {
vkSurfaceCapabilities = queryVkSurfaceCapabilities(stack);
vkSurfaceFormats = queryVkPhysicalDeviceSurfaceFormats(stack);
vkPresentModes = queryVkPhysicalDeviceSurfacePresentModes(stack);
chosenSurfaceFormat = vkSurfaceFormats.get(0);
chosenPresentMode = KHRSurface.VK_PRESENT_MODE_FIFO_KHR;
chosenExtent = chooseSwapExtent(stack);
chosenImageCount = stack.ints(vkSurfaceCapabilities.minImageCount() + 1);
if (vkSurfaceCapabilities.maxImageCount() > 0
&& chosenImageCount.get(0) > vkSurfaceCapabilities.maxImageCount()) {
chosenImageCount.put(0, vkSurfaceCapabilities.maxImageCount());
}
VkSwapchainCreateInfoKHR createInfo = createVkSwapchainCreateInfo(stack, false);
LongBuffer pSwapChain = stack.longs(VK10.VK_NULL_HANDLE);
vkCheck(KHRSwapchain.vkCreateSwapchainKHR(device.getVkDevice(), createInfo, null, pSwapChain),
"Failed to create swapchain");
vkSwapchain = pSwapChain.get(0);
IntBuffer imageCount = stack.ints(0);
KHRSwapchain.vkGetSwapchainImagesKHR(device.getVkDevice(), vkSwapchain, imageCount, null);
LongBuffer pImages = stack.mallocLong(imageCount.get(0));
KHRSwapchain.vkGetSwapchainImagesKHR(device.getVkDevice(), vkSwapchain, imageCount, pImages);
vkImages = new ArrayList<>(imageCount.get(0));
for (int i = 0; i < imageCount.get(0); i++) {
vkImages.add(pImages.get(i));
}
vkImageViews = createVkImageViews(stack);
Logger.debug("Swapchain created");
}
}
public Device getDevice() {
return device;
}
public Surface getSurface() {
return surface;
}
public VkSurfaceCapabilitiesKHR getVkSurfaceCapabilities() {
return vkSurfaceCapabilities;
}
public VkSurfaceFormatKHR.Buffer getVkSurfaceFormats() {
return vkSurfaceFormats;
}
public IntBuffer getVkPresentModes() {
return vkPresentModes;
}
public void destroy() {
for (long imageView : vkImageViews) {
VK10.vkDestroyImageView(device.getVkDevice(), imageView, null);
}
KHRSwapchain.vkDestroySwapchainKHR(device.getVkDevice(), vkSwapchain, null);
}
private VkSwapchainCreateInfoKHR createVkSwapchainCreateInfo(MemoryStack stack, boolean recreate) {
VkSwapchainCreateInfoKHR createInfo = VkSwapchainCreateInfoKHR.calloc(stack);
createInfo.sType(KHRSwapchain.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
createInfo.surface(surface.getVkSurface());
// Image settings
createInfo.minImageCount(chosenImageCount.get(0));
createInfo.imageFormat(chosenSurfaceFormat.format());
createInfo.imageColorSpace(chosenSurfaceFormat.colorSpace());
createInfo.imageExtent(chosenExtent);
createInfo.imageArrayLayers(1);
createInfo.imageUsage(VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
// Queue family indices
createInfo.imageSharingMode(VK10.VK_SHARING_MODE_EXCLUSIVE); // If different queues between graphics and
// present, use VK_SHARING_MODE_CONCURRENT
createInfo.pQueueFamilyIndices(stack.ints(device.getGraphicsQueue().orElseThrow().getFamilyIndex()));
createInfo.preTransform(vkSurfaceCapabilities.currentTransform());
createInfo.compositeAlpha(KHRSurface.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR);
createInfo.presentMode(chosenPresentMode);
createInfo.clipped(true);
if (recreate) {
createInfo.oldSwapchain(vkSwapchain);
} else {
createInfo.oldSwapchain(VK10.VK_NULL_HANDLE);
}
return createInfo;
}
private List<Long> createVkImageViews(MemoryStack stack) throws VulkanError {
List<Long> vkImageViews = new ArrayList<>();
for (long image : vkImages) {
VkImageViewCreateInfo createInfo = createVkImageViewCreateInfo(stack, image);
LongBuffer pImageView = stack.mallocLong(1);
vkCheck(VK10.vkCreateImageView(device.getVkDevice(), createInfo, null, pImageView),
"Failed to create image views");
vkImageViews.add(pImageView.get(0));
}
return vkImageViews;
}
private VkImageViewCreateInfo createVkImageViewCreateInfo(MemoryStack stack, long image) {
VkImageViewCreateInfo createInfo = VkImageViewCreateInfo.calloc(stack);
createInfo.sType(VK10.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO);
createInfo.image(image);
createInfo.viewType(VK10.VK_IMAGE_VIEW_TYPE_2D);
createInfo.format(chosenSurfaceFormat.format());
createInfo.components().r(VK10.VK_COMPONENT_SWIZZLE_IDENTITY);
createInfo.components().g(VK10.VK_COMPONENT_SWIZZLE_IDENTITY);
createInfo.components().b(VK10.VK_COMPONENT_SWIZZLE_IDENTITY);
createInfo.components().a(VK10.VK_COMPONENT_SWIZZLE_IDENTITY);
createInfo.subresourceRange().aspectMask(VK10.VK_IMAGE_ASPECT_COLOR_BIT);
createInfo.subresourceRange().baseMipLevel(0);
createInfo.subresourceRange().levelCount(1);
createInfo.subresourceRange().baseArrayLayer(0);
createInfo.subresourceRange().layerCount(1);
return createInfo;
}
private VkExtent2D chooseSwapExtent(MemoryStack stack) {
DisplayMode displayMode = Display.getDisplayMode();
VkExtent2D actualExtent = VkExtent2D.malloc(stack).set(displayMode.getWidth(), displayMode.getHeight());
VkExtent2D minExtent = vkSurfaceCapabilities.minImageExtent();
VkExtent2D maxExtent = vkSurfaceCapabilities.maxImageExtent();
actualExtent.width(Math.clamp(actualExtent.width(), minExtent.width(), maxExtent.width()));
actualExtent.height(Math.clamp(actualExtent.height(), minExtent.height(), maxExtent.height()));
return actualExtent;
}
private VkSurfaceCapabilitiesKHR queryVkSurfaceCapabilities(MemoryStack stack) {
VkSurfaceCapabilitiesKHR vkSurfaceCapabilities = VkSurfaceCapabilitiesKHR.malloc(stack);
KHRSurface.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device.getPhysicalDevice().getVkPhysicalDevice(),
surface.getVkSurface(),
vkSurfaceCapabilities);
return vkSurfaceCapabilities;
}
private VkSurfaceFormatKHR.Buffer queryVkPhysicalDeviceSurfaceFormats(MemoryStack stack) {
VkSurfaceFormatKHR.Buffer vkSurfaceFormat;
IntBuffer count = stack.ints(0);
KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR(device.getPhysicalDevice().getVkPhysicalDevice(),
surface.getVkSurface(), count, null);
if (count.get(0) == 0) {
throw new RuntimeException("No surface formats found");
}
vkSurfaceFormat = VkSurfaceFormatKHR.malloc(count.get(0), stack);
KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR(device.getPhysicalDevice().getVkPhysicalDevice(),
surface.getVkSurface(), count, vkSurfaceFormat);
return vkSurfaceFormat;
}
private IntBuffer queryVkPhysicalDeviceSurfacePresentModes(MemoryStack stack) {
IntBuffer vkPresentMode;
IntBuffer count = stack.ints(0);
KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR(device.getPhysicalDevice().getVkPhysicalDevice(),
surface.getVkSurface(), count, null);
if (count.get(0) == 0) {
throw new RuntimeException("No present modes found");
}
vkPresentMode = stack.mallocInt(count.get(0));
KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR(device.getPhysicalDevice().getVkPhysicalDevice(),
surface.getVkSurface(), count, vkPresentMode);
return vkPresentMode;
}
}

View file

@ -5,12 +5,15 @@ import static org.lwjgl.glfw.GLFWVulkan.glfwVulkanSupported;
import java.util.Optional;
import java.util.Set;
import org.lwjgl.vulkan.KHRCreateRenderpass2;
import org.lwjgl.vulkan.KHRDepthStencilResolve;
import org.lwjgl.vulkan.KHRDynamicRendering;
import org.lwjgl.vulkan.KHRMaintenance2;
import org.lwjgl.vulkan.KHRMultiview;
import org.lwjgl.vulkan.KHRPortabilitySubset;
import org.lwjgl.vulkan.KHRSwapchain;
import org.lwjgl.vulkan.VK10;
import fr.mrdev023.vulkan_java.vk.utils.SuitablePhysicalDeviceFinder;
import fr.mrdev023.vulkan_java.vk.validators.PhysicalDeviceCompatibilityValidator;
import fr.mrdev023.vulkan_java.window.Display;
@ -20,6 +23,7 @@ public class Vulkan {
private static PhysicalDevice physicalDevice;
private static Device device;
private static Surface surface;
private static Swapchain swapchain;
public static void init() throws VulkanError {
if (!glfwVulkanSupported()) {
@ -30,6 +34,14 @@ public class Vulkan {
surface = new Surface(instance, Display.getWindow());
var extensions = Set.of(KHRDynamicRendering.VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
// Dependency of KHRDynamicRendering
KHRDepthStencilResolve.VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME,
// Dependency of KHRDepthStencilResolve
KHRCreateRenderpass2.VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME,
// Dependency of KHRCreateRenderpass2
KHRMaintenance2.VK_KHR_MAINTENANCE_2_EXTENSION_NAME,
KHRMultiview.VK_KHR_MULTIVIEW_EXTENSION_NAME,
// Required to create swapchain
KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME);
if (VulkanUtils.getOS() == VulkanUtils.OSType.MACOS) {
extensions.add(KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
@ -48,9 +60,11 @@ public class Vulkan {
physicalDevice = physicalDeviceMatch.physicalDevice;
device = new Device(physicalDeviceMatch);
swapchain = new Swapchain(device, surface);
}
public static void destroy() {
swapchain.destroy();
surface.destroy();
device.destroy();
physicalDevice.destroy();

View file

@ -11,13 +11,16 @@ public class PhysicalDevicePropertiesLogger {
ScopedLogger.pushScope("Physical Device Properties");
ScopedLogger.log("Name: {}", properties.deviceNameString());
ScopedLogger.log("API Version: {}", formatApiVersion(properties.apiVersion()));
ScopedLogger.log("Driver Version: {}", formatApiVersion(properties.driverVersion())); // TODO: format driver version because it's depending of the driver
ScopedLogger.log("Driver Version: {}", formatApiVersion(properties.driverVersion())); // TODO: format driver
// version because it's
// depending of the driver
ScopedLogger.log("Device Type: {}", formatDeviceType(properties.deviceType()));
ScopedLogger.popScope();
}
private static String formatApiVersion(int apiVersion) {
return String.format("%d.%d.%d", VK10.VK_API_VERSION_MAJOR(apiVersion), VK10.VK_API_VERSION_MINOR(apiVersion), VK10.VK_API_VERSION_PATCH(apiVersion));
return String.format("%d.%d.%d", VK10.VK_API_VERSION_MAJOR(apiVersion), VK10.VK_API_VERSION_MINOR(apiVersion),
VK10.VK_API_VERSION_PATCH(apiVersion));
}
private static String formatDeviceType(int deviceType) {

View file

@ -8,7 +8,7 @@ import org.tinylog.Logger;
public class ScopedLogger {
private static Deque<String> scopes = new ArrayDeque<>();
public static void pushScope(final String message, final Object... arguments) {
log(message, arguments);
scopes.push(message);

View file

@ -1,163 +0,0 @@
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 InstanceExtensions(Set<String> instanceExtensions) {
this.instanceExtensions = instanceExtensions;
}
public boolean hasInstanceExtensions() {
return !instanceExtensions.isEmpty();
}
public Set<String> getInstanceExtensions() {
return instanceExtensions;
}
public PointerBuffer writeToStack(MemoryStack stack) {
int numExtensions = instanceExtensions.size();
PointerBuffer requiredExtensions = stack.mallocPointer(numExtensions);
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);
Set<String> glfwExtensions = getGLFWRequiredExtensions();
Set<String> portabilityExtensions = getPortabilityExtensions(instanceExtensions);
Set<String> selectedExtensions = new HashSet<>();
selectedExtensions.addAll(glfwExtensions);
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);
}
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 Set<String> getGLFWRequiredExtensions() {
PointerBuffer glfwExtensionsBuffer = GLFWVulkan.glfwGetRequiredInstanceExtensions();
if (glfwExtensionsBuffer == null) {
throw new RuntimeException("Failed to find the GLFW platform surface extensions");
}
Set<String> glfwExtensions = new HashSet<>();
for (int i = 0; i < glfwExtensionsBuffer.remaining(); i++) {
glfwExtensions.add(glfwExtensionsBuffer.getStringUTF8(i));
}
return glfwExtensions;
}
private void log(String title, Set<String> layers) {
Logger.debug("{} ({})", title, layers.size());
for (String layer : layers) {
Logger.debug(" - {}", layer);
}
}
}
}

View file

@ -1,146 +0,0 @@
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);
}
}
}
}