From e5f417458f292a0ae036269c000078ed34845996 Mon Sep 17 00:00:00 2001 From: Enaium Date: Tue, 4 Aug 2026 13:02:25 +0800 Subject: [PATCH 1/3] feat(lwjgl3): add Vulkan renderer backend Port of the Dear ImGui Vulkan renderer (imgui_impl_vulkan) into the imgui-lwjgl3 module: - ImGuiImplVulkan renderer with descriptor sets, pipeline creation and font atlas upload; shader SPIR-V shipped as classpath resources - ImGuiImplVulkanH helper structures - createVulkanSurface() helpers for the GLFW and SDL3 backends - imgui-app: VULKAN backend with WindowVulkan (swapchain, render pass, per-frame sync objects and resize handling) - example: ExampleVulkan smoke test Co-authored-by: DeepSeek --- example/src/main/java/ExampleVulkan.java | 29 + imgui-app/build.gradle | 5 + .../src/main/java/imgui/app/Application.java | 13 +- .../src/main/java/imgui/app/Backend.java | 6 +- .../src/main/java/imgui/app/WindowVulkan.java | 721 ++++++++ imgui-lwjgl3/build.gradle | 1 + .../main/java/imgui/glfw/ImGuiImplGlfw.java | 40 + .../main/java/imgui/sdl3/ImGuiImplSdl3.java | 33 + .../java/imgui/vulkan/ImGuiImplVulkan.java | 1645 +++++++++++++++++ .../java/imgui/vulkan/ImGuiImplVulkanH.java | 86 + .../imgui/vulkan/shaders/spirv_fragment.bin | Bin 0 -> 900 bytes .../imgui/vulkan/shaders/spirv_vertex.bin | Bin 0 -> 1296 bytes 12 files changed, 2577 insertions(+), 2 deletions(-) create mode 100644 example/src/main/java/ExampleVulkan.java create mode 100644 imgui-app/src/main/java/imgui/app/WindowVulkan.java create mode 100644 imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java create mode 100644 imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkanH.java create mode 100644 imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_fragment.bin create mode 100644 imgui-lwjgl3/src/main/resources/imgui/vulkan/shaders/spirv_vertex.bin 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 0000000000000000000000000000000000000000..cef07a8faaa609be60680074cfa97c46176713b9 GIT binary patch literal 900 zcmYk4+e$)F5QZnWr%oO+HMMM;mw|Lq5kx_BA%S<6U51cBpc!V+3-n?=RX0KZx7HH& z#5n8!=RCV8)yKYhKEh5@X`B&h`hxLGNSv+&euxA);WB3nZp!IZMl;v+?U2 zj&&4rcw(vkj1dsgcCg>EM*$2Ol})F*TEZFn;E$Xj}H} zxXsPIYkFtJ#nQU!aThSRi1nM7<+AXX0dp5P;x}Z8r*2!AyATUc{+2Ag;mHTH6I@le zAR7mK9B@$>A-^O`EHxYYA{ANs!6jvd$=MAKV(AIyPDoRe)@6y?a}0a`RG(r!F%bN~z0i4uCAU&y?Z8vT38NCp%==v9_k}r!u{{D14okgq}G0*#O zY<(l1@orH>Ezim;vN_MqA+}Jvg8zB^aWI@64`;>jJ0WO^>zkX-N5yD5iMsadvPb*Q z$(C_DbZ{1f#r(_ubTTjUNkMGC;;zNfNj@Hy`eOFGct6W0^Ko8)+gk?q?IeUg`pzlU z8ha08=Va_Xl1`sm?_%yPO8PD}T=MPrRIl-0OMb$;kzHyeZLuY619kOxILjB--@NoIH{!-=JkM+jRHRZSQtvBDnbFONseU;sun6<`!A7V+%nXBd9ou@kK z?lm-r}E|7yK}sUxl(*TMn7IZ;|-m$b9dRpACMONOHg}W!<@| Date: Thu, 6 Aug 2026 16:14:46 +0800 Subject: [PATCH 2/3] refactor(lwjgl3): align Vulkan descriptor layout with upstream 1.92.7 Match the pinned Dear ImGui 1.92.7 backend: single descriptor set with COMBINED_IMAGE_SAMPLER at set 0, one pool size, one bind per draw, and the upstream shader bytecode shipped as resources. Drops the separate sampler descriptor set, the deprecated addTexture overload and the second pool-size constant. Co-authored-by: DeepSeek --- .../java/imgui/vulkan/ImGuiImplVulkan.java | 260 ++++-------------- .../imgui/vulkan/shaders/spirv_fragment.bin | Bin 900 -> 772 bytes .../imgui/vulkan/shaders/spirv_vertex.bin | Bin 1296 -> 1296 bytes 3 files changed, 56 insertions(+), 204 deletions(-) diff --git a/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java index c90f9b9e..22facd40 100644 --- a/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java +++ b/imgui-lwjgl3/src/main/java/imgui/vulkan/ImGuiImplVulkan.java @@ -83,12 +83,10 @@ 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_DESCRIPTOR_TYPE_COMBINED_IMAGE_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; @@ -114,7 +112,6 @@ 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; @@ -160,8 +157,7 @@ @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 static final int MINIMUM_IMAGE_SAMPLER_POOL_SIZE = 8; // ------------------------------------------------------------------------- // Public types @@ -242,8 +238,7 @@ private static final class WindowRenderBuffers { private InitInfo initInfo; private long bufferMemoryAlignment = 256; private long nonCoherentAtomSize = 64; - private long descriptorSetLayoutTexture; - private long descriptorSetLayoutSampler; + private long descriptorSetLayout; private long pipelineLayout; private long pipeline; private int pipelineCreateFlags; @@ -252,9 +247,6 @@ private static final class WindowRenderBuffers { 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; @@ -308,7 +300,7 @@ public void setMinImageCount(final int minImageCount) { // Texture management // ------------------------------------------------------------------------- - public long addTexture(final long imageView, final int imageLayout) { + public long addTexture(final long sampler, final long imageView, final int imageLayout) { if (initInfo == null) { return 0; } @@ -324,7 +316,7 @@ public long addTexture(final long imageView, final int imageLayout) { allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); allocInfo.descriptorPool(pool); VkDescriptorSetAllocateInfo.ndescriptorSetCount(allocInfo.address(), 1); - allocInfo.pSetLayouts(stack.longs(descriptorSetLayoutTexture)); + allocInfo.pSetLayouts(stack.longs(descriptorSetLayout)); int err = VK10.vkAllocateDescriptorSets(initInfo.device, allocInfo, pDescriptorSet); checkVkResult(err); @@ -334,6 +326,7 @@ public long addTexture(final long imageView, final int imageLayout) { final long descriptorSet = pDescriptorSet.get(0); final VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.calloc(1, stack); + imageInfo.sampler(sampler); imageInfo.imageView(imageView); imageInfo.imageLayout(imageLayout); @@ -341,7 +334,7 @@ public long addTexture(final long imageView, final int imageLayout) { writeDesc.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); writeDesc.dstSet(descriptorSet); writeDesc.descriptorCount(1); - writeDesc.descriptorType(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); + writeDesc.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); writeDesc.pImageInfo(imageInfo); VK10.vkUpdateDescriptorSets(initInfo.device, writeDesc, null); @@ -361,11 +354,6 @@ public void removeTexture(final long descriptorSet) { VK10.vkFreeDescriptorSets(initInfo.device, pool, descriptorSet); } - @Deprecated - public long addTexture(final long sampler, final long imageView, final int imageLayout) { - return addTexture(imageView, imageLayout); - } - // ------------------------------------------------------------------------- // Render // ------------------------------------------------------------------------- @@ -562,18 +550,6 @@ private void destroyDeviceObjects() { } 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; @@ -586,13 +562,9 @@ private void destroyDeviceObjects() { 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 (descriptorSetLayout != 0) { + VK10.vkDestroyDescriptorSetLayout(initInfo.device, descriptorSetLayout, null); + descriptorSetLayout = 0; } if (descriptorPoolOwned && descriptorPool != 0) { VK10.vkDestroyDescriptorPool(initInfo.device, descriptorPool, null); @@ -709,65 +681,32 @@ private boolean createShaderModules() { } 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; + final VkDescriptorSetLayoutBinding.Buffer bindings = + VkDescriptorSetLayoutBinding.calloc(1); + bindings.binding(0); + bindings.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_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); + descriptorSetLayout = pLayout.get(0); + MemoryUtil.memFree(pLayout); + bindings.free(); + layoutInfo.free(); + checkVkResult(err); + return err == VK_SUCCESS; } private boolean createPipelineLayout() { try (MemoryStack stack = MemoryStack.stackPush()) { - final LongBuffer setLayouts = stack.longs( - descriptorSetLayoutTexture, descriptorSetLayoutSampler); + final LongBuffer setLayouts = stack.longs(descriptorSetLayout); final VkPushConstantRange.Buffer pushConstantRange = VkPushConstantRange.calloc(1, stack); @@ -778,7 +717,7 @@ private boolean createPipelineLayout() { final VkPipelineLayoutCreateInfo layoutInfo = VkPipelineLayoutCreateInfo.calloc(stack); layoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); - layoutInfo.setLayoutCount(2); + layoutInfo.setLayoutCount(1); layoutInfo.pSetLayouts(setLayouts); VkPipelineLayoutCreateInfo.npushConstantRangeCount(layoutInfo.address(), 1); layoutInfo.pPushConstantRanges(pushConstantRange); @@ -995,18 +934,16 @@ public void createMainPipeline(final PipelineInfo info) { private boolean createDescriptorPool() { final int poolSize = initInfo.descriptorPoolSize > 0 ? initInfo.descriptorPoolSize - : MINIMUM_SAMPLED_IMAGE_POOL_SIZE + MINIMUM_SAMPLER_POOL_SIZE; + : MINIMUM_IMAGE_SAMPLER_POOL_SIZE; - final VkDescriptorPoolSize.Buffer poolSizes = VkDescriptorPoolSize.calloc(2); - poolSizes.get(0).type(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); + final VkDescriptorPoolSize.Buffer poolSizes = VkDescriptorPoolSize.calloc(1); + poolSizes.get(0).type(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 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.maxSets(poolSize); poolInfo.pPoolSizes(poolSizes); final LongBuffer pPool = MemoryUtil.memAllocLong(1); @@ -1020,103 +957,24 @@ private boolean createDescriptorPool() { } 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; + 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); + return err == VK_SUCCESS; } private boolean createTextureUploadCommandPool() { @@ -1231,10 +1089,6 @@ private boolean createFontsTexture() { MemoryUtil.memFree(pData); } - long fontImage; - long fontMemory; - long fontImageView; - long fontDescriptorSet; { final VkImageCreateInfo imageInfo = VkImageCreateInfo.calloc(); imageInfo.sType(VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO); @@ -1388,7 +1242,7 @@ private boolean createFontsTexture() { checkVkResult(err); } - fontDescriptorSet = addTexture(fontImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + fontDescriptorSet = addTexture(samplerLinear, fontImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); if (fontDescriptorSet == 0) { VK10.vkDestroyImageView(initInfo.device, fontImageView, null); VK10.vkDestroyImage(initInfo.device, fontImage, null); @@ -1577,8 +1431,6 @@ private void setupRenderState(final ImDrawData drawData, final long pipeline, fi VK10.vkCmdSetViewport(commandBuffer, 0, viewport); viewport.free(); - VK10.vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipelineLayout, 1, new long[] {samplerLinearDS}, new int[0]); } // ------------------------------------------------------------------------- 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 index cef07a8faaa609be60680074cfa97c46176713b9..d27b1dca9a4358a9620b0a37873c9319f382b378 100644 GIT binary patch delta 246 zcmZo+Z(-wQW>RKnU|P4iCIJWARQo}4dn9yX$c@^1Y(eS$;nHZ{BuC6 z#eo>4(vg9cK@vzy0WmKSgH+oBRdNAoX&?rPhXd6BUCkf^#2|ej@m8pqERZb(#2`64 RpzAn+G)U|(&`t{=1^}Yi69fPN delta 397 zcmYjNI|{-;6r3dfC4&FICfW%0Vh{x@51?o%7U>j36zpvD0OFy%hMlJnoQWyiWq3RD z=FMhvja}omWp?9$fix26-tDJT1h8KC=i}+%^N1*SRoZ;B+i(5D{nhj2an#1YACGrD@e+5r8uK>}6P+gh6Pw z6`oV91HQs!jR23e>0LBaB6R1Ns_v^1%3ji|+y)_!^eHvvwF#ThJ(^RtLr7oI!+#mP aT(b^w3pKqyN4UfURu)MfB!2k0L*fg_M;agi 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 index 1c95b69f697c2d046de6f0fc80b389e726869752..6f3626605fb12a0911f76d3eaa2d46172b0219c1 100644 GIT binary patch delta 65 zcmbQhHGzwhnMs+Qfq{{MaUrb4#l)EzBpDbOn1I-wfrUW^NXr2+!{mpI+MMnT N%wQhVW=2fP3P delta 85 zcmbQhHGzwhnMs+Qfq{{Mdn4z3MoV`FW(G+H1_mY|_F!NIvw=(oZV&+k?hGspGC*1m V%meY6!EC0 Date: Thu, 6 Aug 2026 16:14:46 +0800 Subject: [PATCH 3/3] fix(lwjgl3): fix font texture leak and star imports in Vulkan backend createFontsTexture() declared locals shadowing the fontImage/fontMemory/ fontImageView/fontDescriptorSet fields, so destroyFontsTexture() never released the atlas resources. Also expand the wildcard Vulkan imports in WindowVulkan to satisfy AvoidStarImport. Co-authored-by: DeepSeek --- .../src/main/java/imgui/app/WindowVulkan.java | 82 ++++++++++++++++++- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/imgui-app/src/main/java/imgui/app/WindowVulkan.java b/imgui-app/src/main/java/imgui/app/WindowVulkan.java index 7019c9d3..5a79e502 100644 --- a/imgui-app/src/main/java/imgui/app/WindowVulkan.java +++ b/imgui-app/src/main/java/imgui/app/WindowVulkan.java @@ -10,16 +10,90 @@ import org.lwjgl.glfw.GLFWVulkan; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; -import org.lwjgl.vulkan.*; +import org.lwjgl.vulkan.KHRSurface; +import org.lwjgl.vulkan.KHRSwapchain; +import org.lwjgl.vulkan.VK10; +import org.lwjgl.vulkan.VkApplicationInfo; +import org.lwjgl.vulkan.VkAttachmentDescription; +import org.lwjgl.vulkan.VkAttachmentReference; +import org.lwjgl.vulkan.VkClearValue; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferAllocateInfo; +import org.lwjgl.vulkan.VkCommandBufferBeginInfo; +import org.lwjgl.vulkan.VkCommandPoolCreateInfo; +import org.lwjgl.vulkan.VkDevice; +import org.lwjgl.vulkan.VkDeviceCreateInfo; +import org.lwjgl.vulkan.VkDeviceQueueCreateInfo; +import org.lwjgl.vulkan.VkFenceCreateInfo; +import org.lwjgl.vulkan.VkFramebufferCreateInfo; +import org.lwjgl.vulkan.VkImageViewCreateInfo; +import org.lwjgl.vulkan.VkInstance; +import org.lwjgl.vulkan.VkInstanceCreateInfo; +import org.lwjgl.vulkan.VkPhysicalDevice; +import org.lwjgl.vulkan.VkPresentInfoKHR; +import org.lwjgl.vulkan.VkQueue; +import org.lwjgl.vulkan.VkQueueFamilyProperties; +import org.lwjgl.vulkan.VkRenderPassBeginInfo; +import org.lwjgl.vulkan.VkRenderPassCreateInfo; +import org.lwjgl.vulkan.VkSemaphoreCreateInfo; +import org.lwjgl.vulkan.VkSubmitInfo; +import org.lwjgl.vulkan.VkSubpassDependency; +import org.lwjgl.vulkan.VkSubpassDescription; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSurfaceFormatKHR; +import org.lwjgl.vulkan.VkSwapchainCreateInfoKHR; 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.*; +import static org.lwjgl.vulkan.KHRSurface.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_FIFO_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_IMMEDIATE_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_MAILBOX_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_ERROR_OUT_OF_DATE_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_SUBOPTIMAL_KHR; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_API_VERSION_1_0; +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_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; +import static org.lwjgl.vulkan.VK10.VK_FENCE_CREATE_SIGNALED_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_GRAPHICS; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +import static org.lwjgl.vulkan.VK10.VK_QUEUE_GRAPHICS_BIT; +import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_APPLICATION_INFO; +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_DEVICE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SUBMIT_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUBPASS_CONTENTS_INLINE; +import static org.lwjgl.vulkan.VK10.VK_SUBPASS_EXTERNAL; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; /** * GLFW + Vulkan implementation of {@link Window}.