diff --git a/example/src/main/java/ExampleVulkan.java b/example/src/main/java/ExampleVulkan.java
new file mode 100644
index 00000000..5e19fdf0
--- /dev/null
+++ b/example/src/main/java/ExampleVulkan.java
@@ -0,0 +1,29 @@
+import imgui.ImGui;
+import imgui.app.Application;
+import imgui.app.Backend;
+import imgui.app.Configuration;
+
+/**
+ * Smoke-test entry point for the Vulkan backend. Run with:
+ *
+ * ./gradlew :example:run -PmainClass=ExampleVulkan
+ *
+ */
+public class ExampleVulkan extends Application {
+ @Override
+ protected void configure(final Configuration config) {
+ config.setTitle("Example Application — Vulkan");
+ config.setBackend(Backend.VULKAN);
+ }
+
+ @Override
+ public void process() {
+ ImGui.begin("Vulkan");
+ ImGui.text("Hello from the Vulkan backend!");
+ ImGui.end();
+ }
+
+ public static void main(final String[] args) {
+ launch(new ExampleVulkan());
+ }
+}
diff --git a/imgui-app/build.gradle b/imgui-app/build.gradle
index daef6c93..1ec7204c 100644
--- a/imgui-app/build.gradle
+++ b/imgui-app/build.gradle
@@ -23,6 +23,7 @@ dependencies {
api 'org.lwjgl:lwjgl-glfw'
api 'org.lwjgl:lwjgl-opengl'
api 'org.lwjgl:lwjgl-sdl'
+ api 'org.lwjgl:lwjgl-vulkan'
['natives-linux', 'natives-windows', 'natives-macos', 'natives-macos-arm64'].each {
api "org.lwjgl:lwjgl::$it"
@@ -31,6 +32,10 @@ dependencies {
api "org.lwjgl:lwjgl-sdl::$it"
}
+ ['natives-macos', 'natives-macos-arm64'].each {
+ api "org.lwjgl:lwjgl-vulkan::$it"
+ }
+
api project(':imgui-binding')
api project(':imgui-lwjgl3')
}
diff --git a/imgui-app/src/main/java/imgui/app/Application.java b/imgui-app/src/main/java/imgui/app/Application.java
index d135726d..676fed0a 100644
--- a/imgui-app/src/main/java/imgui/app/Application.java
+++ b/imgui-app/src/main/java/imgui/app/Application.java
@@ -134,7 +134,18 @@ public final Color getColorBg() {
public static void launch(final Application app) {
final Configuration config = new Configuration();
app.configure(config);
- app.window = (config.getBackend() == Backend.SDL) ? new WindowSdl() : new WindowGlfw();
+ switch (config.getBackend()) {
+ case SDL:
+ app.window = new WindowSdl();
+ break;
+ case VULKAN:
+ app.window = new WindowVulkan();
+ break;
+ case GLFW:
+ default:
+ app.window = new WindowGlfw();
+ break;
+ }
app.window.setOwner(app);
app.window.init(config);
app.preRun();
diff --git a/imgui-app/src/main/java/imgui/app/Backend.java b/imgui-app/src/main/java/imgui/app/Backend.java
index 9eb69aa5..a1bad0e3 100644
--- a/imgui-app/src/main/java/imgui/app/Backend.java
+++ b/imgui-app/src/main/java/imgui/app/Backend.java
@@ -11,5 +11,9 @@ public enum Backend {
/**
* SDL3 platform + SDL_GPU renderer.
*/
- SDL
+ SDL,
+ /**
+ * GLFW platform + Vulkan renderer.
+ */
+ VULKAN
}
diff --git a/imgui-app/src/main/java/imgui/app/WindowVulkan.java b/imgui-app/src/main/java/imgui/app/WindowVulkan.java
new file mode 100644
index 00000000..7019c9d3
--- /dev/null
+++ b/imgui-app/src/main/java/imgui/app/WindowVulkan.java
@@ -0,0 +1,721 @@
+package imgui.app;
+
+import imgui.ImGui;
+import imgui.glfw.ImGuiImplGlfw;
+import imgui.vulkan.ImGuiImplVulkan;
+import imgui.vulkan.ImGuiImplVulkanH;
+import org.lwjgl.PointerBuffer;
+import org.lwjgl.glfw.Callbacks;
+import org.lwjgl.glfw.GLFWErrorCallback;
+import org.lwjgl.glfw.GLFWVulkan;
+import org.lwjgl.system.MemoryStack;
+import org.lwjgl.system.MemoryUtil;
+import org.lwjgl.vulkan.*;
+
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.util.Objects;
+
+import static org.lwjgl.glfw.GLFW.*;
+import static org.lwjgl.vulkan.KHRSurface.*;
+import static org.lwjgl.vulkan.KHRSwapchain.*;
+import static org.lwjgl.vulkan.VK10.*;
+
+/**
+ * GLFW + Vulkan implementation of {@link Window}.
+ *
+ * Creates a GLFW window with {@code GLFW_CLIENT_API == GLFW_NO_API}, sets up a minimal Vulkan
+ * stack (instance, surface, physical device, device, swapchain, render pass, framebuffers, and
+ * per-frame sync objects), and renders ImGui through {@link ImGuiImplVulkan}. The swapchain is
+ * recreated on window resize (including when minimized to zero size).
+ *
+ *
Multi-viewport is not supported.
+ */
+@SuppressWarnings({"checkstyle:DesignForExtension", "checkstyle:NeedBraces", "checkstyle:LocalVariableName", "checkstyle:FinalLocalVariable", "checkstyle:ParameterName", "checkstyle:EmptyBlock", "checkstyle:AvoidNestedBlocks"})
+public class WindowVulkan extends Window {
+ private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw();
+ private final ImGuiImplVulkan imGuiVulkan = new ImGuiImplVulkan();
+ private final ImGuiImplVulkanH.Window wd = new ImGuiImplVulkanH.Window();
+
+ /**
+ * Pointer to the native GLFW window.
+ */
+ private long handle;
+
+ private VkInstance instance;
+ private VkPhysicalDevice physicalDevice;
+ private VkDevice device;
+ private VkQueue queue;
+ private int queueFamily;
+
+ private long[] imageAcquiredSemaphore;
+ private long[] renderCompleteSemaphore;
+ private long[] fence;
+ private long[] commandPool;
+ private long[] commandBuffer;
+
+ @Override
+ protected void init(final Configuration config) {
+ initWindow(config);
+ initVulkan();
+ owner.initImGui(config);
+ imGuiGlfw.init(handle, true);
+
+ final ImGuiImplVulkan.InitInfo initInfo = new ImGuiImplVulkan.InitInfo();
+ initInfo.instance = instance;
+ initInfo.physicalDevice = physicalDevice;
+ initInfo.device = device;
+ initInfo.queueFamily = queueFamily;
+ initInfo.queue = queue;
+ initInfo.descriptorPoolSize = 1;
+ initInfo.minImageCount = 2;
+ initInfo.imageCount = wd.imageCount;
+ initInfo.pipelineInfoMain.renderPass = wd.renderPass;
+ initInfo.pipelineInfoMain.subpass = 0;
+ initInfo.pipelineInfoMain.msaaSamples = VK_SAMPLE_COUNT_1_BIT;
+ if (!imGuiVulkan.init(initInfo)) {
+ throw new IllegalStateException("Failed to initialize the Vulkan ImGui backend");
+ }
+ }
+
+ @Override
+ protected void dispose() {
+ imGuiVulkan.shutdown();
+ imGuiGlfw.shutdown();
+ owner.disposeImGui();
+
+ VK10.vkDeviceWaitIdle(device);
+ destroySyncObjects();
+ destroySwapchainResources();
+ VK10.vkDestroyRenderPass(device, wd.renderPass, null);
+ KHRSurface.vkDestroySurfaceKHR(instance, wd.surface, null);
+ VK10.vkDestroyDevice(device, null);
+ VK10.vkDestroyInstance(instance, null);
+
+ Callbacks.glfwFreeCallbacks(handle);
+ glfwDestroyWindow(handle);
+ glfwTerminate();
+ Objects.requireNonNull(glfwSetErrorCallback(null)).free();
+ }
+
+ /**
+ * Method to create and initialize GLFW window (without a GL context).
+ *
+ * @param config configuration object with basic window information
+ */
+ protected void initWindow(final Configuration config) {
+ GLFWErrorCallback.createPrint(System.err).set();
+
+ if (!glfwInit()) {
+ throw new IllegalStateException("Unable to initialize GLFW");
+ }
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+ glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE);
+ glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
+ handle = glfwCreateWindow(config.getWidth(), config.getHeight(), config.getTitle(), MemoryUtil.NULL, MemoryUtil.NULL);
+
+ if (handle == MemoryUtil.NULL) {
+ throw new RuntimeException("Failed to create the GLFW window");
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pWidth = stack.mallocInt(1); // int*
+ final IntBuffer pHeight = stack.mallocInt(1); // int*
+
+ glfwGetWindowSize(handle, pWidth, pHeight);
+ final org.lwjgl.glfw.GLFWVidMode vidmode = Objects.requireNonNull(glfwGetVideoMode(glfwGetPrimaryMonitor()));
+ glfwSetWindowPos(handle, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2);
+ }
+
+ glfwShowWindow(handle);
+
+
+ glfwSetFramebufferSizeCallback(handle, new org.lwjgl.glfw.GLFWFramebufferSizeCallback() {
+ @Override
+ public void invoke(final long window, final int width, final int height) {
+ wd.swapChainRebuild = true;
+ }
+ });
+ }
+
+ @Override
+ protected void run() {
+ while (!glfwWindowShouldClose(handle)) {
+ runFrame();
+ }
+ }
+
+ @Override
+ protected void runFrame() {
+ glfwPollEvents();
+
+ if (wd.swapChainRebuild) {
+ recreateSwapchain();
+ }
+ if (wd.width == 0 || wd.height == 0) {
+ return;
+ }
+
+ imGuiVulkan.newFrame();
+ imGuiGlfw.newFrame();
+ ImGui.newFrame();
+
+ owner.preProcess();
+ owner.process();
+ owner.postProcess();
+
+ ImGui.render();
+ renderFrame();
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Vulkan setup
+ // -------------------------------------------------------------------------
+
+ private void initVulkan() {
+ final PointerBuffer requiredExtensions = GLFWVulkan.glfwGetRequiredInstanceExtensions();
+ if (requiredExtensions == null) {
+ throw new IllegalStateException("GLFW reports no required Vulkan instance extensions");
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkApplicationInfo appInfo = VkApplicationInfo.calloc(stack);
+ appInfo.sType(VK_STRUCTURE_TYPE_APPLICATION_INFO);
+ appInfo.pApplicationName(stack.UTF8("imgui-java"));
+ appInfo.applicationVersion(1);
+ appInfo.pEngineName(stack.UTF8("imgui-java"));
+ appInfo.engineVersion(1);
+ appInfo.apiVersion(VK_API_VERSION_1_0);
+
+ final VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.calloc(stack);
+ createInfo.sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
+ createInfo.pApplicationInfo(appInfo);
+ VkInstanceCreateInfo.nenabledExtensionCount(createInfo.address(), requiredExtensions.remaining());
+ createInfo.ppEnabledExtensionNames(requiredExtensions);
+
+ final PointerBuffer pInstance = stack.mallocPointer(1);
+ final int err = VK10.vkCreateInstance(createInfo, null, pInstance);
+ checkVkResult(err, "vkCreateInstance");
+ instance = new VkInstance(pInstance.get(0), createInfo);
+ }
+
+ wd.surface = ImGuiImplGlfw.createVulkanSurface(handle, instance);
+ if (wd.surface == 0) {
+ throw new IllegalStateException("Failed to create the Vulkan surface");
+ }
+
+ pickPhysicalDevice();
+ createDevice();
+ selectSurfaceFormat();
+ selectPresentMode();
+ createSwapchain();
+ createSyncObjects();
+ }
+
+ /**
+ * Picks the first physical device exposing a queue family that supports both graphics and
+ * presentation for our surface.
+ */
+ private void pickPhysicalDevice() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pCount = stack.mallocInt(1);
+ int err = VK10.vkEnumeratePhysicalDevices(instance, pCount, null);
+ checkVkResult(err, "vkEnumeratePhysicalDevices");
+ if (pCount.get(0) == 0) {
+ throw new IllegalStateException("No Vulkan physical device found");
+ }
+
+ final PointerBuffer pDevices = stack.mallocPointer(pCount.get(0));
+ err = VK10.vkEnumeratePhysicalDevices(instance, pCount, pDevices);
+ checkVkResult(err, "vkEnumeratePhysicalDevices");
+
+ for (int i = 0; i < pDevices.capacity(); i++) {
+ final long phy = pDevices.get(i);
+ final IntBuffer pQueueCount = stack.mallocInt(1);
+ VK10.vkGetPhysicalDeviceQueueFamilyProperties(new VkPhysicalDevice(phy, instance), pQueueCount, null);
+
+ final VkQueueFamilyProperties.Buffer props = VkQueueFamilyProperties.calloc(pQueueCount.get(0), stack);
+ VK10.vkGetPhysicalDeviceQueueFamilyProperties(new VkPhysicalDevice(phy, instance), pQueueCount, props);
+
+ for (int q = 0; q < props.capacity(); q++) {
+ if ((props.get(q).queueFlags() & VK_QUEUE_GRAPHICS_BIT) != 0) {
+ final IntBuffer pSupported = stack.mallocInt(1);
+ err = KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR(new VkPhysicalDevice(phy, instance), q, wd.surface, pSupported);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfaceSupportKHR");
+ if (pSupported.get(0) != 0) {
+ physicalDevice = new VkPhysicalDevice(phy, instance);
+ queueFamily = q;
+ return;
+ }
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("No Vulkan physical device with graphics + present queue found");
+ }
+
+ private void createDevice() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkDeviceQueueCreateInfo.Buffer queueCreateInfo = VkDeviceQueueCreateInfo.calloc(1, stack);
+ queueCreateInfo.sType(VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO);
+ queueCreateInfo.queueFamilyIndex(queueFamily);
+ queueCreateInfo.pQueuePriorities(stack.floats(1.0f));
+
+ final VkDeviceCreateInfo deviceInfo = VkDeviceCreateInfo.calloc(stack);
+ deviceInfo.sType(VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO);
+ VkDeviceCreateInfo.nqueueCreateInfoCount(deviceInfo.address(), 1);
+ deviceInfo.pQueueCreateInfos(queueCreateInfo);
+ VkDeviceCreateInfo.nenabledExtensionCount(deviceInfo.address(), 1);
+ deviceInfo.ppEnabledExtensionNames(stack.pointers(stack.UTF8("VK_KHR_swapchain")));
+
+ final PointerBuffer pDevice = stack.mallocPointer(1);
+ final int err = VK10.vkCreateDevice(physicalDevice, deviceInfo, null, pDevice);
+ checkVkResult(err, "vkCreateDevice");
+ device = new VkDevice(pDevice.get(0), physicalDevice, deviceInfo);
+
+ final PointerBuffer pQueue = stack.mallocPointer(1);
+ VK10.vkGetDeviceQueue(device, queueFamily, 0, pQueue);
+ queue = new VkQueue(pQueue.get(0), device);
+ }
+ }
+
+ /**
+ * Selects the surface format, preferring B8G8R8A8 with the sRGB color space.
+ */
+ private void selectSurfaceFormat() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pCount = stack.mallocInt(1);
+ int err = KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, wd.surface, pCount, null);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfaceFormatsKHR");
+ if (pCount.get(0) == 0) {
+ throw new IllegalStateException("Surface supports no formats");
+ }
+
+ final VkSurfaceFormatKHR.Buffer formats = VkSurfaceFormatKHR.calloc(pCount.get(0), stack);
+ err = KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, wd.surface, pCount, formats);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfaceFormatsKHR");
+
+ for (int i = 0; i < formats.capacity(); i++) {
+ if (formats.get(i).format() == VK_FORMAT_B8G8R8A8_UNORM
+ && formats.get(i).colorSpace() == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ wd.surfaceFormat = VK_FORMAT_B8G8R8A8_UNORM;
+ return;
+ }
+ }
+ wd.surfaceFormat = formats.get(0).format();
+ }
+ }
+
+ /**
+ * Selects the present mode, preferring mailbox, then immediate, falling back to FIFO.
+ */
+ private void selectPresentMode() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pCount = stack.mallocInt(1);
+ int err = KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, wd.surface, pCount, null);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfacePresentModesKHR");
+
+ final IntBuffer presentModes = stack.mallocInt(pCount.get(0));
+ err = KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, wd.surface, pCount, presentModes);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfacePresentModesKHR");
+
+ wd.presentMode = VK_PRESENT_MODE_FIFO_KHR;
+ for (int i = 0; i < presentModes.capacity(); i++) {
+ final int mode = presentModes.get(i);
+ if (mode == VK_PRESENT_MODE_MAILBOX_KHR || mode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
+ wd.presentMode = mode;
+ break;
+ }
+ }
+ }
+ }
+
+ private void createSwapchain() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pWidth = stack.mallocInt(1);
+ final IntBuffer pHeight = stack.mallocInt(1);
+ glfwGetFramebufferSize(handle, pWidth, pHeight);
+ wd.width = pWidth.get(0);
+ wd.height = pHeight.get(0);
+ if (wd.width == 0 || wd.height == 0) {
+ return;
+ }
+
+ int minImageCount = 2;
+ final VkSurfaceCapabilitiesKHR caps = VkSurfaceCapabilitiesKHR.calloc(stack);
+ int err = KHRSurface.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, wd.surface, caps);
+ checkVkResult(err, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
+ if (caps.maxImageCount() > 0 && minImageCount > caps.maxImageCount()) {
+ minImageCount = caps.maxImageCount();
+ }
+ if (minImageCount < caps.minImageCount()) {
+ minImageCount = caps.minImageCount();
+ }
+ if (wd.width < caps.minImageExtent().width()) {
+ wd.width = caps.minImageExtent().width();
+ }
+ if (wd.width > caps.maxImageExtent().width()) {
+ wd.width = caps.maxImageExtent().width();
+ }
+ if (wd.height < caps.minImageExtent().height()) {
+ wd.height = caps.minImageExtent().height();
+ }
+ if (wd.height > caps.maxImageExtent().height()) {
+ wd.height = caps.maxImageExtent().height();
+ }
+
+ int compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ if ((caps.supportedCompositeAlpha() & compositeAlpha) == 0) {
+ compositeAlpha = KHRSurface.VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+ }
+ if ((caps.supportedCompositeAlpha() & compositeAlpha) == 0) {
+ compositeAlpha = KHRSurface.VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
+ }
+
+ final VkSwapchainCreateInfoKHR createInfo = VkSwapchainCreateInfoKHR.calloc(stack);
+ createInfo.sType(VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR);
+ createInfo.surface(wd.surface);
+ createInfo.minImageCount(minImageCount);
+ createInfo.imageFormat(wd.surfaceFormat);
+ createInfo.imageColorSpace(VK_COLOR_SPACE_SRGB_NONLINEAR_KHR);
+ createInfo.imageExtent().set(wd.width, wd.height);
+ createInfo.imageArrayLayers(1);
+ createInfo.imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+ createInfo.imageSharingMode(VK_SHARING_MODE_EXCLUSIVE);
+ createInfo.preTransform(caps.currentTransform());
+ createInfo.compositeAlpha(compositeAlpha);
+ createInfo.presentMode(wd.presentMode);
+ createInfo.clipped(true);
+ createInfo.oldSwapchain(wd.swapchain);
+
+ final LongBuffer pSwapchain = stack.mallocLong(1);
+ err = KHRSwapchain.vkCreateSwapchainKHR(device, createInfo, null, pSwapchain);
+ checkVkResult(err, "vkCreateSwapchainKHR");
+ wd.swapchain = pSwapchain.get(0);
+
+ final IntBuffer pImageCount = stack.mallocInt(1);
+ err = KHRSwapchain.vkGetSwapchainImagesKHR(device, wd.swapchain, pImageCount, null);
+ checkVkResult(err, "vkGetSwapchainImagesKHR");
+ wd.images.clear();
+ final LongBuffer pImages = stack.mallocLong(pImageCount.get(0));
+ err = KHRSwapchain.vkGetSwapchainImagesKHR(device, wd.swapchain, pImageCount, pImages);
+ checkVkResult(err, "vkGetSwapchainImagesKHR");
+ for (int i = 0; i < pImages.capacity(); i++) {
+ wd.images.add(pImages.get(i));
+ }
+ wd.imageCount = wd.images.size();
+ }
+
+ createImageViews();
+ if (wd.renderPass == 0) {
+ createRenderPass();
+ }
+ createFramebuffers();
+ }
+
+ private void createImageViews() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ for (final long image : wd.images) {
+ final VkImageViewCreateInfo viewInfo = VkImageViewCreateInfo.calloc(stack);
+ viewInfo.sType(VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO);
+ viewInfo.image(image);
+ viewInfo.viewType(VK_IMAGE_VIEW_TYPE_2D);
+ viewInfo.format(wd.surfaceFormat);
+ viewInfo.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT);
+ viewInfo.subresourceRange().levelCount(1);
+ viewInfo.subresourceRange().layerCount(1);
+
+ final LongBuffer pView = stack.mallocLong(1);
+ final int err = VK10.vkCreateImageView(device, viewInfo, null, pView);
+ checkVkResult(err, "vkCreateImageView");
+ wd.imageViews.add(pView.get(0));
+ }
+ }
+ }
+
+ private void createRenderPass() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkAttachmentDescription.Buffer attachment = VkAttachmentDescription.calloc(1, stack);
+ attachment.format(wd.surfaceFormat);
+ attachment.samples(VK_SAMPLE_COUNT_1_BIT);
+ attachment.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR);
+ attachment.storeOp(VK_ATTACHMENT_STORE_OP_STORE);
+ attachment.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE);
+ attachment.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE);
+ attachment.initialLayout(VK_IMAGE_LAYOUT_UNDEFINED);
+ attachment.finalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+
+ final VkAttachmentReference.Buffer colorAttachment = VkAttachmentReference.calloc(1, stack);
+ colorAttachment.attachment(0);
+ colorAttachment.layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+ final VkSubpassDescription.Buffer subpass = VkSubpassDescription.calloc(1, stack);
+ subpass.pipelineBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
+ subpass.colorAttachmentCount(1);
+ subpass.pColorAttachments(colorAttachment);
+
+ final VkSubpassDependency.Buffer dependency = VkSubpassDependency.calloc(1, stack);
+ dependency.srcSubpass(VK_SUBPASS_EXTERNAL);
+ dependency.dstSubpass(0);
+ dependency.srcStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
+ dependency.srcAccessMask(0);
+ dependency.dstStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
+ dependency.dstAccessMask(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
+
+ final VkRenderPassCreateInfo rpInfo = VkRenderPassCreateInfo.calloc(stack);
+ rpInfo.sType(VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO);
+ VkRenderPassCreateInfo.nattachmentCount(rpInfo.address(), 1);
+ rpInfo.pAttachments(attachment);
+ VkRenderPassCreateInfo.nsubpassCount(rpInfo.address(), 1);
+ rpInfo.pSubpasses(subpass);
+ VkRenderPassCreateInfo.ndependencyCount(rpInfo.address(), 1);
+ rpInfo.pDependencies(dependency);
+
+ final LongBuffer pRenderPass = stack.mallocLong(1);
+ final int err = VK10.vkCreateRenderPass(device, rpInfo, null, pRenderPass);
+ checkVkResult(err, "vkCreateRenderPass");
+ wd.renderPass = pRenderPass.get(0);
+ }
+ }
+
+ private void createFramebuffers() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ for (final long view : wd.imageViews) {
+ final VkFramebufferCreateInfo fbInfo = VkFramebufferCreateInfo.calloc(stack);
+ fbInfo.sType(VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO);
+ fbInfo.renderPass(wd.renderPass);
+ fbInfo.pAttachments(stack.longs(view));
+ fbInfo.width(wd.width);
+ fbInfo.height(wd.height);
+ fbInfo.layers(1);
+
+ final LongBuffer pFramebuffer = stack.mallocLong(1);
+ final int err = VK10.vkCreateFramebuffer(device, fbInfo, null, pFramebuffer);
+ checkVkResult(err, "vkCreateFramebuffer");
+ wd.framebuffers.add(pFramebuffer.get(0));
+ }
+ }
+ }
+
+ private void createSyncObjects() {
+ imageAcquiredSemaphore = new long[wd.imageCount];
+ renderCompleteSemaphore = new long[wd.imageCount];
+ fence = new long[wd.imageCount];
+ commandPool = new long[wd.imageCount];
+ commandBuffer = new long[wd.imageCount];
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkSemaphoreCreateInfo semaphoreInfo = VkSemaphoreCreateInfo.calloc(stack);
+ semaphoreInfo.sType(VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO);
+
+ final VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.calloc(stack);
+ fenceInfo.sType(VK_STRUCTURE_TYPE_FENCE_CREATE_INFO);
+ fenceInfo.flags(VK_FENCE_CREATE_SIGNALED_BIT);
+
+ final VkCommandPoolCreateInfo poolInfo = VkCommandPoolCreateInfo.calloc(stack);
+ poolInfo.sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO);
+ poolInfo.flags(0);
+ poolInfo.queueFamilyIndex(queueFamily);
+
+ final VkCommandBufferAllocateInfo allocInfo = VkCommandBufferAllocateInfo.calloc(stack);
+ allocInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO);
+ allocInfo.commandBufferCount(1);
+
+ for (int i = 0; i < wd.imageCount; i++) {
+ final LongBuffer pSemaphore = stack.mallocLong(1);
+ int err = VK10.vkCreateSemaphore(device, semaphoreInfo, null, pSemaphore);
+ checkVkResult(err, "vkCreateSemaphore");
+ imageAcquiredSemaphore[i] = pSemaphore.get(0);
+ err = VK10.vkCreateSemaphore(device, semaphoreInfo, null, pSemaphore);
+ checkVkResult(err, "vkCreateSemaphore");
+ renderCompleteSemaphore[i] = pSemaphore.get(0);
+
+ final LongBuffer pFence = stack.mallocLong(1);
+ err = VK10.vkCreateFence(device, fenceInfo, null, pFence);
+ checkVkResult(err, "vkCreateFence");
+ fence[i] = pFence.get(0);
+
+ final LongBuffer pPool = stack.mallocLong(1);
+ err = VK10.vkCreateCommandPool(device, poolInfo, null, pPool);
+ checkVkResult(err, "vkCreateCommandPool");
+ commandPool[i] = pPool.get(0);
+
+ allocInfo.commandPool(commandPool[i]);
+ final PointerBuffer pCmd = stack.mallocPointer(1);
+ err = VK10.vkAllocateCommandBuffers(device, allocInfo, pCmd);
+ checkVkResult(err, "vkAllocateCommandBuffers");
+ commandBuffer[i] = pCmd.get(0);
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Frame rendering
+ // -------------------------------------------------------------------------
+
+ private void renderFrame() {
+
+ wd.semaphoreIndex = (wd.semaphoreIndex + 1) % wd.imageCount;
+ final int semaphoreIndex = wd.semaphoreIndex;
+
+ int err = VK10.vkWaitForFences(device, fence[semaphoreIndex], true, ~0L);
+ checkVkResult(err, "vkWaitForFences");
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final IntBuffer pImageIndex = stack.mallocInt(1);
+ err = KHRSwapchain.vkAcquireNextImageKHR(device, wd.swapchain, ~0L,
+ imageAcquiredSemaphore[semaphoreIndex], 0, pImageIndex);
+ if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) {
+ wd.swapChainRebuild = true;
+ return;
+ }
+ if (err != VK_SUCCESS) {
+ System.err.println("[WindowVulkan] vkAcquireNextImageKHR returned " + err
+ + ", skipping frame");
+ return;
+ }
+ wd.frameIndex = pImageIndex.get(0);
+
+ err = VK10.vkResetFences(device, fence[semaphoreIndex]);
+ checkVkResult(err, "vkResetFences");
+
+ final VkCommandBuffer cmd = new VkCommandBuffer(commandBuffer[semaphoreIndex], device);
+
+ VK10.vkResetCommandPool(device, commandPool[semaphoreIndex], 0);
+ final VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.calloc(stack);
+ beginInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO);
+ beginInfo.flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
+ err = VK10.vkBeginCommandBuffer(cmd, beginInfo);
+ checkVkResult(err, "vkBeginCommandBuffer");
+
+ final VkRenderPassBeginInfo rpInfo = VkRenderPassBeginInfo.calloc(stack);
+ rpInfo.sType(VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO);
+ rpInfo.renderPass(wd.renderPass);
+ rpInfo.framebuffer(wd.framebuffers.get(wd.frameIndex));
+ rpInfo.renderArea().offset().set(0, 0);
+ rpInfo.renderArea().extent().set(wd.width, wd.height);
+ final VkClearValue.Buffer clearValues = VkClearValue.calloc(1, stack);
+ clearValues.color().float32(0, colorBg.getRed());
+ clearValues.color().float32(1, colorBg.getGreen());
+ clearValues.color().float32(2, colorBg.getBlue());
+ clearValues.color().float32(3, colorBg.getAlpha());
+ rpInfo.clearValueCount(1);
+ rpInfo.pClearValues(clearValues);
+ VK10.vkCmdBeginRenderPass(cmd, rpInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+ imGuiVulkan.renderDrawData(ImGui.getDrawData(), cmd, 0);
+
+ VK10.vkCmdEndRenderPass(cmd);
+ err = VK10.vkEndCommandBuffer(cmd);
+ checkVkResult(err, "vkEndCommandBuffer");
+
+ final VkSubmitInfo submitInfo = VkSubmitInfo.calloc(stack);
+ submitInfo.sType(VK_STRUCTURE_TYPE_SUBMIT_INFO);
+ VkSubmitInfo.nwaitSemaphoreCount(submitInfo.address(), 1);
+ submitInfo.pWaitSemaphores(stack.longs(imageAcquiredSemaphore[semaphoreIndex]));
+ submitInfo.pWaitDstStageMask(stack.ints(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT));
+ VkSubmitInfo.ncommandBufferCount(submitInfo.address(), 1);
+ submitInfo.pCommandBuffers(stack.pointers(cmd.address()));
+ VkSubmitInfo.nsignalSemaphoreCount(submitInfo.address(), 1);
+ submitInfo.pSignalSemaphores(stack.longs(renderCompleteSemaphore[semaphoreIndex]));
+ err = VK10.vkQueueSubmit(queue, submitInfo, fence[semaphoreIndex]);
+ checkVkResult(err, "vkQueueSubmit");
+
+ final VkPresentInfoKHR presentInfo = VkPresentInfoKHR.calloc(stack);
+ presentInfo.sType(VK_STRUCTURE_TYPE_PRESENT_INFO_KHR);
+ VkPresentInfoKHR.nwaitSemaphoreCount(presentInfo.address(), 1);
+ presentInfo.pWaitSemaphores(stack.longs(renderCompleteSemaphore[semaphoreIndex]));
+ VkPresentInfoKHR.nswapchainCount(presentInfo.address(), 1);
+ presentInfo.pSwapchains(stack.longs(wd.swapchain));
+ presentInfo.pImageIndices(stack.ints(wd.frameIndex));
+ err = KHRSwapchain.vkQueuePresentKHR(queue, presentInfo);
+ if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) {
+ wd.swapChainRebuild = true;
+ } else {
+ checkVkResult(err, "vkQueuePresentKHR");
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Swapchain recreation & teardown
+ // -------------------------------------------------------------------------
+
+ private void recreateSwapchain() {
+ VK10.vkDeviceWaitIdle(device);
+ destroySwapchainResources();
+ wd.swapChainRebuild = false;
+ createSwapchain();
+ }
+
+ private void destroySwapchainResources() {
+ for (final long framebuffer : wd.framebuffers) {
+ VK10.vkDestroyFramebuffer(device, framebuffer, null);
+ }
+ wd.framebuffers.clear();
+ for (final long view : wd.imageViews) {
+ VK10.vkDestroyImageView(device, view, null);
+ }
+ wd.imageViews.clear();
+ wd.images.clear();
+ if (wd.swapchain != 0) {
+ KHRSwapchain.vkDestroySwapchainKHR(device, wd.swapchain, null);
+ wd.swapchain = 0;
+ }
+ }
+
+ private void destroySyncObjects() {
+ if (imageAcquiredSemaphore != null) {
+ for (int i = 0; i < wd.imageCount && i < imageAcquiredSemaphore.length; i++) {
+ if (imageAcquiredSemaphore[i] != 0) {
+ VK10.vkDestroySemaphore(device, imageAcquiredSemaphore[i], null);
+ }
+ if (renderCompleteSemaphore[i] != 0) {
+ VK10.vkDestroySemaphore(device, renderCompleteSemaphore[i], null);
+ }
+ if (fence[i] != 0) {
+ VK10.vkDestroyFence(device, fence[i], null);
+ }
+ if (commandBuffer[i] != 0) {
+ VK10.vkFreeCommandBuffers(device, commandPool[i], new VkCommandBuffer(commandBuffer[i], device));
+ }
+ if (commandPool[i] != 0) {
+ VK10.vkDestroyCommandPool(device, commandPool[i], null);
+ }
+ }
+ imageAcquiredSemaphore = null;
+ renderCompleteSemaphore = null;
+ fence = null;
+ commandPool = null;
+ commandBuffer = null;
+ }
+ }
+
+ private static void checkVkResult(final int err, final String op) {
+ if (err != VK_SUCCESS) {
+ throw new IllegalStateException(op + " failed with Vulkan error " + err);
+ }
+ }
+
+ /**
+ * @return pointer to the native GLFW window
+ */
+ public final long getHandle() {
+ return handle;
+ }
+
+ /**
+ * @return the Vulkan device handle
+ */
+ public final long getDevice() {
+ return device.address();
+ }
+
+ /**
+ * @return the swapchain handle
+ */
+ public final long getSwapchain() {
+ return wd.swapchain;
+ }
+}
diff --git a/imgui-lwjgl3/build.gradle b/imgui-lwjgl3/build.gradle
index 8c3d3840..1b5c089a 100644
--- a/imgui-lwjgl3/build.gradle
+++ b/imgui-lwjgl3/build.gradle
@@ -22,6 +22,7 @@ dependencies {
implementation 'org.lwjgl:lwjgl-glfw'
implementation 'org.lwjgl:lwjgl-opengl'
implementation 'org.lwjgl:lwjgl-sdl'
+ implementation 'org.lwjgl:lwjgl-vulkan'
implementation project(':imgui-binding')
}
diff --git a/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java
index 31db5e34..4124587a 100644
--- a/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java
+++ b/imgui-lwjgl3/src/main/java/imgui/glfw/ImGuiImplGlfw.java
@@ -35,12 +35,18 @@
import org.lwjgl.glfw.GLFWScrollCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.glfw.GLFWWindowFocusCallback;
+import org.lwjgl.glfw.GLFWVulkan;
import org.lwjgl.system.Callback;
+import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
+import org.lwjgl.vulkan.VK10;
+import org.lwjgl.vulkan.VkInstance;
+import org.lwjgl.vulkan.VkInstanceCreateInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
+import java.nio.LongBuffer;
import static org.lwjgl.glfw.GLFW.GLFW_ARROW_CURSOR;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR;
@@ -1717,4 +1723,38 @@ enum GlfwClientApi {
VULKAN,
OTHER
}
+
+ /**
+ * Create a Vulkan surface for the given GLFW window.
+ *
This is a convenience wrapper around {@code GLFWVulkan.glfwCreateWindowSurface}.
+ * @param window the GLFW window handle
+ * @param instance the Vulkan instance handle
+ * @param allocator the Vulkan allocation callbacks (may be null)
+ * @return the VkSurfaceKHR handle, or 0 on failure
+ */
+ public static long createVulkanSurface(final long window, final long instance, final long allocator) {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.calloc(stack);
+ createInfo.sType(VK10.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
+ final VkInstance vkInstance = new VkInstance(instance, createInfo);
+
+ final LongBuffer pSurface = MemoryUtil.memAllocLong(1);
+ final int err = GLFWVulkan.glfwCreateWindowSurface(vkInstance,
+ window, null, pSurface);
+ final long surface = pSurface.get(0);
+ MemoryUtil.memFree(pSurface);
+ return err == VK10.VK_SUCCESS ? surface : 0;
+ }
+ }
+
+ /**
+ * Create a Vulkan surface for the given GLFW window with LWJGL types.
+ */
+ public static long createVulkanSurface(final long window, final VkInstance instance) {
+ final LongBuffer pSurface = MemoryUtil.memAllocLong(1);
+ final int err = GLFWVulkan.glfwCreateWindowSurface(instance, window, null, pSurface);
+ final long surface = pSurface.get(0);
+ MemoryUtil.memFree(pSurface);
+ return err == VK10.VK_SUCCESS ? surface : 0;
+ }
}
diff --git a/imgui-lwjgl3/src/main/java/imgui/sdl3/ImGuiImplSdl3.java b/imgui-lwjgl3/src/main/java/imgui/sdl3/ImGuiImplSdl3.java
index 6a092498..93641444 100644
--- a/imgui-lwjgl3/src/main/java/imgui/sdl3/ImGuiImplSdl3.java
+++ b/imgui-lwjgl3/src/main/java/imgui/sdl3/ImGuiImplSdl3.java
@@ -10,7 +10,12 @@
import imgui.flag.ImGuiMouseCursor;
import imgui.flag.ImGuiMouseSource;
import org.lwjgl.sdl.SDL_Event;
+import org.lwjgl.sdl.SDLVulkan;
import org.lwjgl.system.MemoryStack;
+import org.lwjgl.system.MemoryUtil;
+import org.lwjgl.vulkan.VK10;
+import org.lwjgl.vulkan.VkInstance;
+import org.lwjgl.vulkan.VkInstanceCreateInfo;
import org.lwjgl.sdl.SDL_KeyboardEvent;
import org.lwjgl.sdl.SDL_MouseButtonEvent;
import org.lwjgl.sdl.SDL_MouseMotionEvent;
@@ -232,6 +237,7 @@
import static org.lwjgl.sdl.SDLVideo.SDL_WINDOW_INPUT_FOCUS;
import java.nio.IntBuffer;
+import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -389,6 +395,33 @@ public final boolean initForVulkan(final long sdlWindow) {
return init(sdlWindow);
}
+ /**
+ * Create a Vulkan surface for the given SDL window.
+ *
This is a convenience wrapper around {@code SDLVulkan.SDL_Vulkan_CreateSurface}.
+ * @param sdlWindow the SDL window handle
+ * @param vkInstance the Vulkan instance handle
+ * @return the VkSurfaceKHR handle, or 0 on failure
+ */
+ public static long createVulkanSurface(final long sdlWindow, final VkInstance vkInstance) {
+ final LongBuffer pSurface = MemoryUtil.memAllocLong(1);
+ final boolean ok = SDLVulkan.SDL_Vulkan_CreateSurface(sdlWindow, vkInstance, null, pSurface);
+ final long surface = ok ? pSurface.get(0) : 0;
+ MemoryUtil.memFree(pSurface);
+ return surface;
+ }
+
+ /**
+ * Create a Vulkan surface for the given SDL window, using a raw instance handle.
+ * @return the VkSurfaceKHR handle, or 0 on failure
+ */
+ public static long createVulkanSurface(final long sdlWindow, final long vkInstance) {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.calloc(stack);
+ createInfo.sType(VK10.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO);
+ return createVulkanSurface(sdlWindow, new VkInstance(vkInstance, createInfo));
+ }
+ }
+
public final boolean initForD3D(final long sdlWindow) {
return init(sdlWindow);
}
diff --git a/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java
new file mode 100644
index 00000000..c90f9b9e
--- /dev/null
+++ b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java
@@ -0,0 +1,1645 @@
+package imgui.vulkan;
+
+import imgui.ImDrawData;
+import imgui.ImFontAtlas;
+import imgui.ImGui;
+import imgui.ImGuiIO;
+import imgui.ImVec4;
+import imgui.flag.ImGuiBackendFlags;
+import imgui.type.ImInt;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import org.lwjgl.PointerBuffer;
+import org.lwjgl.system.MemoryStack;
+import org.lwjgl.system.MemoryUtil;
+import org.lwjgl.vulkan.KHRDynamicRendering;
+import org.lwjgl.vulkan.VK10;
+import org.lwjgl.vulkan.VkBufferCreateInfo;
+import org.lwjgl.vulkan.VkBufferImageCopy;
+import org.lwjgl.vulkan.VkBufferMemoryBarrier;
+import org.lwjgl.vulkan.VkCommandBuffer;
+import org.lwjgl.vulkan.VkCommandBufferAllocateInfo;
+import org.lwjgl.vulkan.VkCommandBufferBeginInfo;
+import org.lwjgl.vulkan.VkCommandPoolCreateInfo;
+import org.lwjgl.vulkan.VkDescriptorImageInfo;
+import org.lwjgl.vulkan.VkDescriptorPoolCreateInfo;
+import org.lwjgl.vulkan.VkDescriptorPoolSize;
+import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo;
+import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding;
+import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo;
+import org.lwjgl.vulkan.VkDevice;
+import org.lwjgl.vulkan.VkGraphicsPipelineCreateInfo;
+import org.lwjgl.vulkan.VkImageCreateInfo;
+import org.lwjgl.vulkan.VkImageMemoryBarrier;
+import org.lwjgl.vulkan.VkImageViewCreateInfo;
+import org.lwjgl.vulkan.VkInstance;
+import org.lwjgl.vulkan.VkMappedMemoryRange;
+import org.lwjgl.vulkan.VkMemoryAllocateInfo;
+import org.lwjgl.vulkan.VkMemoryRequirements;
+import org.lwjgl.vulkan.VkPhysicalDevice;
+import org.lwjgl.vulkan.VkPhysicalDeviceMemoryProperties;
+import org.lwjgl.vulkan.VkPipelineColorBlendAttachmentState;
+import org.lwjgl.vulkan.VkPipelineColorBlendStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineDepthStencilStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineDynamicStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineInputAssemblyStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo;
+import org.lwjgl.vulkan.VkPipelineMultisampleStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineRasterizationStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineRenderingCreateInfo;
+import org.lwjgl.vulkan.VkPipelineShaderStageCreateInfo;
+import org.lwjgl.vulkan.VkPipelineVertexInputStateCreateInfo;
+import org.lwjgl.vulkan.VkPipelineViewportStateCreateInfo;
+import org.lwjgl.vulkan.VkPushConstantRange;
+import org.lwjgl.vulkan.VkQueue;
+import org.lwjgl.vulkan.VkRect2D;
+import org.lwjgl.vulkan.VkSamplerCreateInfo;
+import org.lwjgl.vulkan.VkShaderModuleCreateInfo;
+import org.lwjgl.vulkan.VkSubmitInfo;
+import org.lwjgl.vulkan.VkVertexInputAttributeDescription;
+import org.lwjgl.vulkan.VkVertexInputBindingDescription;
+import org.lwjgl.vulkan.VkViewport;
+import org.lwjgl.vulkan.VkWriteDescriptorSet;
+
+import static org.lwjgl.vulkan.VK10.VK_ACCESS_HOST_WRITE_BIT;
+import static org.lwjgl.vulkan.VK10.VK_ACCESS_SHADER_READ_BIT;
+import static org.lwjgl.vulkan.VK10.VK_ACCESS_TRANSFER_READ_BIT;
+import static org.lwjgl.vulkan.VK10.VK_ACCESS_TRANSFER_WRITE_BIT;
+import static org.lwjgl.vulkan.VK10.VK_API_VERSION_1_0;
+import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_ONE;
+import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_SRC_ALPHA;
+import static org.lwjgl.vulkan.VK10.VK_BLEND_OP_ADD;
+import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_A_BIT;
+import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_B_BIT;
+import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_G_BIT;
+import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_R_BIT;
+import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+import static org.lwjgl.vulkan.VK10.VK_CULL_MODE_NONE;
+import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
+import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_SAMPLER;
+import static org.lwjgl.vulkan.VK10.VK_DYNAMIC_STATE_SCISSOR;
+import static org.lwjgl.vulkan.VK10.VK_DYNAMIC_STATE_VIEWPORT;
+import static org.lwjgl.vulkan.VK10.VK_FILTER_LINEAR;
+import static org.lwjgl.vulkan.VK10.VK_FILTER_NEAREST;
+import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32_SFLOAT;
+import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_UNORM;
+import static org.lwjgl.vulkan.VK10.VK_FRONT_FACE_COUNTER_CLOCKWISE;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_TILING_OPTIMAL;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_TYPE_2D;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_SAMPLED_BIT;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D;
+import static org.lwjgl.vulkan.VK10.VK_INDEX_TYPE_UINT16;
+import static org.lwjgl.vulkan.VK10.VK_INDEX_TYPE_UINT32;
+import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_GRAPHICS;
+import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_HOST_BIT;
+import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_TRANSFER_BIT;
+import static org.lwjgl.vulkan.VK10.VK_POLYGON_MODE_FILL;
+import static org.lwjgl.vulkan.VK10.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+import static org.lwjgl.vulkan.VK10.VK_QUEUE_FAMILY_IGNORED;
+import static org.lwjgl.vulkan.VK10.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+import static org.lwjgl.vulkan.VK10.VK_SAMPLER_MIPMAP_MODE_LINEAR;
+import static org.lwjgl.vulkan.VK10.VK_SAMPLER_MIPMAP_MODE_NEAREST;
+import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT;
+import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_FRAGMENT_BIT;
+import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT;
+import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SUBMIT_INFO;
+import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+import static org.lwjgl.vulkan.VK10.VK_SUCCESS;
+import static org.lwjgl.vulkan.VK10.VK_VERTEX_INPUT_RATE_VERTEX;
+import static org.lwjgl.vulkan.VK10.VK_WHOLE_SIZE;
+
+/**
+ * Java port of {@code imgui_impl_vulkan.cpp}: Dear ImGui renderer backend for Vulkan.
+ *
+ *
Public lifecycle: {@link #init(InitInfo)}, {@link #shutdown()}, {@link #newFrame()},
+ * {@link #renderDrawData(ImDrawData, VkCommandBuffer, long)} mirrors the upstream API.
+ */
+@SuppressWarnings({"checkstyle:DesignForExtension", "checkstyle:NeedBraces", "checkstyle:LocalVariableName", "checkstyle:FinalLocalVariable", "checkstyle:ParameterName", "checkstyle:EmptyBlock", "checkstyle:AvoidNestedBlocks"})
+public class ImGuiImplVulkan {
+
+ public static final int MINIMUM_SAMPLED_IMAGE_POOL_SIZE = 8;
+ public static final int MINIMUM_SAMPLER_POOL_SIZE = 2;
+
+ // -------------------------------------------------------------------------
+ // Public types
+ // -------------------------------------------------------------------------
+
+ public static class PipelineInfo {
+ public long renderPass;
+ public int subpass;
+ public int msaaSamples;
+ public final List extraDynamicStates = new ArrayList();
+ public boolean useDynamicRendering;
+ public int colorAttachmentFormat;
+ public int depthAttachmentFormat;
+
+ public PipelineInfo() {
+ msaaSamples = VK_SAMPLE_COUNT_1_BIT;
+ }
+ }
+
+ public static class InitInfo {
+ public int apiVersion;
+ public VkInstance instance;
+ public VkPhysicalDevice physicalDevice;
+ public VkDevice device;
+ public int queueFamily;
+ public VkQueue queue;
+ public long descriptorPool;
+ public int descriptorPoolSize;
+ public int minImageCount;
+ public int imageCount;
+ public long pipelineCache;
+ public final PipelineInfo pipelineInfoMain = new PipelineInfo();
+ public boolean useDynamicRendering;
+ public int pipelineCreateFlags;
+ public long minAllocationSize;
+ public CheckVkResult checkVkResultFn;
+ public boolean customShaderVert;
+ public int[] customShaderVertCode;
+ public boolean customShaderFrag;
+ public int[] customShaderFragCode;
+
+ public InitInfo() {
+ minImageCount = 2;
+ imageCount = 2;
+ apiVersion = VK_API_VERSION_1_0;
+ minAllocationSize = 1024 * 1024;
+ }
+ }
+
+ @FunctionalInterface
+ public interface CheckVkResult {
+ void accept(int err);
+ }
+
+ // -------------------------------------------------------------------------
+ // Inner data classes
+ // -------------------------------------------------------------------------
+
+ private static final class FrameRenderBuffers {
+ long vertexBufferMemory;
+ long indexBufferMemory;
+ long vertexBufferSize;
+ long indexBufferSize;
+ long vertexBuffer;
+ long indexBuffer;
+ }
+
+ private static final class WindowRenderBuffers {
+ int index;
+ int count;
+ final List frameRenderBuffers = new ArrayList();
+ }
+
+ // -------------------------------------------------------------------------
+ // Instance fields
+ // -------------------------------------------------------------------------
+
+ private InitInfo initInfo;
+ private long bufferMemoryAlignment = 256;
+ private long nonCoherentAtomSize = 64;
+ private long descriptorSetLayoutTexture;
+ private long descriptorSetLayoutSampler;
+ private long pipelineLayout;
+ private long pipeline;
+ private int pipelineCreateFlags;
+ private long shaderModuleVert;
+ private long shaderModuleFrag;
+ private long descriptorPool;
+ private boolean descriptorPoolOwned;
+ private long samplerLinear;
+ private long samplerNearest;
+ private long samplerLinearDS;
+ private long samplerNearestDS;
+ private long texCommandPool;
+ private long texCommandBuffer;
+ private long fontImage;
+ private long fontImageView;
+ private long fontMemory;
+ private long fontDescriptorSet;
+ private final WindowRenderBuffers mainWindowRenderBuffers = new WindowRenderBuffers();
+ private boolean fontTextureCreated;
+
+ // -------------------------------------------------------------------------
+ // Public API
+ // -------------------------------------------------------------------------
+
+ public boolean init(final InitInfo info) {
+ deepCopyInitInfo(info);
+
+ final ImGuiIO io = ImGui.getIO();
+ io.setBackendRendererName("imgui_impl_vulkan");
+ io.addBackendFlags(ImGuiBackendFlags.RendererHasVtxOffset);
+
+ if (info.descriptorPoolSize > 0) {
+ descriptorPoolOwned = true;
+ }
+ return createDeviceObjects();
+ }
+
+ public void shutdown() {
+ destroyDeviceObjects();
+ final ImGuiIO io = ImGui.getIO();
+ io.setBackendRendererName(null);
+ io.removeBackendFlags(ImGuiBackendFlags.RendererHasVtxOffset);
+ initInfo = null;
+ }
+
+ public void newFrame() {
+ if (initInfo == null) {
+ return;
+ }
+ if (!fontTextureCreated) {
+ createFontsTexture();
+ }
+ }
+
+ public void setMinImageCount(final int minImageCount) {
+ if (initInfo != null) {
+ initInfo.minImageCount = minImageCount;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Texture management
+ // -------------------------------------------------------------------------
+
+ public long addTexture(final long imageView, final int imageLayout) {
+ if (initInfo == null) {
+ return 0;
+ }
+ final long pool = descriptorPool != 0 ? descriptorPool : initInfo.descriptorPool;
+ if (pool == 0) {
+ return 0;
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final LongBuffer pDescriptorSet = stack.mallocLong(1);
+
+ final VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.calloc(stack);
+ allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO);
+ allocInfo.descriptorPool(pool);
+ VkDescriptorSetAllocateInfo.ndescriptorSetCount(allocInfo.address(), 1);
+ allocInfo.pSetLayouts(stack.longs(descriptorSetLayoutTexture));
+
+ int err = VK10.vkAllocateDescriptorSets(initInfo.device, allocInfo, pDescriptorSet);
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return 0;
+ }
+ final long descriptorSet = pDescriptorSet.get(0);
+
+ final VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.calloc(1, stack);
+ imageInfo.imageView(imageView);
+ imageInfo.imageLayout(imageLayout);
+
+ final VkWriteDescriptorSet.Buffer writeDesc = VkWriteDescriptorSet.calloc(1, stack);
+ writeDesc.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET);
+ writeDesc.dstSet(descriptorSet);
+ writeDesc.descriptorCount(1);
+ writeDesc.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
+ writeDesc.pImageInfo(imageInfo);
+
+ VK10.vkUpdateDescriptorSets(initInfo.device, writeDesc, null);
+
+ return descriptorSet;
+ }
+ }
+
+ public void removeTexture(final long descriptorSet) {
+ if (initInfo == null) {
+ return;
+ }
+ final long pool = descriptorPool != 0 ? descriptorPool : initInfo.descriptorPool;
+ if (pool == 0) {
+ return;
+ }
+ VK10.vkFreeDescriptorSets(initInfo.device, pool, descriptorSet);
+ }
+
+ @Deprecated
+ public long addTexture(final long sampler, final long imageView, final int imageLayout) {
+ return addTexture(imageView, imageLayout);
+ }
+
+ // -------------------------------------------------------------------------
+ // Render
+ // -------------------------------------------------------------------------
+
+ public void renderDrawData(final ImDrawData drawData, final VkCommandBuffer commandBuffer, final long pipeline) {
+ final int fbWidth = (int) (drawData.getDisplaySize().x * drawData.getFramebufferScaleX());
+ final int fbHeight = (int) (drawData.getDisplaySize().y * drawData.getFramebufferScaleY());
+ if (fbWidth <= 0 || fbHeight <= 0 || initInfo == null) {
+ return;
+ }
+ long pipelineToUse = pipeline;
+ if (pipelineToUse == 0) {
+ pipelineToUse = this.pipeline;
+ }
+
+ final WindowRenderBuffers wrb = mainWindowRenderBuffers;
+ if (wrb.frameRenderBuffers.isEmpty()) {
+ wrb.index = 0;
+ wrb.count = initInfo.imageCount;
+ for (int i = 0; i < wrb.count; i++) {
+ wrb.frameRenderBuffers.add(new FrameRenderBuffers());
+ }
+ }
+ if (wrb.count != initInfo.imageCount) {
+ wrb.count = initInfo.imageCount;
+ wrb.frameRenderBuffers.clear();
+ for (int i = 0; i < wrb.count; i++) {
+ wrb.frameRenderBuffers.add(new FrameRenderBuffers());
+ }
+ }
+ wrb.index = (wrb.index + 1) % wrb.count;
+ final FrameRenderBuffers rb = wrb.frameRenderBuffers.get(wrb.index);
+
+ if (drawData.getTotalVtxCount() > 0) {
+ final int vtxSize = ImDrawData.sizeOfImDrawVert();
+ final int idxSize = ImDrawData.sizeOfImDrawIdx();
+ final long vertexSize = alignBufferSize(
+ (long) drawData.getTotalVtxCount() * vtxSize, bufferMemoryAlignment);
+ final long indexSize = alignBufferSize(
+ (long) drawData.getTotalIdxCount() * idxSize, bufferMemoryAlignment);
+
+ if (rb.vertexBuffer == 0 || rb.vertexBufferSize < vertexSize) {
+ createOrResizeBuffer(rb, true, vertexSize);
+ }
+ if (rb.indexBuffer == 0 || rb.indexBufferSize < indexSize) {
+ createOrResizeBuffer(rb, false, indexSize);
+ }
+
+ final long vtxDstPtr = mapMemory(rb.vertexBufferMemory, vertexSize);
+ final long idxDstPtr = mapMemory(rb.indexBufferMemory, indexSize);
+ final ByteBuffer vtxDst = MemoryUtil.memByteBuffer(vtxDstPtr, (int) vertexSize);
+ final ByteBuffer idxDst = MemoryUtil.memByteBuffer(idxDstPtr, (int) indexSize);
+
+
+ final int cmdListsCount = drawData.getCmdListsCount();
+ for (int n = 0; n < cmdListsCount; n++) {
+ // NOTE: getCmdListVtxBufferData()/getCmdListIdxBufferData() share the same
+ // internal buffer, so the vertex data must be consumed before fetching indices.
+ vtxDst.put(drawData.getCmdListVtxBufferData(n));
+ idxDst.put(drawData.getCmdListIdxBufferData(n));
+ }
+ vtxDst.flip();
+ idxDst.flip();
+
+ flushMemory(rb.vertexBufferMemory, vertexSize);
+ flushMemory(rb.indexBufferMemory, indexSize);
+ VK10.vkUnmapMemory(initInfo.device, rb.vertexBufferMemory);
+ VK10.vkUnmapMemory(initInfo.device, rb.indexBufferMemory);
+ }
+
+ final float[] constants = new float[4];
+ constants[0] = 2.0f / drawData.getDisplaySize().x;
+ constants[1] = 2.0f / drawData.getDisplaySize().y;
+ constants[2] = -1.0f - drawData.getDisplayPos().x * constants[0];
+ constants[3] = -1.0f - drawData.getDisplayPos().y * constants[1];
+ VK10.vkCmdPushConstants(commandBuffer, pipelineLayout,
+ VK_SHADER_STAGE_VERTEX_BIT, 0, constants);
+
+ setupRenderState(drawData, pipelineToUse, commandBuffer, rb, fbWidth, fbHeight);
+
+ final float clipOffX = drawData.getDisplayPos().x;
+ final float clipOffY = drawData.getDisplayPos().y;
+ final float clipScaleX = drawData.getFramebufferScaleX();
+ final float clipScaleY = drawData.getFramebufferScaleY();
+
+ long lastImageView = 0;
+ int globalVtxOffset = 0;
+ int globalIdxOffset = 0;
+
+ final int cmdListsCount = drawData.getCmdListsCount();
+ for (int n = 0; n < cmdListsCount; n++) {
+ final int cmdBufferSize = drawData.getCmdListCmdBufferSize(n);
+ for (int cmdI = 0; cmdI < cmdBufferSize; cmdI++) {
+ final int elemCount = drawData.getCmdListCmdBufferElemCount(n, cmdI);
+ if (elemCount == 0) {
+ continue;
+ }
+ final ImVec4 clipRect = drawData.getCmdListCmdBufferClipRect(n, cmdI);
+ final long textureId = drawData.getCmdListCmdBufferTextureId(n, cmdI);
+ final int vtxOffset = drawData.getCmdListCmdBufferVtxOffset(n, cmdI);
+ final int idxOffset = drawData.getCmdListCmdBufferIdxOffset(n, cmdI);
+
+ float clipMinX = (clipRect.x - clipOffX) * clipScaleX;
+ float clipMinY = (clipRect.y - clipOffY) * clipScaleY;
+ float clipMaxX = (clipRect.z - clipOffX) * clipScaleX;
+ float clipMaxY = (clipRect.w - clipOffY) * clipScaleY;
+
+ if (clipMinX < 0.0f) {
+ clipMinX = 0.0f;
+ }
+ if (clipMinY < 0.0f) {
+ clipMinY = 0.0f;
+ }
+ if (clipMaxX > fbWidth) {
+ clipMaxX = fbWidth;
+ }
+ if (clipMaxY > fbHeight) {
+ clipMaxY = fbHeight;
+ }
+ if (clipMaxX <= clipMinX || clipMaxY <= clipMinY) {
+ continue;
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkRect2D.Buffer scissor = VkRect2D.calloc(1, stack);
+ scissor.offset().x((int) clipMinX);
+ scissor.offset().y((int) clipMinY);
+ scissor.extent().width((int) (clipMaxX - clipMinX));
+ scissor.extent().height((int) (clipMaxY - clipMinY));
+ VK10.vkCmdSetScissor(commandBuffer, 0, scissor);
+ }
+
+ final long imageView = textureId;
+ if (imageView != lastImageView) {
+ VK10.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ pipelineLayout, 0, new long[] {imageView}, new int[0]);
+ lastImageView = imageView;
+ }
+
+ VK10.vkCmdDrawIndexed(commandBuffer, elemCount, 1,
+ idxOffset + globalIdxOffset, vtxOffset + globalVtxOffset, 0);
+ }
+ globalIdxOffset += drawData.getCmdListIdxBufferSize(n);
+ globalVtxOffset += drawData.getCmdListVtxBufferSize(n);
+ }
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkRect2D.Buffer scissor = VkRect2D.calloc(1, stack);
+ scissor.extent().width(fbWidth);
+ scissor.extent().height(fbHeight);
+ VK10.vkCmdSetScissor(commandBuffer, 0, scissor);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Create/Destroy Device Objects
+ // -------------------------------------------------------------------------
+
+ private boolean createDeviceObjects() {
+ if (initInfo == null) {
+ return false;
+ }
+ if (!createShaderModules()) {
+ return false;
+ }
+ if (!createDescriptorSetLayouts()) {
+ return false;
+ }
+ if (descriptorPoolOwned && !createDescriptorPool()) {
+ return false;
+ }
+ if (!createPipelineLayout()) {
+ return false;
+ }
+ if (!createSamplers()) {
+ return false;
+ }
+ if (!createTextureUploadCommandPool()) {
+ return false;
+ }
+ final boolean createMainPipeline = initInfo.pipelineInfoMain.renderPass != 0
+ || (initInfo.useDynamicRendering
+ && initInfo.pipelineInfoMain.useDynamicRendering
+ && initInfo.pipelineInfoMain.colorAttachmentFormat != 0);
+ if (createMainPipeline) {
+ createMainPipeline(initInfo.pipelineInfoMain);
+ }
+ return true;
+ }
+
+ private void destroyDeviceObjects() {
+ if (initInfo == null) {
+ return;
+ }
+ destroyFontsTexture();
+
+ if (samplerNearestDS != 0) {
+ VK10.vkFreeDescriptorSets(initInfo.device, descriptorPool, samplerNearestDS);
+ samplerNearestDS = 0;
+ }
+ if (samplerLinearDS != 0) {
+ VK10.vkFreeDescriptorSets(initInfo.device, descriptorPool, samplerLinearDS);
+ samplerLinearDS = 0;
+ }
+ if (samplerNearest != 0) {
+ VK10.vkDestroySampler(initInfo.device, samplerNearest, null);
+ samplerNearest = 0;
+ }
+ if (samplerLinear != 0) {
+ VK10.vkDestroySampler(initInfo.device, samplerLinear, null);
+ samplerLinear = 0;
+ }
+ if (pipeline != 0) {
+ VK10.vkDestroyPipeline(initInfo.device, pipeline, null);
+ pipeline = 0;
+ }
+ if (pipelineLayout != 0) {
+ VK10.vkDestroyPipelineLayout(initInfo.device, pipelineLayout, null);
+ pipelineLayout = 0;
+ }
+ if (descriptorSetLayoutSampler != 0) {
+ VK10.vkDestroyDescriptorSetLayout(initInfo.device, descriptorSetLayoutSampler, null);
+ descriptorSetLayoutSampler = 0;
+ }
+ if (descriptorSetLayoutTexture != 0) {
+ VK10.vkDestroyDescriptorSetLayout(initInfo.device, descriptorSetLayoutTexture, null);
+ descriptorSetLayoutTexture = 0;
+ }
+ if (descriptorPoolOwned && descriptorPool != 0) {
+ VK10.vkDestroyDescriptorPool(initInfo.device, descriptorPool, null);
+ descriptorPool = 0;
+ }
+ if (shaderModuleVert != 0) {
+ VK10.vkDestroyShaderModule(initInfo.device, shaderModuleVert, null);
+ shaderModuleVert = 0;
+ }
+ if (shaderModuleFrag != 0) {
+ VK10.vkDestroyShaderModule(initInfo.device, shaderModuleFrag, null);
+ shaderModuleFrag = 0;
+ }
+
+ if (texCommandBuffer != 0) {
+ VK10.vkFreeCommandBuffers(initInfo.device, texCommandPool,
+ new VkCommandBuffer(texCommandBuffer, initInfo.device));
+ texCommandBuffer = 0;
+ }
+ if (texCommandPool != 0) {
+ VK10.vkDestroyCommandPool(initInfo.device, texCommandPool, null);
+ texCommandPool = 0;
+ }
+
+ for (final FrameRenderBuffers rb : mainWindowRenderBuffers.frameRenderBuffers) {
+ destroyFrameRenderBuffers(rb);
+ }
+ mainWindowRenderBuffers.frameRenderBuffers.clear();
+
+ fontTextureCreated = false;
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Shader helpers
+ // -------------------------------------------------------------------------
+
+ private static final String VERT_SHADER_PATH = "/imgui/vulkan/shaders/spirv_vertex.bin";
+ private static final String FRAG_SHADER_PATH = "/imgui/vulkan/shaders/spirv_fragment.bin";
+
+ /**
+ * Loads the default SPIR-V bytecode from the classpath resources, or the custom shader
+ * code supplied via {@link InitInfo}.
+ */
+ private static ByteBuffer loadShaderCode(final boolean custom, final int[] customCode, final String resourcePath) {
+ if (custom) {
+ final ByteBuffer spv = MemoryUtil.memAlloc(customCode.length * 4);
+ spv.asIntBuffer().put(customCode);
+ return spv;
+ }
+ try (java.io.InputStream in = ImGuiImplVulkan.class.getResourceAsStream(resourcePath)) {
+ if (in == null) {
+ throw new IllegalStateException("Failed to load shader resource: " + resourcePath);
+ }
+ final byte[] data = readAllBytes(in);
+ final ByteBuffer spv = MemoryUtil.memAlloc(data.length);
+ spv.put(data);
+ spv.flip();
+ return spv;
+ } catch (final java.io.IOException e) {
+ throw new IllegalStateException("Failed to load shader resource: " + resourcePath, e);
+ }
+ }
+
+ private static byte[] readAllBytes(final java.io.InputStream in) throws java.io.IOException {
+ final java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();
+ final byte[] buffer = new byte[4096];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ return out.toByteArray();
+ }
+
+ private boolean createShaderModules() {
+ {
+ final ByteBuffer spv = loadShaderCode(initInfo.customShaderVert, initInfo.customShaderVertCode, VERT_SHADER_PATH);
+
+ final VkShaderModuleCreateInfo shaderInfo = VkShaderModuleCreateInfo.calloc();
+ shaderInfo.sType(VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO);
+ shaderInfo.pCode(spv);
+
+ final LongBuffer pShaderModule = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateShaderModule(initInfo.device, shaderInfo, null, pShaderModule);
+ shaderModuleVert = pShaderModule.get(0);
+ MemoryUtil.memFree(pShaderModule);
+ MemoryUtil.memFree(spv);
+ shaderInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ {
+ final ByteBuffer spv = loadShaderCode(initInfo.customShaderFrag, initInfo.customShaderFragCode, FRAG_SHADER_PATH);
+
+ final VkShaderModuleCreateInfo shaderInfo = VkShaderModuleCreateInfo.calloc();
+ shaderInfo.sType(VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO);
+ shaderInfo.pCode(spv);
+
+ final LongBuffer pShaderModule = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateShaderModule(initInfo.device, shaderInfo, null, pShaderModule);
+ shaderModuleFrag = pShaderModule.get(0);
+ MemoryUtil.memFree(pShaderModule);
+ MemoryUtil.memFree(spv);
+ shaderInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean createDescriptorSetLayouts() {
+ {
+ final VkDescriptorSetLayoutBinding.Buffer bindings =
+ VkDescriptorSetLayoutBinding.calloc(1);
+ bindings.binding(0);
+ bindings.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
+ bindings.descriptorCount(1);
+ bindings.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT);
+
+ final VkDescriptorSetLayoutCreateInfo layoutInfo =
+ VkDescriptorSetLayoutCreateInfo.calloc();
+ layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO);
+ VkDescriptorSetLayoutCreateInfo.nbindingCount(layoutInfo.address(), 1);
+ layoutInfo.pBindings(bindings);
+
+ final LongBuffer pLayout = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateDescriptorSetLayout(initInfo.device, layoutInfo, null, pLayout);
+ descriptorSetLayoutTexture = pLayout.get(0);
+ MemoryUtil.memFree(pLayout);
+ bindings.free();
+ layoutInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ {
+ final VkDescriptorSetLayoutBinding.Buffer bindings =
+ VkDescriptorSetLayoutBinding.calloc(1);
+ bindings.binding(0);
+ bindings.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLER);
+ bindings.descriptorCount(1);
+ bindings.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT);
+
+ final VkDescriptorSetLayoutCreateInfo layoutInfo =
+ VkDescriptorSetLayoutCreateInfo.calloc();
+ layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO);
+ VkDescriptorSetLayoutCreateInfo.nbindingCount(layoutInfo.address(), 1);
+ layoutInfo.pBindings(bindings);
+
+ final LongBuffer pLayout = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateDescriptorSetLayout(initInfo.device, layoutInfo, null, pLayout);
+ descriptorSetLayoutSampler = pLayout.get(0);
+ MemoryUtil.memFree(pLayout);
+ bindings.free();
+ layoutInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean createPipelineLayout() {
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final LongBuffer setLayouts = stack.longs(
+ descriptorSetLayoutTexture, descriptorSetLayoutSampler);
+
+ final VkPushConstantRange.Buffer pushConstantRange =
+ VkPushConstantRange.calloc(1, stack);
+ pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT);
+ pushConstantRange.offset(0);
+ pushConstantRange.size(4 * Float.BYTES);
+
+ final VkPipelineLayoutCreateInfo layoutInfo =
+ VkPipelineLayoutCreateInfo.calloc(stack);
+ layoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO);
+ layoutInfo.setLayoutCount(2);
+ layoutInfo.pSetLayouts(setLayouts);
+ VkPipelineLayoutCreateInfo.npushConstantRangeCount(layoutInfo.address(), 1);
+ layoutInfo.pPushConstantRanges(pushConstantRange);
+
+ final LongBuffer pLayout = stack.mallocLong(1);
+ final int err = VK10.vkCreatePipelineLayout(initInfo.device, layoutInfo, null, pLayout);
+ pipelineLayout = pLayout.get(0);
+ checkVkResult(err);
+ return err == VK_SUCCESS;
+ }
+ }
+
+ /**
+ * Creates a graphics pipeline with the given pipeline info, mirroring
+ * {@code ImGui_ImplVulkan_CreatePipeline}. Requires {@link #createPipelineLayout()}
+ * to have been called first.
+ *
+ * @param info the pipeline configuration (render pass, MSAA samples, dynamic states, formats)
+ * @return the VkPipeline handle, or 0 on failure
+ */
+ public long createPipeline(final PipelineInfo info) {
+ if (initInfo == null) {
+ return 0;
+ }
+ if (!createShaderModules()) {
+ return 0;
+ }
+
+ final int vertSize = ImDrawData.sizeOfImDrawVert();
+ final VkPipelineShaderStageCreateInfo.Buffer stages =
+ VkPipelineShaderStageCreateInfo.calloc(2);
+ final ByteBuffer mainName = MemoryUtil.memASCII("main");
+ stages.get(0).sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO);
+ stages.get(0).stage(VK_SHADER_STAGE_VERTEX_BIT);
+ stages.get(0).module(shaderModuleVert);
+ stages.get(0).pName(mainName);
+ stages.get(1).sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO);
+ stages.get(1).stage(VK_SHADER_STAGE_FRAGMENT_BIT);
+ stages.get(1).module(shaderModuleFrag);
+ stages.get(1).pName(mainName);
+
+ final VkVertexInputBindingDescription.Buffer bindingDesc =
+ VkVertexInputBindingDescription.calloc(1);
+ bindingDesc.stride(vertSize);
+ bindingDesc.inputRate(VK_VERTEX_INPUT_RATE_VERTEX);
+
+ final VkVertexInputAttributeDescription.Buffer attributeDesc =
+ VkVertexInputAttributeDescription.calloc(3);
+ attributeDesc.get(0).location(0);
+ attributeDesc.get(0).binding(0);
+ attributeDesc.get(0).format(VK_FORMAT_R32G32_SFLOAT);
+ attributeDesc.get(0).offset(0);
+ attributeDesc.get(1).location(1);
+ attributeDesc.get(1).binding(0);
+ attributeDesc.get(1).format(VK_FORMAT_R32G32_SFLOAT);
+ attributeDesc.get(1).offset(2 * Float.BYTES);
+ attributeDesc.get(2).location(2);
+ attributeDesc.get(2).binding(0);
+ attributeDesc.get(2).format(VK_FORMAT_R8G8B8A8_UNORM);
+ attributeDesc.get(2).offset(4 * Float.BYTES);
+
+ final VkPipelineVertexInputStateCreateInfo vertexInfo =
+ VkPipelineVertexInputStateCreateInfo.calloc();
+ vertexInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO);
+ VkPipelineVertexInputStateCreateInfo.nvertexBindingDescriptionCount(vertexInfo.address(), 1);
+ vertexInfo.pVertexBindingDescriptions(bindingDesc);
+ VkPipelineVertexInputStateCreateInfo.nvertexAttributeDescriptionCount(vertexInfo.address(), 3);
+ vertexInfo.pVertexAttributeDescriptions(attributeDesc);
+
+ final VkPipelineInputAssemblyStateCreateInfo iaInfo =
+ VkPipelineInputAssemblyStateCreateInfo.calloc();
+ iaInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO);
+ iaInfo.topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
+
+ final VkPipelineViewportStateCreateInfo viewportInfo =
+ VkPipelineViewportStateCreateInfo.calloc();
+ viewportInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO);
+ viewportInfo.viewportCount(1);
+ viewportInfo.scissorCount(1);
+
+ final VkPipelineRasterizationStateCreateInfo rasterInfo =
+ VkPipelineRasterizationStateCreateInfo.calloc();
+ rasterInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO);
+ rasterInfo.polygonMode(VK_POLYGON_MODE_FILL);
+ rasterInfo.cullMode(VK_CULL_MODE_NONE);
+ rasterInfo.frontFace(VK_FRONT_FACE_COUNTER_CLOCKWISE);
+ rasterInfo.lineWidth(1.0f);
+
+ final VkPipelineMultisampleStateCreateInfo msInfo =
+ VkPipelineMultisampleStateCreateInfo.calloc();
+ msInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO);
+ msInfo.rasterizationSamples(info.msaaSamples != 0
+ ? info.msaaSamples : VK_SAMPLE_COUNT_1_BIT);
+
+ final VkPipelineColorBlendAttachmentState.Buffer colorAttachment =
+ VkPipelineColorBlendAttachmentState.calloc(1);
+ colorAttachment.blendEnable(true);
+ colorAttachment.srcColorBlendFactor(VK_BLEND_FACTOR_SRC_ALPHA);
+ colorAttachment.dstColorBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA);
+ colorAttachment.colorBlendOp(VK_BLEND_OP_ADD);
+ colorAttachment.srcAlphaBlendFactor(VK_BLEND_FACTOR_ONE);
+ colorAttachment.dstAlphaBlendFactor(VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA);
+ colorAttachment.alphaBlendOp(VK_BLEND_OP_ADD);
+ colorAttachment.colorWriteMask(VK_COLOR_COMPONENT_R_BIT
+ | VK_COLOR_COMPONENT_G_BIT
+ | VK_COLOR_COMPONENT_B_BIT
+ | VK_COLOR_COMPONENT_A_BIT);
+
+ final VkPipelineDepthStencilStateCreateInfo depthInfo =
+ VkPipelineDepthStencilStateCreateInfo.calloc();
+ depthInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO);
+
+ final VkPipelineColorBlendStateCreateInfo blendInfo =
+ VkPipelineColorBlendStateCreateInfo.calloc();
+ blendInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO);
+ blendInfo.attachmentCount(1);
+ blendInfo.pAttachments(colorAttachment);
+
+ final int extraStateCount = info.extraDynamicStates.size();
+ final IntBuffer dynamicStates = MemoryUtil.memAllocInt(extraStateCount + 2);
+ for (int i = 0; i < extraStateCount; i++) {
+ dynamicStates.put(i, info.extraDynamicStates.get(i));
+ }
+ dynamicStates.put(extraStateCount, VK_DYNAMIC_STATE_VIEWPORT);
+ dynamicStates.put(extraStateCount + 1, VK_DYNAMIC_STATE_SCISSOR);
+
+ final VkPipelineDynamicStateCreateInfo dynamicState =
+ VkPipelineDynamicStateCreateInfo.calloc();
+ dynamicState.sType(VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO);
+ VkPipelineDynamicStateCreateInfo.ndynamicStateCount(dynamicState.address(),
+ extraStateCount + 2);
+ dynamicState.pDynamicStates(dynamicStates);
+
+ final VkGraphicsPipelineCreateInfo.Buffer createInfo =
+ VkGraphicsPipelineCreateInfo.calloc(1);
+ createInfo.sType(VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO);
+ createInfo.flags(pipelineCreateFlags);
+ createInfo.stageCount(2);
+ createInfo.pStages(stages);
+ createInfo.pVertexInputState(vertexInfo);
+ createInfo.pInputAssemblyState(iaInfo);
+ createInfo.pViewportState(viewportInfo);
+ createInfo.pRasterizationState(rasterInfo);
+ createInfo.pMultisampleState(msInfo);
+ createInfo.pDepthStencilState(depthInfo);
+ createInfo.pColorBlendState(blendInfo);
+ createInfo.pDynamicState(dynamicState);
+ createInfo.layout(pipelineLayout);
+ createInfo.renderPass(info.renderPass);
+ createInfo.subpass(info.subpass);
+
+ if (initInfo.useDynamicRendering && info.useDynamicRendering) {
+ final VkPipelineRenderingCreateInfo renderingInfo =
+ VkPipelineRenderingCreateInfo.calloc();
+ renderingInfo.sType(KHRDynamicRendering.VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR);
+ final IntBuffer colorFormats = MemoryUtil.memAllocInt(1);
+ colorFormats.put(0, info.colorAttachmentFormat);
+ renderingInfo.colorAttachmentCount(1);
+ renderingInfo.pColorAttachmentFormats(colorFormats);
+ renderingInfo.depthAttachmentFormat(info.depthAttachmentFormat);
+ createInfo.pNext(renderingInfo.address());
+ createInfo.renderPass(0);
+ renderingInfo.free();
+ MemoryUtil.memFree(colorFormats);
+ }
+
+ final LongBuffer pPipeline = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateGraphicsPipelines(
+ initInfo.device, initInfo.pipelineCache, createInfo, null, pPipeline);
+ final long result = err == VK_SUCCESS ? pPipeline.get(0) : 0;
+
+ MemoryUtil.memFree(pPipeline);
+ MemoryUtil.memFree(dynamicStates);
+ MemoryUtil.memFree(mainName);
+ dynamicState.free();
+ blendInfo.free();
+ depthInfo.free();
+ colorAttachment.free();
+ msInfo.free();
+ rasterInfo.free();
+ viewportInfo.free();
+ iaInfo.free();
+ vertexInfo.free();
+ attributeDesc.free();
+ bindingDesc.free();
+ stages.free();
+ checkVkResult(err);
+ return result;
+ }
+
+ /**
+ * Replaces the main pipeline with one built from the given pipeline info,
+ * mirroring {@code ImGui_ImplVulkan_CreateMainPipeline}.
+ *
+ * @param info the pipeline configuration (render pass, MSAA samples, dynamic states, formats)
+ */
+ public void createMainPipeline(final PipelineInfo info) {
+ if (initInfo == null) {
+ return;
+ }
+ if (pipeline != 0) {
+ VK10.vkDestroyPipeline(initInfo.device, pipeline, null);
+ pipeline = 0;
+ }
+ initInfo.pipelineInfoMain.renderPass = info.renderPass;
+ initInfo.pipelineInfoMain.subpass = info.subpass;
+ initInfo.pipelineInfoMain.msaaSamples = info.msaaSamples;
+ initInfo.pipelineInfoMain.useDynamicRendering = info.useDynamicRendering;
+ initInfo.pipelineInfoMain.colorAttachmentFormat = info.colorAttachmentFormat;
+ initInfo.pipelineInfoMain.depthAttachmentFormat = info.depthAttachmentFormat;
+ pipeline = createPipeline(info);
+ }
+
+ private boolean createDescriptorPool() {
+ final int poolSize = initInfo.descriptorPoolSize > 0
+ ? initInfo.descriptorPoolSize
+ : MINIMUM_SAMPLED_IMAGE_POOL_SIZE + MINIMUM_SAMPLER_POOL_SIZE;
+
+ final VkDescriptorPoolSize.Buffer poolSizes = VkDescriptorPoolSize.calloc(2);
+ poolSizes.get(0).type(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE);
+ poolSizes.get(0).descriptorCount(poolSize);
+ poolSizes.get(1).type(VK_DESCRIPTOR_TYPE_SAMPLER);
+ poolSizes.get(1).descriptorCount(MINIMUM_SAMPLER_POOL_SIZE);
+
+ final VkDescriptorPoolCreateInfo poolInfo = VkDescriptorPoolCreateInfo.calloc();
+ poolInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO);
+ poolInfo.flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT);
+ poolInfo.maxSets(poolSize + MINIMUM_SAMPLER_POOL_SIZE);
+ poolInfo.pPoolSizes(poolSizes);
+
+ final LongBuffer pPool = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateDescriptorPool(initInfo.device, poolInfo, null, pPool);
+ descriptorPool = pPool.get(0);
+ MemoryUtil.memFree(pPool);
+ poolSizes.free();
+ poolInfo.free();
+ checkVkResult(err);
+ return err == VK_SUCCESS;
+ }
+
+ private boolean createSamplers() {
+ {
+ final VkSamplerCreateInfo samplerInfo = VkSamplerCreateInfo.calloc();
+ samplerInfo.sType(VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO);
+ samplerInfo.magFilter(VK_FILTER_LINEAR);
+ samplerInfo.minFilter(VK_FILTER_LINEAR);
+ samplerInfo.mipmapMode(VK_SAMPLER_MIPMAP_MODE_LINEAR);
+ samplerInfo.addressModeU(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.addressModeV(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.addressModeW(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.minLod(-1000);
+ samplerInfo.maxLod(1000);
+
+ final LongBuffer pSampler = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateSampler(initInfo.device, samplerInfo, null, pSampler);
+ samplerLinear = pSampler.get(0);
+ MemoryUtil.memFree(pSampler);
+ samplerInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ {
+ final VkSamplerCreateInfo samplerInfo = VkSamplerCreateInfo.calloc();
+ samplerInfo.sType(VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO);
+ samplerInfo.magFilter(VK_FILTER_NEAREST);
+ samplerInfo.minFilter(VK_FILTER_NEAREST);
+ samplerInfo.mipmapMode(VK_SAMPLER_MIPMAP_MODE_NEAREST);
+ samplerInfo.addressModeU(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.addressModeV(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.addressModeW(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE);
+ samplerInfo.minLod(-1000);
+ samplerInfo.maxLod(1000);
+
+ final LongBuffer pSampler = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateSampler(initInfo.device, samplerInfo, null, pSampler);
+ samplerNearest = pSampler.get(0);
+ MemoryUtil.memFree(pSampler);
+ samplerInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+
+ samplerLinearDS = allocateSamplerDescriptorSet(samplerLinear);
+ if (samplerLinearDS == 0) {
+ return false;
+ }
+ samplerNearestDS = allocateSamplerDescriptorSet(samplerNearest);
+ if (samplerNearestDS == 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private long allocateSamplerDescriptorSet(final long sampler) {
+ final LongBuffer pDescriptorSet = MemoryUtil.memAllocLong(1);
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkDescriptorSetAllocateInfo allocInfo =
+ VkDescriptorSetAllocateInfo.calloc(stack);
+ allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO);
+ allocInfo.descriptorPool(descriptorPool);
+ VkDescriptorSetAllocateInfo.ndescriptorSetCount(allocInfo.address(), 1);
+ allocInfo.pSetLayouts(stack.longs(descriptorSetLayoutSampler));
+
+ int err = VK10.vkAllocateDescriptorSets(initInfo.device, allocInfo, pDescriptorSet);
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ MemoryUtil.memFree(pDescriptorSet);
+ return 0;
+ }
+ }
+
+ final long descriptorSet = pDescriptorSet.get(0);
+ MemoryUtil.memFree(pDescriptorSet);
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ final VkDescriptorImageInfo.Buffer imageInfo =
+ VkDescriptorImageInfo.calloc(1, stack);
+ imageInfo.sampler(sampler);
+
+ final VkWriteDescriptorSet.Buffer writeDesc =
+ VkWriteDescriptorSet.calloc(1, stack);
+ writeDesc.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET);
+ writeDesc.dstSet(descriptorSet);
+ writeDesc.descriptorCount(1);
+ writeDesc.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLER);
+ writeDesc.pImageInfo(imageInfo);
+
+ VK10.vkUpdateDescriptorSets(initInfo.device, writeDesc, null);
+ }
+
+ return descriptorSet;
+ }
+
+ private boolean createTextureUploadCommandPool() {
+ if (texCommandPool == 0) {
+ final VkCommandPoolCreateInfo poolInfo = VkCommandPoolCreateInfo.calloc();
+ poolInfo.sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO);
+ poolInfo.flags(0);
+ poolInfo.queueFamilyIndex(initInfo.queueFamily);
+
+ final LongBuffer pPool = MemoryUtil.memAllocLong(1);
+ int err = VK10.vkCreateCommandPool(initInfo.device, poolInfo, null, pPool);
+ texCommandPool = pPool.get(0);
+ MemoryUtil.memFree(pPool);
+ poolInfo.free();
+ checkVkResult(err);
+ if (err != VK_SUCCESS) {
+ return false;
+ }
+ }
+ if (texCommandBuffer == 0) {
+ final VkCommandBufferAllocateInfo allocInfo =
+ VkCommandBufferAllocateInfo.calloc();
+ allocInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO);
+ allocInfo.commandPool(texCommandPool);
+ allocInfo.commandBufferCount(1);
+
+ final PointerBuffer pCmd = MemoryUtil.memAllocPointer(1);
+ final int err = VK10.vkAllocateCommandBuffers(initInfo.device, allocInfo, pCmd);
+ texCommandBuffer = pCmd.get(0);
+ MemoryUtil.memFree(pCmd);
+ allocInfo.free();
+ checkVkResult(err);
+ return err == VK_SUCCESS;
+ }
+ return true;
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Font Texture
+ // -------------------------------------------------------------------------
+
+ private boolean createFontsTexture() {
+ if (initInfo == null) {
+ return false;
+ }
+ final ImGuiIO io = ImGui.getIO();
+ final ImFontAtlas fontAtlas = io.getFonts();
+
+ final ImInt width = new ImInt();
+ final ImInt height = new ImInt();
+ final ByteBuffer pixels = fontAtlas.getTexDataAsRGBA32(width, height);
+
+ final long uploadSize = (long) width.get() * height.get() * 4;
+ if (uploadSize == 0) {
+ return false;
+ }
+
+ long uploadBuffer;
+ long uploadBufferMemory;
+ {
+ final VkBufferCreateInfo bufferInfo = VkBufferCreateInfo.calloc();
+ bufferInfo.sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO);
+ bufferInfo.size(uploadSize);
+ bufferInfo.usage(VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
+ bufferInfo.sharingMode(VK_SHARING_MODE_EXCLUSIVE);
+
+ final LongBuffer pBuffer = MemoryUtil.memAllocLong(1);
+ int err = VK10.vkCreateBuffer(initInfo.device, bufferInfo, null, pBuffer);
+ uploadBuffer = pBuffer.get(0);
+ MemoryUtil.memFree(pBuffer);
+ bufferInfo.free();
+ checkVkResult(err);
+
+ final VkMemoryRequirements req = VkMemoryRequirements.calloc();
+ VK10.vkGetBufferMemoryRequirements(initInfo.device, uploadBuffer, req);
+
+ final VkMemoryAllocateInfo allocInfo = VkMemoryAllocateInfo.calloc();
+ allocInfo.sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO);
+ allocInfo.allocationSize(req.size());
+ allocInfo.memoryTypeIndex(memoryType(
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits()));
+ req.free();
+
+ final LongBuffer pMemory = MemoryUtil.memAllocLong(1);
+ err = VK10.vkAllocateMemory(initInfo.device, allocInfo, null, pMemory);
+ uploadBufferMemory = pMemory.get(0);
+ MemoryUtil.memFree(pMemory);
+ allocInfo.free();
+ checkVkResult(err);
+
+ err = VK10.vkBindBufferMemory(initInfo.device, uploadBuffer, uploadBufferMemory, 0);
+ checkVkResult(err);
+ }
+
+ {
+ final PointerBuffer pData = MemoryUtil.memAllocPointer(1);
+ int err = VK10.vkMapMemory(initInfo.device, uploadBufferMemory, 0, uploadSize, 0, pData);
+ checkVkResult(err);
+
+ final ByteBuffer mapped = MemoryUtil.memByteBuffer(pData.get(0), (int) uploadSize);
+ mapped.put(pixels);
+ mapped.flip();
+
+ final VkMappedMemoryRange range = VkMappedMemoryRange.calloc();
+ range.sType(VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE);
+ range.memory(uploadBufferMemory);
+ range.size(VK_WHOLE_SIZE);
+ VK10.vkFlushMappedMemoryRanges(initInfo.device, range);
+ range.free();
+
+ VK10.vkUnmapMemory(initInfo.device, uploadBufferMemory);
+ MemoryUtil.memFree(pData);
+ }
+
+ long fontImage;
+ long fontMemory;
+ long fontImageView;
+ long fontDescriptorSet;
+ {
+ final VkImageCreateInfo imageInfo = VkImageCreateInfo.calloc();
+ imageInfo.sType(VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
+ imageInfo.imageType(VK_IMAGE_TYPE_2D);
+ imageInfo.format(VK_FORMAT_R8G8B8A8_UNORM);
+ imageInfo.extent().width(width.get());
+ imageInfo.extent().height(height.get());
+ imageInfo.extent().depth(1);
+ imageInfo.mipLevels(1);
+ imageInfo.arrayLayers(1);
+ imageInfo.samples(VK_SAMPLE_COUNT_1_BIT);
+ imageInfo.tiling(VK_IMAGE_TILING_OPTIMAL);
+ imageInfo.usage(VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
+ imageInfo.sharingMode(VK_SHARING_MODE_EXCLUSIVE);
+ imageInfo.initialLayout(VK_IMAGE_LAYOUT_UNDEFINED);
+
+ final LongBuffer pImage = MemoryUtil.memAllocLong(1);
+ int err = VK10.vkCreateImage(initInfo.device, imageInfo, null, pImage);
+ fontImage = pImage.get(0);
+ MemoryUtil.memFree(pImage);
+ imageInfo.free();
+ checkVkResult(err);
+
+ final VkMemoryRequirements req = VkMemoryRequirements.calloc();
+ VK10.vkGetImageMemoryRequirements(initInfo.device, fontImage, req);
+
+ final VkMemoryAllocateInfo allocInfo = VkMemoryAllocateInfo.calloc();
+ allocInfo.sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO);
+ allocInfo.allocationSize(Math.max(initInfo.minAllocationSize, req.size()));
+ allocInfo.memoryTypeIndex(memoryType(
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, req.memoryTypeBits()));
+ req.free();
+
+ final LongBuffer pMemory = MemoryUtil.memAllocLong(1);
+ err = VK10.vkAllocateMemory(initInfo.device, allocInfo, null, pMemory);
+ fontMemory = pMemory.get(0);
+ MemoryUtil.memFree(pMemory);
+ allocInfo.free();
+ checkVkResult(err);
+
+ err = VK10.vkBindImageMemory(initInfo.device, fontImage, fontMemory, 0);
+ checkVkResult(err);
+ }
+
+ {
+ final VkCommandBuffer texCmd = new VkCommandBuffer(texCommandBuffer, initInfo.device);
+
+ VK10.vkResetCommandPool(initInfo.device, texCommandPool, 0);
+ final VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.calloc();
+ beginInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO);
+ beginInfo.flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
+ int err = VK10.vkBeginCommandBuffer(texCmd, beginInfo);
+ beginInfo.free();
+ checkVkResult(err);
+
+ final VkBufferMemoryBarrier.Buffer uploadBarrier =
+ VkBufferMemoryBarrier.calloc(1);
+ uploadBarrier.sType(VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER);
+ uploadBarrier.srcAccessMask(VK_ACCESS_HOST_WRITE_BIT);
+ uploadBarrier.dstAccessMask(VK_ACCESS_TRANSFER_READ_BIT);
+ uploadBarrier.srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ uploadBarrier.dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ uploadBarrier.buffer(uploadBuffer);
+ uploadBarrier.offset(0);
+ uploadBarrier.size(uploadSize);
+
+ final VkImageMemoryBarrier.Buffer copyBarrier =
+ VkImageMemoryBarrier.calloc(1);
+ copyBarrier.sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER);
+ copyBarrier.dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT);
+ copyBarrier.oldLayout(VK_IMAGE_LAYOUT_UNDEFINED);
+ copyBarrier.newLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ copyBarrier.srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ copyBarrier.dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ copyBarrier.image(fontImage);
+ copyBarrier.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT);
+ copyBarrier.subresourceRange().levelCount(1);
+ copyBarrier.subresourceRange().layerCount(1);
+
+ VK10.vkCmdPipelineBarrier(texCmd,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_HOST_BIT,
+ VK_PIPELINE_STAGE_TRANSFER_BIT, 0, null, uploadBarrier, copyBarrier);
+
+ final VkBufferImageCopy.Buffer region = VkBufferImageCopy.calloc(1);
+ region.imageSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT);
+ region.imageSubresource().layerCount(1);
+ region.imageExtent().width(width.get());
+ region.imageExtent().height(height.get());
+ region.imageExtent().depth(1);
+
+ VK10.vkCmdCopyBufferToImage(texCmd, uploadBuffer, fontImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, region);
+
+ final VkImageMemoryBarrier.Buffer useBarrier =
+ VkImageMemoryBarrier.calloc(1);
+ useBarrier.sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER);
+ useBarrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT);
+ useBarrier.dstAccessMask(VK_ACCESS_SHADER_READ_BIT);
+ useBarrier.oldLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ useBarrier.newLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ useBarrier.srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ useBarrier.dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ useBarrier.image(fontImage);
+ useBarrier.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT);
+ useBarrier.subresourceRange().levelCount(1);
+ useBarrier.subresourceRange().layerCount(1);
+
+ VK10.vkCmdPipelineBarrier(texCmd, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, null, null, useBarrier);
+
+ err = VK10.vkEndCommandBuffer(texCmd);
+ checkVkResult(err);
+
+ final PointerBuffer pCommandBuffers = MemoryUtil.memAllocPointer(1);
+ pCommandBuffers.put(0, texCmd.address());
+ final VkSubmitInfo submitInfo = VkSubmitInfo.calloc();
+ submitInfo.sType(VK_STRUCTURE_TYPE_SUBMIT_INFO);
+ submitInfo.pCommandBuffers(pCommandBuffers);
+ err = VK10.vkQueueSubmit(initInfo.queue, submitInfo, 0);
+ submitInfo.free();
+ MemoryUtil.memFree(pCommandBuffers);
+ checkVkResult(err);
+
+ err = VK10.vkQueueWaitIdle(initInfo.queue);
+ checkVkResult(err);
+
+ region.free();
+ useBarrier.free();
+ copyBarrier.free();
+ uploadBarrier.free();
+ }
+
+ VK10.vkDestroyBuffer(initInfo.device, uploadBuffer, null);
+ VK10.vkFreeMemory(initInfo.device, uploadBufferMemory, null);
+
+ {
+ final VkImageViewCreateInfo viewInfo = VkImageViewCreateInfo.calloc();
+ viewInfo.sType(VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO);
+ viewInfo.image(fontImage);
+ viewInfo.viewType(VK_IMAGE_VIEW_TYPE_2D);
+ viewInfo.format(VK_FORMAT_R8G8B8A8_UNORM);
+ viewInfo.subresourceRange().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT);
+ viewInfo.subresourceRange().levelCount(1);
+ viewInfo.subresourceRange().layerCount(1);
+
+ final LongBuffer pView = MemoryUtil.memAllocLong(1);
+ final int err = VK10.vkCreateImageView(initInfo.device, viewInfo, null, pView);
+ fontImageView = pView.get(0);
+ MemoryUtil.memFree(pView);
+ viewInfo.free();
+ checkVkResult(err);
+ }
+
+ fontDescriptorSet = addTexture(fontImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ if (fontDescriptorSet == 0) {
+ VK10.vkDestroyImageView(initInfo.device, fontImageView, null);
+ VK10.vkDestroyImage(initInfo.device, fontImage, null);
+ VK10.vkFreeMemory(initInfo.device, fontMemory, null);
+ fontImageView = 0;
+ fontImage = 0;
+ fontMemory = 0;
+ return false;
+ }
+ fontAtlas.setTexID(fontDescriptorSet);
+ fontTextureCreated = true;
+ return true;
+ }
+
+ private void destroyFontsTexture() {
+ if (!fontTextureCreated) {
+ return;
+ }
+ fontTextureCreated = false;
+ if (initInfo == null) {
+ return;
+ }
+ final long pool = descriptorPool != 0 ? descriptorPool : initInfo.descriptorPool;
+ if (fontDescriptorSet != 0 && pool != 0) {
+ VK10.vkFreeDescriptorSets(initInfo.device, pool, fontDescriptorSet);
+ fontDescriptorSet = 0;
+ }
+ if (fontImageView != 0) {
+ VK10.vkDestroyImageView(initInfo.device, fontImageView, null);
+ fontImageView = 0;
+ }
+ if (fontImage != 0) {
+ VK10.vkDestroyImage(initInfo.device, fontImage, null);
+ fontImage = 0;
+ }
+ if (fontMemory != 0) {
+ VK10.vkFreeMemory(initInfo.device, fontMemory, null);
+ fontMemory = 0;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Buffer helpers
+ // -------------------------------------------------------------------------
+
+ private void createOrResizeBuffer(final FrameRenderBuffers rb, final boolean isVertex, final long newSize) {
+ final int usage = isVertex
+ ? VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
+ : VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+
+ if (isVertex) {
+ if (rb.vertexBuffer != 0) {
+ VK10.vkDestroyBuffer(initInfo.device, rb.vertexBuffer, null);
+ rb.vertexBuffer = 0;
+ }
+ if (rb.vertexBufferMemory != 0) {
+ VK10.vkFreeMemory(initInfo.device, rb.vertexBufferMemory, null);
+ rb.vertexBufferMemory = 0;
+ }
+ } else {
+ if (rb.indexBuffer != 0) {
+ VK10.vkDestroyBuffer(initInfo.device, rb.indexBuffer, null);
+ rb.indexBuffer = 0;
+ }
+ if (rb.indexBufferMemory != 0) {
+ VK10.vkFreeMemory(initInfo.device, rb.indexBufferMemory, null);
+ rb.indexBufferMemory = 0;
+ }
+ }
+
+ final long bufferSizeAligned = alignBufferSize(
+ Math.max(initInfo.minAllocationSize, newSize), bufferMemoryAlignment);
+
+ final VkBufferCreateInfo bufferInfo = VkBufferCreateInfo.calloc();
+ bufferInfo.sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO);
+ bufferInfo.size(bufferSizeAligned);
+ bufferInfo.usage(usage);
+ bufferInfo.sharingMode(VK_SHARING_MODE_EXCLUSIVE);
+
+ final LongBuffer pBuffer = MemoryUtil.memAllocLong(1);
+ int err = VK10.vkCreateBuffer(initInfo.device, bufferInfo, null, pBuffer);
+ final long buffer = pBuffer.get(0);
+ MemoryUtil.memFree(pBuffer);
+ bufferInfo.free();
+ checkVkResult(err);
+
+ final VkMemoryRequirements req = VkMemoryRequirements.calloc();
+ VK10.vkGetBufferMemoryRequirements(initInfo.device, buffer, req);
+ bufferMemoryAlignment = Math.max(bufferMemoryAlignment, req.alignment());
+
+ final VkMemoryAllocateInfo allocInfo = VkMemoryAllocateInfo.calloc();
+ allocInfo.sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO);
+ allocInfo.allocationSize(req.size());
+ allocInfo.memoryTypeIndex(memoryType(
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits()));
+ req.free();
+
+ final LongBuffer pMemory = MemoryUtil.memAllocLong(1);
+ err = VK10.vkAllocateMemory(initInfo.device, allocInfo, null, pMemory);
+ final long memory = pMemory.get(0);
+ MemoryUtil.memFree(pMemory);
+ allocInfo.free();
+ checkVkResult(err);
+
+ err = VK10.vkBindBufferMemory(initInfo.device, buffer, memory, 0);
+ checkVkResult(err);
+
+ if (isVertex) {
+ rb.vertexBuffer = buffer;
+ rb.vertexBufferMemory = memory;
+ rb.vertexBufferSize = bufferSizeAligned;
+ } else {
+ rb.indexBuffer = buffer;
+ rb.indexBufferMemory = memory;
+ rb.indexBufferSize = bufferSizeAligned;
+ }
+ }
+
+ private long mapMemory(final long memory, final long size) {
+ final PointerBuffer pData = MemoryUtil.memAllocPointer(1);
+ final int err = VK10.vkMapMemory(initInfo.device, memory, 0, size, 0, pData);
+ checkVkResult(err);
+ final long ptr = pData.get(0);
+ MemoryUtil.memFree(pData);
+ return ptr;
+ }
+
+ private void flushMemory(final long memory, final long size) {
+ final VkMappedMemoryRange range = VkMappedMemoryRange.calloc();
+ range.sType(VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE);
+ range.memory(memory);
+ range.size(size);
+ VK10.vkFlushMappedMemoryRanges(initInfo.device, range);
+ range.free();
+ }
+
+ private void destroyFrameRenderBuffers(final FrameRenderBuffers rb) {
+ if (initInfo == null) {
+ return;
+ }
+ if (rb.vertexBuffer != 0) {
+ VK10.vkDestroyBuffer(initInfo.device, rb.vertexBuffer, null);
+ rb.vertexBuffer = 0;
+ }
+ if (rb.vertexBufferMemory != 0) {
+ VK10.vkFreeMemory(initInfo.device, rb.vertexBufferMemory, null);
+ rb.vertexBufferMemory = 0;
+ }
+ if (rb.indexBuffer != 0) {
+ VK10.vkDestroyBuffer(initInfo.device, rb.indexBuffer, null);
+ rb.indexBuffer = 0;
+ }
+ if (rb.indexBufferMemory != 0) {
+ VK10.vkFreeMemory(initInfo.device, rb.indexBufferMemory, null);
+ rb.indexBufferMemory = 0;
+ }
+ rb.vertexBufferSize = 0;
+ rb.indexBufferSize = 0;
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Setup render state
+ // -------------------------------------------------------------------------
+
+ private void setupRenderState(final ImDrawData drawData, final long pipeline, final VkCommandBuffer commandBuffer,
+ final FrameRenderBuffers rb, final int fbWidth, final int fbHeight) {
+ VK10.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+ if (drawData.getTotalVtxCount() > 0) {
+ final long[] vertexBuffers = {rb.vertexBuffer};
+ final long[] offsets = {0};
+ VK10.vkCmdBindVertexBuffers(commandBuffer, 0, vertexBuffers, offsets);
+
+ final int indexType = ImDrawData.sizeOfImDrawIdx() == 2
+ ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32;
+ VK10.vkCmdBindIndexBuffer(commandBuffer, rb.indexBuffer, 0, indexType);
+ }
+
+ final VkViewport.Buffer viewport = VkViewport.calloc(1);
+ viewport.x(0);
+ viewport.y(0);
+ viewport.width(fbWidth);
+ viewport.height(fbHeight);
+ viewport.minDepth(0.0f);
+ viewport.maxDepth(1.0f);
+ VK10.vkCmdSetViewport(commandBuffer, 0, viewport);
+ viewport.free();
+
+ VK10.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ pipelineLayout, 1, new long[] {samplerLinearDS}, new int[0]);
+ }
+
+ // -------------------------------------------------------------------------
+ // Private: Vulkan helpers
+ // -------------------------------------------------------------------------
+
+ private void checkVkResult(final int err) {
+ if (initInfo != null && initInfo.checkVkResultFn != null) {
+ initInfo.checkVkResultFn.accept(err);
+ }
+ }
+
+ private int memoryType(final int properties, final int typeBits) {
+ final VkPhysicalDeviceMemoryProperties prop = VkPhysicalDeviceMemoryProperties.calloc();
+ VK10.vkGetPhysicalDeviceMemoryProperties(initInfo.physicalDevice, prop);
+ for (int i = 0; i < prop.memoryTypeCount(); i++) {
+ if ((prop.memoryTypes(i).propertyFlags() & properties) == properties
+ && (typeBits & (1 << i)) != 0) {
+ prop.free();
+ return i;
+ }
+ }
+ prop.free();
+ return -1;
+ }
+
+ private static long alignBufferSize(final long size, final long alignment) {
+ return (size + alignment - 1) & ~(alignment - 1);
+ }
+
+ private void deepCopyInitInfo(final InitInfo src) {
+ initInfo = new InitInfo();
+ initInfo.apiVersion = src.apiVersion;
+ initInfo.instance = src.instance;
+ initInfo.physicalDevice = src.physicalDevice;
+ initInfo.device = src.device;
+ initInfo.queueFamily = src.queueFamily;
+ initInfo.queue = src.queue;
+ initInfo.descriptorPool = src.descriptorPool;
+ initInfo.descriptorPoolSize = src.descriptorPoolSize;
+ initInfo.minImageCount = src.minImageCount;
+ initInfo.imageCount = src.imageCount;
+ initInfo.pipelineCache = src.pipelineCache;
+ initInfo.pipelineInfoMain.renderPass = src.pipelineInfoMain.renderPass;
+ initInfo.pipelineInfoMain.subpass = src.pipelineInfoMain.subpass;
+ initInfo.pipelineInfoMain.msaaSamples = src.pipelineInfoMain.msaaSamples;
+ initInfo.pipelineInfoMain.extraDynamicStates
+ .addAll(src.pipelineInfoMain.extraDynamicStates);
+ initInfo.pipelineInfoMain.useDynamicRendering =
+ src.pipelineInfoMain.useDynamicRendering;
+ initInfo.pipelineInfoMain.colorAttachmentFormat =
+ src.pipelineInfoMain.colorAttachmentFormat;
+ initInfo.pipelineInfoMain.depthAttachmentFormat =
+ src.pipelineInfoMain.depthAttachmentFormat;
+ initInfo.useDynamicRendering = src.useDynamicRendering;
+ initInfo.pipelineCreateFlags = src.pipelineCreateFlags;
+ initInfo.minAllocationSize = src.minAllocationSize;
+ initInfo.checkVkResultFn = src.checkVkResultFn;
+ initInfo.customShaderVert = src.customShaderVert;
+ initInfo.customShaderVertCode = src.customShaderVertCode;
+ initInfo.customShaderFrag = src.customShaderFrag;
+ initInfo.customShaderFragCode = src.customShaderFragCode;
+ }
+}
diff --git a/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkanH.java b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkanH.java
new file mode 100644
index 00000000..458529fd
--- /dev/null
+++ b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkanH.java
@@ -0,0 +1,86 @@
+package imgui.vulkan;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_LOAD_OP_CLEAR;
+import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_STORE_OP_DONT_CARE;
+import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_STORE_OP_STORE;
+import static org.lwjgl.vulkan.VK10.VK_FORMAT_UNDEFINED;
+import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED;
+import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT;
+
+/**
+ * Java port of the {@code ImGui_ImplVulkanH_XXX} helper structures from
+ * {@code imgui_impl_vulkan.h}.
+ */
+public final class ImGuiImplVulkanH {
+
+ private ImGuiImplVulkanH() { }
+
+ /** VK_PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF (not exposed by LWJGL) */
+ static final int PRESENT_MODE_MAX_ENUM_KHR = 0x7FFFFFFF;
+
+ public static final class Frame {
+ public long commandPool;
+ public long commandBuffer;
+ public long fence;
+ public long backbuffer;
+ public long backbufferView;
+ public long framebuffer;
+ }
+
+ public static final class FrameSemaphores {
+ public long imageAcquiredSemaphore;
+ public long renderCompleteSemaphore;
+ }
+
+ public static final class Window {
+ public boolean useDynamicRendering;
+ public long surface;
+
+ public int surfaceFormat;
+ public int presentMode;
+
+ public int attachmentFormat;
+ public int attachmentSamples;
+ public int attachmentLoadOp;
+ public int attachmentStoreOp;
+ public int attachmentStencilLoadOp;
+ public int attachmentStencilStoreOp;
+ public int attachmentInitialLayout;
+ public int attachmentFinalLayout;
+
+ public float[] clearValue = new float[4];
+
+ public int width;
+ public int height;
+ public long swapchain;
+ public boolean swapChainRebuild;
+ public long renderPass;
+ public long pipeline;
+ public int frameIndex;
+ public int imageCount;
+ public int semaphoreCount;
+ public int semaphoreIndex;
+ public final List images = new ArrayList();
+ public final List imageViews = new ArrayList();
+ public final List framebuffers = new ArrayList();
+ public final List frames = new ArrayList();
+ public final List frameSemaphores = new ArrayList();
+
+ public Window() {
+ presentMode = PRESENT_MODE_MAX_ENUM_KHR;
+ attachmentFormat = VK_FORMAT_UNDEFINED;
+ attachmentSamples = VK_SAMPLE_COUNT_1_BIT;
+ attachmentLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ attachmentStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attachmentStencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ attachmentStencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ attachmentInitialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attachmentFinalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ }
+ }
+}
diff --git a/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_fragment.bin b/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_fragment.bin
new file mode 100644
index 00000000..cef07a8f
Binary files /dev/null and b/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_fragment.bin differ
diff --git a/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_vertex.bin b/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_vertex.bin
new file mode 100644
index 00000000..1c95b69f
Binary files /dev/null and b/imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_vertex.bin differ