From e5f417458f292a0ae036269c000078ed34845996 Mon Sep 17 00:00:00 2001 From: Enaium Date: Tue, 4 Aug 2026 13:02:25 +0800 Subject: [PATCH] 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!<@|