Created
May 13, 2026 14:32
-
-
Save brickwall2900/a71dc6ce51323f230199d786e05f661b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package io.github.brickwall2900.test; | |
| import org.lwjgl.PointerBuffer; | |
| import org.lwjgl.system.MemoryStack; | |
| import org.lwjgl.system.MemoryUtil; | |
| import org.lwjgl.system.Platform; | |
| import org.lwjgl.vulkan.*; | |
| import java.io.*; | |
| import java.nio.ByteBuffer; | |
| import java.nio.FloatBuffer; | |
| import java.nio.IntBuffer; | |
| import java.nio.LongBuffer; | |
| import java.util.*; | |
| import java.util.function.Function; | |
| import static org.lwjgl.glfw.GLFW.*; | |
| import static org.lwjgl.glfw.GLFWVulkan.glfwCreateWindowSurface; | |
| import static org.lwjgl.glfw.GLFWVulkan.glfwGetRequiredInstanceExtensions; | |
| import static org.lwjgl.system.MemoryStack.stackPush; | |
| import static org.lwjgl.system.MemoryUtil.NULL; | |
| import static org.lwjgl.vulkan.EXTDebugUtils.*; | |
| import static org.lwjgl.vulkan.KHRPortabilityEnumeration.VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; | |
| import static org.lwjgl.vulkan.KHRSurface.*; | |
| import static org.lwjgl.vulkan.KHRSwapchain.*; | |
| import static org.lwjgl.vulkan.VK10.*; | |
| import static org.lwjgl.vulkan.VK11.vkGetPhysicalDeviceFeatures2; | |
| import static org.lwjgl.vulkan.VK13.*; | |
| // the Hello Triangle Vulkan example program in one Java class. | |
| // uses Vulkan 1.3 and GLFW from LWJGL 3 in Java 25. | |
| // to be honest, some parts of this or lines here aren't required: | |
| // (i.e. logging, debug callbacks, validation layers, device picking) | |
| // so removing these it'd probably take about less than 1k lines. | |
| // but holy Vulkan... even with dynamic rendering, | |
| // there's so much to unpack here. | |
| // resources: | |
| // https://docs.vulkan.org/tutorial/latest/00_Introduction.html | |
| // https://vulkan-tutorial.com/Introduction | |
| // https://github.com/lwjglgamedev/vulkanbook | |
| // here's the shadercode in one file: | |
| /* | |
| static float2 positions[3] = float2[]( | |
| float2(0.0, -0.5), | |
| float2(0.5, 0.5), | |
| float2(-0.5, 0.5) | |
| ); | |
| static float3 colors[3] = float3[]( | |
| float3(1.0, 0.0, 0.0), | |
| float3(0.0, 1.0, 0.0), | |
| float3(0.0, 0.0, 1.0) | |
| ); | |
| struct VertexOutput { | |
| float3 color; | |
| float4 sv_position : SV_Position; | |
| }; | |
| [shader("vertex")] | |
| VertexOutput vertMain(uint vid : SV_VertexID) { | |
| VertexOutput output; | |
| output.sv_position = float4(positions[vid], 0.0, 1.0); | |
| output.color = colors[vid]; | |
| return output; | |
| } | |
| [shader("fragment")] | |
| float4 fragMain(VertexOutput inVert) : SV_Target | |
| { | |
| float3 color = inVert.color; | |
| return float4(color, 1.0); | |
| } | |
| */ | |
| public class HelloTriangle { | |
| public static final int WIDTH = 800; | |
| public static final int HEIGHT = 600; | |
| public static final String APPLICATION_NAME = HelloTriangle.class.getName(); | |
| public static final String ENGINE_NAME = "Unnamed Engine (2)"; | |
| public static final int APPLICATION_VERSION = 0x01; | |
| public static final int ENGINE_VERSION = 0x02; | |
| public static final boolean VALIDATION = true; | |
| public static final String PORTABILITY_EXTENSION = "VK_KHR_portability_enumeration"; | |
| public static final String VALIDATION_LAYER = "VK_LAYER_KHRONOS_validation"; | |
| public static final int MESSAGE_SEVERITY_BITMASK = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | |
| | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; | |
| public static final int MESSAGE_TYPE_BITMASK = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | |
| | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; | |
| public static final boolean SWAPCHAIN_SUPPORT = true; | |
| public static final Set<String> DEVICE_EXTENSIONS = Set.of("VK_KHR_swapchain"); | |
| public static final int MAX_FRAMES_IN_FLIGHT = 2; | |
| static void main() { | |
| log(Level.INFO, "Starting application"); | |
| log(Level.INFO, System.getenv("VK_LAYER_PATH")); | |
| log(Level.INFO, System.getenv("LD_LIBRARY_PATH")); | |
| HelloTriangle app = null; | |
| try { | |
| app = new HelloTriangle(); | |
| app.loop(); | |
| } finally { | |
| if (app != null) { | |
| app.destroy(); | |
| } | |
| } | |
| } | |
| private long pWindow; | |
| private boolean frameBufferResized = false; | |
| private long vkSurface; | |
| private long vkDebugHandle; | |
| private long vkSwapChain; | |
| private long vkShaderModule; | |
| private long vkPipelineLayout; | |
| private long vkGraphicsPipeline; | |
| private long vkCommandPool; | |
| private boolean validationEnabled; | |
| private VkInstance vkInstance; | |
| private VkDebugUtilsMessengerCreateInfoEXT debugCallback; | |
| private VkPhysicalDevice vkPhysicalDevice; | |
| private VkPhysicalDeviceProperties vkPhysicalDeviceProperties; | |
| private VkPhysicalDeviceFeatures vkPhysicalDeviceFeatures; | |
| private VkDevice vkDevice; | |
| private VkQueue vkGraphicsQueue; | |
| private VkQueue vkPresentQueue; | |
| private int graphicsQueueIndex, presentQueueIndex; | |
| private SwapChainSupportDetails swapChainSupportDetails; | |
| private long[] vkSwapChainImages; | |
| private int vkSwapChainImageFormat; | |
| private VkExtent2D vkSwapChainExtent; | |
| private long[] vkSwapChainImageViews; | |
| private VkCommandBuffer[] vkCommandBuffers; | |
| private long[] vkPresentCompleteSemaphores; | |
| private long[] vkRenderFinishedSemaphores; | |
| private long[] vkDrawFences; | |
| private int frameIndex; | |
| public record SwapChainSupportDetails(VkSurfaceCapabilitiesKHR capabilities, | |
| int[] surfaceFormats, int[] colorSpaces, | |
| int[] presentModes) implements AutoCloseable { | |
| public void free() { | |
| capabilities.free(); | |
| } | |
| @Override | |
| public void close() { | |
| free(); | |
| } | |
| } | |
| public HelloTriangle() { | |
| initWindow(); | |
| initVulkan(); | |
| } | |
| private void initWindow() { | |
| if (!glfwInit()) { | |
| throw new IllegalStateException("GLFW failed to initialize"); | |
| } | |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
| //glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); | |
| glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); | |
| pWindow = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", NULL, NULL); | |
| glfwSetFramebufferSizeCallback(pWindow, this::onFrameBufferResizeCallback); | |
| log(Level.DEBUG, "Created window:", pWindow); | |
| } | |
| private void onFrameBufferResizeCallback(long pWindow, int width, int height) { | |
| frameBufferResized = true; | |
| } | |
| private void initVulkan() { | |
| initInstance(); | |
| initSurface(); | |
| initPhysicalDevice(); | |
| initDevice(); | |
| initSwapchain(); | |
| initSwapchainImages(); | |
| initImageViews(); | |
| initGraphicsPipeline(); | |
| initCommandPool(); | |
| initCommandBuffers(); | |
| initSyncObjects(); | |
| } | |
| private void initInstance() { | |
| log(Level.INFO, "Initializing Vulkan instance"); | |
| try (MemoryStack stack = stackPush()) { | |
| VkApplicationInfo pApplicationInfo = VkApplicationInfo.calloc(stack) | |
| .sType$Default() | |
| .pApplicationName(stack.ASCII(APPLICATION_NAME)) | |
| .pEngineName(stack.ASCII(ENGINE_NAME)) | |
| .applicationVersion(APPLICATION_VERSION) | |
| .engineVersion(ENGINE_VERSION) | |
| .apiVersion(VK_API_VERSION_1_3); | |
| // get validation layers | |
| IntBuffer pCount = stack.callocInt(1); | |
| vkEnumerateInstanceLayerProperties(pCount, null); | |
| VkLayerProperties.Buffer pProperties = VkLayerProperties.calloc(pCount.get(0), stack); | |
| vkEnumerateInstanceLayerProperties(pCount, pProperties); | |
| boolean validation = VALIDATION; | |
| Set<String> supportedLayers = new HashSet<>(pCount.get(0)); | |
| for (int i = 0, c = pCount.get(0); i < c; i++) { | |
| VkLayerProperties properties = pProperties.get(i); | |
| String layerName = properties.layerNameString(); | |
| supportedLayers.add(layerName); | |
| log(Level.DEBUG, "Layer:", layerName); | |
| } | |
| if (!supportedLayers.contains(VALIDATION_LAYER)) { | |
| validation = false; | |
| } | |
| PointerBuffer ppRequiredLayers = null; | |
| if (validation) { | |
| ppRequiredLayers = stack.mallocPointer(1); | |
| ppRequiredLayers.put(stack.UTF8(VALIDATION_LAYER)) | |
| .flip(); | |
| } | |
| // get extensions | |
| vkEnumerateInstanceExtensionProperties((String) null, pCount, null); | |
| VkExtensionProperties.Buffer ppInstanceExtensionProperties = | |
| VkExtensionProperties.calloc(pCount.get(0), stack); | |
| vkEnumerateInstanceExtensionProperties((String) null, pCount, ppInstanceExtensionProperties); | |
| boolean usePortability = false; | |
| for (int i = 0, c = pCount.get(0); i < c; i++) { | |
| VkExtensionProperties pInstanceExtensionProperties = ppInstanceExtensionProperties.get(i); | |
| String extensionName = pInstanceExtensionProperties.extensionNameString(); | |
| log(Level.DEBUG, "Extension:", extensionName); | |
| usePortability |= Objects.equals(extensionName, PORTABILITY_EXTENSION); | |
| } | |
| usePortability &= Platform.get() == Platform.MACOSX; | |
| /* String[] */ PointerBuffer ppGlfwExtensions = glfwGetRequiredInstanceExtensions(); | |
| if (ppGlfwExtensions == null) { | |
| throw new IllegalStateException("Failed to find GLFW platform surfaces"); | |
| } | |
| List<ByteBuffer> additionalExtensions = new ArrayList<>(); | |
| if (validation) { | |
| additionalExtensions.add(stack.UTF8(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)); | |
| log(Level.DEBUG, "Using validation extension"); | |
| } | |
| if (usePortability) { | |
| additionalExtensions.add(stack.UTF8(PORTABILITY_EXTENSION)); | |
| log(Level.DEBUG, "Using portability extension"); | |
| } | |
| /* void* */ PointerBuffer ppRequiredExtensions = | |
| stack.mallocPointer(additionalExtensions.size() + ppGlfwExtensions.remaining()); | |
| ppRequiredExtensions.put(ppGlfwExtensions); | |
| additionalExtensions.forEach(ppRequiredExtensions::put); | |
| ppRequiredExtensions.flip(); | |
| long extension; | |
| debugCallback = validation | |
| ? VkDebugUtilsMessengerCreateInfoEXT | |
| .calloc() | |
| .sType$Default() | |
| .messageSeverity(MESSAGE_SEVERITY_BITMASK) | |
| .messageType(MESSAGE_TYPE_BITMASK) | |
| .pfnUserCallback(this::onDebugCallback) | |
| : null; | |
| extension = validation ? debugCallback.address() : NULL; | |
| this.validationEnabled = validation; | |
| VkInstanceCreateInfo pInstanceCreateInfo = VkInstanceCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .pApplicationInfo(pApplicationInfo) | |
| .ppEnabledLayerNames(ppRequiredLayers) | |
| .ppEnabledExtensionNames(ppRequiredExtensions); | |
| if (validation) { | |
| pInstanceCreateInfo.pNext(extension); | |
| } | |
| if (usePortability) { | |
| pInstanceCreateInfo.flags(VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR); | |
| } | |
| PointerBuffer pInstance = stack.mallocPointer(1); | |
| vkCheck(vkCreateInstance(pInstanceCreateInfo, null, pInstance), | |
| "failed to create instance"); | |
| log(Level.INFO, "Vulkan instance created"); | |
| vkInstance = new VkInstance(pInstance.get(0), pInstanceCreateInfo); | |
| // so we instantiate the debug extension | |
| if (validation) { | |
| LongBuffer pMessenger = stack.mallocLong(1); | |
| int createStatus = vkCreateDebugUtilsMessengerEXT(vkInstance, debugCallback, null, pMessenger); | |
| vkCheck(createStatus, "createDebugUtilsMessenger failed FAHHH"); | |
| log(Level.INFO, "Vulkan debug callback created"); | |
| vkDebugHandle = pMessenger.get(0); | |
| } else { | |
| vkDebugHandle = NULL; | |
| } | |
| } | |
| } | |
| private int onDebugCallback(int messageSeverity, int messageTypes, long pCallbackData, long pUserData) { | |
| String message = VkDebugUtilsMessengerCallbackDataEXT.npMessageString(pCallbackData); | |
| if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0) { | |
| // info | |
| log(Level.INFO, message); | |
| } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0) { | |
| // warning | |
| log(Level.WARN, message); | |
| } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0) { | |
| // error | |
| log(Level.ERROR, message); | |
| } else { | |
| // debug | |
| log(Level.DEBUG, message); | |
| } | |
| return VK_FALSE; | |
| } | |
| private void initPhysicalDevice() { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pDeviceCount = stack.callocInt(1); | |
| vkEnumeratePhysicalDevices(vkInstance, pDeviceCount, null); | |
| int deviceCount = pDeviceCount.get(0); | |
| if (deviceCount == 0) { | |
| throw new IllegalStateException("No Vulkan devices found"); | |
| } | |
| PointerBuffer ppDevices = stack.mallocPointer(deviceCount); | |
| vkEnumeratePhysicalDevices(vkInstance, pDeviceCount, ppDevices); | |
| List<VkPhysicalDevice> physicalDeviceList = pointerBufferToObject(ppDevices, | |
| handle -> new VkPhysicalDevice(handle, vkInstance)); | |
| if (physicalDeviceList.isEmpty()) { | |
| throw new IllegalStateException("Failed to find suitable GPU"); | |
| } | |
| physicalDeviceList.sort(this::pickDeviceScore); | |
| // last has more score so we picking that | |
| VkPhysicalDevice pChosenDevice = physicalDeviceList.getLast(); | |
| printDeviceExtensions(pChosenDevice); | |
| if (!findQueueFamilies(pChosenDevice).isComplete()) { | |
| throw new IllegalStateException("Queue is incomplete"); | |
| } | |
| if (SWAPCHAIN_SUPPORT && !checkDeviceExtensionSupport(pChosenDevice)) { | |
| throw new IllegalStateException("Swapchain support is missing"); | |
| } | |
| try (SwapChainSupportDetails swapChainSupportDetails = querySwapChainSupport(pChosenDevice)) { | |
| if (swapChainSupportDetails.surfaceFormats().length == 0 | |
| || swapChainSupportDetails.presentModes().length == 0) { | |
| throw new IllegalStateException("Swapchain not supported"); | |
| } | |
| } | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT pDynamicState = | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceVulkan13Features pVulkan13Features = | |
| VkPhysicalDeviceVulkan13Features.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceFeatures2 pPhysicalDeviceFeatures = VkPhysicalDeviceFeatures2.calloc(stack) | |
| .sType$Default() | |
| .pNext(pVulkan13Features); | |
| vkGetPhysicalDeviceFeatures2(pChosenDevice, pPhysicalDeviceFeatures); | |
| if (!pVulkan13Features.dynamicRendering()) { | |
| throw new IllegalStateException("dynamic rendering non existent"); | |
| } | |
| if (!pVulkan13Features.synchronization2()) { | |
| throw new IllegalStateException("synchronization2 non existent"); | |
| } | |
| pPhysicalDeviceFeatures.pNext(pDynamicState); | |
| vkGetPhysicalDeviceFeatures2(pChosenDevice, pPhysicalDeviceFeatures); | |
| if (!pDynamicState.extendedDynamicState()) { | |
| throw new IllegalStateException("extended dynamic state non existent"); | |
| } | |
| vkPhysicalDeviceProperties = VkPhysicalDeviceProperties.calloc(); | |
| vkGetPhysicalDeviceProperties(pChosenDevice, vkPhysicalDeviceProperties); | |
| vkPhysicalDeviceFeatures = VkPhysicalDeviceFeatures.calloc(); | |
| vkGetPhysicalDeviceFeatures(pChosenDevice, vkPhysicalDeviceFeatures); | |
| log(Level.INFO, "Device chosen:", vkPhysicalDeviceProperties.deviceNameString()); | |
| vkPhysicalDevice = pChosenDevice; | |
| } | |
| } | |
| private static <T> List<T> pointerBufferToObject(PointerBuffer buffer, Function<Long, T> mapper) { | |
| int size = buffer.capacity(); | |
| List<T> list = new ArrayList<>(size); | |
| for (int i = 0; i < size; i++) { | |
| list.add(mapper.apply(buffer.get(i))); | |
| } | |
| return list; | |
| } | |
| // what was bro respectfully doing here | |
| private int pickDeviceScore(VkPhysicalDevice device, VkPhysicalDevice other) { | |
| try (MemoryStack stack = stackPush()) { | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT pDynamicState = | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceVulkan11Features pVulkan11Features = | |
| VkPhysicalDeviceVulkan11Features.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceVulkan13Features pVulkan13Features = | |
| VkPhysicalDeviceVulkan13Features.calloc(stack) | |
| .sType$Default() | |
| .pNext(pVulkan11Features.address()); | |
| VkPhysicalDeviceFeatures2 pPhysicalDeviceFeatures = VkPhysicalDeviceFeatures2.calloc(stack) | |
| .sType$Default() | |
| .pNext(pVulkan13Features); | |
| vkGetPhysicalDeviceFeatures2(device, pPhysicalDeviceFeatures); | |
| pPhysicalDeviceFeatures.pNext(pDynamicState); | |
| vkGetPhysicalDeviceFeatures2(device, pPhysicalDeviceFeatures); | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT pOtherDynamicState = | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceVulkan13Features pOtherVulkan13Features = | |
| VkPhysicalDeviceVulkan13Features.calloc(stack) | |
| .sType$Default(); | |
| VkPhysicalDeviceVulkan11Features pOtherVulkan11Features = | |
| VkPhysicalDeviceVulkan11Features.calloc(stack) | |
| .sType$Default() | |
| .pNext(pOtherVulkan13Features.address()); | |
| VkPhysicalDeviceFeatures2 pOtherPhysicalDeviceFeatures = VkPhysicalDeviceFeatures2.calloc(stack) | |
| .sType$Default() | |
| .pNext(pOtherVulkan13Features); | |
| vkGetPhysicalDeviceFeatures2(other, pOtherPhysicalDeviceFeatures); | |
| pOtherPhysicalDeviceFeatures.pNext(pOtherDynamicState); | |
| vkGetPhysicalDeviceFeatures2(device, pOtherPhysicalDeviceFeatures); | |
| VkPhysicalDeviceProperties deviceProperties = VkPhysicalDeviceProperties.calloc(stack); | |
| VkPhysicalDeviceProperties otherDeviceProperties = VkPhysicalDeviceProperties.calloc(stack); | |
| vkGetPhysicalDeviceProperties(device, deviceProperties); | |
| vkGetPhysicalDeviceProperties(other, otherDeviceProperties); | |
| int physicalDeviceTypeScore = switch (deviceProperties.deviceType()) { | |
| case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU -> 1000; | |
| case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU -> 500; | |
| case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, | |
| VK_PHYSICAL_DEVICE_TYPE_OTHER -> 300; | |
| case VK_PHYSICAL_DEVICE_TYPE_CPU -> 0; | |
| default -> 0; | |
| }; | |
| int otherPhysicalDeviceTypeScore = switch (otherDeviceProperties.deviceType()) { | |
| case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU -> 1000; | |
| case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU -> 500; | |
| case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, | |
| VK_PHYSICAL_DEVICE_TYPE_OTHER -> 300; | |
| case VK_PHYSICAL_DEVICE_TYPE_CPU -> 0; | |
| default -> 0; | |
| }; | |
| int maxImageDimension = deviceProperties.limits().maxImageDimension2D(); | |
| int otherMaxImageDimension = otherDeviceProperties.limits().maxImageDimension2D(); | |
| QueueFamilyIndices queueFamily = findQueueFamilies(device); | |
| QueueFamilyIndices otherQueueFamily = findQueueFamilies(device); | |
| IntBuffer pCount = stack.callocInt(1); | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(device, vkSurface, pCount, null); | |
| boolean hasFormats = pCount.get(0) != 0; | |
| vkGetPhysicalDeviceSurfacePresentModesKHR(device, vkSurface, pCount, null); | |
| boolean hasPresentModes = pCount.get(0) != 0; | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(other, vkSurface, pCount, null); | |
| boolean otherHasFormats = pCount.get(0) != 0; | |
| vkGetPhysicalDeviceSurfacePresentModesKHR(other, vkSurface, pCount, null); | |
| boolean otherHasPresentModes = pCount.get(0) != 0; | |
| int hasQueueFamilies = queueFamily.isComplete() ? 500 : 0; | |
| int otherHasQueueFamilies = otherQueueFamily.isComplete() ? 500 : 0; | |
| int isEqualQueueFamilyIndex = queueFamily.isSameFamily() ? 500 : 0; | |
| int otherIsEqualQueueFamilyIndex = otherQueueFamily.isSameFamily() ? 500 : 0; | |
| // swapchain support is critical | |
| int deviceExtensionSupport = checkDeviceExtensionSupport(device) ? 2000 : 0; | |
| int otherDeviceExtensionSupport = checkDeviceExtensionSupport(other) ? 2000 : 0; | |
| int hasFormatsScore = hasFormats ? 2000 : 0; | |
| int otherHasFormatsScore = otherHasFormats ? 2000 : 0; | |
| int hasPresentModesScore = hasPresentModes ? 2000 : 0; | |
| int otherHasPresentModesScore = otherHasPresentModes ? 2000 : 0; | |
| int dynamicRenderingScore = pVulkan13Features.dynamicRendering() ? 1000 : 0; | |
| int otherDynamicRenderingScore = pOtherVulkan13Features.dynamicRendering() ? 1000 : 0; | |
| int synchronization2Score = pVulkan13Features.synchronization2() ? 1000 : 0; | |
| int otherSynchronization2Score = pOtherVulkan13Features.synchronization2() ? 1000 : 0; | |
| int shaderDrawParamsScore = pVulkan11Features.shaderDrawParameters() ? 1000 : 0; | |
| int otherShaderDrawParamsScore = pOtherVulkan11Features.shaderDrawParameters() ? 1000 : 0; | |
| int dynamicStateScore = pDynamicState.extendedDynamicState() ? 1000 : 0; | |
| int otherDynamicStateScore = pOtherDynamicState.extendedDynamicState() ? 1000 : 0; | |
| int score = physicalDeviceTypeScore - otherPhysicalDeviceTypeScore; | |
| score += maxImageDimension - otherMaxImageDimension; | |
| score += hasQueueFamilies - otherHasQueueFamilies; | |
| score += isEqualQueueFamilyIndex - otherIsEqualQueueFamilyIndex; | |
| score += deviceExtensionSupport - otherDeviceExtensionSupport; | |
| score += hasFormatsScore - otherHasFormatsScore; | |
| score += hasPresentModesScore - otherHasPresentModesScore; | |
| score += dynamicRenderingScore - otherDynamicRenderingScore; | |
| score += synchronization2Score - otherSynchronization2Score; | |
| score += dynamicStateScore - otherDynamicStateScore; | |
| score += shaderDrawParamsScore - otherShaderDrawParamsScore; | |
| log(Level.DEBUG, | |
| deviceProperties.deviceNameString(), | |
| "VS", | |
| otherDeviceProperties.deviceNameString()); | |
| log(Level.DEBUG, | |
| "Score (", | |
| deviceProperties.deviceNameString(), | |
| "VS", | |
| otherDeviceProperties.deviceNameString(), | |
| ") :", | |
| score); | |
| if (score != 0) { | |
| log(Level.DEBUG, | |
| "AND THE CROWD CHOOSES", | |
| score > 0 ? deviceProperties.deviceNameString() : otherDeviceProperties.deviceNameString()); | |
| } else { | |
| log(Level.DEBUG, "AND THE CROWD CHOOSES... both..?"); | |
| } | |
| return score; | |
| } | |
| } | |
| private boolean checkDeviceExtensionSupport(VkPhysicalDevice pDevice) { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pExtensionCount = stack.callocInt(1); | |
| vkEnumerateDeviceExtensionProperties(pDevice, (String) null, pExtensionCount, null); | |
| VkExtensionProperties.Buffer ppExtensionProperties = | |
| VkExtensionProperties.calloc(pExtensionCount.get(0), stack); | |
| vkEnumerateDeviceExtensionProperties(pDevice, (String) null, pExtensionCount, ppExtensionProperties); | |
| Set<String> requiredExtensions = new HashSet<>(DEVICE_EXTENSIONS); | |
| for (int i = 0, c = pExtensionCount.get(0); i < c; i++) { | |
| VkExtensionProperties pExtensionProperties = ppExtensionProperties.get(i); | |
| requiredExtensions.remove(pExtensionProperties.extensionNameString()); | |
| } | |
| return requiredExtensions.isEmpty(); | |
| } | |
| } | |
| private void printDeviceExtensions(VkPhysicalDevice pDevice) { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pExtensionCount = stack.callocInt(1); | |
| vkEnumerateDeviceExtensionProperties(pDevice, (String) null, pExtensionCount, null); | |
| VkExtensionProperties.Buffer ppExtensionProperties = | |
| VkExtensionProperties.calloc(pExtensionCount.get(0), stack); | |
| vkEnumerateDeviceExtensionProperties(pDevice, (String) null, pExtensionCount, ppExtensionProperties); | |
| for (int i = 0, c = pExtensionCount.get(0); i < c; i++) { | |
| VkExtensionProperties pExtensionProperties = ppExtensionProperties.get(i); | |
| log(Level.DEBUG, "Device extension:", pExtensionProperties.extensionNameString()); | |
| } | |
| } | |
| } | |
| record QueueFamilyIndices(OptionalInt graphicsFamily, OptionalInt presentFamily) { | |
| public boolean isComplete() { | |
| return graphicsFamily.isPresent() && presentFamily.isPresent(); | |
| } | |
| public boolean isSameFamily() { | |
| return isComplete() && graphicsFamily.orElseThrow() == presentFamily.orElseThrow(); | |
| } | |
| } | |
| private QueueFamilyIndices findQueueFamilies(VkPhysicalDevice pDevice) { | |
| OptionalInt graphicsFamily = OptionalInt.empty(); | |
| OptionalInt presentFamily = OptionalInt.empty(); | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pQueueFamilyCount = stack.callocInt(1); | |
| vkGetPhysicalDeviceQueueFamilyProperties(pDevice, pQueueFamilyCount, null); | |
| int queueFamilyCount = pQueueFamilyCount.get(0); | |
| VkQueueFamilyProperties.Buffer ppQueueFamilies = | |
| VkQueueFamilyProperties.calloc(pQueueFamilyCount.get(0), stack); | |
| vkGetPhysicalDeviceQueueFamilyProperties(pDevice, pQueueFamilyCount, ppQueueFamilies); | |
| for (int i = 0; i < queueFamilyCount; i++) { | |
| VkQueueFamilyProperties pQueueFamily = ppQueueFamilies.get(i); | |
| if ((pQueueFamily.queueFlags() & VK_QUEUE_GRAPHICS_BIT) != 0) { | |
| graphicsFamily = OptionalInt.of(i); | |
| } | |
| try (MemoryStack stack2 = stackPush()) { | |
| IntBuffer pSupported = stack2.callocInt(1); | |
| vkGetPhysicalDeviceSurfaceSupportKHR(pDevice, i, vkSurface, pSupported); | |
| if (pSupported.get() > 0) { | |
| presentFamily = OptionalInt.of(i); | |
| } | |
| } | |
| } | |
| } | |
| return new QueueFamilyIndices(graphicsFamily, presentFamily); | |
| } | |
| private void initDevice() { | |
| QueueFamilyIndices queuesIndex = findQueueFamilies(vkPhysicalDevice); | |
| try (MemoryStack stack = stackPush()) { | |
| graphicsQueueIndex = queuesIndex.graphicsFamily.orElseThrow(); | |
| presentQueueIndex = queuesIndex.presentFamily.orElseThrow(); | |
| Set<Integer> queueFamilies = queuesIndex.isSameFamily() | |
| ? Set.of(graphicsQueueIndex) | |
| : Set.of(graphicsQueueIndex, presentQueueIndex); | |
| final float queuePriority = 1; | |
| final int queueCount = queueFamilies.size(); | |
| FloatBuffer pQueuePriority = stack.callocFloat(1); | |
| pQueuePriority.put(queuePriority); | |
| pQueuePriority.flip(); | |
| VkDeviceQueueCreateInfo.Buffer ppQueueCreateInfo = VkDeviceQueueCreateInfo.calloc(queueCount, stack); | |
| int i = 0; | |
| for (int queueFamily : queueFamilies) { | |
| VkDeviceQueueCreateInfo pQueueCreateInfo = VkDeviceQueueCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .queueFamilyIndex(queueFamily) | |
| .pQueuePriorities(pQueuePriority); | |
| ppQueueCreateInfo.put(i++, pQueueCreateInfo); | |
| } | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT pDynamicState = | |
| VkPhysicalDeviceExtendedDynamicStateFeaturesEXT.calloc(stack) | |
| .sType$Default() | |
| .extendedDynamicState(true); | |
| VkPhysicalDeviceVulkan11Features pVulkan11Features = | |
| VkPhysicalDeviceVulkan11Features.calloc(stack) | |
| .sType$Default() | |
| .shaderDrawParameters(true) | |
| .pNext(pDynamicState.address()); | |
| VkPhysicalDeviceVulkan13Features pVulkan13Features = | |
| VkPhysicalDeviceVulkan13Features.calloc(stack) | |
| .sType$Default() | |
| .dynamicRendering(true) | |
| .synchronization2(true) | |
| .pNext(pVulkan11Features.address()); | |
| VkDeviceCreateInfo pDeviceCreateInfo = VkDeviceCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .pQueueCreateInfos(ppQueueCreateInfo) | |
| .pNext(pVulkan13Features); | |
| if (validationEnabled) { | |
| PointerBuffer pLayerNames = stack.mallocPointer(1); | |
| pLayerNames.put(stack.ASCII(VALIDATION_LAYER)); | |
| pDeviceCreateInfo.ppEnabledLayerNames(pLayerNames); | |
| } | |
| int deviceExtensionCount = DEVICE_EXTENSIONS.size(); | |
| PointerBuffer ppEnabledExtensions = stack.mallocPointer(deviceExtensionCount); | |
| DEVICE_EXTENSIONS.forEach(ext -> ppEnabledExtensions.put(stack.ASCII(ext))); | |
| ppEnabledExtensions.flip(); | |
| pDeviceCreateInfo.ppEnabledExtensionNames(ppEnabledExtensions); | |
| PointerBuffer ppDevice = stack.mallocPointer(1); | |
| vkCheck(vkCreateDevice(vkPhysicalDevice, pDeviceCreateInfo, null, ppDevice), | |
| "failed to create device"); | |
| vkDevice = new VkDevice(ppDevice.get(0), vkPhysicalDevice, pDeviceCreateInfo); | |
| log(Level.INFO, "Device created"); | |
| PointerBuffer ppQueue = stack.mallocPointer(1); | |
| vkGetDeviceQueue(vkDevice, queuesIndex.graphicsFamily().orElseThrow(), 0, ppQueue); | |
| vkGraphicsQueue = new VkQueue(ppQueue.get(0), vkDevice); | |
| log(Level.INFO, "Graphics queue retrieved"); | |
| vkGetDeviceQueue(vkDevice, queuesIndex.presentFamily().orElseThrow(), 0, ppQueue); | |
| vkPresentQueue = new VkQueue(ppQueue.get(0), vkDevice); | |
| log(Level.INFO, "Present queue retrieved"); | |
| } | |
| } | |
| private void initSurface() { | |
| try (MemoryStack stack = stackPush()) { | |
| LongBuffer pSurface = stack.mallocLong(1); | |
| vkCheck(glfwCreateWindowSurface(vkInstance, pWindow, null, pSurface), | |
| "Failed to create surface"); | |
| log(Level.INFO, "Surface created"); | |
| vkSurface = pSurface.get(0); | |
| } | |
| } | |
| private SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice pDevice) { | |
| try (MemoryStack stack = stackPush()) { | |
| VkSurfaceCapabilitiesKHR pSurfaceCapabilities = VkSurfaceCapabilitiesKHR.calloc(); | |
| vkGetPhysicalDeviceSurfaceCapabilitiesKHR(pDevice, vkSurface, pSurfaceCapabilities); | |
| IntBuffer pCount = stack.callocInt(1); | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(pDevice, vkSurface, pCount, null); | |
| int formatCount = pCount.get(0); | |
| boolean hasFormats = formatCount != 0; | |
| VkSurfaceFormatKHR.Buffer ppSurfaceFormat = VkSurfaceFormatKHR.calloc(formatCount, stack); | |
| if (hasFormats) { | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(pDevice, vkSurface, pCount, ppSurfaceFormat); | |
| } | |
| vkGetPhysicalDeviceSurfacePresentModesKHR(pDevice, vkSurface, pCount, null); | |
| int presentModeCount = pCount.get(0); | |
| boolean hasPresentModes = presentModeCount != 0; | |
| IntBuffer pPresentModes = stack.callocInt(presentModeCount); | |
| if (hasPresentModes) { | |
| vkGetPhysicalDeviceSurfacePresentModesKHR(pDevice, vkSurface, pCount, pPresentModes); | |
| } | |
| int[] surfaceFormats = new int[formatCount]; | |
| for (int i = 0; i < formatCount; i++) { | |
| surfaceFormats[i] = ppSurfaceFormat.get(i).format(); | |
| } | |
| int[] colorSpaces = new int[formatCount]; | |
| for (int i = 0; i < formatCount; i++) { | |
| colorSpaces[i] = ppSurfaceFormat.get(i).colorSpace(); | |
| } | |
| int[] presentModes = new int[presentModeCount]; | |
| for (int i = 0; i < presentModeCount; i++) { | |
| presentModes[i] = pPresentModes.get(i); | |
| } | |
| return new SwapChainSupportDetails(pSurfaceCapabilities, surfaceFormats, colorSpaces, presentModes); | |
| } | |
| } | |
| private int chooseSwapSurfaceFormat(int[] availableFormats, int[] availableColorSpaces) { | |
| for (int i = 0; i < availableFormats.length; i++) { | |
| int format = availableFormats[i]; | |
| int colorSpace = availableColorSpaces[i]; | |
| if (format == VK_FORMAT_B8G8R8_SRGB | |
| && colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { | |
| return i; | |
| } | |
| } | |
| return 0; | |
| } | |
| private int chooseSwapPresentMode(int[] availablePresetModes) { | |
| for (int presentMode : availablePresetModes) { | |
| if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { | |
| return presentMode; | |
| } | |
| } | |
| return VK_PRESENT_MODE_FIFO_KHR; | |
| } | |
| private VkExtent2D chooseSwapExtent(VkSurfaceCapabilitiesKHR pCapabilities) { | |
| VkExtent2D pExtent = VkExtent2D.calloc(); | |
| VkExtent2D currentExtent = pCapabilities.currentExtent(); | |
| if (currentExtent.width() != Integer.MAX_VALUE) { | |
| return pExtent.set(currentExtent); | |
| } else { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pWidth = stack.callocInt(1); | |
| IntBuffer pHeight = stack.callocInt(1); | |
| glfwGetFramebufferSize(pWindow, pWidth, pHeight); | |
| int width = pWidth.get(0); | |
| int height = pHeight.get(0); | |
| int extentWidth = Math.clamp(width, | |
| pCapabilities.minImageExtent().width(), | |
| pCapabilities.maxImageExtent().width()); | |
| int extentHeight = Math.clamp(height, | |
| pCapabilities.minImageExtent().height(), | |
| pCapabilities.maxImageExtent().height()); | |
| pExtent.set(extentWidth, extentHeight); | |
| } | |
| return pExtent; | |
| } | |
| } | |
| private void initSwapchain() { | |
| swapChainSupportDetails = querySwapChainSupport(vkPhysicalDevice); | |
| try (MemoryStack stack = stackPush()) { | |
| VkExtent2D pExtent = chooseSwapExtent(swapChainSupportDetails.capabilities()); | |
| log(Level.INFO, "Creating swapchain"); | |
| // prevent double free | |
| // noinspection resource | |
| int surfaceFormatIndex = chooseSwapSurfaceFormat(swapChainSupportDetails.surfaceFormats(), | |
| swapChainSupportDetails.colorSpaces()); | |
| int presentMode = chooseSwapPresentMode(swapChainSupportDetails.presentModes()); | |
| IntBuffer pCount = stack.callocInt(1); | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(vkPhysicalDevice, vkSurface, pCount, null); | |
| int formatCount = pCount.get(0); | |
| boolean hasFormats = formatCount != 0; | |
| VkSurfaceFormatKHR.Buffer ppSurfaceFormat = VkSurfaceFormatKHR.calloc(formatCount, stack); | |
| if (hasFormats) { | |
| vkGetPhysicalDeviceSurfaceFormatsKHR(vkPhysicalDevice, vkSurface, pCount, ppSurfaceFormat); | |
| } | |
| VkSurfaceFormatKHR pSurfaceFormat = ppSurfaceFormat.get(surfaceFormatIndex); | |
| int imageCount = swapChainSupportDetails.capabilities().minImageCount() + 1; | |
| if (swapChainSupportDetails.capabilities().maxImageCount() > 0 | |
| && imageCount > swapChainSupportDetails.capabilities().maxImageCount()) { | |
| imageCount = swapChainSupportDetails.capabilities().maxImageCount(); | |
| } | |
| log(Level.INFO, "image count:", imageCount); | |
| log(Level.INFO, "present mode:", presentMode); | |
| log(Level.INFO, "surface format:", pSurfaceFormat.format()); | |
| log(Level.INFO, "color space:", pSurfaceFormat.colorSpace()); | |
| VkSwapchainCreateInfoKHR pSwapchainCreateInfo = VkSwapchainCreateInfoKHR.calloc(stack) | |
| .sType$Default() | |
| .surface(vkSurface) | |
| .minImageCount(imageCount) | |
| .imageFormat(pSurfaceFormat.format()) | |
| .imageColorSpace(pSurfaceFormat.colorSpace()) | |
| .imageExtent(pExtent) | |
| .imageArrayLayers(1) | |
| .imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); | |
| QueueFamilyIndices indices = findQueueFamilies(vkPhysicalDevice); | |
| if (!indices.isSameFamily()) { | |
| IntBuffer pIndices = stack.callocInt(2); | |
| pIndices.put(indices.graphicsFamily().orElseThrow()); | |
| pIndices.put(indices.presentFamily().orElseThrow()); | |
| pIndices.flip(); | |
| pSwapchainCreateInfo.imageSharingMode(VK_SHARING_MODE_CONCURRENT) | |
| .queueFamilyIndexCount(2) | |
| .pQueueFamilyIndices(pIndices); | |
| log(Level.INFO, "Using concurrent sharing mode"); | |
| } else { | |
| pSwapchainCreateInfo.imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) | |
| .queueFamilyIndexCount(0) | |
| .pQueueFamilyIndices(null); | |
| log(Level.INFO, "Using exclusive sharing mode"); | |
| } | |
| pSwapchainCreateInfo.preTransform(swapChainSupportDetails.capabilities().currentTransform()) | |
| .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) | |
| .presentMode(presentMode) | |
| .clipped(true) | |
| .oldSwapchain(NULL); | |
| LongBuffer pSwapChain = stack.callocLong(1); | |
| vkCheck(vkCreateSwapchainKHR(vkDevice, pSwapchainCreateInfo, null, pSwapChain), | |
| "swapchain failed to create"); | |
| vkSwapChain = pSwapChain.get(0); | |
| log(Level.INFO, "Created swapchain"); | |
| vkSwapChainImageFormat = pSurfaceFormat.format(); | |
| vkSwapChainExtent = pExtent; | |
| } | |
| } | |
| private void initSwapchainImages() { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pImageCount = stack.callocInt(1); | |
| vkGetSwapchainImagesKHR(vkDevice, vkSwapChain, pImageCount, null); | |
| vkSwapChainImages = new long[pImageCount.get(0)]; | |
| LongBuffer ppSwapChainImages = stack.callocLong(pImageCount.get(0)); | |
| vkGetSwapchainImagesKHR(vkDevice, vkSwapChain, pImageCount, ppSwapChainImages); | |
| for (int i = 0, c = pImageCount.get(0); i < c; i++) { | |
| vkSwapChainImages[i] = ppSwapChainImages.get(i); | |
| } | |
| log(Level.INFO, "swapchain image count:", pImageCount.get(0)); | |
| } | |
| } | |
| private void initImageViews() { | |
| try (MemoryStack stack = stackPush()) { | |
| vkSwapChainImageViews = new long[vkSwapChainImages.length]; | |
| LongBuffer ppImageView = stack.callocLong(1); | |
| for (int i = 0; i < vkSwapChainImages.length; i++) { | |
| long pSwapChainImage = vkSwapChainImages[i]; | |
| VkImageViewCreateInfo pImageViewCreateInfo = VkImageViewCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .image(pSwapChainImage) | |
| .viewType(VK_IMAGE_VIEW_TYPE_2D) | |
| .format(vkSwapChainImageFormat); | |
| pImageViewCreateInfo.components() | |
| .r(VK_COMPONENT_SWIZZLE_IDENTITY) | |
| .g(VK_COMPONENT_SWIZZLE_IDENTITY) | |
| .b(VK_COMPONENT_SWIZZLE_IDENTITY) | |
| .a(VK_COMPONENT_SWIZZLE_IDENTITY); | |
| pImageViewCreateInfo.subresourceRange() | |
| .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) | |
| .baseMipLevel(0) | |
| .levelCount(1) | |
| .baseArrayLayer(0) | |
| .layerCount(1); | |
| vkCheck(vkCreateImageView(vkDevice, pImageViewCreateInfo, null, ppImageView), | |
| "failed to create image views"); | |
| vkSwapChainImageViews[i] = ppImageView.get(0); | |
| } | |
| } | |
| } | |
| private ByteBuffer readResourceAsByteBuffer(String resource) { | |
| try (InputStream stream = | |
| Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResourceAsStream(resource))) { | |
| byte[] bytes = stream.readAllBytes(); | |
| ByteBuffer buf = MemoryUtil.memAlloc(bytes.length); | |
| buf.put(bytes).flip(); | |
| return buf; | |
| } catch (IOException e) { | |
| throw new UncheckedIOException(e); | |
| } catch (NullPointerException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| } | |
| private void initGraphicsPipeline() { | |
| ByteBuffer pShaderCode = null; | |
| try (MemoryStack stack = stackPush()) { | |
| pShaderCode = readResourceAsByteBuffer("slang.spv"); | |
| vkShaderModule = createShaderModule(pShaderCode); | |
| VkPipelineShaderStageCreateInfo pVertStageInfo = VkPipelineShaderStageCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .stage(VK_SHADER_STAGE_VERTEX_BIT) | |
| .module(vkShaderModule) | |
| .pName(stack.ASCII("vertMain")); | |
| VkPipelineShaderStageCreateInfo pFragStageInfo = VkPipelineShaderStageCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .stage(VK_SHADER_STAGE_FRAGMENT_BIT) | |
| .module(vkShaderModule) | |
| .pName(stack.ASCII("fragMain")); | |
| VkPipelineShaderStageCreateInfo.Buffer ppShaderStages = VkPipelineShaderStageCreateInfo.calloc(2, stack); | |
| ppShaderStages.put(pVertStageInfo) | |
| .put(pFragStageInfo) | |
| .flip(); | |
| IntBuffer pDynamicStates = stack.ints(VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR); | |
| VkPipelineDynamicStateCreateInfo pDynamicStateInfo = VkPipelineDynamicStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .pDynamicStates(pDynamicStates); | |
| VkPipelineVertexInputStateCreateInfo pVertexInputInfo = VkPipelineVertexInputStateCreateInfo.calloc(stack) | |
| .sType$Default(); | |
| VkPipelineInputAssemblyStateCreateInfo pInputAssemblyInfo = | |
| VkPipelineInputAssemblyStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); | |
| VkPipelineViewportStateCreateInfo pViewportStateInfo = VkPipelineViewportStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .viewportCount(1) | |
| .scissorCount(1); | |
| VkPipelineRasterizationStateCreateInfo pRasterizerInfo = | |
| VkPipelineRasterizationStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .depthBiasEnable(false) | |
| .rasterizerDiscardEnable(false) | |
| .polygonMode(VK_POLYGON_MODE_FILL) | |
| .cullMode(VK_CULL_MODE_BACK_BIT) | |
| .frontFace(VK_FRONT_FACE_CLOCKWISE) | |
| .depthBiasEnable(false) | |
| .lineWidth(1); | |
| VkPipelineMultisampleStateCreateInfo pMultisampleInfo = VkPipelineMultisampleStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .rasterizationSamples(VK_SAMPLE_COUNT_1_BIT) | |
| .sampleShadingEnable(false); | |
| int colorWriteMask = VK_COLOR_COMPONENT_R_BIT | |
| | VK_COLOR_COMPONENT_G_BIT | |
| | VK_COLOR_COMPONENT_B_BIT | |
| | VK_COLOR_COMPONENT_A_BIT; | |
| VkPipelineColorBlendAttachmentState pColorBlendAttachmentInfo = | |
| VkPipelineColorBlendAttachmentState.calloc(stack) | |
| .blendEnable(false) | |
| .colorWriteMask(colorWriteMask); | |
| VkPipelineColorBlendAttachmentState.Buffer ppColorBlendAttachments = | |
| VkPipelineColorBlendAttachmentState.calloc(1, stack); | |
| ppColorBlendAttachments.put(pColorBlendAttachmentInfo) | |
| .flip(); | |
| VkPipelineColorBlendStateCreateInfo pColorBlendingInfo = VkPipelineColorBlendStateCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .logicOpEnable(false) | |
| .logicOp(VK_LOGIC_OP_COPY) | |
| .attachmentCount(1) | |
| .pAttachments(ppColorBlendAttachments); | |
| VkPipelineLayoutCreateInfo pPipelineLayoutInfo = VkPipelineLayoutCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .setLayoutCount(0); | |
| LongBuffer ppPipelineLayout = stack.callocLong(1); | |
| vkCheck(vkCreatePipelineLayout(vkDevice, pPipelineLayoutInfo, null, ppPipelineLayout), | |
| "graphics pipeline layout failed to create"); | |
| vkPipelineLayout = ppPipelineLayout.get(0); | |
| log(Level.INFO, "graphics pipeline layout created"); | |
| IntBuffer pSwapChainImageFormat = stack.ints(vkSwapChainImageFormat); | |
| VkPipelineRenderingCreateInfo pPipelineRenderingInfo = VkPipelineRenderingCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .colorAttachmentCount(1) | |
| .pColorAttachmentFormats(pSwapChainImageFormat); | |
| VkGraphicsPipelineCreateInfo pGraphicsPipelineInfo = VkGraphicsPipelineCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .pNext(pPipelineRenderingInfo) | |
| .pStages(ppShaderStages) | |
| .pVertexInputState(pVertexInputInfo) | |
| .pInputAssemblyState(pInputAssemblyInfo) | |
| .pViewportState(pViewportStateInfo) | |
| .pRasterizationState(pRasterizerInfo) | |
| .pMultisampleState(pMultisampleInfo) | |
| .pColorBlendState(pColorBlendingInfo) | |
| .pDynamicState(pDynamicStateInfo) | |
| .layout(vkPipelineLayout) | |
| .renderPass(NULL); | |
| VkGraphicsPipelineCreateInfo.Buffer ppGraphicsPipelineInfo = | |
| VkGraphicsPipelineCreateInfo.calloc(1, stack); | |
| ppGraphicsPipelineInfo.put(pGraphicsPipelineInfo) | |
| .flip(); | |
| LongBuffer ppGraphicsPipeline = stack.callocLong(1); | |
| vkCheck(vkCreateGraphicsPipelines(vkDevice, | |
| NULL, | |
| ppGraphicsPipelineInfo, | |
| null, | |
| ppGraphicsPipeline), | |
| "graphics pipeline failed to create"); | |
| vkGraphicsPipeline = ppGraphicsPipeline.get(0); | |
| log(Level.INFO, "graphics pipeline successfully created!"); | |
| } finally { | |
| if (pShaderCode != null) { | |
| MemoryUtil.memFree(pShaderCode); | |
| } | |
| } | |
| } | |
| private long createShaderModule(ByteBuffer shaderCode) { | |
| try (MemoryStack stack = stackPush()) { | |
| VkShaderModuleCreateInfo pShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .pCode(shaderCode); | |
| LongBuffer ppShader = stack.callocLong(1); | |
| vkCheck(vkCreateShaderModule(vkDevice, pShaderModuleCreateInfo, null, ppShader), | |
| "failed to create shader"); | |
| return ppShader.get(0); | |
| } | |
| } | |
| private void initCommandPool() { | |
| try (MemoryStack stack = stackPush()) { | |
| VkCommandPoolCreateInfo pPoolInfo = VkCommandPoolCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .flags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) | |
| .queueFamilyIndex(graphicsQueueIndex); | |
| LongBuffer ppCommandPool = stack.callocLong(1); | |
| vkCheck(vkCreateCommandPool(vkDevice, pPoolInfo, null, ppCommandPool), | |
| "command pool failation creature"); | |
| vkCommandPool = ppCommandPool.get(0); | |
| log(Level.INFO, "created command pool"); | |
| } | |
| } | |
| private void initCommandBuffers() { | |
| try (MemoryStack stack = stackPush()) { | |
| final int count = MAX_FRAMES_IN_FLIGHT; | |
| VkCommandBufferAllocateInfo bufferAllocateInfo = VkCommandBufferAllocateInfo.calloc(stack) | |
| .sType$Default() | |
| .commandPool(vkCommandPool) | |
| .level(VK_COMMAND_BUFFER_LEVEL_PRIMARY) | |
| .commandBufferCount(count); | |
| PointerBuffer ppCommandBuffers = stack.mallocPointer(count); | |
| vkCheck(vkAllocateCommandBuffers(vkDevice, bufferAllocateInfo, ppCommandBuffers), | |
| "error creation command buffers"); | |
| vkCommandBuffers = new VkCommandBuffer[count]; | |
| for (int i = 0; i < count; i++) { | |
| vkCommandBuffers[i] = new VkCommandBuffer(ppCommandBuffers.get(i), vkDevice); | |
| } | |
| log(Level.INFO, "created command buffers"); | |
| } | |
| } | |
| private void recordCommandBuffer(int inFlightIndex, int imageIndex) { | |
| try (MemoryStack stack = stackPush()) { | |
| VkCommandBuffer vkCommandBuffer = vkCommandBuffers[inFlightIndex]; | |
| VkCommandBufferBeginInfo pBeginInfo = VkCommandBufferBeginInfo.calloc(stack) | |
| .sType$Default(); | |
| vkBeginCommandBuffer(vkCommandBuffer, pBeginInfo); | |
| transitionImageLayout(imageIndex, | |
| VK_IMAGE_LAYOUT_UNDEFINED, | |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, | |
| 0, | |
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, | |
| VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| vkCommandBuffer); | |
| VkClearColorValue pClearColorValue = VkClearColorValue.calloc(stack) | |
| .float32(stack.floats(0, 0, 0, 1)); | |
| VkClearValue pClearColor = VkClearValue.calloc(stack) | |
| .color(pClearColorValue); | |
| VkRenderingAttachmentInfo pAttachmentInfo = VkRenderingAttachmentInfo.calloc(stack) | |
| .sType$Default() | |
| .imageView(vkSwapChainImageViews[imageIndex]) | |
| .imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) | |
| .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) | |
| .storeOp(VK_ATTACHMENT_STORE_OP_STORE) | |
| .clearValue(pClearColor); | |
| VkRenderingAttachmentInfo.Buffer ppAttachmentInfo = VkRenderingAttachmentInfo.calloc(1, stack) | |
| .put(pAttachmentInfo).flip(); | |
| VkOffset2D pOffset = VkOffset2D.calloc(stack) | |
| .set(0, 0); | |
| VkExtent2D pExtent = VkExtent2D.calloc(stack) | |
| .set(vkSwapChainExtent); | |
| VkRect2D pRenderArea = VkRect2D.calloc(stack) | |
| .set(pOffset, pExtent); | |
| VkRenderingInfo pRenderingInfo = VkRenderingInfo.calloc(stack) | |
| .sType$Default() | |
| .renderArea(pRenderArea) | |
| .layerCount(1) | |
| .pColorAttachments(ppAttachmentInfo); | |
| vkCmdBeginRendering(vkCommandBuffer, pRenderingInfo); | |
| vkCmdBindPipeline(vkCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkGraphicsPipeline); | |
| VkViewport pViewport = VkViewport.calloc(stack) | |
| .set(0, 0, vkSwapChainExtent.width(), vkSwapChainExtent.height(), 0, 1); | |
| VkViewport.Buffer ppViewport = VkViewport.calloc(1, stack) | |
| .put(pViewport).flip(); | |
| VkOffset2D pOrigin = VkOffset2D.calloc(stack) | |
| .set(0, 0); | |
| VkRect2D pScissor = VkRect2D.calloc(stack) | |
| .set(pOrigin, vkSwapChainExtent); | |
| VkRect2D.Buffer ppScissor = VkRect2D.calloc(1, stack) | |
| .put(pScissor).flip(); | |
| vkCmdSetViewport(vkCommandBuffer, 0, ppViewport); | |
| vkCmdSetScissor(vkCommandBuffer, 0, ppScissor); | |
| vkCmdDraw(vkCommandBuffer, 3, 1, 0, 0); | |
| vkCmdEndRendering(vkCommandBuffer); | |
| transitionImageLayout(imageIndex, | |
| VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, | |
| VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, | |
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, | |
| 0, | |
| VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, | |
| VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, | |
| vkCommandBuffer); | |
| vkCheck(vkEndCommandBuffer(vkCommandBuffer), "oh shit"); | |
| } | |
| } | |
| private void transitionImageLayout(int imageIndex, | |
| int oldLayout, | |
| int newLayout, | |
| long pSrcAccessMask, | |
| long pDstAccessMask, | |
| long pSrcStageMask, | |
| long pDstStageMask, | |
| VkCommandBuffer pCommandBuffer) { | |
| try (MemoryStack stack = stackPush()) { | |
| VkImageSubresourceRange pSubresourceInfo = VkImageSubresourceRange.calloc(stack) | |
| .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) | |
| .baseMipLevel(0) | |
| .levelCount(1) | |
| .baseArrayLayer(0) | |
| .layerCount(1); | |
| VkImageMemoryBarrier2 pBarrier = VkImageMemoryBarrier2.calloc(stack) | |
| .sType$Default() | |
| .srcAccessMask(pSrcAccessMask) | |
| .dstAccessMask(pDstAccessMask) | |
| .srcStageMask(pSrcStageMask) | |
| .dstStageMask(pDstStageMask) | |
| .oldLayout(oldLayout) | |
| .newLayout(newLayout) | |
| .srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) | |
| .dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) | |
| .image(vkSwapChainImages[imageIndex]); | |
| pBarrier.subresourceRange(pSubresourceInfo); | |
| VkImageMemoryBarrier2.Buffer ppBarrier = VkImageMemoryBarrier2.calloc(1, stack); | |
| ppBarrier.put(pBarrier).flip(); | |
| VkDependencyInfo pDependencyInfo = VkDependencyInfo.calloc(stack) | |
| .sType$Default() | |
| .dependencyFlags(0) | |
| .pImageMemoryBarriers(ppBarrier); | |
| vkCmdPipelineBarrier2(pCommandBuffer, pDependencyInfo); | |
| } | |
| } | |
| private void initSyncObjects() { | |
| try (MemoryStack stack = stackPush()) { | |
| final int count = MAX_FRAMES_IN_FLIGHT; | |
| vkPresentCompleteSemaphores = new long[count]; | |
| vkRenderFinishedSemaphores = new long[count]; | |
| vkDrawFences = new long[count]; | |
| LongBuffer ppSyncObject = stack.callocLong(1); | |
| VkSemaphoreCreateInfo pSemaphoreInfo = VkSemaphoreCreateInfo.calloc(stack) | |
| .sType$Default(); | |
| for (int i = 0; i < count; i++) { | |
| vkCreateSemaphore(vkDevice, pSemaphoreInfo, null, ppSyncObject); | |
| vkPresentCompleteSemaphores[i] = ppSyncObject.get(0); | |
| } | |
| for (int i = 0; i < count; i++) { | |
| vkCreateSemaphore(vkDevice, pSemaphoreInfo, null, ppSyncObject); | |
| vkRenderFinishedSemaphores[i] = ppSyncObject.get(0); | |
| } | |
| VkFenceCreateInfo pFenceInfo = VkFenceCreateInfo.calloc(stack) | |
| .sType$Default() | |
| .flags(VK_FENCE_CREATE_SIGNALED_BIT); | |
| for (int i = 0; i < count; i++) { | |
| vkCreateFence(vkDevice, pFenceInfo, null, ppSyncObject); | |
| vkDrawFences[i] = ppSyncObject.get(0); | |
| } | |
| log(Level.INFO, "created semaphores and fences"); | |
| } | |
| } | |
| private void loop() { | |
| glfwShowWindow(pWindow); | |
| while (!glfwWindowShouldClose(pWindow)) { | |
| glfwPollEvents(); | |
| drawFrame(); | |
| } | |
| vkDeviceWaitIdle(vkDevice); | |
| } | |
| private void drawFrame() { | |
| try (MemoryStack stack = stackPush()) { | |
| final int inFlightIndex = frameIndex % MAX_FRAMES_IN_FLIGHT; | |
| long vkDrawFence = vkDrawFences[inFlightIndex]; | |
| long vkPresentCompleteSemaphore = vkPresentCompleteSemaphores[inFlightIndex]; | |
| long vkRenderFinishedSemaphore = vkRenderFinishedSemaphores[inFlightIndex]; | |
| VkCommandBuffer vkCommandBuffer = vkCommandBuffers[inFlightIndex]; | |
| vkCheck(vkWaitForFences(vkDevice, vkDrawFence, true, Long.MAX_VALUE), | |
| "failed to wait for fence"); | |
| IntBuffer pImageIndex = stack.callocInt(1); | |
| int result = vkAcquireNextImageKHR(vkDevice, vkSwapChain, Long.MAX_VALUE, vkPresentCompleteSemaphore, | |
| NULL, pImageIndex); | |
| if (result == VK_ERROR_OUT_OF_DATE_KHR) { | |
| log(Level.WARN, "vkAcquireNextImageKHR returned VK_ERROR_OUT_OF_DATE_KHR, reinit swapchain"); | |
| recreateSwapChain(); | |
| return; | |
| } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { | |
| vkCheck(result, "failed to acquire swap chain image"); | |
| } | |
| vkResetFences(vkDevice, vkDrawFence); | |
| recordCommandBuffer(inFlightIndex, pImageIndex.get(0)); | |
| VkSubmitInfo pSubmitInfo = VkSubmitInfo.calloc(stack) | |
| .sType$Default() | |
| .waitSemaphoreCount(1) | |
| .pWaitSemaphores(stack.longs(vkPresentCompleteSemaphore)) | |
| .pWaitDstStageMask(stack.ints(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)) | |
| .pSignalSemaphores(stack.longs(vkRenderFinishedSemaphore)) | |
| .pCommandBuffers(stack.pointers(vkCommandBuffer)); | |
| vkCheck(vkQueueSubmit(vkGraphicsQueue, pSubmitInfo, vkDrawFence), "submit to queue failed"); | |
| VkPresentInfoKHR pPresentInfo = VkPresentInfoKHR.calloc(stack) | |
| .sType$Default() | |
| .pWaitSemaphores(stack.longs(vkRenderFinishedSemaphore)) | |
| .pSwapchains(stack.longs(vkSwapChain)) | |
| .swapchainCount(1) | |
| .pImageIndices(pImageIndex) | |
| .pResults(null); | |
| result = vkQueuePresentKHR(vkPresentQueue, pPresentInfo); | |
| if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || frameBufferResized) { | |
| log(Level.WARN, "vkQueuePresentKHR flagged", result, | |
| "frameBufferResized =", frameBufferResized, "; reinit swapchain"); | |
| frameBufferResized = false; | |
| recreateSwapChain(); | |
| } else if (result != VK_SUCCESS) { | |
| vkCheck(result, "failed to acquire swap chain image"); | |
| } | |
| frameIndex++; | |
| } | |
| } | |
| private void recreateSwapChain() { | |
| waitForWindow(); | |
| vkDeviceWaitIdle(vkDevice); | |
| destroySwapchain(); | |
| initSwapchain(); | |
| initSwapchainImages(); | |
| initImageViews(); | |
| } | |
| private void waitForWindow() { | |
| try (MemoryStack stack = stackPush()) { | |
| IntBuffer pWidth = stack.callocInt(1); | |
| IntBuffer pHeight = stack.callocInt(1); | |
| glfwGetFramebufferSize(pWindow, pWidth, pHeight); | |
| while (pWidth.get(0) == 0 || pHeight.get(0) == 0) { | |
| glfwGetFramebufferSize(pWindow, pWidth, pHeight); | |
| glfwWaitEvents(); | |
| } | |
| } | |
| } | |
| private void destroy() { | |
| log(Level.INFO, "Shutting down!"); | |
| destroyVulkan(); | |
| destroyWindow(); | |
| } | |
| private void destroyWindow() { | |
| if (pWindow != NULL) { | |
| glfwDestroyWindow(pWindow); | |
| } | |
| glfwTerminate(); | |
| } | |
| private void destroySwapchain() { | |
| if (vkSwapChainImageViews != null) { | |
| for (long vkSwapChainImageView : vkSwapChainImageViews) { | |
| if (vkSwapChainImageView != NULL) { | |
| vkDestroyImageView(vkDevice, vkSwapChainImageView, null); | |
| } | |
| } | |
| vkSwapChainImageViews = null; | |
| } | |
| if (vkSwapChainExtent != null) { | |
| vkSwapChainExtent.free(); | |
| vkSwapChainExtent = null; | |
| } | |
| if (swapChainSupportDetails != null) { | |
| swapChainSupportDetails.free(); | |
| swapChainSupportDetails = null; | |
| } | |
| if (vkSwapChain != NULL) { | |
| vkDestroySwapchainKHR(vkDevice, vkSwapChain, null); | |
| vkSwapChain = NULL; | |
| } | |
| } | |
| private void destroyVulkan() { | |
| vkDeviceWaitIdle(vkDevice); | |
| if (vkDrawFences != null) { | |
| for (long vkDrawFence : vkDrawFences) { | |
| if (vkDrawFence != NULL) { | |
| vkDestroyFence(vkDevice, vkDrawFence, null); | |
| } | |
| } | |
| } | |
| if (vkPresentCompleteSemaphores != null) { | |
| for (long vkPresentCompleteSemaphore : vkPresentCompleteSemaphores) { | |
| if (vkPresentCompleteSemaphore != NULL) { | |
| vkDestroySemaphore(vkDevice, vkPresentCompleteSemaphore, null); | |
| } | |
| } | |
| } | |
| if (vkRenderFinishedSemaphores != null) { | |
| for (long vkRenderFinishedSemaphore : vkRenderFinishedSemaphores) { | |
| if (vkRenderFinishedSemaphore != NULL) { | |
| vkDestroySemaphore(vkDevice, vkRenderFinishedSemaphore, null); | |
| } | |
| } | |
| } | |
| if (vkCommandBuffers != null) { | |
| for (VkCommandBuffer vkCommandBuffer : vkCommandBuffers) { | |
| if (vkCommandBuffer != null) { | |
| vkFreeCommandBuffers(vkDevice, vkCommandPool, vkCommandBuffer); | |
| } | |
| } | |
| } | |
| if (vkCommandPool != NULL) { | |
| vkDestroyCommandPool(vkDevice, vkCommandPool, null); | |
| } | |
| if (vkGraphicsPipeline != NULL) { | |
| vkDestroyPipeline(vkDevice, vkGraphicsPipeline, null); | |
| } | |
| if (vkPipelineLayout != NULL) { | |
| vkDestroyPipelineLayout(vkDevice, vkPipelineLayout, null); | |
| } | |
| if (vkShaderModule != NULL) { | |
| vkDestroyShaderModule(vkDevice, vkShaderModule, null); | |
| } | |
| destroySwapchain(); | |
| if (vkDevice != null) { | |
| vkDestroyDevice(vkDevice, null); | |
| } | |
| if (vkPhysicalDeviceFeatures != null) { | |
| vkPhysicalDeviceFeatures.free(); | |
| } | |
| if (vkPhysicalDeviceProperties != null) { | |
| vkPhysicalDeviceProperties.free(); | |
| } | |
| if (vkDebugHandle != NULL && vkInstance != null) { | |
| vkDestroyDebugUtilsMessengerEXT(vkInstance, vkDebugHandle, null); | |
| } | |
| if (debugCallback != null) { | |
| debugCallback.free(); | |
| } | |
| if (vkSurface != NULL && vkInstance != null) { | |
| vkDestroySurfaceKHR(vkInstance, vkSurface, null); | |
| } | |
| if (vkInstance != null) { | |
| vkDestroyInstance(vkInstance, null); | |
| } | |
| } | |
| public static void vkCheck(int err, String errMsg) { | |
| if (err != VK_SUCCESS) { | |
| String errCode = switch (err) { | |
| case VK_NOT_READY -> "VK_NOT_READY"; | |
| case VK_TIMEOUT -> "VK_TIMEOUT"; | |
| case VK_EVENT_SET -> "VK_EVENT_SET"; | |
| case VK_EVENT_RESET -> "VK_EVENT_RESET"; | |
| case VK_INCOMPLETE -> "VK_INCOMPLETE"; | |
| case VK_ERROR_OUT_OF_HOST_MEMORY -> "VK_ERROR_OUT_OF_HOST_MEMORY"; | |
| case VK_ERROR_OUT_OF_DEVICE_MEMORY -> "VK_ERROR_OUT_OF_DEVICE_MEMORY"; | |
| case VK_ERROR_INITIALIZATION_FAILED -> "VK_ERROR_INITIALIZATION_FAILED"; | |
| case VK_ERROR_DEVICE_LOST -> "VK_ERROR_DEVICE_LOST"; | |
| case VK_ERROR_MEMORY_MAP_FAILED -> "VK_ERROR_MEMORY_MAP_FAILED"; | |
| case VK_ERROR_LAYER_NOT_PRESENT -> "VK_ERROR_LAYER_NOT_PRESENT"; | |
| case VK_ERROR_EXTENSION_NOT_PRESENT -> "VK_ERROR_EXTENSION_NOT_PRESENT"; | |
| case VK_ERROR_FEATURE_NOT_PRESENT -> "VK_ERROR_FEATURE_NOT_PRESENT"; | |
| case VK_ERROR_INCOMPATIBLE_DRIVER -> "VK_ERROR_INCOMPATIBLE_DRIVER"; | |
| case VK_ERROR_TOO_MANY_OBJECTS -> "VK_ERROR_TOO_MANY_OBJECTS"; | |
| case VK_ERROR_FORMAT_NOT_SUPPORTED -> "VK_ERROR_FORMAT_NOT_SUPPORTED"; | |
| case VK_ERROR_FRAGMENTED_POOL -> "VK_ERROR_FRAGMENTED_POOL"; | |
| case VK_ERROR_UNKNOWN -> "VK_ERROR_UNKNOWN"; | |
| default -> "Not mapped"; | |
| }; | |
| throw new IllegalStateException(errMsg + ": " + errCode + " [" + err + "]"); | |
| } | |
| } | |
| public enum Level { | |
| ERROR, WARN, INFO, DEBUG; | |
| } | |
| private static final Level LOG_LEVEL = Level.DEBUG; | |
| private static final long INIT_TIMESTAMP = System.currentTimeMillis(); | |
| public static void log(Level level, String message) { | |
| if (LOG_LEVEL.ordinal() >= level.ordinal()) { | |
| String finalMessage = "[%.3f] (%s) %s" | |
| .formatted((System.currentTimeMillis() - INIT_TIMESTAMP) / 1e3, | |
| level.name(), | |
| message); | |
| if (level.ordinal() <= Level.WARN.ordinal()) { | |
| System.err.println(finalMessage); | |
| } else { | |
| System.out.println(finalMessage); | |
| } | |
| } | |
| } | |
| private static void log(Level level, Object... objects) { | |
| log(level, String.join(" ", Arrays.stream(objects) | |
| .map(String::valueOf) | |
| .toArray(String[]::new))); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment