diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d15b4fa0..3f7fdd04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,8 @@ jobs: - if: matrix.os == 'ubuntu-latest' name: Install MinGW-w64/GCC/G++ - run: sudo apt install mingw-w64 + run: | + sudo apt install mingw-w64 - if: matrix.os == 'ubuntu-latest' && matrix.type == 'linux' && matrix.freetype == true name: FreeType - Install diff --git a/.gitmodules b/.gitmodules index 0923cb14..0cb98cf8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,9 @@ [submodule "include/imgui_club"] path = include/imgui_club url = https://github.com/ocornut/imgui_club +[submodule "include/Vulkan-Headers"] + path = include/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers +[submodule "include/glfw"] + path = include/glfw + url = https://github.com/glfw/glfw diff --git a/README.md b/README.md index fd229d38..a6c4d4b8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ JNI based binding for [Dear ImGui](https://github.com/ocornut/imgui) with no dep Read official [documentation](https://github.com/ocornut/imgui#usage) and [wiki](https://github.com/ocornut/imgui/wiki) to see how to work with Dear ImGui. Almost everything from C++ could be done in Java in the same way. -Binding has an OpenGL renderer and a GLFW backend implementation, using a [LWJGL3](https://www.lwjgl.org/) library. Could be found in [imgui-lwjgl3](https://github.com/SpaiR/imgui-java/blob/v1.86.3/imgui-lwjgl3) module.
+Binding has an OpenGL/Vulkan renderer and a GLFW backendType implementation, using a [LWJGL3](https://www.lwjgl.org/) library. Could be found in [imgui-lwjgl3](https://github.com/SpaiR/imgui-java/blob/v1.86.3/imgui-lwjgl3) module.
They are recommended, yet optional to use. The advantage of Dear ImGui is a portability, so feel free to copy-paste classes or write your own implementations.
Additionally, there is an [imgui-app](https://github.com/SpaiR/imgui-java/blob/v1.86.3/imgui-app) module, which provides **a high abstraction layer**.
@@ -53,9 +53,10 @@ You can use this video as a basic step-by-step tutorial. It shows how to integra Take a note, that integration itself is a very flexible process. It could be done in one way or another. If you just need a framework for your GUI - use **Application** module. Otherwise, if you need more control, the best way is not just to repeat steps, but to understand what each step does. ## Application -If you don't care about OpenGL or other low-level stuff, then you can use application layer from [imgui-app](https://github.com/SpaiR/imgui-java/blob/v1.86.3/imgui-app) module. -It is a **one jar solution** which includes: GLFW, OpenGL and Dear ImGui itself. So you only need **one dependency** line or **one jar in classpath** to make everything to work. You don't need to add separate dependencies to LWJGL or native libraries, since they are already included.
-Application module is the best choice if everything you care is the GUI for your app. +If you don't care about OpenGL, Vulkan or other low-level stuff, then you can use application layer from [imgui-app](https://github.com/SpaiR/imgui-java/blob/v1.86.3/imgui-app) module. +It is a **one jar solution** which includes: GLFW, OpenGL, Vulkan and Dear ImGui itself. So you only need **one dependency** line or **one jar in classpath** to make everything to work. You don't need to add separate dependencies to LWJGL or native libraries, since they are already included.
+Application module is the best choice if everything you care is the GUI for your app. +Note on Vulkan: No vulkan natives are provided since these are provided by your GPU driver. At the same time, Application gives an option to override any life-cycle method it has. That means that if you're seeking for a bit more low-level control - you can gain it too. @@ -69,6 +70,9 @@ public class Main extends Application { @Override protected void configure(Configuration config) { config.setTitle("Dear ImGui is Awesome!"); + + //Optional, by default it will use opengl + config.setBackendType(Backend.OPENGL); //Set to Backend.VULKAN for vulkan } @Override @@ -355,20 +359,28 @@ Ensure you've downloaded git submodules. That could be achieved: * Mingw-w64 (recommended way: use [MSYS2](https://www.msys2.org/) and install [mingw-w64-x86_64-toolchain](https://packages.msys2.org/group/mingw-w64-x86_64-toolchain) group) - Build with: `./gradlew :imgui-binding:generateLibs -Denvs=win -Dlocal` - Run with: `./gradlew :example:run -PlibPath="../imgui-binding/build/libsNative/windows64"` + - Or run with Vulkan: `./gradlew :example:run -PlibPath="../imgui-binding/build/libsNative/windows64" -PuseVulkan=true` ### Linux - Install dependencies: `openjdk8`, `mingw-w64-gcc`, `ant`. (Package names could vary from system to system.) - Build with: `./gradlew :imgui-binding:generateLibs -Denvs=linux -Dlocal` - Run with: `./gradlew :example:run -PlibPath=../imgui-binding/build/libsNative/linux64` +- Or run with Vulkan: `./gradlew :example:run -PlibPath="../imgui-binding/build/libsNative/linux64" -PuseVulkan=true` ### MacOS - Check dependencies from "Linux" section and make sure you have them installed. - Build with: `./gradlew :imgui-binding:generateLibs -Denvs=mac -Dlocal` - Run with: `./gradlew :example:run -PlibPath=../imgui-binding/build/libsNative/macosx64` +- Or run with Vulkan: `./gradlew :example:run -PlibPath="../imgui-binding/build/libsNative/macosx64" -PuseVulkan=true` In `envs` parameter next values could be used `win`, `linux` or `mac`.
`-Dlocal` is optional and means that natives will be built under the `./imgui-binding/build/` folder. Otherwise `/tmp/imgui` folder will be used. On Windows always use local build. +### Vulkan bindings +Vulkan Loader binaries are stored in `/bin/vulkan/...` for each platform. These binaries are used to link against during +the build process when building the native libraries. To link against a different version of the vulkan loader, simply +replace the binary for your platform in `/bin/vulkan`. These binaries are not included in the distibution. + # License See the LICENSE file for license rights and limitations (Apache-2.0). diff --git a/bin/glfw/LINUX/libglfw.so b/bin/glfw/LINUX/libglfw.so new file mode 100644 index 00000000..f8c17be3 Binary files /dev/null and b/bin/glfw/LINUX/libglfw.so differ diff --git a/bin/glfw/MACOS/libglfw.dylib b/bin/glfw/MACOS/libglfw.dylib new file mode 100644 index 00000000..76b89ff0 Binary files /dev/null and b/bin/glfw/MACOS/libglfw.dylib differ diff --git a/bin/glfw/MACOS/libglfw_async.dylib b/bin/glfw/MACOS/libglfw_async.dylib new file mode 100644 index 00000000..f747347e Binary files /dev/null and b/bin/glfw/MACOS/libglfw_async.dylib differ diff --git a/bin/glfw/WINDOWS/glfw.dll b/bin/glfw/WINDOWS/glfw.dll new file mode 100644 index 00000000..5f8d072f Binary files /dev/null and b/bin/glfw/WINDOWS/glfw.dll differ diff --git a/bin/vulkan/LINUX/libvulkan.so.1 b/bin/vulkan/LINUX/libvulkan.so.1 new file mode 100644 index 00000000..ed695ba5 Binary files /dev/null and b/bin/vulkan/LINUX/libvulkan.so.1 differ diff --git a/bin/vulkan/MACOS/libvulkan.1.3.204.dylib b/bin/vulkan/MACOS/libvulkan.1.3.204.dylib new file mode 100644 index 00000000..33379069 Binary files /dev/null and b/bin/vulkan/MACOS/libvulkan.1.3.204.dylib differ diff --git a/bin/vulkan/MACOS/libvulkan.1.dylib b/bin/vulkan/MACOS/libvulkan.1.dylib new file mode 100644 index 00000000..33379069 Binary files /dev/null and b/bin/vulkan/MACOS/libvulkan.1.dylib differ diff --git a/bin/vulkan/MACOS/libvulkan.dylib b/bin/vulkan/MACOS/libvulkan.dylib new file mode 100644 index 00000000..33379069 Binary files /dev/null and b/bin/vulkan/MACOS/libvulkan.dylib differ diff --git a/bin/vulkan/WINDOWS/vulkan-1.dll b/bin/vulkan/WINDOWS/vulkan-1.dll new file mode 100644 index 00000000..dba9435d Binary files /dev/null and b/bin/vulkan/WINDOWS/vulkan-1.dll differ diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 679f2dbe..17294766 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -7,7 +7,7 @@ repositories { } ext { - jnigenVersion = '2.0.1' + jnigenVersion = '2.3.1' } dependencies { diff --git a/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy b/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy index bb2d3234..9b950b97 100644 --- a/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy +++ b/buildSrc/src/main/groovy/tool/generator/GenerateLibs.groovy @@ -7,6 +7,8 @@ import org.gradle.api.file.CopySpec import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction +import org.gradle.internal.os.OperatingSystem +import org.gradle.process.ExecSpec @CompileStatic class GenerateLibs extends DefaultTask { @@ -27,6 +29,8 @@ class GenerateLibs extends DefaultTask { private final String classpath = project.file('build/classes/java/main') private final String jniDir = (isLocal ? project.buildDir.path : '/tmp/imgui') + '/jni' private final String tmpFolder = (isLocal ? project.buildDir.path : '/tmp/imgui') + '/tmp' + private final String vulkanDir = project.rootProject.file('bin/vulkan') + private final String glfwDir = project.rootProject.file('bin/glfw') private final String libsFolder = 'libsNative' @TaskAction @@ -35,6 +39,8 @@ class GenerateLibs extends DefaultTask { println "Build targets: $buildEnvs" println "Local: $isLocal" println "FreeType: $withFreeType" + println "Vulkan Libs: $vulkanDir" + println "GLFW Libs: $glfwDir" println '=====================================' if (!buildEnvs) { @@ -42,8 +48,9 @@ class GenerateLibs extends DefaultTask { return } + println "Generating C/C++ jni bindings" // Generate h/cpp files for JNI - new NativeCodeGenerator().generate(sourceDir, classpath, jniDir) + new NativeCodeGenerator().generate(sourceDir, classpath, jniDir, null, new String[]{"**/package-info.java"}) // Copy ImGui h/cpp files project.copy { CopySpec spec -> @@ -71,6 +78,34 @@ class GenerateLibs extends DefaultTask { spec.into(jniDir + '/dirent') } + //Copy glfw + project.copy { CopySpec spec -> + ['include/glfw/include/GLFW'].each { + spec.from(project.rootProject.file(it)) { CopySpec s -> s.include('*.h') } + } + spec.into(jniDir + "/GLFW") + } + + //Copy vulkan + project.copy { CopySpec spec -> + ['include/Vulkan-Headers/include/vulkan'].each { + spec.from(project.rootProject.file(it)) { CopySpec s -> s.include('*.h', "*.hpp", '*.cpp', '*.inl') } + } + spec.into(jniDir + "/vulkan") + } + + //Copy backends + def backendsToCopy = ['imgui_impl_vulkan*', 'imgui_impl_glfw*'] + project.copy { CopySpec spec -> + ['include/imgui/backends'].each { + spec.from(project.rootProject.file(it)) { + CopySpec s -> s.include(backendsToCopy) + } + } + + spec.into(jniDir + "/backends") + } + if (withFreeType) { project.copy { CopySpec spec -> spec.from(project.rootProject.file('include/imgui/misc/freetype')) { CopySpec it -> it.include('*.h', '*.cpp') } @@ -80,6 +115,7 @@ class GenerateLibs extends DefaultTask { enableDefine('IMGUI_ENABLE_FREETYPE') } + println "Building natives" // Generate platform dependant ant configs and header files def buildConfig = new BuildConfig('imgui-java', tmpFolder, libsFolder, jniDir) def buildTargets = [] as BuildTarget[] @@ -87,12 +123,34 @@ class GenerateLibs extends DefaultTask { if (forWindows) { def win64 = BuildTarget.newDefaultTarget(BuildTarget.TargetOs.Windows, true) addFreeTypeIfEnabled(win64) + + //Add vulkan for linker + win64.linkerFlags += " -L $vulkanDir/WINDOWS" + win64.libraries += ' -l:vulkan-1.dll' + + //Add glfw for linker + win64.linkerFlags += " -L $glfwDir/WINDOWS" + win64.libraries += ' -l:glfw.dll' + + //Win Api + win64.libraries += ' -limm32' + win64.libraries += ' -ldwmapi' + buildTargets += win64 } if (forLinux) { def linux64 = BuildTarget.newDefaultTarget(BuildTarget.TargetOs.Linux, true) addFreeTypeIfEnabled(linux64) + + //Vulkan + linux64.linkerFlags += " -L $vulkanDir/LINUX" + linux64.libraries += ' -l:libvulkan.so.1' + + //Add glfw for linker + linux64.linkerFlags += " -L $glfwDir/LINUX" + linux64.libraries += ' -l:libglfw.so' + buildTargets += linux64 } @@ -102,7 +160,17 @@ class GenerateLibs extends DefaultTask { mac64.cppFlags += ' -std=c++14' mac64.cppFlags = mac64.cppFlags.replace('10.7', minMacOsVersion) mac64.linkerFlags = mac64.linkerFlags.replace('10.7', minMacOsVersion) + addFreeTypeIfEnabled(mac64) + + //Vulkan + mac64.linkerFlags += " -L $vulkanDir/MACOS" + mac64.libraries += ' -l:vulkan' + + //Add glfw for linker + mac64.linkerFlags += " -L $glfwDir/MACOS" + mac64.libraries += ' -l:glfw' + buildTargets += mac64 } diff --git a/example/build.gradle b/example/build.gradle index a79cec19..93cd4661 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -25,4 +25,12 @@ run { if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) { jvmArgs += ['-XstartOnFirstThread', '-Djava.awt.headless=true'] } + try { + if (useVulkan) { + args += 'vulkan' + } + } catch (ignored) { + // ignore + } + } diff --git a/example/src/main/java/FontAwesomeIcons.java b/example/src/main/java/FontAwesomeIcons.java index 89517545..d6cb02a6 100644 --- a/example/src/main/java/FontAwesomeIcons.java +++ b/example/src/main/java/FontAwesomeIcons.java @@ -1,7 +1,7 @@ /** * Font Awesome icons codepoints, can be used to render icons with imgui in any place where text can be rendered. * - * To preview and search for icons use: https://fontawesome.com/icons?d=gallery&p=2&m=free + * To preview and search for icons use: icons */ @SuppressWarnings("unused") public class FontAwesomeIcons { diff --git a/example/src/main/java/Main.java b/example/src/main/java/Main.java index bc452923..23d18cb8 100644 --- a/example/src/main/java/Main.java +++ b/example/src/main/java/Main.java @@ -3,6 +3,7 @@ import imgui.ImGui; import imgui.ImGuiIO; import imgui.app.Application; +import imgui.app.BackendType; import imgui.app.Configuration; import imgui.flag.ImGuiConfigFlags; import imgui.flag.ImGuiInputTextFlags; @@ -18,9 +19,20 @@ public class Main extends Application { private final float[] flt = new float[1]; private int count = 0; + private final boolean useVulkan; + + public Main() { + useVulkan = false; + } + + public Main(boolean useVulkan) { + this.useVulkan = useVulkan; + } + @Override protected void configure(final Configuration config) { config.setTitle("Example Application"); + config.setBackendType(useVulkan ? BackendType.VULKAN : BackendType.OPENGL); } @Override @@ -93,7 +105,11 @@ private static byte[] loadFromResources(String name) { } public static void main(final String[] args) { - launch(new Main()); + if (args.length == 1 && args[0].equals("vulkan")) { + launch(new Main(true)); + } else { + launch(new Main()); + } System.exit(0); } } diff --git a/imgui-app/build.gradle b/imgui-app/build.gradle index 9e70faf0..4e18c7fb 100644 --- a/imgui-app/build.gradle +++ b/imgui-app/build.gradle @@ -16,6 +16,7 @@ dependencies { api 'org.lwjgl:lwjgl' api 'org.lwjgl:lwjgl-glfw' api 'org.lwjgl:lwjgl-opengl' + api 'org.lwjgl:lwjgl-vulkan' ['natives-linux', 'natives-windows', 'natives-macos'].each { api "org.lwjgl:lwjgl::$it" diff --git a/imgui-app/src/main/java/imgui/app/Backend.java b/imgui-app/src/main/java/imgui/app/Backend.java new file mode 100644 index 00000000..262e459e --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/Backend.java @@ -0,0 +1,19 @@ +package imgui.app; + +public interface Backend { + default void preCreateWindow() { + } + + default void postCreateWindow(long windowHandle) { + } + + void init(Color clearColor); + + void begin(); + + void end(); + + void destroy(); + + void resize(long windowHandle, int width, int height); +} diff --git a/imgui-app/src/main/java/imgui/app/BackendType.java b/imgui-app/src/main/java/imgui/app/BackendType.java new file mode 100644 index 00000000..3aee35ba --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/BackendType.java @@ -0,0 +1,7 @@ +package imgui.app; + +public enum BackendType { + OPENGL, + VULKAN, + CUSTOM +} diff --git a/imgui-app/src/main/java/imgui/app/Configuration.java b/imgui-app/src/main/java/imgui/app/Configuration.java index 4c52a0eb..def75164 100644 --- a/imgui-app/src/main/java/imgui/app/Configuration.java +++ b/imgui-app/src/main/java/imgui/app/Configuration.java @@ -20,6 +20,10 @@ public class Configuration { * When true, application will be maximized by default. */ private boolean fullScreen = false; + /** + * Backend rendering API to use for ImGUI + */ + private BackendType backendType = BackendType.OPENGL; public String getTitle() { return title; @@ -52,4 +56,12 @@ public boolean isFullScreen() { public void setFullScreen(final boolean fullScreen) { this.fullScreen = fullScreen; } + + public BackendType getBackendType() { + return backendType; + } + + public void setBackendType(final BackendType backendType) { + this.backendType = backendType; + } } diff --git a/imgui-app/src/main/java/imgui/app/ImGuiGlBackend.java b/imgui-app/src/main/java/imgui/app/ImGuiGlBackend.java new file mode 100644 index 00000000..b91ad743 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/ImGuiGlBackend.java @@ -0,0 +1,96 @@ +package imgui.app; + +import imgui.ImGui; +import imgui.flag.ImGuiConfigFlags; +import imgui.gl3.ImGuiImplGl3; +import imgui.glfw.ImGuiImplGlfw; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL32; + +public class ImGuiGlBackend implements Backend { + + private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); + private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); + private long windowHandle; + private Color clearColor; + private String glslVersion = null; + + public ImGuiGlBackend() { + + } + + @Override + public void postCreateWindow(final long windowHandle) { + this.windowHandle = windowHandle; + GLFW.glfwMakeContextCurrent(windowHandle); + GL.createCapabilities(); + GLFW.glfwSwapInterval(GLFW.GLFW_TRUE); + imGuiGlfw.init(windowHandle, true); + } + + @Override + public void init(final Color clearColor) { + this.clearColor = clearColor; + decideGlGlslVersions(); + imGuiGl3.init(glslVersion); + clearBuffer(); + } + + private void decideGlGlslVersions() { + final boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac"); + if (isMac) { + glslVersion = "#version 150"; + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2); + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); // Required on Mac + } else { + glslVersion = "#version 130"; + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0); + } + } + + @Override + public void begin() { + imGuiGlfw.newFrame(); + ImGui.newFrame(); + clearBuffer(); + } + + /** + * Method used to clear the OpenGL buffer. + */ + private void clearBuffer() { + GL32.glClearColor(clearColor.getRed(), clearColor.getGreen(), clearColor.getBlue(), clearColor.getAlpha()); + GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT); + } + + @Override + public void end() { + ImGui.render(); + imGuiGl3.renderDrawData(ImGui.getDrawData()); + + if (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable)) { + final long backupWindowPtr = GLFW.glfwGetCurrentContext(); + ImGui.updatePlatformWindows(); + ImGui.renderPlatformWindowsDefault(); + GLFW.glfwMakeContextCurrent(backupWindowPtr); + } + + GLFW.glfwSwapBuffers(windowHandle); + } + + @Override + public void destroy() { + imGuiGlfw.dispose(); + imGuiGl3.dispose(); + ImGui.destroyContext(); + } + + @Override + public void resize(final long windowHandle, final int width, final int height) { + + } +} diff --git a/imgui-app/src/main/java/imgui/app/ImGuiVkBackend.java b/imgui-app/src/main/java/imgui/app/ImGuiVkBackend.java new file mode 100644 index 00000000..d39c315b --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/ImGuiVkBackend.java @@ -0,0 +1,436 @@ +package imgui.app; + +import imgui.ImDrawData; +import imgui.ImGui; +import imgui.app.vk.*; +import imgui.backends.glfw.ImGuiImplGlfw; +import imgui.backends.vk.callback.ImGuiImplVkCheckResultCallback; +import imgui.lwjgl3.backends.vk.ImGuiVk; +import imgui.lwjgl3.backends.vk.ImGuiVkInitInfo; +import org.lwjgl.PointerBuffer; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWVulkan; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VK10; +import org.lwjgl.vulkan.VkClearValue; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkRenderPassBeginInfo; + +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.system.MemoryUtil.NULL; +import static org.lwjgl.vulkan.KHRSurface.vkDestroySurfaceKHR; +import static org.lwjgl.vulkan.VK10.*; + +public class ImGuiVkBackend implements Backend { + + //GLFW Window + private long window = NULL; + private boolean resizeFlag = false; + + //Vulkan Objects + private long surface = VK_NULL_HANDLE; + private final ImVkInstance instance = new ImVkInstance(); + private final ImVkPhysicalDevice physicalDevice = new ImVkPhysicalDevice(); + private final ImVkDevice device = new ImVkDevice(); + private final ImVkPipelineCache pipelineCache = new ImVkPipelineCache(); + private final ImVkQueue graphicsQueue = new ImVkQueue(); + private final ImVkQueue presentationQueue = new ImVkQueue(); + private final ImVkSwapchain swapchain = new ImVkSwapchain(); + private final ImVkRenderPass renderPass = new ImVkRenderPass(); + private final ImVkCommandPool commandPool = new ImVkCommandPool(); + private final ImVkDescriptorPool descriptorPool = new ImVkDescriptorPool(); + private final List fences = new ArrayList<>(); + private final ImGuiVkInitInfo imguiVkInit = new ImGuiVkInitInfo(); + + //Buffers + private final List frameBuffers = new ArrayList<>(); + private final List depthBuffers = new ArrayList<>(); + private final List commandBuffers = new ArrayList<>(); + + //Logger + private static final Logger LOGGER = Logger.getLogger(ImGuiGlBackend.class.getName()); + + //Engine Info + private final String engineName = "imgui-app"; + private final int[] engineVersion = {1, 86, 3}; //FIXME: We should set this automatically to the correct build version + private final boolean validationEnabled = false; + + //Render vars + private Color clearColor; + private boolean transitionFonts = true; + + public ImGuiVkBackend() { + + } + + @Override + public void preCreateWindow() { + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + } + + @Override + public void postCreateWindow(final long windowHandle) { + this.window = windowHandle; + ImGuiImplGlfw.initForVulkan(window, true); + } + + @Override + public void init(final Color clearColor) { + this.clearColor = clearColor; + + LOGGER.info("Creating Vulkan rendering backend"); + + //Create instance + instance.setEngineName(engineName); + instance.setEngineVersion(engineVersion[0], engineVersion[1], engineVersion[2]); + instance.setValidationEnabled(validationEnabled); + instance.getExtensions().addAll(Arrays.asList(getVulkanRequiredExtensions())); + instance.create(); + + //Create surface + createSurface(); + + //Create physical device + physicalDevice.setInstance(instance); + physicalDevice.setSurface(surface); + physicalDevice.create(); + + //Create logical device + device.setPhysicalDevice(physicalDevice); + device.create(); + + //Create pipeline cache + pipelineCache.setDevice(device); + pipelineCache.create(); + + //Create graphics queue + graphicsQueue.setFamilyIndex(physicalDevice.getIndices().getGraphicsFamily()); + graphicsQueue.setDevice(device); + graphicsQueue.create(); + + //Create presentation queue + presentationQueue.setFamilyIndex(physicalDevice.getIndices().getPresentFamily()); + presentationQueue.setDevice(device); + presentationQueue.create(); + + //Create swapchain + swapchain.setSurface(surface); + swapchain.setVsync(true); + try (MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer pWidth = stack.mallocInt(1); + final IntBuffer pHeight = stack.mallocInt(1); + + GLFW.glfwGetWindowSize(window, pWidth, pHeight); + swapchain.setWidth(pWidth.get(0)); + swapchain.setHeight(pHeight.get(0)); + } + swapchain.setGraphicsQueue(graphicsQueue); + swapchain.create(); + + //Create render pass + renderPass.setSwapchain(swapchain); + renderPass.create(); + + //Create command pool + commandPool.setDevice(device); + commandPool.create(); + + //Create buffers + createDepthBuffers(); + createFrameBuffers(); + createFencesAndCommandBuffers(); + + //Create descriptor pool + descriptorPool.setDevice(device); + descriptorPool.create(); + + //Init imgui vulkan + imguiVkInit.setCheckVkResultFn(new ImGuiImplVkCheckResultCallback() { + @Override + public void callback(final int resultCode) { + vkOK(resultCode); + } + }); + imguiVkInit.setInstance(instance.getInstance()); + imguiVkInit.setAllocator(instance.getAllocationCallbacks()); + imguiVkInit.setPhysicalDevice(physicalDevice.getPhysicalDevice()); + imguiVkInit.setDevice(device.getDevice()); + imguiVkInit.setMinImageCount(swapchain.getImageViews().size()); + imguiVkInit.setImageCount(swapchain.getImageViews().size()); + imguiVkInit.setDescriptorPool(descriptorPool.getNativeHandle()); + imguiVkInit.setPipelineCache(pipelineCache.getNativeHandle()); + imguiVkInit.setQueue(graphicsQueue.getQueue()); + imguiVkInit.setQueueFamily(physicalDevice.getIndices().getGraphicsFamily()); + imguiVkInit.setMSAASamples(VK_SAMPLE_COUNT_1_BIT); + imguiVkInit.setSubpass(0); + ImGuiVk.init(imguiVkInit, renderPass.getNativeHandle()); + + LOGGER.info("Vulkan backend ready"); + } + + + private void createSurface() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final LongBuffer longBuff = stack.callocLong(1); + if (GLFWVulkan.glfwCreateWindowSurface(instance.getInstance(), window, null, longBuff) == VK10.VK_SUCCESS) { + surface = longBuff.get(); + } else { + throw new RuntimeException("Failed to create glfw vulkan surface"); + } + } + } + + private void createDepthBuffers() { + final int numImages = swapchain.getImageViews().size(); + for (int i = 0; i < numImages; i++) { + final ImVkAttachment attachment = new ImVkAttachment(); + attachment.setFormat(VK_FORMAT_D32_SFLOAT); + attachment.setUsage(VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + attachment.setWidth(swapchain.getExtent().width()); + attachment.setHeight(swapchain.getExtent().height()); + attachment.setDevice(device); + attachment.create(); + depthBuffers.add(attachment); + } + } + + private void createFrameBuffers() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final LongBuffer attachments = stack.callocLong(2); + for (int i = 0; i < swapchain.getImageViews().size(); i++) { + attachments.put(0, swapchain.getImageViews().get(i).getNativeHandle()); + attachments.put(1, depthBuffers.get(i).getImageView().getNativeHandle()); + final ImVkFrameBuffer frameBuffer = new ImVkFrameBuffer(); + frameBuffers.add(frameBuffer); + frameBuffer.setAttachments(attachments); + frameBuffer.setRenderPass(renderPass); + frameBuffer.create(); + } + } + } + + private void createFencesAndCommandBuffers() { + for (int i = 0; i < swapchain.getImageViews().size(); i++) { + //Create Fences + final ImVkFence fence = new ImVkFence(); + fences.add(fence); + fence.setSignaled(true); + fence.setDevice(device); + fence.create(); + + //Create Command Buffers + final ImVkCommandBuffer commandBuffer = new ImVkCommandBuffer(); + commandBuffers.add(commandBuffer); + commandBuffer.setCommandPool(commandPool); + commandBuffer.setPrimary(true); + commandBuffer.setOneTimeSubmit(false); + commandBuffer.create(); + } + } + + private String[] getVulkanRequiredExtensions() { + final PointerBuffer glfwExtensions = GLFWVulkan.glfwGetRequiredInstanceExtensions(); + final String[] extensions = new String[glfwExtensions.capacity()]; + for (int i = 0; i < extensions.length; i++) { + extensions[i] = glfwExtensions.getStringASCII(i); + } + return extensions; + } + + @Override + public void resize(final long windowHandle, final int width, final int height) { + resizeFlag = true; + } + + public void resize() { + //=== Wait for gpu to be ready + device.waitIdle(); + graphicsQueue.waitIdle(); + presentationQueue.waitIdle(); + + //=== Unload things with old size + //Unload frame buffers + frameBuffers.forEach(ImVkFrameBuffer::destroy); + frameBuffers.clear(); + + //Unload depth buffers + depthBuffers.forEach(ImVkAttachment::destroy); + depthBuffers.clear(); + + //Unload swap chain + swapchain.destroy(); + + //=== Load with new size + //Update swapchain device support + physicalDevice.resize(); + + //Create swap chain + swapchain.setGraphicsQueue(graphicsQueue); + swapchain.setSurface(surface); + swapchain.create(); + + //Create depth buffers + createDepthBuffers(); + + //Create frame buffer + createFrameBuffers(); + + //Done + resizeFlag = false; + } + + @Override + public void begin() { + ImGuiImplGlfw.newFrame(); + ImGui.newFrame(); + //Handle if we have been reized + if (resizeFlag || swapchain.nextImage()) { + resize(); + swapchain.nextImage(); + } + + //Create new imgui vulkan frame + ImGuiVk.newFrame(); + + //Begin the render pass for the frame + beginRenderPass(); + } + + @Override + public void end() { + //Get draw calls from imgui + ImGui.render(); + final ImDrawData drawData = ImGui.getDrawData(); + if (drawData.getDisplaySizeX() > 0.0f && drawData.getDisplaySizeY() > 0.0f) { + final int currentFrame = swapchain.getCurrentFrame(); + final ImVkCommandBuffer commandBuffer = commandBuffers.get(currentFrame); + ImGuiVk.renderDrawData(drawData, commandBuffer.getCommandBuffer(), VK_NULL_HANDLE); + } + + //Complete render pass + endRenderPass(); + + //Submit command buffer to GPU for complete render of the scene + submit(presentationQueue); + + //Check if fonts were transitioned, if so allow imgui to delete temp objects + if (transitionFonts) { + ImGuiVk.destroyFontUploadObjects(); + transitionFonts = false; + } + + //Present new frame + if (swapchain.presentImage()) { + resizeFlag = true; + } + } + + private void submit(final ImVkQueue queue) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final int currentFrame = swapchain.getCurrentFrame(); + final ImVkCommandBuffer commandBuffer = commandBuffers.get(currentFrame); + final ImVkFence currentFence = fences.get(currentFrame); + queue.submit( + stack.pointers(commandBuffer.getCommandBuffer()), + stack.longs(swapchain.getImageAvailableSemaphores()[currentFrame].getNativeHandle()), + stack.ints(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT), + stack.longs(swapchain.getRenderFinishedSemaphores()[currentFrame].getNativeHandle()), + currentFence + ); + } + } + + private void beginRenderPass() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkExtent2D swapChainExtent = swapchain.getExtent(); + final int width = swapChainExtent.width(); + final int height = swapChainExtent.height(); + final int currentFrame = swapchain.getCurrentFrame(); + + //Start render pass + final ImVkFence fence = fences.get(currentFrame); + final ImVkCommandBuffer commandBuffer = commandBuffers.get(currentFrame); + final ImVkFrameBuffer frameBuffer = frameBuffers.get(currentFrame); + + fence.waitFor(); + fence.reset(); + + commandBuffer.reset(); + final VkClearValue.Buffer clearValues = VkClearValue.calloc(2, stack); + clearValues.apply(0, v -> v.color().float32(0, clearColor.getRed()).float32(1, clearColor.getGreen()).float32(2, clearColor.getBlue()).float32(3, clearColor.getAlpha())); + clearValues.apply(1, v -> v.depthStencil().depth(1.0f)); + + final VkRenderPassBeginInfo renderPassBeginInfo = VkRenderPassBeginInfo.calloc(stack) + .sType$Default() + .renderPass(renderPass.getNativeHandle()) + .pClearValues(clearValues) + .renderArea(a -> a.extent().set(width, height)) + .framebuffer(frameBuffer.getNativeHandle()); + + commandBuffer.begin(); + + //Check if we need to perform the one time upload of fonts to the GPU + if (transitionFonts) { + ImGuiVk.createFontsTexture(commandBuffer.getCommandBuffer()); + } + + //Start render pass + vkCmdBeginRenderPass(commandBuffer.getCommandBuffer(), renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + } + } + + private void endRenderPass() { + final ImVkCommandBuffer commandBuffer = commandBuffers.get(swapchain.getCurrentFrame()); + vkCmdEndRenderPass(commandBuffer.getCommandBuffer()); + commandBuffer.end(); + } + + @Override + public void destroy() { + ImGuiImplGlfw.shutdown(); + //Destroy imgui vulkan backend + ImGuiVk.shutdown(); + ImGui.destroyContext(); + + //Wait for GPU to be ready + device.waitIdle(); + presentationQueue.waitIdle(); + graphicsQueue.waitIdle(); + + //Destroy vulkan + commandBuffers.forEach(ImVkCommandBuffer::destroy); + commandBuffers.clear(); + + fences.forEach(ImVkFence::destroy); + fences.clear(); + + commandPool.destroy(); + + frameBuffers.forEach(ImVkFrameBuffer::destroy); + frameBuffers.clear(); + + depthBuffers.forEach(ImVkAttachment::destroy); + depthBuffers.clear(); + + swapchain.destroy(); + presentationQueue.destroy(); + graphicsQueue.destroy(); + pipelineCache.destroy(); + device.destroy(); + physicalDevice.destroy(); + destroySurface(); + instance.destroy(); + } + + private void destroySurface() { + vkDestroySurfaceKHR(instance.getInstance(), surface, null); + } +} diff --git a/imgui-app/src/main/java/imgui/app/Window.java b/imgui-app/src/main/java/imgui/app/Window.java index b04f2bdd..79c0b825 100644 --- a/imgui-app/src/main/java/imgui/app/Window.java +++ b/imgui-app/src/main/java/imgui/app/Window.java @@ -1,16 +1,12 @@ package imgui.app; import imgui.ImGui; -import imgui.flag.ImGuiConfigFlags; -import imgui.gl3.ImGuiImplGl3; import imgui.glfw.ImGuiImplGlfw; import org.lwjgl.glfw.Callbacks; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWErrorCallback; import org.lwjgl.glfw.GLFWVidMode; import org.lwjgl.glfw.GLFWWindowSizeCallback; -import org.lwjgl.opengl.GL; -import org.lwjgl.opengl.GL32; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; @@ -24,10 +20,8 @@ */ public abstract class Window { - private final ImGuiImplGlfw imGuiGlfw = new ImGuiImplGlfw(); - private final ImGuiImplGl3 imGuiGl3 = new ImGuiImplGl3(); - - private String glslVersion = null; + private Backend backend; + private Backend customBackend; /** * Pointer to the native GLFW window. @@ -45,19 +39,28 @@ public abstract class Window { * @param config configuration object with basic window information */ protected void init(final Configuration config) { + if (config.getBackendType() == BackendType.OPENGL) { + backend = new ImGuiGlBackend(); + } else if (config.getBackendType() == BackendType.VULKAN) { + backend = new ImGuiVkBackend(); + } else if (config.getBackendType() == BackendType.CUSTOM) { + if (customBackend != null) { + backend = customBackend; + } else { + throw new RuntimeException("No custom rendering backend provided"); + } + } + initWindow(config); initImGui(config); - imGuiGlfw.init(handle, true); - imGuiGl3.init(glslVersion); + backend.init(colorBg); } /** * Method to dispose all used application resources and destroy its window. */ protected void dispose() { - imGuiGl3.dispose(); - imGuiGlfw.dispose(); - disposeImGui(); + backend.destroy(); disposeWindow(); } @@ -73,9 +76,10 @@ protected void initWindow(final Configuration config) { throw new IllegalStateException("Unable to initialize GLFW"); } - decideGlGlslVersions(); - GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE); + + backend.preCreateWindow(); + handle = GLFW.glfwCreateWindow(config.getWidth(), config.getHeight(), config.getTitle(), MemoryUtil.NULL, MemoryUtil.NULL); if (handle == MemoryUtil.NULL) { @@ -91,11 +95,7 @@ protected void initWindow(final Configuration config) { GLFW.glfwSetWindowPos(handle, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2); } - GLFW.glfwMakeContextCurrent(handle); - - GL.createCapabilities(); - - GLFW.glfwSwapInterval(GLFW.GLFW_TRUE); + backend.postCreateWindow(handle); if (config.isFullScreen()) { GLFW.glfwMaximizeWindow(handle); @@ -103,31 +103,15 @@ protected void initWindow(final Configuration config) { GLFW.glfwShowWindow(handle); } - clearBuffer(); - renderBuffer(); - GLFW.glfwSetWindowSizeCallback(handle, new GLFWWindowSizeCallback() { @Override public void invoke(final long window, final int width, final int height) { + backend.resize(window, width, height); runFrame(); } }); } - private void decideGlGlslVersions() { - final boolean isMac = System.getProperty("os.name").toLowerCase().contains("mac"); - if (isMac) { - glslVersion = "#version 150"; - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 2); - GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); // 3.2+ only - GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE); // Required on Mac - } else { - glslVersion = "#version 130"; - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0); - } - } /** * Method to initialize Dear ImGui context. Could be overridden to do custom Dear ImGui setup before application start. @@ -175,22 +159,12 @@ protected void runFrame() { */ public abstract void process(); - /** - * Method used to clear the OpenGL buffer. - */ - private void clearBuffer() { - GL32.glClearColor(colorBg.getRed(), colorBg.getGreen(), colorBg.getBlue(), colorBg.getAlpha()); - GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT); - } - /** * Method called at the beginning of the main cycle. - * It clears OpenGL buffer and starts an ImGui frame. + * Starts an ImGui frame, then hands off to the backend to begin the frame */ protected void startFrame() { - clearBuffer(); - imGuiGlfw.newFrame(); - ImGui.newFrame(); + backend.begin(); } /** @@ -198,42 +172,37 @@ protected void startFrame() { * It renders ImGui and swaps GLFW buffers to show an updated frame. */ protected void endFrame() { - ImGui.render(); - imGuiGl3.renderDrawData(ImGui.getDrawData()); - - if (ImGui.getIO().hasConfigFlags(ImGuiConfigFlags.ViewportsEnable)) { - final long backupWindowPtr = GLFW.glfwGetCurrentContext(); - ImGui.updatePlatformWindows(); - ImGui.renderPlatformWindowsDefault(); - GLFW.glfwMakeContextCurrent(backupWindowPtr); - } - - renderBuffer(); + backend.end(); + GLFW.glfwPollEvents(); } /** - * Method to render the OpenGL buffer and poll window events. + * Method to destroy GLFW window. */ - private void renderBuffer() { - GLFW.glfwSwapBuffers(handle); - GLFW.glfwPollEvents(); + protected void disposeWindow() { + Callbacks.glfwFreeCallbacks(handle); + GLFW.glfwDestroyWindow(handle); + GLFW.glfwTerminate(); + Objects.requireNonNull(GLFW.glfwSetErrorCallback(null)).free(); } /** - * Method to destroy Dear ImGui context. + * Get the backend being used to render on the window + * + * @return The current backend */ - protected void disposeImGui() { - ImGui.destroyContext(); + protected Backend getBackend() { + return backend; } /** - * Method to destroy GLFW window. + * Use a custom backend for rendering + * Must be called before {@link #init(Configuration)} + * + * @param backend The custom backend to use */ - protected void disposeWindow() { - Callbacks.glfwFreeCallbacks(handle); - GLFW.glfwDestroyWindow(handle); - GLFW.glfwTerminate(); - Objects.requireNonNull(GLFW.glfwSetErrorCallback(null)).free(); + public void setBackend(final Backend backend) { + customBackend = backend; } /** diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkAttachment.java b/imgui-app/src/main/java/imgui/app/vk/ImVkAttachment.java new file mode 100644 index 00000000..03b3385e --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkAttachment.java @@ -0,0 +1,117 @@ +package imgui.app.vk; + +import java.util.logging.Logger; + +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_DEPTH_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_SAMPLED_BIT; + +public class ImVkAttachment { + + private static final Logger LOGGER = Logger.getLogger(ImVkAttachment.class.getName()); + + private ImVkDevice device; + + private Integer format; + private Integer usage; + private ImVkImage image; + private ImVkImageView imageView; + private int width = 0; + private int height = 0; + + public void create() { + if (format == null || usage == null) { + throw new IllegalStateException("Format, and usage must be specified to build attachment!"); + } + + if (device == null) { + throw new IllegalStateException("Cannot build attachment without a device being set"); + } + + createAttachment(); + } + + public void destroy() { + destroyAttachment(); + } + + private void createAttachment() { + image = new ImVkImage(); + image.setUsage(usage | VK_IMAGE_USAGE_SAMPLED_BIT); + image.setFormat(format); + image.setWidth(width); + image.setHeight(height); + image.setDevice(device); + image.create(); + + int aspectMask = 0; + if ((usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) > 0) { + aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + if ((usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) > 0) { + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + } + + imageView = new ImVkImageView(); + imageView.setDevice(device); + imageView.setFormat(image.getFormat()); + imageView.setAspectMask(aspectMask); + imageView.setImage(image.getNativeHandle()); + imageView.create(); + } + + private void destroyAttachment() { + imageView.destroy(); + image.destroy(); + } + + public Integer getFormat() { + return format; + } + + public void setFormat(final Integer format) { + this.format = format; + } + + public Integer getUsage() { + return usage; + } + + public void setUsage(final Integer usage) { + this.usage = usage; + } + + public ImVkImage getImage() { + return image; + } + + public ImVkImageView getImageView() { + return imageView; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkCommandBuffer.java b/imgui-app/src/main/java/imgui/app/vk/ImVkCommandBuffer.java new file mode 100644 index 00000000..3b72b78c --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkCommandBuffer.java @@ -0,0 +1,124 @@ +package imgui.app.vk; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferAllocateInfo; +import org.lwjgl.vulkan.VkCommandBufferBeginInfo; + +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_PRIMARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_SECONDARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkAllocateCommandBuffers; +import static org.lwjgl.vulkan.VK10.vkBeginCommandBuffer; +import static org.lwjgl.vulkan.VK10.vkEndCommandBuffer; +import static org.lwjgl.vulkan.VK10.vkFreeCommandBuffers; +import static org.lwjgl.vulkan.VK10.vkResetCommandBuffer; + +public class ImVkCommandBuffer { + + private static final Logger LOGGER = Logger.getLogger(ImVkCommandBuffer.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private VkCommandBuffer vkCommandBuffer; + private ImVkCommandPool commandPool = null; + private boolean primary = true; + private boolean oneTimeSubmit = false; + + public void create() { + if (commandPool == null) { + throw new IllegalStateException("Command Pool must be specified for a Command Buffer"); + } + + createCommandBuffer(); + } + + public void destroy() { + destroyCommandBuffer(); + } + + private void createCommandBuffer() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkCommandBufferAllocateInfo cmdBufAllocateInfo = VkCommandBufferAllocateInfo.calloc(stack) + .sType$Default() + .commandPool(commandPool.getNativeHandle()) + .level(isPrimary() ? VK_COMMAND_BUFFER_LEVEL_PRIMARY : VK_COMMAND_BUFFER_LEVEL_SECONDARY) + .commandBufferCount(1); + final PointerBuffer pb = stack.mallocPointer(1); + vkOK(vkAllocateCommandBuffers(getCommandPool().getDevice().getDevice(), cmdBufAllocateInfo, pb)); + nativeHandle = pb.get(); + vkCommandBuffer = new VkCommandBuffer(nativeHandle, getCommandPool().getDevice().getDevice()); + } + } + + public void submitAndWait(final ImVkQueue queue) { + final ImVkFence fence = new ImVkFence(); + fence.setDevice(queue.getDevice()); + fence.setSignaled(true); + fence.create(); + fence.reset(); + try (MemoryStack stack = MemoryStack.stackPush()) { + queue.submit(stack.pointers(vkCommandBuffer), null, null, null, fence); + } + fence.waitFor(); + fence.destroy(); + } + + private void destroyCommandBuffer() { + vkFreeCommandBuffers(getCommandPool().getDevice().getDevice(), commandPool.getNativeHandle(), vkCommandBuffer); + nativeHandle = VK_NULL_HANDLE; + } + + public void begin() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkCommandBufferBeginInfo cmdBufInfo = VkCommandBufferBeginInfo.calloc(stack) + .sType$Default(); + if (oneTimeSubmit) { + cmdBufInfo.flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + } + vkOK(vkBeginCommandBuffer(vkCommandBuffer, cmdBufInfo)); + } + } + + public void end() { + vkEndCommandBuffer(vkCommandBuffer); + } + + public void reset() { + vkResetCommandBuffer(vkCommandBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); + } + + public VkCommandBuffer getCommandBuffer() { + return vkCommandBuffer; + } + + public ImVkCommandPool getCommandPool() { + return commandPool; + } + + public void setCommandPool(final ImVkCommandPool commandPool) { + this.commandPool = commandPool; + } + + public boolean isPrimary() { + return primary; + } + + public void setPrimary(final boolean primary) { + this.primary = primary; + } + + public boolean isOneTimeSubmit() { + return oneTimeSubmit; + } + + public void setOneTimeSubmit(final boolean oneTimeSubmit) { + this.oneTimeSubmit = oneTimeSubmit; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkCommandPool.java b/imgui-app/src/main/java/imgui/app/vk/ImVkCommandPool.java new file mode 100644 index 00000000..fba7ff69 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkCommandPool.java @@ -0,0 +1,70 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandPoolCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateCommandPool; +import static org.lwjgl.vulkan.VK10.vkDestroyCommandPool; + +public class ImVkCommandPool { + + private static final Logger LOGGER = Logger.getLogger(ImVkCommandPool.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create command pool without a vulkan device set"); + } + + createCommandPool(); + } + + public void destroy() { + destroyCommandPool(); + } + + private void createCommandPool() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkCommandPoolCreateInfo cmdPoolInfo = VkCommandPoolCreateInfo.calloc(stack) + .sType$Default() + .flags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) + .queueFamilyIndex(getDevice().getPhysicalDevice().getIndices().getGraphicsFamily()); + + final LongBuffer longBuff = stack.callocLong(1); + vkOK( + vkCreateCommandPool( + getDevice().getDevice(), + cmdPoolInfo, + null, + longBuff + ) + ); + nativeHandle = longBuff.get(); + } + } + + private void destroyCommandPool() { + vkDestroyCommandPool(getDevice().getDevice(), nativeHandle, null); + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkDebug.java b/imgui-app/src/main/java/imgui/app/vk/ImVkDebug.java new file mode 100644 index 00000000..c2ed930e --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkDebug.java @@ -0,0 +1,210 @@ +package imgui.app.vk; + +import org.lwjgl.vulkan.EXTBufferDeviceAddress; +import org.lwjgl.vulkan.EXTDebugReport; +import org.lwjgl.vulkan.EXTDescriptorIndexing; +import org.lwjgl.vulkan.EXTFullScreenExclusive; +import org.lwjgl.vulkan.EXTGlobalPriority; +import org.lwjgl.vulkan.EXTImageDrmFormatModifier; +import org.lwjgl.vulkan.EXTPipelineCreationCacheControl; +import org.lwjgl.vulkan.KHRBufferDeviceAddress; +import org.lwjgl.vulkan.KHRDeferredHostOperations; +import org.lwjgl.vulkan.KHRDisplaySwapchain; +import org.lwjgl.vulkan.KHRExternalMemory; +import org.lwjgl.vulkan.KHRMaintenance1; +import org.lwjgl.vulkan.KHRSurface; +import org.lwjgl.vulkan.KHRSwapchain; +import org.lwjgl.vulkan.NVGLSLShader; +import org.lwjgl.vulkan.VK10; +import org.lwjgl.vulkan.VK11; +import org.lwjgl.vulkan.VK12; +import org.lwjgl.vulkan.VkDebugUtilsMessengerCallbackDataEXT; +import org.lwjgl.vulkan.VkDebugUtilsMessengerCreateInfoEXT; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; +import static org.lwjgl.vulkan.VK10.VK_FALSE; + +/** + * Vulkan debugging helper functions. + * Provides helper functions for managing the messenger lifecycle and result error valudation. + */ +public final class ImVkDebug { + private ImVkDebug() { + } + + private static final Logger LOGGER = Logger.getLogger(ImVkDebug.class.getName()); + + private static final int MESSAGE_SEVERITY_BITMASK = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + + private static final int MESSAGE_TYPE_BITMASK = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + public static VkDebugUtilsMessengerCreateInfoEXT createDebugCallback() { + return VkDebugUtilsMessengerCreateInfoEXT + .calloc() + .sType$Default() + .messageSeverity(MESSAGE_SEVERITY_BITMASK) + .messageType(MESSAGE_TYPE_BITMASK) + .pfnUserCallback((messageSeverity, messageTypes, pCallbackData, pUserData) -> { + final VkDebugUtilsMessengerCallbackDataEXT callbackData = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData); + Level logLevel = Level.FINE; + if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0) { + logLevel = Level.INFO; + } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0) { + logLevel = Level.WARNING; + } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0) { + logLevel = Level.SEVERE; + } + + LOGGER.log(logLevel, "[validation] " + callbackData.pMessageString()); + if (logLevel == Level.SEVERE) { + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement stackTraceElement : stackTrace) { + LOGGER.log(logLevel, "[validation] [trace] " + stackTraceElement.toString()); + } + } + return VK_FALSE; + }); + } + + public static void destroyDebugCallback(final VkDebugUtilsMessengerCreateInfoEXT callback) { + if (callback != null) { + callback.pfnUserCallback().free(); + callback.free(); + } + } + + public static void vkOK(final int result) { + if (result != VK10.VK_SUCCESS) { + throw new RuntimeException("Failed to execute vulkan call: (" + result + ") " + getResultMessage(result)); + } + } + + /** + * Get the result message from a vulkan result code. + * + * @param vkResultCode The VkResult to get the message for + * @return The message for the corresponding + */ + public static String getResultMessage(final int vkResultCode) { + for (VkResults results : VkResults.values()) { + if (results.getResultCode() == vkResultCode) { + return results.getResultMessage(); + } + } + return "Unknown VkResult code!"; + } + + public enum VkResults { + //Vulkan 1.0 errors + VK_SUCCESS(VK10.VK_SUCCESS, "Command successfully completed"), + VK_NOT_READY(VK10.VK_NOT_READY, "A fence or query has not yet completed"), + VK_TIMEOUT(VK10.VK_TIMEOUT, "A wait operation has not completed in the specified time"), + VK_EVENT_SET(VK10.VK_EVENT_SET, "An event is signaled"), + VK_EVENT_RESET(VK10.VK_EVENT_RESET, "An event is unsignaled"), + VK_INCOMPLETE(VK10.VK_INCOMPLETE, "A return array was too small for the result"), + VK_ERROR_OUT_OF_HOST_MEMORY(VK10.VK_ERROR_OUT_OF_HOST_MEMORY, "A host memory allocation has failed"), + VK_ERROR_OUT_OF_DEVICE_MEMORY(VK10.VK_ERROR_OUT_OF_DEVICE_MEMORY, "A device memory allocation has failed"), + VK_ERROR_INITIALIZATION_FAILED(VK10.VK_ERROR_INITIALIZATION_FAILED, "Initialization of an object could not be completed for implementation-specific reasons"), + VK_ERROR_DEVICE_LOST(VK10.VK_ERROR_DEVICE_LOST, "The logical or physical device has been lost"), + VK_ERROR_MEMORY_MAP_FAILED(VK10.VK_ERROR_MEMORY_MAP_FAILED, "Mapping of a memory object has failed"), + VK_ERROR_LAYER_NOT_PRESENT(VK10.VK_ERROR_LAYER_NOT_PRESENT, "A requested layer is not present or could not be loaded"), + VK_ERROR_EXTENSION_NOT_PRESENT(VK10.VK_ERROR_EXTENSION_NOT_PRESENT, "A requested extension is not supported"), + VK_ERROR_FEATURE_NOT_PRESENT(VK10.VK_ERROR_FEATURE_NOT_PRESENT, "A requested feature is not supported"), + VK_ERROR_INCOMPATIBLE_DRIVER(VK10.VK_ERROR_INCOMPATIBLE_DRIVER, "The requested version of Vulkan is not supported by the driver or is otherwise incompatible for implementation-specific reasons"), + VK_ERROR_TOO_MANY_OBJECTS(VK10.VK_ERROR_TOO_MANY_OBJECTS, "Too many objects of the type have already been created"), + VK_ERROR_FORMAT_NOT_SUPPORTED(VK10.VK_ERROR_FORMAT_NOT_SUPPORTED, "A requested format is not supported on this device"), + VK_ERROR_FRAGMENTED_POOL(VK10.VK_ERROR_FRAGMENTED_POOL, "A pool allocation has failed due to fragmentation of the pool’s memory. This must only be returned if no attempt to allocate host or device memory was made to accommodate the new allocation"), + VK_ERROR_UNKNOWN(VK10.VK_ERROR_UNKNOWN, "An unknown error has occurred; either the application has provided invalid input, or an implementation failure has occurred"), + + //Vulkan 1.1 errors + VK_ERROR_OUT_OF_POOL_MEMORY(VK11.VK_ERROR_OUT_OF_POOL_MEMORY, "A descriptor pool allocation has failed"), + VK_ERROR_INVALID_EXTERNAL_HANDLE(VK11.VK_ERROR_INVALID_EXTERNAL_HANDLE, "Invalid external handle"), + + //Vulkan 1.2 errors + VK_ERROR_FRAGMENTATION(VK12.VK_ERROR_FRAGMENTATION, "Fragmentation error"), + VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS(VK12.VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS, "Invalid opaque address"), + + //KHR Surface + VK_ERROR_SURFACE_LOST_KHR(KHRSurface.VK_ERROR_SURFACE_LOST_KHR, "Surface lost"), + VK_ERROR_NATIVE_WINDOW_IN_USE_KHR(KHRSurface.VK_ERROR_NATIVE_WINDOW_IN_USE_KHR, "Native window in use error"), + + //KHR Swapchain + VK_SUBOPTIMAL_KHR(KHRSwapchain.VK_SUBOPTIMAL_KHR, "Swapchain suboptimal"), + VK_ERROR_OUT_OF_DATE_KHR(KHRSwapchain.VK_ERROR_OUT_OF_DATE_KHR, "Swapchain out of date"), + + //KHR Display Swapchain + VK_ERROR_INCOMPATIBLE_DISPLAY_KHR(KHRDisplaySwapchain.VK_ERROR_INCOMPATIBLE_DISPLAY_KHR, "Incompatable display"), + + //EXT debug report + VK_ERROR_VALIDATION_FAILED_EXT(EXTDebugReport.VK_ERROR_VALIDATION_FAILED_EXT, "Validation failed"), + + //NV GLSL Shader + VK_ERROR_INVALID_SHADER_NV(NVGLSLShader.VK_ERROR_INVALID_SHADER_NV, "Invalid shader"), + + //EXT image drm format modifier + VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT(EXTImageDrmFormatModifier.VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT, "Invalid DRM format modifier plane layout"), + + //EXT global priority + VK_ERROR_NOT_PERMITTED_EXT(EXTGlobalPriority.VK_ERROR_NOT_PERMITTED_EXT, "Not permitted"), + + //EXT full screen exclusive + VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT(EXTFullScreenExclusive.VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT, "Full screen exclusive mode lost"), + + //HKR deferred host operations + VK_THREAD_IDLE_KHR(KHRDeferredHostOperations.VK_THREAD_IDLE_KHR, "Thread is idle"), + VK_THREAD_DONE_KHR(KHRDeferredHostOperations.VK_THREAD_DONE_KHR, "Thread is done"), + VK_OPERATION_DEFERRED_KHR(KHRDeferredHostOperations.VK_OPERATION_DEFERRED_KHR, "Operation deferred"), + VK_OPERATION_NOT_DEFERRED_KHR(KHRDeferredHostOperations.VK_OPERATION_NOT_DEFERRED_KHR, "Operation not deferred"), + + //EXT pipeline creation cache control + VK_PIPELINE_COMPILE_REQUIRED_EXT(EXTPipelineCreationCacheControl.VK_PIPELINE_COMPILE_REQUIRED_EXT, "Pipeline compile required"), + VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT(EXTPipelineCreationCacheControl.VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT, "Pipeline compile required error"), + + //KHR maintenance 1 + VK_ERROR_OUT_OF_POOL_MEMORY_KHR(KHRMaintenance1.VK_ERROR_OUT_OF_POOL_MEMORY_KHR, "Out of pool memory"), + + //KHR external memory + VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR(KHRExternalMemory.VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR, "Invalid external handle"), + + //EXT descriptor indexing + VK_ERROR_FRAGMENTATION_EXT(EXTDescriptorIndexing.VK_ERROR_FRAGMENTATION_EXT, "Fragmentation error"), + + //EXT buffer device address + VK_ERROR_INVALID_DEVICE_ADDRESS_EXT(EXTBufferDeviceAddress.VK_ERROR_INVALID_DEVICE_ADDRESS_EXT, "Invalid device address"), + + //KHR buffer device address + VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR(KHRBufferDeviceAddress.VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR, "Invalid opaque capture address"); + + private final int resultCode; + private final String resultMessage; + + VkResults(final int code, final String message) { + this.resultCode = code; + this.resultMessage = message; + } + + public int getResultCode() { + return resultCode; + } + + public String getResultMessage() { + return resultMessage; + } + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkDescriptorPool.java b/imgui-app/src/main/java/imgui/app/vk/ImVkDescriptorPool.java new file mode 100644 index 00000000..8b80a468 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkDescriptorPool.java @@ -0,0 +1,78 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkDescriptorPoolCreateInfo; +import org.lwjgl.vulkan.VkDescriptorPoolSize; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorPool; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorPool; + +public class ImVkDescriptorPool { + private static final Logger LOGGER = Logger.getLogger(ImVkDescriptorPool.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create command pool without a vulkan device set"); + } + + createDescriptorPool(); + } + + private void createDescriptorPool() { + try (MemoryStack stack = MemoryStack.stackPush()) { + + //FIXME: We need to dynamically set these + final VkDescriptorPoolSize.Buffer poolSizes = VkDescriptorPoolSize.calloc(2); + poolSizes.get(0) + .type(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .descriptorCount(1); + poolSizes.get(1) + .type(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER) + .descriptorCount(1); + + final VkDescriptorPoolCreateInfo descriptorPoolInfo = VkDescriptorPoolCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO) + .flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT) + .pPoolSizes(poolSizes) + .maxSets(1000); //TODO: FIX ME + + final LongBuffer pDescriptorPool = stack.mallocLong(1); + vkOK(vkCreateDescriptorPool(device.getDevice(), descriptorPoolInfo, null, pDescriptorPool)); + nativeHandle = pDescriptorPool.get(0); + } + } + + public void destroy() { + destroyDescriptorPool(); + } + + private void destroyDescriptorPool() { + vkDestroyDescriptorPool(device.getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkDevice.java b/imgui-app/src/main/java/imgui/app/vk/ImVkDevice.java new file mode 100644 index 00000000..cc5ad78a --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkDevice.java @@ -0,0 +1,139 @@ +package imgui.app.vk; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkDevice; +import org.lwjgl.vulkan.VkDeviceCreateInfo; +import org.lwjgl.vulkan.VkDeviceQueueCreateInfo; +import org.lwjgl.vulkan.VkPhysicalDeviceFeatures; + +import java.nio.FloatBuffer; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.vkCreateDevice; +import static org.lwjgl.vulkan.VK10.vkDestroyDevice; +import static org.lwjgl.vulkan.VK10.vkDeviceWaitIdle; + +public class ImVkDevice { + + private static final Logger LOGGER = Logger.getLogger(ImVkDevice.class.getName()); + + + private boolean samplerAnisotropy; + private ImVkPhysicalDevice physicalDevice; + private VkDevice device; + + private final Set extensions = new HashSet<>(); + + public void create() { + if (physicalDevice == null) { + throw new IllegalStateException("Cannot create device without a physical device set"); + } + + LOGGER.fine("creating vulkan device"); + createDevice(); + LOGGER.fine("Done creating vulkan device"); + } + + public void destroy() { + waitIdle(); + destroyDevice(); + } + + public void waitIdle() { + vkDeviceWaitIdle(device); + } + + private void createDevice() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkDeviceQueueCreateInfo.Buffer queues = initQueues(stack); + + samplerAnisotropy = physicalDevice.getPhysicalDeviceFeatures().samplerAnisotropy(); + + final VkPhysicalDeviceFeatures features = VkPhysicalDeviceFeatures.calloc(stack) + .samplerAnisotropy(samplerAnisotropy); + + final VkDeviceCreateInfo createInfo = VkDeviceCreateInfo.calloc(stack) + .sType$Default() + .pQueueCreateInfos(queues) + .pEnabledFeatures(features); + + if (getPhysicalDevice().getInstance().isValidationEnabled()) { + final PointerBuffer validationBuff = stack.mallocPointer(getPhysicalDevice().getInstance().getEnabledValidationLayers().size()); + int i = 0; + for (String validationLayer : getPhysicalDevice().getInstance().getEnabledValidationLayers()) { + validationBuff.put(i++, stack.ASCII(validationLayer)); + } + createInfo.ppEnabledLayerNames(validationBuff); + } else { + createInfo.ppEnabledLayerNames(null); + } + + extensions.clear(); + extensions.addAll(getPhysicalDevice().getRequiredExtensions()); + for (String optionalExtension : getPhysicalDevice().getOptionalExtensions()) { + if (getPhysicalDevice().getSupportedExtensions().contains(optionalExtension)) { + extensions.add(optionalExtension); + } else { + LOGGER.fine("Optional vulkan device extension not supported: " + optionalExtension); + } + } + + for (String extension : extensions) { + LOGGER.fine("Using vulkan device extension: " + extension); + } + + final PointerBuffer extensionBuff = stack.mallocPointer(extensions.size()); + int i = 0; + for (String extension : extensions) { + extensionBuff.put(i++, stack.ASCII(extension)); + } + createInfo.ppEnabledExtensionNames(extensionBuff); + + final PointerBuffer devicePointerBuff = stack.callocPointer(1); + vkOK(vkCreateDevice(getPhysicalDevice().getPhysicalDevice(), createInfo, null, devicePointerBuff)); + device = new VkDevice(devicePointerBuff.get(0), getPhysicalDevice().getPhysicalDevice(), createInfo); + } + } + + private VkDeviceQueueCreateInfo.Buffer initQueues(final MemoryStack stack) { + final Set uniqueQueueFamilies = new HashSet<>(); + uniqueQueueFamilies.add(getPhysicalDevice().getIndices().getGraphicsFamily()); + uniqueQueueFamilies.add(getPhysicalDevice().getIndices().getPresentFamily()); + + final VkDeviceQueueCreateInfo.Buffer queueBuff = VkDeviceQueueCreateInfo.calloc(uniqueQueueFamilies.size(), stack); + + int i = 0; + for (Integer queueFamily : uniqueQueueFamilies) { + final VkDeviceQueueCreateInfo queueCreateInfo = queueBuff.get(i++); + queueCreateInfo.sType$Default(); + queueCreateInfo.queueFamilyIndex(queueFamily); + final FloatBuffer queuePrioritiesBuff = stack.callocFloat(1); + queuePrioritiesBuff.put(1.0f); + queuePrioritiesBuff.flip(); + queueCreateInfo.pQueuePriorities(queuePrioritiesBuff); + } + + return queueBuff; + } + + private void destroyDevice() { + vkDestroyDevice(device, null); + device = null; + } + + public ImVkPhysicalDevice getPhysicalDevice() { + return physicalDevice; + } + + public void setPhysicalDevice(final ImVkPhysicalDevice physicalDevice) { + this.physicalDevice = physicalDevice; + } + + public VkDevice getDevice() { + return device; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkFence.java b/imgui-app/src/main/java/imgui/app/vk/ImVkFence.java new file mode 100644 index 00000000..d02cd2c1 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkFence.java @@ -0,0 +1,88 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkFenceCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_FENCE_CREATE_SIGNALED_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateFence; +import static org.lwjgl.vulkan.VK10.vkDestroyFence; +import static org.lwjgl.vulkan.VK10.vkResetFences; +import static org.lwjgl.vulkan.VK10.vkWaitForFences; + +public class ImVkFence { + + private static final Logger LOGGER = Logger.getLogger(ImVkFence.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + private boolean signaled = false; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create fence without a vulkan device"); + } + + createFence(); + } + + public void destroy() { + destroyFence(); + } + + private void createFence() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkFenceCreateInfo fenceCreateInfo = VkFenceCreateInfo.calloc(stack) + .sType$Default() + .flags(isSignaled() ? VK_FENCE_CREATE_SIGNALED_BIT : 0); + final LongBuffer longBuff = stack.callocLong(1); + vkOK( + vkCreateFence( + getDevice().getDevice(), + fenceCreateInfo, + null, + longBuff + ) + ); + nativeHandle = longBuff.get(); + } + } + + private void destroyFence() { + vkDestroyFence(getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public void waitFor() { + vkWaitForFences(getDevice().getDevice(), nativeHandle, true, Long.MAX_VALUE); + } + + public void reset() { + vkResetFences(getDevice().getDevice(), nativeHandle); + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public boolean isSignaled() { + return signaled; + } + + public void setSignaled(final boolean signaled) { + this.signaled = signaled; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkFrameBuffer.java b/imgui-app/src/main/java/imgui/app/vk/ImVkFrameBuffer.java new file mode 100644 index 00000000..89ffad8d --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkFrameBuffer.java @@ -0,0 +1,82 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkFramebufferCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateFramebuffer; +import static org.lwjgl.vulkan.VK10.vkDestroyFramebuffer; + +public class ImVkFrameBuffer { + private static final Logger LOGGER = Logger.getLogger(ImVkFrameBuffer.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkRenderPass renderPass; + + private LongBuffer attachments; + + public void create() { + if (attachments == null) { + throw new IllegalStateException("Frame Buffer must have attachments specified!"); + } + + if (renderPass == null) { + throw new IllegalStateException("Cannot create frame buffer without vulkan render pass"); + } + + LOGGER.fine("Creating vulkan frame buffer"); + createFrameBuffer(); + LOGGER.fine("Done creating vulkan frame buffer"); + } + + public void destroy() { + destroyFrameBuffer(); + } + + private void createFrameBuffer() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkFramebufferCreateInfo fci = VkFramebufferCreateInfo.calloc(stack) + .sType$Default() + .pAttachments(attachments) + .width(getRenderPass().getSwapchain().getExtent().width()) + .height(getRenderPass().getSwapchain().getExtent().height()) + .layers(1) + .renderPass(getRenderPass().getNativeHandle()); + final LongBuffer longBuff = stack.callocLong(1); + vkOK( + vkCreateFramebuffer(getRenderPass().getSwapchain().getGraphicsQueue().getDevice().getDevice(), fci, null, longBuff) + ); + nativeHandle = longBuff.get(); + } + } + + private void destroyFrameBuffer() { + vkDestroyFramebuffer(getRenderPass().getSwapchain().getGraphicsQueue().getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public ImVkRenderPass getRenderPass() { + return renderPass; + } + + public void setRenderPass(final ImVkRenderPass renderPass) { + this.renderPass = renderPass; + } + + public LongBuffer getAttachments() { + return attachments; + } + + public void setAttachments(final LongBuffer attachments) { + this.attachments = attachments; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkImage.java b/imgui-app/src/main/java/imgui/app/vk/ImVkImage.java new file mode 100644 index 00000000..86e527dd --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkImage.java @@ -0,0 +1,172 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkImageCreateInfo; +import org.lwjgl.vulkan.VkMemoryAllocateInfo; +import org.lwjgl.vulkan.VkMemoryRequirements; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SRGB; +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_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.vkAllocateMemory; +import static org.lwjgl.vulkan.VK10.vkBindImageMemory; +import static org.lwjgl.vulkan.VK10.vkCreateImage; +import static org.lwjgl.vulkan.VK10.vkDestroyImage; +import static org.lwjgl.vulkan.VK10.vkFreeMemory; +import static org.lwjgl.vulkan.VK10.vkGetImageMemoryRequirements; + +public class ImVkImage { + + private static final Logger LOGGER = Logger.getLogger(ImVkImage.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + private int format = VK_FORMAT_R8G8B8A8_SRGB; + private int mips = 1; + private int samples = 1; + private int layers = 1; + private int usage; + private int width = 0; + private int height = 0; + private long memory = VK_NULL_HANDLE; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create image without a device being set"); + } + + createImage(); + } + + public void destroy() { + destroyImage(); + } + + private void createImage() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkImageCreateInfo imageCreateInfo = VkImageCreateInfo.calloc(stack) + .sType$Default() + .imageType(VK_IMAGE_TYPE_2D) + .format(format) + .extent(it -> it + .width(getWidth()) + .height(getHeight()) + .depth(1) + ) + .mipLevels(mips) + .arrayLayers(layers) + .samples(samples) + .initialLayout(VK_IMAGE_LAYOUT_UNDEFINED) + .sharingMode(VK_SHARING_MODE_EXCLUSIVE) + .tiling(VK_IMAGE_TILING_OPTIMAL) + .usage(usage); + + final LongBuffer lp = stack.mallocLong(1); + vkOK(vkCreateImage(getDevice().getDevice(), imageCreateInfo, null, lp)); + nativeHandle = lp.get(0); + + // Get memory requirements for this object + final VkMemoryRequirements memReqs = VkMemoryRequirements.calloc(stack); + vkGetImageMemoryRequirements(getDevice().getDevice(), nativeHandle, memReqs); + + // Select memory size and type + final VkMemoryAllocateInfo memAlloc = VkMemoryAllocateInfo.calloc(stack) + .sType$Default() + .allocationSize(memReqs.size()) + .memoryTypeIndex(getDevice().getPhysicalDevice().memoryTypeFromProperties(memReqs.memoryTypeBits(), 0)); + + // Allocate memory + vkOK(vkAllocateMemory(getDevice().getDevice(), memAlloc, null, lp)); + memory = lp.get(0); + + // Bind memory + vkOK(vkBindImageMemory(getDevice().getDevice(), nativeHandle, memory, 0)); + } + } + + private void destroyImage() { + vkDestroyImage(getDevice().getDevice(), nativeHandle, null); + vkFreeMemory(getDevice().getDevice(), memory, null); + } + + public long getNativeHandle() { + return nativeHandle; + } + + public int getFormat() { + return format; + } + + public void setFormat(final int format) { + this.format = format; + } + + public int getMips() { + return mips; + } + + public void setMips(final int mips) { + this.mips = mips; + } + + public int getSamples() { + return samples; + } + + public void setSamples(final int samples) { + this.samples = samples; + } + + public int getLayers() { + return layers; + } + + public void setLayers(final int layers) { + this.layers = layers; + } + + public int getUsage() { + return usage; + } + + public void setUsage(final int usage) { + this.usage = usage; + } + + public long getMemory() { + return memory; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkImageView.java b/imgui-app/src/main/java/imgui/app/vk/ImVkImageView.java new file mode 100644 index 00000000..9917d079 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkImageView.java @@ -0,0 +1,144 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkImageViewCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateImageView; +import static org.lwjgl.vulkan.VK10.vkDestroyImageView; + +public class ImVkImageView { + + private static final Logger LOGGER = Logger.getLogger(ImVkImageView.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + private Integer aspectMask; + private int mips = 1; + private int baseArrayLayer = 0; + private Integer format; + private int layers = 1; + private int viewType = VK_IMAGE_VIEW_TYPE_2D; + private long image = VK_NULL_HANDLE; + + public void create() { + if (aspectMask == null || format == null) { + throw new IllegalStateException("To create an image view, aspectMask and format must be specified."); + } + + if (image == VK_NULL_HANDLE) { + throw new IllegalStateException("Image must be a valid address to a vulkan image!"); + } + + if (device == null) { + throw new IllegalStateException("Cannot create image view without a device being set"); + } + + createImageView(); + } + + public void destroy() { + destroyImageView(); + } + + private void createImageView() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkImageViewCreateInfo createInfo = VkImageViewCreateInfo.calloc(stack) + .sType$Default() + .image(image) + .viewType(viewType) + .format(format) + .subresourceRange(it -> it + .aspectMask(aspectMask) + .baseMipLevel(0) + .levelCount(mips) + .baseArrayLayer(baseArrayLayer) + .layerCount(layers) + ); + + final LongBuffer imageBuff = stack.callocLong(1); + vkOK(vkCreateImageView(getDevice().getDevice(), createInfo, null, imageBuff)); + nativeHandle = imageBuff.get(); + } + } + + private void destroyImageView() { + vkDestroyImageView(getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public Integer getAspectMask() { + return aspectMask; + } + + public void setAspectMask(final Integer aspectMask) { + this.aspectMask = aspectMask; + } + + public int getMips() { + return mips; + } + + public void setMips(final int mips) { + this.mips = mips; + } + + public int getBaseArrayLayer() { + return baseArrayLayer; + } + + public void setBaseArrayLayer(final int baseArrayLayer) { + this.baseArrayLayer = baseArrayLayer; + } + + public Integer getFormat() { + return format; + } + + public void setFormat(final Integer format) { + this.format = format; + } + + public int getLayers() { + return layers; + } + + public void setLayers(final int layers) { + this.layers = layers; + } + + public int getViewType() { + return viewType; + } + + public void setViewType(final int viewType) { + this.viewType = viewType; + } + + public long getImage() { + return image; + } + + public void setImage(final long image) { + this.image = image; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkInstance.java b/imgui-app/src/main/java/imgui/app/vk/ImVkInstance.java new file mode 100644 index 00000000..8835f981 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkInstance.java @@ -0,0 +1,293 @@ +package imgui.app.vk; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkAllocationCallbacks; +import org.lwjgl.vulkan.VkApplicationInfo; +import org.lwjgl.vulkan.VkDebugUtilsMessengerCreateInfoEXT; +import org.lwjgl.vulkan.VkExtensionProperties; +import org.lwjgl.vulkan.VkInstance; +import org.lwjgl.vulkan.VkInstanceCreateInfo; +import org.lwjgl.vulkan.VkLayerProperties; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_EXT_DEBUG_UTILS_EXTENSION_NAME; +import static org.lwjgl.vulkan.EXTDebugUtils.vkCreateDebugUtilsMessengerEXT; +import static org.lwjgl.vulkan.EXTDebugUtils.vkDestroyDebugUtilsMessengerEXT; +import static org.lwjgl.vulkan.VK10.VK_MAKE_VERSION; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateInstance; +import static org.lwjgl.vulkan.VK10.vkDestroyInstance; +import static org.lwjgl.vulkan.VK10.vkEnumerateInstanceExtensionProperties; +import static org.lwjgl.vulkan.VK10.vkEnumerateInstanceLayerProperties; +import static org.lwjgl.vulkan.VK12.VK_API_VERSION_1_2; + +public class ImVkInstance { + + private static final Logger LOGGER = Logger.getLogger(ImVkInstance.class.getName()); + + private VkInstance vkInstance; + private long nativeHandle = VK_NULL_HANDLE; + private boolean validationEnabled; + private VkDebugUtilsMessengerCreateInfoEXT callback; + private long callbackHandle = VK_NULL_HANDLE; + private final VkAllocationCallbacks allocationCallbacks = null; + + private String appName = "Dear ImGui Java"; + private int appVersionMajor = 0; + private int appVersionMinor = 0; + private int appVersionPatch = 0; + + private String engineName = "Dear ImGui Java"; + private int engineVersionMajor = 0; + private int engineVersionMinor = 0; + private int engineVersionPatch = 0; + + private final Set extensions = new HashSet<>(); + private final Set validationLayers = new HashSet<>(); + private final Set enabledValidationLayers = new HashSet<>(); + + public void create() { + if (validationEnabled) { + //Required extensions + extensions.add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + //Required validation layers + validationLayers.add("VK_LAYER_KHRONOS_validation"); + + //Build callback + callback = ImVkDebug.createDebugCallback(); + } + + //Create instance + createInstance(); + } + + private void createInstance() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkApplicationInfo appInfo = VkApplicationInfo.calloc(stack) + .sType$Default(); + + //Build app name + final String name = getAppName(); + final ByteBuffer nameBuff = stack.ASCIISafe(name); + appInfo.pApplicationName(nameBuff); + + //Build app version info + final int vkVersion = VK_MAKE_VERSION(getAppVersionMajor(), getAppVersionMinor(), getAppVersionPatch()); + appInfo.applicationVersion(vkVersion); + + //Build engine name + final String engName = getEngineName(); + final ByteBuffer engNameBuff = stack.ASCIISafe(engName); + appInfo.pEngineName(engNameBuff); + + //Build app version info + final int vkEngVersion = VK_MAKE_VERSION(getEngineVersionMajor(), getEngineVersionMinor(), getEngineVersionPatch()); + appInfo.engineVersion(vkEngVersion); + + //VK API Version + appInfo.apiVersion(VK_API_VERSION_1_2); + + final VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.calloc(stack) + .sType$Default() + .pApplicationInfo(appInfo); + + //Extensions + final List foundExtensions = new ArrayList<>(); + + final IntBuffer extensionCountBuff = stack.callocInt(1); + vkEnumerateInstanceExtensionProperties((ByteBuffer) null, extensionCountBuff, null); + final int extensionsCount = extensionCountBuff.get(); + final VkExtensionProperties.Buffer availableExtensions = VkExtensionProperties.calloc(extensionsCount, stack); + extensionCountBuff.flip(); + vkEnumerateInstanceExtensionProperties((ByteBuffer) null, extensionCountBuff, availableExtensions); + + for (VkExtensionProperties prop : availableExtensions) { + LOGGER.finest("Available Vulkan Extension: " + prop.extensionNameString()); + } + + for (String extensionName : extensions) { + boolean found = false; + for (VkExtensionProperties extension : availableExtensions) { + if (extensionName.equals(extension.extensionNameString())) { + found = true; + foundExtensions.add(extensionName); + LOGGER.fine("Using Vulkan Extension: " + extensionName); + break; + } + } + if (!found) { + LOGGER.log(Level.SEVERE, "Failed to load required Vulkan extension: " + extensionName); + } + } + + //Set extensions + final PointerBuffer extensionBuff = stack.mallocPointer(foundExtensions.size()); + for (int i = 0; i < foundExtensions.size(); i++) { + extensionBuff.put(i, stack.ASCII(foundExtensions.get(i))); + } + createInfo.ppEnabledExtensionNames(extensionBuff); + + //Validation layers + if (isValidationEnabled()) { + final IntBuffer layerCount = stack.callocInt(1); + vkEnumerateInstanceLayerProperties(layerCount, null); + final VkLayerProperties.Buffer availableLayers = VkLayerProperties.calloc(layerCount.get(), stack); + layerCount.flip(); + vkEnumerateInstanceLayerProperties(layerCount, availableLayers); + + for (VkLayerProperties layer : availableLayers) { + LOGGER.finest("Available Vulkan Validation Layer: " + layer.layerNameString()); + } + + enabledValidationLayers.clear(); + for (String validationLayer : validationLayers) { + boolean found = false; + for (VkLayerProperties layer : availableLayers) { + if (validationLayer.equals(layer.layerNameString())) { + found = true; + enabledValidationLayers.add(validationLayer); + LOGGER.fine("Using Vulkan Validation Layer: " + validationLayer); + } + } + if (!found) { + LOGGER.log(Level.SEVERE, "Failed to load required Vulkan layer: " + validationLayer); + } + } + + //Set layers + final PointerBuffer layerBuff = stack.mallocPointer(enabledValidationLayers.size()); + for (String validationLayer : enabledValidationLayers) { + layerBuff.put(stack.ASCII(validationLayer)); + } + layerBuff.flip(); + + createInfo.ppEnabledLayerNames(layerBuff); + createInfo.pNext(callback.address()); + } else { + createInfo.ppEnabledLayerNames(null); + createInfo.pNext(MemoryUtil.NULL); //No debugging info + } + + final PointerBuffer instancePointerBuff = stack.mallocPointer(1); + vkOK(vkCreateInstance(createInfo, allocationCallbacks, instancePointerBuff)); + + //Save native handle to vk instance + nativeHandle = instancePointerBuff.get(0); + vkInstance = new VkInstance(nativeHandle, createInfo); + + //Create validation callback + if (validationEnabled) { + final LongBuffer longBuff = stack.mallocLong(1); + vkOK(vkCreateDebugUtilsMessengerEXT(vkInstance, callback, null, longBuff)); + callbackHandle = longBuff.get(0); + } + } + } + + public void destroy() { + destroyInstance(); + } + + private void destroyInstance() { + if (validationEnabled) { + if (callbackHandle != VK_NULL_HANDLE) { + vkDestroyDebugUtilsMessengerEXT(vkInstance, callbackHandle, null); + } + } + allocationCallbacks.free(); + vkDestroyInstance(vkInstance, null); + } + + public VkInstance getInstance() { + return vkInstance; + } + + public String getAppName() { + return appName; + } + + public void setAppName(final String appName) { + this.appName = appName; + } + + public String getEngineName() { + return engineName; + } + + public void setEngineName(final String engineName) { + this.engineName = engineName; + } + + public int getEngineVersionMajor() { + return engineVersionMajor; + } + + public int getEngineVersionMinor() { + return engineVersionMinor; + } + + public int getEngineVersionPatch() { + return engineVersionPatch; + } + + public void setEngineVersion(final int major, final int minor, final int patch) { + this.engineVersionMajor = major; + this.engineVersionMinor = minor; + this.engineVersionPatch = patch; + } + + public int getAppVersionMajor() { + return appVersionMajor; + } + + public int getAppVersionMinor() { + return appVersionMinor; + } + + public int getAppVersionPatch() { + return appVersionPatch; + } + + public void setAppVersion(final int major, final int minor, final int patch) { + this.appVersionMajor = major; + this.appVersionMinor = minor; + this.appVersionPatch = patch; + } + + public boolean isValidationEnabled() { + return validationEnabled; + } + + public void setValidationEnabled(final boolean validationEnabled) { + this.validationEnabled = validationEnabled; + } + + public Set getExtensions() { + return extensions; + } + + public Set getValidationLayers() { + return validationLayers; + } + + public Set getEnabledValidationLayers() { + return enabledValidationLayers; + } + + public VkAllocationCallbacks getAllocationCallbacks() { + return allocationCallbacks; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkPhysicalDevice.java b/imgui-app/src/main/java/imgui/app/vk/ImVkPhysicalDevice.java new file mode 100644 index 00000000..b4e80868 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkPhysicalDevice.java @@ -0,0 +1,391 @@ +package imgui.app.vk; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkExtensionProperties; +import org.lwjgl.vulkan.VkMemoryType; +import org.lwjgl.vulkan.VkPhysicalDevice; +import org.lwjgl.vulkan.VkPhysicalDeviceFeatures; +import org.lwjgl.vulkan.VkPhysicalDeviceIDProperties; +import org.lwjgl.vulkan.VkPhysicalDeviceMemoryProperties; +import org.lwjgl.vulkan.VkPhysicalDeviceProperties; +import org.lwjgl.vulkan.VkPhysicalDeviceProperties2; +import org.lwjgl.vulkan.VkQueueFamilyProperties; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSurfaceFormatKHR; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Logger; + +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceCapabilitiesKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME; +import static org.lwjgl.vulkan.VK10.VK_MAX_MEMORY_TYPES; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; +import static org.lwjgl.vulkan.VK10.VK_QUEUE_GRAPHICS_BIT; +import static org.lwjgl.vulkan.VK10.VK_TRUE; +import static org.lwjgl.vulkan.VK10.vkEnumerateDeviceExtensionProperties; +import static org.lwjgl.vulkan.VK10.vkEnumeratePhysicalDevices; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceFeatures; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceMemoryProperties; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceProperties; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceQueueFamilyProperties; +import static org.lwjgl.vulkan.VK11.vkGetPhysicalDeviceProperties2; + +public class ImVkPhysicalDevice { + + private static final Logger LOGGER = Logger.getLogger(ImVkPhysicalDevice.class.getName()); + + private ImVkInstance instance; + private long surface = VK_NULL_HANDLE; + private VkPhysicalDevice physicalDevice; + private VkPhysicalDeviceMemoryProperties memoryProperties; + private VkPhysicalDeviceFeatures physicalDeviceFeatures; + + private VkPhysicalDeviceProperties2 physicalDeviceProperties2; + private VkPhysicalDeviceIDProperties physicalDeviceIDProperties; + private QueueFamilyIndices indices; + private SwapChainDetails details; + + private final Set requiredExtensions = new HashSet<>(); + private final Set optionalExtensions = new HashSet<>(); + private final Set supportedExtensions = new HashSet<>(); + + public void create() { + if (instance == null) { + throw new IllegalStateException("Cannot create physical device without a vulkan instance"); + } + + if (surface == VK_NULL_HANDLE) { + throw new IllegalStateException("Cannot create physical device without a surface"); + } + + LOGGER.fine("Loading vulkan physical device"); + + getRequiredExtensions().add(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + + pickPhysicalDevice(); + getDeviceInfo(); + + LOGGER.fine("Done creating vulkan physical device"); + } + + public void destroy() { + details.free(); + details = null; + + indices = null; + + physicalDevice = null; + + getRequiredExtensions().clear(); + getOptionalExtensions().clear(); + getSupportedExtensions().clear(); + physicalDeviceFeatures.free(); + physicalDeviceProperties2.free(); + physicalDeviceIDProperties.free(); + } + + public void resize() { + details.free(); + details = findSwapChainDetails(physicalDevice); + } + + private void pickPhysicalDevice() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer deviceCountBuff = stack.callocInt(1); + vkEnumeratePhysicalDevices(instance.getInstance(), deviceCountBuff, null); + final int deviceCount = deviceCountBuff.get(); + deviceCountBuff.flip(); + + if (deviceCount < 1) { + LOGGER.severe("This system does not have any GPUs that support Vulkan!"); + return; + } else { + LOGGER.finest("Vulkan GPUs found: " + deviceCount); + } + + final PointerBuffer devicePointerBuff = stack.callocPointer(deviceCount); + vkEnumeratePhysicalDevices(instance.getInstance(), deviceCountBuff, devicePointerBuff); + + final HashMap scores = new HashMap<>(); + + for (int i = 0; i < deviceCount; i++) { + final VkPhysicalDevice device = new VkPhysicalDevice(devicePointerBuff.get(i), instance.getInstance()); + scores.put(device, rateDeviceSuitability(device)); + } + + this.physicalDevice = Collections.max(scores.entrySet(), Comparator.comparingInt(Map.Entry::getValue)).getKey(); + this.details = findSwapChainDetails(physicalDevice); + this.indices = findQueueFamilies(physicalDevice); + } + } + + private int rateDeviceSuitability(final VkPhysicalDevice device) { + int score = 0; + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkPhysicalDeviceProperties deviceProperties = VkPhysicalDeviceProperties.calloc(stack); + vkGetPhysicalDeviceProperties(device, deviceProperties); + + final VkPhysicalDeviceFeatures deviceFeatures = VkPhysicalDeviceFeatures.calloc(stack); + vkGetPhysicalDeviceFeatures(device, deviceFeatures); + + if (deviceProperties.deviceType() == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { + score += 1000; + } + + if (deviceFeatures.samplerAnisotropy()) { + score += 100; + } + + final QueueFamilyIndices indices = findQueueFamilies(device); + + if (Objects.equals(indices.getPresentFamily(), indices.getGraphicsFamily())) { + score += 500; + } + + LOGGER.finest("Checking if device is suitable"); + if (!isDeviceSuitable(device)) { + LOGGER.fine("GPU: " + deviceProperties.deviceNameString() + ", Score: Disqualified"); + } else { + LOGGER.fine("GPU: " + deviceProperties.deviceNameString() + ", Score: " + score); + } + } + return score; + } + + private boolean isDeviceSuitable(final VkPhysicalDevice device) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkPhysicalDeviceProperties deviceProperties = VkPhysicalDeviceProperties.calloc(stack); + vkGetPhysicalDeviceProperties(device, deviceProperties); + + final VkPhysicalDeviceFeatures deviceFeatures = VkPhysicalDeviceFeatures.calloc(stack); + vkGetPhysicalDeviceFeatures(device, deviceFeatures); + + final boolean extensionsSupported = checkDeviceExtensionSupport(device); + boolean swapChainAdequate = false; + if (extensionsSupported) { + final SwapChainDetails details = findSwapChainDetails(device); + swapChainAdequate = details.getFormats().capacity() != 0 && details.getPresentModes().capacity() != 0; + details.free(); + } + + return extensionsSupported + && swapChainAdequate + && deviceProperties.deviceType() == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + && deviceFeatures.geometryShader() + && findQueueFamilies(device).isComplete(); + } + } + + private QueueFamilyIndices findQueueFamilies(final VkPhysicalDevice device) { + final QueueFamilyIndices indices = new QueueFamilyIndices(); + try (MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer queueFamilyCountBuff = stack.callocInt(1); + vkGetPhysicalDeviceQueueFamilyProperties(device, queueFamilyCountBuff, null); + final int queueFamilyCount = queueFamilyCountBuff.get(); + queueFamilyCountBuff.flip(); + + + final VkQueueFamilyProperties.Buffer queueFamilyProperties = VkQueueFamilyProperties.calloc(queueFamilyCount, stack); + vkGetPhysicalDeviceQueueFamilyProperties(device, queueFamilyCountBuff, queueFamilyProperties); + + int i = 0; + for (VkQueueFamilyProperties queueFamily : queueFamilyProperties) { + if ((queueFamily.queueFlags() & VK_QUEUE_GRAPHICS_BIT) != 0) { + indices.setGraphicsFamily(i); + } + + final IntBuffer vkBool = stack.callocInt(1); + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, vkBool); + + if (vkBool.get() == VK_TRUE) { + indices.setPresentFamily(i); + } + + if (indices.isComplete()) { + break; + } + i++; + } + } + return indices; + } + + private boolean checkDeviceExtensionSupport(final VkPhysicalDevice device) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer extensionCountBuff = stack.callocInt(1); + vkEnumerateDeviceExtensionProperties(device, (ByteBuffer) null, extensionCountBuff, null); + + final int extensionCount = extensionCountBuff.get(); + extensionCountBuff.flip(); + + final VkExtensionProperties.Buffer deviceExtensionPointerBuff = VkExtensionProperties.calloc(extensionCount); + vkEnumerateDeviceExtensionProperties(device, (ByteBuffer) null, extensionCountBuff, deviceExtensionPointerBuff); + + final Set requiredExtensions = new HashSet<>(getRequiredExtensions()); + + //If the extension is a required extension, remove it from the list. + for (VkExtensionProperties prop : deviceExtensionPointerBuff) { + requiredExtensions.remove(prop.extensionNameString()); + } + + return requiredExtensions.isEmpty(); //If all extensions are found, the list will be empty. + } + } + + private SwapChainDetails findSwapChainDetails(final VkPhysicalDevice device) { + final SwapChainDetails swapChainDetails = new SwapChainDetails(); + try (MemoryStack stack = MemoryStack.stackPush()) { + //Get capabilities + final VkSurfaceCapabilitiesKHR capabilitiesKHR = VkSurfaceCapabilitiesKHR.calloc(); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, capabilitiesKHR); + swapChainDetails.setCapabilities(capabilitiesKHR); + + //Get formats + final IntBuffer formatsCountBuff = stack.callocInt(1); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, formatsCountBuff, null); + final int formatCount = formatsCountBuff.get(); + formatsCountBuff.flip(); + + if (formatCount != 0) { + final VkSurfaceFormatKHR.Buffer formatBuffer = VkSurfaceFormatKHR.calloc(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, formatsCountBuff, formatBuffer); + swapChainDetails.setFormats(formatBuffer); + } + + final IntBuffer presentModeCountBuff = stack.callocInt(1); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, presentModeCountBuff, null); + final int presentModeCount = presentModeCountBuff.get(); + presentModeCountBuff.flip(); + + if (presentModeCount != 0) { + final IntBuffer presentModes = MemoryUtil.memAllocInt(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, presentModeCountBuff, presentModes); + swapChainDetails.setPresentModes(presentModes); + } + } + return swapChainDetails; + } + + public int memoryTypeFromProperties(final int typeBits, final int reqsMask) { + int result = -1; + int typeBitsTemp = typeBits; + final VkMemoryType.Buffer memoryTypes = getMemoryProperties().memoryTypes(); + for (int i = 0; i < VK_MAX_MEMORY_TYPES; i++) { + if ((typeBitsTemp & 1) == 1 && (memoryTypes.get(i).propertyFlags() & reqsMask) == reqsMask) { + result = i; + break; + } + typeBitsTemp >>= 1; + } + if (result < 0) { + throw new RuntimeException("Failed to find memoryType"); + } + return result; + } + + private void getDeviceInfo() { + try (MemoryStack stack = MemoryStack.stackPush()) { + // Get Memory information and properties + memoryProperties = VkPhysicalDeviceMemoryProperties.calloc(); + vkGetPhysicalDeviceMemoryProperties(physicalDevice, memoryProperties); + + //List extensions + final IntBuffer extensionCountBuff = stack.callocInt(1); + vkEnumerateDeviceExtensionProperties(physicalDevice, (ByteBuffer) null, extensionCountBuff, null); + + final int extensionCount = extensionCountBuff.get(); + extensionCountBuff.flip(); + + final VkExtensionProperties.Buffer deviceExtensionPointerBuff = VkExtensionProperties.calloc(extensionCount); + vkEnumerateDeviceExtensionProperties(physicalDevice, (ByteBuffer) null, extensionCountBuff, deviceExtensionPointerBuff); + + supportedExtensions.clear(); + for (VkExtensionProperties prop : deviceExtensionPointerBuff) { + LOGGER.fine("Available Vulkan Device Extension: " + prop.extensionNameString()); + supportedExtensions.add(prop.extensionNameString()); + } + + //Get features and properties + physicalDeviceFeatures = VkPhysicalDeviceFeatures.calloc(); + vkGetPhysicalDeviceFeatures(physicalDevice, physicalDeviceFeatures); + + physicalDeviceIDProperties = VkPhysicalDeviceIDProperties.calloc() + .sType$Default(); + physicalDeviceProperties2 = VkPhysicalDeviceProperties2.calloc() + .sType$Default() + .pNext(physicalDeviceIDProperties); + vkGetPhysicalDeviceProperties2(physicalDevice, physicalDeviceProperties2); + } + } + + public ImVkInstance getInstance() { + return instance; + } + + public void setInstance(final ImVkInstance instance) { + this.instance = instance; + } + + public long getSurface() { + return surface; + } + + public void setSurface(final long surface) { + this.surface = surface; + } + + public VkPhysicalDevice getPhysicalDevice() { + return physicalDevice; + } + + public VkPhysicalDeviceMemoryProperties getMemoryProperties() { + return memoryProperties; + } + + public VkPhysicalDeviceFeatures getPhysicalDeviceFeatures() { + return physicalDeviceFeatures; + } + + public VkPhysicalDeviceProperties2 getPhysicalDeviceProperties2() { + return physicalDeviceProperties2; + } + + public VkPhysicalDeviceIDProperties getPhysicalDeviceIDProperties() { + return physicalDeviceIDProperties; + } + + public QueueFamilyIndices getIndices() { + return indices; + } + + public SwapChainDetails getDetails() { + return details; + } + + public Set getRequiredExtensions() { + return requiredExtensions; + } + + public Set getOptionalExtensions() { + return optionalExtensions; + } + + public Set getSupportedExtensions() { + return supportedExtensions; + } +} + diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkPipelineCache.java b/imgui-app/src/main/java/imgui/app/vk/ImVkPipelineCache.java new file mode 100644 index 00000000..552e7ee7 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkPipelineCache.java @@ -0,0 +1,63 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkPipelineCacheCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreatePipelineCache; +import static org.lwjgl.vulkan.VK10.vkDestroyPipelineCache; + +public class ImVkPipelineCache { + + private static final Logger LOGGER = Logger.getLogger(ImVkPipelineCache.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create pipeline cache without a vulkan device"); + } + + LOGGER.fine("creating vulkan pipeline cache"); + createPipelineCache(); + LOGGER.fine("Done creating vulkan pipeline cache"); + } + + public void destroy() { + destroyPipelineCache(); + } + + private void createPipelineCache() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkPipelineCacheCreateInfo createInfo = VkPipelineCacheCreateInfo.calloc(stack) + .sType$Default(); + + final LongBuffer lp = stack.mallocLong(1); + vkOK(vkCreatePipelineCache(getDevice().getDevice(), createInfo, null, lp)); + nativeHandle = lp.get(0); + } + } + + private void destroyPipelineCache() { + vkDestroyPipelineCache(getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkQueue.java b/imgui-app/src/main/java/imgui/app/vk/ImVkQueue.java new file mode 100644 index 00000000..4dc40b77 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkQueue.java @@ -0,0 +1,117 @@ +package imgui.app.vk; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkQueue; +import org.lwjgl.vulkan.VkSubmitInfo; + +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkGetDeviceQueue; +import static org.lwjgl.vulkan.VK10.vkQueueSubmit; +import static org.lwjgl.vulkan.VK10.vkQueueWaitIdle; + +public class ImVkQueue { + + private static final Logger LOGGER = Logger.getLogger(ImVkQueue.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + private VkQueue queue; + + private ImVkDevice device; + private Integer familyIndex = -1; + private Integer index = 0; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot get a queue without a vulkan device"); + } + + if (familyIndex < 0) { + throw new IllegalStateException("Index must be specified to get a vulkan queue"); + } + + LOGGER.fine("Loading vulkan queue"); + createQueue(); + LOGGER.fine("Done loading vulkan queue"); + } + + private void createQueue() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final PointerBuffer queuePointerBuff = stack.callocPointer(1); + vkGetDeviceQueue(getDevice().getDevice(), familyIndex, index, queuePointerBuff); + nativeHandle = queuePointerBuff.get(); + queue = new VkQueue(nativeHandle, getDevice().getDevice()); + } + } + + public void destroy() { + destroyQueue(); + } + + private void destroyQueue() { + queue = null; + nativeHandle = VK_NULL_HANDLE; + } + + public void waitIdle() { + vkQueueWaitIdle(queue); + } + + public void submit(final PointerBuffer commandBuffers, final LongBuffer waitSemaphores, final IntBuffer dstStageMasks, final LongBuffer signalSemaphores, final ImVkFence fence) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkSubmitInfo submitInfo = VkSubmitInfo.calloc(stack) + .sType$Default() + .pCommandBuffers(commandBuffers) + .pSignalSemaphores(signalSemaphores); + + if (waitSemaphores != null) { + submitInfo.waitSemaphoreCount(waitSemaphores.capacity()) + .pWaitSemaphores(waitSemaphores) + .pWaitDstStageMask(dstStageMasks); + } else { + submitInfo.waitSemaphoreCount(0); + } + + final long fenceHandle = fence != null ? fence.getNativeHandle() : VK_NULL_HANDLE; + + vkOK(vkQueueSubmit(queue, submitInfo, fenceHandle)); + } + } + + public long getNativeHandle() { + return nativeHandle; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } + + public Integer getFamilyIndex() { + return familyIndex; + } + + public void setFamilyIndex(final Integer familyIndex) { + this.familyIndex = familyIndex; + } + + public Integer getIndex() { + return index; + } + + public void setIndex(final Integer index) { + this.index = index; + } + + public VkQueue getQueue() { + return queue; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkRenderPass.java b/imgui-app/src/main/java/imgui/app/vk/ImVkRenderPass.java new file mode 100644 index 00000000..e9568b45 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkRenderPass.java @@ -0,0 +1,131 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkAttachmentDescription; +import org.lwjgl.vulkan.VkAttachmentReference; +import org.lwjgl.vulkan.VkRenderPassCreateInfo; +import org.lwjgl.vulkan.VkSubpassDependency; +import org.lwjgl.vulkan.VkSubpassDescription; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_LOAD_OP_CLEAR; +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_D32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_GRAPHICS; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK10.VK_SUBPASS_EXTERNAL; +import static org.lwjgl.vulkan.VK10.vkCreateRenderPass; +import static org.lwjgl.vulkan.VK10.vkDestroyRenderPass; + +public class ImVkRenderPass { + private static final Logger LOGGER = Logger.getLogger(ImVkRenderPass.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkSwapchain swapchain; + + public void create() { + if (swapchain == null) { + throw new IllegalStateException("Cannot create render pass without a vulkan swapchain"); + } + + LOGGER.fine("Creating vulkan render pass"); + createRenderPass(); + LOGGER.fine("Done creating vulkan render pass"); + } + + public void destroy() { + destroyRenderPass(); + } + + private void createRenderPass() { + try (MemoryStack stack = MemoryStack.stackPush()) { + + final VkAttachmentDescription.Buffer attachmentBuffer = VkAttachmentDescription.calloc(2, stack); + //Color + attachmentBuffer.get(0) + .format(getSwapchain().getSurfaceFormat().format()) + .samples(VK_SAMPLE_COUNT_1_BIT) + .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .storeOp(VK_ATTACHMENT_STORE_OP_STORE) + //.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE) + //.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE) + .initialLayout(VK_IMAGE_LAYOUT_UNDEFINED) + .finalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + //Depth + attachmentBuffer.get(1) + .format(VK_FORMAT_D32_SFLOAT) + .samples(VK_SAMPLE_COUNT_1_BIT) + .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .storeOp(VK_ATTACHMENT_STORE_OP_DONT_CARE) + .initialLayout(VK_IMAGE_LAYOUT_UNDEFINED) + .finalLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + final VkAttachmentReference colorAttachmentRef = VkAttachmentReference.calloc(stack) + .attachment(0) + .layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + final VkAttachmentReference.Buffer colorAttachmentRefBuff = VkAttachmentReference.calloc(1, stack) + .put(colorAttachmentRef) + .flip(); + + final VkAttachmentReference depthReference = VkAttachmentReference.malloc(stack) + .attachment(1) + .layout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + final VkSubpassDescription.Buffer subpassBuff = VkSubpassDescription.calloc(1, stack); + subpassBuff.get(0) + .colorAttachmentCount(colorAttachmentRefBuff.remaining()) + .pColorAttachments(colorAttachmentRefBuff) + .pDepthStencilAttachment(depthReference) + .pipelineBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS); + + final VkSubpassDependency.Buffer subpassDependencyBuff = VkSubpassDependency.calloc(1); + subpassDependencyBuff.get(0) + .srcSubpass(VK_SUBPASS_EXTERNAL) + .dstSubpass(0) + .srcStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT) + .dstStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT) + .srcAccessMask(0) + .dstAccessMask(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + + final VkRenderPassCreateInfo renderPassInfo = VkRenderPassCreateInfo.calloc(stack) + .sType$Default() + .pAttachments(attachmentBuffer) + .pSubpasses(subpassBuff) + .pDependencies(subpassDependencyBuff); + + final LongBuffer renderPassBuff = stack.callocLong(1); + vkOK(vkCreateRenderPass(getSwapchain().getGraphicsQueue().getDevice().getDevice(), renderPassInfo, null, renderPassBuff)); + nativeHandle = renderPassBuff.get(); + } + } + + private void destroyRenderPass() { + vkDestroyRenderPass(getSwapchain().getGraphicsQueue().getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public ImVkSwapchain getSwapchain() { + return swapchain; + } + + public void setSwapchain(final ImVkSwapchain swapchain) { + this.swapchain = swapchain; + } + + public long getNativeHandle() { + return nativeHandle; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkSemaphore.java b/imgui-app/src/main/java/imgui/app/vk/ImVkSemaphore.java new file mode 100644 index 00000000..eb274c92 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkSemaphore.java @@ -0,0 +1,69 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkSemaphoreCreateInfo; + +import java.nio.LongBuffer; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.vkCreateSemaphore; +import static org.lwjgl.vulkan.VK10.vkDestroySemaphore; + +public class ImVkSemaphore { + + private static final Logger LOGGER = Logger.getLogger(ImVkSemaphore.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkDevice device; + + public void create() { + if (device == null) { + throw new IllegalStateException("Cannot create semaphore without a vulkan device"); + } + createSemaphore(); + } + + public void destroy() { + destroySemaphore(); + } + + private void createSemaphore() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkSemaphoreCreateInfo semaphoreCreateInfo = VkSemaphoreCreateInfo.calloc(stack) + .sType$Default(); + final LongBuffer longBuff = stack.callocLong(1); + vkOK( + vkCreateSemaphore(getDevice().getDevice(), + semaphoreCreateInfo, + null, + longBuff + ) + ); + nativeHandle = longBuff.get(); + } + } + + private void destroySemaphore() { + vkDestroySemaphore(getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + public long getNativeHandle() { + return nativeHandle; + } + + public void setNativeHandle(final long nativeHandle) { + this.nativeHandle = nativeHandle; + } + + public ImVkDevice getDevice() { + return device; + } + + public void setDevice(final ImVkDevice device) { + this.device = device; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/ImVkSwapchain.java b/imgui-app/src/main/java/imgui/app/vk/ImVkSwapchain.java new file mode 100644 index 00000000..c254571c --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/ImVkSwapchain.java @@ -0,0 +1,355 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkPresentInfoKHR; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSurfaceFormatKHR; +import org.lwjgl.vulkan.VkSwapchainCreateInfoKHR; + +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import static imgui.app.vk.ImVkDebug.vkOK; +import static org.lwjgl.system.MemoryUtil.NULL; +import static org.lwjgl.vulkan.KHRSurface.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_FIFO_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_IMMEDIATE_KHR; +import static org.lwjgl.vulkan.KHRSurface.VK_PRESENT_MODE_MAILBOX_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_ERROR_OUT_OF_DATE_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_SUBOPTIMAL_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkAcquireNextImageKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkCreateSwapchainKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkDestroySwapchainKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkGetSwapchainImagesKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkQueuePresentKHR; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_CONCURRENT; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; + +public class ImVkSwapchain { + + private static final Logger LOGGER = Logger.getLogger(ImVkSwapchain.class.getName()); + + private long nativeHandle = VK_NULL_HANDLE; + + private ImVkQueue graphicsQueue; + private long surface = VK_NULL_HANDLE; + private boolean vsync = false; + private int width = 0; + private int height = 0; + private VkExtent2D extent; + private VkSurfaceFormatKHR surfaceFormat; + private LongBuffer images; + private final List imageViews = new ArrayList<>(); + private ImVkSemaphore[] imageAvailableSemaphores; + private ImVkSemaphore[] renderFinishedSemaphores; + private int currentFrame; + + public void create() { + if (graphicsQueue == null) { + throw new IllegalStateException("Cannot create swapchain without a vulkan graphics queue"); + } + + if (surface == VK_NULL_HANDLE) { + throw new IllegalStateException("Cannot create swapchain without a vulkan surface"); + } + + LOGGER.fine("Loading vulkan swapchain"); + createSwapChain(); + createImageViews(); + createSemaphores(); + LOGGER.fine("Done loading vulkan swapchain"); + } + + public void destroy() { + destroySemaphores(); + destroyImageViews(); + destroySwapChain(); + getExtent().free(); + MemoryUtil.memFree(images); + } + + private void createSwapChain() { + try (MemoryStack stack = MemoryStack.stackPush()) { + final SwapChainDetails swapChainSupport = getGraphicsQueue().getDevice().getPhysicalDevice().getDetails(); + final VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.getFormats()); + final int presentMode = isVsync() ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR; + final VkExtent2D extent = chooseSwapExtent(swapChainSupport.getCapabilities()); + + int imageCount = swapChainSupport.getCapabilities().minImageCount() + 1; + if (swapChainSupport.getCapabilities().maxImageCount() > 0 && imageCount > swapChainSupport.getCapabilities().maxImageCount()) { + imageCount = swapChainSupport.getCapabilities().maxImageCount(); + } + + final VkSwapchainCreateInfoKHR creatInfo = VkSwapchainCreateInfoKHR.calloc(stack) + .sType$Default() + .surface(surface) + .minImageCount(imageCount) + .imageFormat(surfaceFormat.format()) + .imageColorSpace(surfaceFormat.colorSpace()) + .imageExtent(extent) + .imageArrayLayers(1); + + //For post processing we will need: VK_IMAGE_USAGE_TRANSFER_DST_BIT + creatInfo.imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + + if (getGraphicsQueue().getDevice().getPhysicalDevice().getIndices().getGraphicsFamily() != getGraphicsQueue().getDevice().getPhysicalDevice().getIndices().getPresentFamily()) { + creatInfo.imageSharingMode(VK_SHARING_MODE_CONCURRENT); + final IntBuffer queueFamilyIndicesBuff = stack.callocInt(2); + queueFamilyIndicesBuff.put(0, getGraphicsQueue().getDevice().getPhysicalDevice().getIndices().getGraphicsFamily()); + queueFamilyIndicesBuff.put(1, getGraphicsQueue().getDevice().getPhysicalDevice().getIndices().getPresentFamily()); + creatInfo.pQueueFamilyIndices(queueFamilyIndicesBuff); + } else { + creatInfo.imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) + .pQueueFamilyIndices(null); + } + + creatInfo.preTransform(swapChainSupport.getCapabilities().currentTransform()) + .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + .presentMode(presentMode) + .clipped(true) + .oldSwapchain(VK_NULL_HANDLE); + + final LongBuffer swapchainBuff = stack.callocLong(1); + vkOK(vkCreateSwapchainKHR(getGraphicsQueue().getDevice().getDevice(), creatInfo, null, swapchainBuff)); + nativeHandle = swapchainBuff.get(); + + //Get swap chain images + final IntBuffer imageCountBuff = stack.callocInt(1); + vkOK(vkGetSwapchainImagesKHR(getGraphicsQueue().getDevice().getDevice(), nativeHandle, imageCountBuff, null)); + final LongBuffer imagesBuff = MemoryUtil.memAllocLong(imageCountBuff.get(0)); + vkOK(vkGetSwapchainImagesKHR(getGraphicsQueue().getDevice().getDevice(), nativeHandle, imageCountBuff, imagesBuff)); + + LOGGER.finer("Got swapchain images: " + imageCountBuff.get(0)); + for (int i = 0; i < imagesBuff.capacity(); i++) { + LOGGER.finer("Image: " + imagesBuff.get(i)); + } + + this.extent = extent; + this.surfaceFormat = surfaceFormat; + this.images = imagesBuff; + } + } + + private void createImageViews() { + imageViews.clear(); + for (int i = 0; i < images.capacity(); i++) { + final ImVkImageView imageView = new ImVkImageView(); + imageView.setDevice(getGraphicsQueue().getDevice()); + imageView.setImage(images.get(i)); + imageView.setAspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + imageView.setViewType(VK_IMAGE_VIEW_TYPE_2D); + imageView.setFormat(surfaceFormat.format()); + imageView.setBaseArrayLayer(0); + imageView.setMips(1); + imageView.create(); + imageViews.add(imageView); + } + } + + + private void createSemaphores() { + imageAvailableSemaphores = new ImVkSemaphore[imageViews.size()]; + renderFinishedSemaphores = new ImVkSemaphore[imageViews.size()]; + for (int i = 0; i < imageViews.size(); i++) { + imageAvailableSemaphores[i] = new ImVkSemaphore(); + imageAvailableSemaphores[i].setDevice(getGraphicsQueue().getDevice()); + imageAvailableSemaphores[i].create(); + renderFinishedSemaphores[i] = new ImVkSemaphore(); + renderFinishedSemaphores[i].setDevice(getGraphicsQueue().getDevice()); + renderFinishedSemaphores[i].create(); + } + } + + private VkExtent2D chooseSwapExtent(final VkSurfaceCapabilitiesKHR capabilities) { + final VkExtent2D actualExtent = VkExtent2D.calloc(); + if (capabilities.currentExtent().width() != 0xffffffff) { + actualExtent.set(capabilities.currentExtent()); + return actualExtent; + } else { + actualExtent.width( + Math.max( + capabilities.minImageExtent().width(), + Math.min( + capabilities.maxImageExtent().width(), + getWidth() + ) + ) + ); + actualExtent.height( + Math.max( + capabilities.minImageExtent().height(), + Math.min( + capabilities.maxImageExtent().height(), + getHeight() + ) + ) + ); + return actualExtent; + } + } + + private int chooseSwapPresentMode(final IntBuffer modes) { + for (int i = 0; i < modes.capacity(); i++) { + if (modes.get(i) == VK_PRESENT_MODE_MAILBOX_KHR) { + return VK_PRESENT_MODE_MAILBOX_KHR; + } + } + return VK_PRESENT_MODE_FIFO_KHR; + } + + private VkSurfaceFormatKHR chooseSwapSurfaceFormat(final VkSurfaceFormatKHR.Buffer formats) { + VkSurfaceFormatKHR format = formats.get(0); + for (VkSurfaceFormatKHR f : formats) { + if (f.format() == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace() == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + format = f; + break; + } + } + return format; + } + + private void destroySwapChain() { + vkDestroySwapchainKHR(getGraphicsQueue().getDevice().getDevice(), nativeHandle, null); + nativeHandle = VK_NULL_HANDLE; + } + + private void destroyImageViews() { + imageViews.forEach(ImVkImageView::destroy); + } + + private void destroySemaphores() { + for (ImVkSemaphore semaphore : imageAvailableSemaphores) { + semaphore.destroy(); + } + for (ImVkSemaphore semaphore : renderFinishedSemaphores) { + semaphore.destroy(); + } + } + + public boolean nextImage() { + boolean resize = false; + try (MemoryStack stack = MemoryStack.stackPush()) { + final IntBuffer ip = stack.mallocInt(1); + final int err = vkAcquireNextImageKHR(getGraphicsQueue().getDevice().getDevice(), nativeHandle, ~0L, imageAvailableSemaphores[currentFrame].getNativeHandle(), NULL, ip); + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + resize = true; + } else if (err != VK_SUCCESS) { + throw new RuntimeException("Failed to acquire image: " + err); + } + /*else if (err == VK_SUBOPTIMAL_KHR) { + // Not optimal but swapchain can still be used + }*/ + currentFrame = ip.get(); + } + + return resize; + } + + public boolean presentImage() { + boolean resize = false; + try (MemoryStack stack = MemoryStack.stackPush()) { + final VkPresentInfoKHR present = VkPresentInfoKHR.calloc(stack) + .sType$Default() + .pWaitSemaphores(stack.longs(renderFinishedSemaphores[currentFrame].getNativeHandle())) + .swapchainCount(1) + .pSwapchains(stack.longs(nativeHandle)) + .pImageIndices(stack.ints(currentFrame)); + + final int err = vkQueuePresentKHR(getGraphicsQueue().getQueue(), present); + if (err == VK_ERROR_OUT_OF_DATE_KHR) { + resize = true; + } else if (err == VK_SUBOPTIMAL_KHR) { + // Not optimal but swap chain can still be used + LOGGER.warning("Vulkan KHR Suboptimal"); + } else { + vkOK(err); + } + } + currentFrame = (currentFrame + 1) % imageViews.size(); + return resize; + } + + public VkExtent2D getExtent() { + return extent; + } + + public VkSurfaceFormatKHR getSurfaceFormat() { + return surfaceFormat; + } + + public LongBuffer getImages() { + return images; + } + + public int getCurrentFrame() { + return currentFrame; + } + + public ImVkSemaphore[] getImageAvailableSemaphores() { + return imageAvailableSemaphores; + } + + public ImVkSemaphore[] getRenderFinishedSemaphores() { + return renderFinishedSemaphores; + } + + public List getImageViews() { + return imageViews; + } + + public ImVkQueue getGraphicsQueue() { + return graphicsQueue; + } + + public void setGraphicsQueue(final ImVkQueue graphicsQueue) { + this.graphicsQueue = graphicsQueue; + } + + public long getSurface() { + return surface; + } + + public void setSurface(final long surface) { + this.surface = surface; + } + + public long getNativeHandle() { + return nativeHandle; + } + + public boolean isVsync() { + return vsync; + } + + public void setVsync(final boolean vsync) { + this.vsync = vsync; + } + + public int getWidth() { + return width; + } + + public void setWidth(final int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(final int height) { + this.height = height; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/QueueFamilyIndices.java b/imgui-app/src/main/java/imgui/app/vk/QueueFamilyIndices.java new file mode 100644 index 00000000..11a19123 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/QueueFamilyIndices.java @@ -0,0 +1,26 @@ +package imgui.app.vk; + +public class QueueFamilyIndices { + private Integer graphicsFamily = null; + private Integer presentFamily = null; + + public boolean isComplete() { + return graphicsFamily != null && presentFamily != null; + } + + public Integer getGraphicsFamily() { + return graphicsFamily; + } + + public void setGraphicsFamily(final Integer graphicsFamily) { + this.graphicsFamily = graphicsFamily; + } + + public Integer getPresentFamily() { + return presentFamily; + } + + public void setPresentFamily(final Integer presentFamily) { + this.presentFamily = presentFamily; + } +} diff --git a/imgui-app/src/main/java/imgui/app/vk/SwapChainDetails.java b/imgui-app/src/main/java/imgui/app/vk/SwapChainDetails.java new file mode 100644 index 00000000..b20ef5a1 --- /dev/null +++ b/imgui-app/src/main/java/imgui/app/vk/SwapChainDetails.java @@ -0,0 +1,43 @@ +package imgui.app.vk; + +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSurfaceFormatKHR; + +import java.nio.IntBuffer; + +public class SwapChainDetails { + private VkSurfaceCapabilitiesKHR capabilities; + private VkSurfaceFormatKHR.Buffer formats; + private IntBuffer presentModes; + + public void free() { + capabilities.free(); + formats.free(); + MemoryUtil.memFree(presentModes); + } + + public VkSurfaceCapabilitiesKHR getCapabilities() { + return capabilities; + } + + public void setCapabilities(final VkSurfaceCapabilitiesKHR capabilities) { + this.capabilities = capabilities; + } + + public VkSurfaceFormatKHR.Buffer getFormats() { + return formats; + } + + public void setFormats(final VkSurfaceFormatKHR.Buffer formats) { + this.formats = formats; + } + + public IntBuffer getPresentModes() { + return presentModes; + } + + public void setPresentModes(final IntBuffer presentModes) { + this.presentModes = presentModes; + } +} diff --git a/imgui-binding/src/main/java/imgui/backends/glfw/ImGuiImplGlfw.java b/imgui-binding/src/main/java/imgui/backends/glfw/ImGuiImplGlfw.java new file mode 100644 index 00000000..f7e4acbb --- /dev/null +++ b/imgui-binding/src/main/java/imgui/backends/glfw/ImGuiImplGlfw.java @@ -0,0 +1,84 @@ +package imgui.backends.glfw; + +/** + * Native binding to the Dear ImGui GLFW backend + */ +public final class ImGuiImplGlfw { + private ImGuiImplGlfw() { + + } + + /*JNI + #include "_common.h" + #include "_glfw.h" + */ + + public static native boolean initForOpenGL(long window, boolean installCallbacks); /* + return ImGui_ImplGlfw_InitForOpenGL((GLFWwindow*) window, installCallbacks); + */ + + public static native boolean initForVulkan(long window, boolean installCallbacks); /* + return ImGui_ImplGlfw_InitForVulkan((GLFWwindow*) window, installCallbacks); + */ + + public static native boolean initForOther(long window, boolean installCallbacks); /* + return ImGui_ImplGlfw_InitForOther((GLFWwindow*) window, installCallbacks); + */ + + public static native void newFrame(); /* + ImGui_ImplGlfw_NewFrame(); + */ + + public static native void shutdown(); /* + ImGui_ImplGlfw_Shutdown(); + */ + + // GLFW callbacks (installer) + // - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any. + // - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call those function yourself from your own GLFW callbacks. + + //FIXME: Added in current Dear Imgui Master, uncomment once Dear Imgui is updated + //public static native void installCallbacks(long window); /* + // ImGui_ImplGlfw_InstallCallbacks((GLFWwindow*) window); + //*/ + + //FIXME: Added in current Dear Imgui Master, uncomment once Dear Imgui is updated + //public static native void restoreCallbacks(long window); /* + // ImGui_ImplGlfw_RestoreCallbacks((GLFWwindow*) window); + //*/ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused); + public static native void windowFocusCallback(long window, int focused); /* + ImGui_ImplGlfw_WindowFocusCallback((GLFWwindow*) window, focused); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); + public static native void cursorEnterCallback(long window, int entered); /* + ImGui_ImplGlfw_CursorEnterCallback((GLFWwindow*) window, entered); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); + public static native void mouseButtonCallback(long window, int button, int action, int mods); /* + ImGui_ImplGlfw_MouseButtonCallback((GLFWwindow*) window, button, action, mods); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); + public static native void scrollCallback(long window, double xoffset, double yoffset); /* + ImGui_ImplGlfw_ScrollCallback((GLFWwindow*) window, xoffset, yoffset); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); + public static native void keyCallback(long window, int key, int scancode, int action, int mods); /* + ImGui_ImplGlfw_KeyCallback((GLFWwindow*) window, key, scancode, action, mods); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); + public static native void charCallback(long window, char c); /* + ImGui_ImplGlfw_CharCallback((GLFWwindow*) window, (unsigned int) c); + */ + + //IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event); + public static native void monitorCallback(long monitor, int event); /* + ImGui_ImplGlfw_MonitorCallback((GLFWmonitor*) monitor, event); + */ +} diff --git a/imgui-binding/src/main/java/imgui/backends/vk/ImGuiImplVk.java b/imgui-binding/src/main/java/imgui/backends/vk/ImGuiImplVk.java new file mode 100644 index 00000000..174f0e6a --- /dev/null +++ b/imgui-binding/src/main/java/imgui/backends/vk/ImGuiImplVk.java @@ -0,0 +1,62 @@ +package imgui.backends.vk; + +import imgui.ImDrawData; +import imgui.backends.vk.type.ImGuiImplVkInitInfo; + +public final class ImGuiImplVk { + + private ImGuiImplVk() { + } + + /*JNI + #include "_common.h" + #include "_vulkan.h" + */ + + public static void nInit(final ImGuiImplVkInitInfo info, final long renderPass) { + nInit(info.ptr, renderPass); + } + + //Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass) + public static native void nInit(long info, long renderPass); /* + ImGui_ImplVulkan_Init((ImGui_ImplVulkan_InitInfo*) info, (VkRenderPass) renderPass); + */ + + public static native void shutdown(); /* + ImGui_ImplVulkan_Shutdown(); + */ + + public static native void newFrame(); /* + ImGui_ImplVulkan_NewFrame(); + */ + + public static void nRenderDrawData(final ImDrawData drawData, final long commandBuffer, final long pipeline) { + nRenderDrawData(drawData.ptr, commandBuffer, pipeline); + } + + //RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE); + public static native void nRenderDrawData(long drawData, long commandBuffer, long pipeline); /* + ImGui_ImplVulkan_RenderDrawData((ImDrawData*) drawData, (VkCommandBuffer) commandBuffer, (VkPipeline) pipeline); + */ + + //CreateFontsTexture(VkCommandBuffer command_buffer) + public static native void nCreateFontsTexture(long commandBuffer); /* + ImGui_ImplVulkan_CreateFontsTexture((VkCommandBuffer) commandBuffer); + */ + + public static native void destroyFontUploadObjects(); /* + ImGui_ImplVulkan_DestroyFontUploadObjects(); + */ + + public static native void setMinImageCount(int minImageCount); /* + ImGui_ImplVulkan_SetMinImageCount(minImageCount); + */ + + //FIXME: This is currently implemented in the latest 'master' of Dear ImGui + //FIXME: Once the latest master is merged in, this should be un-commented + ////AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); + //public static native void nAddTexture(long sampler, long imageView, long imageLayout); /* + // ImGui_ImplVulkan_AddTexture((VkSampler) sampler, (VkImageView) imageView, (VkImageLayout) imageLayout); + //*/ + +} diff --git a/imgui-binding/src/main/java/imgui/backends/vk/callback/ImGuiImplVkCheckResultCallback.java b/imgui-binding/src/main/java/imgui/backends/vk/callback/ImGuiImplVkCheckResultCallback.java new file mode 100644 index 00000000..dc6dfca4 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/backends/vk/callback/ImGuiImplVkCheckResultCallback.java @@ -0,0 +1,8 @@ +package imgui.backends.vk.callback; + +/** + * Callback to check the result of a vulkan call + */ +public abstract class ImGuiImplVkCheckResultCallback { + public abstract void callback(int resultCode); +} diff --git a/imgui-binding/src/main/java/imgui/backends/vk/type/ImGuiImplVkInitInfo.java b/imgui-binding/src/main/java/imgui/backends/vk/type/ImGuiImplVkInitInfo.java new file mode 100644 index 00000000..6528a5d6 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/backends/vk/type/ImGuiImplVkInitInfo.java @@ -0,0 +1,244 @@ +package imgui.backends.vk.type; + +import imgui.binding.ImGuiStructDestroyable; +import imgui.backends.vk.callback.ImGuiImplVkCheckResultCallback; + +public class ImGuiImplVkInitInfo extends ImGuiStructDestroyable { + + public ImGuiImplVkInitInfo() { + + } + + public ImGuiImplVkInitInfo(final long ptr) { + super(ptr); + } + + /*JNI + #include "_common.h" + #include "_vulkan.h" + + #define IM_VK_INIT_INFO ((ImGui_ImplVulkan_InitInfo*)STRUCT_PTR) + */ + + /** + * Create a new native instance of the ImGui_ImplVulkan_InitInfo object + * @return new native instance of ImGui_ImplVulkan_InitInfo + */ + @Override + protected long create() { + return nCreate(); + } + + private native long nCreate(); /* + return (intptr_t) new ImGui_ImplVulkan_InitInfo(); + */ + + /** + * Set the VkInstance using a pointer + * @param instance The pointer to the VkInstance + */ + public native void nSetInstance(long instance); /* + IM_VK_INIT_INFO->Instance = (VkInstance) instance; + */ + + /** + * Get the pointer to the VkInstance + * @return The pointer to the VkInstance + */ + public native long nGetInstance(); /* + return (intptr_t) IM_VK_INIT_INFO->Instance; + */ + + /** + * Set the VkPhysicalDevice using a pointer + * @param physicalDevice The pointer to the VkPhysicalDevice + */ + public native void nSetPhysicalDevice(long physicalDevice); /* + IM_VK_INIT_INFO->PhysicalDevice = (VkPhysicalDevice) physicalDevice; + */ + + /** + * Get the pointer to the VkPhysicalDevice + * @return The pointer to the VkPhysicalDevice + */ + public native long nGetPhysicalDevice(); /* + return (intptr_t) IM_VK_INIT_INFO->PhysicalDevice; + */ + + /** + * Set the VkDevice using a pointer + * @param device The pointer to the VkDevice + */ + public native void nSetDevice(long device); /* + IM_VK_INIT_INFO->Device = (VkDevice) device; + */ + + /** + * Get the pointer to the VkDevice + * @return The pointer to the VkDevice + */ + public native long nGetDevice(); /* + return (intptr_t) IM_VK_INIT_INFO->Device; + */ + + /** + * Set vulkan queue family + * @param queueFamily vulkan queue family + */ + public native void setQueueFamily(int queueFamily); /* + IM_VK_INIT_INFO->QueueFamily = queueFamily; + */ + + /** + * Get vulkan queue family + * @return vulkan queue family + */ + public native int getQueueFamily(); /* + return IM_VK_INIT_INFO->QueueFamily; + */ + + /** + * Set the pointer to the VkQueue + * @param queue the pointer to the VkQueue + */ + public native void nSetQueue(long queue); /* + IM_VK_INIT_INFO->Queue = (VkQueue) queue; + */ + + /** + * Set the pointer to the VkQueue + * @return the pointer to the VkQueue + */ + public native long nGetQueue(); /* + return (intptr_t) IM_VK_INIT_INFO->Queue; + */ + + /** + * Set the pointer to the VkPipelineCache + * @param pipelineCache the pointer to the VkPipelineCache + */ + public native void nSetPipelineCache(long pipelineCache); /* + IM_VK_INIT_INFO->PipelineCache = (VkPipelineCache) pipelineCache; + */ + + /** + * Get the pointer to the VkPipelineCache + * @return the pointer to the VkPipelineCache + */ + public native long nGetPipelineCache(); /* + return (intptr_t) IM_VK_INIT_INFO->PipelineCache; + */ + + /** + * Set the pointer to the VkDescriptorPool + * @param descriptorPool the pointer to the VkDescriptorPool + */ + public native void nSetDescriptorPool(long descriptorPool); /* + IM_VK_INIT_INFO->DescriptorPool = (VkDescriptorPool) descriptorPool; + */ + + /** + * Get the pointer to the VkDescriptorPool + * @return the pointer to the VkDescriptorPool + */ + public native long nGetDescriptorPool(); /* + return (intptr_t) IM_VK_INIT_INFO->DescriptorPool; + */ + + /** + * Set the render subpass + * @param subpass the render subpass + */ + public native void setSubpass(int subpass); /* + IM_VK_INIT_INFO->Subpass = subpass; + */ + + /** + * Get the render subpass + * @return the render subpass + */ + public native int getSubpass(); /* + return IM_VK_INIT_INFO->Subpass; + */ + + /** + * Set the min image count + * Must be greater or equal to 2 + * @param minImageCount the min image count + */ + public native void setMinImageCount(int minImageCount); /* + IM_VK_INIT_INFO->MinImageCount = minImageCount; + */ + + /** + * Get the min image count + * @return the min image count + */ + public native int getMinImageCount(); /* + return IM_VK_INIT_INFO->MinImageCount; + */ + + /** + * Set the image count + * Must be greater or equal to min image count + * @param imageCount the image count + */ + public native void setImageCount(int imageCount); /* + IM_VK_INIT_INFO->ImageCount = imageCount; + */ + + /** + * Get the image count + * @return the image count + */ + public native int getImageCount(); /* + return IM_VK_INIT_INFO->ImageCount; + */ + + /** + * Set MSAA Samples + * Must be greater or equal to VK_SAMPLE_COUNT_1_BIT (default to VK_SAMPLE_COUNT_1_BIT = 0) + * @param msaaSamples MSAA Samples (VK_SAMPLE_COUNT_x) + */ + public native void setMSAASamples(int msaaSamples); /* + IM_VK_INIT_INFO->MSAASamples = (VkSampleCountFlagBits) msaaSamples; + */ + + /** + * Get MSAA Samples + * This will be returned in the integer form of VkSampleCountFlagBits + * @return MSAA Samples (VK_SAMPLE_COUNT_x) + */ + public native int getMSAASamples(); /* + return (uint32_t) IM_VK_INIT_INFO->MSAASamples; + */ + + /** + * Set the VkAllocationCallbacks pointer + * @param allocator the VkAllocationCallbacks pointer + */ + public native void nSetAllocator(long allocator); /* + IM_VK_INIT_INFO->Allocator = (VkAllocationCallbacks*) allocator; + */ + + /** + * Set the callback function for validating vulkan result codes + * @param checkVkResultFn Callback for VkResult + */ + /*JNI + jobject imGuiImplVkCheckResultCallback = NULL; + void vkCheckResultCallback(VkResult result) { + if (imGuiImplVkCheckResultCallback != NULL) { + JNIEnv* env = Jni::GetEnv(); + Jni::CallImGuiImplVkCheckResultCallback(env, imGuiImplVkCheckResultCallback, (int) result); + } + } + */ + public native void setCheckVkResultFn(ImGuiImplVkCheckResultCallback checkVkResultFn); /* + if (imGuiImplVkCheckResultCallback != NULL) { + env->DeleteGlobalRef(imGuiImplVkCheckResultCallback); + } + imGuiImplVkCheckResultCallback = env->NewGlobalRef(checkVkResultFn); + IM_VK_INIT_INFO->CheckVkResultFn = &vkCheckResultCallback; + */ +} diff --git a/imgui-binding/src/main/java/imgui/lwjgl3/glfw/package-info.java b/imgui-binding/src/main/java/imgui/lwjgl3/glfw/package-info.java new file mode 100644 index 00000000..4ad6c050 --- /dev/null +++ b/imgui-binding/src/main/java/imgui/lwjgl3/glfw/package-info.java @@ -0,0 +1,7 @@ +/** + * Deprecation Notice: This packaged is deprecated for removal upon the removal of all deprecated child classes. + * Any future backend implementation development for the Dear ImGui GLFW backend native bindings should take place + * in the package {@link imgui.backends.glfw}. + */ +@Deprecated +package imgui.lwjgl3.glfw; diff --git a/imgui-binding/src/main/java/imgui/lwjgl3/package-info.java b/imgui-binding/src/main/java/imgui/lwjgl3/package-info.java new file mode 100644 index 00000000..900fb93b --- /dev/null +++ b/imgui-binding/src/main/java/imgui/lwjgl3/package-info.java @@ -0,0 +1,7 @@ +/** + * Deprecation Notice: This packaged is deprecated for removal upon the removal of all deprecated child classes. + * Any future backend implementation development for the Dear ImGui GLFW backends should take place + * in the package {@link imgui.backends}. + */ +@Deprecated +package imgui.lwjgl3; diff --git a/imgui-binding/src/main/native/_glfw.h b/imgui-binding/src/main/native/_glfw.h new file mode 100644 index 00000000..e4c0b86c --- /dev/null +++ b/imgui-binding/src/main/native/_glfw.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/imgui-binding/src/main/native/_vulkan.h b/imgui-binding/src/main/native/_vulkan.h new file mode 100644 index 00000000..95d3a4cd --- /dev/null +++ b/imgui-binding/src/main/native/_vulkan.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/imgui-binding/src/main/native/imconfig.h b/imgui-binding/src/main/native/imconfig.h index 35adb4ac..794f85ec 100644 --- a/imgui-binding/src/main/native/imconfig.h +++ b/imgui-binding/src/main/native/imconfig.h @@ -114,7 +114,7 @@ //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX //---- Debug Tools: Enable slower asserts -//#define IMGUI_DEBUG_PARANOID +#define IMGUI_DEBUG_PARANOID //FIXME: Remember to comment this back out! //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. /* diff --git a/imgui-binding/src/main/native/jni_callbacks.cpp b/imgui-binding/src/main/native/jni_callbacks.cpp index 06d320f2..a5ee714c 100644 --- a/imgui-binding/src/main/native/jni_callbacks.cpp +++ b/imgui-binding/src/main/native/jni_callbacks.cpp @@ -12,6 +12,7 @@ jmethodID jImPlatformFuncViewportStringAcceptMID; jmethodID jImPlatformFuncViewportSuppImVec2GetMID; jmethodID jImPlatformFuncViewportSuppBooleanGetMID; jmethodID jImPlatformFuncViewportSuppFloatGetMID; +jmethodID jImGuiImplVkCheckResultCallbackMID; jmethodID jImGuiFileDialogPaneFunMID; namespace Jni @@ -47,6 +48,9 @@ namespace Jni jclass jImPlatformFuncViewportSuppFloat = env->FindClass("imgui/callback/ImPlatformFuncViewportSuppFloat"); jImPlatformFuncViewportSuppFloatGetMID = env->GetMethodID(jImPlatformFuncViewportSuppFloat, "get", "(Limgui/ImGuiViewport;)F"); + jclass jImGuiImplVkCheckResultCallback = env->FindClass("imgui/lwjgl3/vk/callback/ImGuiImplVkCheckResultCallback"); + jImGuiImplVkCheckResultCallbackMID = env->GetMethodID(jImGuiImplVkCheckResultCallback, "callback", "(I)V"); + jclass jImGuiFileDialogPaneFun = env->FindClass("imgui/extension/imguifiledialog/callback/ImGuiFileDialogPaneFun"); jImGuiFileDialogPaneFunMID = env->GetMethodID(jImGuiFileDialogPaneFun, "paneFun", "(Ljava/lang/String;JZ)V"); } @@ -90,6 +94,10 @@ namespace Jni jfloat CallImPlatformFuncViewportSuppFloat(JNIEnv* env, jobject func, jobject vp) { return (jfloat)env->CallFloatMethod(func, jImPlatformFuncViewportSuppFloatGetMID, vp); } + + void CallImGuiImplVkCheckResultCallback(JNIEnv* env, jobject func, int vkResult) { + env->CallVoidMethod(func, jImGuiImplVkCheckResultCallbackMID, vkResult); + } void CallImGuiFileDialogPaneFun(JNIEnv* env, jobject func, const char* filter, long user_datas, bool canWeContinue) { env->CallVoidMethod(func, jImGuiFileDialogPaneFunMID, env->NewStringUTF(filter), user_datas, canWeContinue); diff --git a/imgui-binding/src/main/native/jni_callbacks.h b/imgui-binding/src/main/native/jni_callbacks.h index 9724fe52..743d7fd2 100644 --- a/imgui-binding/src/main/native/jni_callbacks.h +++ b/imgui-binding/src/main/native/jni_callbacks.h @@ -25,6 +25,8 @@ namespace Jni jboolean CallImPlatformFuncViewportSuppBoolean(JNIEnv* env, jobject func, jobject vp); jfloat CallImPlatformFuncViewportSuppFloat(JNIEnv* env, jobject func, jobject vp); + + void CallImGuiImplVkCheckResultCallback(JNIEnv* env, jobject func, int vkResult); void CallImGuiFileDialogPaneFun(JNIEnv* env, jobject func, const char* filter, long user_datas, bool canWeContinue); } diff --git a/imgui-lwjgl3/build.gradle b/imgui-lwjgl3/build.gradle index 5ea842af..b0f82300 100644 --- a/imgui-lwjgl3/build.gradle +++ b/imgui-lwjgl3/build.gradle @@ -11,6 +11,7 @@ dependencies { implementation 'org.lwjgl:lwjgl' implementation 'org.lwjgl:lwjgl-glfw' implementation 'org.lwjgl:lwjgl-opengl' + implementation 'org.lwjgl:lwjgl-vulkan' implementation project(':imgui-binding') } diff --git a/imgui-lwjgl3/src/main/java/imgui/gl3/package-info.java b/imgui-lwjgl3/src/main/java/imgui/gl3/package-info.java new file mode 100644 index 00000000..73fe4432 --- /dev/null +++ b/imgui-lwjgl3/src/main/java/imgui/gl3/package-info.java @@ -0,0 +1,7 @@ +/** + * Deprecation Notice: This packaged is deprecated for removal upon the removal of all deprecated child classes. + * Any future backend implementation development for the Dear ImGui OpenGL 3 backend should take place + * in the package {@link imgui.lwjgl3.backends.gl3}. + */ +@Deprecated +package imgui.gl3; diff --git a/imgui-lwjgl3/src/main/java/imgui/glfw/package-info.java b/imgui-lwjgl3/src/main/java/imgui/glfw/package-info.java new file mode 100644 index 00000000..223e7e37 --- /dev/null +++ b/imgui-lwjgl3/src/main/java/imgui/glfw/package-info.java @@ -0,0 +1,7 @@ +/** + * Deprecation Notice: This packaged is deprecated for removal upon the removal of all deprecated child classes. + * Any future backend implementation development for the Dear ImGui GLFW backend should take place + * in the package {@link imgui.lwjgl3.backends.glfw}. + */ +@Deprecated +package imgui.glfw; diff --git a/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVk.java b/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVk.java new file mode 100644 index 00000000..c4576e29 --- /dev/null +++ b/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVk.java @@ -0,0 +1,39 @@ +package imgui.lwjgl3.backends.vk; + +import imgui.ImDrawData; +import imgui.backends.vk.ImGuiImplVk; +import org.lwjgl.vulkan.VkCommandBuffer; + +public final class ImGuiVk { + + private ImGuiVk() { + } + + public static void init(final ImGuiVkInitInfo info, final long renderPass) { + ImGuiImplVk.nInit(info, renderPass); + } + + public static void shutdown() { + ImGuiImplVk.shutdown(); + } + + public static void newFrame() { + ImGuiImplVk.newFrame(); + } + + public static void renderDrawData(final ImDrawData drawData, final VkCommandBuffer commandBuffer, final long pipeline) { + ImGuiImplVk.nRenderDrawData(drawData, commandBuffer.address(), pipeline); + } + + public static void createFontsTexture(final VkCommandBuffer commandBuffer) { + ImGuiImplVk.nCreateFontsTexture(commandBuffer.address()); + } + + public static void destroyFontUploadObjects() { + ImGuiImplVk.destroyFontUploadObjects(); + } + + public static void setMinImageCount(final int minImageCount) { + ImGuiImplVk.setMinImageCount(minImageCount); + } +} diff --git a/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVkInitInfo.java b/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVkInitInfo.java new file mode 100644 index 00000000..3d2ceefa --- /dev/null +++ b/imgui-lwjgl3/src/main/java/imgui/lwjgl3/backends/vk/ImGuiVkInitInfo.java @@ -0,0 +1,143 @@ +package imgui.lwjgl3.backends.vk; + +import imgui.backends.vk.type.ImGuiImplVkInitInfo; +import org.lwjgl.vulkan.VkAllocationCallbacks; +import org.lwjgl.vulkan.VkDevice; +import org.lwjgl.vulkan.VkInstance; +import org.lwjgl.vulkan.VkPhysicalDevice; +import org.lwjgl.vulkan.VkQueue; + +public class ImGuiVkInitInfo extends ImGuiImplVkInitInfo { + + //Cache LWJGL Vk objects + private VkInstance instance; + private VkPhysicalDevice physicalDevice; + private VkDevice device; + private VkQueue queue; + + public ImGuiVkInitInfo() { + + } + + public ImGuiVkInitInfo(final long ptr) { + super(ptr); + } + + /** + * Get LWJGL Vulkan Instance + * @return Vulkan Instance + */ + public VkInstance getInstance() { + return instance; + } + + /** + * Set the LWJGL Vulkan Instance + * @param instance Vulkan Instance + */ + public void setInstance(final VkInstance instance) { + this.nSetInstance(instance.address()); + this.instance = instance; + } + + /** + * Get the LWJGL Vulkan Physical Device + * @return Vulkan Physical Device + */ + public VkPhysicalDevice getPhysicalDevice() { + return physicalDevice; + } + + /** + * Set the LWJGL Vulkan Physical Device + * @param physicalDevice Vulkan Physical Device + */ + public void setPhysicalDevice(final VkPhysicalDevice physicalDevice) { + this.nSetPhysicalDevice(physicalDevice.address()); + this.physicalDevice = physicalDevice; + } + + /** + * Get the LWJGL Vulkan Device + * @return Vulkan Device + */ + public VkDevice getDevice() { + return device; + } + + /** + * Set the LWJGL Vulkan Device + * @param device Vulkan Device + */ + public void setDevice(final VkDevice device) { + this.nSetDevice(device.address()); + this.device = device; + } + + /** + * Get the LWJGL Vulkan Queue + * @return Vulkan Queue + */ + public VkQueue getQueue() { + return queue; + } + + /** + * Set the LWJGL Vulkan Queue + * @param queue Vulkan Queue + */ + public void setQueue(final VkQueue queue) { + this.nSetQueue(queue.address()); + this.queue = queue; + } + + /** + * Set the pipeline cache using address (pointer) + * LWJGL does not have a safe wrapper for VkPipelineCache. + * Take care to use the correct pointer + * @param address The pointer to the pipeline cache + */ + public void setPipelineCache(final long address) { + this.nSetPipelineCache(address); + } + + /** + * Get the pointer to the vulkan pipeline cache. + * LWJGL does not have a safe wrapper for VkPipelineCache. + * @return The pointer to the pipeline cache + */ + public long getPipelineCache() { + return this.nGetPipelineCache(); + } + + /** + * Set the descriptor pool using address (pointer) + * LWJGL does not have a safe wrapper for VkDescriptorPool. + * Take care to use the correct pointer + * @param address The pointer to the descriptor pool + */ + public void setDescriptorPool(final long address) { + this.nSetDescriptorPool(address); + } + + /** + * Get the pointer to the vulkan descriptor pool. + * LWJGL does not have a safe wrapper for VkDescriptorPool. + * @return The pointer to the descriptor pool + */ + public long getDescriptorPool() { + return this.nGetDescriptorPool(); + } + + /** + * Set the LWJGL Vulkan Allocation Callback + * @param allocator Allocation Callback + */ + public void setAllocator(final VkAllocationCallbacks allocator) { + if (allocator != null) { + this.nSetAllocator(allocator.address()); + } else { + this.nSetAllocator(0L); + } + } +} diff --git a/include/Vulkan-Headers b/include/Vulkan-Headers new file mode 160000 index 00000000..aa18f182 --- /dev/null +++ b/include/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit aa18f182ebba65438b1cfdbd571f020bb2e34d04 diff --git a/include/glfw b/include/glfw new file mode 160000 index 00000000..955fbd9d --- /dev/null +++ b/include/glfw @@ -0,0 +1 @@ +Subproject commit 955fbd9d265fa95adf9cb94896eb9a516aa50420