From 30edbcb921abe42249464eaccaad4191cb5bae02 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Tue, 26 Apr 2022 22:55:23 +0930 Subject: [PATCH] Yet another attempt at a vulkan binding generator. This uses the registry exclusively to form the api and parameterised templates to generate output. --- Makefile | 15 +- README | 11 +- .../classes/module-info.java | 7 + .../classes/vulkan/test/TestMandelbrot.java | 600 ++++++++ src/notzed.vulkan.test/gen/cube.frag | 8 + src/notzed.vulkan.test/gen/cube.vert | 14 + src/notzed.vulkan.test/gen/gen.make | 17 + src/notzed.vulkan.test/gen/mandelbrot.comp | 60 + src/notzed.vulkan/classes/module-info.java | 8 + src/notzed.vulkan/gen/command-types.api | 562 ++++++++ src/notzed.vulkan/gen/gen.make | 14 + src/notzed.vulkan/gen/generate-vulkan | 1252 +++++++++++++++++ src/notzed.vulkan/gen/struct-types.api | 821 +++++++++++ src/notzed.vulkan/gen/vulkan.pm | 830 +++++++++++ src/notzed.xcb/classes/module-info.java | 6 + src/notzed.xcb/classes/xcb/Connection.java | 28 + 16 files changed, 4246 insertions(+), 7 deletions(-) create mode 100644 src/notzed.vulkan.test/classes/module-info.java create mode 100755 src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java create mode 100644 src/notzed.vulkan.test/gen/cube.frag create mode 100644 src/notzed.vulkan.test/gen/cube.vert create mode 100644 src/notzed.vulkan.test/gen/gen.make create mode 100644 src/notzed.vulkan.test/gen/mandelbrot.comp create mode 100644 src/notzed.vulkan/classes/module-info.java create mode 100644 src/notzed.vulkan/gen/command-types.api create mode 100644 src/notzed.vulkan/gen/gen.make create mode 100755 src/notzed.vulkan/gen/generate-vulkan create mode 100644 src/notzed.vulkan/gen/struct-types.api create mode 100644 src/notzed.vulkan/gen/vulkan.pm create mode 100644 src/notzed.xcb/classes/module-info.java create mode 100644 src/notzed.xcb/classes/xcb/Connection.java diff --git a/Makefile b/Makefile index 1bc01ce..1f9ce1d 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,10 @@ java_MODULES = notzed.nativez \ notzed.apistatic notzed.apiobject \ notzed.ffmpeg \ notzed.clstatic \ - notzed.xlib \ + notzed.xlib notzed.xcb \ notzed.vkregistry notzed.vkregistry.test \ - notzed.vkheader notzed.vkheader.test + notzed.vkheader notzed.vkheader.test \ + notzed.vulkan notzed.vulkan.test native_MODULES = notzed.api notzed.apistatic_JDEPMOD = notzed.nativez notzed.api @@ -20,19 +21,23 @@ notzed.ffmpeg_JDEPMOD = notzed.nativez notzed.clstatic_JDEPMOD = notzed.nativez notzed.vkregistry_JDEPMOD = notzed.nativez notzed.vkregistry.test_JDEPMOD = notzed.vkregistry notzed.xlib -notzed.vkheader_JDEPMOD = notzed.nativez +notzed.vkheader_JDEPMOD = notzed.nativez notzed.xlib notzed.vkheader.test_JDEPMOD = notzed.vkheader +notzed.vulkan_JDEPMOD = notzed.nativez notzed.xlib notzed.xcb +notzed.vulkan.test_JDEPMOD = notzed.vulkan notzed.apistatic_JMAIN = api.test.TestAPI notzed.apiobject_JMAIN = api.test.TestAPI notzed.ffmpeg_JMAIN = ffmpeg.test.TestFFMPEG notzed.clstatic_JMAIN = opencl.test.clinfo notzed.vkregistry.test_JMAIN = vulkan.test.TestMandelbrot vulkan.test.TestCube -notzed.vkheader.test_JMAIN = vulkan.test.TestMandelbrot +notzed.vkheader.test_JMAIN = vulkan.test.TestMandelbrot vulkan.test.TestCube +notzed.vulkan.test_JMAIN = vulkan.test.TestMandelbrot $(foreach module,$(java_MODULES),$(eval $(module)_JMAINFLAGS=--enable-native-access=notzed.nativez,$(module))) notzed.vkregistry.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.vkregistry -notzed.vkheader.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.vkheader +notzed.vkheader.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.vkheader +notzed.vulkan.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.xcb,notzed.vulkan include java.make diff --git a/README b/README index 2451943..ee4c1ab 100644 --- a/README +++ b/README @@ -113,13 +113,20 @@ The demo requires a file 'movie.api' in the root directory to run. notzed.vkheader uses the header-based generator on the vulkan installed headers. This is still very incomplete work in progress. Much meta-data is lost such as which functions are extensions during -the generation of the headers. +the generation of the headers. This is incomplete and dead-ended. notzed.vkregistry uses a completely different generator which directly parses the official xml registry specification for vulkan (/usr/share/vulkan/registry/vk.xml). This is directly converted to about-as-java-friendly a vulkan api as one can hope for, particularly -the constructors for all the config objects. +the constructors for all the config objects. This is incomplete and +dead-ended. + +notzed.vulkan also uses a different generator which directly parses +the official xml registry specification for vulkan +(/usr/share/vulkan/registry/vk.xml). This version uses templates to +generate the various structures in more concise and relatively +consistenet way. Work in progress. Export process -------------- diff --git a/src/notzed.vulkan.test/classes/module-info.java b/src/notzed.vulkan.test/classes/module-info.java new file mode 100644 index 0000000..452fec9 --- /dev/null +++ b/src/notzed.vulkan.test/classes/module-info.java @@ -0,0 +1,7 @@ + +module notzed.vulkan.test { + requires notzed.vulkan; + requires notzed.xlib; + + requires java.desktop; +} diff --git a/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java b/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java new file mode 100755 index 0000000..4ebb002 --- /dev/null +++ b/src/notzed.vulkan.test/classes/vulkan/test/TestMandelbrot.java @@ -0,0 +1,600 @@ + /* +The MIT License (MIT) + +Copyright (C) 2017 Eric Arnebäck +Copyright (C) 2019 Michael Zucchi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + */ + +/* + * This is a Java conversion of a C conversion of this: + * https://github.com/Erkaman/vulkan_minimal_compute + * + * It's been simplified a bit and converted to the 'zvk' api. + */ + +package vulkan.test; + +import java.io.InputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.image.MemoryImageSource; +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.KeyStroke; + +import java.lang.ref.WeakReference; + +import java.lang.invoke.*; +import jdk.incubator.foreign.*; +import jdk.incubator.foreign.MemoryLayout.PathElement; +import au.notzed.nativez.*; + +import vulkan.*; + +import static vulkan.VkConstants.*; + +public class TestMandelbrot { + static final boolean debug = true; + ResourceScope scope = ResourceScope.newSharedScope(); + + int WIDTH = 1920*1; + int HEIGHT = 1080*1; + + VkInstance instance; + VkPhysicalDevice physicalDevice; + + VkDevice device; + VkQueue computeQueue; + + long dstBufferSize = WIDTH * HEIGHT * 4; + //VkBuffer dstBuffer; + //VkDeviceMemory dstMemory; + BufferMemory dst; + + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorPool descriptorPool; + HandleArray descriptorSets; + + int computeQueueIndex; + VkPhysicalDeviceMemoryProperties deviceMemoryProperties; + + String mandelbrot_entry = "main"; + IntArray mandelbrot_cs; + + VkShaderModule mandelbrotShader; + VkPipelineLayout pipelineLayout; + HandleArray computePipeline = VkPipeline.createArray(1, (SegmentAllocator)scope); + + VkCommandPool commandPool; + HandleArray commandBuffers; + + record BufferMemory ( VkBuffer buffer, VkDeviceMemory memory ) {}; + + VkDebugUtilsMessengerEXT logger; + + void init_debug() throws Exception { + if (!debug) + return; + /* + try (Frame frame = Frame.frame()) { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data, dummy) -> { + System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); + return 0; + }, scope); + VkDebugUtilsMessengerCreateInfoEXT info = VkDebugUtilsMessengerCreateInfoEXT.create(frame, + 0, + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT + | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, + cb, + null); + + logger = instance.vkCreateDebugUtilsMessengerEXT(info, null, scope); + } + */ + //typedef VkBool32 (*PFN_vkDebugUtilsMessengerCallbackEXT)(VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT *, void *); + + } + + void init_instance() throws Exception { + try (Frame frame = Frame.frame()) { + VkInstanceCreateInfo info = VkInstanceCreateInfo.create(frame, + 0, + VkApplicationInfo.create(frame, "test", 1, "test-engine", 2, VK_MAKE_API_VERSION(0, 1, 0, 0)), + new String[] { "VK_LAYER_KHRONOS_validation" }, + debug ? new String[] { "VK_EXT_debug_utils" } : null + ); + + instance = VkInstance.vkCreateInstance(info, null, scope); + } + } + + void init_device() throws Exception { + try (Frame frame = Frame.frame()) { + HandleArray devs; + int count; + int res; + + devs = instance.vkEnumeratePhysicalDevices(frame, scope); + + int best = 0; + int devid = -1; + int queueid = -1; + + for (int i=0;i best) { + score = best; + devid = i; + queueid = j; + } + } + } + + if (devid == -1) + throw new Exception("Cannot find a suitable device"); + + computeQueueIndex = queueid; + physicalDevice = devs.getAtIndex(devid); + + FloatArray qpri = FloatArray.create(frame, 0.0f); + VkDeviceQueueCreateInfo qinfo = VkDeviceQueueCreateInfo.create( + frame, + 0, + queueid, + qpri); + VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( + frame, + 0, + qinfo, + null, + null, + null); + + device = physicalDevice.vkCreateDevice(devinfo, null, scope); + + System.out.printf("device = %s\n", device.address()); + + // NOTE: app scope + deviceMemoryProperties = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); + physicalDevice.vkGetPhysicalDeviceMemoryProperties(deviceMemoryProperties); + + computeQueue = device.vkGetDeviceQueue(queueid, 0, scope); + } + } + + /** + * Buffers are created in three steps: + * 1) create buffer, specifying usage and size + * 2) allocate memory based on memory requirements + * 3) bind memory + * + */ + BufferMemory init_buffer(long dataSize, int usage, int properties) throws Exception { + try (Frame frame = Frame.frame()) { + VkMemoryRequirements req = VkMemoryRequirements.create(frame); + VkBufferCreateInfo buf_info = VkBufferCreateInfo.create(frame, + 0, + dataSize, + usage, + VK_SHARING_MODE_EXCLUSIVE, + null); + + VkBuffer buffer = device.vkCreateBuffer(buf_info, null, scope); + + device.vkGetBufferMemoryRequirements(buffer, req); + + VkMemoryAllocateInfo alloc = VkMemoryAllocateInfo.create(frame, + req.getSize(), + find_memory_type(deviceMemoryProperties, req.getMemoryTypeBits(), properties)); + + VkDeviceMemory memory = device.vkAllocateMemory(alloc, null, scope); + + device.vkBindBufferMemory(buffer, memory, 0); + + return new BufferMemory(buffer, memory); + } + } + + /** + * Descriptors are used to bind and describe memory blocks + * to shaders. + * + * *Pool is used to allocate descriptors, it is per-device. + * *Layout is used to group descriptors for a given pipeline, + * The descriptors describe individually-addressable blocks. + */ + void init_descriptor() throws Exception { + try (Frame frame = Frame.frame()) { + /* Create descriptorset layout */ + VkDescriptorSetLayoutBinding layout_binding = VkDescriptorSetLayoutBinding.create(frame, + 0, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1, + VK_SHADER_STAGE_COMPUTE_BIT, + null); + + VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create(frame, + 0, + layout_binding); + + descriptorSetLayout = device.vkCreateDescriptorSetLayout(descriptor_layout, null, scope); + + /* Create descriptor pool */ + VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create(frame, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + 1); + + VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create(frame, + 0, + 1, + type_count); + + descriptorPool = device.vkCreateDescriptorPool(descriptor_pool, null, scope); + + /* Allocate from pool */ + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkDescriptorSetAllocateInfo alloc_info = VkDescriptorSetAllocateInfo.create(frame, + descriptorPool, + layout_table); + + descriptorSets = device.vkAllocateDescriptorSets(alloc_info, (SegmentAllocator)scope); + + /* Bind a buffer to the descriptor */ + VkDescriptorBufferInfo bufferInfo = VkDescriptorBufferInfo.create(frame, + dst.buffer, + 0, + dstBufferSize); + + VkWriteDescriptorSet writeSet = VkWriteDescriptorSet.create(frame, + descriptorSets.getAtIndex(0), + 0, + 0, + 1, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + null, + bufferInfo, + null); + + System.out.println(writeSet); + + device.vkUpdateDescriptorSets(writeSet, null); + } + } + + /** + * Create the compute pipeline. This is the shader and data layouts for it. + */ + void init_pipeline() throws Exception { + try (Frame frame = Frame.frame()) { + /* Set shader code */ + VkShaderModuleCreateInfo vsInfo = VkShaderModuleCreateInfo.create(frame, + 0, + mandelbrot_cs.length() * 4, + mandelbrot_cs); + + mandelbrotShader = device.vkCreateShaderModule(vsInfo, null, scope); + + /* Link shader to layout */ + HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); + + layout_table.setAtIndex(0, descriptorSetLayout); + + VkPipelineLayoutCreateInfo pipelineinfo = VkPipelineLayoutCreateInfo.create(frame, + 0, + layout_table, + null); + + pipelineLayout = device.vkCreatePipelineLayout(pipelineinfo, null, scope); + + /* Create pipeline */ + VkComputePipelineCreateInfo pipeline = VkComputePipelineCreateInfo.create(frame, + 0, + pipelineLayout, + null, + 0); + + VkPipelineShaderStageCreateInfo stage = pipeline.getStage(); + + stage.setStage(VK_SHADER_STAGE_COMPUTE_BIT); + stage.setModule(mandelbrotShader); + stage.setName(mandelbrot_entry, frame); + + device.vkCreateComputePipelines(null, pipeline, null, computePipeline); + } + } + + /** + * Create a command buffer, this is somewhat like a display list. + */ + void init_command_buffer() throws Exception { + try (Frame frame = Frame.frame()) { + VkCommandPoolCreateInfo poolinfo = VkCommandPoolCreateInfo.create(frame, + 0, + computeQueueIndex); + + commandPool = device.vkCreateCommandPool(poolinfo, null, scope); + + VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create(frame, + commandPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1); + + // should it take a scope? + commandBuffers = device.vkAllocateCommandBuffers(cmdinfo, (SegmentAllocator)scope, scope); + + /* Fill command buffer with commands for later operation */ + VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.create(frame, + VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + null); + + commandBuffers.get(0).vkBeginCommandBuffer(beginInfo); + + /* Bind the compute operation and data */ + commandBuffers.get(0).vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline.get(0)); + commandBuffers.get(0).vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, descriptorSets, null); + + /* Run it */ + commandBuffers.get(0).vkCmdDispatch(WIDTH, HEIGHT, 1); + + commandBuffers.get(0).vkEndCommandBuffer(); + } + } + + /** + * Execute the pre-created command buffer. + * + * A fence is used to wait for completion. + */ + void execute() throws Exception { + try (Frame frame = Frame.frame()) { + VkSubmitInfo submitInfo = VkSubmitInfo.create(frame); + + submitInfo.setCommandBufferCount(1); + submitInfo.setCommandBuffers(commandBuffers); + + /* Create fence to mark the task completion */ + VkFence fence; + HandleArray fences = VkFence.createArray(1, frame); + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(frame); + + // maybe this should take a HandleArray rather than being a constructor + // FIXME: some local scope + fence = device.vkCreateFence(fenceInfo, null, scope); + fences.set(0, fence); + + /* Await completion */ + computeQueue.vkQueueSubmit(submitInfo, fence); + + int VK_TRUE = 1; + int res; + do { + res = device.vkWaitForFences(fences, VK_TRUE, 1000000); + } while (res == VK_TIMEOUT); + + device.vkDestroyFence(fence, null); + } + } + + void shutdown() { + device.vkDestroyCommandPool(commandPool, null); + device.vkDestroyPipeline(computePipeline.getAtIndex(0), null); + device.vkDestroyPipelineLayout(pipelineLayout, null); + device.vkDestroyShaderModule(mandelbrotShader, null); + + device.vkDestroyDescriptorPool(descriptorPool, null); + device.vkDestroyDescriptorSetLayout(descriptorSetLayout, null); + + device.vkFreeMemory(dst.memory(), null); + device.vkDestroyBuffer(dst.buffer(), null); + + device.vkDestroyDevice(null); + if (logger != null) + instance.vkDestroyDebugUtilsMessengerEXT(logger, null); + instance.vkDestroyInstance(null); + } + + /** + * Accesses the gpu buffer, converts it to RGB byte, and saves it as a pam file. + */ + void save_result() throws Exception { + try (ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment mem = device.vkMapMemory(dst.memory(), 0, dstBufferSize, 0, scope); + byte[] pixels = new byte[WIDTH * HEIGHT * 3]; + + System.out.printf("map %d bytes\n", dstBufferSize); + + for (int i = 0; i < WIDTH * HEIGHT; i++) { + pixels[i * 3 + 0] = mem.get(Memory.BYTE, i * 4 + 0); + pixels[i * 3 + 1] = mem.get(Memory.BYTE, i * 4 + 1); + pixels[i * 3 + 2] = mem.get(Memory.BYTE, i * 4 + 2); + } + + device.vkUnmapMemory(dst.memory()); + + pam_save("mandelbrot.pam", WIDTH, HEIGHT, 3, pixels); + } + } + + void show_result() throws Exception { + try (ResourceScope scope = ResourceScope.newConfinedScope()) { + MemorySegment mem = device.vkMapMemory(dst.memory(), 0, dstBufferSize, 0, scope); + int[] pixels = new int[WIDTH * HEIGHT]; + + System.out.printf("map %d bytes\n", dstBufferSize); + + MemorySegment.ofArray(pixels).copyFrom(mem); + + device.vkUnmapMemory(dst.memory()); + + swing_show(WIDTH, HEIGHT, pixels); + } + } + + /** + * Trivial pnm format image output. + */ + void pam_save(String name, int width, int height, int depth, byte[] pixels) throws IOException { + try (FileOutputStream fos = new FileOutputStream(name)) { + fos.write(String.format("P6\n%d\n%d\n255\n", width, height).getBytes()); + fos.write(pixels); + System.out.printf("wrote: %s\n", name); + } + } + + static class DataImage extends JPanel { + + final int w, h, stride; + final MemoryImageSource source; + final Image image; + final int[] pixels; + + public DataImage(int w, int h, int[] pixels) { + this.w = w; + this.h = h; + this.stride = w; + this.pixels = pixels; + this.source = new MemoryImageSource(w, h, pixels, 0, w); + this.source.setAnimated(true); + this.source.setFullBufferUpdates(true); + this.image = Toolkit.getDefaultToolkit().createImage(source); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + g.drawImage(image, 0, 0, this); + } + } + + void swing_show(int w, int h, int[] pixels) { + JFrame window; + DataImage image = new DataImage(w, h, pixels); + + window = new JFrame("mandelbrot"); + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + window.setContentPane(image); + window.setSize(w, h); + window.setVisible(true); + } + + IntArray loadSPIRV0(String name) throws IOException { + // hmm any way to just load this directly? + try (InputStream is = TestMandelbrot.class.getResourceAsStream(name)) { + ByteBuffer bb = ByteBuffer.allocateDirect(8192).order(ByteOrder.nativeOrder()); + int length = Channels.newChannel(is).read(bb); + + bb.position(0); + bb.limit(length); + + return IntArray.create(MemorySegment.ofByteBuffer(bb)); + } + } + + IntArray loadSPIRV(String name) throws IOException { + try (InputStream is = TestMandelbrot.class.getResourceAsStream(name)) { + MemorySegment seg = ((SegmentAllocator)scope).allocateArray(Memory.INT, 2048); + int length = Channels.newChannel(is).read(seg.asByteBuffer()); + + return IntArray.create(seg.asSlice(0, length)); + } + } + + /** + * This finds the memory type index for the memory on a specific device. + */ + static int find_memory_type(VkPhysicalDeviceMemoryProperties memory, int typeMask, int query) { + VkMemoryType mtypes = memory.getMemoryTypes(); + + for (int i = 0; i < memory.getMemoryTypeCount(); i++) { + if (((1 << i) & typeMask) != 0 && ((mtypes.getAtIndex(i).getPropertyFlags() & query) == query)) + return i; + } + return -1; + } + + public static int VK_MAKE_API_VERSION(int variant, int major, int minor, int patch) { + return (variant << 29) | (major << 22) | (minor << 12) | patch; + } + + void demo() throws Exception { + mandelbrot_cs = loadSPIRV("mandelbrot.bin"); + + init_instance(); + init_debug(); + + init_device(); + + dst = init_buffer(dstBufferSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + init_descriptor(); + + init_pipeline(); + init_command_buffer(); + + System.out.printf("Calculating %dx%d\n", WIDTH, HEIGHT); + execute(); + //System.out.println("Saving ..."); + //save_result(); + System.out.println("Showing ..."); + show_result(); + System.out.println("Done."); + + shutdown(); + } + + + public static void main(String[] args) throws Throwable { + System.loadLibrary("vulkan"); + + new TestMandelbrot().demo(); + } +} diff --git a/src/notzed.vulkan.test/gen/cube.frag b/src/notzed.vulkan.test/gen/cube.frag new file mode 100644 index 0000000..de24544 --- /dev/null +++ b/src/notzed.vulkan.test/gen/cube.frag @@ -0,0 +1,8 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (location = 0) in vec4 color; +layout (location = 0) out vec4 outColor; +void main() { + outColor = color; +} diff --git a/src/notzed.vulkan.test/gen/cube.vert b/src/notzed.vulkan.test/gen/cube.vert new file mode 100644 index 0000000..5d21e1e --- /dev/null +++ b/src/notzed.vulkan.test/gen/cube.vert @@ -0,0 +1,14 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (std140, binding = 0) uniform bufferVals { + mat4 mvp; +} data; +layout (location = 0) in vec4 pos; +layout (location = 1) in vec4 inColor; +layout (location = 0) out vec4 outColor; + +void main() { + outColor = inColor; + gl_Position = data.mvp * pos; +} diff --git a/src/notzed.vulkan.test/gen/gen.make b/src/notzed.vulkan.test/gen/gen.make new file mode 100644 index 0000000..1abdde1 --- /dev/null +++ b/src/notzed.vulkan.test/gen/gen.make @@ -0,0 +1,17 @@ + +bin/status/notzed.vulkan.test.classes: \ + bin/modules/notzed.vulkan.test/vulkan/test/mandelbrot.bin \ + bin/modules/notzed.vulkan.test/vulkan/test/cube_vs.bin \ + bin/modules/notzed.vulkan.test/vulkan/test/cube_fs.bin + +bin/modules/notzed.vulkan.test/vulkan/test/mandelbrot.bin: src/notzed.vulkan.test/gen/mandelbrot.comp + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vulkan.test/vulkan/test/cube_vs.bin: src/notzed.vulkan.test/gen/cube.vert + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vulkan.test/vulkan/test/cube_fs.bin: src/notzed.vulkan.test/gen/cube.frag + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< diff --git a/src/notzed.vulkan.test/gen/mandelbrot.comp b/src/notzed.vulkan.test/gen/mandelbrot.comp new file mode 100644 index 0000000..6a45590 --- /dev/null +++ b/src/notzed.vulkan.test/gen/mandelbrot.comp @@ -0,0 +1,60 @@ +#version 450 + +#define WIDTH (1920*1) +#define HEIGHT (1080*1) +#define LWS_X 8 +#define LWS_Y 8 +#define LIMIT 10000 + +layout (local_size_x = LWS_X, local_size_y = LWS_Y, local_size_z = 1 ) in; + +layout(std430, binding = 0) buffer buf { + uint imageData[]; +}; + +void main() { + + /* + In order to fit the work into workgroups, some unnecessary threads are launched. + We terminate those threads here. + */ + if(gl_GlobalInvocationID.x >= WIDTH || gl_GlobalInvocationID.y >= HEIGHT) + return; + + float x = float(gl_GlobalInvocationID.x) / float(WIDTH); + float y = float(gl_GlobalInvocationID.y) / float(HEIGHT); + + /* + What follows is code for rendering the mandelbrot set. + */ + vec2 uv = vec2(x, (y - 0.5) * (12.0 / 19.0) + 0.5); + float n = 0.0; + vec2 c = vec2(-.445, 0.0) + (uv - 0.5)*(4.0); + vec2 z = vec2(0.0); + const int M = LIMIT; + + for (int i = 0; i 4) + break; + n++; + } + + // we use a simple cosine palette to determine color: + // http://iquilezles.org/www/articles/palettes/palettes.htm + float t = float(n) * 500.0 / float(M); + vec3 d = vec3(0.5, 0.5, 0.5); + vec3 e = vec3(0.5, 0.5, 0.5); + vec3 f = vec3(1.0, 1.0, 1.0); + vec3 g = vec3(0.00, 0.33, 0.67); + + vec4 color = vec4( d + e*cos( 6.28318*(f*t+g) ) ,1.0); + + if (n == M) + color = vec4(0, 0, 0, 1); + + // store the rendered mandelbrot set into a storage buffer: + imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x] = packUnorm4x8(color); + //imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x].value = color; +} diff --git a/src/notzed.vulkan/classes/module-info.java b/src/notzed.vulkan/classes/module-info.java new file mode 100644 index 0000000..3e7cdec --- /dev/null +++ b/src/notzed.vulkan/classes/module-info.java @@ -0,0 +1,8 @@ + +module notzed.vulkan { + requires transitive notzed.nativez; + requires notzed.xlib; + requires notzed.xcb; + + exports vulkan; +} diff --git a/src/notzed.vulkan/gen/command-types.api b/src/notzed.vulkan/gen/command-types.api new file mode 100644 index 0000000..0bf03ce --- /dev/null +++ b/src/notzed.vulkan/gen/command-types.api @@ -0,0 +1,562 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# things to check; vkCmdSetFragmentShadingRateKHR + +# types for function calls + +code method { + invoke {{ + /* method:invoke */ + static final MethodHandle {name}$FH = Memory.downcall("{name}", + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arguments}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$FH.invokeExact({invoke-arguments}); + {result-test}{ + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-query { + invoke {{ + /* method-query:invoke */ + static final MethodHandle {name}$FH = Memory.downcall("{name}", + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arguments}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$FH.invokeExact({query-arguments}); + {result-test}{ + {query-init} + {native-result-assign}{name}$FH.invokeExact({invoke-arguments}); + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-extension { + dispatch {{ + final NativeSymbol {name}$NS; + }} + invoke {{ + /* method-extension:invoke */ + final static MethodHandle {name}$DH = Memory.downcall( + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arguments}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {invoke-arguments}); + {result-test}{ + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +code method-extension-query { + dispatch {{ + final NativeSymbol {name}$NS; + }} + invoke {{ + /* method-extension:invoke */ + final static MethodHandle {name}$DH = Memory.downcall( + {function-descriptor}); + /** + * success: {successcodes} + * errors: {errorcodes} + */ + public {static}{java-result} {rename}( + {java-arguments}) { + {native-result-define} + try {create-frame}{ + {native-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {query-arguments}); + {result-test}{ + {query-init} + {native-result-assign}{name}$DH.invokeExact(dispatch.{name}$NS, {invoke-arguments}); + {java-result-assign} + {java-result-return} + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + {result-throw} + } + }} +} + +type value { + java-arg {{ {type} {name} }} + native-arg {{ {carrier} {name}$ }} + + invoke-arg {{ {name} }} +} + +type value-array { + java-arg {{ {type} {name} }} + native-arg {{ {carrier} {name}$ }} + + invoke-arg {{ {name} }} +} + +type value-array2d { + java-arg {{ {type} {name} }} + native-arg {{ {carrier} {name}$ }} + + invoke-arg {{ {name} }} +} + +type uint8_t,char value { + type {{ byte }} + layout {{ Memory.BYTE }} +} + +type uint16_t value { + type {{ short }} + layout {{ Memory.SHORT }} +} + +type uint32_t,int,int32_t value { + type {{ int }} + layout {{ Memory.INT }} +} + +type uint64_t,int64_t,size_t value { + type {{ long }} + layout {{ Memory.LONG }} +} + +type float value { + type {{ float }} + layout {{ Memory.FLOAT }} +} + +type double value { + type {{ double }} + layout {{ Memory.DOUBLE }} +} + +# ###################################################################### # +# implied length types +type uint64_t-length,size_t-length uint64_t { + java-arg {{ }} + invoke-arg {{ Memory.length({lengthfor}) }} +} + +type uint32_t-length uint32_t { + java-arg {{ }} + invoke-arg {{ (int)Memory.length({lengthfor}) }} +} + +# ###################################################################### # + +# type uint8_t[],char[] value-array { +# type {{ ByteArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.BYTE) }} +# typei {{ byte }} +# } + +#type uint32_t[],int32_t[] value-array { +# type {{ IntArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.INT) }} +# typei {{ int }} +#} + +# type uint64_t[] value-array { +# type {{ LongArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.LONG) }} +# typei {{ long }} +# } + +# calls float[] =-> pointer +#type float[] value-array { +# # or should it be float[] ? +# type {{ FloatArray }} +# layout {{ MemoryLayout.sequenceLayout({len1}, Memory.FLOAT) }} +# typei {{ float }} +#} + +# type struct[] inline-array { +# type {{ {baseType} }} +# layout {{ MemoryLayout.sequenceLayout({len1}, {baseType}.LAYOUT) }} +# } + +# type float[][] value-array2d { +# type {{ FloatArray }} +# typei {{ float }} +# layout {{ MemoryLayout.sequenceLayout({len1}, MemoryLayout.sequenceLayout({len2}, Memory.FLOAT)) }} +# } + +# select=len? or what? + +type pointer value { + layout {{ Memory.POINTER }} + type {{ MemoryAddress }} + + invoke-arg {{ Memory.address({name}) }} +} + +type void* pointer; + +type funcpointer pointer { + type {{ NativeSymbol }} +} + +# FIXME: clenaup, value-pointer does nothing +type value-pointer pointer { +} + +type uint8_t* value-pointer { + type ByteArray; +} + +type uint32_t*,int32_t*,int* value-pointer { + type IntArray; +} + +type uint64_t* value-pointer { + type LongArray; +} + +type float* value-pointer { + type FloatArray; +} + +type size_t* value-pointer { + type LongArray; +} + +type pointer-length pointer { +} + +type void*-length pointer-length { + type MemorySegment; + java-get {{ MemorySegment.ofAddress({native-get}, {length}, this.segment.scope()) }} +} + +type uint8_t*-length pointer-length { + type ByteArray; +} + +type uint32_t*-length,int32_t*-length pointer-length { + type IntArray; +} + +type uint32_t[] pointer-length { + type IntArray; + length {{ {len1} }} +} + +type uint64_t*-length pointer-length { + type LongArray; +} + +type float*-length pointer-length { + type FloatArray; +} + +type float[] pointer-length { + type FloatArray; + length {{ {len1} }} +} + +# special handling for strings, will fail if it isn't +type char* pointer need-frame { + type {{ String }} + + invoke-arg {{ (Addressable)frame$.allocateUtf8String({name}) }} + + # this just verifies it's a string type + length eval {{ + if ($v->{len} =~ m/null-terminated/) { + 1; + } else { + die Dumper($v, $s); + } + }} + +} + +# type XXchar**-length pointer-length accessor=value-alloc { +# type {{ String[] }} + +# java-set {{ {name}$VH.set(segment, Memory.copyStringArray({name}, alloc$)); {set-length} }} +# java-get {{ Memory.copyStringArray((MemoryAddress){name}$VH.get(segment), {length}) }} + +# set-length eval {{ +# if ($v->{len} =~ m/(.*),null-terminated/) { +# 'set'.ucfirst($1).'({name}.length)'; +# } else { +# die Dumper($v, $s); +# } +# }} +# } + +# FIXME: wrong +type Xuint32_t** pointer { + type {{ HandleArray }} + typei {{ IntArray }} +} + +type uint32_t**-length pointer { + type {{ HandleArray }} + typei {{ IntArray }} +} + +type void** pointer { + type PointerArray; +} + +type void**-length pointer-length { + type PointerArray; +} + +type handle pointer { + type {{ {baseType} }} +} + +type handle* pointer { + type {{ HandleArray<{typei}> }} + typei {{ {baseType} }} + + invoke-arg {{ Memory.address({name}) }} +} + +type handle*-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} +} + +type struct inline { + type {{ {baseType} }} + layout {{ {baseType}.LAYOUT }} +} + +type struct* pointer { + type {{ {baseType} }} +} + +type struct*-length pointer-length { + type {{ {baseType} }} + typei {{ {baseType} }} +} + +type struct**-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} +} + +type struct** pointer { +} + +# xlib +type XID,Window,VisualID uint64_t; + +type Display* handle { + type xlib.XDisplay; +} + +# xcb +type xcb_window_t uint32_t; +type xcb_visualid_t uint32_t; +type xcb_connection_t* handle { + type xcb.Connection; +} + +# special types for call overrides +type instance handle is-instance { + java-arg {{ }} + invoke-arg {{ (Addressable)self }} + native-arg {{ }} +} + +type funcpointer-return funcpointer need-scope { + native-result-define {{ MemoryAddress result$; }} + native-result-assign {{ result$ = (MemoryAddress) }} + java-result-return {{ return NativeSymbol.ofAddress(pName, result$, scope$); }} +} + +type uint32_t-return uint32_t { + native-result-define {{ int result$; }} + native-result-assign {{ result$ = (int) }} + java-result-return {{ return result$; /* uint32_t */ }} +} + +type uint64_t-return,size_t-return uint64_t { + native-result-define {{ long result$; }} + native-result-assign {{ result$ = (long) }} + java-result-return {{ return result$; /* uint64_t */ }} +} + +# for handle constructors +type handle*-output handle* need-frame need-scope { + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + + java-result {{ {baseType} }} + native-init {{ MemorySegment {name} = frame$.allocate(Memory.POINTER); }} + java-result-assign {{ {baseType} result$$ = {baseType}.create({name}.get(Memory.POINTER, 0), scope$); }} + java-result-return {{ return result$$; }} +} + +# for handle constructors of dispatchable types +type dispatch*-output handle*-output { + java-result-assign {{ {baseType} result$$ = {baseType}.create({name}.get(Memory.POINTER, 0), dispatch, scope$); }} +} + +# for query and return types +type handle*-length-query handle*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$); }} + java-result-return {{ return {name}; }} +} + +type dispatch*-length-query handle*-length-query need-scope { + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$, dispatch, scope$); }} +} + +type struct*-length-query struct*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name}.address() }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = {typei}.createArray({length}, alloc$); }} + java-result-return {{ return {name}; }} +} + +type void*-length-query void*-length need-alloc { + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = alloc$.allocate({length}); }} + java-result-return {{ return {name}; }} +} + +type uint32_t*-length-query uint32_t*-length need-alloc { + java-arg {{ }} + invoke-arg {{ {name} }} + query-arg {{ (Addressable)MemoryAddress.NULL }} + + java-result {{ {type} }} + query-init {{ {type} {name} = IntArray.createArray({length}, alloc$); }} + java-result-return {{ return {name}; }} +} + +type uint32_t*-querylen uint32_t* need-frame { + type {{ MemorySegment }} + java-arg {{ }} + invoke-arg {{ (Addressable){name}$ }} + + native-init {{ {type} {name}$ = alloc$.allocate(Memory.INT, 0); }} + query-init {{ long {name} = {name}$.get(Memory.INT, 0); }} +} + +type size_t*-querylen uint32_t* need-frame { + type {{ MemorySegment }} + java-arg {{ }} + invoke-arg {{ (Addressable){name}$ }} + + native-init {{ {type} {name}$ = alloc$.allocate(Memory.LONG, 0); }} + query-init {{ long {name} = {name}$.get(Memory.LONG, 0); }} +} + +# Or should it just use an insert function? + +# there's only two like this +type handle*-alloc handle* { + java-arg {{ }} + java-result {{ {type} }} + java-result-return {{ return {name}; }} +} + +type VkCommandBuffer-alloc handle*-alloc need-scope need-alloc { + native-init {{ + {type} {name} = {typei}.createArray( + (int)VkCommandBufferAllocateInfo.commandBufferCount$VH.get(pAllocateInfo.segment), + alloc$, + dispatch, + scope$); + }} +} + +type VkDescriptorSet-alloc handle*-alloc need-alloc { + native-init {{ + {type} {name} = {typei}.createArray( + (int)VkCommandBufferAllocateInfo.commandBufferCount$VH.get(pAllocateInfo.segment), + alloc$); + }} +} + +type void**-output void** need-frame need-scope { + type {{ MemorySegment }} + + java-arg {{ }} + invoke-arg {{ (Addressable){name} }} + + native-init {{ MemorySegment {name} = frame$.allocate(Memory.POINTER); }} + + java-result {{ {type} }} + java-result-assign {{ {type} result$$ = MemorySegment.ofAddress({name}.get(Memory.POINTER, 0), {length}, scope$); }} + java-result-return {{ return result$$; }} +} + +type vkMapMemory-output void**-output { + length {{ size }} +} + +# some tweaks that the auto-discovery code misses/isn't worth adding to +override commands { + vkGetPhysicalDeviceQueueFamilyProperties template=method-query + physicalDevice=type:instance + pQueueFamilyPropertyCount=type:uint32_t*-querylen + pQueueFamilyProperties=type:struct*-length-query; + + vkAllocateCommandBuffers device=type:instance pCommandBuffers=type:VkCommandBuffer-alloc; + vkAllocateDescriptorSets device=type:instance pDescriptorSets=type:VkDescriptorSet-alloc; + + vkMapMemory device=type:instance ppData=type:vkMapMemory-output; +} diff --git a/src/notzed.vulkan/gen/gen.make b/src/notzed.vulkan/gen/gen.make new file mode 100644 index 0000000..3e34cad --- /dev/null +++ b/src/notzed.vulkan/gen/gen.make @@ -0,0 +1,14 @@ + +bin/status/notzed.vulkan.classes: \ + bin/status/notzed.vulkan.export + +bin/status/notzed.vulkan.export: \ + src/notzed.vulkan/gen/generate-vulkan \ + src/notzed.vulkan/gen/vulkan.pm \ + src/notzed.vulkan/gen/struct-types.api \ + src/notzed.vulkan/gen/command-types.api + +bin/status/notzed.vulkan.export: + src/notzed.vulkan/gen/generate-vulkan -t vulkan -d bin/gen/notzed.vulkan/classes + mkdir -p $(@D) + touch $@ diff --git a/src/notzed.vulkan/gen/generate-vulkan b/src/notzed.vulkan/gen/generate-vulkan new file mode 100755 index 0000000..cb50afd --- /dev/null +++ b/src/notzed.vulkan/gen/generate-vulkan @@ -0,0 +1,1252 @@ +#!/usr/bin/perl + +use Data::Dumper; + +use File::Path qw(make_path); +use File::Basename; + +use strict; + +use Carp 'verbose'; +use FindBin; +use lib "$FindBin::Bin", 'bin/linux-amd64/lib'; + +use vulkan; +use config; +use code; + +$SIG{__DIE__} = sub { Carp::confess( @_ ) }; +$SIG{'INT'} = sub { Carp::confess() }; +$Data::Dumper::Indent = 1; + +# ###################################################################### # + +my $enumInfo = { + 'uint32_t' => { + type => 'int' + }, + 'uint64_t' => { + type => 'long', + suffix => 'L' + }, + 'float' => { + type => 'float', + suffix => 'f' + }, + 'const char *' => { + type => 'String' + }, +}; + +# all seen types for dev only +my $all = {}; + +# ###################################################################### # +my $vk = new vulkan(); + +my $api = $vk->buildFeatures( + [ 'VK_VERSION_1_0', 'VK_VERSION_1_1', 'VK_VERSION_1_2' ], + [ 'xlib', + #'wayland', + 'xcb' ]); + +my $sys = {}; + +$sys->{output} = 'bin/gen/notzed.vulkan/classes'; +$sys->{package} = 'vulkan'; +$sys->{verbose} = 1; + +my $structTypes = loadTypes($api, 'struct-types.api'); +my $commandTypes = loadTypes($api, 'command-types.api'); + +analyseTypes($vk, $api); + +# for structs +my %defaultTemplate; + +foreach my $s (values %{$api->{types}}) { + my $overrides = $structTypes->{overrides}; + my $types = $structTypes->{types}; + + next if (defined($overrides->{$s->{name}})); + + my %lengths; + map { $lengths{$_->{lengthfrom}}++ if $_->{lengthfrom} } @{$s->{items}}; + + foreach my $m (@{$s->{items}}) { + my $nstar = $m->{deref} =~ tr/*/*/; + + # FIXME: this belongs in analyse + if ($m->{lengthfrom} && $lengths{$m->{lengthfrom}} != 1) { + $m->{'set-length'} = ''; + } + + if ($m->{lengthfor} && $nstar == 0 && $lengths{$m->{name}} == 1) { + $overrides->{$s->{name}}->{$m->{name}}->{type} = $m->{deref}.'-implied'; + } + if ($m->{deref} eq 'struct*-length') { + $defaultTemplate{$m->{baseType}}->{array} = 1; + } elsif ($m->{deref} eq 'struct[]') { + $defaultTemplate{$m->{baseType}}->{array} = 1; + } + } + + if ($s->{returnedonly} eq 'true') { + $defaultTemplate{$s->{name}}->{name} = 'struct-readonly'; + } +} + +# build default overrides for commands +foreach my $s (values %{$api->{commands}}) { + my $overrides = $commandTypes->{overrides}; + my $types = $commandTypes->{types}; + + # check type updates anyway + foreach my $m (@{$s->{items}}) { + if ($m->{deref} eq 'struct*-length') { + $defaultTemplate{$m->{baseType}}->{name} = 'struct-readwrite' if !($m->{fullType} =~ m/const/n); + $defaultTemplate{$m->{baseType}}->{array} = 1; + } elsif ($m->{deref} eq 'struct*') { + $defaultTemplate{$m->{baseType}}->{name} = 'struct-readwrite' if !($m->{fullType} =~ m/const/n); + } + } + + next if (defined($overrides->{$s->{name}})); + + my $first = $s->{items}->[0]; + my $last = $s->{items}->[$#{$s->{items}}]; + my $result = $s->{proto}; + my $index = $s->{index}; + + #map { $index->{$_->{name}} = $_ } @{$s->{items}}; + + my $override = {}; + + # force handles to be instance types + if ($first->{deref} eq 'handle') { + $override->{$first->{name}}->{type} = 'instance'; + } + + # extension functions + if (defined($s->{extensions})) { + $override->{template} = $commandTypes->{templates}->{'method-extension'}; + } + + if ($last->{deref} eq 'handle*') { + if (!$last->{len}) { + my $t = $api->{handles}->{$last->{baseType}}; + print "constructor: $s->{name}\n";# if $sys->{verbose}; + $override->{$last->{name}}->{type} = + $t->{type} eq 'VK_DEFINE_HANDLE' && $t->{name} ne 'VkInstance' ? + 'dispatch*-output' : 'handle*-output'; + } elsif ($index->{$last->{len}}) { + print "constructor?: $s->{name}\n";# if $sys->{verbose}; + die; + } else { + print "allocate-constructor?: $s->{name} $last->{len}\n";# if $sys->{verbose}; + } + } + + # ones we care about with output + # handle*-length + # struct*-length + # uint32_t*-length + # void*-length + + if ($s->{successcodes} =~ m/VK_INCOMPLETE/ && $last->{deref} =~ m/-length$/) { + my $protoa = "$result->{fullType} $s->{name}(" + .join(', ', map { "$_->{fullType}" } @{$s->{items}}) + .")"; + my $protob = "$result->{deref} $s->{name}(" + .join(', ', map { $_->{len} ? "$_->{deref} \[$_->{len}\]" : $_->{deref} } @{$s->{items}}) + .")"; + + print "array-constructor: $protoa\n"; + print "array-constructor: $protob\n"; + $override->{template} = + defined($s->{extensions}) ? + $commandTypes->{templates}->{'method-extension-query'} : + $commandTypes->{templates}->{'method-query'}; + foreach my $m (@{$s->{items}}) { + if ($m->{deref} =~ m/-length$/ && (my $len = $index->{$m->{len}})) { + my $type; + + if ($m->{deref} eq 'handle*-length' && $api->{handles}->{$last->{baseType}}->{type} eq 'VK_DEFINE_HANDLE') { + $type = 'dispatch*-length-query'; + } else { + $type = $m->{deref}.'-query'; + } + + die "no template $m->{deref}-query" if !defined($commandTypes->{types}->{$type}); + $override->{$m->{name}}->{type} = $type; + + die "no template $len->{deref}-querysize" if !defined($commandTypes->{types}->{$len->{deref}.'-querylen'}); + $override->{$len->{name}}->{type} = $len->{deref}.'-querylen'; + } + } + } + + # implied lengths + foreach my $m (@{$s->{items}}) { + if ($m->{lengthfor}) { + my $nstar = $m->{deref} =~ tr/*/*/; + if ($nstar == 0) { + die "No '$m->{deref}-length' type ".Dumper($s) if !defined $types->{$m->{deref}.'-length'}; + $override->{$m->{name}}->{type} = $m->{deref}.'-length'; + } else { + if (defined($s->{extensions})) { + #$overrides->{$s->{name}}->{template} = $commandTypes->{templates}->{'method-extension'}; + print "length-extension: $m->{deref} $s->{name} $m->{name} $m->{lengthfor}\n" if $sys->{verbose}; + } else { + #die "No '$m->{deref}-output' type ".Dumper($s) if !defined $types->{$m->{deref}.'-otuput'}; + + #$overrides->{$s->{name}}->{template} = $commandTypes->{templates}->{'method-query'}; + #$overrides->{$s->{name}}->{$m->{name}}->{type} = $types->{$m->{deref}.'-output'}; + + print "length: $m->{deref} $s->{name} $m->{name} $m->{lengthfor}\n" if $sys->{verbose}; + } + + } + # TODO: implied return things + } + } + + $overrides->{$s->{name}} = $override if %{$override}; +} + +{ + my $overrides = $structTypes->{overrides}; + my $templates = $structTypes->{templates}; + + print Dumper(\%defaultTemplate); + + foreach my $k (keys %defaultTemplate) { + print "what?: ".Dumper($overrides->{$k}->{template}) if defined($overrides->{$k}) && defined($overrides->{$k}->{template}); + next if defined($overrides->{$k}) && defined($overrides->{$k}->{template}); + + my $t = $defaultTemplate{$k}; + my $name = $t->{name} || "struct-writeonly"; + + $name .= "-array" if $t->{array}; + + print "$name: $k\n"; + die "No override $k $name" if !$templates->{$name}; + $overrides->{$k}->{template} = $templates->{$name}; + } +} + + +#$overrides->{$s->{name}}->{template} = $structTypes->{templates}->{'struct-readonly'}; + +#print Dumper({ types=>$types, templates=>$templates }); +#print Dumper($vk); + +open(my $f, '>', 'types.pm'); +print $f Dumper($commandTypes, $structTypes); +close $f; + +if (1) { + + if (0) { + open(my $f, '>', 'api.pm'); + print $f Dumper($api); + close $f; + die; + } + + if (1) { + open(my $f, '>', 'data.pm'); + print $f Dumper({ + 'handles' => $api->{handles}, + 'types' => $api->{types}, + 'commands' => $api->{commands} + }); + close $f; + } + + if (0) { + my $f = openOutput($sys, "API"); + + print $f "package vulkan;\n"; + print $f "import jdk.incubator.foreign.*;\n"; + print $f "import java.lang.invoke.*;\n"; + print $f "import au.notzed.nativez.*;\n"; + print $f "public class API {\n"; + print $f "MemoryAddress self;\n"; + + foreach my $c (values %{$api->{commands}}) { + die if ($c->{alias}); + #print "$c->{name}\n"; + print $f formatFunction($api, $commandTypes, $c)."\n"; + #print formatFunctionDescriptor($commandTypes, $c)."\n"; + } + print $f "}\n"; + closeOutput($sys, "API", $f); + } + + exportEnums($vk, $api, 'VkConstants'); + + # dump out the extension function tables + { + my $f = openOutput($sys, 'DispatchInstance'); + my $template = $structTypes->{templates}->{dispatch}; + my @init = (); + my @fieldInit = (); + + foreach my $k (sort keys %{$api->{commands}}) { + my $c = $api->{commands}->{$k}; + + next if !defined($c->{extensions}); + + push @fieldInit, code::formatTemplate($template->{'field-init'}, $c); + push @init, code::formatTemplate($template->{'init'}, $c); + + } + + my $vars = { + package => 'vulkan', + Name => 'DispatchInstance', + init => join("\n\t\t", @init), + 'field-init' => join("\n\t", @fieldInit), + }; + print $f code::formatTemplateStream($template->{class}, $vars); + + closeOutput($sys, 'DispatchInstance', $f); + } + + + + foreach my $k (sort keys %{$api->{types}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $f = openOutput($sys, $s->{Name}); + print $f formatStruct($vk, $api, $s); + closeOutput($sys, $s->{Name}, $f); + } + + foreach my $k (sort keys %{$api->{handles}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $f = openOutput($sys, $s->{name}); + print $f formatHandle($vk, $api, $s); + closeOutput($sys, $s->{name}, $f); + } + + foreach my $k (sort keys %{$api->{funcpointers}}) { + my $s = $api->{data}->{$k}; + + die if !defined $s; + next if $s->{alias}; + + my $f = openOutput($sys, $s->{name}); + print $f formatFunctionPointer($vk, $api, $s); + closeOutput($sys, $s->{name}, $f); + } + + + exit 0; + + #print Dumper (grep { !defined $_->{items} } values %{$api->{enums}}); + + #print Dumper($api->{data}); + + print "Unique Types:\n"; + foreach my $k (sort keys %$all) { + print "$k\n"; + } + + exit 0; + + foreach my $k (sort keys %{$api->{commands}}) { + my $c = $api->{commands}->{$k}; + + #print Dumper ($c); + + foreach my $m ($c->{proto}, @{$c->{items}}) { + my $t = $vk->{data}->{$m->{baseType}}; + + die if !defined $m->{baseType}; + print "? $m->{baseType} ($k $m->{name}\n" if !defined($t); + + while ($t->{alias}) { + print "Alias: $t->{name} -> $t->{alias}\n"; + $t = $vk->{data}->{$t->{alias}}; + } + } + } + +} else { + +foreach my $k (sort keys %{$vk->{index}}) { + print "$k\n"; +} + +my $masks = {}; +my $enums = {}; + +foreach my $k (sort keys %{$vk->{types}}) { + my $s = $vk->{types}->{$k}; + #print Dumper($s); + foreach my $m (@{$s->{items}}) { + my $t = $vk->{data}->{$m->{baseType}}; + + die if !defined $m->{baseType}; + print "? $m->{baseType} ($k $m->{name}\n" if !defined($t); + + while ($t->{alias}) { + print "Alias: $t->{name} -> $t->{alias}\n"; + $t = $vk->{data}->{$t->{alias}}; + } + + $masks->{$t->{name}} = $t if ($t->{category} eq 'bitmask'); + $enums->{$t->{name}} = $t if ($t->{category} eq 'enum'); + } +} + +print Dumper($masks); +foreach my $k (sort keys %{$masks}) { + my $s = $masks->{$k}; + my $t; + + if ($s->{requires}) { + $t = $vk->{data}->{$s->{requires}}; + } elsif ($s->{name} =~ m/(.*)Flags([0-9A-Z]*)/o && defined $vk->{data}->{"$1FlagBits$2"}) { + print "> $s->{name} $1FlagBits$2\n"; + $t = $vk->{data}->{"$1FlagBits$2"}; + } else { + print "? $s->{name}\n"; + $t = $s; + } + print "! $s->{name} r=$s->{requires}\n" if !defined($t); + #print Dumper($t); + +} +} + +sub loadTypes { + my $api = shift; + my $file = shift; + my $config = new config({ includes=>[ $FindBin::Bin ] }, "$FindBin::Bin/$file"); + + my $types = {}; + my $templates = {}; + my $overrides = {}; + + foreach my $t (@{$config->{objects}}) { + if ($t->{type} eq 'type') { + my $nopts = $#{$t->{options}}; + my $type; + + if ($nopts >= 0 && defined($types->{$t->{options}->[0]})) { + $type = { %{$types->{$t->{options}->[0]}} }; + } elsif ($#{$t->{items}} >= 0) { + $type = {}; + } else { + die ("No prototype provided/found for empty type ".Dumper($t)); + } + + if ($#{$t->{items}} >= 0) { + foreach my $s (@{$t->{items}}) { + my $x = { + code => defined($s->{literal}) ? $s->{literal} : $s->{options}->[$#{$s->{options}}] + }; + + $x->{eval} = 1 if config::optionFlag('eval', $s); + + $type->{$s->{match}} = $x; + } + } + + # check other options + foreach my $o (@{$t->{options}}) { + if ($o =~ m/^accessor=(.*)$/o) { + die "No template $1" if !defined($templates->{$1}); + $type->{accessor} = $templates->{$1}; + } elsif ($o eq 'need-frame') { + $type->{'need-frame'} = 1; + } elsif ($o eq 'need-scope') { + $type->{'need-scope'} = 1; + } elsif ($o eq 'need-alloc') { + $type->{'need-alloc'} = 1; + } elsif ($o eq 'is-instance') { + $type->{'is-instance'} = 1; + } else { + # doesn't ignore implied parant type + #die "Unknown option '$o' in ".Dumper($t); + } + } + + foreach my $k (split /,/,$t->{name}) { + $types->{$k} = $type; + } + } elsif ($t->{type} eq 'code') { + my $code = { + insert => {}, + }; + + foreach my $s (@{$t->{items}}) { + $code->{$s->{match}} = $s->{literal}; + $code->{$s->{match}} =~ s/^\t//gm; + } + foreach my $o (@{$t->{options}}) { + if ($o =~ m/insert=(.*)/) { + foreach my $t (split /,/,$1) { + if ($t =~ m/(.*):(.*)/) { + die if !defined $templates->{$1}->{$2}; + $code->{insert}->{$2} = $templates->{$1}->{$2}; + } + } + } + } + + $templates->{$t->{name}} = $code; + } elsif ($t->{type} eq 'override') { + foreach my $s (@{$t->{items}}) { + my $c = { }; + foreach my $o (@{$s->{options}}) { + if ($o =~ m/^(.*)=type:(.*)/) { + die "No such type $s->{match} $2\n" if !defined $types->{$2}; + $c->{$1}->{type} = $2; + } elsif ($o =~ m/^(.*)=accessor:(.*)/) { + die "No accessor template $o" if !defined($templates->{$2}); + $c->{$1}->{accessor} = $templates->{$2}; + #} elsif ($o =~ m/^(.*)=method:(.*)/) { + # die "No method template $o" if !defined($templates->{$2}); + # $c->{$1}->{method} = $templates->{$2}; + } elsif ($o =~ m/^template=(.*)/) { + die "No template $o" if !defined($templates->{$1}); + $c->{template} = $templates->{$1}; + } + } + $overrides->{$s->{match}} = $c; + } + } + } + + # templates should probably just go in one + { + types => $types, + templates => $templates, + overrides => $overrides, + }; +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub exportEnums { + my $vk = shift; + my $api = shift; + my $name = shift; + my $seen = {}; + + my $f = openOutput($sys, $name); + + print $f "package vulkan;\npublic interface VkConstants {\n"; + + # special case for api constants + # special case for api constants + { + my $s = $api->{data}->{'API Constants'}; + + print $f "\n\t// API Constants\n"; + + foreach my $m (@{$s->{items}}) { + next if defined($m->{alias}); + next if $seen->{$m->{name}}++; + + my $i = $enumInfo->{$m->{type}}; + my $v = $m->{value}; + + # convert to java + $v =~ s/[()ULF]+//gon if (!($v =~ m/^"/)); + + print $f "\tpublic final static $i->{type} $m->{name} = $v$i->{suffix};\n"; + } + } + + foreach my $k (sort keys %{$api->{enums}}) { + my $s = $api->{data}->{$k}; + my $type = $s->{fullType} ? $s->{fullType} : 'VkFlags'; + my $i = $enumInfo->{$vk->{data}->{$type}->{type}}; + + next if $s->{alias}; + + print $f "\n\t// $s->{name} $type\n"; + + foreach my $m (@{$s->{items}}) { + next if defined($m->{alias}); + next if $seen->{$m->{name}}++; + + my $v = $m->{value}; + + $v = 1<<$m->{bitpos} if !defined($v) && defined($m->{bitpos}); + + die Dumper("Ca't work out value", $m, $s) if !defined($v); + print $f "\tpublic final static $i->{type} $m->{name} = $v$i->{suffix};\n"; + } + } + print $f "}\n"; + + closeOutput($sys, $name, $f); +} + +# ###################################################################### # +# class.name to class/name.java +sub classToPath { + my $sys = shift; + my $name = shift; + + $name = $sys->{package}.'.'.$name; + $name =~ s@\.@/@g; + $name = $sys->{output}.'/'.$name.'.java'; + $name; +} + +sub closeOutput { + my $sys = shift; + my $name = shift; + my $f = shift; + my $path = classToPath($sys, $name); + + close($f) || die; + rename($path.'~', $path) || die ("rename failed: $!"); +} + +sub openOutput { + my $sys = shift; + my $name = shift; + + my $path = classToPath($sys, $name); + my $dir = dirname($path); + + make_path($dir) if (!-d $dir); + + open(my $f, ">", $path.'~') || die ("Cannot open '$path' for writing"); + print "writing '$path'\n" if $sys->{verbose} > 0; + $f; +} + + +# Calculate canonical derefernce types for each field and parameter +sub analyseFields { + my $vk = shift; + my $api = shift; + my $seen = shift; + my $s = shift; + + # what about const? + # FIXME: bitfields + + my $index = {}; + + map { $index->{$_->{name}} = $_ } @_; + + $s->{index} = $index; + + foreach my $m (@_) { + my $t = $api->{data}->{$m->{baseType}}; + my $nstar = $m->{fullType} =~ tr/*/*/; + my $type; + my $array = ''; + + # Check array sizes + if ($m->{fullType} =~ m/\[(.*)\]\[(.*)\]$/o) { + $array = '[][]'; + $m->{len1} = $1; + $m->{len2} = $2; + } elsif ($m->{fullType} =~ m/\[(.*)\]$/o) { + my $isize = $1; + my $size; + if ($isize =~ m/^\d+$/n) { + $size = $isize; + } else { + $size = $vk->{data}->{'API Constants'}->{index}->{$isize}->{value}; + } + $array = '[]'; + $m->{len1} = $size; + } elsif ($m->{fullType} =~ m/[\[\]]/on) { + die Dumper($m) + } + + if (!defined($t)) { + # will be primitive or external + $type = $m->{baseType} if ($nstar == 0); + $type = "$m->{baseType}*" if ($nstar == 1); + $type = "$m->{baseType}**" if ($nstar == 2); + die if $nstar > 2; + } else { + # unmap aliases + while ($t->{alias}) { + print "alias $t->{name} -> $t->{alias}\n"; + $t = $api->{data}->{$t->{alias}}; + die if !defined $t; + $m->{baseType} = $t->{name}; + } + + if ($t->{category} =~ m/enum|bitmask/on) { + $t = $vk->{data}->{$t->{fullType}}; + $type = $t->{type} if ($nstar == 0); + $type = "$t->{type}*" if ($nstar == 1); + die if $nstar > 1; + } elsif ($t->{category} =~ m/struct|union/on) { + $m->{type} = $t->{name}; + $type = 'struct' if ($nstar == 0); + $type = 'struct*' if ($nstar == 1); + $type = 'struct**' if ($nstar == 2); + die if $nstar > 2; + } elsif ($t->{category} eq 'handle') { + $m->{type} = $t->{name}; + #if ($t->{type} eq 'VK_DEFINE_HANDLE') { + # $type = 'dhandle' if ($nstar == 0); + # $type = 'dhandle*' if ($nstar == 1); + #} else { + $type = 'handle' if ($nstar == 0); + $type = 'handle*' if ($nstar == 1); + #} + die if $nstar > 1; + } elsif ($t->{category} eq 'basetype') { + # ?? + $type = $t->{type} if ($nstar == 0); + $type = "$t->{type}*" if ($nstar == 1); + die Dumper($m, $t) if $nstar > 1; + } elsif ($t->{category} eq 'funcpointer') { + $m->{type} = $t->{name}; + $type = "funcpointer" if ($nstar == 0); + die if $nstar > 0; + } else { + die Dumper($m, $t); + } + } + + # an array type with a length + if ($nstar > 0 && $m->{len}) { + if ($s->{category} =~ m/struct|union/on) { + if ($m->{altlen}) { + if ($m->{altlen} =~ m/^(.*)(VK_UUID_SIZE)(.*)$/) { + $m->{length} = $1.'VkConstants.'.$2.$3; + } elsif ($m->{altlen} =~ m/^([^a-zA-Z_]*)([a-zA-Z_]+)(.*)$/) { + my $len = $index->{$2}; + if (defined $len) { + $m->{length} = $1.'get'.ucfirst($2).'()'.$3; + #$index->{$2}->{lengthfor} = $m->{name} if $index->{$2}; + $m->{lengthfrom} = $len->{name}; + } + } else { + die "Unhandled len/altlen: ".Dumper($m); + } + } elsif ($m->{len} =~ m/(.*),null-terminated/) { + my $len = $index->{$1}; + if (defined $len) { + $m->{length} = "get$len->{Name}()"; + $m->{lengthfrom} = $len->{name}; + $len->{lengthfor} = $m->{name}; + $m->{'set-length'} = "set$len->{Name}((int)Memory.length($m->{name}))"; + } + } elsif ($m->{len} eq 'null-terminated') { + # ignore + } elsif ($m->{len} =~ m/^(.*),(\d+)$/) { + # ignore? + } else { + my $len = $index->{$m->{len}}; + if (defined $len) { + my $cast = ($len->{fullType} eq 'uint32_t') ? '(int)' : ''; + + die "Not simple type" if ($len->{fullType} ne $len->{baseType}); + $m->{length} = "get$len->{Name}()"; + $m->{lengthfrom} = $len->{name}; + $len->{lengthfor} = $m->{name}; + $m->{'set-length'} = "set$len->{Name}($cast"."Memory.length($m->{name}))"; + } else { + die "what?".Dumper($m); + } + } + } elsif ($s->{category} eq 'command') { + if ($m->{altlen}) { + die; + } else { + $m->{length} = $m->{len} if $index->{$m->{len}}; + $index->{$m->{len}}->{lengthfor} = $m->{name} if $index->{$m->{len}}; + } + } else { + die Dumper($s); + } + $type = $type.'-length' if $m->{length}; + } + + $seen->{$m->{fullType}} = $type.$array; + $m->{deref} = $type.$array; + + my $name = $m->{name}; + #if ($s->{type} =~ m/struct|union/on) + { + # Strip leading 'p' for pointers + if ($name eq 'ppGeometries') { # && $s->{name} eq 'VkAccelerationStructureBuildGeometryInfoKHR') { + $name = 'PGeometries'; + } elsif ($nstar > 0 && $name =~ m/^p{$nstar}/) { + my $strip = $nstar; + + if ($t->{category} eq 'handle' && $type ne 'handle*-length') { + $strip -= 1; + } + + $name = substr $name, $strip; + } + + $name =~ s/^pfn//o if $type eq 'funcpointer'; + # CamelCase + $name =~ s/(?:^|_)(.)/\U$1/og; + } + $m->{Name} = $name; + } +} + +sub analyseTypes { + my $vk = shift; + my $api = shift; + my $seen = {}; + + foreach my $s (grep { $_->{items} } values %{$api->{types}}) { + analyseFields($vk, $api, $seen, $s, @{$s->{items}}); + + my $name = $s->{name}; + $name =~ s/(?:^|_)(.)/\U$1/og; + $s->{Name} = $name; + + my $first = $s->{items}->[0]; + my $second = $s->{items}->[1]; + + if ($first->{name} eq 'sType' && $second && $second->{name} eq 'pNext') { + $first->{'no-setall'} = 1; + $second->{'no-setall'} = 1; + print "typed: $s->{name}\n"; + } else { + print "untyped: $s->{name}\n"; + } + } + + foreach my $c (values %{$api->{funcpointers}}) { + analyseFields($vk, $api, $seen, $c, $c->{proto}, @{$c->{items}}); + } + + foreach my $c (values %{$api->{commands}}) { + $c->{proto}->{name} = 'result$'; + $c->{proto}->{Name} = 'result$'; + analyseFields($vk, $api, $seen, $c, $c->{proto}, @{$c->{items}}); + + # collect all member functions on handles + my $first = $c->{items}->[0]; + my $last = $c->{items}->[$#{$c->{items}}]; + if ($first->{deref} eq 'handle') { + my $t = $api->{handles}->{$first->{baseType}}; + while ($t->{alias}) { + $t = $api->{handles}->{$t->{alias}}; + } + die "No handle found ".Dumper($c) if !defined $t; + push @{$t->{commands}}, $c->{name}; + } elsif ($c->{name} =~ m/vkEnumerateInstance|vkCreateInstance/) { + push @{$api->{handles}->{'VkInstance'}->{commands}}, $c->{name}; + } else { + die "No owner for call ".Dumper($c); + } + } + + print "Unique Types:\n"; + my $base = {}; + map { $base->{$_} = 1 } values %$seen; + + foreach my $k (sort keys %$base) { + print "$k\n"; + } +} + +# what else? +# vk? api? +# this way-over-evaluates, probably only call once on every field and member instead +sub buildVars { + my $s = shift; + my $m = shift; + my $type = shift; + my $v = { %{$m} }; + + foreach my $k (keys %$type) { + my $t = $type->{$k}; + + if (ref($t) eq '') { + $v->{$k} = $t; + } elsif ($t->{eval}) { + $v->{$k} = eval $t->{code}; + + die "Eval failed: $! $@: ".Dumper($m, $type) if !defined($v->{$k}); + } elsif (defined($t->{code})) { + $v->{$k} = $t->{code}; + } + } + + $v; +} + +sub formatStructLayout { + my $types = shift; + my $s = shift; + my $offset = 0; + my @fields = (); + + # This doens't need to worry about overrides + + foreach my $m (@{$s->{items}}) { + my $type = $types->{$m->{deref}}; + my $diff = $m->{bitOffset} - $offset; + + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + if ($type) { + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v).".withName(\"$m->{name}\") /* $m->{deref} $m->{fullType} */"; + $offset = $m->{bitOffset} + $m->{bitSize}; + } else { + push @fields, "/* Missing: $m->{deref} $m->{name} */"; + } + } + my $diff = $s->{bitSize} - $offset; + push @fields, "MemoryLayout.paddingLayout($diff)" if $diff; + + return "MemoryLayout.".$s->{category}."Layout(\n\t\t".join(",\n\t\t", @fields).").withName(\"$s->{name}\")"; +} + +sub formatStruct { + my $vk = shift; + my $api = shift; + my $s = shift; + my $templates = $structTypes->{templates}; + my $types = $structTypes->{types}; + my $overrides = $structTypes->{overrides}; + + my $override = $overrides->{$s->{name}}; + my $template = $override->{template} ? $override->{template} : $templates->{'struct-writeonly'}; + + my $info = { + get => [], + set => [], + getorset => [], + varhandle => [], + init => [], + create => {}, + }; + + if ($s->{category} eq 'struct') { + $info->{create}->{create} = { + setallArgs => [ 'SegmentAllocator alloc$' ], + setall => [], + }; + } elsif ($s->{category} eq 'union') { + foreach my $m (@{$s->{items}}) { + $info->{create}->{"create$m->{Name}"} = { + setallArgs => [ 'SegmentAllocator alloc$' ], + setall => [], + }; + } + } else { + die; + } + + #map { $_->{typeInfo} = buildVars($s, $_, $types->{$_->{deref}}) } @{$s->{items}}; + + # FIXME: unions need multiple constructors! + + foreach my $m (@{$s->{items}}) { + my $nstar = $m->{deref} =~ tr/*/*/; + my $deref = defined($override->{$m->{name}}) && defined($override->{$m->{name}}->{type}) ? $override->{$m->{name}}->{type} : $m->{deref}; + my $type = $types->{$deref}; + my $v = buildVars($s, $m, $type); + + die "No type $deref ".Dumper($m, $s) if !$type; + + #push @{$info->{getset}}, map { "// $_" } split(/\n/, Dumper($m->{deref}, $type, $override->{$m->{name}})); + push @{$info->{varhandle}}, "/* ? Accessor: $m->{fullType} [$m->{deref}] $m->{name} len=$m->{len} lenfor=$m->{lengthfor} override=$deref */"; + + if ($type->{accessor}) { + push @{$info->{getorset}}, code::formatTemplate($type->{accessor}->{getorset}, $v) if $type->{accessor}->{getorset}; + + if ($m->{values}) { + push @{$info->{init}}, code::formatTemplate($type->{accessor}->{init}, $v) if $type->{accessor}->{init}; + push @{$info->{get}}, code::formatTemplate($type->{accessor}->{get}, $v) if $type->{accessor}->{get}; + } else { + push @{$info->{get}}, code::formatTemplate($type->{accessor}->{get}, $v) if $type->{accessor}->{get}; + push @{$info->{set}}, code::formatTemplate($type->{accessor}->{set}, $v) if $type->{accessor}->{set}; + + # FIXME: something here is adding length parameters which are already handled by the setXX() calls + if (!$m->{'no-setall'}) { + my $create = $s->{category} eq 'struct' ? $info->{create}->{create} : $info->{create}->{"create$m->{Name}"}; + push @{$create->{setallArgs}}, code::formatTemplate($type->{accessor}->{'setall-arg'}, $v) if $type->{accessor}->{'setall-arg'}; + push @{$create->{setall}}, code::formatTemplate($type->{accessor}->{setall}, $v) if $type->{accessor}->{setall}; + } + } + } + push @{$info->{varhandle}}, code::formatTemplate($v->{handle}, $v) if $v->{handle}; + } + + # create constructors + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + layout => formatStructLayout($types, $s), + init => join ("\n", @{$info->{init}}), + get => join ("\n", @{$info->{get}}), + set => join ("\n", @{$info->{set}}), + getorset => join ("\n", @{$info->{getorset}}), + #'java-setall-arguments' => join (",", @{$info->{setallArgs}}), + #'java-setall' => join ("\n", @{$info->{setall}}), + varhandle => join ("\n", @{$info->{varhandle}}), + }; + + + # build sub-components using the full $v + my @createAll = (); + foreach my $k (keys %{$template->{insert}}) { + my $t = $template->{insert}->{$k}; + + if ($k eq 'create-all') { + foreach my $kk (keys %{$info->{create}}) { + my $create = $info->{create}->{$kk}; + + if ($#{$create->{setallArgs}} > 0) { + my $v = { + create => $kk, + Name => $s->{Name}, + 'java-setall-arguments' => join (', ', @{$create->{setallArgs}}), + 'java-setall' => join ("\n\t\t", @{$create->{setall}}), + }; + push @createAll, code::formatTemplateStream($t, $v); + } + } + } else { + $v->{$k} = code::formatTemplate($t, $v); + } + } + $v->{'create-all'} = join("\n", @createAll); + + join("\n", map { '// '.$_ } split(/\n/,Dumper($s)))."\n". + code::formatTemplateStream($template->{class}, $v); +} + +# TODO: the template here could be mapped from types.api perhaps? +# also same for the various fields, init/getset/etc. +sub formatHandle { + my $vk = shift; + my $api = shift; + my $s = shift; + my $templates = $structTypes->{templates}; + my $overrides = $structTypes->{overrides}; + my $override = $overrides->{$s->{name}}; + my $template = $override->{template} ? $override->{template} : $templates->{handle}; + + my $info = { + init => [], + commands => [], + }; + + if (defined $s->{commands}) { + foreach my $k (sort @{$s->{commands}}) { + my $c = $api->{commands}->{$k}; + push @{$info->{commands}}, formatFunction($api, $commandTypes, $c); + } + } + + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + init => join ("\n", @{$info->{init}}), + commands => join ("\n", @{$info->{commands}}), + }; + + code::formatTemplateStream($template->{class}, $v); +} + +sub formatFunctionPointer { + my $vk = shift; + my $api = shift; + my $s = shift; + my $templates = $structTypes->{templates}; + my $template = $templates->{funcpointer}->{class}; + + my $info = { + init => [], + }; + + my $vcall = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + }; + + my $v = { + package => 'vulkan', + name => $s->{name}, + Name => $s->{Name}, + init => join ("\n", @{$info->{init}}), + upcall => code::formatTemplateStream($templates->{funcpointer}->{upcall}, $vcall), + downcall => code::formatTemplateStream($templates->{funcpointer}->{downcall}, $vcall), + }; + + code::formatTemplateStream($template, $v); +} + +sub formatFunctionDescriptor { + my $ct = shift; + my $s = shift; + + my $templates = $ct->{templates}; + my $types = $ct->{types}; + my $overrides = $ct->{overrides}; + + my @fields = (); + my $void = $s->{proto}->{fullType} eq 'void'; + my $override = $overrides->{$s->{name}}; + + foreach my $m ($void ? () : $s->{proto}, @{$s->{items}}) { + my $deref = defined($override->{$m->{name}}) && defined($override->{$m->{name}}->{type}) ? $override->{$m->{name}}->{type} : $m->{deref}; + my $type = $types->{$deref}; + + die "No type found ".Dumper($m, $s, $override) if !$type; + + my $v = buildVars($s, $m, $type); + + push @fields, code::formatTemplate($v->{layout}, $v)." /* $m->{deref} $m->{name} */"; + } + + return ($void ? 'FunctionDescriptor.ofVoid(' : 'FunctionDescriptor.of(') + .join(",\n\t\t", @fields).')'; +} + +sub formatFunction { + my $api = shift; + my $ct = shift; + my $s = shift; + my $templates = $ct->{templates}; + my $types = $ct->{types}; + my $overrides = $ct->{overrides}; + my $void = $s->{proto}->{fullType} eq 'void'; + my $override = $overrides->{$s->{name}}; + my $template = $override->{template} ? $override->{template} : $templates->{method}; + + my @javaArgs = (); + my @invokeArgs = (); + my @nativeInit = (); + my @queryInit = (); + my @queryArgs = (); + + my $info = { + rename => $s->{name}, + name => $s->{name}, + 'function-descriptor' => formatFunctionDescriptor($ct, $s), + 'native-result-define' => '', + 'native-result-assign' => '', + 'result-test' => '', + 'create-frame' => '', + 'java-result' => 'void', + 'java-result-assign' => '', + 'java-result-return' => 'return;', + 'result-throw' => '', + }; + + my $hasInstance = 0; + my $needFrame = 0; + my $needAlloc = 0; + my $needScope = 0; + + #return if !defined($override->{template}); + #return if ($s->{name} ne 'vkCmdUpdateBuffer'); + + foreach my $m (@{$s->{items}}) { + my $deref = defined($override->{$m->{name}}) && defined($override->{$m->{name}}->{type}) ? $override->{$m->{name}}->{type} : $m->{deref}; + my $type = $types->{$deref}; + + die "No type found ".Dumper($m, $s, $override) if !$type; + + my $v = buildVars($s, $m, $type); + + #push @javaArgs, "/* $m->{name} $m->{deref} */ " if !$v->{'java-argument'}; + + push @javaArgs, "/* $m->{name} $m->{deref} */ ".code::formatTemplate($v->{'java-arg'}, $v) if ($v->{'java-arg'}); + push @invokeArgs, code::formatTemplate($v->{'invoke-arg'}, $v) if ($v->{'invoke-arg'}); + + push @nativeInit, code::formatTemplate($v->{'native-init'}, $v) if ($v->{'native-init'}); + push @queryInit, code::formatTemplate($v->{'query-init'}, $v) if ($v->{'query-init'}); + + if ($v->{'query-arg'}) { + push @queryArgs, code::formatTemplate($v->{'query-arg'}, $v); + } elsif ($v->{'invoke-arg'}) { + push @queryArgs, code::formatTemplate($v->{'invoke-arg'}, $v); + } + + $info->{'java-result'} = code::formatTemplate($v->{'java-result'}, $v) if ($v->{'java-result'}); + $info->{'java-result-return'} = code::formatTemplate($v->{'java-result-return'}, $v) if ($v->{'java-result-return'}); + $info->{'java-result-assign'} = code::formatTemplate($v->{'java-result-assign'}, $v) if ($v->{'java-result-assign'}); + + $needScope = 1 if $type->{'need-scope'}; + $needFrame = 1 if $type->{'need-frame'}; + $needAlloc = 1 if $type->{'need-alloc'}; + $hasInstance = 1 if $type->{'is-instance'}; + } + + $info->{'static'} = $hasInstance ? '' : 'static '; + + if ($s->{successcodes}) { + my @codes = split(/,/,$s->{successcodes}); + + $info->{'native-result-define'} = 'int result$;'; + $info->{'native-result-assign'} = 'result$ = (int)'; + $info->{'result-test'} = 'if ('.join("||", map { '(result$ == VkConstants.'.$_.')' } @codes).')'; + $info->{'result-throw'} = 'throw new RuntimeException("error " + result$);'; + + if ($#codes > 0 && $info->{'java-result'} eq 'void') { + $info->{'java-result'} = 'int'; + $info->{'java-result-return'} = 'return result$;'; + } + + } elsif ($s->{proto}->{fullType} ne 'void') { + my $m = $s->{proto}; + my $type = defined($override->{$m->{name}}) ? $override->{$m->{name}}->{type} : $types->{$m->{deref}.'-return'}; + + die Dumper($m, $s) if !defined($type); + die Dumper($m, $s) if !defined($type->{'java-result-return'}); + + my $v = buildVars($s, $m, $type); + + $info->{'native-result-define'} = code::formatTemplate($v->{'native-result-define'}, $v); + $info->{'native-result-assign'} = code::formatTemplate($v->{'native-result-assign'}, $v); + $info->{'native-result-define'}.= join("", map { "// $_\n" } split(/\n/, Dumper($m))); + $info->{'java-result'} = code::formatTemplate($v->{type}, $v); + $info->{'java-result-return'} = code::formatTemplate($v->{'java-result-return'}, $v); + + $needScope = 1 if $type->{'need-scope'}; + } + + $info->{'create-frame'} = '(Frame frame$ = Frame.frame())' if $needFrame; + push @javaArgs, 'SegmentAllocator alloc$' if $needAlloc; + push @javaArgs, 'ResourceScope scope$' if $needScope; + + $info->{'java-arguments'} = join ",\n\t", @javaArgs; + $info->{'native-init'} = join "\n\t", @nativeInit; + $info->{'invoke-arguments'} = join ", ", @invokeArgs; + $info->{'query-init'} = join "\n\t\t\t", @queryInit; + $info->{'query-arguments'} = join ", ", @queryArgs; + + $info->{successcodes} = $s->{successcodes} ? $s->{successcodes} : ''; + $info->{errorcodes} = $s->{errorcodes} ? $s->{errorcodes}: ''; + + #join("\n", map { '// '.$_ } split(/\n/,Dumper($s)))."\n". + code::formatTemplate($template->{invoke}, $info); +} diff --git a/src/notzed.vulkan/gen/struct-types.api b/src/notzed.vulkan/gen/struct-types.api new file mode 100644 index 0000000..1625945 --- /dev/null +++ b/src/notzed.vulkan/gen/struct-types.api @@ -0,0 +1,821 @@ +# -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- + +# types for structs and unions + +#accessor=code template +# {type} - java type +# {java-get} +# {java-set} +# {init}* + +code value { + get {{ + /* {deref} */ + public {type} get{Name}() { + return {java-get}; + } + }} + set {{ + /* {deref} */ + public void set{Name}({type} {name}) { + {java-set}; + } + }} + + # FIXME: only handles single element arrays + init {{ {name}$VH.set(this.segment, VkConstants.{values}); }} + + # for complex constructors? + setall-arg {{ {type} {name} /* value */ }} + setall {{ self$.set{Name}({name}); }} +} + +code value-array { + getorset {{ + /* value-array {deref} */ + public {type} get{Name}() { + try { + return {java-get}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + public {typei} get{Name}Element(int i$) { + return {java-geti}; + } + public void set{Name}Element(int i$, {typei} {name}) { + {java-seti}; + } + }} +} + +code value-array2d { + getorset {{ + /* value-array2d {deref} */ + public {type} get{Name}() { + try { + return {java-get}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + public {typei} get{Name}Element(int i$, int j$) { + return {java-geti}; + } + public void set{Name}Element(int i$, int j$, {typei} {name}) { + {java-seti}; + } + }} +} + +code inline { + getorset {{ + /* inline {deref} */ + public {type} get{Name}() { + try { + return {java-get}; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} +} + +# value with a SegmentAllocator passed to set() +code value-alloc { + get {{ + /* {deref} */ + public {type} get{Name}() { + return {java-get}; + } + }} + set {{ + /* {deref} */ + public void set{Name}({type} {name}, SegmentAllocator alloc$) { + {java-set}; + } + }} + setall-arg {{ {type} {name} }} + setall {{ self$.set{Name}({name}, alloc$); }} +} + +# implied accessors are ignored in constructors +code value-implied { + get {{ + /* {deref} */ + public {type} get{Name}() { + return {java-get}; + } + }} + set {{ + /* {deref} */ + public void set{Name}({type} {name}) { + {java-set}; + } + }} + + # supposed to be handled in set of the target, but ...? + setall {{ self$.set{Name}(({type})Memory.length({lengthfor})); }} +} + +# ###################################################################### # + +code dispatch { + class {{ + // template: dispatch:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + class {Name} { + + {field-init} + + {Name}(VkInstance instance$, ResourceScope scope$) { + {init} + } + } + }} + field-init {{ final NativeSymbol {name}$NS; }} + init {{ {name}$NS = instance$.vkGetInstanceProcAddr("{name}", scope$); }} + +} + +# non-dispatchable handle +code handle { + class {{ + // template: handle:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + + private {name}(MemoryAddress address, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + {init} + } + + public static {name} create(MemoryAddress address, ResourceScope scope) { + return new {name}(address, scope); + } + + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc) { + return HandleArray.createArray(1, alloc, {name}::create); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + } + }} +} + +# VkInstance +code handle-instance { + class {{ + // template: handle-instance:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + final DispatchInstance dispatch; + + private {name}(MemoryAddress address, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + this.dispatch = new DispatchInstance(this, scope); + {init} + } + + public static {name} create(MemoryAddress address, ResourceScope scope) { + return new {name}(address, scope); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + + {commands} + } + }} +} + +# dispatchable handle +code handle-dispatch { + class {{ + // template: handle-dispatch:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {name} implements Pointer { + final NativeSymbol self; + final DispatchInstance dispatch; + + private {name}(MemoryAddress address, DispatchInstance dispatch, ResourceScope scope) { + this.self = NativeSymbol.ofAddress("{name}", address, scope); + this.dispatch = dispatch; + {init} + } + + public static {name} create(MemoryAddress address, DispatchInstance dispatch, ResourceScope scope) { + return new {name}(address, dispatch, scope); + } + + // TODO: evaluate how scope fits here + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc, DispatchInstance dispatch, ResourceScope scope) { + return HandleArray.createArray(1, alloc, (a, s) -> create(a, dispatch, s), scope); + } + + public static HandleArray<{name}> createArray(long length, SegmentAllocator alloc, VkInstance instance, ResourceScope scope) { + return HandleArray.createArray(1, alloc, (a, s) -> create(a, instance.dispatch, s), scope); + } + + public MemoryAddress address() { + return self.address(); + } + + public ResourceScope scope() { + return self.scope(); + } + + {commands} + } + }} +} + +# FIXME: unimplemented +code funcpointer { + class {{ + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public interface {name} { + + void call(); + + {upcall} + {downcall} + } + }} + upcall {{ + public static FunctionPointer<{Name}> upcall({Name} target$, ResourceScope scope$) { + throw new UnsupportedOperationException(); + } + }} + downcall {{ + public static FunctionPointer<{Name}> downcall(MemoryAddress target$, ResourceScope scope$) { + throw new UnsupportedOperationException(); + } + }} +} + +# shared struct components +code struct { + header {{ + public MemorySegment segment; + + public static final GroupLayout LAYOUT = {layout}; + + private {Name}(MemorySegment segment) { + this.segment = segment; + {init} + } + + public static {Name} create(MemorySegment segment) { + return new {Name}(segment); + } + + public static {Name} create(MemoryAddress address, ResourceScope scope) { + return new {Name}(MemorySegment.ofAddress(address, LAYOUT.byteSize(), scope)); + } + + public static {Name} create(SegmentAllocator alloc) { + return new {Name}(alloc.allocate(LAYOUT)); + } + + // Pointer + @Override + public MemoryAddress address() { + return segment.address(); + } + + // Pointer + @Override + public ResourceScope scope() { + return segment.scope(); + } + + @Override + public String toString() { + return Memory.toString(segment, LAYOUT); + } + }} + create-all {{ + public static {Name} {create}({java-setall-arguments}) { + {Name} self$ = create(alloc$.allocate(LAYOUT)); + + {java-setall} + + return self$; + } + }} + set-all {{ + public void init({java-setall-arguments}) { + {Name} self$ = this; + + {java-setall} + } + }} + array {{ + static {Name} createArray(MemoryAddress addr, long length, ResourceScope scope) { + return new {Name}(MemorySegment.ofAddress(addr, length * LAYOUT.byteSize(), scope)); + } + + public static {Name} createArray(long length, SegmentAllocator alloc) { + return new {Name}(alloc.allocateArray(LAYOUT, length)); + } + + public final static MethodHandle LAYOUT$SH = MemoryLayout.sequenceLayout(LAYOUT).sliceHandle(MemoryLayout.PathElement.sequenceElement()); + + // Array + @Override + public long length() { + return segment.byteSize() / LAYOUT.byteSize(); + } + + // Array + #Override + public {Name} getAtIndex(long index$) { + try { + return create((MemorySegment)LAYOUT$SH.invokeExact(this.segment, index$)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} +} + +# default - writeonly struct +code struct-writeonly insert=struct:header,struct:create-all { + class {{ + // template: struct-writeonly:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {create-all} + + {set} + {getorset} + + {varhandle} + } + }} +} + +code struct-writeonly-array insert=struct:header,struct:create-all,struct:array { + class {{ + // template: struct-writeonly-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer, Array<{Name}> { + {header} + {array} + + {create-all} + + {set} + {getorset} + + {varhandle} + } + }} +} + +code struct-readonly insert=struct:header { + class {{ + // template: struct-readonly:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {get} + {getorset} + + {varhandle} + } + }} +} + +code struct-readonly-array insert=struct:header,struct:array { + class {{ + // template: struct-readonly-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer, Array<{Name}> { + {header} + {array} + + {get} + {getorset} + + {varhandle} + } + }} +} + +code struct-readwrite insert=struct:header,struct:create-all { + class {{ + // template: struct-readwrite:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer { + {header} + + {create-all} + + {get} + {set} + {getorset} + + {varhandle} + } + }} +} + +code struct-readwrite-array insert=struct:header,struct:create-all,struct:array { + class {{ + // template: struct-readwrite-array:class + package {package}; + import jdk.incubator.foreign.*; + import java.lang.invoke.*; + import au.notzed.nativez.*; + + public class {Name} implements Pointer,Array<{Name}> { + {header} + {array} + + {create-all} + + {get} + {set} + {getorset} + + {varhandle} + } + }} +} + +# how to arrays? in code? +# how to lengths? + +type value accessor=value { + java-get {{ ({type}){name}$VH.get(this.segment) }} + java-set {{ {name}$VH.set(this.segment, {name}) }} + handle {{ final static VarHandle {name}$VH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}")); }} +} + +type value-array accessor=value-array { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + java-geti {{ ({typei}){name}$EH.get(i$) }} + java-seti {{ {name}$EH.set(i$, {name}) }} + handle {{ + final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); + final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement()); + }} +} + +type value-array2d accessor=value-array2d { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + java-geti {{ ({typei}){name}$EH.get(i$, j$) }} + java-seti {{ {name}$EH.set(i$, j$, {name}) }} + handle {{ + final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); + final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.sequenceElement()); + }} +} + +type inline accessor=inline { + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} + java-get {{ {type}.create({native-get}) }} + handle {{ final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); }} +} + +type inline-array accessor=inline { +# type {{ MemorySegment }} + native-get {{ (MemorySegment){name}$SH.invokeExact(this.segment) }} +# java-get {{ {native-get} }} + java-get {{ {type}.create({native-get}) }} + handle {{ + final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}")); + }} +} + +type uint8_t,char value { + type {{ byte }} + layout {{ Memory.BYTE }} +} + +type uint16_t value { + type {{ short }} + layout {{ Memory.SHORT }} +} + +type uint32_t,int,int32_t value { + type {{ int }} + layout {{ Memory.INT }} +} + +type uint64_t,int64_t,size_t value { + type {{ long }} + layout {{ Memory.LONG }} +} + +type float value { + type {{ float }} + layout {{ Memory.FLOAT }} +} + +type double value { + type {{ double }} + layout {{ Memory.DOUBLE }} +} + +# ###################################################################### # +# implied length types +type uint64_t-implied,size_t-implied uint64_t accessor=value-implied { + +} + +type uint32_t-implied uint32_t accessor=value-implied { +} + +# ###################################################################### # + +type uint8_t[],char[] value-array { + type {{ ByteArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.BYTE) }} + typei {{ byte }} +} + +type uint32_t[],int32_t[] value-array { + type {{ IntArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.INT) }} + typei {{ int }} +} + +type uint64_t[] value-array { + type {{ LongArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.LONG) }} + typei {{ long }} +} + +type float[] value-array { + type {{ FloatArray }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.FLOAT) }} + typei {{ float }} +} + +type struct[] inline-array { + type {{ {baseType} }} + layout {{ MemoryLayout.sequenceLayout({len1}, {baseType}.LAYOUT) }} +} + +type float[][] value-array2d { + type {{ FloatArray }} + typei {{ float }} + layout {{ MemoryLayout.sequenceLayout({len1}, MemoryLayout.sequenceLayout({len2}, Memory.FLOAT)) }} +} + +# select=len? or what? + +type pointer value { + layout {{ Memory.POINTER }} + type {{ MemoryAddress }} + + native-get {{ (MemoryAddress){name}$VH.get(this.segment) }} + + java-get {{ {native-get} }} + java-set {{ {name}$VH.set(this.segment, Memory.address({name})) }} +} + +type void* pointer; + +type value-pointer pointer { + java-get {{ {type}.create((MemoryAddress){name}$VH.get(this.segment), segment.scope()) }} +} + +type uint8_t* value-pointer { + type ByteArray; +} + +type uint32_t*,int32_t*,int* value-pointer { + type IntArray; +} + +type uint64_t* value-pointer { + type LongArray; +} + +type float* value-pointer { + type FloatArray; +} + +type size_t* value-pointer { + type LongArray; +} + +type pointer-length pointer { + java-get {{ {type}.createArray({native-get}, {length}, this.segment.scope()) }} +} + +type void*-length pointer-length { + type MemorySegment; + java-get {{ MemorySegment.ofAddress({native-get}, {length}, this.segment.scope()) }} +} + +type uint8_t*-length pointer-length { + type ByteArray; +} + +type uint32_t*-length,int32_t*-length pointer-length { + type IntArray; +} + +type uint64_t*-length pointer-length { + type LongArray; +} + +type float*-length pointer-length { + type FloatArray; +} + +# special handling for strings, will fail if it isn't +type char* pointer accessor=value-alloc { + type {{ String }} + + java-get {{ ({native-get}).getUtf8String(0) }} + java-set {{ {name}$VH.set(this.segment, alloc$.allocateUtf8String({name}).address()) }} + + # this just verifies it's a string type + length eval {{ + if ($v->{len} =~ m/null-terminated/) { + 1; + } else { + die Dumper($v, $s); + } + }} + +} + +type char**-length pointer-length accessor=value-alloc { + type {{ String[] }} + + java-set {{ {name}$VH.set(this.segment, Memory.copyStringArray({name}, alloc$).address()); {set-length} }} + java-get {{ Memory.copyStringArray((MemoryAddress){name}$VH.get(this.segment), {length}) }} + +# set-length eval {{ +# if ($v->{len} =~ m/(.*),null-terminated/) { +# 'set'.ucfirst($1).'({name}.length)'; +# } else { +# die Dumper($v, $s); +# } +# }} +} + +# FIXME: wrong +type uint32_t** pointer { + type {{ HandleArray }} +} + +type funcpointer pointer { + type {{ FunctionPointer<{baseType}> }} + typei {{ {baseType} }} + java-get {{ {baseType}.downcall({native-get}, this.segment.scope()) }} +} + +type void** pointer { +} + +type void**-length pointer-length { + type PointerArray; +} + +type handle pointer { + type {{ {baseType} }} + java-get {{ {type}.create({native-get}, this.segment.scope()) }} +} + +# FIXME: wrong +type handle[] { + type {{ HandleArray<{typei}> }} + layout {{ MemoryLayout.sequenceLayout({len1}, Memory.POINTER) }} + #java-get {{ HandleArray.create({get}, {typei}::create) }} + java-get {{ error }} + typei {{ {baseType} }} +} + +type handle* pointer { + type {{ HandleArray<{typei}> }} + typei {{ {baseType} }} + java-get {{ HandleArray.create({native-get}, {typei}::create) }} +} + +type handle*-length pointer-length { + type {{ HandleArray<{baseType}> }} + typei {{ {baseType} }} + java-get {{ HandleArray.createArray({native-get}, {length}, {typei}::create, this.segment.scope()) }} + java-set {{ {name}$VH.set(this.segment, Memory.address({name})); {set-length} }} +} + +type struct inline { + type {{ {baseType} }} + layout {{ {baseType}.LAYOUT }} +} + +# how? length? +type struct* pointer { + type {{ {baseType} }} + java-get {{ {baseType}.create({native-get}, this.segment.scope()) }} +} + +type struct*-length pointer-length { + type {{ {baseType} }} + typei {{ {baseType} }} +} + +type struct** pointer { +} + +# xlib +type XID,Window,VisualID uint64_t; + +type Display* handle { + type xlib.XDisplay; +} + +# xcb +type xcb_window_t uint32_t; +type xcb_visualid_t uint32_t; +type xcb_connection_t* handle { + type xcb.Connection; +} + +override structs { + VkInstance template=handle-instance; + VkPhysicalDevice template=handle-dispatch; + VkDevice template=handle-dispatch; + VkCommandBuffer template=handle-dispatch; + VkQueue template=handle-dispatch; + + #VkQueueFamilyProperties template=struct-readwrite; + + # TODO: fill these in, there's a good number of them + #VkPhysicalDeviceMemoryProperties template=struct-readonly; + #VkMemoryRequirements template=struct-readonly; + #VkPipelineExecutableInternalRepresentationKHR template=struct-readonly; + + # override default array-length behaviours, some of these are independent of the array pointer + VkAccelerationStructureBuildGeometryInfoKHR geometryCount=type:uint32_t; + VkDescriptorSetLayoutBinding pImmutableSamplers=type:handle* descriptorCount=type:uint32_t; + VkPipelineViewportStateCreateInfo scissorCount=type:uint32_t viewportCount=type:uint32_t; + +# can't really work out what this is it's a void ** but it stays it's a pointer to uint8_t * in the spec + VkCuLaunchInfoNVX pParams=type:pointer pExtras=type:pointer; +} diff --git a/src/notzed.vulkan/gen/vulkan.pm b/src/notzed.vulkan/gen/vulkan.pm new file mode 100644 index 0000000..4cc1e26 --- /dev/null +++ b/src/notzed.vulkan/gen/vulkan.pm @@ -0,0 +1,830 @@ + +# Routines for working with vulkan registry + +package vulkan; + +use strict; + +use Data::Dumper; +use XML::Parser; +use Time::HiRes qw(clock_gettime CLOCK_REALTIME); + +sub new { + my $class = shift; + my $self = { + data => {}, + extensions => [], + handles => {}, + types => {}, + commands => {}, + features => [], + funcpointers => {}, + }; + + bless $self, $class; + + my $now = clock_gettime(CLOCK_REALTIME); + + loadRegistry($self); + + $now = clock_gettime(CLOCK_REALTIME) - $now; + print "$now load registry\n"; + + # build various indices + my $data = $self->{data}; + my $handles = $self->{handles}; + my $types = $self->{types}; + my $commands = $self->{commands}; + my $extensions = $self->{extensions}; + my $funcpointers = $self->{funcpointers}; + + foreach my $t (keys %{$data}) { + my $v = $data->{$t}; + $handles->{$v->{name}} = $v if $v->{category} eq 'handle'; + $types->{$v->{name}} = $v if $v->{category} =~ m/struct|union|platform/; + $commands->{$v->{name}} = $v if $v->{category} eq 'command'; + $funcpointers->{$v->{name}} = $v if $v->{category} eq 'funcpointer'; + } + + # mark extension functions? + foreach my $e (@{$extensions}) { + foreach my $name (map { @{$_->{commands}} } @{$e->{require}}) { + my $r = $data->{$name}; + + die if !defined($r); + + push @{$r->{extensions}}, $e; + } + } + + #print Dumper($self->{types}); + + # FIXME: link extensions up + # my $r = findData($data, $alias, "type:$ma->{name}"); + # die "cann't find $ma->{name}" if !defined $r; + # push @{$r->{extensions}}, $ext; + # push @{$r->{commands}}, $ma->{name}; + + $self; +} + +sub buildRequirement { + my $vk = shift; + my $data = shift; + my $req = shift; + my $ext = shift; + my $outconst = $data->{'API Constants'}; + my $allconst = $vk->{data}->{'API Constants'}; + + # add a couple of constants that the api's dont reference + push @{$outconst->{items}}, grep { $_->{name} =~ m/VK_UUID_SIZE/ } @{$allconst->{items}}; + + #print " $req->{comment}\n"; + #print Dumper($req->{types}); + foreach my $c (@{$req->{commands}}, @{$req->{types}}) { + my $d = $vk->{data}->{$c}; + + #print Dumper({ d=> $d, c => $c }); + + # what about aliases? + if (defined $d) { + if ($d->{category} eq 'enum' && !defined($d->{alias})) { + $d = { %$d }; + $d->{items} = [ @{$d->{items}} ] if defined($d->{items}); + } + $data->{$c} = $d; + + #print "Alias: $d->{alias}\n" if defined($d->{alias}); + } else { + print "Ignored: ".Dumper($c); + } + } + foreach my $c (@{$req->{enums}}) { + if ($c->{extends}) { + my $d = $data->{$c->{extends}}; + + if (defined($c->{value})) { + } elsif (defined($c->{bitpos})) { + $c->{value} = "".(1<<$c->{bitpos}); + } elsif (defined($c->{extnumber})) { + $c->{value} = "".(1000000000 + + 1000 * ($c->{extnumber} - 1) + + $c->{offset}); + } elsif (defined($c->{offset})) { + $c->{value} = $c->{dir}."".(1000000000 + + 1000 * ($ext->{number} - 1) + + $c->{offset}); + } elsif (defined($c->{alias})) { + } else { + print Dumper($c); + die; + } + + push @{$d->{items}}, $c; + } elsif ($c->{value}) { + if ($c->{value} =~ m/^"/) { + push @{$outconst->{items}}, { %$c, type=>'const char *' }; + } else { + push @{$outconst->{items}}, { %$c, type=>'uint32_t' }; + } + } elsif (!$c->{alias}) { + my @list = grep { $_->{name} eq $c->{name} } @{$allconst->{items}}; + die "Can't find constant '$c->{name}'".Dumper($c) if ($#list < 0); + push @{$outconst->{items}}, @list; + } + } +} + +sub buildFeatures { + my $vk = shift; + my $vers = shift; + my $plat = shift; + my $data = {}; + my $versions = {}; + my $platform = {}; + + map { $versions->{$_} = 1 } @$vers; + map { $platform->{$_} = 1 } @$plat; + + #print Dumper($vk->{features}); + + $data->{'API Constants'} = { + category => 'define', + items => [], + }; + + foreach my $feature (grep { $versions->{$_->{name}} } @{$vk->{features}}) { + print "Feature $feature->{name}\n"; + foreach my $req (@{$feature->{require}}) { + buildRequirement($vk, $data, $req); + } + } + + foreach my $extension (grep { $_->{supported} eq 'vulkan' && (!defined($_->{platform}) || $platform->{$_->{platform}}) + } @{$vk->{extensions}}) { + foreach my $req (grep { (!defined($_->{feature})) || $versions->{$_->{feature}} } + @{$extension->{require}}) { + print "Extension $extension->{name} $req->{feature}\n"; + buildRequirement($vk, $data, $req, $extension); + } + } + + #print "rest\n"; + #print Dumper($data); + + # TODO: need to remove aliases here? + my $handles = {}; + my $types = {}; + my $commands = {}; + my $enums = {}; + my $bitmasks = {}; + my $funcpointers = {}; + + foreach my $t (keys %{$data}) { + my $v = $data->{$t}; + $handles->{$v->{name}} = $v if $v->{category} eq 'handle'; + $types->{$v->{name}} = $v if $v->{category} =~ m/struct|union/on; + $commands->{$v->{name}} = $v if $v->{category} eq 'command'; + $enums->{$v->{name}} = $v if $v->{category} eq 'enum'; + $bitmasks->{$v->{name}} = $v if $v->{category} eq 'bitmask'; + $funcpointers->{$v->{name}} = $v if $v->{category} eq 'funcpointer'; + } + + # link enums to their type(s) + foreach my $s (values %$bitmasks) { + my $t; + + if ($s->{requires}) { + $t = $data->{$s->{requires}}; + while ($t && $t->{alias}) { + $t = $data->{$t->{alias}}; + } + die if !defined($t); + $t->{uses} = $s; + $t->{fullType} = $s->{baseType}; + } elsif ($s->{name} =~ m/(.*)Flags([0-9A-Z]*)/o && defined $data->{"$1FlagBits$2"}) { + print "> $s->{name} $1FlagBits$2\n"; + $t = $data->{"$1FlagBits$2"}; + while ($t && $t->{alias}) { + $t = $data->{$t->{alias}}; + } + die if !defined($t); + $t->{uses} = $s; + $t->{fullType} = $s->{baseType}; + } else { + $t->{fullType} = 'VkFlags'; + } + } + foreach my $s (values %$enums) { + $s->{fullType} = 'VkFlags' if !defined $s->{fullType}; + } + + if (0) { + # Have to actually map the types too + { + my $del = {}; + my $add = {}; + foreach my $c (values %{$types}) { + if ($c->{alias}) { + print "T"; + while ($c->{alias}) { + print " $c->{name}"; + $c = $vk->{data}->{$c->{alias}}; + } + print " -> $c->{name}\n"; + } + } + map {delete $types->{$_} } (keys %$del); + map {$data->{$_->{name}} = $_; $enums->{$_->{name}} = $_ } (values %$add); + } + + # check type sare included? + { + my $del = {}; + my $add = {}; + foreach my $e (values %{$enums}) { + if ($e->{alias}) { + print "D"; + while ($e->{alias}) { + print " $e->{name}"; + $del->{$e->{name}} = $e; + $e = $vk->{data}->{$e->{alias}}; + } + die if !defined($e); + print " -> $e->{name}\n"; + $add->{$e->{name}} = $e; + } + } + map {delete $enums->{$_} } (keys %$del); + map {$data->{$_->{name}} = $_; $enums->{$_->{name}} = $_ } (values %$add); + } + } + + my $api = { + data => $data, + handles => $handles, + types => $types, + commands => $commands, + funcpointers => $funcpointers, + enums => $enums, + bitmasks => $bitmasks, + }; + + # create sizes for every struct of interest + foreach my $s (values %$types) { + next if $s->{alias}; + + if ($s->{category} eq 'struct') { + structSize($vk, $api, $s); + } elsif ($s->{category} eq 'union') { + unionSize($vk, $api, $s); + } else { + die; + } + } + + return $api; +} + +my $typeInfo = { + 'void *' => { bitSize => 64, bitAlign => 64 }, + 'int' => { bitSize => 32, bitAlign => 32 }, + 'char' => { bitSize => 8, bitAlign => 8 }, + 'uint8_t' => { bitSize => 8, bitAlign => 8 }, + 'uint16_t' => { bitSize => 16, bitAlign => 16 }, + 'int32_t' => { bitSize => 32, bitAlign => 32 }, + 'uint32_t' => { bitSize => 32, bitAlign => 32 }, + 'int64_t' => { bitSize => 64, bitAlign => 64 }, + 'uint64_t' => { bitSize => 64, bitAlign => 64 }, + 'size_t' => { bitSize => 64, bitAlign => 64 }, + 'float' => { bitSize => 32, bitAlign => 32 }, + 'double' => { bitSize => 64, bitAlign => 64 }, + 'size_t' => { bitSize => 64, bitAlign => 64 }, + 'Window' => { bitSize => 64, bitAlign => 64 }, + 'Display' => { bitSize => 64, bitAlign => 64 }, + 'xcb_window_t' => { bitSize => 32, bitAlign => 32 }, + 'xcb_connection_t' => { bitSize => 64, bitAlign => 64 }, +# 'VkFlags' => { bitSize => 32, bitAlign => 32 }, +# 'VkFlags64' => { bitSize => 64, bitAlign => 64 }, +}; + +# fuck how can i parameterise this shit? +# ?create a 'deref' thing that all these things can work from? +# this should probably do it too +sub memberSize { + my $vk = shift; + my $api = shift; + my $m = shift; + my $t = $api->{data}->{$m->{baseType}}; + my $nstar = $m->{fullType} =~ tr/*/*/; + my ($nbits) = $m->{fullType} =~ m/:(\d+)$/o; + my $array = 1; + my $info = $typeInfo->{'void *'}; + + # arrays and bitfields + if ($m->{fullType} =~ m/\[(.*)\]\[(.*)\]$/) { + $array = $1 * $2; + } elsif ($m->{fullType} =~ m/\[(\d+)\]$/o) { + $array = $1; + } elsif ($m->{fullType} =~ m/\[(.+)\]$/o) { + $array = $vk->{data}->{'API Constants'}->{index}->{$1}->{value}; + } + + if (!defined($t)) { + if ($nbits) { + die Dumper($m) if $nstar > 0; + $info = { bitSize => $nbits, bitAlign => 1 }; + } else { + $info = $typeInfo->{$m->{baseType}} if ($nstar == 0); + } + } else { + while ($t->{alias}) { + $t = $api->{data}->{$t->{alias}}; + } + + die Dumper($m) if !defined $t; + + if ($t->{category} =~ m/enum|bitmask/on) { + if ($nbits) { + die Dumper($m) if $nstar > 0; + $info = { bitSize => $nbits, bitAlign => 1 }; + } else { + $t = $vk->{data}->{$t->{fullType}}; + $info = $typeInfo->{$t->{type}} if ($nstar == 0); + } + } elsif ($t->{category} eq 'struct') { + $info = structSize($vk, $api, $t) if ($nstar == 0); + } elsif ($t->{category} eq 'union') { + $info = unionSize($vk, $api, $t) if ($nstar == 0); + } elsif ($t->{category} eq 'handle') { + # already set + } elsif ($t->{category} eq 'basetype') { + $info = $typeInfo->{$t->{type}} if ($nstar == 0); + } elsif ($t->{category} eq 'funcpointer') { + # already set + } else { + die Dumper($m, $t); + } + } + + die Dumper($m, $t) if !defined($info); + + #print Dumper($m, $t, $info); + #print "size $m->{name} $m->{fullType} = $info->{bitSize}\n"; + + + return { bitSize => $info->{bitSize} * $array, bitAlign => $info->{bitAlign} }; +} + +sub align { + my $v = shift; + my $a = shift; + + return ($v + $a - 1) & ~($a - 1); +} + +sub structSize { + my $vk = shift; + my $api = shift; + my $s = shift; + my $bitSize = 0; + my $bitAlign = 8; + + if (!defined($s->{bitSize})) { + foreach my $m (@{$s->{items}}) { + use integer; + my $info = memberSize($vk, $api, $m); + + $bitSize = align($bitSize, $info->{bitAlign}); + + $m->{bitOffset} = $bitSize; + $m->{bitSize} = $info->{bitSize}; + + $bitSize = $bitSize + $info->{bitSize}; + $bitAlign = $info->{bitAlign} if $info->{bitAlign} > $bitAlign; + } + + $bitSize = align($bitSize, $bitAlign); + + $s->{bitSize} = $bitSize; + $s->{bitAlign} = $bitAlign; + } else { + $bitSize = $s->{bitSize}; + $bitAlign = $s->{bitAlign}; + } + + return { bitSize => $bitSize, bitAlign => $bitAlign }; +} + +sub unionSize { + my $vk = shift; + my $api = shift; + my $s = shift; + my $bitSize = 0; + my $bitAlign = 8; + + if (!defined($s->{bitSize})) { + foreach my $m (@{$s->{items}}) { + use integer; + my $info = memberSize($vk, $api, $m); + + $m->{bitOffset} = 0; + $m->{bitSize} = $info->{bitSize}; + + $bitSize = $info->{bitSize} if $info->{bitSize} > $bitSize; + $bitAlign = $info->{bitAlign} if $info->{bitAlign} > $bitAlign; + } + + $bitSize = align($bitSize, $bitAlign); + + $s->{bitSize} = $bitSize; + $s->{bitAlign} = $bitAlign; + } else { + $bitSize = $s->{bitSize}; + $bitAlign = $s->{bitAlign}; + } + + return { bitSize => $bitSize, bitAlign => $bitAlign }; +} + +sub loadRegistry { + my $vk = shift; + + my $xml = XML::Parser->new(Style => 'Tree'); + my $doc = $xml->parsefile('/usr/share/vulkan/registry/vk.xml') || die "unable to parse vulkan registry"; + + #print Dumper($doc); + + my $root = $doc->[1]; + my $roota = shift @{$root}; + + my $data = $vk->{data}; + my $alias = $vk->{alias}; + my $extensions = $vk->{extensions}; + my $features = $vk->{features}; + + # This destructively consumes the whole tree so must be one pass + while ($#{$root} >= 0) { + my $xt = shift @{$root}; + my $xn = shift @{$root}; + + next if $xt eq '0'; + + my $xa = shift @{$xn}; + + if ($xt eq 'types') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'type'; + + my $ya = $yn->[0]; + + if ($ya->{category} =~ m/struct|union/) { + if (!defined($ya->{alias})) { + my $s = $ya; + + $s->{items} = []; + + shift @{$yn}; + while ($#{$yn} >= 0) { + my $mt = shift @{$yn}; + my $mm = shift @{$yn}; + + push @{$s->{items}}, loadMember($mm) if $mt eq 'member'; + } + + $data->{$s->{name}} = $s; + } else { + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } elsif ($ya->{category} =~ m/^(handle|basetype|funcpointer|bitmask)$/n) { + if (!defined($ya->{alias})) { + my $info = loadMember($yn); + my $s = $ya; + + $s->{name} = $info->{name}; + $s->{type} = $info->{baseType} if defined $info->{baseType}; + + analyseFunctionPointer($s) if ($s->{category} eq 'funcpointer'); + + $data->{$s->{name}} = $s; + } else { + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } elsif ($ya->{category} eq 'enum') { + $data->{$ya->{name}} = $ya; + } elsif ($ya->{requires} || $ya->{name} eq 'int') { + # ?? wtf to do with this + $ya->{category} = 'platform'; + $data->{$ya->{name}} = $ya; + } + } + } elsif ($xt eq 'enums') { + if ($xa->{type} =~ m/enum|bitmask/o) { + # these are forward referenced from block so re-use, or just overwrite? + my $e = $data->{$xa->{name}}; + + $e = { category => "enum", name => $xa->{name} } if (!defined($e)); + + $e->{items} = []; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'enum'; + + my $ya = shift @{$yn}; + + #next if $ya->{alias}; + + push @{$e->{items}}, $ya; + } + + $data->{$xa->{name}} = $e; + } elsif ($xa->{name} eq 'API Constants') { + my $d = { category => "define", name => $xa->{name}, items =>[], index=>{} }; + + $data->{$xa->{name}} = $d; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'enum'; + + my $ya = shift @{$yn}; + + #next if $ya->{alias}; + + push @{$d->{items}}, $ya; + $d->{index}->{$ya->{name}} = $ya; + } + } + } elsif ($xt eq 'commands') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'command'; + + my $ya = shift @{$yn}; + + if (!defined($ya->{alias})) { + my $cmd = $ya; + + $cmd->{category} = 'command'; + $cmd->{items} = []; + $cmd->{proto} = {}; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + if ($zt eq 'proto') { + $cmd->{proto} = loadMember($zn); + } elsif ($zt eq 'param') { + push @{$cmd->{items}}, loadMember($zn); + } + } + + my $name = $cmd->{proto}->{name}; + + # check we parsed it properly + if ($cmd->{proto}->{fullType} eq "") { + print Dumper([$ya, $yn]); + die(); + } + $cmd->{name} = $name; + + $data->{$name} = $cmd; + } else { + # want forward ref or not? + $alias->{$ya->{name}} = $ya->{alias}; + $data->{$ya->{name}} = $ya; + } + } + } elsif ($xt eq 'feature') { + my $feature = $xa; + + $feature->{require} = []; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'require'; + + push @{$feature->{require}}, loadRequire($data, $alias, $yn); + } + + push @{$features}, $feature; + } elsif ($xt eq 'extensions') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'extension'; + + my $ext = shift @{$yn}; + + $ext->{require} = []; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + next if $zt ne 'require'; + + push @{$ext->{require}}, loadRequire($data, $alias, $zn); + } + + push @{$extensions}, $ext; + } + } else { + print "vulkan.pm: Ignore node: $xt\n"; + } + } +} + +# find an object including via alias +sub findData { + my $data = shift; + my $alias = shift; + my $name = shift; + + do { + my $s = $data->{$name}; + return $s if defined $s; + #print "alias $name => $alias->{$name}\n"; + $name = $alias->{$name}; + } while ($name); + + die "No match for type '$name'"; +} + +sub makeParameter { + my $name = shift; + my $fullType = shift; + my $type = $fullType; + + $type =~ s/const|\*|\s//gon; + + $fullType =~ s/\s{2,}/ /go; # collapse all whitespace to ' ' + + # canonicalise spaces in c type + #$fullType =~ s/(? $name, + Name => ucfirst($name), + fullType => $fullType, + baseType => $type, + type => $type, + }; +} + +# Convert function typedef into function info +sub analyseFunctionPointer { + my $s = shift; + + if ($s->{fullType} =~ m/^(.+)\s+\(VKAPI_PTR \*\)\((.*)\)$/o) { + my $rt = $1; + my @args = split /,/,$2; + + $s->{proto} = makeParameter('result$', $rt); + $s->{items} = []; + + foreach my $a (@args) { + my ($fullType, $name) = $a =~ m/^(.*)\s+(\S+)$/o; + + push @{$s->{items}}, makeParameter($name, $fullType); + } + } else { + die Dumper($s); + } + $s->{Name} = $s->{name}; + delete $s->{type}; + delete $s->{baseType}; + delete $s->{fullType}; +} + +sub loadMember { + my $nn = shift; + #my $x = (join '',split('\n',Dumper($nn))); $x =~ s/ +/ /g; print "load: $x\n"; + my $m = shift @{$nn}; + my $baseType = ""; + my $fullType = ""; + my $name = ""; + + while ($#{$nn} >= 0) { + my $pt = shift @{$nn}; + my $pn = shift @{$nn}; + + if ($pt eq '0') { + $fullType .= $pn; + } elsif ($pt eq 'type') { + die if $pn->[1] != 0; + $baseType = $pn->[2]; + $fullType .= $baseType; + } elsif ($pt eq 'name') { + die if $pn->[1] != 0; + $name = $pn->[2]; + } elsif ($pt eq 'enum') { + die if $pn->[1] != 0; + $fullType .= $pn->[2]; + } + } + + $fullType =~ s/^typedef (.*);$/\1/os; # strip out 'typedef' part + $fullType =~ s/\s{2,}/ /go; # collapse all whitespace to ' ' + + # canonicalise spaces in c type + #$fullType =~ s/(?{name} = $name; + $m->{baseType} = $baseType; + $m->{fullType} = $fullType; + + $m; +} + +sub loadRequire { + my $data = shift; + my $alias = shift; + my $nn = shift; + my $r = shift @{$nn}; + + $r->{enums} = []; + $r->{types} = []; + $r->{commands} = []; + + while ($#{$nn} >= 0) { + my $mt = shift @{$nn}; + my $mn = shift @{$nn}; + + if ($mt eq 'type') { + my $ma = shift @{$mn}; + push @{$r->{types}}, $ma->{name}; + } elsif ($mt eq 'command') { + my $ma = shift @{$mn}; + push @{$r->{commands}}, $ma->{name}; + } elsif ($mt eq 'enum') { + my $ma = shift @{$mn}; + push @{$r->{enums}}, $ma; + } + } + + $r; +} + +sub findElements { + my $n = shift; + my $name = shift; + my @list; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + if ($tag eq $name) { + push @list, [$tag, $con]; + } + } + @list; +} + +sub scanElements { + my $n = shift; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + print "$#{$n} "; + print "tag $tag\n"; + } +} + +1; diff --git a/src/notzed.xcb/classes/module-info.java b/src/notzed.xcb/classes/module-info.java new file mode 100644 index 0000000..4b1e747 --- /dev/null +++ b/src/notzed.xcb/classes/module-info.java @@ -0,0 +1,6 @@ + +module notzed.xcb { + requires transitive notzed.nativez; + + exports xcb; +} diff --git a/src/notzed.xcb/classes/xcb/Connection.java b/src/notzed.xcb/classes/xcb/Connection.java new file mode 100644 index 0000000..27ff8fc --- /dev/null +++ b/src/notzed.xcb/classes/xcb/Connection.java @@ -0,0 +1,28 @@ + +package xcb; + +import jdk.incubator.foreign.*; +import au.notzed.nativez.Pointer; + +/* small hack to get it to compile, unimplemented functions so far */ + +public class Connection implements Pointer { + NativeSymbol symbol; + + public Connection(MemoryAddress addr, ResourceScope scope) { + symbol = NativeSymbol.ofAddress("Connection", addr, scope); + } + + public static Connection create(MemoryAddress addr, ResourceScope scope) { + return new Connection(addr, scope); + } + + public MemoryAddress address() { + return symbol.address(); + } + + public ResourceScope scope() { + return symbol.scope(); + } + +} -- 2.39.5