diff --git a/res/org/lwjgl/demo/vulkan/khrraytracing/closesthit.glsl b/res/org/lwjgl/demo/vulkan/khrraytracing/closesthit.glsl new file mode 100644 index 00000000..34a2569e --- /dev/null +++ b/res/org/lwjgl/demo/vulkan/khrraytracing/closesthit.glsl @@ -0,0 +1,12 @@ +/* + * Copyright LWJGL. All rights reserved. + * License terms: https://www.lwjgl.org/license + */ +#version 460 +#extension GL_EXT_ray_tracing : enable + +layout(location = 0) rayPayloadInEXT bool payload; + +void main(void) { + payload = true; +} diff --git a/res/org/lwjgl/demo/vulkan/khrraytracing/raygen.glsl b/res/org/lwjgl/demo/vulkan/khrraytracing/raygen.glsl new file mode 100644 index 00000000..0597c923 --- /dev/null +++ b/res/org/lwjgl/demo/vulkan/khrraytracing/raygen.glsl @@ -0,0 +1,39 @@ +/* + * Copyright LWJGL. All rights reserved. + * License terms: https://www.lwjgl.org/license + */ +#version 460 +#extension GL_EXT_ray_tracing : enable + +layout(location = 0) rayPayloadEXT bool payload; +layout(binding = 0, set = 0) uniform accelerationStructureEXT acc; +layout(binding = 1, set = 0, rgba8) uniform image2D image; +layout(binding = 2, set = 0) uniform Camera { + mat4 projInverse; + mat4 viewInverse; +} cam; + +void main(void) { + vec2 px = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + vec2 ndc = (px / vec2(gl_LaunchSizeEXT.xy)) * 2.0 - vec2(1.0); + vec3 origin = cam.viewInverse[3].xyz; + vec4 target = cam.projInverse * vec4(ndc, 0.0, 1.0); + vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0.0); + uint rayFlags = gl_RayFlagsOpaqueEXT | gl_RayFlagsCullBackFacingTrianglesEXT; + float tMin = 0.1; + float tMax = 100.0; + traceRayEXT( + acc, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + imageStore(image, ivec2(gl_LaunchIDEXT), payload ? vec4(1.0) : vec4(0.0)); +} diff --git a/res/org/lwjgl/demo/vulkan/khrraytracing/raymiss.glsl b/res/org/lwjgl/demo/vulkan/khrraytracing/raymiss.glsl new file mode 100644 index 00000000..c7b79f81 --- /dev/null +++ b/res/org/lwjgl/demo/vulkan/khrraytracing/raymiss.glsl @@ -0,0 +1,12 @@ +/* + * Copyright LWJGL. All rights reserved. + * License terms: https://www.lwjgl.org/license + */ +#version 460 +#extension GL_EXT_ray_tracing : enable + +layout(location = 0) rayPayloadInEXT bool payload; + +void main(void) { + payload = false; +} diff --git a/src/org/lwjgl/demo/vulkan/KhrRayTracingTriangleDemo.java b/src/org/lwjgl/demo/vulkan/KhrRayTracingTriangleDemo.java new file mode 100644 index 00000000..d3cfcd04 --- /dev/null +++ b/src/org/lwjgl/demo/vulkan/KhrRayTracingTriangleDemo.java @@ -0,0 +1,1615 @@ +/* + * Copyright LWJGL. All rights reserved. + * License terms: https://www.lwjgl.org/license + */ +package org.lwjgl.demo.vulkan; + +import static java.lang.Math.*; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.range; +import static org.lwjgl.demo.vulkan.VKUtil.*; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.glfw.GLFWVulkan.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.util.vma.Vma.*; +import static org.lwjgl.vulkan.EXTDebugUtils.*; +import static org.lwjgl.vulkan.EXTDescriptorIndexing.*; +import static org.lwjgl.vulkan.KHRAccelerationStructure.*; +import static org.lwjgl.vulkan.KHRBufferDeviceAddress.*; +import static org.lwjgl.vulkan.KHRDeferredHostOperations.VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRRayTracingPipeline.*; +import static org.lwjgl.vulkan.KHRShaderFloatControls.VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRSpirv14.VK_KHR_SPIRV_1_4_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRSurface.*; +import static org.lwjgl.vulkan.KHRSwapchain.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK11.*; + +import java.io.IOException; +import java.nio.*; +import java.util.*; + +import org.joml.*; +import org.lwjgl.PointerBuffer; +import org.lwjgl.demo.util.DynamicByteBuffer; +import org.lwjgl.glfw.GLFWKeyCallback; +import org.lwjgl.system.*; +import org.lwjgl.util.vma.*; +import org.lwjgl.vulkan.*; + +/** + * VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure demo that draws a triangle. + * + * @author Kai Burjack + */ +public class KhrRayTracingTriangleDemo { + + private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("debug", "false")); + static { + if (DEBUG) { + /* When we are in debug mode, enable all LWJGL debug flags */ + System.setProperty("org.lwjgl.util.Debug", "true"); + System.setProperty("org.lwjgl.util.NoChecks", "false"); + System.setProperty("org.lwjgl.util.DebugLoader", "true"); + System.setProperty("org.lwjgl.util.DebugAllocator", "true"); + System.setProperty("org.lwjgl.util.DebugStack", "true"); + } + } + + private static WindowAndCallbacks windowAndCallbacks; + private static VkInstance instance; + private static long surface; + private static DebugCallbackAndHandle debugCallbackHandle; + private static DeviceAndQueueFamilies deviceAndQueueFamilies; + private static int queueFamily; + private static VkDevice device; + private static long vmaAllocator; + private static VkQueue queue; + private static Swapchain swapchain; + private static long commandPool, commandPoolTransient; + private static VkCommandBuffer[] commandBuffers; + private static long[] imageAcquireSemaphores; + private static long[] renderCompleteSemaphores; + private static long[] renderFences; + private static final Map waitingFenceActions = new HashMap<>(); + private static AccelerationStructure blas, tlas; + private static RayTracingPipeline rayTracingPipeline; + private static AllocationAndBuffer[] rayTracingUbos; + private static AllocationAndBuffer rayTracingShaderBindingTable; + private static DescriptorSets rayTracingDescriptorSets; + private static final Matrix4f projMatrix = new Matrix4f(); + private static final Matrix4f viewMatrix = new Matrix4f(); + private static final Matrix4f invProjMatrix = new Matrix4f(); + private static final Matrix4f invViewMatrix = new Matrix4f(); + + private static class WindowAndCallbacks { + private final long window; + private final GLFWKeyCallback keyCallback; + private int width; + private int height; + private WindowAndCallbacks(long window, int width, int height, GLFWKeyCallback keyCallback) { + this.window = window; + this.width = width; + this.height = height; + this.keyCallback = keyCallback; + } + private void free() { + glfwDestroyWindow(window); + keyCallback.free(); + } + } + + private static class DebugCallbackAndHandle { + private final long messengerHandle; + private final VkDebugUtilsMessengerCallbackEXT callback; + private DebugCallbackAndHandle(long handle, VkDebugUtilsMessengerCallbackEXT callback) { + this.messengerHandle = handle; + this.callback = callback; + } + private void free() { + vkDestroyDebugUtilsMessengerEXT(instance, messengerHandle, null); + callback.free(); + } + } + + private static class QueueFamilies { + private final List computeFamilies = new ArrayList<>(); + private final List presentFamilies = new ArrayList<>(); + private int findSingleSuitableQueue() { + return computeFamilies + .stream() + .filter(presentFamilies::contains) + .findAny() + .orElseThrow(() -> new AssertionError("No suitable queue found")); + } + } + + private static class DeviceAndQueueFamilies { + private final VkPhysicalDevice physicalDevice; + private final QueueFamilies queuesFamilies; + private final int shaderGroupHandleSize; + private final int shaderGroupBaseAlignment; + private final int minAccelerationStructureScratchOffsetAlignment; + private DeviceAndQueueFamilies(VkPhysicalDevice physicalDevice, QueueFamilies queuesFamilies, + int shaderGroupHandleSize, + int shaderGroupBaseAlignment, + int minAccelerationStructureScratchOffsetAlignment) { + this.physicalDevice = physicalDevice; + this.queuesFamilies = queuesFamilies; + this.shaderGroupHandleSize = shaderGroupHandleSize; + this.shaderGroupBaseAlignment = shaderGroupBaseAlignment; + this.minAccelerationStructureScratchOffsetAlignment = minAccelerationStructureScratchOffsetAlignment; + } + } + + private static class ColorFormatAndSpace { + private final int colorFormat; + private final int colorSpace; + private ColorFormatAndSpace(int colorFormat, int colorSpace) { + this.colorFormat = colorFormat; + this.colorSpace = colorSpace; + } + } + + private static class Swapchain { + private final long swapchain; + private final long[] images; + private final long[] imageViews; + private final int width, height; + private Swapchain(long swapchain, long[] images, long[] imageViews, int width, int height) { + this.swapchain = swapchain; + this.images = images; + this.imageViews = imageViews; + this.width = width; + this.height = height; + } + private void free() { + vkDestroySwapchainKHR(device, swapchain, null); + for (long imageView : imageViews) + vkDestroyImageView(device, imageView, null); + } + } + + private static class RayTracingPipeline { + private final long pipelineLayout; + private final long descriptorSetLayout; + private final long pipeline; + private RayTracingPipeline(long pipelineLayout, long descriptorSetLayout, long pipeline) { + this.pipelineLayout = pipelineLayout; + this.descriptorSetLayout = descriptorSetLayout; + this.pipeline = pipeline; + } + private void free() { + vkDestroyPipelineLayout(device, pipelineLayout, null); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, null); + vkDestroyPipeline(device, pipeline, null); + } + } + + private static class DescriptorSets { + private final long descriptorPool; + private final long[] sets; + private DescriptorSets(long descriptorPool, long[] sets) { + this.descriptorPool = descriptorPool; + this.sets = sets; + } + private void free() { + vkDestroyDescriptorPool(device, descriptorPool, null); + } + } + + private static PointerBuffer pointers(MemoryStack stack, PointerBuffer pts, ByteBuffer... pointers) { + PointerBuffer res = stack.mallocPointer(pts.remaining() + pointers.length); + res.put(pts); + for (ByteBuffer pointer : pointers) { + res.put(pointer); + } + res.flip(); + return res; + } + + private static List enumerateSupportedInstanceExtensions() { + try (MemoryStack stack = stackPush()) { + IntBuffer pPropertyCount = stack.mallocInt(1); + _CHECK_(vkEnumerateInstanceExtensionProperties((ByteBuffer) null, pPropertyCount, null), + "Could not enumerate number of instance extensions"); + int propertyCount = pPropertyCount.get(0); + VkExtensionProperties.Buffer pProperties = VkExtensionProperties.mallocStack(propertyCount, stack); + _CHECK_(vkEnumerateInstanceExtensionProperties((ByteBuffer) null, pPropertyCount, pProperties), + "Could not enumerate instance extensions"); + List res = new ArrayList<>(propertyCount); + for (int i = 0; i < propertyCount; i++) { + res.add(pProperties.get(i).extensionNameString()); + } + return res; + } + } + + private static VkInstance createInstance(PointerBuffer requiredExtensions) { + List supportedInstanceExtensions = enumerateSupportedInstanceExtensions(); + try (MemoryStack stack = stackPush()) { + PointerBuffer ppEnabledExtensionNames; + if (DEBUG) { + if (!supportedInstanceExtensions.contains(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { + throw new AssertionError(VK_EXT_DEBUG_UTILS_EXTENSION_NAME + " is not supported on the instance"); + } + ppEnabledExtensionNames = pointers(stack, requiredExtensions, stack.UTF8(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)); + } else { + ppEnabledExtensionNames = pointers(stack, requiredExtensions); + } + PointerBuffer enabledLayers = null; + if (DEBUG) { + enabledLayers = stack.pointers(stack.UTF8("VK_LAYER_KHRONOS_validation")); + } + VkInstanceCreateInfo pCreateInfo = VkInstanceCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO) + .pApplicationInfo(VkApplicationInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_APPLICATION_INFO) + .apiVersion(VK_API_VERSION_1_1)) + .ppEnabledExtensionNames(ppEnabledExtensionNames) + .ppEnabledLayerNames(enabledLayers); + PointerBuffer pInstance = stack.mallocPointer(1); + _CHECK_(vkCreateInstance(pCreateInfo, null, pInstance), "Failed to create VkInstance"); + return new VkInstance(pInstance.get(0), pCreateInfo); + } + } + + private static PointerBuffer initGlfwAndReturnRequiredExtensions() { + if (!glfwInit()) + throw new AssertionError("Failed to initialize GLFW"); + if (!glfwVulkanSupported()) + throw new AssertionError("GLFW failed to find the Vulkan loader"); + PointerBuffer requiredExtensions = glfwGetRequiredInstanceExtensions(); + if (requiredExtensions == null) + throw new AssertionError("Failed to find list of required Vulkan extensions"); + return requiredExtensions; + } + + private static WindowAndCallbacks createWindow() { + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + long window = glfwCreateWindow(1200, 800, "Hello, ray traced triangle!", NULL, NULL); + GLFWKeyCallback keyCallback = new GLFWKeyCallback() { + public void invoke(long window, int key, int scancode, int action, int mods) { + if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) + glfwSetWindowShouldClose(window, true); + } + }; + int w, h; + try (MemoryStack stack = stackPush()) { + long addr = stack.nmalloc(2 * Integer.BYTES); + nglfwGetFramebufferSize(window, addr, addr + Integer.BYTES); + w = memGetInt(addr); + h = memGetInt(addr + Integer.BYTES); + } + glfwSetKeyCallback(window, keyCallback); + return new WindowAndCallbacks(window, w, h, keyCallback); + } + + private static long createSurface() { + try (MemoryStack stack = stackPush()) { + LongBuffer surface = stack.mallocLong(1); + _CHECK_(glfwCreateWindowSurface(instance, windowAndCallbacks.window, null, surface), "Failed to create surface"); + return surface.get(0); + } + } + + private static DebugCallbackAndHandle setupDebugging() { + if (!DEBUG) { + return null; + } + VkDebugUtilsMessengerCallbackEXT callback = new VkDebugUtilsMessengerCallbackEXT() { + public int invoke(int messageSeverity, int messageTypes, long pCallbackData, long pUserData) { + VkDebugUtilsMessengerCallbackDataEXT message = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData); + System.err.println(message.pMessageString()); + return 0; + } + }; + try (MemoryStack stack = stackPush()) { + LongBuffer pMessenger = stack.mallocLong(1); + _CHECK_(vkCreateDebugUtilsMessengerEXT(instance, + VkDebugUtilsMessengerCreateInfoEXT + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT) + .messageSeverity(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + .messageType(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + .pfnUserCallback(callback), + null, pMessenger), + "Failed to create debug messenger"); + return new DebugCallbackAndHandle(pMessenger.get(0), callback); + } + } + + private static boolean familySupports(VkQueueFamilyProperties prop, int bit) { + return (prop.queueFlags() & bit) != 0; + } + + private static QueueFamilies obtainQueueFamilies(VkPhysicalDevice physicalDevice) { + QueueFamilies ret = new QueueFamilies(); + try (MemoryStack stack = stackPush()) { + IntBuffer pQueueFamilyPropertyCount = stack.mallocInt(1); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, null); + int numQueueFamilies = pQueueFamilyPropertyCount.get(0); + if (numQueueFamilies == 0) + throw new AssertionError("No queue families found"); + VkQueueFamilyProperties.Buffer familyProperties = VkQueueFamilyProperties.mallocStack(numQueueFamilies, stack); + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyPropertyCount, familyProperties); + int queueFamilyIndex = 0; + IntBuffer pSupported = stack.mallocInt(1); + for (VkQueueFamilyProperties queueFamilyProps : familyProperties) { + if (queueFamilyProps.queueCount() < 1) { + continue; + } + vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, surface, pSupported); + // we need compute for acceleration structure build and ray tracing commands. + // we will also use this for vkCmdCopyBuffer + if (familySupports(queueFamilyProps, VK_QUEUE_COMPUTE_BIT)) + ret.computeFamilies.add(queueFamilyIndex); + // we also need present + if (pSupported.get(0) != 0) + ret.presentFamilies.add(queueFamilyIndex); + queueFamilyIndex++; + } + return ret; + } + } + + private static boolean isDeviceSuitable(QueueFamilies queuesFamilies) { + return !queuesFamilies.computeFamilies.isEmpty() && !queuesFamilies.presentFamilies.isEmpty(); + } + + private static DeviceAndQueueFamilies selectPhysicalDevice() { + try (MemoryStack stack = stackPush()) { + // Retrieve number of available physical devices + IntBuffer pPhysicalDeviceCount = stack.mallocInt(1); + _CHECK_(vkEnumeratePhysicalDevices(instance, pPhysicalDeviceCount, null), + "Failed to get number of physical devices"); + int physicalDeviceCount = pPhysicalDeviceCount.get(0); + if (physicalDeviceCount == 0) + throw new AssertionError("No physical devices available"); + + // Retrieve pointers to all available physical devices + PointerBuffer pPhysicalDevices = stack.mallocPointer(physicalDeviceCount); + _CHECK_(vkEnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices), + "Failed to get physical devices"); + + // and enumerate them to see which one we will use... + for (int i = 0; i < physicalDeviceCount; i++) { + VkPhysicalDevice dev = new VkPhysicalDevice(pPhysicalDevices.get(i), instance); + // Check if the device supports all needed features + VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures = VkPhysicalDeviceAccelerationStructureFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR); + VkPhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingPipelineFeaturesKHR = VkPhysicalDeviceRayTracingPipelineFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR) + .pNext(accelerationStructureFeatures.address()); + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR bufferDeviceAddressFeatures = VkPhysicalDeviceBufferDeviceAddressFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR) + .pNext(rayTracingPipelineFeaturesKHR.address()); + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2 = VkPhysicalDeviceFeatures2 + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) + .pNext(bufferDeviceAddressFeatures.address()); + vkGetPhysicalDeviceFeatures2(dev, physicalDeviceFeatures2); + + // If any of the above is not supported, we continue with the next physical device + if (!bufferDeviceAddressFeatures.bufferDeviceAddress() || + !accelerationStructureFeatures.accelerationStructure() || + !bufferDeviceAddressFeatures.bufferDeviceAddress()) + continue; + + // Retrieve physical device properties (limits, offsets, alignments, ...) + VkPhysicalDeviceAccelerationStructurePropertiesKHR accelerationStructureProperties = VkPhysicalDeviceAccelerationStructurePropertiesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR); + VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayTracingProperties = VkPhysicalDeviceRayTracingPipelinePropertiesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR) + .pNext(accelerationStructureProperties.address()); + VkPhysicalDeviceProperties2 props = VkPhysicalDeviceProperties2 + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2) + .pNext(rayTracingProperties.address()); + vkGetPhysicalDeviceProperties2(dev, props); + + // Check queue families + QueueFamilies queuesFamilies = obtainQueueFamilies(dev); + if (isDeviceSuitable(queuesFamilies)) { + return new DeviceAndQueueFamilies(dev, queuesFamilies, + rayTracingProperties.shaderGroupHandleSize(), + rayTracingProperties.shaderGroupBaseAlignment(), + accelerationStructureProperties.minAccelerationStructureScratchOffsetAlignment()); + } + } + throw new AssertionError("No suitable physical device found"); + } + } + + private static VkDevice createDevice(List requiredExtensions) { + List supportedDeviceExtensions = enumerateSupportedDeviceExtensions(); + for (String requiredExtension : requiredExtensions) { + if (!supportedDeviceExtensions.contains(requiredExtension)) + throw new AssertionError(requiredExtension + " device extension is not supported"); + } + try (MemoryStack stack = stackPush()) { + if (!supportedDeviceExtensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { + throw new AssertionError(VK_KHR_SWAPCHAIN_EXTENSION_NAME + " device extension is not supported"); + } + PointerBuffer extensions = stack.mallocPointer(1 + requiredExtensions.size()); + extensions.put(stack.UTF8(VK_KHR_SWAPCHAIN_EXTENSION_NAME)); + for (String requiredExtension : requiredExtensions) + extensions.put(stack.UTF8(requiredExtension)); + extensions.flip(); + PointerBuffer ppEnabledLayerNames = null; + if (DEBUG) { + ppEnabledLayerNames = stack.pointers(stack.UTF8("VK_LAYER_KHRONOS_validation")); + } + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR bufferDeviceAddressFeatures = VkPhysicalDeviceBufferDeviceAddressFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR) + .bufferDeviceAddress(true); + VkPhysicalDeviceDescriptorIndexingFeaturesEXT indexingFeatures = VkPhysicalDeviceDescriptorIndexingFeaturesEXT + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT) + .pNext(bufferDeviceAddressFeatures.address()) + .runtimeDescriptorArray(true); + VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures = VkPhysicalDeviceAccelerationStructureFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR) + .pNext(indexingFeatures.address()) + .accelerationStructure(true); + VkPhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingFeatures = VkPhysicalDeviceRayTracingPipelineFeaturesKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR) + .pNext(accelerationStructureFeatures.address()) + .rayTracingPipeline(true); + VkDeviceCreateInfo pCreateInfo = VkDeviceCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO) + .pNext(rayTracingFeatures.address()) + .pQueueCreateInfos(VkDeviceQueueCreateInfo + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO) + .queueFamilyIndex(queueFamily) + .pQueuePriorities(stack.floats(1.0f))) + .ppEnabledExtensionNames(extensions) + .ppEnabledLayerNames(ppEnabledLayerNames); + PointerBuffer pDevice = stack.mallocPointer(1); + _CHECK_(vkCreateDevice(deviceAndQueueFamilies.physicalDevice, pCreateInfo, null, pDevice), + "Failed to create device"); + return new VkDevice(pDevice.get(0), deviceAndQueueFamilies.physicalDevice, pCreateInfo, VK_API_VERSION_1_1); + } + } + + private static List enumerateSupportedDeviceExtensions() { + try (MemoryStack stack = stackPush()) { + IntBuffer pPropertyCount = stack.mallocInt(1); + _CHECK_(vkEnumerateDeviceExtensionProperties(deviceAndQueueFamilies.physicalDevice, (ByteBuffer) null, pPropertyCount, null), + "Failed to get number of device extensions"); + int propertyCount = pPropertyCount.get(0); + VkExtensionProperties.Buffer pProperties = VkExtensionProperties.mallocStack(propertyCount, stack); + _CHECK_(vkEnumerateDeviceExtensionProperties(deviceAndQueueFamilies.physicalDevice, (ByteBuffer) null, pPropertyCount, pProperties), + "Failed to enumerate the device extensions"); + return range(0, propertyCount).mapToObj(i -> pProperties.get(i).extensionNameString()).collect(toList()); + } + } + + private static long createVmaAllocator() { + try (MemoryStack stack = stackPush()) { + PointerBuffer pAllocator = stack.mallocPointer(1); + _CHECK_(vmaCreateAllocator(VmaAllocatorCreateInfo + .callocStack(stack) + .instance(instance) + .flags(VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT) + .device(device) + .physicalDevice(deviceAndQueueFamilies.physicalDevice) + .pVulkanFunctions(VmaVulkanFunctions + .callocStack(stack) + .set(instance, device)), pAllocator), + "Failed to create VMA allocator"); + return pAllocator.get(0); + } + } + + private static VkQueue retrieveQueue() { + try (MemoryStack stack = stackPush()) { + PointerBuffer pQueue = stack.mallocPointer(1); + vkGetDeviceQueue(device, queueFamily, 0, pQueue); + return new VkQueue(pQueue.get(0), device); + } + } + + private static ColorFormatAndSpace determineSurfaceFormat(VkPhysicalDevice physicalDevice, long surface) { + try (MemoryStack stack = stackPush()) { + IntBuffer pSurfaceFormatCount = stack.mallocInt(1); + _CHECK_(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, pSurfaceFormatCount, null), + "Failed to get number of device surface formats"); + VkSurfaceFormatKHR.Buffer pSurfaceFormats = VkSurfaceFormatKHR + .callocStack(pSurfaceFormatCount.get(0), stack); + _CHECK_(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, pSurfaceFormatCount, pSurfaceFormats), + "Failed to get device surface formats"); + for (VkSurfaceFormatKHR surfaceFormat : pSurfaceFormats) { + if (surfaceFormat.format() == VK_FORMAT_B8G8R8A8_UNORM) { + return new ColorFormatAndSpace(surfaceFormat.format(), surfaceFormat.colorSpace()); + } + } + return new ColorFormatAndSpace(pSurfaceFormats.get(0).format(), pSurfaceFormats.get(0).colorSpace()); + } + } + + private static Vector2i determineSwapchainExtents(VkSurfaceCapabilitiesKHR surfCaps) { + VkExtent2D extent = surfCaps.currentExtent(); + Vector2i ret = new Vector2i(extent.width(), extent.height()); + if (extent.width() == -1) { + ret.set(max(min(1280, surfCaps.maxImageExtent().width()), surfCaps.minImageExtent().width()), + max(min(720, surfCaps.maxImageExtent().height()), surfCaps.minImageExtent().height())); + } + return ret; + } + + private static Swapchain createSwapchain() { + try (MemoryStack stack = stackPush()) { + VkSurfaceCapabilitiesKHR pSurfaceCapabilities = VkSurfaceCapabilitiesKHR + .callocStack(stack); + _CHECK_(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(deviceAndQueueFamilies.physicalDevice, surface, pSurfaceCapabilities), + "Failed to get physical device surface capabilities"); + IntBuffer pPresentModeCount = stack.mallocInt(1); + _CHECK_(vkGetPhysicalDeviceSurfacePresentModesKHR(deviceAndQueueFamilies.physicalDevice, surface, pPresentModeCount, null), + "Failed to get presentation modes count"); + int presentModeCount = pPresentModeCount.get(0); + IntBuffer pPresentModes = stack.mallocInt(presentModeCount); + _CHECK_(vkGetPhysicalDeviceSurfacePresentModesKHR(deviceAndQueueFamilies.physicalDevice, surface, pPresentModeCount, pPresentModes), + "Failed to get presentation modes"); + int imageCount = min(max(pSurfaceCapabilities.minImageCount() + 1, 3), pSurfaceCapabilities.maxImageCount()); + ColorFormatAndSpace surfaceFormat = determineSurfaceFormat(deviceAndQueueFamilies.physicalDevice, surface); + Vector2i swapchainExtents = determineSwapchainExtents(pSurfaceCapabilities); + VkSwapchainCreateInfoKHR pCreateInfo = VkSwapchainCreateInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR) + .surface(surface) + .minImageCount(imageCount) + .imageExtent(e -> e.width(swapchainExtents.x).height(swapchainExtents.y)) + .imageFormat(surfaceFormat.colorFormat) + .imageColorSpace(surfaceFormat.colorSpace) + .imageUsage(VK_IMAGE_USAGE_STORAGE_BIT) // <- for writing to in the raygen shader + .preTransform(pSurfaceCapabilities.currentTransform()) + .imageArrayLayers(1) + .imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) + .presentMode(VK_PRESENT_MODE_FIFO_KHR) + .oldSwapchain(swapchain != null ? swapchain.swapchain : VK_NULL_HANDLE) + .clipped(true) + .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + LongBuffer pSwapchain = stack.mallocLong(Long.BYTES); + _CHECK_(vkCreateSwapchainKHR(device, pCreateInfo, null, pSwapchain), + "Failed to create swap chain"); + if (swapchain != null) { + swapchain.free(); + } + long swapchain = pSwapchain.get(0); + IntBuffer pSwapchainImageCount = stack.mallocInt(1); + _CHECK_(vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, null), + "Failed to get swapchain images count"); + int actualImageCount = pSwapchainImageCount.get(0); + LongBuffer pSwapchainImages = stack.mallocLong(actualImageCount); + _CHECK_(vkGetSwapchainImagesKHR(device, swapchain, pSwapchainImageCount, pSwapchainImages), + "Failed to get swapchain images"); + long[] images = new long[actualImageCount]; + pSwapchainImages.get(images, 0, images.length); + long[] imageViews = new long[actualImageCount]; + LongBuffer pImageView = stack.mallocLong(1); + for (int i = 0; i < actualImageCount; i++) { + _CHECK_(vkCreateImageView(device, + VkImageViewCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO) + .format(surfaceFormat.colorFormat) + .viewType(VK_IMAGE_TYPE_2D) + .subresourceRange(r -> r + .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) + .layerCount(1) + .levelCount(1)) + .image(pSwapchainImages.get(i)), + null, pImageView), + "Failed to create image view"); + imageViews[i] = pImageView.get(0); + } + return new Swapchain(swapchain, images, imageViews, swapchainExtents.x, swapchainExtents.y); + } + } + + private static long createCommandPool(int flags) { + try (MemoryStack stack = stackPush()) { + LongBuffer pCmdPool = stack.mallocLong(1); + _CHECK_(vkCreateCommandPool(device, VkCommandPoolCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO) + .queueFamilyIndex(queueFamily) + .flags(flags), null, pCmdPool), + "Failed to create command pool"); + return pCmdPool.get(0); + } + } + + private static VkCommandBuffer createCommandBuffer(long pool) { + try (MemoryStack stack = stackPush()) { + PointerBuffer pCommandBuffer = stack.mallocPointer(1); + _CHECK_(vkAllocateCommandBuffers(device, + VkCommandBufferAllocateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO) + .commandBufferCount(1) + .commandPool(pool) + .level(VK_COMMAND_BUFFER_LEVEL_PRIMARY), pCommandBuffer), + "Failed to create command buffer"); + VkCommandBuffer cmdBuffer = new VkCommandBuffer(pCommandBuffer.get(0), device); + _CHECK_(vkBeginCommandBuffer(cmdBuffer, VkCommandBufferBeginInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO)), + "Failed to begin command buffer"); + return cmdBuffer; + } + } + + private static void createSyncObjects() { + imageAcquireSemaphores = new long[swapchain.imageViews.length]; + renderCompleteSemaphores = new long[swapchain.imageViews.length]; + renderFences = new long[swapchain.imageViews.length]; + for (int i = 0; i < swapchain.imageViews.length; i++) { + try (MemoryStack stack = stackPush()) { + LongBuffer pSemaphore = stack.mallocLong(1); + VkSemaphoreCreateInfo pCreateInfo = VkSemaphoreCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO); + _CHECK_(vkCreateSemaphore(device, pCreateInfo, null, pSemaphore), + "Failed to create image acquire semaphore"); + imageAcquireSemaphores[i] = pSemaphore.get(0); + _CHECK_(vkCreateSemaphore(device, pCreateInfo, null, pSemaphore), + "Failed to create render complete semaphore"); + renderCompleteSemaphores[i] = pSemaphore.get(0); + LongBuffer pFence = stack.mallocLong(1); + _CHECK_(vkCreateFence(device, VkFenceCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_FENCE_CREATE_INFO) + .flags(VK_FENCE_CREATE_SIGNALED_BIT), null, pFence), + "Failed to create fence"); + renderFences[i] = pFence.get(0); + } + } + } + + private static void updateFramebufferSize() { + try (MemoryStack stack = stackPush()) { + long mem = stack.nmalloc(2 * Integer.BYTES); + nglfwGetFramebufferSize(windowAndCallbacks.window, mem, mem + Integer.BYTES); + windowAndCallbacks.width = memGetInt(mem); + windowAndCallbacks.height = memGetInt(mem + Integer.BYTES); + } + } + + private static boolean isWindowRenderable() { + return windowAndCallbacks.width > 0 && windowAndCallbacks.height > 0; + } + + private static boolean windowSizeChanged() { + return windowAndCallbacks.width != swapchain.width || windowAndCallbacks.height != swapchain.height; + } + + private static void recreateSwapchainAndDependentResources() { + swapchain = createSwapchain(); + rayTracingDescriptorSets = createRayTracingDescriptorSets(); + commandBuffers = createRayTracingCommandBuffers(); + } + + private static void processFinishedFences() { + Iterator> it = waitingFenceActions.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + if (vkGetFenceStatus(device, e.getKey()) == VK_SUCCESS) { + it.remove(); + vkDestroyFence(device, e.getKey(), null); + e.getValue().run(); + } + } + } + + private static boolean submitAndPresent(int imageIndex, int idx) { + try (MemoryStack stack = stackPush()) { + _CHECK_(vkQueueSubmit(queue, VkSubmitInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO) + .pCommandBuffers(stack.pointers(commandBuffers[idx])) + .pWaitSemaphores(stack.longs(imageAcquireSemaphores[idx])) + .waitSemaphoreCount(1) + .pWaitDstStageMask(stack.ints(VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR)) + .pSignalSemaphores(stack.longs(renderCompleteSemaphores[idx])), + renderFences[idx]), + "Failed to submit command buffer"); + int result = vkQueuePresentKHR(queue, VkPresentInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PRESENT_INFO_KHR) + .pWaitSemaphores(stack.longs(renderCompleteSemaphores[idx])) + .swapchainCount(1) + .pSwapchains(stack.longs(swapchain.swapchain)) + .pImageIndices(stack.ints(imageIndex))); + return result != VK_ERROR_OUT_OF_DATE_KHR && result != VK_SUBOPTIMAL_KHR; + } + } + + private static boolean acquireSwapchainImage(IntBuffer pImageIndex, int idx) { + int res = vkAcquireNextImageKHR(device, swapchain.swapchain, -1L, imageAcquireSemaphores[idx], VK_NULL_HANDLE, pImageIndex); + return res != VK_ERROR_OUT_OF_DATE_KHR; + } + + private static void runWndProcLoop() { + glfwShowWindow(windowAndCallbacks.window); + while (!glfwWindowShouldClose(windowAndCallbacks.window)) { + glfwWaitEvents(); + } + } + + private static class AllocationAndBuffer { + private final long allocation; + private final long buffer; + private long mapped; + private AllocationAndBuffer(long allocation, long buffer) { + this.allocation = allocation; + this.buffer = buffer; + } + private void free() { + if (mapped != NULL) { + vmaUnmapMemory(vmaAllocator, allocation); + mapped = NULL; + } + vmaDestroyBuffer(vmaAllocator, buffer, allocation); + } + private void map() { + if (mapped != NULL) { + return; + } + try (MemoryStack stack = stackPush()) { + PointerBuffer pb = stack.mallocPointer(1); + _CHECK_(vmaMapMemory(vmaAllocator, allocation, pb), "Failed to map allocation"); + mapped = pb.get(0); + } + } + private void flushMapped(long offset, long size) { + vmaFlushAllocation(vmaAllocator, allocation, offset, size); + } + } + + private static class Geometry { + private final AllocationAndBuffer positions; + private final AllocationAndBuffer indices; + private final int numPrimities; + private Geometry(AllocationAndBuffer positions, AllocationAndBuffer indices, int numPrimities) { + this.positions = positions; + this.indices = indices; + this.numPrimities = numPrimities; + } + private void free() { + positions.free(); + indices.free(); + } + } + + private static long submitCommandBuffer(VkCommandBuffer commandBuffer, boolean endCommandBuffer, Runnable afterComplete) { + if (endCommandBuffer) + _CHECK_(vkEndCommandBuffer(commandBuffer), + "Failed to end command buffer"); + try (MemoryStack stack = stackPush()) { + LongBuffer pFence = stack.mallocLong(1); + _CHECK_(vkCreateFence(device, VkFenceCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_FENCE_CREATE_INFO), null, pFence), + "Failed to create fence"); + _CHECK_(vkQueueSubmit(queue, VkSubmitInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO) + .pCommandBuffers(stack.pointers(commandBuffer)), pFence.get(0)), + "Failed to submit command buffer"); + long fence = pFence.get(0); + if (afterComplete != null) + waitingFenceActions.put(fence, afterComplete); + return fence; + } + } + + private static AllocationAndBuffer createBuffer(int usageFlags, long size, ByteBuffer data, long alignment) { + try (MemoryStack stack = stackPush()) { + // create the final destination buffer + LongBuffer pBuffer = stack.mallocLong(1); + PointerBuffer pAllocation = stack.mallocPointer(1); + _CHECK_(vmaCreateBuffer(vmaAllocator, + VkBufferCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO) + .size(size) + .usage(usageFlags | (data != null ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : 0)), + VmaAllocationCreateInfo + .callocStack(stack) + .flags(usageFlags) + .usage(VMA_MEMORY_USAGE_GPU_ONLY), pBuffer, pAllocation, null), + "Failed to allocate buffer"); + + // validate alignment + VmaAllocationInfo ai = VmaAllocationInfo.create(pAllocation.get(0)); + if ((ai.offset() % alignment) != 0) + throw new AssertionError("Illegal offset alignment"); + + // if we have data to upload, use a staging buffer + if (data != null) { + // create the staging buffer + LongBuffer pBufferStage = stack.mallocLong(1); + PointerBuffer pAllocationStage = stack.mallocPointer(1); + _CHECK_(vmaCreateBuffer(vmaAllocator, VkBufferCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO) + .size(data.remaining()) + .usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT), + VmaAllocationCreateInfo + .callocStack(stack) + .usage(VMA_MEMORY_USAGE_CPU_ONLY), pBufferStage, pAllocationStage, null), + "Failed to allocate stage buffer"); + + // map the memory and memcpy into it + PointerBuffer pData = stack.mallocPointer(1); + _CHECK_(vmaMapMemory(vmaAllocator, pAllocationStage.get(0), pData), "Failed to map memory"); + memCopy(memAddress(data), pData.get(0), data.remaining()); + vmaUnmapMemory(vmaAllocator, pAllocationStage.get(0)); + + // issue copy buffer command + VkCommandBuffer cmdBuffer = createCommandBuffer(commandPoolTransient); + vkCmdCopyBuffer(cmdBuffer, pBufferStage.get(0), pBuffer.get(0), VkBufferCopy + .callocStack(1, stack) + .size(data.remaining())); + long bufferStage = pBufferStage.get(0); + long allocationStage = pAllocationStage.get(0); + + // and submit that, with a callback to destroy the staging buffer once copying is complete + submitCommandBuffer(cmdBuffer, true, () -> { + vkFreeCommandBuffers(device, commandPoolTransient, cmdBuffer); + vmaDestroyBuffer(vmaAllocator, bufferStage, allocationStage); + }); + } + return new AllocationAndBuffer(pAllocation.get(0), pBuffer.get(0)); + } + } + private static AllocationAndBuffer createBuffer(int usageFlags, ByteBuffer data, long alignment) { + return createBuffer(usageFlags, data.remaining(), data, alignment); + } + + private static AllocationAndBuffer[] createUniformBufferObjects(int size) { + AllocationAndBuffer[] ret = new AllocationAndBuffer[swapchain.imageViews.length]; + for (int i = 0; i < ret.length; i++) { + try (MemoryStack stack = stackPush()) { + LongBuffer pBuffer = stack.mallocLong(1); + PointerBuffer pAllocation = stack.mallocPointer(1); + _CHECK_(vmaCreateBuffer(vmaAllocator, VkBufferCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO) + .size(size) + .usage(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT), + VmaAllocationCreateInfo + .callocStack(stack) + .usage(VMA_MEMORY_USAGE_CPU_TO_GPU), pBuffer, pAllocation, null), + "Failed to allocate buffer"); + AllocationAndBuffer a = new AllocationAndBuffer(pAllocation.get(0), pBuffer.get(0)); + a.map(); + ret[i] = a; + } + } + return ret; + } + + private static Geometry createGeometry() { + DynamicByteBuffer positions = new DynamicByteBuffer(); + positions.putFloat(-2).putFloat(-1).putFloat(0); + positions.putFloat(2).putFloat(-1).putFloat(0); + positions.putFloat(0).putFloat(1).putFloat(0); + DynamicByteBuffer indices = new DynamicByteBuffer(); + indices.putShort(0).putShort(1).putShort(2); + AllocationAndBuffer positionsBuffer = createBuffer( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, memByteBuffer(positions.addr, positions.pos), Float.BYTES); + positions.free(); + AllocationAndBuffer indicesBuffer = createBuffer( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, memByteBuffer(indices.addr, indices.pos), Short.BYTES); + indices.free(); + return new Geometry(positionsBuffer, indicesBuffer, 1); + } + + private static VkDeviceOrHostAddressKHR deviceAddress(MemoryStack stack, long buffer, long alignment) { + return VkDeviceOrHostAddressKHR + .callocStack(stack) + .deviceAddress(bufferAddress(buffer, alignment)); + } + private static VkDeviceOrHostAddressConstKHR deviceAddressConst(MemoryStack stack, long buffer, long alignment) { + return VkDeviceOrHostAddressConstKHR + .callocStack(stack) + .deviceAddress(bufferAddress(buffer, alignment)); + } + private static long bufferAddress(long buffer, long alignment) { + long address; + try (MemoryStack stack = stackPush()) { + address = vkGetBufferDeviceAddressKHR(device, VkBufferDeviceAddressInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR) + .buffer(buffer)); + } + // check alignment + if ((address % alignment) != 0) + throw new AssertionError("Illegal address alignment"); + return address; + } + + private static class AccelerationStructure { + private final long accelerationStructure; + private final AllocationAndBuffer buffer; + private AccelerationStructure(long accelerationStructure, AllocationAndBuffer buffer) { + this.accelerationStructure = accelerationStructure; + this.buffer = buffer; + } + private void free() { + vkDestroyAccelerationStructureKHR(device, accelerationStructure, null); + buffer.free(); + } + } + + private static AccelerationStructure createBottomLevelAccelerationStructure( + Geometry geometry) { + try (MemoryStack stack = stackPush()) { + // Create the build geometry info holding the vertex and index data + VkAccelerationStructureBuildGeometryInfoKHR.Buffer pInfos = + VkAccelerationStructureBuildGeometryInfoKHR + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR) + .type(VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR) + .flags(VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR) + .pGeometries(VkAccelerationStructureGeometryKHR + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR) + .geometryType(VK_GEOMETRY_TYPE_TRIANGLES_KHR) + .geometry(VkAccelerationStructureGeometryDataKHR + .callocStack(stack) + .triangles(VkAccelerationStructureGeometryTrianglesDataKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR) + .vertexFormat(VK_FORMAT_R32G32B32_SFLOAT) + .vertexData(deviceAddressConst(stack, geometry.positions.buffer, Float.BYTES)) + .vertexStride(3 * Float.BYTES) + .maxVertex(geometry.numPrimities * 3) + .indexType(VK_INDEX_TYPE_UINT16) + .indexData(deviceAddressConst(stack, geometry.indices.buffer, Short.BYTES)))) + .flags(VK_GEOMETRY_OPAQUE_BIT_KHR)) + .geometryCount(1); + + // Query necessary sizes for the acceleration structure buffer and for the scratch buffer + VkAccelerationStructureBuildSizesInfoKHR buildSizesInfo = VkAccelerationStructureBuildSizesInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR); + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + pInfos.get(0), + stack.ints(1), + buildSizesInfo); + + // Create a buffer that will hold the final BLAS + AllocationAndBuffer accelerationStructureBuffer = createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, buildSizesInfo.accelerationStructureSize(), + null, 256); + + // Create a BLAS object (not currently built) + LongBuffer pAccelerationStructure = stack.mallocLong(1); + _CHECK_(vkCreateAccelerationStructureKHR(device, VkAccelerationStructureCreateInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR) + .type(VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR) + .size(buildSizesInfo.accelerationStructureSize()) + .buffer(accelerationStructureBuffer.buffer), null, pAccelerationStructure), + "Failed to create bottom-level acceleration structure"); + + // Create a scratch buffer for the BLAS build + AllocationAndBuffer scratchBuffer = createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, buildSizesInfo.buildScratchSize(), null, + deviceAndQueueFamilies.minAccelerationStructureScratchOffsetAlignment); + pInfos + .scratchData(deviceAddress(stack, scratchBuffer.buffer, deviceAndQueueFamilies.minAccelerationStructureScratchOffsetAlignment)) + .dstAccelerationStructure(pAccelerationStructure.get(0)); + VkCommandBuffer cmdBuf = createCommandBuffer(commandPoolTransient); + + // Insert barrier to let BLAS build wait for the geometry data transfer from the staging buffer to the GPU + vkCmdPipelineBarrier(cmdBuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, // <- copying of the geometry data from the staging buffer to the GPU buffer + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, // <- accessing the buffer for acceleration structure build + 0, // <- no dependency flags + VkMemoryBarrier + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_MEMORY_BARRIER) + .srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT) // <- GPU buffer was written to during the transfer + .dstAccessMask( + VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | // <- Accesses to the destination acceleration structures, and the scratch buffers + VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | + VK_ACCESS_SHADER_READ_BIT), // <- Accesses to input buffers for the build (vertex, index, transform, aabb, or instance data) + null, null); + + // Issue build command + vkCmdBuildAccelerationStructuresKHR( + cmdBuf, + pInfos, + stack.pointers( + VkAccelerationStructureBuildRangeInfoKHR + .callocStack(stack) + .primitiveCount(geometry.numPrimities))); + + // Finally submit command buffer and register callback when fence signals to + // dispose of resources + submitCommandBuffer(cmdBuf, true, () -> { + vkFreeCommandBuffers(device, commandPoolTransient, cmdBuf); + scratchBuffer.free(); + // the BLAS is completely self-contained after build, so + // we can also free the geometry (vertex, index buffers), since + // we also don't access the geometry data in the shaders. + geometry.free(); + }); + return new AccelerationStructure(pAccelerationStructure.get(0), accelerationStructureBuffer); + } + } + + private static class VkAccelerationStructureInstanceKHR { + public static final int SIZEOF = 64; + private final ByteBuffer memory; + private VkAccelerationStructureInstanceKHR(ByteBuffer memory) { + this.memory = memory; + } + private static VkAccelerationStructureInstanceKHR callocStack(MemoryStack stack) { + return new VkAccelerationStructureInstanceKHR(stack.calloc(64)); + } + private VkAccelerationStructureInstanceKHR accelerationStructureReference(long accelerationStructureReference) { + memory.putLong(56, accelerationStructureReference); + return this; + } + private VkAccelerationStructureInstanceKHR mask(int mask) { + memory.putInt(VkTransformMatrixKHR.SIZEOF, memory.getInt(VkTransformMatrixKHR.SIZEOF) & 0xFFFFFF | mask << 24); + return this; + } + private VkAccelerationStructureInstanceKHR flags(int flags) { + memory.putInt(VkTransformMatrixKHR.SIZEOF + 4, memory.getInt(VkTransformMatrixKHR.SIZEOF + 4) & 0xFFFFFF | flags << 24); + return this; + } + private VkAccelerationStructureInstanceKHR transform(VkTransformMatrixKHR matrix) { + memCopy(matrix.address(), address(), VkTransformMatrixKHR.SIZEOF); + return this; + } + private long address() { + return memAddress(memory); + } + } + + private static AccelerationStructure createTopLevelAccelerationStructure(AccelerationStructure blas) { + try (MemoryStack stack = stackPush()) { + // Query the BLAS device address to reference in the TLAS instance + long blasDeviceAddress = vkGetAccelerationStructureDeviceAddressKHR(device, + VkAccelerationStructureDeviceAddressInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR) + .accelerationStructure(blas.accelerationStructure)); + + // Create a single instance for our TLAS + VkAccelerationStructureInstanceKHR instance = VkAccelerationStructureInstanceKHR + .callocStack(stack) + .accelerationStructureReference(blasDeviceAddress) + .mask(~0) // <- we do not want to mask-away any geometry, so use 0b11111111 + .flags(0) + .transform(VkTransformMatrixKHR + .callocStack(stack) + .matrix(new Matrix4x3f().getTransposed(stack.mallocFloat(12)))); + + // This instance data also needs to reside in a GPU buffer, so copy it + AllocationAndBuffer instanceData = createBuffer( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, + memByteBuffer(instance.address(), VkAccelerationStructureInstanceKHR.SIZEOF), + 16); + + // Create the build geometry info holding the BLAS reference + VkAccelerationStructureBuildGeometryInfoKHR.Buffer pInfos = + VkAccelerationStructureBuildGeometryInfoKHR + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR) + .type(VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR) + .flags(VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR) + .pGeometries(VkAccelerationStructureGeometryKHR + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR) + .geometryType(VK_GEOMETRY_TYPE_INSTANCES_KHR) + .geometry(VkAccelerationStructureGeometryDataKHR + .callocStack(stack) + .instances(VkAccelerationStructureGeometryInstancesDataKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR) + .data(deviceAddressConst(stack, instanceData.buffer, 16)))) + .flags(VK_GEOMETRY_OPAQUE_BIT_KHR)) + .geometryCount(1); + + // Query necessary sizes for the acceleration structure buffer and for the scratch buffer + VkAccelerationStructureBuildSizesInfoKHR buildSizesInfo = VkAccelerationStructureBuildSizesInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR); + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + pInfos.get(0), + stack.ints(1), + buildSizesInfo); + + // Create a buffer that will hold the final TLAS + AllocationAndBuffer accelerationStructureBuffer = createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR | + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, buildSizesInfo.accelerationStructureSize(), null, + 256); + + // Create a TLAS object (not currently built) + LongBuffer pAccelerationStructure = stack.mallocLong(1); + _CHECK_(vkCreateAccelerationStructureKHR(device, VkAccelerationStructureCreateInfoKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR) + .type(VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR) + .size(buildSizesInfo.accelerationStructureSize()) + .buffer(accelerationStructureBuffer.buffer), null, pAccelerationStructure), + "Failed to create top-level acceleration structure"); + + // Create a scratch buffer for the TLAS build + AllocationAndBuffer scratchBuffer = createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, buildSizesInfo.buildScratchSize(), null, + deviceAndQueueFamilies.minAccelerationStructureScratchOffsetAlignment); + pInfos + .scratchData(deviceAddress(stack, scratchBuffer.buffer, deviceAndQueueFamilies.minAccelerationStructureScratchOffsetAlignment)) + .dstAccelerationStructure(pAccelerationStructure.get(0)); + VkCommandBuffer cmdBuf = createCommandBuffer(commandPoolTransient); + + // Insert barrier to let TLAS build wait for the instance data transfer from the staging buffer to the GPU + vkCmdPipelineBarrier(cmdBuf, + VK_PIPELINE_STAGE_TRANSFER_BIT, // <- copying of the instance data from the staging buffer to the GPU buffer + VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, // <- accessing the buffer for acceleration structure build + 0, // <- no dependency flags + VkMemoryBarrier + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_MEMORY_BARRIER) + .srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT) // <- GPU buffer was written to during the transfer + .dstAccessMask( + VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | // <- Accesses to the destination acceleration structures, and the scratch buffers + VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | + VK_ACCESS_SHADER_READ_BIT), // <- Accesses to input buffers for the build (vertex, index, transform, aabb, or instance data) + null, null); + + // Issue build command + vkCmdBuildAccelerationStructuresKHR( + cmdBuf, + pInfos, + stack.pointers( + VkAccelerationStructureBuildRangeInfoKHR + .callocStack(stack) + .primitiveCount(1))); // <- number of BLASes! + + // Finally submit command buffer and register callback when fence signals to + // dispose of resources + submitCommandBuffer(cmdBuf, true, () -> { + vkFreeCommandBuffers(device, commandPoolTransient, cmdBuf); + scratchBuffer.free(); + // the TLAS is completely self-contained after build, so + // we can free the instance data. + instanceData.free(); + }); + + return new AccelerationStructure(pAccelerationStructure.get(0), accelerationStructureBuffer); + } + } + + private static RayTracingPipeline createRayTracingPipeline() throws IOException { + int numDescriptors = 3; + try (MemoryStack stack = stackPush()) { + LongBuffer pSetLayout = stack.mallocLong(1); + // create the descriptor set layout + // we have one acceleration structure, one storage image and one uniform buffer + _CHECK_(vkCreateDescriptorSetLayout(device, VkDescriptorSetLayoutCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO) + .pBindings(VkDescriptorSetLayoutBinding + .callocStack(numDescriptors, stack) + .apply(dslb -> dslb + .binding(0) + .descriptorType(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) + .descriptorCount(1) + .stageFlags(VK_SHADER_STAGE_RAYGEN_BIT_KHR)) + .apply(dslb -> dslb + .binding(1) + .descriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + .descriptorCount(1) + .stageFlags(VK_SHADER_STAGE_RAYGEN_BIT_KHR)) + .apply(dslb -> dslb + .binding(2) + .descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .descriptorCount(1) + .stageFlags(VK_SHADER_STAGE_RAYGEN_BIT_KHR)) + .flip()), + null, pSetLayout), + "Failed to create descriptor set layout"); + LongBuffer pPipelineLayout = stack.mallocLong(1); + _CHECK_(vkCreatePipelineLayout(device, VkPipelineLayoutCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO) + .pSetLayouts(pSetLayout), null, pPipelineLayout), + "Failed to create pipeline layout"); + VkPipelineShaderStageCreateInfo.Buffer pStages = VkPipelineShaderStageCreateInfo + .callocStack(3, stack); + + // load shaders + String pkg = KhrRayTracingTriangleDemo.class.getPackage().getName().replace('.', '/') + "/"; + loadShader(pStages + .get(0) + .sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO), + null, stack, device, pkg + "khrraytracing/raygen.glsl", VK_SHADER_STAGE_RAYGEN_BIT_KHR); + loadShader(pStages + .get(1) + .sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO), + null, stack, device, pkg + "khrraytracing/raymiss.glsl", VK_SHADER_STAGE_MISS_BIT_KHR); + loadShader(pStages + .get(2) + .sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO), + null, stack, device, pkg + "khrraytracing/closesthit.glsl", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + + VkRayTracingShaderGroupCreateInfoKHR.Buffer groups = VkRayTracingShaderGroupCreateInfoKHR + .callocStack(3, stack); + groups.forEach(g -> g + .sType(VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR) + .generalShader(VK_SHADER_UNUSED_KHR) + .closestHitShader(VK_SHADER_UNUSED_KHR) + .anyHitShader(VK_SHADER_UNUSED_KHR) + .intersectionShader(VK_SHADER_UNUSED_KHR)); + groups.apply(0, g -> + g.type(VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR) + .generalShader(0)) + .apply(1, g -> + g.type(VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR) + .generalShader(1)) + .apply(2, g -> + g.type(VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR) + .closestHitShader(2)); + LongBuffer pPipelines = stack.mallocLong(1); + _CHECK_(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, VkRayTracingPipelineCreateInfoKHR + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR) + .pStages(pStages) + .maxPipelineRayRecursionDepth(0) + .pGroups(groups) + .layout(pPipelineLayout.get(0)), null, pPipelines), + "Failed to create ray tracing pipeline"); + pStages.forEach(stage -> vkDestroyShaderModule(device, stage.module(), null)); + return new RayTracingPipeline(pPipelineLayout.get(0), pSetLayout.get(0), pPipelines.get(0)); + } + } + + private static int alignUp(int size, int alignment) { + return (size + alignment - 1) & -alignment; + } + + private static AllocationAndBuffer createRayTracingShaderBindingTable() { + if (rayTracingShaderBindingTable != null) + rayTracingShaderBindingTable.free(); + try (MemoryStack stack = stackPush()) { + int groupCount = 3; + int groupHandleSize = deviceAndQueueFamilies.shaderGroupHandleSize; + // group handles must be properly aligned when writing them to the final GPU buffer, so compute + // the aligned group handle size + int groupSizeAligned = alignUp(groupHandleSize, deviceAndQueueFamilies.shaderGroupBaseAlignment); + + // compute the final size of the GPU buffer + int sbtSize = groupCount * groupSizeAligned; + + // retrieve the three shader group handles + ByteBuffer handles = stack.malloc(groupCount * groupHandleSize); + _CHECK_(vkGetRayTracingShaderGroupHandlesKHR(device, rayTracingPipeline.pipeline, 0, 3, handles), + "Failed to obtain ray tracing group handles"); + + // prepare memory with properly aligned group handles + ByteBuffer handlesForGpu = stack.malloc(sbtSize); + memCopy(memAddress(handles), memAddress(handlesForGpu), groupHandleSize); + memCopy(memAddress(handles) + groupHandleSize, memAddress(handlesForGpu) + groupSizeAligned, groupHandleSize); + memCopy(memAddress(handles) + 2L * groupHandleSize, memAddress(handlesForGpu) + 2L * groupSizeAligned, groupHandleSize); + + // and upload to a new GPU buffer + return createBuffer(VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR, handlesForGpu, + deviceAndQueueFamilies.shaderGroupBaseAlignment); + } + } + + private static LongBuffer repeat(MemoryStack stack, long value, int count) { + LongBuffer ret = stack.mallocLong(count); + for (int i = 0; i < count; i++) { + ret.put(i, value); + } + return ret; + } + + private static DescriptorSets createRayTracingDescriptorSets() { + if (rayTracingDescriptorSets != null) { + rayTracingDescriptorSets.free(); + } + int numSets = swapchain.imageViews.length; + int numDescriptors = 3; + try (MemoryStack stack = stackPush()) { + LongBuffer pDescriptorPool = stack.mallocLong(1); + _CHECK_(vkCreateDescriptorPool(device, VkDescriptorPoolCreateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO) + .pPoolSizes(VkDescriptorPoolSize + .callocStack(numDescriptors, stack) + .apply(0, dps -> dps + .type(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) + .descriptorCount(numSets)) + .apply(1, dps -> dps + .type(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + .descriptorCount(numSets)) + .apply(2, dps -> dps + .type(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .descriptorCount(numSets))) + .maxSets(numSets), null, pDescriptorPool), + "Failed to create descriptor pool"); + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO) + .descriptorPool(pDescriptorPool.get(0)) + .pSetLayouts(repeat(stack, rayTracingPipeline.descriptorSetLayout, numSets)); + LongBuffer pDescriptorSets = stack.mallocLong(numSets); + _CHECK_(vkAllocateDescriptorSets(device, descriptorSetAllocateInfo, pDescriptorSets), + "Failed to allocate descriptor set"); + long[] sets = new long[pDescriptorSets.remaining()]; + pDescriptorSets.get(sets, 0, sets.length); + VkWriteDescriptorSet.Buffer writeDescriptorSet = VkWriteDescriptorSet + .callocStack(numDescriptors * numSets, stack); + for (int i = 0; i < numSets; i++) { + final int idx = i; + writeDescriptorSet + .apply(wds -> wds + .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET) + .descriptorType(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) + .dstBinding(0) + .dstSet(pDescriptorSets.get(idx)) + .descriptorCount(1) + .pNext(VkWriteDescriptorSetAccelerationStructureKHR + .callocStack(stack) + .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR) + .pAccelerationStructures(stack.longs(tlas.accelerationStructure)) + .address())) + .apply(wds -> wds + .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET) + .descriptorType(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + .dstBinding(1) + .dstSet(pDescriptorSets.get(idx)) + .descriptorCount(1) + .pImageInfo(VkDescriptorImageInfo + .callocStack(1, stack) + .imageView(swapchain.imageViews[idx]) + .imageLayout(VK_IMAGE_LAYOUT_GENERAL))) + .apply(wds -> wds + .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET) + .descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .dstBinding(2) + .dstSet(pDescriptorSets.get(idx)) + .descriptorCount(1) + .pBufferInfo(VkDescriptorBufferInfo + .callocStack(1, stack) + .buffer(rayTracingUbos[idx].buffer) + .range(VK_WHOLE_SIZE))); + } + vkUpdateDescriptorSets(device, writeDescriptorSet.flip(), null); + return new DescriptorSets(pDescriptorPool.get(0), sets); + } + } + + private static VkCommandBuffer[] createRayTracingCommandBuffers() { + if (commandBuffers != null) { + try (MemoryStack stack = stackPush()) { + vkFreeCommandBuffers(device, commandPool, stack.pointers(commandBuffers)); + } + } + int count = swapchain.imageViews.length; + VkCommandBuffer[] buffers = new VkCommandBuffer[count]; + for (int i = 0; i < count; i++) { + VkCommandBuffer cmdBuf = createCommandBuffer(commandPool); + try (MemoryStack stack = stackPush()) { + // insert a barrier to transition the framebuffer image from undefined to general, + // and do it somewhere between the top of the pipe and the start of the ray tracing. + vkCmdPipelineBarrier(cmdBuf, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + 0, + null, + null, + VkImageMemoryBarrier + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER) + .srcAccessMask(0) + .dstAccessMask(VK_ACCESS_SHADER_WRITE_BIT) + .oldLayout(VK_IMAGE_LAYOUT_UNDEFINED) + .newLayout(VK_IMAGE_LAYOUT_GENERAL) + .image(swapchain.images[i]) + .subresourceRange(r -> r + .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) + .layerCount(1) + .levelCount(1))); + + // bind ray tracing pipeline + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, rayTracingPipeline.pipeline); + // and descriptor set + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, rayTracingPipeline.pipelineLayout, 0, + stack.longs(rayTracingDescriptorSets.sets[i]), null); + + // compute shader group offsets and sizes in the SBT + int groupSize = alignUp(deviceAndQueueFamilies.shaderGroupHandleSize, deviceAndQueueFamilies.shaderGroupBaseAlignment); + long sbtAddress = bufferAddress(rayTracingShaderBindingTable.buffer, deviceAndQueueFamilies.shaderGroupBaseAlignment); + + // and issue a tracing command + vkCmdTraceRaysKHR(cmdBuf, + VkStridedDeviceAddressRegionKHR.callocStack(stack).deviceAddress(sbtAddress).stride(groupSize).size(groupSize), + VkStridedDeviceAddressRegionKHR.callocStack(stack).deviceAddress(sbtAddress + groupSize).stride(groupSize).size(groupSize), + VkStridedDeviceAddressRegionKHR.callocStack(stack).deviceAddress(sbtAddress + 2L * groupSize).stride(groupSize).size(groupSize), + VkStridedDeviceAddressRegionKHR.callocStack(stack), swapchain.width, swapchain.height, 1); + + // insert barrier to transition the image from general to present source, + // and wait for the tracing to complete. + vkCmdPipelineBarrier( + cmdBuf, + VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + 0, + null, + null, + VkImageMemoryBarrier + .callocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER) + .srcAccessMask(VK_ACCESS_SHADER_WRITE_BIT) + .dstAccessMask(0) + .oldLayout(VK_IMAGE_LAYOUT_GENERAL) + .newLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) + .image(swapchain.images[i]) + .subresourceRange(r -> r + .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) + .layerCount(1) + .levelCount(1))); + } + _CHECK_(vkEndCommandBuffer(cmdBuf), "Failed to end command buffer"); + buffers[i] = cmdBuf; + } + return buffers; + } + + private static void updateRayTracingUniformBufferObject(int idx) { + projMatrix.scaling(1, -1, 1).perspective((float) toRadians(45.0f), (float) windowAndCallbacks.width / windowAndCallbacks.height, 0.1f, 100.0f, true); + viewMatrix.setLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0); + projMatrix.invert(invProjMatrix); + viewMatrix.invert(invViewMatrix); + ByteBuffer bb = memByteBuffer(rayTracingUbos[idx].mapped, Float.BYTES * 16 * 2); + invProjMatrix.get(0, bb); + invViewMatrix.get(Float.BYTES * 16, bb); + rayTracingUbos[idx].flushMapped(0, Float.BYTES * 16 * 2); + } + + private static void init() throws IOException { + PointerBuffer requiredExtensions = initGlfwAndReturnRequiredExtensions(); + instance = createInstance(requiredExtensions); + windowAndCallbacks = createWindow(); + surface = createSurface(); + debugCallbackHandle = setupDebugging(); + deviceAndQueueFamilies = selectPhysicalDevice(); + queueFamily = deviceAndQueueFamilies.queuesFamilies.findSingleSuitableQueue(); + device = createDevice( + asList(VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, + VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, + VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, + VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, + VK_KHR_SPIRV_1_4_EXTENSION_NAME, + VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME)); + vmaAllocator = createVmaAllocator(); + queue = retrieveQueue(); + swapchain = createSwapchain(); + commandPool = createCommandPool(0); + commandPoolTransient = createCommandPool(VK_COMMAND_POOL_CREATE_TRANSIENT_BIT); + Geometry geometry = createGeometry(); + blas = createBottomLevelAccelerationStructure(geometry); + tlas = createTopLevelAccelerationStructure(blas); + rayTracingUbos = createUniformBufferObjects(2 * 16 * Float.BYTES); + rayTracingPipeline = createRayTracingPipeline(); + rayTracingShaderBindingTable = createRayTracingShaderBindingTable(); + rayTracingDescriptorSets = createRayTracingDescriptorSets(); + commandBuffers = createRayTracingCommandBuffers(); + createSyncObjects(); + } + + private static void runOnRenderThread() { + try (MemoryStack stack = stackPush()) { + IntBuffer pImageIndex = stack.mallocInt(1); + int idx = 0; + boolean needRecreate = false; + while (!glfwWindowShouldClose(windowAndCallbacks.window)) { + updateFramebufferSize(); + if (!isWindowRenderable()) + continue; + if (windowSizeChanged()) + needRecreate = true; + if (needRecreate) { + vkQueueWaitIdle(queue); + recreateSwapchainAndDependentResources(); + idx = 0; + } + vkWaitForFences(device, renderFences[idx], true, Long.MAX_VALUE); + vkResetFences(device, renderFences[idx]); + updateRayTracingUniformBufferObject(idx); + if (!acquireSwapchainImage(pImageIndex, idx)) { + needRecreate = true; + continue; + } + needRecreate = !submitAndPresent(pImageIndex.get(0), idx); + processFinishedFences(); + idx = (idx + 1) % swapchain.imageViews.length; + } + } + } + + private static void destroy() { + _CHECK_(vkDeviceWaitIdle(device), "Failed to wait for device idle"); + for (AllocationAndBuffer rayTracingUbo : rayTracingUbos) + rayTracingUbo.free(); + rayTracingDescriptorSets.free(); + rayTracingShaderBindingTable.free(); + rayTracingPipeline.free(); + tlas.free(); + blas.free(); + for (int i = 0; i < swapchain.imageViews.length; i++) { + vkDestroySemaphore(device, imageAcquireSemaphores[i], null); + vkDestroySemaphore(device, renderCompleteSemaphores[i], null); + vkDestroyFence(device, renderFences[i], null); + } + vkDestroyCommandPool(device, commandPoolTransient, null); + vkDestroyCommandPool(device, commandPool, null); + swapchain.free(); + vmaDestroyAllocator(vmaAllocator); + vkDestroyDevice(device, null); + if (DEBUG) { + debugCallbackHandle.free(); + } + vkDestroySurfaceKHR(instance, surface, null); + windowAndCallbacks.free(); + vkDestroyInstance(instance, null); + } + + public static void main(String[] args) throws InterruptedException, IOException { + init(); + Thread updateAndRenderThread = new Thread(KhrRayTracingTriangleDemo::runOnRenderThread); + updateAndRenderThread.start(); + runWndProcLoop(); + updateAndRenderThread.join(); + destroy(); + } + +} diff --git a/src/org/lwjgl/demo/vulkan/VKUtil.java b/src/org/lwjgl/demo/vulkan/VKUtil.java index 2122d712..92a297e3 100644 --- a/src/org/lwjgl/demo/vulkan/VKUtil.java +++ b/src/org/lwjgl/demo/vulkan/VKUtil.java @@ -5,7 +5,6 @@ package org.lwjgl.demo.vulkan; import static org.lwjgl.BufferUtils.createByteBuffer; -import static org.lwjgl.assimp.Assimp.*; import static org.lwjgl.demo.util.IOUtils.ioResourceToByteBuffer; import static org.lwjgl.demo.vulkan.VKFactory.VkShaderModuleCreateInfo; import static org.lwjgl.system.MemoryUtil.*; @@ -22,7 +21,6 @@ import java.util.*; import org.lwjgl.PointerBuffer; -import org.lwjgl.assimp.*; import org.lwjgl.system.*; import org.lwjgl.util.shaderc.*; import org.lwjgl.vulkan.*; @@ -61,6 +59,8 @@ public static ByteBuffer glslToSpirv(String classPath, int vulkanStage) throws I long options = shaderc_compile_options_initialize(); ShadercIncludeResolve resolver; ShadercIncludeResultRelease releaser; + shaderc_compile_options_set_target_env(options, shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); + shaderc_compile_options_set_target_spirv(options, shaderc_spirv_version_1_4); shaderc_compile_options_set_optimization_level(options, shaderc_optimization_level_performance); shaderc_compile_options_set_include_callbacks(options, resolver = new ShadercIncludeResolve() { public long invoke(long user_data, long requested_source, int type, long requesting_source, long include_depth) { @@ -181,526 +181,6 @@ public static String translateVulkanResult(int result) { } } - public static String format_to_string(int format) { - switch (format) { - case 0: - return "UNDEFINED"; - case 1: - return "R4G4_UNORM_PACK8"; - case 2: - return "R4G4B4A4_UNORM_PACK16"; - case 3: - return "B4G4R4A4_UNORM_PACK16"; - case 4: - return "R5G6B5_UNORM_PACK16"; - case 5: - return "B5G6R5_UNORM_PACK16"; - case 6: - return "R5G5B5A1_UNORM_PACK16"; - case 7: - return "B5G5R5A1_UNORM_PACK16"; - case 8: - return "A1R5G5B5_UNORM_PACK16"; - case 9: - return "R8_UNORM"; - case 10: - return "R8_SNORM"; - case 11: - return "R8_USCALED"; - case 12: - return "R8_SSCALED"; - case 13: - return "R8_UINT"; - case 14: - return "R8_SINT"; - case 15: - return "R8_SRGB"; - case 16: - return "R8G8_UNORM"; - case 17: - return "R8G8_SNORM"; - case 18: - return "R8G8_USCALED"; - case 19: - return "R8G8_SSCALED"; - case 20: - return "R8G8_UINT"; - case 21: - return "R8G8_SINT"; - case 22: - return "R8G8_SRGB"; - case 23: - return "R8G8B8_UNORM"; - case 24: - return "R8G8B8_SNORM"; - case 25: - return "R8G8B8_USCALED"; - case 26: - return "R8G8B8_SSCALED"; - case 27: - return "R8G8B8_UINT"; - case 28: - return "R8G8B8_SINT"; - case 29: - return "R8G8B8_SRGB"; - case 30: - return "B8G8R8_UNORM"; - case 31: - return "B8G8R8_SNORM"; - case 32: - return "B8G8R8_USCALED"; - case 33: - return "B8G8R8_SSCALED"; - case 34: - return "B8G8R8_UINT"; - case 35: - return "B8G8R8_SINT"; - case 36: - return "B8G8R8_SRGB"; - case 37: - return "R8G8B8A8_UNORM"; - case 38: - return "R8G8B8A8_SNORM"; - case 39: - return "R8G8B8A8_USCALED"; - case 40: - return "R8G8B8A8_SSCALED"; - case 41: - return "R8G8B8A8_UINT"; - case 42: - return "R8G8B8A8_SINT"; - case 43: - return "R8G8B8A8_SRGB"; - case 44: - return "B8G8R8A8_UNORM"; - case 45: - return "B8G8R8A8_SNORM"; - case 46: - return "B8G8R8A8_USCALED"; - case 47: - return "B8G8R8A8_SSCALED"; - case 48: - return "B8G8R8A8_UINT"; - case 49: - return "B8G8R8A8_SINT"; - case 50: - return "B8G8R8A8_SRGB"; - case 51: - return "A8B8G8R8_UNORM_PACK32"; - case 52: - return "A8B8G8R8_SNORM_PACK32"; - case 53: - return "A8B8G8R8_USCALED_PACK32"; - case 54: - return "A8B8G8R8_SSCALED_PACK32"; - case 55: - return "A8B8G8R8_UINT_PACK32"; - case 56: - return "A8B8G8R8_SINT_PACK32"; - case 57: - return "A8B8G8R8_SRGB_PACK32"; - case 58: - return "A2R10G10B10_UNORM_PACK32"; - case 59: - return "A2R10G10B10_SNORM_PACK32"; - case 60: - return "A2R10G10B10_USCALED_PACK32"; - case 61: - return "A2R10G10B10_SSCALED_PACK32"; - case 62: - return "A2R10G10B10_UINT_PACK32"; - case 63: - return "A2R10G10B10_SINT_PACK32"; - case 64: - return "A2B10G10R10_UNORM_PACK32"; - case 65: - return "A2B10G10R10_SNORM_PACK32"; - case 66: - return "A2B10G10R10_USCALED_PACK32"; - case 67: - return "A2B10G10R10_SSCALED_PACK32"; - case 68: - return "A2B10G10R10_UINT_PACK32"; - case 69: - return "A2B10G10R10_SINT_PACK32"; - case 70: - return "R16_UNORM"; - case 71: - return "R16_SNORM"; - case 72: - return "R16_USCALED"; - case 73: - return "R16_SSCALED"; - case 74: - return "R16_UINT"; - case 75: - return "R16_SINT"; - case 76: - return "R16_SFLOAT"; - case 77: - return "R16G16_UNORM"; - case 78: - return "R16G16_SNORM"; - case 79: - return "R16G16_USCALED"; - case 80: - return "R16G16_SSCALED"; - case 81: - return "R16G16_UINT"; - case 82: - return "R16G16_SINT"; - case 83: - return "R16G16_SFLOAT"; - case 84: - return "R16G16B16_UNORM"; - case 85: - return "R16G16B16_SNORM"; - case 86: - return "R16G16B16_USCALED"; - case 87: - return "R16G16B16_SSCALED"; - case 88: - return "R16G16B16_UINT"; - case 89: - return "R16G16B16_SINT"; - case 90: - return "R16G16B16_SFLOAT"; - case 91: - return "R16G16B16A16_UNORM"; - case 92: - return "R16G16B16A16_SNORM"; - case 93: - return "R16G16B16A16_USCALED"; - case 94: - return "R16G16B16A16_SSCALED"; - case 95: - return "R16G16B16A16_UINT"; - case 96: - return "R16G16B16A16_SINT"; - case 97: - return "R16G16B16A16_SFLOAT"; - case 98: - return "R32_UINT"; - case 99: - return "R32_SINT"; - case 100: - return "R32_SFLOAT"; - case 101: - return "R32G32_UINT"; - case 102: - return "R32G32_SINT"; - case 103: - return "R32G32_SFLOAT"; - case 104: - return "R32G32B32_UINT"; - case 105: - return "R32G32B32_SINT"; - case 106: - return "R32G32B32_SFLOAT"; - case 107: - return "R32G32B32A32_UINT"; - case 108: - return "R32G32B32A32_SINT"; - case 109: - return "R32G32B32A32_SFLOAT"; - case 110: - return "R64_UINT"; - case 111: - return "R64_SINT"; - case 112: - return "R64_SFLOAT"; - case 113: - return "R64G64_UINT"; - case 114: - return "R64G64_SINT"; - case 115: - return "R64G64_SFLOAT"; - case 116: - return "R64G64B64_UINT"; - case 117: - return "R64G64B64_SINT"; - case 118: - return "R64G64B64_SFLOAT"; - case 119: - return "R64G64B64A64_UINT"; - case 120: - return "R64G64B64A64_SINT"; - case 121: - return "R64G64B64A64_SFLOAT"; - case 122: - return "B10G11R11_UFLOAT_PACK32"; - case 123: - return "E5B9G9R9_UFLOAT_PACK32"; - case 124: - return "D16_UNORM"; - case 125: - return "X8_D24_UNORM_PACK32"; - case 126: - return "D32_SFLOAT"; - case 127: - return "S8_UINT"; - case 128: - return "D16_UNORM_S8_UINT"; - case 129: - return "D24_UNORM_S8_UINT"; - case 130: - return "D32_SFLOAT_S8_UINT"; - case 131: - return "BC1_RGB_UNORM_BLOCK"; - case 132: - return "BC1_RGB_SRGB_BLOCK"; - case 133: - return "BC1_RGBA_UNORM_BLOCK"; - case 134: - return "BC1_RGBA_SRGB_BLOCK"; - case 135: - return "BC2_UNORM_BLOCK"; - case 136: - return "BC2_SRGB_BLOCK"; - case 137: - return "BC3_UNORM_BLOCK"; - case 138: - return "BC3_SRGB_BLOCK"; - case 139: - return "BC4_UNORM_BLOCK"; - case 140: - return "BC4_SNORM_BLOCK"; - case 141: - return "BC5_UNORM_BLOCK"; - case 142: - return "BC5_SNORM_BLOCK"; - case 143: - return "BC6H_UFLOAT_BLOCK"; - case 144: - return "BC6H_SFLOAT_BLOCK"; - case 145: - return "BC7_UNORM_BLOCK"; - case 146: - return "BC7_SRGB_BLOCK"; - case 147: - return "ETC2_R8G8B8_UNORM_BLOCK"; - case 148: - return "ETC2_R8G8B8_SRGB_BLOCK"; - case 149: - return "ETC2_R8G8B8A1_UNORM_BLOCK"; - case 150: - return "ETC2_R8G8B8A1_SRGB_BLOCK"; - case 151: - return "ETC2_R8G8B8A8_UNORM_BLOCK"; - case 152: - return "ETC2_R8G8B8A8_SRGB_BLOCK"; - case 153: - return "EAC_R11_UNORM_BLOCK"; - case 154: - return "EAC_R11_SNORM_BLOCK"; - case 155: - return "EAC_R11G11_UNORM_BLOCK"; - case 156: - return "EAC_R11G11_SNORM_BLOCK"; - case 157: - return "ASTC_4x4_UNORM_BLOCK"; - case 158: - return "ASTC_4x4_SRGB_BLOCK"; - case 159: - return "ASTC_5x4_UNORM_BLOCK"; - case 160: - return "ASTC_5x4_SRGB_BLOCK"; - case 161: - return "ASTC_5x5_UNORM_BLOCK"; - case 162: - return "ASTC_5x5_SRGB_BLOCK"; - case 163: - return "ASTC_6x5_UNORM_BLOCK"; - case 164: - return "ASTC_6x5_SRGB_BLOCK"; - case 165: - return "ASTC_6x6_UNORM_BLOCK"; - case 166: - return "ASTC_6x6_SRGB_BLOCK"; - case 167: - return "ASTC_8x5_UNORM_BLOCK"; - case 168: - return "ASTC_8x5_SRGB_BLOCK"; - case 169: - return "ASTC_8x6_UNORM_BLOCK"; - case 170: - return "ASTC_8x6_SRGB_BLOCK"; - case 171: - return "ASTC_8x8_UNORM_BLOCK"; - case 172: - return "ASTC_8x8_SRGB_BLOCK"; - case 173: - return "ASTC_10x5_UNORM_BLOCK"; - case 174: - return "ASTC_10x5_SRGB_BLOCK"; - case 175: - return "ASTC_10x6_UNORM_BLOCK"; - case 176: - return "ASTC_10x6_SRGB_BLOCK"; - case 177: - return "ASTC_10x8_UNORM_BLOCK"; - case 178: - return "ASTC_10x8_SRGB_BLOCK"; - case 179: - return "ASTC_10x10_UNORM_BLOCK"; - case 180: - return "ASTC_10x10_SRGB_BLOCK"; - case 181: - return "ASTC_12x10_UNORM_BLOCK"; - case 182: - return "ASTC_12x10_SRGB_BLOCK"; - case 183: - return "ASTC_12x12_UNORM_BLOCK"; - case 184: - return "ASTC_12x12_SRGB_BLOCK"; - case 1000156000: - return "G8B8G8R8_422_UNORM"; - case 1000156001: - return "B8G8R8G8_422_UNORM"; - case 1000156002: - return "G8_B8_R8_3PLANE_420_UNORM"; - case 1000156003: - return "G8_B8R8_2PLANE_420_UNORM"; - case 1000156004: - return "G8_B8_R8_3PLANE_422_UNORM"; - case 1000156005: - return "G8_B8R8_2PLANE_422_UNORM"; - case 1000156006: - return "G8_B8_R8_3PLANE_444_UNORM"; - case 1000156007: - return "R10X6_UNORM_PACK16"; - case 1000156008: - return "R10X6G10X6_UNORM_2PACK16"; - case 1000156009: - return "R10X6G10X6B10X6A10X6_UNORM_4PACK16"; - case 1000156010: - return "G10X6B10X6G10X6R10X6_422_UNORM_4PACK16"; - case 1000156011: - return "B10X6G10X6R10X6G10X6_422_UNORM_4PACK16"; - case 1000156012: - return "G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16"; - case 1000156013: - return "G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16"; - case 1000156014: - return "G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16"; - case 1000156015: - return "G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16"; - case 1000156016: - return "G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16"; - case 1000156017: - return "R12X4_UNORM_PACK16"; - case 1000156018: - return "R12X4G12X4_UNORM_2PACK16"; - case 1000156019: - return "R12X4G12X4B12X4A12X4_UNORM_4PACK16"; - case 1000156020: - return "G12X4B12X4G12X4R12X4_422_UNORM_4PACK16"; - case 1000156021: - return "B12X4G12X4R12X4G12X4_422_UNORM_4PACK16"; - case 1000156022: - return "G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16"; - case 1000156023: - return "G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16"; - case 1000156024: - return "G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16"; - case 1000156025: - return "G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16"; - case 1000156026: - return "G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16"; - case 1000156027: - return "G16B16G16R16_422_UNORM"; - case 1000156028: - return "B16G16R16G16_422_UNORM"; - case 1000156029: - return "G16_B16_R16_3PLANE_420_UNORM"; - case 1000156030: - return "G16_B16R16_2PLANE_420_UNORM"; - case 1000156031: - return "G16_B16_R16_3PLANE_422_UNORM"; - case 1000156032: - return "G16_B16R16_2PLANE_422_UNORM"; - case 1000156033: - return "G16_B16_R16_3PLANE_444_UNORM"; - case 1000054000: - return "PVRTC1_2BPP_UNORM_BLOCK_IMG"; - case 1000054001: - return "PVRTC1_4BPP_UNORM_BLOCK_IMG"; - case 1000054002: - return "PVRTC2_2BPP_UNORM_BLOCK_IMG"; - case 1000054003: - return "PVRTC2_4BPP_UNORM_BLOCK_IMG"; - case 1000054004: - return "PVRTC1_2BPP_SRGB_BLOCK_IMG"; - case 1000054005: - return "PVRTC1_4BPP_SRGB_BLOCK_IMG"; - case 1000054006: - return "PVRTC2_2BPP_SRGB_BLOCK_IMG"; - case 1000054007: - return "PVRTC2_4BPP_SRGB_BLOCK_IMG"; - default: - return ""; - } - } - - static AIScene loadModel(String classpathResource) { - AIFileIO fileIo = AIFileIO.create(); - List callbacks = new ArrayList<>(); - AIFileOpenProc fileOpenProc = new AIFileOpenProc() { - public long invoke(long pFileIO, long fileName, long openMode) { - AIFile aiFile = AIFile.create(); - final ByteBuffer data; - String fileNameUtf8 = memUTF8(fileName); - try { - data = ioResourceToByteBuffer(fileNameUtf8, 8192); - } catch (IOException e) { - throw new RuntimeException("Could not open file: " + fileNameUtf8); - } - AIFileReadProc fileReadProc = new AIFileReadProc() { - public long invoke(long pFile, long pBuffer, long size, long count) { - long max = Math.min(data.remaining(), size * count); - memCopy(memAddress(data) + data.position(), pBuffer, max); - return max; - } - }; - callbacks.add(fileReadProc); - AIFileSeek fileSeekProc = new AIFileSeek() { - public int invoke(long pFile, long offset, int origin) { - if (origin == Assimp.aiOrigin_CUR) { - data.position(data.position() + (int) offset); - } else if (origin == Assimp.aiOrigin_SET) { - data.position((int) offset); - } else if (origin == Assimp.aiOrigin_END) { - data.position(data.limit() + (int) offset); - } - return 0; - } - }; - callbacks.add(fileSeekProc); - AIFileTellProc fileTellProc = new AIFileTellProc() { - public long invoke(long pFile) { - return data.limit(); - } - }; - callbacks.add(fileTellProc); - aiFile.ReadProc(fileReadProc); - aiFile.SeekProc(fileSeekProc); - aiFile.FileSizeProc(fileTellProc); - return aiFile.address(); - } - }; - AIFileCloseProc fileCloseProc = new AIFileCloseProc() { - public void invoke(long pFileIO, long pFile) { - /* Nothing to do */ - } - }; - callbacks.add(fileOpenProc); - callbacks.add(fileCloseProc); - fileIo.set(fileOpenProc, fileCloseProc, NULL); - AIScene scene = aiImportFileEx(classpathResource, aiProcess_JoinIdenticalVertices | aiProcess_Triangulate, fileIo); - callbacks.forEach(Callback::free); - return scene; - } - public static final PointerBuffer allocateLayerBuffer(String[] layers) { final Set availableLayers = getAvailableLayers();