Skip to content

Instantly share code, notes, and snippets.

@brickwall2900
Created May 13, 2026 14:32
Show Gist options
  • Select an option

  • Save brickwall2900/a71dc6ce51323f230199d786e05f661b to your computer and use it in GitHub Desktop.

Select an option

Save brickwall2900/a71dc6ce51323f230199d786e05f661b to your computer and use it in GitHub Desktop.
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