├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── docs └── graphics.png ├── examples ├── Makefile ├── compute │ ├── Cargo.toml │ └── src │ │ ├── fill.comp │ │ ├── fill.comp.spv │ │ └── main.rs └── graphics │ ├── Cargo.toml │ └── src │ ├── command_buffer.rs │ ├── context.rs │ ├── main.rs │ ├── swapchain.rs │ ├── test.frag │ ├── test.frag.spv │ ├── test.vert │ ├── test.vert.spv │ └── window_surface.rs ├── generator ├── Cargo.toml └── src │ ├── builder_prefix.rs │ ├── c_parse.rs │ ├── lib_postfix.rs │ ├── lib_prefix.rs │ ├── main.rs │ └── vk_prefix.rs ├── rustfmt.toml ├── spark-egui ├── Cargo.toml ├── Makefile └── src │ ├── egui.frag │ ├── egui.frag.spv │ ├── egui.vert │ ├── egui.vert.spv │ └── lib.rs ├── spark-imgui ├── Cargo.toml ├── Makefile └── src │ ├── imgui.frag │ ├── imgui.frag.spv │ ├── imgui.vert │ ├── imgui.vert.spv │ └── lib.rs └── spark ├── Cargo.toml └── src ├── builder.rs ├── lib.rs └── vk.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /target 3 | Cargo.lock 4 | imgui.ini 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "generator", 4 | "spark", 5 | "spark-egui", 6 | "spark-imgui", 7 | "examples/compute", 8 | "examples/graphics" 9 | ] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spark 2 | 3 | This library aims to expose [Vulkan](https://www.khronos.org/registry/vulkan/) in [Rust](https://www.rust-lang.org/) with convenient syntax. 4 | 5 | Supports Vulkan 1.4.316 and all extensions (apart from `GGP`/`QNX` extensions that use unknown data types). It should compile on Windows, Linux and Android. 6 | 7 | ## Design 8 | 9 | The library is similar in scope to [`ash`](https://github.com/MaikKlein/ash). Ash seems to be the most popular low-library for Vulkan in Rust, so if you are looking for something with wide support, then I recommend using [`ash`](https://github.com/MaikKlein/ash) instead. 10 | 11 | Since `ash` widely used, I'll just list the ways this library currently differs from `ash`. These are just alternatives I personally found interesting to explore: 12 | 13 | ### Helpers To Manage Extensions 14 | 15 | The structs `InstanceExtensions` and `DeviceExtensions` can be used to simplify code that manages extensions. 16 | These expose a method per extension that checks for support, which handles checking for dependencies and the core Vulkan version: 17 | 18 | ```rust 19 | // parse physical device extension properties into DeviceExtensions 20 | let available_extensions = { 21 | let extension_properties = 22 | unsafe { instance.enumerate_device_extension_properties_to_vec(physical_device, None) }.unwrap(); 23 | DeviceExtensions::from_properties(core_version, &extension_properties) 24 | }; 25 | 26 | // check availability 27 | if available_extensions.supports_khr_draw_indirect_count() { 28 | println!("VK_KHR_draw_indirect_count is present OR core_version >= 1.2"); 29 | } 30 | ``` 31 | 32 | These structs also expose a method per extension to enable that extension and all of its dependencies. Extensions that have been promoted to the given core Vulkan version are skipped. 33 | 34 | ```rust 35 | let mut extensions = DeviceExtensions::new(core_version); 36 | 37 | // enables VK_KHR_draw_indirect_count (unless core_version >= 1.2) 38 | extensions.enable_khr_draw_indirect_count(); 39 | 40 | // enables all the following: 41 | // VK_KHR_acceleration_structure 42 | // VK_EXT_descriptor_indexing (unless core_version >= 1.2) 43 | // VK_KHR_maintenance3 (unless core_version >= 1.1) 44 | // VK_EXT_buffer_device_address (unless core_version >= 1.2) 45 | // VK_KHR_deferred_host_operations 46 | extensions.enable_khr_acceleration_structure(); 47 | ``` 48 | 49 | Once all extensions have been enabled, the set can be passed to `Instance` or `Device` creation as a list of names: 50 | 51 | ```rust 52 | // for passing to device creation 53 | let extension_names = extensions.to_name_vec(); 54 | ``` 55 | 56 | ### Extensions Are Part Of `Instance` And `Device` 57 | 58 | To simplify the use of extensions and core Vulkan versions, the library manages all Vulkan function pointers directly on the `Instance` or `Device`. 59 | 60 | When the `Instance` or `Device` is created, the core version and list of extensions is checked, and all Vulkan commands are loaded that are enabled for that combination. This handles complex cases such as commands that are loaded only when a combination of extensions are present. The `extensions` struct on `Instance` or `Device` can be used to query which extensions are supported. 61 | 62 | ```rust 63 | // emit a marker if EXT_debug_utils was loaded 64 | if instance.extensions.supports_ext_debug_utils() { 65 | let label = vk::DebugUtilsLabelEXT { 66 | p_label_name: name.as_ptr(), 67 | ..Default::default() 68 | }; 69 | instance.cmd_begin_debug_utils_label_ext(cmd, &label); 70 | } 71 | ``` 72 | 73 | ### Vulkan Command Aliases Are Eqivalent 74 | 75 | Only one function pointer is stored for Vulkan commands that are aliases of each other. Once loaded, any alias can be used to emit the command, since they all call through to the same function pointer on `Device` or `Instance`. 76 | 77 | For example, when the `Device` is created, `vkCmdDrawIndirectCount` is loaded for one of the following cases: 78 | * If the core version is 1.2 or greater (loaded as `vkCmdDrawIndirectCount`) 79 | * If the `VK_KHR_draw_indirect_count` extension is enabled (loaded as the alias `vkCmdDrawIndirectCountKHR`) 80 | * If the `VK_AMD_draw_indirect_count` extension is enabled (loaded as the alias `vkCmdDrawIndirectCountAMD`) 81 | 82 | The resulting function pointer is stored as `Device.fp_cmd_draw_indirect_count` regardless of how it was loaded, so client code can use any wrapper function to emit it: 83 | 84 | ```rust 85 | // all the following are equivalent, they all call through to device.fp_cmd_draw_indirect_count 86 | device.cmd_draw_indirect_count(/*...*/) 87 | device.cmd_draw_indirect_count_khr(/*...*/) 88 | device.cmd_draw_indirect_count_amd(/*...*/) 89 | ``` 90 | 91 | ### Non-Zero Handles 92 | 93 | This is opinionated, but the library enforces that Vulkan handles must be non-null, by making use of the `NonZeroUsize` and `NonZeroU64` types. For optional function parameters or struct members, they can be wrapped in `Option` to represent `VK_NULL_HANDLE` directly as `None`. 94 | 95 | The parameter type then encodes whether that object is required: 96 | 97 | ```rust 98 | impl Device { 99 | /* ... */ 100 | pub unsafe fn acquire_next_image_khr( 101 | &self, 102 | swapchain: vk::SwapchainKHR, // not optional 103 | timeout: u64, 104 | semaphore: Option, // optional 105 | fence: Option, // optional 106 | ) -> Result<(vk::Result, u32)> { 107 | /* ... */ 108 | } 109 | /* ... */ 110 | } 111 | ``` 112 | 113 | But struct declarations always use `Option` (to be able to have a `Default`), so get a bit more noisy: 114 | 115 | ```rust 116 | pub struct DescriptorImageInfo { 117 | pub sampler: Option, 118 | pub image_view: Option, 119 | pub image_layout: ImageLayout, 120 | } 121 | ``` 122 | 123 | On balance I think this is worth it and more Rust-y for handles to always be valid. 124 | 125 | ### Fully Generated 126 | 127 | I had a go at generating not only the struct and function pointer types as much as possible (hopefully there will be a standard `vk-sys` for this one day), but also **all** the wrappers that exist to make Vulkan functions more Rust-y on `Instance` and `Device` (and all the struct builders too). 128 | 129 | These are generated using [vk_parse](https://github.com/krolli/vk-parse) to parse the Vulkan specifications XML, then taking care to use info in the spec as much as possible, such as: 130 | * All the sensible translations to C from `bool`, rust native types, `CStr`, `Option`, references and slices 131 | * Pair up arrays with lengths (including cases where multiple arrays share a single length) 132 | * Which result codes are considered to be successful for that call 133 | 134 | This seems to handle tricky cases reasonable well, like functions that have multiple "success" codes: 135 | 136 | ```rust 137 | impl Device { 138 | /* ... */ 139 | pub unsafe fn wait_semaphores_khr( 140 | &self, 141 | p_wait_info: &vk::SemaphoreWaitInfoKHR, 142 | timeout: u64, 143 | ) -> Result { 144 | /* 145 | returns Ok(SUCCESS), Ok(TIMEOUT) or Err(other) 146 | */ 147 | } 148 | /* ... */ 149 | } 150 | ``` 151 | 152 | Or functions (in this case a builder) where two arrays must be the same length (so are built together): 153 | 154 | ```rust 155 | impl<'a> SubmitInfoBuilder<'a> { 156 | /* ... */ 157 | pub fn p_wait_semaphores( 158 | mut self, 159 | p_wait_semaphores: &'a [vk::Semaphore], 160 | p_wait_dst_stage_mask: &'a [vk::PipelineStageFlags], 161 | ) -> Self { 162 | self.inner.wait_semaphore_count = p_wait_semaphores.len() as u32; 163 | assert_eq!(self.inner.wait_semaphore_count, p_wait_dst_stage_mask.len() as u32); 164 | self.inner.p_wait_semaphores = p_wait_semaphores.as_ptr(); 165 | self.inner.p_wait_dst_stage_mask = p_wait_dst_stage_mask.as_ptr(); 166 | self 167 | } 168 | /* ... */ 169 | } 170 | ``` 171 | 172 | ### Zero-Allocation Where Possible 173 | 174 | This is maybe overkill, but functions that fill an array of known size have `_array` and `_single` variants that do not allocate from the heap, in addition to a `_to_vec` variant that requires a heap allocation. 175 | 176 | ```rust 177 | impl Device { 178 | /* ... */ 179 | pub unsafe fn create_compute_pipelines_to_vec( 180 | &self, 181 | pipeline_cache: Option, 182 | p_create_infos: &[vk::ComputePipelineCreateInfo], 183 | p_allocator: Option<&vk::AllocationCallbacks>, 184 | ) -> Result> { 185 | /* ... */ 186 | } 187 | pub unsafe fn create_compute_pipelines_array( 188 | &self, 189 | pipeline_cache: Option, 190 | p_create_infos: &[vk::ComputePipelineCreateInfo], 191 | p_allocator: Option<&vk::AllocationCallbacks>, 192 | ) -> Result<[vk::Pipeline; N]> { 193 | /* ... */ 194 | } 195 | pub unsafe fn create_compute_pipelines_single( 196 | &self, 197 | pipeline_cache: Option, 198 | p_create_infos: &vk::ComputePipelineCreateInfo, 199 | p_allocator: Option<&vk::AllocationCallbacks>, 200 | ) -> Result { 201 | /* ... */ 202 | } 203 | /* ... */ 204 | } 205 | ``` 206 | 207 | The `_array` version now makes use of const generics to be fully generic over array length. 208 | 209 | ## Examples 210 | 211 | Examples can be found in the `examples` folder. 212 | 213 | ### [`compute`](https://github.com/sjb3d/spark/blob/master/examples/compute) 214 | 215 | A minimal console application that runs a compute shader to fill some memory. Shows basic usage of a Vulkan device. 216 | 217 | ### [`graphics`](https://github.com/sjb3d/spark/blob/master/examples/graphics) 218 | 219 | A minimal windowed application that draws a spinning triangle and some UI. 220 | * Uses [`winit`](https://github.com/rust-windowing/winit) for windowing 221 | * Demonstrates [`spark-egui`](https://github.com/sjb3d/spark/tree/master/spark-egui) as a renderer for [`egui`](https://github.com/emilk/egui). 222 | 223 | ![graphics](https://raw.githubusercontent.com/sjb3d/spark/master/docs/graphics.png) 224 | -------------------------------------------------------------------------------- /docs/graphics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/docs/graphics.png -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | GLSLC=glslangValidator 2 | GLSLCFLAGS=-V 3 | 4 | SHD=compute-lib/src/fill.comp.spv\ 5 | graphics-lib/src/test.vert.spv\ 6 | graphics-lib/src/test.frag.spv 7 | 8 | all: shaders 9 | .PHONY: all clean shaders 10 | 11 | clean: 12 | $(RM) $(SHD) 13 | 14 | shaders: $(SHD) 15 | 16 | %.spv: % Makefile 17 | $(GLSLC) $(GLSLCFLAGS) -o $@ $< 18 | 19 | -------------------------------------------------------------------------------- /examples/compute/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compute" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | spark = { path = "../../spark" } 10 | -------------------------------------------------------------------------------- /examples/compute/src/fill.comp: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(local_size_x = 64) in; 4 | 5 | layout(std430, binding = 0) writeonly buffer Output { 6 | float g_data[]; 7 | }; 8 | 9 | void main(){ 10 | g_data[gl_GlobalInvocationID.x] = float(gl_GlobalInvocationID.x); 11 | } 12 | -------------------------------------------------------------------------------- /examples/compute/src/fill.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/examples/compute/src/fill.comp.spv -------------------------------------------------------------------------------- /examples/compute/src/main.rs: -------------------------------------------------------------------------------- 1 | use spark::{vk, Builder, Loader}; 2 | use std::{ffi::CStr, mem, slice}; 3 | 4 | fn get_memory_type_index( 5 | memory_properties: &vk::PhysicalDeviceMemoryProperties, 6 | memory_type_bits: u32, 7 | property_flags: vk::MemoryPropertyFlags, 8 | ) -> Option { 9 | for i in 0..memory_properties.memory_type_count { 10 | let mt = &memory_properties.memory_types[i as usize]; 11 | if (memory_type_bits & (1 << i)) != 0 && mt.property_flags.contains(property_flags) { 12 | return Some(i); 13 | } 14 | } 15 | None 16 | } 17 | 18 | #[allow(clippy::float_cmp)] 19 | pub fn main() { 20 | // this example only requires Vulkan 1.0.0 21 | let version = Default::default(); 22 | 23 | // load the Vulkan lib 24 | let instance = { 25 | let loader = Loader::new().unwrap(); 26 | let app_info = vk::ApplicationInfo::builder() 27 | .p_application_name(Some(CStr::from_bytes_with_nul(b"compute\0").unwrap())) 28 | .api_version(version); 29 | let instance_create_info = vk::InstanceCreateInfo::builder().p_application_info(Some(&app_info)); 30 | unsafe { loader.create_instance(&instance_create_info, None) }.unwrap() 31 | }; 32 | 33 | // find the first physical device 34 | let physical_device = { 35 | let physical_devices = unsafe { instance.enumerate_physical_devices_to_vec() }.unwrap(); 36 | for physical_device in &physical_devices { 37 | let props = unsafe { instance.get_physical_device_properties(*physical_device) }; 38 | println!("physical device ({}): {:?}", props.device_type, unsafe { 39 | CStr::from_ptr(props.device_name.as_ptr()) 40 | }); 41 | } 42 | physical_devices.first().copied().expect("no physical device found") 43 | }; 44 | 45 | // find the first queue family that supports compute 46 | let queue_family_properties = 47 | unsafe { instance.get_physical_device_queue_family_properties_to_vec(physical_device) }; 48 | let queue_family_index = queue_family_properties 49 | .iter() 50 | .enumerate() 51 | .filter_map(|(index, &info)| { 52 | if info.queue_flags.contains(vk::QueueFlags::COMPUTE) { 53 | Some(index as u32) 54 | } else { 55 | None 56 | } 57 | }) 58 | .next() 59 | .expect("no queue family supports compute"); 60 | 61 | // create a device for this queue family 62 | let device = { 63 | let queue_priority = 1.0; 64 | let device_queue_create_info = vk::DeviceQueueCreateInfo::builder() 65 | .queue_family_index(queue_family_index) 66 | .p_queue_priorities(slice::from_ref(&queue_priority)); 67 | let device_create_info = 68 | vk::DeviceCreateInfo::builder().p_queue_create_infos(slice::from_ref(&device_queue_create_info)); 69 | unsafe { instance.create_device(physical_device, &device_create_info, None, version) }.unwrap() 70 | }; 71 | 72 | // load the compute shader 73 | let shader_module = { 74 | let shader_bytes = include_bytes!("fill.comp.spv"); 75 | let shader_module_create_info = vk::ShaderModuleCreateInfo { 76 | code_size: shader_bytes.len(), 77 | p_code: shader_bytes.as_ptr() as _, 78 | ..Default::default() 79 | }; 80 | unsafe { device.create_shader_module(&shader_module_create_info, None) }.unwrap() 81 | }; 82 | 83 | // create a buffer for outputs 84 | let dispatch_size = 256; 85 | let buffer_size = dispatch_size * mem::size_of::(); 86 | let buffer = { 87 | let buffer_create_info = vk::BufferCreateInfo { 88 | size: buffer_size as vk::DeviceSize, 89 | usage: vk::BufferUsageFlags::STORAGE_BUFFER, 90 | ..Default::default() 91 | }; 92 | unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap() 93 | }; 94 | let mem_req = unsafe { device.get_buffer_memory_requirements(buffer) }; 95 | 96 | // allocate memory for the buffer 97 | let mem = { 98 | let memory_properties = unsafe { instance.get_physical_device_memory_properties(physical_device) }; 99 | let memory_type_index = get_memory_type_index( 100 | &memory_properties, 101 | mem_req.memory_type_bits, 102 | vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, 103 | ) 104 | .expect("no suitable memory type found"); 105 | let memory_allocate_info = vk::MemoryAllocateInfo { 106 | allocation_size: mem_req.size, 107 | memory_type_index, 108 | ..Default::default() 109 | }; 110 | unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap() 111 | }; 112 | unsafe { device.bind_buffer_memory(buffer, mem, 0) }.unwrap(); 113 | 114 | // make the pipeline layout 115 | let descriptor_set_layout = { 116 | let descriptor_set_layout_bindings = [vk::DescriptorSetLayoutBinding { 117 | binding: 0, 118 | descriptor_type: vk::DescriptorType::STORAGE_BUFFER, 119 | descriptor_count: 1, 120 | stage_flags: vk::ShaderStageFlags::COMPUTE, 121 | ..Default::default() 122 | }]; 123 | let descriptor_set_layout_create_info = 124 | vk::DescriptorSetLayoutCreateInfo::builder().p_bindings(&descriptor_set_layout_bindings); 125 | unsafe { device.create_descriptor_set_layout(&descriptor_set_layout_create_info, None) }.unwrap() 126 | }; 127 | let pipeline_layout = { 128 | let pipeline_layout_create_info = 129 | vk::PipelineLayoutCreateInfo::builder().p_set_layouts(slice::from_ref(&descriptor_set_layout)); 130 | unsafe { device.create_pipeline_layout(&pipeline_layout_create_info, None) }.unwrap() 131 | }; 132 | 133 | // create the pipeline 134 | let pipeline_create_info = vk::ComputePipelineCreateInfo { 135 | stage: vk::PipelineShaderStageCreateInfo { 136 | stage: vk::ShaderStageFlags::COMPUTE, 137 | module: Some(shader_module), 138 | p_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"main\0") }.as_ptr(), 139 | ..Default::default() 140 | }, 141 | layout: Some(pipeline_layout), 142 | ..Default::default() 143 | }; 144 | let pipeline = unsafe { device.create_compute_pipelines_single(None, &pipeline_create_info, None) }.unwrap(); 145 | 146 | // create a pool for the descriptor we need 147 | let descriptor_pool = { 148 | let descriptor_pool_sizes = [vk::DescriptorPoolSize { 149 | ty: vk::DescriptorType::STORAGE_BUFFER, 150 | descriptor_count: 1, 151 | }]; 152 | let descriptor_pool_create_info = vk::DescriptorPoolCreateInfo::builder() 153 | .max_sets(1) 154 | .p_pool_sizes(&descriptor_pool_sizes); 155 | unsafe { device.create_descriptor_pool(&descriptor_pool_create_info, None) }.unwrap() 156 | }; 157 | 158 | // allocate and write the descriptor set 159 | let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder() 160 | .descriptor_pool(descriptor_pool) 161 | .p_set_layouts(slice::from_ref(&descriptor_set_layout)); 162 | let descriptor_set = unsafe { device.allocate_descriptor_sets_single(&descriptor_set_allocate_info) }.unwrap(); 163 | 164 | let descriptor_buffer_info = [vk::DescriptorBufferInfo { 165 | buffer: Some(buffer), 166 | offset: 0, 167 | range: vk::WHOLE_SIZE, 168 | }]; 169 | let write_descriptor_set = vk::WriteDescriptorSet::builder() 170 | .dst_set(descriptor_set) 171 | .dst_binding(0) 172 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 173 | .p_buffer_info(&descriptor_buffer_info); 174 | unsafe { device.update_descriptor_sets(slice::from_ref(&write_descriptor_set), &[]) }; 175 | 176 | // run a command buffer to run the shader 177 | let command_pool_create_info = vk::CommandPoolCreateInfo { 178 | queue_family_index, 179 | ..Default::default() 180 | }; 181 | let command_pool = unsafe { device.create_command_pool(&command_pool_create_info, None) }.unwrap(); 182 | let command_buffer_allocate_info = vk::CommandBufferAllocateInfo { 183 | command_pool: Some(command_pool), 184 | level: vk::CommandBufferLevel::PRIMARY, 185 | command_buffer_count: 1, 186 | ..Default::default() 187 | }; 188 | let command_buffer = unsafe { device.allocate_command_buffers_single(&command_buffer_allocate_info) }.unwrap(); 189 | 190 | // make a command buffer that runs the ceompute shader 191 | let command_buffer_begin_info = vk::CommandBufferBeginInfo { 192 | flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, 193 | ..Default::default() 194 | }; 195 | unsafe { device.begin_command_buffer(command_buffer, &command_buffer_begin_info) }.unwrap(); 196 | unsafe { device.cmd_bind_pipeline(command_buffer, vk::PipelineBindPoint::COMPUTE, pipeline) }; 197 | unsafe { 198 | device.cmd_bind_descriptor_sets( 199 | command_buffer, 200 | vk::PipelineBindPoint::COMPUTE, 201 | pipeline_layout, 202 | 0, 203 | slice::from_ref(&descriptor_set), 204 | &[], 205 | ) 206 | }; 207 | unsafe { device.cmd_dispatch(command_buffer, (dispatch_size as u32) / 16, 1, 1) }; 208 | unsafe { device.end_command_buffer(command_buffer) }.unwrap(); 209 | 210 | // run it and wait until it is completed 211 | let queue = unsafe { device.get_device_queue(queue_family_index, 0) }; 212 | let submit_info = vk::SubmitInfo::builder().p_command_buffers(slice::from_ref(&command_buffer)); 213 | unsafe { device.queue_submit(queue, slice::from_ref(&submit_info), None) }.unwrap(); 214 | unsafe { device.device_wait_idle() }.unwrap(); 215 | 216 | // check results 217 | let mapping = unsafe { device.map_memory(mem, 0, vk::WHOLE_SIZE, Default::default()) }.unwrap(); 218 | let check = unsafe { slice::from_raw_parts(mapping as *const f32, dispatch_size) }; 219 | for (i, v) in check.iter().copied().enumerate() { 220 | assert_eq!(i as f32, v); 221 | } 222 | println!("compute shader run successfully!"); 223 | } 224 | -------------------------------------------------------------------------------- /examples/graphics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphics" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | spark = { path = "../../spark" } 10 | spark-egui = { path = "../../spark-egui" } 11 | winit = "0.28" 12 | egui = "0.23" 13 | egui-winit = "0.23" 14 | raw-window-handle = "0.5" 15 | -------------------------------------------------------------------------------- /examples/graphics/src/command_buffer.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use spark::{vk, Builder}; 3 | use std::slice; 4 | use std::sync::Arc; 5 | 6 | struct CommandBufferSet { 7 | pool: vk::CommandPool, 8 | command_buffer: vk::CommandBuffer, 9 | fence: vk::Fence, 10 | image_available_semaphore: vk::Semaphore, 11 | rendering_finished_semaphore: vk::Semaphore, 12 | } 13 | 14 | impl CommandBufferSet { 15 | fn new(context: &Context) -> Self { 16 | let device = &context.device; 17 | 18 | let pool = { 19 | let command_pool_create_info = vk::CommandPoolCreateInfo { 20 | flags: vk::CommandPoolCreateFlags::empty(), 21 | queue_family_index: context.queue_family_index, 22 | ..Default::default() 23 | }; 24 | unsafe { device.create_command_pool(&command_pool_create_info, None) }.unwrap() 25 | }; 26 | 27 | let command_buffer = { 28 | let command_buffer_allocate_info = vk::CommandBufferAllocateInfo { 29 | command_pool: Some(pool), 30 | level: vk::CommandBufferLevel::PRIMARY, 31 | command_buffer_count: 1, 32 | ..Default::default() 33 | }; 34 | 35 | unsafe { device.allocate_command_buffers_single(&command_buffer_allocate_info) }.unwrap() 36 | }; 37 | 38 | let fence = { 39 | let fence_create_info = vk::FenceCreateInfo { 40 | flags: vk::FenceCreateFlags::SIGNALED, 41 | ..Default::default() 42 | }; 43 | unsafe { device.create_fence(&fence_create_info, None) }.unwrap() 44 | }; 45 | 46 | let image_available_semaphore = unsafe { device.create_semaphore(&Default::default(), None) }.unwrap(); 47 | let rendering_finished_semaphore = unsafe { device.create_semaphore(&Default::default(), None) }.unwrap(); 48 | 49 | Self { 50 | pool, 51 | command_buffer, 52 | fence, 53 | image_available_semaphore, 54 | rendering_finished_semaphore, 55 | } 56 | } 57 | } 58 | 59 | pub struct CommandBufferPool { 60 | context: Arc, 61 | sets: [CommandBufferSet; Self::COUNT], 62 | index: usize, 63 | } 64 | 65 | impl CommandBufferPool { 66 | pub const COUNT: usize = 2; 67 | 68 | pub fn new(context: &Arc) -> Self { 69 | Self { 70 | context: Arc::clone(context), 71 | sets: [CommandBufferSet::new(context), CommandBufferSet::new(context)], 72 | index: 0, 73 | } 74 | } 75 | 76 | pub fn acquire(&mut self) -> (vk::CommandBuffer, vk::Semaphore) { 77 | self.index = (self.index + 1) % Self::COUNT; 78 | 79 | let set = &self.sets[self.index]; 80 | 81 | let timeout_ns = 1000 * 1000 * 1000; 82 | loop { 83 | let res = unsafe { 84 | self.context 85 | .device 86 | .wait_for_fences(slice::from_ref(&set.fence), true, timeout_ns) 87 | }; 88 | match res { 89 | Ok(_) => break, 90 | Err(vk::Result::TIMEOUT) => {} 91 | Err(err_code) => panic!("failed to wait for fence {}", err_code), 92 | } 93 | } 94 | 95 | unsafe { self.context.device.reset_fences(slice::from_ref(&set.fence)) }.unwrap(); 96 | 97 | unsafe { 98 | self.context 99 | .device 100 | .reset_command_pool(set.pool, vk::CommandPoolResetFlags::empty()) 101 | } 102 | .unwrap(); 103 | 104 | let command_buffer_begin_info = vk::CommandBufferBeginInfo { 105 | flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, 106 | ..Default::default() 107 | }; 108 | unsafe { 109 | self.context 110 | .device 111 | .begin_command_buffer(set.command_buffer, &command_buffer_begin_info) 112 | } 113 | .unwrap(); 114 | 115 | (set.command_buffer, set.image_available_semaphore) 116 | } 117 | 118 | pub fn submit(&self) -> vk::Semaphore { 119 | let set = &self.sets[self.index]; 120 | 121 | unsafe { self.context.device.end_command_buffer(set.command_buffer) }.unwrap(); 122 | 123 | let submit_info = vk::SubmitInfo::builder() 124 | .p_wait_semaphores( 125 | slice::from_ref(&set.image_available_semaphore), 126 | slice::from_ref(&vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT), 127 | ) 128 | .p_command_buffers(slice::from_ref(&set.command_buffer)) 129 | .p_signal_semaphores(slice::from_ref(&set.rendering_finished_semaphore)); 130 | 131 | unsafe { 132 | self.context 133 | .device 134 | .queue_submit(self.context.queue, slice::from_ref(&submit_info), Some(set.fence)) 135 | } 136 | .unwrap(); 137 | 138 | set.rendering_finished_semaphore 139 | } 140 | } 141 | 142 | impl Drop for CommandBufferPool { 143 | fn drop(&mut self) { 144 | let device = &self.context.device; 145 | for set in self.sets.iter() { 146 | unsafe { 147 | device.destroy_semaphore(Some(set.rendering_finished_semaphore), None); 148 | device.destroy_semaphore(Some(set.image_available_semaphore), None); 149 | device.destroy_fence(Some(set.fence), None); 150 | device.free_command_buffers(set.pool, slice::from_ref(&set.command_buffer)); 151 | device.destroy_command_pool(Some(set.pool), None); 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /examples/graphics/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::window_surface; 2 | use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; 3 | use spark::{vk, Builder, Device, DeviceExtensions, Instance, InstanceExtensions, Loader}; 4 | use std::ffi::CStr; 5 | use std::os::raw::c_void; 6 | use std::slice; 7 | use winit::window::Window; 8 | 9 | unsafe extern "system" fn debug_messenger( 10 | message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, 11 | message_types: vk::DebugUtilsMessageTypeFlagsEXT, 12 | p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, 13 | _: *mut c_void, 14 | ) -> vk::Bool32 { 15 | if let Some(data) = p_callback_data.as_ref() { 16 | let message = CStr::from_ptr(data.p_message); 17 | println!("{}, {}: {:?}", message_severity, message_types, message); 18 | } 19 | vk::FALSE 20 | } 21 | 22 | pub trait InstanceExt { 23 | fn get_queue_family_index( 24 | &self, 25 | physical_device: vk::PhysicalDevice, 26 | queue_flags: vk::QueueFlags, 27 | surface: vk::SurfaceKHR, 28 | ) -> Option; 29 | } 30 | 31 | impl InstanceExt for Instance { 32 | fn get_queue_family_index( 33 | &self, 34 | physical_device: vk::PhysicalDevice, 35 | queue_flags: vk::QueueFlags, 36 | surface: vk::SurfaceKHR, 37 | ) -> Option { 38 | unsafe { self.get_physical_device_queue_family_properties_to_vec(physical_device) } 39 | .iter() 40 | .enumerate() 41 | .filter_map(|(index, &info)| { 42 | if info.queue_flags.contains(queue_flags) 43 | && unsafe { self.get_physical_device_surface_support_khr(physical_device, index as u32, surface) } 44 | .unwrap() 45 | { 46 | Some(index as u32) 47 | } else { 48 | None 49 | } 50 | }) 51 | .next() 52 | } 53 | } 54 | 55 | pub struct Context { 56 | pub instance: Instance, 57 | pub debug_utils_messenger: Option, 58 | pub surface: vk::SurfaceKHR, 59 | pub physical_device: vk::PhysicalDevice, 60 | pub physical_device_properties: vk::PhysicalDeviceProperties, 61 | pub physical_device_memory_properties: vk::PhysicalDeviceMemoryProperties, 62 | pub queue_family_index: u32, 63 | pub device: Device, 64 | pub queue: vk::Queue, 65 | } 66 | 67 | impl Context { 68 | pub fn new(window: &Window, version: vk::Version, is_debug: bool) -> Self { 69 | let display_handle = window.raw_display_handle(); 70 | let instance = { 71 | let loader = Loader::new().unwrap(); 72 | 73 | let mut extensions = InstanceExtensions::new(version); 74 | window_surface::enable_extensions(&display_handle, &mut extensions); 75 | if is_debug { 76 | extensions.enable_ext_debug_utils(); 77 | } 78 | let extension_names = extensions.to_name_vec(); 79 | 80 | let app_info = vk::ApplicationInfo::builder() 81 | .p_application_name(Some(CStr::from_bytes_with_nul(b"graphics\0").unwrap())) 82 | .api_version(version); 83 | 84 | let extension_name_ptrs: Vec<_> = extension_names.iter().map(|s| s.as_ptr()).collect(); 85 | let instance_create_info = vk::InstanceCreateInfo::builder() 86 | .p_application_info(Some(&app_info)) 87 | .pp_enabled_extension_names(&extension_name_ptrs); 88 | unsafe { loader.create_instance(&instance_create_info, None) }.unwrap() 89 | }; 90 | 91 | let debug_utils_messenger = if is_debug { 92 | let create_info = vk::DebugUtilsMessengerCreateInfoEXT { 93 | message_severity: vk::DebugUtilsMessageSeverityFlagsEXT::ERROR 94 | | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING, 95 | message_type: vk::DebugUtilsMessageTypeFlagsEXT::GENERAL 96 | | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION 97 | | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, 98 | pfn_user_callback: Some(debug_messenger), 99 | ..Default::default() 100 | }; 101 | Some(unsafe { instance.create_debug_utils_messenger_ext(&create_info, None) }.unwrap()) 102 | } else { 103 | None 104 | }; 105 | 106 | let window_handle = window.raw_window_handle(); 107 | let surface = window_surface::create(&instance, &display_handle, &window_handle).unwrap(); 108 | 109 | let physical_device = { 110 | let physical_devices = unsafe { instance.enumerate_physical_devices_to_vec() }.unwrap(); 111 | physical_devices[0] 112 | }; 113 | let physical_device_properties = unsafe { instance.get_physical_device_properties(physical_device) }; 114 | let physical_device_memory_properties = 115 | unsafe { instance.get_physical_device_memory_properties(physical_device) }; 116 | 117 | let queue_family_index = instance 118 | .get_queue_family_index(physical_device, vk::QueueFlags::GRAPHICS, surface) 119 | .unwrap(); 120 | 121 | let device = { 122 | let queue_priorities = [1.0]; 123 | let device_queue_create_info = vk::DeviceQueueCreateInfo::builder() 124 | .queue_family_index(queue_family_index) 125 | .p_queue_priorities(&queue_priorities); 126 | 127 | let mut extensions = DeviceExtensions::new(version); 128 | extensions.enable_khr_swapchain(); 129 | let extension_names = extensions.to_name_vec(); 130 | 131 | let extension_name_ptrs: Vec<_> = extension_names.iter().map(|s| s.as_ptr()).collect(); 132 | let device_create_info = vk::DeviceCreateInfo::builder() 133 | .p_queue_create_infos(slice::from_ref(&device_queue_create_info)) 134 | .pp_enabled_extension_names(&extension_name_ptrs); 135 | unsafe { instance.create_device(physical_device, &device_create_info, None, version) }.unwrap() 136 | }; 137 | 138 | let queue = unsafe { device.get_device_queue(queue_family_index, 0) }; 139 | 140 | Self { 141 | instance, 142 | debug_utils_messenger, 143 | surface, 144 | physical_device, 145 | physical_device_properties, 146 | physical_device_memory_properties, 147 | queue_family_index, 148 | device, 149 | queue, 150 | } 151 | } 152 | } 153 | 154 | impl Drop for Context { 155 | fn drop(&mut self) { 156 | unsafe { 157 | self.device.destroy_device(None); 158 | self.instance.destroy_surface_khr(Some(self.surface), None); 159 | if self.debug_utils_messenger.is_some() { 160 | self.instance 161 | .destroy_debug_utils_messenger_ext(self.debug_utils_messenger, None); 162 | } 163 | self.instance.destroy_instance(None); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /examples/graphics/src/main.rs: -------------------------------------------------------------------------------- 1 | mod command_buffer; 2 | mod context; 3 | mod swapchain; 4 | mod window_surface; 5 | 6 | use crate::{command_buffer::*, context::*, swapchain::*}; 7 | use egui::Key; 8 | use spark::{vk, Builder}; 9 | use std::{collections::HashMap, env, f32::consts::PI, ffi::CStr, mem, slice, sync::Arc}; 10 | use winit::{ 11 | dpi::{LogicalSize, Size}, 12 | event::{Event, WindowEvent}, 13 | event_loop::EventLoop, 14 | window::{Fullscreen, Window, WindowBuilder}, 15 | }; 16 | 17 | struct SwapTarget { 18 | context: Arc, 19 | image_view: vk::ImageView, 20 | framebuffer: vk::Framebuffer, 21 | } 22 | 23 | impl SwapTarget { 24 | fn new(context: &Arc, render_pass: vk::RenderPass, swapchain: &Swapchain, image_index: u32) -> Self { 25 | let image = swapchain.image(image_index); 26 | let format = swapchain.format(); 27 | let extent = swapchain.extent(); 28 | 29 | let image_view_create_info = vk::ImageViewCreateInfo { 30 | image: Some(image), 31 | view_type: vk::ImageViewType::N2D, 32 | format, 33 | subresource_range: vk::ImageSubresourceRange { 34 | aspect_mask: vk::ImageAspectFlags::COLOR, 35 | base_mip_level: 0, 36 | level_count: 1, 37 | base_array_layer: 0, 38 | layer_count: 1, 39 | }, 40 | ..Default::default() 41 | }; 42 | let image_view = unsafe { context.device.create_image_view(&image_view_create_info, None) }.unwrap(); 43 | 44 | let attachments = [image_view]; 45 | let framebuffer_create_info = vk::FramebufferCreateInfo::builder() 46 | .render_pass(render_pass) 47 | .p_attachments(&attachments) 48 | .width(extent.width) 49 | .height(extent.height) 50 | .layers(1); 51 | let framebuffer = unsafe { context.device.create_framebuffer(&framebuffer_create_info, None) }.unwrap(); 52 | 53 | Self { 54 | context: Arc::clone(context), 55 | image_view, 56 | framebuffer, 57 | } 58 | } 59 | } 60 | 61 | impl Drop for SwapTarget { 62 | fn drop(&mut self) { 63 | let device = &self.context.device; 64 | unsafe { 65 | device.destroy_framebuffer(Some(self.framebuffer), None); 66 | device.destroy_image_view(Some(self.image_view), None); 67 | } 68 | } 69 | } 70 | 71 | struct App { 72 | context: Arc, 73 | egui_ctx: egui::Context, 74 | egui_winit: egui_winit::State, 75 | egui_renderer: spark_egui::Renderer, 76 | egui_pipeline: vk::Pipeline, 77 | 78 | swapchain: Swapchain, 79 | recreate_swapchain: bool, 80 | command_buffer_pool: CommandBufferPool, 81 | 82 | render_pass: vk::RenderPass, 83 | vertex_shader: vk::ShaderModule, 84 | fragment_shader: vk::ShaderModule, 85 | pipeline_layout: vk::PipelineLayout, 86 | pipeline: vk::Pipeline, 87 | 88 | swap_targets: HashMap, 89 | old_swap_targets: Vec, 90 | 91 | frame_index: u32, 92 | angle: f32, 93 | } 94 | 95 | #[repr(C)] 96 | struct TestData { 97 | angle: f32, 98 | x_scale: f32, 99 | } 100 | 101 | impl App { 102 | const SWAPCHAIN_USAGE: vk::ImageUsageFlags = vk::ImageUsageFlags::COLOR_ATTACHMENT; 103 | 104 | fn new(window: &Window, version: vk::Version, is_debug: bool) -> Self { 105 | let context = Arc::new(Context::new(window, version, is_debug)); 106 | 107 | println!( 108 | "physical device ({}): {:?}", 109 | context.physical_device_properties.device_type, 110 | unsafe { CStr::from_ptr(context.physical_device_properties.device_name.as_ptr()) } 111 | ); 112 | 113 | let egui_max_vertex_count = 64 * 1024; 114 | let egui_max_texture_side = context 115 | .physical_device_properties 116 | .limits 117 | .max_image_dimension_2d 118 | .min(2048); 119 | 120 | let egui_ctx = egui::Context::default(); 121 | let mut egui_winit = egui_winit::State::new(window); 122 | egui_winit.set_pixels_per_point(window.scale_factor() as f32); 123 | egui_winit.set_max_texture_side(egui_max_texture_side as usize); 124 | let egui_renderer = spark_egui::Renderer::new( 125 | &context.device, 126 | &context.physical_device_properties, 127 | &context.physical_device_memory_properties, 128 | egui_max_vertex_count, 129 | egui_max_texture_side, 130 | ); 131 | 132 | let window_extent = { 133 | let inner_size = window.inner_size(); 134 | vk::Extent2D { 135 | width: inner_size.width, 136 | height: inner_size.height, 137 | } 138 | }; 139 | let swapchain = Swapchain::new(&context, window_extent, Self::SWAPCHAIN_USAGE); 140 | let command_buffer_pool = CommandBufferPool::new(&context); 141 | 142 | let render_pass = { 143 | let attachments = [vk::AttachmentDescription { 144 | flags: vk::AttachmentDescriptionFlags::empty(), 145 | format: swapchain.format(), 146 | samples: vk::SampleCountFlags::N1, 147 | load_op: vk::AttachmentLoadOp::CLEAR, 148 | store_op: vk::AttachmentStoreOp::STORE, 149 | stencil_load_op: vk::AttachmentLoadOp::DONT_CARE, 150 | stencil_store_op: vk::AttachmentStoreOp::DONT_CARE, 151 | initial_layout: vk::ImageLayout::UNDEFINED, 152 | final_layout: vk::ImageLayout::PRESENT_SRC_KHR, 153 | }]; 154 | let subpass_color_attachment = vk::AttachmentReference { 155 | attachment: 0, 156 | layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, 157 | }; 158 | let subpass_description = vk::SubpassDescription::builder() 159 | .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) 160 | .p_color_attachments(slice::from_ref(&subpass_color_attachment), None); 161 | let render_pass_create_info = vk::RenderPassCreateInfo::builder() 162 | .p_attachments(&attachments) 163 | .p_subpasses(slice::from_ref(&subpass_description)); 164 | unsafe { context.device.create_render_pass(&render_pass_create_info, None) }.unwrap() 165 | }; 166 | 167 | let vertex_shader = { 168 | let shader_bytes = include_bytes!("test.vert.spv"); 169 | let shader_module_create_info = vk::ShaderModuleCreateInfo { 170 | code_size: shader_bytes.len(), 171 | p_code: shader_bytes.as_ptr() as _, 172 | ..Default::default() 173 | }; 174 | unsafe { context.device.create_shader_module(&shader_module_create_info, None) }.unwrap() 175 | }; 176 | let fragment_shader = { 177 | let shader_bytes = include_bytes!("test.frag.spv"); 178 | let shader_module_create_info = vk::ShaderModuleCreateInfo { 179 | code_size: shader_bytes.len(), 180 | p_code: shader_bytes.as_ptr() as _, 181 | ..Default::default() 182 | }; 183 | unsafe { context.device.create_shader_module(&shader_module_create_info, None) }.unwrap() 184 | }; 185 | let pipeline_layout = { 186 | let push_constant_range = vk::PushConstantRange { 187 | stage_flags: vk::ShaderStageFlags::VERTEX, 188 | offset: 0, 189 | size: mem::size_of::() as u32, 190 | }; 191 | let pipeline_layout_create_info = 192 | vk::PipelineLayoutCreateInfo::builder().p_push_constant_ranges(slice::from_ref(&push_constant_range)); 193 | unsafe { 194 | context 195 | .device 196 | .create_pipeline_layout(&pipeline_layout_create_info, None) 197 | } 198 | .unwrap() 199 | }; 200 | 201 | let egui_pipeline = egui_renderer.create_pipeline(&context.device, render_pass, vk::SampleCountFlags::N1); 202 | let pipeline = { 203 | let shader_entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap(); 204 | let shader_stage_create_info = [ 205 | vk::PipelineShaderStageCreateInfo { 206 | stage: vk::ShaderStageFlags::VERTEX, 207 | module: Some(vertex_shader), 208 | p_name: shader_entry_name.as_ptr(), 209 | ..Default::default() 210 | }, 211 | vk::PipelineShaderStageCreateInfo { 212 | stage: vk::ShaderStageFlags::FRAGMENT, 213 | module: Some(fragment_shader), 214 | p_name: shader_entry_name.as_ptr(), 215 | ..Default::default() 216 | }, 217 | ]; 218 | 219 | let vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo { ..Default::default() }; 220 | let input_assembly_state_create_info = vk::PipelineInputAssemblyStateCreateInfo { 221 | topology: vk::PrimitiveTopology::TRIANGLE_LIST, 222 | ..Default::default() 223 | }; 224 | 225 | let viewport_state_create_info = vk::PipelineViewportStateCreateInfo { 226 | viewport_count: 1, 227 | scissor_count: 1, 228 | ..Default::default() 229 | }; 230 | 231 | let rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo { 232 | polygon_mode: vk::PolygonMode::FILL, 233 | cull_mode: vk::CullModeFlags::BACK, 234 | front_face: vk::FrontFace::CLOCKWISE, 235 | line_width: 1.0, 236 | ..Default::default() 237 | }; 238 | let multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo { 239 | rasterization_samples: vk::SampleCountFlags::N1, 240 | ..Default::default() 241 | }; 242 | 243 | let color_blend_attachment_state = vk::PipelineColorBlendAttachmentState { 244 | color_write_mask: vk::ColorComponentFlags::all(), 245 | ..Default::default() 246 | }; 247 | let color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo::builder() 248 | .p_attachments(slice::from_ref(&color_blend_attachment_state)); 249 | 250 | let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; 251 | let pipeline_dynamic_state_create_info = 252 | vk::PipelineDynamicStateCreateInfo::builder().p_dynamic_states(&dynamic_states); 253 | 254 | let pipeline_create_info = vk::GraphicsPipelineCreateInfo::builder() 255 | .p_stages(&shader_stage_create_info) 256 | .p_vertex_input_state(Some(&vertex_input_state_create_info)) 257 | .p_input_assembly_state(Some(&input_assembly_state_create_info)) 258 | .p_viewport_state(Some(&viewport_state_create_info)) 259 | .p_rasterization_state(Some(&rasterization_state_create_info)) 260 | .p_multisample_state(Some(&multisample_state_create_info)) 261 | .p_color_blend_state(Some(&color_blend_state_create_info)) 262 | .p_dynamic_state(Some(&pipeline_dynamic_state_create_info)) 263 | .layout(Some(pipeline_layout)) 264 | .render_pass(Some(render_pass)); 265 | 266 | unsafe { 267 | context 268 | .device 269 | .create_graphics_pipelines_single(None, &pipeline_create_info, None) 270 | } 271 | .unwrap() 272 | }; 273 | 274 | Self { 275 | context, 276 | egui_ctx, 277 | egui_winit, 278 | egui_renderer, 279 | egui_pipeline, 280 | 281 | swapchain, 282 | recreate_swapchain: false, 283 | command_buffer_pool, 284 | 285 | render_pass, 286 | vertex_shader, 287 | fragment_shader, 288 | pipeline_layout, 289 | pipeline, 290 | 291 | swap_targets: HashMap::new(), 292 | old_swap_targets: Vec::new(), 293 | 294 | frame_index: 0, 295 | angle: 0.0, 296 | } 297 | } 298 | 299 | fn render(&mut self, window: &Window, exit_requested: &mut bool) { 300 | // acquire a command buffer from the pool, blocks on a fence 301 | /* 302 | We want to build commands for frame N. We have 2 command buffers 303 | in the pool, we when the acquire returns we know that: 304 | 305 | * Frame N-2 has completed its command buffer (but maybe not presented yet) 306 | * Frame N-1 is likely still running its command buffer 307 | 308 | This is the intended location of CPU/GPU synchronisation: the 309 | CPU waits for the GPU to consume command buffers until at most 310 | 1 is still running. 311 | 312 | The GPU handles the synchronisation with the swapchain itself 313 | via semaphores, which in turn can limit the rate at which command 314 | buffers are consumed (for example using VSYNC at 60Hz). The CPU 315 | does not need to handle this explicitly, on the CPU side we only 316 | check command buffer consumption. 317 | */ 318 | let (cmd, image_available_semaphore) = self.command_buffer_pool.acquire(); 319 | 320 | // clean up targets that are 2+ frames old 321 | self.old_swap_targets.clear(); 322 | 323 | // run the UI 324 | let raw_input = self.egui_winit.take_egui_input(window); 325 | let egui::FullOutput { 326 | platform_output, 327 | repaint_after: _repaint_after, 328 | textures_delta, 329 | shapes, 330 | } = self.egui_ctx.run(raw_input, |ctx| { 331 | egui::Window::new("Debug").show(ctx, |ui| { 332 | if ui.button("Close Window").clicked() { 333 | *exit_requested = true; 334 | } 335 | ui.label(format!("Frame: {}", self.frame_index)); 336 | }); 337 | ctx.input(|i| { 338 | if i.key_pressed(Key::Escape) { 339 | *exit_requested = true; 340 | } 341 | }) 342 | }); 343 | self.egui_winit 344 | .handle_platform_output(window, &self.egui_ctx, platform_output); 345 | 346 | // prepare egui texture and shape data for rendering 347 | let clipped_primitives = self.egui_ctx.tessellate(shapes); 348 | self.egui_renderer.update( 349 | &self.context.device, 350 | &self.context.physical_device_memory_properties, 351 | cmd, 352 | clipped_primitives, 353 | textures_delta, 354 | ); 355 | 356 | // we want to render to the swapchain, so acquire an image from it (this usually does not block) 357 | let window_extent = { 358 | let inner_size = window.inner_size(); 359 | vk::Extent2D { 360 | width: inner_size.width, 361 | height: inner_size.height, 362 | } 363 | }; 364 | let swap_image_index = loop { 365 | if self.recreate_swapchain { 366 | for (_, target) in self.swap_targets.drain() { 367 | self.old_swap_targets.push(target); 368 | } 369 | self.swapchain.recreate(window_extent, Self::SWAPCHAIN_USAGE); 370 | self.recreate_swapchain = false; 371 | } 372 | match self.swapchain.acquire(window_extent, image_available_semaphore) { 373 | SwapchainAcquireResult::Ok(image_index) => break image_index, 374 | SwapchainAcquireResult::RecreateSoon(image_index) => { 375 | self.recreate_swapchain = true; 376 | break image_index; 377 | } 378 | SwapchainAcquireResult::RecreateNow => self.recreate_swapchain = true, 379 | }; 380 | }; 381 | 382 | // get (and keep for later) a framebuffer for this swapchain image 383 | let target = { 384 | let context = &self.context; 385 | let render_pass = self.render_pass; 386 | let swapchain = &self.swapchain; 387 | self.swap_targets 388 | .entry(swap_image_index) 389 | .or_insert_with(|| SwapTarget::new(context, render_pass, swapchain, swap_image_index)) 390 | }; 391 | 392 | // start our render pass to the swapchain 393 | { 394 | let clear_value = vk::ClearValue { 395 | color: vk::ClearColorValue { 396 | float32: [0.1f32, 0.1f32, 0.1f32, 0f32], 397 | }, 398 | }; 399 | let render_pass_begin_info = vk::RenderPassBeginInfo::builder() 400 | .render_pass(self.render_pass) 401 | .framebuffer(target.framebuffer) 402 | .render_area(vk::Rect2D { 403 | offset: Default::default(), 404 | extent: self.swapchain.extent(), 405 | }) 406 | .p_clear_values(slice::from_ref(&clear_value)); 407 | unsafe { 408 | self.context 409 | .device 410 | .cmd_begin_render_pass(cmd, &render_pass_begin_info, vk::SubpassContents::INLINE) 411 | }; 412 | } 413 | 414 | // draw a triangle 415 | { 416 | let device = &self.context.device; 417 | 418 | let extent = self.swapchain.extent(); 419 | let viewport = vk::Viewport { 420 | width: extent.width as f32, 421 | height: extent.height as f32, 422 | max_depth: 1.0, 423 | ..Default::default() 424 | }; 425 | let scissor = vk::Rect2D { 426 | extent, 427 | ..Default::default() 428 | }; 429 | 430 | let test_data = TestData { 431 | angle: self.angle, 432 | x_scale: (extent.height as f32) / (extent.width as f32), 433 | }; 434 | 435 | unsafe { 436 | device.cmd_set_viewport(cmd, 0, slice::from_ref(&viewport)); 437 | device.cmd_set_scissor(cmd, 0, slice::from_ref(&scissor)); 438 | 439 | device.cmd_bind_pipeline(cmd, vk::PipelineBindPoint::GRAPHICS, self.pipeline); 440 | device.cmd_push_constants( 441 | cmd, 442 | self.pipeline_layout, 443 | vk::ShaderStageFlags::VERTEX, 444 | 0, 445 | slice::from_ref(&test_data), 446 | ); 447 | device.cmd_draw(cmd, 3, 1, 0, 0); 448 | } 449 | } 450 | 451 | // draw egui in the same render pass 452 | { 453 | let extent = self.swapchain.extent(); 454 | let pixels_per_point = self.egui_ctx.pixels_per_point(); 455 | self.egui_renderer.render( 456 | &self.context.device, 457 | cmd, 458 | self.egui_pipeline, 459 | extent.width, 460 | extent.height, 461 | pixels_per_point, 462 | ); 463 | } 464 | 465 | // end the render pass to the swapchain 466 | unsafe { self.context.device.cmd_end_render_pass(cmd) }; 467 | 468 | // submit the command buffer and queue up the swapchain present 469 | let rendering_finished_semaphore = self.command_buffer_pool.submit(); 470 | self.swapchain.present(swap_image_index, rendering_finished_semaphore); 471 | 472 | self.frame_index += 1; 473 | self.angle += self.egui_ctx.input(|i| PI * i.stable_dt); 474 | } 475 | } 476 | 477 | impl Drop for App { 478 | fn drop(&mut self) { 479 | let device = &self.context.device; 480 | unsafe { 481 | device.device_wait_idle().unwrap(); 482 | 483 | device.destroy_pipeline(Some(self.egui_pipeline), None); 484 | self.egui_renderer.destroy(device); 485 | 486 | device.destroy_pipeline(Some(self.pipeline), None); 487 | device.destroy_pipeline_layout(Some(self.pipeline_layout), None); 488 | device.destroy_shader_module(Some(self.fragment_shader), None); 489 | device.destroy_shader_module(Some(self.vertex_shader), None); 490 | device.destroy_render_pass(Some(self.render_pass), None); 491 | } 492 | } 493 | } 494 | 495 | pub fn main() { 496 | let version = Default::default(); 497 | let mut is_debug = false; 498 | let mut is_fullscreen = false; 499 | for arg in env::args().skip(1) { 500 | match arg.as_str() { 501 | "-d" => is_debug = true, 502 | "-f" => is_fullscreen = true, 503 | _ => panic!("unknown argument {:?}", arg), 504 | } 505 | } 506 | 507 | let event_loop = EventLoop::new(); 508 | 509 | let mut window_builder = WindowBuilder::new().with_title("graphics example"); 510 | window_builder = if is_fullscreen { 511 | window_builder.with_fullscreen(Some(Fullscreen::Borderless(event_loop.primary_monitor()))) 512 | } else { 513 | window_builder.with_inner_size(Size::Logical(LogicalSize::new(480.0, 360.0))) 514 | }; 515 | let window = window_builder.build(&event_loop).unwrap(); 516 | 517 | let mut app = App::new(&window, version, is_debug); 518 | let mut exit_requested = false; 519 | event_loop.run(move |event, _target, control_flow| { 520 | control_flow.set_poll(); 521 | match event { 522 | Event::RedrawEventsCleared => { 523 | app.render(&window, &mut exit_requested); 524 | } 525 | Event::WindowEvent { event, .. } => { 526 | if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { 527 | exit_requested = true; 528 | } 529 | 530 | let event_response = app.egui_winit.on_event(&app.egui_ctx, &event); 531 | if event_response.repaint { 532 | window.request_redraw(); 533 | } 534 | } 535 | _ => {} 536 | } 537 | if exit_requested { 538 | control_flow.set_exit(); 539 | } 540 | }); 541 | } 542 | -------------------------------------------------------------------------------- /examples/graphics/src/swapchain.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use spark::{vk, Builder}; 3 | use std::cmp; 4 | use std::slice; 5 | use std::sync::Arc; 6 | use std::u64; 7 | 8 | pub struct Swapchain { 9 | context: Arc, 10 | swapchain: vk::SwapchainKHR, 11 | surface_format: vk::SurfaceFormatKHR, 12 | extent: vk::Extent2D, 13 | images: Vec, 14 | } 15 | 16 | pub enum SwapchainAcquireResult { 17 | RecreateNow, 18 | RecreateSoon(u32), 19 | Ok(u32), 20 | } 21 | 22 | impl Swapchain { 23 | const MIN_IMAGE_COUNT: u32 = 2; 24 | 25 | fn create( 26 | context: &Context, 27 | window_extent: vk::Extent2D, 28 | usage: vk::ImageUsageFlags, 29 | old_swapchain: Option, 30 | ) -> (vk::SwapchainKHR, vk::SurfaceFormatKHR, vk::Extent2D) { 31 | let surface_capabilities = unsafe { 32 | context 33 | .instance 34 | .get_physical_device_surface_capabilities_khr(context.physical_device, context.surface) 35 | } 36 | .unwrap(); 37 | let mut extent = surface_capabilities.current_extent; 38 | if extent.width == u32::MAX && extent.height == u32::MAX { 39 | extent = window_extent; 40 | } 41 | let surface_supported = unsafe { 42 | context.instance.get_physical_device_surface_support_khr( 43 | context.physical_device, 44 | context.queue_family_index, 45 | context.surface, 46 | ) 47 | } 48 | .unwrap(); 49 | if !surface_supported { 50 | panic!("swapchain surface not supported"); 51 | } 52 | 53 | let surface_formats = unsafe { 54 | context 55 | .instance 56 | .get_physical_device_surface_formats_khr_to_vec(context.physical_device, Some(context.surface)) 57 | } 58 | .unwrap(); 59 | 60 | let surface_format = surface_formats 61 | .iter() 62 | .find(|sf| match (sf.format, sf.color_space) { 63 | (vk::Format::R8G8B8A8_SRGB, vk::ColorSpaceKHR::SRGB_NONLINEAR) => true, 64 | (vk::Format::B8G8R8A8_SRGB, vk::ColorSpaceKHR::SRGB_NONLINEAR) => true, 65 | _ => false, 66 | }) 67 | .copied() 68 | .expect("no supported swapchain format found"); 69 | 70 | let min_image_count = cmp::max(Self::MIN_IMAGE_COUNT, surface_capabilities.min_image_count); 71 | 72 | let swapchain_create_info = vk::SwapchainCreateInfoKHR::builder() 73 | .surface(context.surface) 74 | .min_image_count(min_image_count) 75 | .image_format(surface_format.format) 76 | .image_color_space(surface_format.color_space) 77 | .image_extent(extent) 78 | .image_array_layers(1) 79 | .image_usage(usage) 80 | .p_queue_family_indices(slice::from_ref(&context.queue_family_index)) 81 | .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) 82 | .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) 83 | .present_mode(vk::PresentModeKHR::FIFO) 84 | .clipped(true) 85 | .old_swapchain(old_swapchain); 86 | let swapchain = unsafe { context.device.create_swapchain_khr(&swapchain_create_info, None) }.unwrap(); 87 | 88 | (swapchain, surface_format, extent) 89 | } 90 | 91 | pub fn new(context: &Arc, window_extent: vk::Extent2D, usage: vk::ImageUsageFlags) -> Self { 92 | let (swapchain, surface_format, extent) = Swapchain::create(context, window_extent, usage, None); 93 | 94 | let images = unsafe { context.device.get_swapchain_images_khr_to_vec(swapchain) }.unwrap(); 95 | 96 | Swapchain { 97 | context: Arc::clone(context), 98 | swapchain, 99 | surface_format, 100 | extent, 101 | images, 102 | } 103 | } 104 | 105 | pub fn recreate(&mut self, window_extent: vk::Extent2D, usage: vk::ImageUsageFlags) { 106 | let (swapchain, surface_format, extent) = 107 | Swapchain::create(&self.context, window_extent, usage, Some(self.swapchain)); 108 | unsafe { self.context.device.destroy_swapchain_khr(Some(self.swapchain), None) }; 109 | 110 | self.swapchain = swapchain; 111 | self.surface_format = surface_format; 112 | self.extent = extent; 113 | self.images = unsafe { self.context.device.get_swapchain_images_khr_to_vec(swapchain) }.unwrap(); 114 | } 115 | 116 | pub fn acquire( 117 | &self, 118 | window_extent: vk::Extent2D, 119 | image_available_semaphore: vk::Semaphore, 120 | ) -> SwapchainAcquireResult { 121 | let res = unsafe { 122 | self.context 123 | .device 124 | .acquire_next_image_khr(self.swapchain, u64::MAX, Some(image_available_semaphore), None) 125 | }; 126 | match res { 127 | Ok((vk::Result::SUCCESS, image_index)) => { 128 | if self.extent == window_extent { 129 | SwapchainAcquireResult::Ok(image_index) 130 | } else { 131 | SwapchainAcquireResult::RecreateSoon(image_index) 132 | } 133 | } 134 | Ok((vk::Result::SUBOPTIMAL_KHR, image_index)) => SwapchainAcquireResult::RecreateSoon(image_index), 135 | Ok((err, _)) => panic!("failed to acquire next image {}", err), 136 | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => SwapchainAcquireResult::RecreateNow, 137 | Err(err) => panic!("failed to acquire next image {}", err), 138 | } 139 | } 140 | 141 | pub fn image(&self, image_index: u32) -> vk::Image { 142 | self.images[image_index as usize] 143 | } 144 | 145 | pub fn format(&self) -> vk::Format { 146 | self.surface_format.format 147 | } 148 | 149 | pub fn extent(&self) -> vk::Extent2D { 150 | self.extent 151 | } 152 | 153 | pub fn present(&self, image_index: u32, rendering_finished_semaphore: vk::Semaphore) { 154 | let present_info = vk::PresentInfoKHR::builder() 155 | .p_wait_semaphores(slice::from_ref(&rendering_finished_semaphore)) 156 | .p_swapchains(slice::from_ref(&self.swapchain), slice::from_ref(&image_index)); 157 | match unsafe { self.context.device.queue_present_khr(self.context.queue, &present_info) } { 158 | Ok(vk::Result::SUCCESS) | Ok(vk::Result::SUBOPTIMAL_KHR) | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {} 159 | Ok(err) | Err(err) => panic!("failed to present {}", err), 160 | } 161 | } 162 | } 163 | 164 | impl Drop for Swapchain { 165 | fn drop(&mut self) { 166 | unsafe { 167 | self.context.device.destroy_swapchain_khr(Some(self.swapchain), None); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /examples/graphics/src/test.frag: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(location = 0) out vec4 o_col; 4 | 5 | layout(location = 0) in vec2 v_uv; 6 | 7 | void main() 8 | { 9 | o_col = vec4(v_uv, 1.f, 0.f); 10 | } 11 | -------------------------------------------------------------------------------- /examples/graphics/src/test.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/examples/graphics/src/test.frag.spv -------------------------------------------------------------------------------- /examples/graphics/src/test.vert: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(push_constant) uniform TestData { 4 | float angle; 5 | float x_scale; 6 | } g_test; 7 | 8 | out gl_PerVertex { 9 | vec4 gl_Position; 10 | }; 11 | 12 | layout(location = 0) out vec2 v_uv; 13 | 14 | void main() 15 | { 16 | vec2 v = vec2( 17 | gl_VertexIndex == 1 ? .5f : -.5f, 18 | gl_VertexIndex == 2 ? .5f : -.5f); 19 | v_uv = v + .5f; 20 | 21 | float ca = cos(g_test.angle); 22 | float sa = sin(g_test.angle); 23 | v = vec2(v.x*ca + v.y*sa, -v.x*sa + v.y*ca); 24 | 25 | gl_Position = vec4(v.x*g_test.x_scale, v.y, 0.f, 1.f); 26 | } 27 | -------------------------------------------------------------------------------- /examples/graphics/src/test.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/examples/graphics/src/test.vert.spv -------------------------------------------------------------------------------- /examples/graphics/src/window_surface.rs: -------------------------------------------------------------------------------- 1 | use raw_window_handle::{RawDisplayHandle, RawWindowHandle}; 2 | use spark::{vk, Instance, InstanceExtensions, Result}; 3 | 4 | pub fn enable_extensions(display_handle: &RawDisplayHandle, extensions: &mut InstanceExtensions) { 5 | match display_handle { 6 | #[cfg(target_os = "linux")] 7 | RawDisplayHandle::Xlib(_) => extensions.enable_khr_xlib_surface(), 8 | 9 | #[cfg(target_os = "linux")] 10 | RawDisplayHandle::Wayland(_) => extensions.enable_khr_wayland_surface(), 11 | 12 | #[cfg(target_os = "windows")] 13 | RawDisplayHandle::Windows(_) => extensions.enable_khr_win32_surface(), 14 | 15 | #[cfg(target_os = "android")] 16 | RawDisplayHandle::AndroidNdk(_) => extensions.enable_khr_android_surface(), 17 | 18 | _ => unimplemented!(), 19 | } 20 | } 21 | 22 | pub fn create( 23 | instance: &Instance, 24 | display_handle: &RawDisplayHandle, 25 | window_handle: &RawWindowHandle, 26 | ) -> Result { 27 | match (display_handle, window_handle) { 28 | #[cfg(target_os = "linux")] 29 | (RawDisplayHandle::Xlib(display_handle), RawWindowHandle::Xlib(window_handle)) => { 30 | let create_info = vk::XlibSurfaceCreateInfoKHR { 31 | dpy: display_handle.display as _, 32 | window: window_handle.window, 33 | ..Default::default() 34 | }; 35 | unsafe { instance.create_xlib_surface_khr(&create_info, None) } 36 | } 37 | 38 | #[cfg(target_os = "linux")] 39 | (RawDisplayHandle::Wayland(display_handle), RawWindowHandle::Wayland(window_handle)) => { 40 | let create_info = vk::WaylandSurfaceCreateInfoKHR { 41 | display: display_handle.display as _, 42 | surface: window_handle.surface as _, 43 | ..Default::default() 44 | }; 45 | unsafe { instance.create_wayland_surface_khr(&create_info, None) } 46 | } 47 | 48 | #[cfg(target_os = "windows")] 49 | (RawDisplayHandle::Windows(_), RawWindowHandle::Win32(window_handle)) => { 50 | let create_info = vk::Win32SurfaceCreateInfoKHR { 51 | hwnd: window_handle.hwnd, 52 | ..Default::default() 53 | }; 54 | unsafe { instance.create_win32_surface_khr(&create_info, None) } 55 | } 56 | 57 | #[cfg(target_os = "android")] 58 | (RawDisplayHandle::AndroidNdk(_), RawWindowHandle::AndroidNdk(window_handle)) => { 59 | let create_info = vk::AndroidSurfaceCreateInfoKHR { 60 | window: window_handle.a_native_window as _, 61 | ..Default::default() 62 | }; 63 | unsafe { instance.create_android_surface_khr(&create_info, None) } 64 | } 65 | 66 | _ => unimplemented!(), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | heck = "0.4.0" 10 | nom = "7.1.0" 11 | take_mut = "0.2.2" 12 | vk-parse = "0.15" 13 | -------------------------------------------------------------------------------- /generator/src/builder_prefix.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::wrong_self_convention, clippy::unnecessary_cast)] 2 | 3 | use std::{ 4 | ffi::CStr, 5 | mem, 6 | os::raw::{c_void, c_char, c_int}, 7 | ptr, 8 | marker::PhantomData, 9 | ops::Deref, 10 | }; 11 | use super::vk; 12 | 13 | pub trait Builder<'a> { 14 | type Type; 15 | fn builder() -> Self::Type; 16 | } 17 | 18 | unsafe fn insert_next(head: *mut vk::BaseOutStructure, other: *mut vk::BaseOutStructure) { 19 | assert!((*other).p_next.is_null()); 20 | (*other).p_next = (*head).p_next; 21 | (*head).p_next = other as *mut _; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /generator/src/c_parse.rs: -------------------------------------------------------------------------------- 1 | use nom::{ 2 | branch::alt, 3 | bytes::complete::{tag, take_while1}, 4 | character::complete::{char, digit1, hex_digit1, multispace0}, 5 | combinator::{all_consuming, map, map_res, not, opt, peek}, 6 | error::VerboseError, 7 | multi::{many0, separated_list1}, 8 | number::complete::float, 9 | sequence::{delimited, preceded, separated_pair, terminated, tuple}, 10 | IResult, 11 | }; 12 | use std::fmt; 13 | 14 | #[derive(Debug, Clone)] 15 | pub enum DependencyExpr<'a> { 16 | Never, 17 | Always, 18 | Version((u16, u16)), 19 | Extension(&'a str), 20 | Feature((&'a str, &'a str)), 21 | And(Vec>), 22 | Or(Vec>), 23 | } 24 | 25 | impl<'a> DependencyExpr<'a> { 26 | pub fn is_never(&self) -> bool { 27 | matches!(self, DependencyExpr::Never) 28 | } 29 | 30 | pub fn is_always(&self) -> bool { 31 | matches!(self, DependencyExpr::Always) 32 | } 33 | 34 | pub fn visit_leaves(&mut self, f: &impl Fn(&mut DependencyExpr)) { 35 | match self { 36 | Self::Never | Self::Always | Self::Version(_) | Self::Extension(_) | Self::Feature(_) => f(self), 37 | Self::And(v) | Self::Or(v) => { 38 | for dep in v.iter_mut() { 39 | dep.visit_leaves(f) 40 | } 41 | } 42 | } 43 | } 44 | 45 | fn matches(&self, other: &Self) -> bool { 46 | match (self, other) { 47 | (Self::Never, Self::Never) => true, 48 | (Self::Always, Self::Always) => true, 49 | (Self::Version(a), Self::Version(b)) => a == b, 50 | (Self::Extension(a), Self::Extension(b)) => a == b, 51 | (Self::Feature(a), Self::Feature(b)) => a == b, 52 | (Self::And(ref a), Self::And(ref b)) | (Self::Or(ref a), Self::Or(ref b)) => match (a.len(), b.len()) { 53 | (0, 0) => true, 54 | (1, 1) => a[0].matches(&b[0]), 55 | (2, 2) => (a[0].matches(&b[0]) && a[1].matches(&b[1])) || (a[0].matches(&b[1]) && a[1].matches(&b[0])), 56 | _ => false, 57 | }, 58 | _ => false, 59 | } 60 | } 61 | 62 | // only true when "other" is true 63 | fn is_subset_of(&self, other: &Self) -> bool { 64 | self.matches(other) 65 | || match (self, other) { 66 | (Self::Never, _) => true, 67 | (_, Self::Always) => true, 68 | (Self::Version(a), Self::Version(b)) => a >= b, 69 | (Self::And(ref a), _) => a.iter().any(|a_elem| a_elem.is_subset_of(other)), 70 | (Self::Or(ref a), _) => a.iter().all(|a_elem| a_elem.is_subset_of(other)), 71 | _ => false, 72 | } 73 | } 74 | 75 | fn try_merge_and(a: &Self, b: &Self) -> Option { 76 | if a.is_subset_of(b) { 77 | Some(a.clone()) 78 | } else if b.is_subset_of(a) { 79 | Some(b.clone()) 80 | } else { 81 | None 82 | } 83 | } 84 | 85 | fn try_merge_or(a: &Self, b: &Self) -> Option { 86 | if b.is_subset_of(a) { 87 | Some(a.clone()) 88 | } else if a.is_subset_of(b) { 89 | Some(b.clone()) 90 | } else { 91 | None 92 | } 93 | } 94 | 95 | fn max_involved_version(&self) -> (u16, u16) { 96 | match self { 97 | Self::Never | Self::Always | Self::Extension(_) | Self::Feature(_) => (0, 0), 98 | Self::Version(v) => *v, 99 | Self::And(v) | Self::Or(v) => { 100 | let mut result = (0, 0); 101 | for dep in v.iter() { 102 | result = result.max(dep.max_involved_version()); 103 | } 104 | result 105 | } 106 | } 107 | } 108 | 109 | pub fn simplify(&mut self) { 110 | match self { 111 | Self::Never => {} 112 | Self::Always => {} 113 | Self::Version(v) => { 114 | if *v == (1, 0) { 115 | *self = Self::Always; 116 | } 117 | } 118 | Self::Extension(_) => {} 119 | Self::Feature(_) => {} 120 | Self::And(v) => { 121 | let mut tmp = Vec::new(); 122 | for mut dep in v.drain(..) { 123 | dep.simplify(); 124 | if let Self::And(mut inner) = dep { 125 | tmp.append(&mut inner); 126 | } else { 127 | tmp.push(dep); 128 | } 129 | } 130 | let mut result = Vec::new(); 131 | 'outer: for dep in tmp.drain(..) { 132 | for prev in &mut result { 133 | if let Some(merged) = Self::try_merge_and(prev, &dep) { 134 | *prev = merged; 135 | continue 'outer; 136 | } 137 | } 138 | result.push(dep); 139 | } 140 | match result.len() { 141 | 0 => unreachable!(), 142 | 1 => *self = result.pop().unwrap(), 143 | _ => *v = result, 144 | } 145 | } 146 | Self::Or(v) => { 147 | let mut tmp = Vec::new(); 148 | for mut dep in v.drain(..) { 149 | dep.simplify(); 150 | if let Self::Or(mut inner) = dep { 151 | tmp.append(&mut inner); 152 | } else { 153 | tmp.push(dep); 154 | } 155 | } 156 | let mut result = Vec::new(); 157 | 'outer: for dep in tmp.drain(..) { 158 | for prev in &mut result { 159 | if let Some(merged) = Self::try_merge_or(prev, &dep) { 160 | *prev = merged; 161 | continue 'outer; 162 | } 163 | } 164 | result.push(dep); 165 | } 166 | result.sort_by(|a, b| { 167 | let av = a.max_involved_version(); 168 | let bv = b.max_involved_version(); 169 | av.cmp(&bv).reverse() 170 | }); 171 | match result.len() { 172 | 0 => unreachable!(), 173 | 1 => *self = result.pop().unwrap(), 174 | _ => *v = result, 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 182 | pub enum CDecoration { 183 | None, 184 | Const, 185 | Pointer, 186 | PointerToConst, 187 | PointerToPointer, 188 | PointerToConstPointerToConst, 189 | } 190 | 191 | impl CDecoration { 192 | pub fn is_pointer(&self) -> bool { 193 | match self { 194 | CDecoration::None | CDecoration::Const => false, 195 | CDecoration::Pointer 196 | | CDecoration::PointerToConst 197 | | CDecoration::PointerToPointer 198 | | CDecoration::PointerToConstPointerToConst => true, 199 | } 200 | } 201 | 202 | pub fn is_mutable(&self) -> bool { 203 | match self { 204 | CDecoration::None 205 | | CDecoration::Const 206 | | CDecoration::PointerToConst 207 | | CDecoration::PointerToConstPointerToConst => false, 208 | CDecoration::Pointer | CDecoration::PointerToPointer => true, 209 | } 210 | } 211 | } 212 | 213 | impl fmt::Display for CDecoration { 214 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 215 | let s = match self { 216 | CDecoration::None | CDecoration::Const => "", 217 | CDecoration::Pointer => "* mut ", 218 | CDecoration::PointerToConst => "* const ", 219 | CDecoration::PointerToPointer => "* mut *mut ", 220 | CDecoration::PointerToConstPointerToConst => "*const *const ", 221 | }; 222 | s.fmt(f) 223 | } 224 | } 225 | 226 | #[derive(Debug, Clone, Copy)] 227 | pub enum CArraySize<'a> { 228 | Literal(usize), 229 | Ident(&'a str), 230 | } 231 | 232 | impl fmt::Display for CArraySize<'_> { 233 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 234 | match self { 235 | CArraySize::Literal(n) => n.fmt(f), 236 | CArraySize::Ident(s) => s.fmt(f), 237 | } 238 | } 239 | } 240 | 241 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 242 | pub enum CBaseType<'a> { 243 | Void, 244 | Char, 245 | Int, 246 | F32, 247 | F64, 248 | U8, 249 | U16, 250 | U32, 251 | U64, 252 | I8, 253 | I16, 254 | I32, 255 | I64, 256 | USize, 257 | Named(&'a str), 258 | } 259 | 260 | impl<'a> CBaseType<'a> { 261 | pub fn try_name(&self) -> Option<&'a str> { 262 | if let CBaseType::Named(name) = *self { 263 | Some(name) 264 | } else { 265 | None 266 | } 267 | } 268 | } 269 | 270 | #[derive(Debug, Clone, Copy)] 271 | pub struct CType<'a> { 272 | pub base: CBaseType<'a>, 273 | pub decoration: CDecoration, 274 | pub array_size: Option>, 275 | pub bit_count: Option, 276 | } 277 | 278 | impl<'a> CType<'a> { 279 | pub fn is_base_type(&self, base: CBaseType) -> bool { 280 | self.base == base && !self.decoration.is_pointer() && self.array_size.is_none() && self.bit_count.is_none() 281 | } 282 | 283 | pub fn strip_array(&self) -> CType<'a> { 284 | if self.array_size.is_some() { 285 | let decoration = match self.decoration { 286 | CDecoration::None => CDecoration::Pointer, 287 | CDecoration::Const => CDecoration::PointerToConst, 288 | _ => panic!("cannot convert array to pointer type"), 289 | }; 290 | CType { 291 | base: self.base, 292 | decoration, 293 | array_size: None, 294 | bit_count: self.bit_count, 295 | } 296 | } else { 297 | Clone::clone(self) 298 | } 299 | } 300 | } 301 | 302 | #[derive(Debug, Clone, Copy)] 303 | pub struct CVariableDecl<'a> { 304 | pub name: &'a str, 305 | pub ty: CType<'a>, 306 | } 307 | 308 | #[derive(Debug)] 309 | pub struct CFunctionDecl<'a> { 310 | pub proto: CVariableDecl<'a>, 311 | pub parameters: Vec>, 312 | } 313 | 314 | #[derive(Debug)] 315 | pub enum CConstant { 316 | UInt(usize), 317 | UInt32(u32), 318 | UInt64(u64), 319 | Float(f32), 320 | } 321 | 322 | fn ignore_remainder((_i, o): (&str, T)) -> T { 323 | o 324 | } 325 | 326 | type Res<'a, T> = IResult<&'a str, T, VerboseError<&'a str>>; 327 | 328 | fn version_num(i: &str) -> Res { 329 | map_res(digit1, str::parse::)(i) 330 | } 331 | 332 | fn version(i: &str) -> Res<(u16, u16)> { 333 | tuple(( 334 | preceded(tag("VK_VERSION_"), version_num), 335 | preceded(tag("_"), version_num), 336 | ))(i) 337 | } 338 | 339 | pub fn c_try_parse_version(i: &str) -> Option<(u16, u16)> { 340 | all_consuming(version)(i).map(ignore_remainder).ok() 341 | } 342 | 343 | fn depends_expr_inner(i: &str) -> Res { 344 | alt(( 345 | delimited(char('('), depends_expr, char(')')), 346 | map(version, DependencyExpr::Version), 347 | map( 348 | separated_pair(take_while1(is_ident), tag("::"), take_while1(is_ident)), 349 | DependencyExpr::Feature, 350 | ), 351 | map(take_while1(is_ident), DependencyExpr::Extension), 352 | ))(i) 353 | } 354 | 355 | fn depends_expr(i: &str) -> Res { 356 | let (mut i, mut dep) = depends_expr_inner(i)?; 357 | loop { 358 | // equal precedence! 359 | let (op_i, op_c) = opt(alt((char('+'), char(','))))(i)?; 360 | if let Some(c) = op_c { 361 | let (other_i, other) = depends_expr_inner(op_i)?; 362 | i = other_i; 363 | dep = match c { 364 | '+' => DependencyExpr::And(vec![dep, other]), 365 | ',' => DependencyExpr::Or(vec![dep, other]), 366 | _ => unreachable!(), 367 | } 368 | } else { 369 | break; 370 | } 371 | } 372 | Ok((i, dep)) 373 | } 374 | 375 | pub fn c_parse_depends(i: &str) -> DependencyExpr { 376 | all_consuming(depends_expr)(i) 377 | .map(ignore_remainder) 378 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 379 | } 380 | 381 | fn parse_i32(i: &str) -> Res { 382 | alt(( 383 | preceded(tag("0x"), map_res(hex_digit1, |s: &str| i32::from_str_radix(s, 16))), 384 | preceded(char('-'), map(map_res(digit1, str::parse::), |n| -n)), 385 | map_res(digit1, str::parse::), 386 | ))(i) 387 | } 388 | 389 | pub fn c_parse_int(i: &str) -> i32 { 390 | all_consuming(parse_i32)(i) 391 | .map(ignore_remainder) 392 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 393 | } 394 | 395 | fn is_ident(c: char) -> bool { 396 | matches!(c, 'a'..='z' | 'A'..='Z' | '_' | '0'..='9') 397 | } 398 | 399 | fn ident(i: &str) -> Res<&str> { 400 | preceded(multispace0, take_while1(is_ident))(i) 401 | } 402 | 403 | fn keyword<'a>(k: &'static str) -> impl FnMut(&'a str) -> Res<'a, &str> { 404 | delimited(multispace0, tag(k), not(peek(take_while1(is_ident)))) 405 | } 406 | 407 | fn op<'a>(c: char) -> impl FnMut(&'a str) -> Res<'a, char> { 408 | preceded(multispace0, char(c)) 409 | } 410 | 411 | fn array_size(i: &str) -> Res { 412 | alt(( 413 | map(map_res(digit1, str::parse::), CArraySize::Literal), 414 | map(ident, CArraySize::Ident), 415 | ))(i) 416 | } 417 | 418 | fn base_type(i: &str) -> Res { 419 | alt(( 420 | map(keyword("void"), |_| CBaseType::Void), 421 | map(keyword("char"), |_| CBaseType::Char), 422 | map(keyword("int"), |_| CBaseType::Int), 423 | map(keyword("float"), |_| CBaseType::F32), 424 | map(keyword("double"), |_| CBaseType::F64), 425 | map(keyword("uint8_t"), |_| CBaseType::U8), 426 | map(keyword("uint16_t"), |_| CBaseType::U16), 427 | map(keyword("uint32_t"), |_| CBaseType::U32), 428 | map(keyword("uint64_t"), |_| CBaseType::U64), 429 | map(keyword("int8_t"), |_| CBaseType::I8), 430 | map(keyword("int16_t"), |_| CBaseType::I16), 431 | map(keyword("int32_t"), |_| CBaseType::I32), 432 | map(keyword("int64_t"), |_| CBaseType::I64), 433 | map(keyword("size_t"), |_| CBaseType::USize), 434 | map(ident, CBaseType::Named), 435 | ))(i) 436 | } 437 | 438 | fn variable_decl(i: &str) -> Res { 439 | let (i, const0) = opt(keyword("const"))(i)?; 440 | let (i, _) = opt(keyword("struct"))(i)?; 441 | let (i, base) = base_type(i)?; 442 | let (i, ptr0) = opt(op('*'))(i)?; 443 | let (i, const1) = opt(keyword("const"))(i)?; 444 | let (i, ptr1) = opt(op('*'))(i)?; 445 | let (i, var_name) = ident(i)?; 446 | let (i, array_sizes) = many0(delimited(op('['), array_size, op(']')))(i)?; 447 | let (i, bit_count) = opt(preceded(op(':'), map_res(digit1, str::parse::)))(i)?; 448 | 449 | let array_size = array_sizes.split_first().map(|(&first, rest)| { 450 | rest.iter().fold(first, |acc, x| match (acc, x) { 451 | (CArraySize::Literal(a), CArraySize::Literal(b)) => CArraySize::Literal(a * b), 452 | _ => panic!("cannot fold array sizes"), 453 | }) 454 | }); 455 | 456 | Ok(( 457 | i, 458 | CVariableDecl { 459 | name: var_name, 460 | ty: CType { 461 | base, 462 | decoration: match (const0.is_some(), ptr0.is_some(), const1.is_some(), ptr1.is_some()) { 463 | (false, false, false, false) => CDecoration::None, 464 | (true, false, false, false) => CDecoration::Const, 465 | (false, true, false, false) => CDecoration::Pointer, 466 | (true, true, false, false) => CDecoration::PointerToConst, 467 | (false, true, false, true) => CDecoration::PointerToPointer, 468 | (true, true, true, true) => CDecoration::PointerToConstPointerToConst, 469 | v => panic!("unsupported decoration {:?}", v), 470 | }, 471 | array_size, 472 | bit_count, 473 | }, 474 | }, 475 | )) 476 | } 477 | 478 | pub fn c_parse_variable_decl(i: &str) -> CVariableDecl { 479 | all_consuming(terminated(variable_decl, multispace0))(i) 480 | .map(ignore_remainder) 481 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 482 | } 483 | 484 | fn function_decl(i: &str) -> Res { 485 | let (i, ret_base) = base_type(i)?; 486 | let (i, ret_ptr) = opt(op('*'))(i)?; 487 | let (i, func_name) = ident(i)?; 488 | let (i, parameters) = delimited( 489 | op('('), 490 | alt(( 491 | separated_list1(op(','), variable_decl), 492 | map(keyword("void"), |_| Vec::new()), 493 | )), 494 | tuple((op(')'), op(';'))), 495 | )(i)?; 496 | Ok(( 497 | i, 498 | CFunctionDecl { 499 | proto: CVariableDecl { 500 | name: func_name, 501 | ty: CType { 502 | base: ret_base, 503 | decoration: if ret_ptr.is_some() { 504 | CDecoration::Pointer 505 | } else { 506 | CDecoration::None 507 | }, 508 | array_size: None, 509 | bit_count: None, 510 | }, 511 | }, 512 | parameters, 513 | }, 514 | )) 515 | } 516 | 517 | pub fn c_parse_function_decl(i: &str) -> CFunctionDecl { 518 | all_consuming(terminated(function_decl, multispace0))(i) 519 | .map(ignore_remainder) 520 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 521 | } 522 | 523 | fn function_ptr_typedef(i: &str) -> Res<'_, CFunctionDecl> { 524 | let (i, ret_base) = preceded(keyword("typedef"), base_type)(i)?; 525 | let (i, ret_ptr) = opt(op('*'))(i)?; 526 | let (i, func_name) = delimited(tuple((op('('), keyword("VKAPI_PTR"), op('*'))), ident, op(')'))(i)?; 527 | let (i, parameters) = delimited( 528 | op('('), 529 | alt(( 530 | separated_list1(op(','), variable_decl), 531 | map(keyword("void"), |_| Vec::new()), 532 | )), 533 | tuple((op(')'), op(';'))), 534 | )(i)?; 535 | Ok(( 536 | i, 537 | CFunctionDecl { 538 | proto: CVariableDecl { 539 | name: func_name, 540 | ty: CType { 541 | base: ret_base, 542 | decoration: if ret_ptr.is_some() { 543 | CDecoration::Pointer 544 | } else { 545 | CDecoration::None 546 | }, 547 | array_size: None, 548 | bit_count: None, 549 | }, 550 | }, 551 | parameters, 552 | }, 553 | )) 554 | } 555 | 556 | pub fn c_parse_func_pointer_typedef(i: &str) -> CFunctionDecl { 557 | all_consuming(terminated(function_ptr_typedef, multispace0))(i) 558 | .map(ignore_remainder) 559 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 560 | } 561 | 562 | fn typedef(i: &str) -> Res { 563 | let (i, base) = preceded(keyword("typedef"), base_type)(i)?; 564 | let (i, ptr) = opt(op('*'))(i)?; 565 | let (i, var_name) = terminated(ident, op(';'))(i)?; 566 | Ok(( 567 | i, 568 | CVariableDecl { 569 | name: var_name, 570 | ty: CType { 571 | base, 572 | decoration: if ptr.is_some() { 573 | CDecoration::Pointer 574 | } else { 575 | CDecoration::None 576 | }, 577 | array_size: None, 578 | bit_count: None, 579 | }, 580 | }, 581 | )) 582 | } 583 | 584 | pub fn c_try_parse_typedef(i: &str) -> Option { 585 | all_consuming(terminated(typedef, multispace0))(i) 586 | .map(ignore_remainder) 587 | .ok() 588 | } 589 | 590 | fn constant_expr_inner(i: &str) -> Res { 591 | alt(( 592 | map(terminated(float, alt((char('f'), char('F')))), CConstant::Float), 593 | map( 594 | terminated(map_res(digit1, str::parse::), tag("ULL")), 595 | CConstant::UInt64, 596 | ), 597 | map( 598 | terminated(map_res(digit1, str::parse::), tag("U")), 599 | CConstant::UInt32, 600 | ), 601 | map(map_res(digit1, str::parse::), CConstant::UInt), 602 | delimited(char('('), constant_expr, char(')')), 603 | map(preceded(char('~'), constant_expr_inner), |e| match e { 604 | CConstant::UInt32(x) => CConstant::UInt32(!x), 605 | CConstant::UInt64(x) => CConstant::UInt64(!x), 606 | _ => panic!("cannot bitwise invert unsized literal"), 607 | }), 608 | ))(i) 609 | } 610 | 611 | fn constant_expr(i: &str) -> Res { 612 | alt(( 613 | map( 614 | separated_pair(constant_expr_inner, char('-'), constant_expr_inner), 615 | |(a, b)| match a { 616 | CConstant::UInt32(x) => match b { 617 | CConstant::UInt32(y) => CConstant::UInt32(x - y), 618 | CConstant::UInt(y) => CConstant::UInt32(x - y as u32), 619 | _ => panic!("bad rhs type in arithmetic"), 620 | }, 621 | _ => panic!("bad lhs type in arithmetic"), 622 | }, 623 | ), 624 | constant_expr_inner, 625 | ))(i) 626 | } 627 | 628 | pub fn c_parse_constant_expr(i: &str) -> CConstant { 629 | all_consuming(constant_expr)(i) 630 | .map(ignore_remainder) 631 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 632 | } 633 | 634 | #[derive(Debug)] 635 | pub enum CExpr<'a> { 636 | Bracket(Box), 637 | Mul(Box<(Self, Self)>), 638 | Div(Box<(Self, Self)>), 639 | Add(Box<(Self, Self)>), 640 | Literal(usize), 641 | Ident(&'a str), 642 | } 643 | 644 | impl<'a> CExpr<'a> { 645 | pub fn write_to(&self, w: &mut impl fmt::Write, f: impl Fn(&'a str) -> String + Copy) -> fmt::Result { 646 | match self { 647 | Self::Bracket(e) => { 648 | write!(w, "(")?; 649 | e.write_to(w, f)?; 650 | write!(w, ")") 651 | } 652 | Self::Mul(e) => { 653 | e.0.write_to(w, f)?; 654 | write!(w, " * ")?; 655 | e.1.write_to(w, f) 656 | } 657 | Self::Div(e) => { 658 | e.0.write_to(w, f)?; 659 | write!(w, " / ")?; 660 | e.1.write_to(w, f) 661 | } 662 | Self::Add(e) => { 663 | e.0.write_to(w, f)?; 664 | write!(w, " + ")?; 665 | e.1.write_to(w, f) 666 | } 667 | Self::Literal(n) => write!(w, "{}", n), 668 | Self::Ident(s) => write!(w, "{}", f(s)), 669 | } 670 | } 671 | } 672 | 673 | fn expr_inner(i: &str) -> Res> { 674 | preceded( 675 | multispace0, 676 | alt(( 677 | map(delimited(char('('), expr, char(')')), |expr| { 678 | CExpr::Bracket(Box::new(expr)) 679 | }), 680 | map(map_res(digit1, str::parse::), CExpr::Literal), 681 | map(take_while1(is_ident), CExpr::Ident), 682 | )), 683 | )(i) 684 | } 685 | 686 | fn expr(i: &str) -> Res> { 687 | alt(( 688 | map(separated_pair(expr_inner, op('+'), expr), |(a, b)| { 689 | CExpr::Add(Box::new((a, b))) 690 | }), 691 | map(separated_pair(expr_inner, op('*'), expr), |(a, b)| { 692 | CExpr::Mul(Box::new((a, b))) 693 | }), 694 | map(separated_pair(expr_inner, op('/'), expr), |(a, b)| { 695 | CExpr::Div(Box::new((a, b))) 696 | }), 697 | expr_inner, 698 | ))(i) 699 | } 700 | 701 | pub fn c_parse_expr(i: &str) -> CExpr<'_> { 702 | all_consuming(expr)(i) 703 | .map(ignore_remainder) 704 | .unwrap_or_else(|res| panic!("parse fail: {} -> {:?}", i, res)) 705 | } 706 | -------------------------------------------------------------------------------- /generator/src/lib_postfix.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use super::*; 6 | 7 | #[test] 8 | fn enumerate_instance_version() { 9 | let loader = Loader::new().unwrap(); 10 | let v = unsafe { loader.enumerate_instance_version() }; 11 | assert!(v.is_ok()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /generator/src/lib_prefix.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::too_many_arguments, clippy::trivially_copy_pass_by_ref, clippy::missing_safety_doc, clippy::unnecessary_cast)] 2 | 3 | pub mod vk; 4 | pub mod builder; 5 | 6 | use lazy_static::lazy_static; 7 | use std::{ 8 | ffi::CStr, 9 | os::raw::{c_void, c_int}, 10 | mem::{self, MaybeUninit}, 11 | path::Path, 12 | ptr, 13 | result, 14 | slice, 15 | }; 16 | use shared_library::dynamic_library::DynamicLibrary; 17 | 18 | #[doc(no_inline)] 19 | pub use self::builder::*; 20 | 21 | pub type Result = result::Result; 22 | 23 | struct Lib { 24 | _lib: DynamicLibrary, 25 | fp_get_instance_proc_addr: vk::FnGetInstanceProcAddr, 26 | } 27 | 28 | #[derive(Debug, Clone)] 29 | pub enum LoaderError { 30 | DynamicLibrary(String), 31 | MissingSymbol(String), 32 | Vulkan(vk::Result), 33 | } 34 | 35 | impl From for LoaderError { 36 | fn from(err: vk::Result) -> Self { 37 | LoaderError::Vulkan(err) 38 | } 39 | } 40 | 41 | pub type LoaderResult = result::Result; 42 | 43 | #[cfg(target_os = "linux")] 44 | const DL_PATH: &str = "libvulkan.so.1"; 45 | 46 | #[cfg(target_os = "windows")] 47 | const DL_PATH: &str = "vulkan-1.dll"; 48 | 49 | #[cfg(target_os = "android")] 50 | const DL_PATH: &str = "libvulkan.so"; 51 | 52 | impl Lib { 53 | pub fn new() -> LoaderResult { 54 | match DynamicLibrary::open(Some(Path::new(&DL_PATH))) { 55 | Ok(lib) => match unsafe { 56 | lib.symbol("vkGetInstanceProcAddr") 57 | .map(|f: *mut c_void| mem::transmute(f)) 58 | } { 59 | Ok(fp_get_instance_proc_addr) => Ok(Self { 60 | _lib: lib, 61 | fp_get_instance_proc_addr, 62 | }), 63 | Err(s) => Err(LoaderError::MissingSymbol(s)), 64 | }, 65 | Err(s) => Err(LoaderError::DynamicLibrary(s)), 66 | } 67 | } 68 | 69 | pub unsafe fn get_instance_proc_addr( 70 | &self, 71 | instance: Option, 72 | name: &CStr, 73 | ) -> Option { 74 | (self.fp_get_instance_proc_addr)(instance, name.as_ptr()) 75 | } 76 | } 77 | 78 | lazy_static! { 79 | static ref LIB: LoaderResult = Lib::new(); 80 | } 81 | 82 | struct VecMaybeUninit(Vec>); 83 | 84 | impl VecMaybeUninit { 85 | fn with_len(n: usize) -> Self { 86 | let mut v = Vec::with_capacity(n); 87 | unsafe { v.set_len(n); } 88 | Self(v) 89 | } 90 | 91 | fn as_mut_ptr(&mut self) -> *mut T { 92 | self.0.as_mut_ptr() as *mut T 93 | } 94 | 95 | unsafe fn assume_init(self) -> Vec { 96 | let s: Box<[T]> = mem::transmute(self.0.into_boxed_slice()); 97 | s.into_vec() 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /generator/src/vk_prefix.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::too_many_arguments, clippy::unreadable_literal)] 2 | 3 | use std::{ 4 | default::Default, 5 | os::raw::{c_void, c_char, c_int, c_ulong}, 6 | ptr, 7 | ffi::CStr, 8 | mem, 9 | num, 10 | fmt, 11 | ops, 12 | }; 13 | 14 | /// Wrapper around Vulkan API version number 15 | #[repr(transparent)] 16 | #[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] 17 | pub struct Version(u32); 18 | impl Version { 19 | /// Forms a version number from major, minor and patch numbers 20 | /// 21 | /// ``` 22 | /// # use spark::vk; 23 | /// let v = vk::Version::from_raw_parts(1, 2, 0); 24 | /// assert_eq!(v.to_raw(), (1 << 22) | (2 << 12)); 25 | /// ``` 26 | pub const fn from_raw_parts(major: u32, minor: u32, patch: u32) -> Self { 27 | Self((major << 22) | ((minor & 0x3ff) << 12) | (patch & 0xfff)) 28 | } 29 | 30 | pub const fn from_raw(version: u32) -> Self { 31 | Self(version) 32 | } 33 | pub fn to_raw(self) -> u32 { 34 | self.0 35 | } 36 | 37 | pub fn get_major(self) -> u32 { 38 | self.0 >> 22 39 | } 40 | pub fn get_minor(self) -> u32 { 41 | (self.0 >> 12) & 0x3ff 42 | } 43 | pub fn get_patch(self) -> u32 { 44 | self.0 & 0xfff 45 | } 46 | } 47 | impl Default for Version { 48 | fn default() -> Self { 49 | Self::from_raw_parts(1, 0, 0) 50 | } 51 | } 52 | impl fmt::Display for Version { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | write!(f, "{}.{}.{}", self.get_major(), self.get_minor(), self.get_patch()) 55 | } 56 | } 57 | 58 | // TODO: replace with ! type when stable 59 | #[doc(hidden)] 60 | pub enum Never {} 61 | 62 | // X11 63 | pub type Display = Never; 64 | pub type VisualID = c_ulong; 65 | pub type Window = c_ulong; 66 | pub type RROutput = c_ulong; 67 | 68 | // MIR 69 | pub type MirConnection = Never; 70 | pub type MirSurface = Never; 71 | 72 | // wayland 73 | #[allow(non_camel_case_types)] 74 | pub type wl_display = Never; 75 | #[allow(non_camel_case_types)] 76 | pub type wl_surface = Never; 77 | 78 | // windows 79 | pub type HINSTANCE = *mut c_void; 80 | pub type HWND = *mut c_void; 81 | pub type HANDLE = *mut c_void; 82 | pub type HMONITOR = *mut c_void; 83 | #[allow(non_camel_case_types)] 84 | pub type SECURITY_ATTRIBUTES = Never; 85 | pub type DWORD = c_ulong; 86 | pub type LPCWSTR = *const u16; 87 | 88 | #[allow(non_camel_case_types)] 89 | pub type xcb_connection_t = Never; 90 | #[allow(non_camel_case_types)] 91 | pub type xcb_window_t = u32; 92 | #[allow(non_camel_case_types)] 93 | pub type xcb_visualid_t = Never; 94 | 95 | pub type IDirectFB = Never; 96 | pub type IDirectFBSurface = Never; 97 | 98 | // Android 99 | pub type ANativeWindow = Never; 100 | pub type AHardwareBuffer = Never; 101 | 102 | // Metal 103 | pub type CAMetalLayer = Never; 104 | #[allow(non_camel_case_types)] 105 | pub type MTLDevice_id = *mut c_void; 106 | #[allow(non_camel_case_types)] 107 | pub type MTLCommandQueue_id = *mut c_void; 108 | #[allow(non_camel_case_types)] 109 | pub type MTLBuffer_id = *mut c_void; 110 | #[allow(non_camel_case_types)] 111 | pub type MTLTexture_id = *mut c_void; 112 | #[allow(non_camel_case_types)] 113 | pub type MTLSharedEvent_id = *mut c_void; 114 | #[allow(non_camel_case_types)] 115 | pub type __IOSurface = Never; 116 | pub type IOSurfaceRef = *mut __IOSurface; 117 | 118 | // Zircon 119 | #[allow(non_camel_case_types)] 120 | pub type zx_handle_t = u32; 121 | 122 | fn display_bitmask(bits: u64, bit_names: &[(u64, &str)], f: &mut fmt::Formatter) -> fmt::Result { 123 | let mut has_output = false; 124 | let mut remain = bits; 125 | for (bit, name) in bit_names.iter().copied() { 126 | if (remain & bit) == bit { 127 | if has_output { 128 | f.write_str(" | ")?; 129 | } 130 | write!(f, "{}", name)?; 131 | has_output = true; 132 | remain &= !bit; 133 | } 134 | } 135 | if remain != 0 { 136 | if has_output { 137 | f.write_str(" | ")?; 138 | } 139 | write!(f, "{:#x}", remain)?; 140 | has_output = true; 141 | } 142 | if !has_output { 143 | f.write_str("0")?; 144 | } 145 | Ok(()) 146 | } 147 | 148 | macro_rules! impl_bitmask { 149 | ($name:ident, $all_bits:literal) => { 150 | impl $name { 151 | pub fn empty() -> Self { 152 | Self(0) 153 | } 154 | pub fn all() -> Self { 155 | Self($all_bits) 156 | } 157 | pub fn is_empty(self) -> bool { 158 | self.0 == 0 159 | } 160 | pub fn is_all(self) -> bool { 161 | self.0 == $all_bits 162 | } 163 | pub fn intersects(self, other: Self) -> bool { 164 | (self.0 & other.0) != 0 165 | } 166 | pub fn contains(self, other: Self) -> bool { 167 | (self.0 & other.0) == other.0 168 | } 169 | } 170 | impl ops::BitOr for $name { 171 | type Output = Self; 172 | fn bitor(self, rhs: Self) -> Self { 173 | Self(self.0 | rhs.0) 174 | } 175 | } 176 | impl ops::BitOrAssign for $name { 177 | fn bitor_assign(&mut self, rhs: Self) { 178 | self.0 |= rhs.0; 179 | } 180 | } 181 | impl ops::BitAnd for $name { 182 | type Output = Self; 183 | fn bitand(self, rhs: Self) -> Self { 184 | Self(self.0 & rhs.0) 185 | } 186 | } 187 | impl ops::BitAndAssign for $name { 188 | fn bitand_assign(&mut self, rhs: Self) { 189 | self.0 &= rhs.0; 190 | } 191 | } 192 | impl ops::BitXor for $name { 193 | type Output = Self; 194 | fn bitxor(self, rhs: Self) -> Self { 195 | Self(self.0 ^ rhs.0) 196 | } 197 | } 198 | impl ops::BitXorAssign for $name { 199 | fn bitxor_assign(&mut self, rhs: Self) { 200 | self.0 ^= rhs.0; 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | newline_style = "Native" 3 | -------------------------------------------------------------------------------- /spark-egui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spark-egui" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | spark = { path = "../spark" } 10 | egui = { version = "0.23", features = [ "bytemuck" ] } 11 | memoffset = "0.9" 12 | bytemuck = "1.14.0" 13 | 14 | -------------------------------------------------------------------------------- /spark-egui/Makefile: -------------------------------------------------------------------------------- 1 | GLSLC=glslangValidator 2 | GLSLCFLAGS=-V 3 | 4 | SHD=src/egui.vert.spv \ 5 | src/egui.frag.spv 6 | 7 | all: shaders 8 | .PHONY: all clean shaders 9 | 10 | clean: 11 | $(RM) $(SHD) 12 | 13 | shaders: $(SHD) 14 | 15 | %.spv: % Makefile 16 | $(GLSLC) $(GLSLCFLAGS) -o $@ $< 17 | 18 | -------------------------------------------------------------------------------- /spark-egui/src/egui.frag: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(location = 0) in vec2 v_uv; 4 | layout(location = 1) in vec4 v_col; 5 | 6 | layout(set = 0, binding = 0) uniform sampler2D g_tex; 7 | 8 | layout(location = 0) out vec4 o_col; 9 | 10 | float linear_from_gamma(float x) 11 | { 12 | const float lo = x/12.92f; 13 | const float hi = pow((x + 0.055f)/1.055f, 2.4f); 14 | return (x < 0.04045f) ? lo : hi; 15 | } 16 | vec4 linear_from_gamma(vec4 c) 17 | { 18 | return vec4( 19 | linear_from_gamma(c.x), 20 | linear_from_gamma(c.y), 21 | linear_from_gamma(c.z), 22 | c.w); 23 | } 24 | 25 | float gamma_from_linear(float x) 26 | { 27 | const float lo = x*12.92f; 28 | const float hi = 1.055f*pow(x, 1.f/2.4f) - 0.055f; 29 | return (x < 0.0031308f) ? lo : hi; 30 | } 31 | vec4 gamma_from_linear(vec4 c) 32 | { 33 | return vec4( 34 | gamma_from_linear(c.x), 35 | gamma_from_linear(c.y), 36 | gamma_from_linear(c.z), 37 | c.w); 38 | } 39 | 40 | void main() { 41 | o_col = v_col*gamma_from_linear(texture(g_tex, v_uv)); 42 | } 43 | -------------------------------------------------------------------------------- /spark-egui/src/egui.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/spark-egui/src/egui.frag.spv -------------------------------------------------------------------------------- /spark-egui/src/egui.vert: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(location = 0) in vec2 a_pos; 4 | layout(location = 1) in vec2 a_uv; 5 | layout(location = 2) in vec4 a_col; 6 | 7 | layout(push_constant) uniform BatchData { 8 | vec2 size_in_points_rcp; 9 | } g_batch; 10 | 11 | out gl_PerVertex { 12 | vec4 gl_Position; 13 | }; 14 | 15 | layout(location = 0) out vec2 v_uv; 16 | layout(location = 1) out vec4 v_col; 17 | 18 | void main() { 19 | gl_Position = vec4(a_pos*g_batch.size_in_points_rcp*2.0 - 1.0, 0, 1); 20 | v_uv = a_uv; 21 | v_col = a_col; 22 | } 23 | -------------------------------------------------------------------------------- /spark-egui/src/egui.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/spark-egui/src/egui.vert.spv -------------------------------------------------------------------------------- /spark-egui/src/lib.rs: -------------------------------------------------------------------------------- 1 | use egui::{ 2 | ahash::HashMap, 3 | emath, 4 | epaint::{ClippedPrimitive, ImageDelta, Primitive, Vertex}, 5 | ImageData, TextureFilter, TextureId, TextureOptions, TexturesDelta, 6 | }; 7 | use memoffset::offset_of; 8 | use spark::{vk, Builder, Device}; 9 | use std::{borrow::Cow, collections::VecDeque, ffi::CStr, mem, ops::Range, os::raw::c_void, slice}; 10 | 11 | type Index = u32; 12 | 13 | fn load_shader_module(device: &Device, bytes: &[u8]) -> vk::ShaderModule { 14 | let shader_module_create_info = vk::ShaderModuleCreateInfo { 15 | code_size: bytes.len(), 16 | p_code: bytes.as_ptr() as *const u32, 17 | ..Default::default() 18 | }; 19 | unsafe { device.create_shader_module(&shader_module_create_info, None) }.unwrap() 20 | } 21 | 22 | fn get_memory_type_index( 23 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 24 | type_filter: u32, 25 | property_flags: vk::MemoryPropertyFlags, 26 | ) -> Option { 27 | for i in 0..physical_device_memory_properties.memory_type_count { 28 | let mt = &physical_device_memory_properties.memory_types[i as usize]; 29 | if (type_filter & (1 << i)) != 0 && mt.property_flags.contains(property_flags) { 30 | return Some(i); 31 | } 32 | } 33 | None 34 | } 35 | 36 | fn align_up(x: u32, alignment: u32) -> u32 { 37 | (x + alignment - 1) & !(alignment - 1) 38 | } 39 | 40 | trait TakePrefix { 41 | type Output; 42 | fn take_prefix(&mut self, len: usize) -> Option; 43 | } 44 | 45 | impl TakePrefix for Range { 46 | type Output = Self; 47 | fn take_prefix(&mut self, len: usize) -> Option { 48 | let next = self.start + len; 49 | if next <= self.end { 50 | let prefix = self.start..next; 51 | self.start = next; 52 | Some(prefix) 53 | } else { 54 | None 55 | } 56 | } 57 | } 58 | 59 | #[repr(C)] 60 | struct BatchData { 61 | size_in_points_rcp: (f32, f32), 62 | } 63 | 64 | struct ClippedDraw { 65 | clip_rect: emath::Rect, 66 | texture_id: TextureId, 67 | first_index: u32, 68 | index_count: u32, 69 | vertex_offset: i32, 70 | } 71 | 72 | struct Layouts { 73 | descriptor_set_layout: vk::DescriptorSetLayout, 74 | pipeline_layout: vk::PipelineLayout, 75 | vertex_shader: vk::ShaderModule, 76 | fragment_shader: vk::ShaderModule, 77 | } 78 | 79 | struct Staging { 80 | size: usize, 81 | atom_size: u32, 82 | mem: vk::DeviceMemory, 83 | mapping: *mut c_void, 84 | buffer: vk::Buffer, 85 | next_subset_index: u8, 86 | } 87 | 88 | struct MeshBuffers { 89 | vertex_buffer_size: usize, 90 | index_buffer_size: usize, 91 | mem: vk::DeviceMemory, 92 | vertex_buffer: vk::Buffer, 93 | index_buffer: vk::Buffer, 94 | clipped_draws: Vec, 95 | } 96 | 97 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 98 | enum ImageState { 99 | TransferDest, 100 | ShaderRead, 101 | } 102 | 103 | struct Texture { 104 | mem: vk::DeviceMemory, 105 | image: vk::Image, 106 | image_view: vk::ImageView, 107 | sampler: vk::Sampler, 108 | descriptor_pool: vk::DescriptorPool, 109 | descriptor_set: vk::DescriptorSet, 110 | image_state: ImageState, 111 | } 112 | 113 | #[derive(Default)] 114 | struct TextureSet { 115 | active: HashMap, 116 | pending_updates: VecDeque<(TextureId, ImageDelta)>, 117 | pending_frees: Vec, 118 | pending_deletes: Vec, 119 | } 120 | 121 | /* 122 | Notes: 123 | Single allocation for vertex and index buffer 124 | Single allocation per texture 125 | N buffer some staging memory, copy to VB/IB/tex as needed 126 | */ 127 | pub struct Renderer { 128 | layouts: Layouts, 129 | staging: Staging, 130 | mesh_buffers: MeshBuffers, 131 | texture_set: TextureSet, 132 | } 133 | 134 | impl Layouts { 135 | pub fn new(device: &Device) -> Self { 136 | let descriptor_set_layout = { 137 | let binding = vk::DescriptorSetLayoutBinding::builder() 138 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 139 | .descriptor_count(1) 140 | .stage_flags(vk::ShaderStageFlags::FRAGMENT); 141 | let descriptor_set_layout_create_info = 142 | vk::DescriptorSetLayoutCreateInfo::builder().p_bindings(slice::from_ref(&binding)); 143 | unsafe { device.create_descriptor_set_layout(&descriptor_set_layout_create_info, None) }.unwrap() 144 | }; 145 | 146 | let pipeline_layout = { 147 | let push_constant_range = vk::PushConstantRange { 148 | stage_flags: vk::ShaderStageFlags::VERTEX, 149 | offset: 0, 150 | size: mem::size_of::() as u32, 151 | }; 152 | let pipeline_layout_create_info = vk::PipelineLayoutCreateInfo::builder() 153 | .p_set_layouts(slice::from_ref(&descriptor_set_layout)) 154 | .p_push_constant_ranges(slice::from_ref(&push_constant_range)); 155 | unsafe { device.create_pipeline_layout(&pipeline_layout_create_info, None) }.unwrap() 156 | }; 157 | 158 | let vertex_shader = load_shader_module(device, include_bytes!("egui.vert.spv")); 159 | let fragment_shader = load_shader_module(device, include_bytes!("egui.frag.spv")); 160 | 161 | Self { 162 | descriptor_set_layout, 163 | pipeline_layout, 164 | vertex_shader, 165 | fragment_shader, 166 | } 167 | } 168 | 169 | pub fn destroy(&self, device: &Device) { 170 | unsafe { 171 | device.destroy_shader_module(Some(self.fragment_shader), None); 172 | device.destroy_shader_module(Some(self.vertex_shader), None); 173 | device.destroy_pipeline_layout(Some(self.pipeline_layout), None); 174 | device.destroy_descriptor_set_layout(Some(self.descriptor_set_layout), None); 175 | } 176 | } 177 | 178 | pub fn create_pipeline( 179 | &self, 180 | device: &Device, 181 | render_pass: vk::RenderPass, 182 | samples: vk::SampleCountFlags, 183 | ) -> vk::Pipeline { 184 | let shader_entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap(); 185 | let shader_stage_create_info = [ 186 | vk::PipelineShaderStageCreateInfo { 187 | stage: vk::ShaderStageFlags::VERTEX, 188 | module: Some(self.vertex_shader), 189 | p_name: shader_entry_name.as_ptr(), 190 | ..Default::default() 191 | }, 192 | vk::PipelineShaderStageCreateInfo { 193 | stage: vk::ShaderStageFlags::FRAGMENT, 194 | module: Some(self.fragment_shader), 195 | p_name: shader_entry_name.as_ptr(), 196 | ..Default::default() 197 | }, 198 | ]; 199 | 200 | let vertex_input_binding = vk::VertexInputBindingDescription { 201 | binding: 0, 202 | stride: mem::size_of::() as u32, 203 | input_rate: vk::VertexInputRate::VERTEX, 204 | }; 205 | let vertex_input_attributes = [ 206 | vk::VertexInputAttributeDescription { 207 | location: 0, 208 | binding: 0, 209 | format: vk::Format::R32G32_SFLOAT, 210 | offset: offset_of!(Vertex, pos) as u32, 211 | }, 212 | vk::VertexInputAttributeDescription { 213 | location: 1, 214 | binding: 0, 215 | format: vk::Format::R32G32_SFLOAT, 216 | offset: offset_of!(Vertex, uv) as u32, 217 | }, 218 | vk::VertexInputAttributeDescription { 219 | location: 2, 220 | binding: 0, 221 | format: vk::Format::R8G8B8A8_UNORM, 222 | offset: offset_of!(Vertex, color) as u32, 223 | }, 224 | ]; 225 | 226 | let vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo::builder() 227 | .p_vertex_binding_descriptions(slice::from_ref(&vertex_input_binding)) 228 | .p_vertex_attribute_descriptions(&vertex_input_attributes); 229 | 230 | let input_assembly_state_create_info = vk::PipelineInputAssemblyStateCreateInfo { 231 | topology: vk::PrimitiveTopology::TRIANGLE_LIST, 232 | ..Default::default() 233 | }; 234 | 235 | let viewport_state_create_info = vk::PipelineViewportStateCreateInfo { 236 | viewport_count: 1, 237 | scissor_count: 1, 238 | ..Default::default() 239 | }; 240 | 241 | let rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo { 242 | polygon_mode: vk::PolygonMode::FILL, 243 | cull_mode: vk::CullModeFlags::NONE, 244 | front_face: vk::FrontFace::CLOCKWISE, 245 | line_width: 1.0, 246 | ..Default::default() 247 | }; 248 | let multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo { 249 | rasterization_samples: samples, 250 | ..Default::default() 251 | }; 252 | 253 | let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo { ..Default::default() }; 254 | 255 | let color_blend_attachment_state = vk::PipelineColorBlendAttachmentState { 256 | blend_enable: vk::TRUE, 257 | src_color_blend_factor: vk::BlendFactor::ONE, 258 | dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA, 259 | color_blend_op: vk::BlendOp::ADD, 260 | src_alpha_blend_factor: vk::BlendFactor::ONE_MINUS_DST_ALPHA, 261 | dst_alpha_blend_factor: vk::BlendFactor::ONE, 262 | alpha_blend_op: vk::BlendOp::ADD, 263 | color_write_mask: vk::ColorComponentFlags::all(), 264 | }; 265 | let color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo::builder() 266 | .p_attachments(slice::from_ref(&color_blend_attachment_state)); 267 | 268 | let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; 269 | let pipeline_dynamic_state_create_info = 270 | vk::PipelineDynamicStateCreateInfo::builder().p_dynamic_states(&dynamic_states); 271 | 272 | let pipeline_create_info = vk::GraphicsPipelineCreateInfo::builder() 273 | .p_stages(&shader_stage_create_info) 274 | .p_vertex_input_state(Some(&vertex_input_state_create_info)) 275 | .p_input_assembly_state(Some(&input_assembly_state_create_info)) 276 | .p_viewport_state(Some(&viewport_state_create_info)) 277 | .p_rasterization_state(Some(&rasterization_state_create_info)) 278 | .p_multisample_state(Some(&multisample_state_create_info)) 279 | .p_depth_stencil_state(Some(&depth_stencil_state)) 280 | .p_color_blend_state(Some(&color_blend_state_create_info)) 281 | .p_dynamic_state(Some(&pipeline_dynamic_state_create_info)) 282 | .layout(Some(self.pipeline_layout)) 283 | .render_pass(Some(render_pass)); 284 | 285 | unsafe { device.create_graphics_pipelines_single(None, &pipeline_create_info, None) }.unwrap() 286 | } 287 | } 288 | 289 | impl Staging { 290 | pub fn new( 291 | device: &Device, 292 | physical_device_properties: &vk::PhysicalDeviceProperties, 293 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 294 | size_per_subset: usize, 295 | ) -> Self { 296 | let atom_size = physical_device_properties.limits.non_coherent_atom_size as u32; 297 | 298 | let size = 2 * size_per_subset; 299 | 300 | let buffer = { 301 | let buffer_create_info = vk::BufferCreateInfo { 302 | size: size as vk::DeviceSize, 303 | usage: vk::BufferUsageFlags::TRANSFER_SRC, 304 | ..Default::default() 305 | }; 306 | unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap() 307 | }; 308 | 309 | let mem = { 310 | let staging_mem_req = unsafe { device.get_buffer_memory_requirements(buffer) }; 311 | let allocation_size = staging_mem_req.size; 312 | let memory_type_index = get_memory_type_index( 313 | physical_device_memory_properties, 314 | staging_mem_req.memory_type_bits, 315 | vk::MemoryPropertyFlags::HOST_VISIBLE, 316 | ) 317 | .unwrap(); 318 | let memory_allocate_info = vk::MemoryAllocateInfo { 319 | allocation_size, 320 | memory_type_index, 321 | ..Default::default() 322 | }; 323 | unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap() 324 | }; 325 | 326 | unsafe { device.bind_buffer_memory(buffer, mem, 0) }.unwrap(); 327 | 328 | let mapping = unsafe { device.map_memory(mem, 0, vk::WHOLE_SIZE, Default::default()) }.unwrap(); 329 | 330 | Self { 331 | size, 332 | atom_size, 333 | mem, 334 | mapping, 335 | buffer, 336 | next_subset_index: 0, 337 | } 338 | } 339 | 340 | pub fn destroy(&self, device: &Device) { 341 | unsafe { 342 | device.unmap_memory(self.mem); 343 | device.destroy_buffer(Some(self.buffer), None); 344 | device.free_memory(Some(self.mem), None); 345 | } 346 | } 347 | 348 | pub fn next_subset(&mut self) -> Range { 349 | let size = self.size / 2; 350 | let start = if (self.next_subset_index & 1) != 0 { size } else { 0 }; 351 | self.next_subset_index ^= 1; 352 | start..(start + size) 353 | } 354 | 355 | pub fn bytes_mut(&mut self) -> &mut [u8] { 356 | unsafe { slice::from_raw_parts_mut(self.mapping as *mut _, self.size) } 357 | } 358 | 359 | pub fn flush_mapped_range(&self, device: &Device, range: Range) { 360 | if !range.is_empty() { 361 | let mapped_range = vk::MappedMemoryRange { 362 | memory: Some(self.mem), 363 | offset: range.start as vk::DeviceSize, 364 | size: align_up(range.len() as u32, self.atom_size) as vk::DeviceSize, 365 | ..Default::default() 366 | }; 367 | unsafe { device.flush_mapped_memory_ranges(slice::from_ref(&mapped_range)) }.unwrap(); 368 | } 369 | } 370 | } 371 | 372 | impl MeshBuffers { 373 | pub fn new( 374 | device: &Device, 375 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 376 | vertex_buffer_size: usize, 377 | index_buffer_size: usize, 378 | ) -> Self { 379 | let vertex_buffer = { 380 | let buffer_create_info = vk::BufferCreateInfo { 381 | size: vertex_buffer_size as vk::DeviceSize, 382 | usage: vk::BufferUsageFlags::VERTEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, 383 | ..Default::default() 384 | }; 385 | unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap() 386 | }; 387 | let index_buffer = { 388 | let buffer_create_info = vk::BufferCreateInfo { 389 | size: index_buffer_size as vk::DeviceSize, 390 | usage: vk::BufferUsageFlags::INDEX_BUFFER | vk::BufferUsageFlags::TRANSFER_DST, 391 | ..Default::default() 392 | }; 393 | unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap() 394 | }; 395 | 396 | let mem = { 397 | let vertex_mem_req = unsafe { device.get_buffer_memory_requirements(vertex_buffer) }; 398 | let index_mem_req = unsafe { device.get_buffer_memory_requirements(index_buffer) }; 399 | let allocation_size = vertex_mem_req.size + index_mem_req.size; 400 | let memory_type_index = get_memory_type_index( 401 | physical_device_memory_properties, 402 | vertex_mem_req.memory_type_bits & index_mem_req.memory_type_bits, 403 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 404 | ) 405 | .unwrap(); 406 | let memory_allocate_info = vk::MemoryAllocateInfo { 407 | allocation_size, 408 | memory_type_index, 409 | ..Default::default() 410 | }; 411 | let mem = unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap(); 412 | unsafe { device.bind_buffer_memory(vertex_buffer, mem, 0) }.unwrap(); 413 | unsafe { device.bind_buffer_memory(index_buffer, mem, vertex_mem_req.size) }.unwrap(); 414 | mem 415 | }; 416 | 417 | Self { 418 | vertex_buffer_size, 419 | index_buffer_size, 420 | mem, 421 | vertex_buffer, 422 | index_buffer, 423 | clipped_draws: Vec::new(), 424 | } 425 | } 426 | 427 | pub fn destroy(&self, device: &Device) { 428 | unsafe { 429 | device.destroy_buffer(Some(self.index_buffer), None); 430 | device.destroy_buffer(Some(self.vertex_buffer), None); 431 | device.free_memory(Some(self.mem), None); 432 | } 433 | } 434 | 435 | pub fn update( 436 | &mut self, 437 | device: &Device, 438 | command_buffer: vk::CommandBuffer, 439 | clipped_primitives: Vec, 440 | staging: &mut Staging, 441 | staging_remain: &mut Range, 442 | ) { 443 | let (vertex_count, index_count) = clipped_primitives 444 | .iter() 445 | .filter_map(|clipped_primitive| match &clipped_primitive.primitive { 446 | Primitive::Mesh(mesh) => Some(mesh), 447 | Primitive::Callback(_) => None, 448 | }) 449 | .fold((0, 0), |acc, mesh| { 450 | (acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len()) 451 | }); 452 | let vertex_write_size = vertex_count * mem::size_of::(); 453 | let index_write_size = index_count * mem::size_of::(); 454 | 455 | self.clipped_draws.clear(); 456 | if vertex_write_size > self.vertex_buffer_size { 457 | eprintln!("out of space trying to write {vertex_write_size} bytes of vertex data"); 458 | } else if index_write_size > self.index_buffer_size { 459 | eprintln!("out of space trying to write {index_write_size} bytes of index data"); 460 | } else if vertex_write_size > 0 && index_write_size > 0 { 461 | let mut vertex_range = staging_remain.take_prefix(vertex_write_size).unwrap(); 462 | let mut index_range = staging_remain.take_prefix(index_write_size).unwrap(); 463 | let vertex_start = vertex_range.start; 464 | let index_start = index_range.start; 465 | let mut vertex_offset = 0; 466 | let mut first_index = 0; 467 | let staging_bytes = staging.bytes_mut(); 468 | for clipped_primitive in &clipped_primitives { 469 | match &clipped_primitive.primitive { 470 | Primitive::Mesh(mesh) => { 471 | let src_vertex_bytes: &[u8] = bytemuck::cast_slice(&mesh.vertices); 472 | let dst_vertex_range = vertex_range.take_prefix(src_vertex_bytes.len()).unwrap(); 473 | staging_bytes[dst_vertex_range].copy_from_slice(src_vertex_bytes); 474 | 475 | let src_index_bytes: &[u8] = bytemuck::cast_slice(&mesh.indices); 476 | let dst_index_range = index_range.take_prefix(src_index_bytes.len()).unwrap(); 477 | staging_bytes[dst_index_range].copy_from_slice(src_index_bytes); 478 | 479 | let index_count = mesh.indices.len() as u32; 480 | self.clipped_draws.push(ClippedDraw { 481 | clip_rect: clipped_primitive.clip_rect, 482 | texture_id: mesh.texture_id, 483 | first_index, 484 | index_count, 485 | vertex_offset, 486 | }); 487 | vertex_offset += mesh.vertices.len() as i32; 488 | first_index += index_count; 489 | } 490 | Primitive::Callback(_) => {} 491 | } 492 | } 493 | assert!(vertex_range.is_empty()); 494 | assert!(index_range.is_empty()); 495 | 496 | let vertex_buffer_copy = vk::BufferCopy { 497 | src_offset: vertex_start as vk::DeviceSize, 498 | dst_offset: 0, 499 | size: vertex_write_size as vk::DeviceSize, 500 | }; 501 | let index_buffer_copy = vk::BufferCopy { 502 | src_offset: index_start as vk::DeviceSize, 503 | dst_offset: 0, 504 | size: index_write_size as vk::DeviceSize, 505 | }; 506 | unsafe { 507 | device.cmd_copy_buffer( 508 | command_buffer, 509 | staging.buffer, 510 | self.vertex_buffer, 511 | slice::from_ref(&vertex_buffer_copy), 512 | ); 513 | device.cmd_copy_buffer( 514 | command_buffer, 515 | staging.buffer, 516 | self.index_buffer, 517 | slice::from_ref(&index_buffer_copy), 518 | ); 519 | } 520 | 521 | let buffer_memory_barriers = [ 522 | vk::BufferMemoryBarrier { 523 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 524 | dst_access_mask: vk::AccessFlags::VERTEX_ATTRIBUTE_READ, 525 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 526 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 527 | buffer: Some(self.vertex_buffer), 528 | size: vertex_write_size as vk::DeviceSize, 529 | ..Default::default() 530 | }, 531 | vk::BufferMemoryBarrier { 532 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 533 | dst_access_mask: vk::AccessFlags::INDEX_READ, 534 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 535 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 536 | buffer: Some(self.index_buffer), 537 | size: index_write_size as vk::DeviceSize, 538 | ..Default::default() 539 | }, 540 | ]; 541 | unsafe { 542 | device.cmd_pipeline_barrier( 543 | command_buffer, 544 | vk::PipelineStageFlags::TRANSFER, 545 | vk::PipelineStageFlags::VERTEX_INPUT, 546 | vk::DependencyFlags::empty(), 547 | &[], 548 | &buffer_memory_barriers, 549 | &[], 550 | ) 551 | }; 552 | } 553 | } 554 | } 555 | 556 | impl Texture { 557 | pub fn new( 558 | device: &Device, 559 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 560 | command_buffer: vk::CommandBuffer, 561 | width: u32, 562 | height: u32, 563 | options: TextureOptions, 564 | layouts: &Layouts, 565 | ) -> Self { 566 | let image = { 567 | let image_create_info = vk::ImageCreateInfo { 568 | image_type: vk::ImageType::N2D, 569 | format: vk::Format::R8G8B8A8_SRGB, 570 | extent: vk::Extent3D { 571 | width, 572 | height, 573 | depth: 1, 574 | }, 575 | mip_levels: 1, 576 | array_layers: 1, 577 | samples: vk::SampleCountFlags::N1, 578 | usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST, 579 | ..Default::default() 580 | }; 581 | unsafe { device.create_image(&image_create_info, None) }.unwrap() 582 | }; 583 | 584 | let mem = { 585 | let mem_req = unsafe { device.get_image_memory_requirements(image) }; 586 | 587 | let memory_type_index = get_memory_type_index( 588 | physical_device_memory_properties, 589 | mem_req.memory_type_bits, 590 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 591 | ) 592 | .unwrap(); 593 | let memory_allocate_info = vk::MemoryAllocateInfo { 594 | allocation_size: mem_req.size, 595 | memory_type_index, 596 | ..Default::default() 597 | }; 598 | let mem = unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap(); 599 | unsafe { device.bind_image_memory(image, mem, 0) }.unwrap(); 600 | mem 601 | }; 602 | 603 | let descriptor_pool = { 604 | let descriptor_pool_sizes = [vk::DescriptorPoolSize { 605 | ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 606 | descriptor_count: 1, 607 | }]; 608 | let descriptor_pool_create_info = vk::DescriptorPoolCreateInfo::builder() 609 | .max_sets(1) 610 | .p_pool_sizes(&descriptor_pool_sizes); 611 | unsafe { device.create_descriptor_pool(&descriptor_pool_create_info, None) }.unwrap() 612 | }; 613 | 614 | let descriptor_set = { 615 | let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder() 616 | .descriptor_pool(descriptor_pool) 617 | .p_set_layouts(slice::from_ref(&layouts.descriptor_set_layout)); 618 | unsafe { device.allocate_descriptor_sets_single(&descriptor_set_allocate_info) }.unwrap() 619 | }; 620 | 621 | let image_view = { 622 | let image_view_create_info = vk::ImageViewCreateInfo { 623 | image: Some(image), 624 | view_type: vk::ImageViewType::N2D, 625 | format: vk::Format::R8G8B8A8_SRGB, 626 | subresource_range: vk::ImageSubresourceRange { 627 | aspect_mask: vk::ImageAspectFlags::COLOR, 628 | level_count: 1, 629 | layer_count: 1, 630 | ..Default::default() 631 | }, 632 | components: vk::ComponentMapping { 633 | r: vk::ComponentSwizzle::R, 634 | g: vk::ComponentSwizzle::G, 635 | b: vk::ComponentSwizzle::B, 636 | a: vk::ComponentSwizzle::A, 637 | }, 638 | ..Default::default() 639 | }; 640 | unsafe { device.create_image_view(&image_view_create_info, None) }.unwrap() 641 | }; 642 | 643 | let sampler = { 644 | let map_filter = |filter: TextureFilter| match filter { 645 | TextureFilter::Nearest => vk::Filter::NEAREST, 646 | TextureFilter::Linear => vk::Filter::LINEAR, 647 | }; 648 | let sampler_create_info = vk::SamplerCreateInfo { 649 | mag_filter: map_filter(options.magnification), 650 | min_filter: map_filter(options.minification), 651 | address_mode_u: vk::SamplerAddressMode::CLAMP_TO_EDGE, 652 | address_mode_v: vk::SamplerAddressMode::CLAMP_TO_EDGE, 653 | ..Default::default() 654 | }; 655 | unsafe { device.create_sampler(&sampler_create_info, None) }.unwrap() 656 | }; 657 | 658 | { 659 | let image_info = vk::DescriptorImageInfo { 660 | sampler: Some(sampler), 661 | image_view: Some(image_view), 662 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 663 | }; 664 | let write_descriptor_set = vk::WriteDescriptorSet::builder() 665 | .dst_set(descriptor_set) 666 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 667 | .p_image_info(slice::from_ref(&image_info)); 668 | unsafe { device.update_descriptor_sets(slice::from_ref(&write_descriptor_set), &[]) }; 669 | } 670 | 671 | let image_memory_barrier = vk::ImageMemoryBarrier { 672 | dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, 673 | new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 674 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 675 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 676 | image: Some(image), 677 | subresource_range: vk::ImageSubresourceRange { 678 | aspect_mask: vk::ImageAspectFlags::COLOR, 679 | level_count: 1, 680 | layer_count: 1, 681 | ..Default::default() 682 | }, 683 | ..Default::default() 684 | }; 685 | unsafe { 686 | device.cmd_pipeline_barrier( 687 | command_buffer, 688 | vk::PipelineStageFlags::HOST, 689 | vk::PipelineStageFlags::TRANSFER, 690 | vk::DependencyFlags::empty(), 691 | &[], 692 | &[], 693 | slice::from_ref(&image_memory_barrier), 694 | ) 695 | }; 696 | 697 | Self { 698 | mem, 699 | image, 700 | image_view, 701 | sampler, 702 | descriptor_pool, 703 | descriptor_set, 704 | image_state: ImageState::TransferDest, 705 | } 706 | } 707 | 708 | pub fn destroy(&self, device: &Device) { 709 | unsafe { 710 | device.destroy_descriptor_pool(Some(self.descriptor_pool), None); 711 | device.destroy_sampler(Some(self.sampler), None); 712 | device.destroy_image_view(Some(self.image_view), None); 713 | device.destroy_image(Some(self.image), None); 714 | device.free_memory(Some(self.mem), None); 715 | } 716 | } 717 | 718 | pub fn make_transfer_dest(&mut self, device: &Device, command_buffer: vk::CommandBuffer) { 719 | if self.image_state != ImageState::TransferDest { 720 | let image_memory_barrier = vk::ImageMemoryBarrier { 721 | src_access_mask: vk::AccessFlags::SHADER_READ, 722 | dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, 723 | old_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 724 | new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 725 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 726 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 727 | image: Some(self.image), 728 | subresource_range: vk::ImageSubresourceRange { 729 | aspect_mask: vk::ImageAspectFlags::COLOR, 730 | level_count: 1, 731 | layer_count: 1, 732 | ..Default::default() 733 | }, 734 | ..Default::default() 735 | }; 736 | unsafe { 737 | device.cmd_pipeline_barrier( 738 | command_buffer, 739 | vk::PipelineStageFlags::FRAGMENT_SHADER, 740 | vk::PipelineStageFlags::TRANSFER, 741 | vk::DependencyFlags::empty(), 742 | &[], 743 | &[], 744 | slice::from_ref(&image_memory_barrier), 745 | ) 746 | }; 747 | self.image_state = ImageState::TransferDest; 748 | } 749 | } 750 | 751 | pub fn make_shader_read(&mut self, device: &Device, command_buffer: vk::CommandBuffer) { 752 | if self.image_state != ImageState::ShaderRead { 753 | let image_memory_barrier = vk::ImageMemoryBarrier { 754 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 755 | dst_access_mask: vk::AccessFlags::SHADER_READ, 756 | old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 757 | new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 758 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 759 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 760 | image: Some(self.image), 761 | subresource_range: vk::ImageSubresourceRange { 762 | aspect_mask: vk::ImageAspectFlags::COLOR, 763 | level_count: 1, 764 | layer_count: 1, 765 | ..Default::default() 766 | }, 767 | ..Default::default() 768 | }; 769 | unsafe { 770 | device.cmd_pipeline_barrier( 771 | command_buffer, 772 | vk::PipelineStageFlags::TRANSFER, 773 | vk::PipelineStageFlags::FRAGMENT_SHADER, 774 | vk::DependencyFlags::empty(), 775 | &[], 776 | &[], 777 | slice::from_ref(&image_memory_barrier), 778 | ) 779 | }; 780 | self.image_state = ImageState::ShaderRead; 781 | } 782 | } 783 | } 784 | 785 | impl TextureSet { 786 | pub fn destroy(&mut self, device: &Device) { 787 | for (_, texture) in self.active.drain() { 788 | texture.destroy(device); 789 | } 790 | self.pending_updates.clear(); 791 | self.pending_frees.clear(); 792 | for texture in self.pending_deletes.drain(..) { 793 | texture.destroy(device); 794 | } 795 | } 796 | 797 | #[allow(clippy::too_many_arguments)] 798 | pub fn update( 799 | &mut self, 800 | device: &Device, 801 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 802 | command_buffer: vk::CommandBuffer, 803 | mut textures_delta: TexturesDelta, 804 | layouts: &Layouts, 805 | staging: &mut Staging, 806 | staging_remain: &mut Range, 807 | ) { 808 | // peform pending deletes 809 | for texture in self.pending_deletes.drain(..) { 810 | texture.destroy(device); 811 | } 812 | 813 | // perform pending frees 814 | for id in self.pending_frees.drain(..) { 815 | self.pending_updates.retain(|(update_id, _)| *update_id != id); 816 | if let Some(texture) = self.active.remove(&id) { 817 | self.pending_deletes.push(texture); 818 | } 819 | } 820 | 821 | // copy in the new deltas 822 | self.pending_updates.append(&mut VecDeque::from(textures_delta.set)); 823 | self.pending_frees.append(&mut textures_delta.free); 824 | 825 | // stage as many of the updates as we have staging memory for 826 | while let Some((id, image_delta)) = self.pending_updates.pop_front() { 827 | let width = image_delta.image.width(); 828 | let height = image_delta.image.height(); 829 | if let Some(dst_range) = staging_remain.take_prefix(width * height * 4) { 830 | let src_pixels = match &image_delta.image { 831 | ImageData::Color(image) => Cow::Borrowed(&image.pixels), 832 | ImageData::Font(image) => Cow::Owned(image.srgba_pixels(None).collect::>()), 833 | }; 834 | let staging_bytes = staging.bytes_mut(); 835 | let buffer_offset = dst_range.start; 836 | staging_bytes[dst_range].copy_from_slice(bytemuck::cast_slice(src_pixels.as_slice())); 837 | 838 | if image_delta.pos.is_none() { 839 | if let Some(texture) = self.active.remove(&id) { 840 | self.pending_deletes.push(texture); 841 | } 842 | self.active.insert( 843 | id, 844 | Texture::new( 845 | device, 846 | physical_device_memory_properties, 847 | command_buffer, 848 | width as u32, 849 | height as u32, 850 | image_delta.options, 851 | layouts, 852 | ), 853 | ); 854 | } 855 | 856 | if let Some(texture) = self.active.get_mut(&id) { 857 | texture.make_transfer_dest(device, command_buffer); 858 | 859 | let offset = image_delta.pos.unwrap_or([0, 0]); 860 | let buffer_image_copy = vk::BufferImageCopy { 861 | buffer_offset: buffer_offset as vk::DeviceSize, 862 | image_subresource: vk::ImageSubresourceLayers { 863 | aspect_mask: vk::ImageAspectFlags::COLOR, 864 | layer_count: 1, 865 | ..Default::default() 866 | }, 867 | image_offset: vk::Offset3D { 868 | x: offset[0] as i32, 869 | y: offset[1] as i32, 870 | z: 0, 871 | }, 872 | image_extent: vk::Extent3D { 873 | width: width as u32, 874 | height: height as u32, 875 | depth: 1, 876 | }, 877 | ..Default::default() 878 | }; 879 | unsafe { 880 | device.cmd_copy_buffer_to_image( 881 | command_buffer, 882 | staging.buffer, 883 | texture.image, 884 | vk::ImageLayout::TRANSFER_DST_OPTIMAL, 885 | slice::from_ref(&buffer_image_copy), 886 | ) 887 | }; 888 | } 889 | } else { 890 | // try again next frame 891 | self.pending_updates.push_front((id, image_delta)); 892 | break; 893 | } 894 | } 895 | 896 | // ensure all images are ready to sample 897 | for texture in self.active.values_mut() { 898 | texture.make_shader_read(device, command_buffer); 899 | } 900 | } 901 | } 902 | 903 | impl Renderer { 904 | pub fn new( 905 | device: &Device, 906 | physical_device_properties: &vk::PhysicalDeviceProperties, 907 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 908 | max_vertex_count: u32, 909 | max_texture_side: u32, 910 | ) -> Self { 911 | let vertex_buffer_size = mem::size_of::() * (max_vertex_count as usize); 912 | let index_buffer_size = mem::size_of::() * (max_vertex_count as usize); 913 | let texture_upload_size = (4 * max_texture_side * max_texture_side) as usize; 914 | let staging_area_size = vertex_buffer_size + index_buffer_size + texture_upload_size; 915 | 916 | let layouts = Layouts::new(device); 917 | let staging = Staging::new( 918 | device, 919 | physical_device_properties, 920 | physical_device_memory_properties, 921 | staging_area_size, 922 | ); 923 | let mesh_buffers = MeshBuffers::new( 924 | device, 925 | physical_device_memory_properties, 926 | vertex_buffer_size, 927 | index_buffer_size, 928 | ); 929 | 930 | Self { 931 | layouts, 932 | staging, 933 | mesh_buffers, 934 | texture_set: Default::default(), 935 | } 936 | } 937 | 938 | pub fn destroy(&mut self, device: &Device) { 939 | self.texture_set.destroy(device); 940 | self.mesh_buffers.destroy(device); 941 | self.staging.destroy(device); 942 | self.layouts.destroy(device); 943 | } 944 | 945 | pub fn create_pipeline( 946 | &self, 947 | device: &Device, 948 | render_pass: vk::RenderPass, 949 | samples: vk::SampleCountFlags, 950 | ) -> vk::Pipeline { 951 | self.layouts.create_pipeline(device, render_pass, samples) 952 | } 953 | 954 | pub fn update( 955 | &mut self, 956 | device: &Device, 957 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 958 | command_buffer: vk::CommandBuffer, 959 | clipped_primitives: Vec, 960 | textures_delta: TexturesDelta, 961 | ) { 962 | let mut staging_remain = self.staging.next_subset(); 963 | let staging_start = staging_remain.start; 964 | 965 | self.mesh_buffers.update( 966 | device, 967 | command_buffer, 968 | clipped_primitives, 969 | &mut self.staging, 970 | &mut staging_remain, 971 | ); 972 | 973 | self.texture_set.update( 974 | device, 975 | physical_device_memory_properties, 976 | command_buffer, 977 | textures_delta, 978 | &self.layouts, 979 | &mut self.staging, 980 | &mut staging_remain, 981 | ); 982 | 983 | let written_range = staging_start..staging_remain.start; 984 | self.staging.flush_mapped_range(device, written_range); 985 | } 986 | 987 | pub fn render( 988 | &mut self, 989 | device: &Device, 990 | command_buffer: vk::CommandBuffer, 991 | pipeline: vk::Pipeline, 992 | width_in_pixels: u32, 993 | height_in_pixels: u32, 994 | pixels_per_point: f32, 995 | ) { 996 | unsafe { 997 | device.cmd_bind_pipeline(command_buffer, vk::PipelineBindPoint::GRAPHICS, pipeline); 998 | } 999 | 1000 | let batch_data = BatchData { 1001 | size_in_points_rcp: ( 1002 | pixels_per_point / (width_in_pixels as f32), 1003 | pixels_per_point / (height_in_pixels as f32), 1004 | ), 1005 | }; 1006 | unsafe { 1007 | device.cmd_push_constants( 1008 | command_buffer, 1009 | self.layouts.pipeline_layout, 1010 | vk::ShaderStageFlags::VERTEX, 1011 | 0, 1012 | slice::from_ref(&batch_data), 1013 | ) 1014 | }; 1015 | 1016 | unsafe { 1017 | device.cmd_bind_vertex_buffers( 1018 | command_buffer, 1019 | 0, 1020 | slice::from_ref(&self.mesh_buffers.vertex_buffer), 1021 | &[0], 1022 | ); 1023 | device.cmd_bind_index_buffer( 1024 | command_buffer, 1025 | Some(self.mesh_buffers.index_buffer), 1026 | 0, 1027 | vk::IndexType::UINT32, 1028 | ); 1029 | } 1030 | 1031 | for clipped_draw in &self.mesh_buffers.clipped_draws { 1032 | if let Some(texture) = self.texture_set.active.get(&clipped_draw.texture_id) { 1033 | unsafe { 1034 | device.cmd_bind_descriptor_sets( 1035 | command_buffer, 1036 | vk::PipelineBindPoint::GRAPHICS, 1037 | self.layouts.pipeline_layout, 1038 | 0, 1039 | slice::from_ref(&texture.descriptor_set), 1040 | &[], 1041 | ); 1042 | } 1043 | 1044 | let min_x = width_in_pixels.min((clipped_draw.clip_rect.min.x * pixels_per_point).round() as u32); 1045 | let max_x = width_in_pixels.min((clipped_draw.clip_rect.max.x * pixels_per_point).round() as u32); 1046 | 1047 | let min_y = height_in_pixels.min((clipped_draw.clip_rect.min.y * pixels_per_point).round() as u32); 1048 | let max_y = height_in_pixels.min((clipped_draw.clip_rect.max.y * pixels_per_point).round() as u32); 1049 | 1050 | let scissor = vk::Rect2D { 1051 | offset: vk::Offset2D { 1052 | x: min_x as i32, 1053 | y: min_y as i32, 1054 | }, 1055 | extent: vk::Extent2D { 1056 | width: max_x - min_x, 1057 | height: max_y - min_y, 1058 | }, 1059 | }; 1060 | unsafe { 1061 | device.cmd_set_scissor(command_buffer, 0, slice::from_ref(&scissor)); 1062 | } 1063 | 1064 | unsafe { 1065 | device.cmd_draw_indexed( 1066 | command_buffer, 1067 | clipped_draw.index_count, 1068 | 1, 1069 | clipped_draw.first_index, 1070 | clipped_draw.vertex_offset, 1071 | 0, 1072 | ); 1073 | } 1074 | } 1075 | } 1076 | } 1077 | } 1078 | -------------------------------------------------------------------------------- /spark-imgui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spark-imgui" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | spark = { path = "../spark" } 10 | imgui = "0.8.2" 11 | arrayvec = "0.7.2" 12 | memoffset = "0.6.5" 13 | -------------------------------------------------------------------------------- /spark-imgui/Makefile: -------------------------------------------------------------------------------- 1 | GLSLC=glslangValidator 2 | GLSLCFLAGS=-V 3 | 4 | SHD=src/imgui.vert.spv \ 5 | src/imgui.frag.spv 6 | 7 | all: shaders 8 | .PHONY: all clean shaders 9 | 10 | clean: 11 | $(RM) $(SHD) 12 | 13 | shaders: $(SHD) 14 | 15 | %.spv: % Makefile 16 | $(GLSLC) $(GLSLCFLAGS) -o $@ $< 17 | 18 | -------------------------------------------------------------------------------- /spark-imgui/src/imgui.frag: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(location = 0) in vec2 v_uv; 4 | layout(location = 1) in vec4 v_col; 5 | 6 | layout(set = 0, binding = 0) uniform sampler2D g_tex; 7 | 8 | layout(location = 0) out vec4 o_col; 9 | 10 | void main() { 11 | o_col = v_col*texture(g_tex, v_uv); 12 | } 13 | -------------------------------------------------------------------------------- /spark-imgui/src/imgui.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/spark-imgui/src/imgui.frag.spv -------------------------------------------------------------------------------- /spark-imgui/src/imgui.vert: -------------------------------------------------------------------------------- 1 | #version 430 core 2 | 3 | layout(location = 0) in vec2 a_pos; 4 | layout(location = 1) in vec2 a_uv; 5 | layout(location = 2) in vec4 a_col; 6 | 7 | layout(push_constant) uniform BatchData { 8 | vec2 dims_rcp; 9 | } g_batch; 10 | 11 | out gl_PerVertex { 12 | vec4 gl_Position; 13 | }; 14 | 15 | layout(location = 0) out vec2 v_uv; 16 | layout(location = 1) out vec4 v_col; 17 | 18 | void main() { 19 | gl_Position = vec4(a_pos*g_batch.dims_rcp*2.0 - 1.0, 0, 1); 20 | v_uv = a_uv; 21 | v_col = a_col; 22 | } 23 | -------------------------------------------------------------------------------- /spark-imgui/src/imgui.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjb3d/spark/1155a8d2a90f3273883965806a92d2abdeb1be64/spark-imgui/src/imgui.vert.spv -------------------------------------------------------------------------------- /spark-imgui/src/lib.rs: -------------------------------------------------------------------------------- 1 | // silence unneeded_field_pattern due to offset_of, cast_ptr_alignment in memory management 2 | #![allow(clippy::unneeded_field_pattern, clippy::cast_ptr_alignment)] 3 | 4 | use arrayvec::ArrayVec; 5 | use imgui::internal::RawWrapper; 6 | use imgui::{Context, DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert}; 7 | use memoffset::offset_of; 8 | use spark::vk; 9 | use spark::{Builder, Device}; 10 | use std::ffi::CStr; 11 | use std::mem; 12 | use std::os::raw::{c_uchar, c_void}; 13 | use std::slice; 14 | 15 | fn load_shader_module(device: &Device, bytes: &[u8]) -> vk::ShaderModule { 16 | let shader_module_create_info = vk::ShaderModuleCreateInfo { 17 | code_size: bytes.len(), 18 | p_code: bytes.as_ptr() as *const u32, 19 | ..Default::default() 20 | }; 21 | unsafe { device.create_shader_module(&shader_module_create_info, None) }.unwrap() 22 | } 23 | 24 | fn get_memory_type_index( 25 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 26 | type_filter: u32, 27 | property_flags: vk::MemoryPropertyFlags, 28 | ) -> Option { 29 | for i in 0..physical_device_memory_properties.memory_type_count { 30 | let mt = &physical_device_memory_properties.memory_types[i as usize]; 31 | if (type_filter & (1 << i)) != 0 && mt.property_flags.contains(property_flags) { 32 | return Some(i); 33 | } 34 | } 35 | None 36 | } 37 | 38 | fn align_up(x: u32, alignment: u32) -> u32 { 39 | (x + alignment - 1) & !(alignment - 1) 40 | } 41 | 42 | #[repr(C)] 43 | struct BatchData { 44 | dims_rcp: (f32, f32), 45 | } 46 | 47 | pub struct Renderer { 48 | descriptor_set_layout: vk::DescriptorSetLayout, 49 | pipeline_layout: vk::PipelineLayout, 50 | vertex_shader: vk::ShaderModule, 51 | fragment_shader: vk::ShaderModule, 52 | linear_sampler: vk::Sampler, 53 | vertex_buffers: [vk::Buffer; Self::FRAME_COUNT], 54 | vertex_mem_offsets: [usize; Self::FRAME_COUNT], 55 | index_buffers: [vk::Buffer; Self::FRAME_COUNT], 56 | index_mem_offsets: [usize; Self::FRAME_COUNT], 57 | image_buffer: vk::Buffer, 58 | host_mem: vk::DeviceMemory, 59 | host_mapping: *mut c_void, 60 | image_width: u32, 61 | image_height: u32, 62 | image: vk::Image, 63 | image_view: vk::ImageView, 64 | local_mem: vk::DeviceMemory, 65 | descriptor_pool: vk::DescriptorPool, 66 | descriptor_set: vk::DescriptorSet, 67 | atom_size: u32, 68 | frame_index: usize, 69 | image_needs_copy: bool, 70 | } 71 | 72 | impl Renderer { 73 | const QUAD_COUNT_PER_FRAME: usize = 64 * 1024; 74 | const VERTEX_COUNT_PER_FRAME: usize = 4 * Self::QUAD_COUNT_PER_FRAME; 75 | const INDEX_COUNT_PER_FRAME: usize = 6 * Self::QUAD_COUNT_PER_FRAME; 76 | const FRAME_COUNT: usize = 2; 77 | 78 | pub fn new( 79 | device: &Device, 80 | physical_device_properties: &vk::PhysicalDeviceProperties, 81 | physical_device_memory_properties: &vk::PhysicalDeviceMemoryProperties, 82 | imgui: &mut Context, 83 | ) -> Self { 84 | let vertex_shader = load_shader_module(device, include_bytes!("imgui.vert.spv")); 85 | let fragment_shader = load_shader_module(device, include_bytes!("imgui.frag.spv")); 86 | 87 | let linear_sampler = { 88 | let sampler_create_info = vk::SamplerCreateInfo { 89 | mag_filter: vk::Filter::LINEAR, 90 | min_filter: vk::Filter::LINEAR, 91 | ..Default::default() 92 | }; 93 | unsafe { device.create_sampler(&sampler_create_info, None) }.unwrap() 94 | }; 95 | 96 | let descriptor_set_layout = { 97 | let binding = vk::DescriptorSetLayoutBinding::builder() 98 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 99 | .descriptor_count(1) 100 | .stage_flags(vk::ShaderStageFlags::FRAGMENT) 101 | .p_immutable_samplers(slice::from_ref(&linear_sampler)); 102 | let descriptor_set_layout_create_info = 103 | vk::DescriptorSetLayoutCreateInfo::builder().p_bindings(slice::from_ref(&binding)); 104 | unsafe { device.create_descriptor_set_layout(&descriptor_set_layout_create_info, None) }.unwrap() 105 | }; 106 | 107 | let pipeline_layout = { 108 | let push_constant_range = vk::PushConstantRange { 109 | stage_flags: vk::ShaderStageFlags::VERTEX, 110 | offset: 0, 111 | size: mem::size_of::() as u32, 112 | }; 113 | let pipeline_layout_create_info = vk::PipelineLayoutCreateInfo::builder() 114 | .p_set_layouts(slice::from_ref(&descriptor_set_layout)) 115 | .p_push_constant_ranges(slice::from_ref(&push_constant_range)); 116 | unsafe { device.create_pipeline_layout(&pipeline_layout_create_info, None) }.unwrap() 117 | }; 118 | 119 | let mut host_allocation_size = 0; 120 | let mut host_memory_type_filter = 0xffff_ffff; 121 | 122 | let (vertex_buffers, vertex_mem_offsets) = { 123 | let buffer_create_info = vk::BufferCreateInfo { 124 | size: (Self::VERTEX_COUNT_PER_FRAME * mem::size_of::()) as vk::DeviceSize, 125 | usage: vk::BufferUsageFlags::VERTEX_BUFFER, 126 | ..Default::default() 127 | }; 128 | let mut buffers = ArrayVec::::new(); 129 | let mut mem_offsets = ArrayVec::::new(); 130 | for _i in 0..Self::FRAME_COUNT { 131 | let buffer = unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap(); 132 | let mem_req = unsafe { device.get_buffer_memory_requirements(buffer) }; 133 | assert_eq!(mem_req.size, buffer_create_info.size); 134 | let mem_offset = host_allocation_size as usize; 135 | host_allocation_size += buffer_create_info.size; 136 | buffers.push(buffer); 137 | mem_offsets.push(mem_offset); 138 | host_memory_type_filter &= mem_req.memory_type_bits; 139 | } 140 | (buffers.into_inner().unwrap(), mem_offsets.into_inner().unwrap()) 141 | }; 142 | 143 | let (index_buffers, index_mem_offsets) = { 144 | let buffer_create_info = vk::BufferCreateInfo { 145 | size: (Self::INDEX_COUNT_PER_FRAME * mem::size_of::()) as vk::DeviceSize, 146 | usage: vk::BufferUsageFlags::INDEX_BUFFER, 147 | ..Default::default() 148 | }; 149 | let mut buffers = ArrayVec::::new(); 150 | let mut mem_offsets = ArrayVec::::new(); 151 | for _i in 0..Self::FRAME_COUNT { 152 | let buffer = unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap(); 153 | let mem_req = unsafe { device.get_buffer_memory_requirements(buffer) }; 154 | assert_eq!(mem_req.size, buffer_create_info.size); 155 | let mem_offset = host_allocation_size as usize; 156 | host_allocation_size += buffer_create_info.size; 157 | buffers.push(buffer); 158 | mem_offsets.push(mem_offset); 159 | host_memory_type_filter &= mem_req.memory_type_bits; 160 | } 161 | (buffers.into_inner().unwrap(), mem_offsets.into_inner().unwrap()) 162 | }; 163 | 164 | let mut fonts = imgui.fonts(); 165 | let texture = fonts.build_alpha8_texture(); 166 | 167 | let (image_buffer, image_mem_offset) = { 168 | let buffer_create_info = vk::BufferCreateInfo { 169 | size: vk::DeviceSize::from(texture.width * texture.height), 170 | usage: vk::BufferUsageFlags::TRANSFER_SRC, 171 | ..Default::default() 172 | }; 173 | let buffer = unsafe { device.create_buffer(&buffer_create_info, None) }.unwrap(); 174 | let mem_req = unsafe { device.get_buffer_memory_requirements(buffer) }; 175 | assert_eq!(mem_req.size, buffer_create_info.size); 176 | let mem_offset = host_allocation_size as usize; 177 | host_allocation_size += buffer_create_info.size; 178 | host_memory_type_filter &= mem_req.memory_type_bits; 179 | (buffer, mem_offset) 180 | }; 181 | 182 | let host_mem = { 183 | let memory_type_index = get_memory_type_index( 184 | physical_device_memory_properties, 185 | host_memory_type_filter, 186 | vk::MemoryPropertyFlags::HOST_VISIBLE, 187 | ) 188 | .unwrap(); 189 | let memory_allocate_info = vk::MemoryAllocateInfo { 190 | allocation_size: host_allocation_size, 191 | memory_type_index, 192 | ..Default::default() 193 | }; 194 | unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap() 195 | }; 196 | 197 | for (&buf, &ofs) in vertex_buffers.iter().zip(vertex_mem_offsets.iter()) { 198 | unsafe { device.bind_buffer_memory(buf, host_mem, ofs as vk::DeviceSize) }.unwrap(); 199 | } 200 | for (&buf, &ofs) in index_buffers.iter().zip(index_mem_offsets.iter()) { 201 | unsafe { device.bind_buffer_memory(buf, host_mem, ofs as vk::DeviceSize) }.unwrap(); 202 | } 203 | unsafe { device.bind_buffer_memory(image_buffer, host_mem, image_mem_offset as vk::DeviceSize) }.unwrap(); 204 | 205 | let host_mapping = unsafe { device.map_memory(host_mem, 0, vk::WHOLE_SIZE, Default::default()) }.unwrap(); 206 | 207 | let image = { 208 | let image_create_info = vk::ImageCreateInfo { 209 | image_type: vk::ImageType::N2D, 210 | format: vk::Format::R8_UNORM, 211 | extent: vk::Extent3D { 212 | width: texture.width, 213 | height: texture.height, 214 | depth: 1, 215 | }, 216 | mip_levels: 1, 217 | array_layers: 1, 218 | samples: vk::SampleCountFlags::N1, 219 | usage: vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::TRANSFER_DST, 220 | ..Default::default() 221 | }; 222 | unsafe { device.create_image(&image_create_info, None) }.unwrap() 223 | }; 224 | 225 | let (local_allocation_size, local_memory_type_filter) = { 226 | let mem_req = unsafe { device.get_image_memory_requirements(image) }; 227 | (mem_req.size, mem_req.memory_type_bits) 228 | }; 229 | 230 | let local_mem = { 231 | let memory_type_index = get_memory_type_index( 232 | physical_device_memory_properties, 233 | local_memory_type_filter, 234 | vk::MemoryPropertyFlags::DEVICE_LOCAL, 235 | ) 236 | .unwrap(); 237 | let memory_allocate_info = vk::MemoryAllocateInfo { 238 | allocation_size: local_allocation_size, 239 | memory_type_index, 240 | ..Default::default() 241 | }; 242 | unsafe { device.allocate_memory(&memory_allocate_info, None) }.unwrap() 243 | }; 244 | 245 | unsafe { device.bind_image_memory(image, local_mem, 0) }.unwrap(); 246 | 247 | let descriptor_pool = { 248 | let descriptor_pool_sizes = [vk::DescriptorPoolSize { 249 | ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 250 | descriptor_count: 1, 251 | }]; 252 | let descriptor_pool_create_info = vk::DescriptorPoolCreateInfo::builder() 253 | .max_sets(1) 254 | .p_pool_sizes(&descriptor_pool_sizes); 255 | unsafe { device.create_descriptor_pool(&descriptor_pool_create_info, None) }.unwrap() 256 | }; 257 | 258 | let descriptor_set = { 259 | let descriptor_set_allocate_info = vk::DescriptorSetAllocateInfo::builder() 260 | .descriptor_pool(descriptor_pool) 261 | .p_set_layouts(slice::from_ref(&descriptor_set_layout)); 262 | unsafe { device.allocate_descriptor_sets_single(&descriptor_set_allocate_info) }.unwrap() 263 | }; 264 | 265 | let image_view = { 266 | let image_view_create_info = vk::ImageViewCreateInfo { 267 | image: Some(image), 268 | view_type: vk::ImageViewType::N2D, 269 | format: vk::Format::R8_UNORM, 270 | subresource_range: vk::ImageSubresourceRange { 271 | aspect_mask: vk::ImageAspectFlags::COLOR, 272 | level_count: 1, 273 | layer_count: 1, 274 | ..Default::default() 275 | }, 276 | components: vk::ComponentMapping { 277 | r: vk::ComponentSwizzle::ONE, 278 | g: vk::ComponentSwizzle::ONE, 279 | b: vk::ComponentSwizzle::ONE, 280 | a: vk::ComponentSwizzle::R, 281 | }, 282 | ..Default::default() 283 | }; 284 | unsafe { device.create_image_view(&image_view_create_info, None) }.unwrap() 285 | }; 286 | 287 | { 288 | let image_info = vk::DescriptorImageInfo { 289 | sampler: Some(linear_sampler), 290 | image_view: Some(image_view), 291 | image_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 292 | }; 293 | let write_descriptor_set = vk::WriteDescriptorSet::builder() 294 | .dst_set(descriptor_set) 295 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 296 | .p_image_info(slice::from_ref(&image_info)); 297 | unsafe { device.update_descriptor_sets(slice::from_ref(&write_descriptor_set), &[]) }; 298 | } 299 | 300 | let atom_size = physical_device_properties.limits.non_coherent_atom_size as u32; 301 | 302 | { 303 | let image_base = unsafe { (host_mapping as *mut u8).add(image_mem_offset) } as *mut c_uchar; 304 | 305 | assert_eq!(texture.data.len() as u32, texture.width * texture.height); 306 | unsafe { image_base.copy_from_nonoverlapping(texture.data.as_ptr(), texture.data.len()) }; 307 | 308 | let mapped_memory_range = vk::MappedMemoryRange { 309 | memory: Some(host_mem), 310 | offset: image_mem_offset as vk::DeviceSize, 311 | size: vk::DeviceSize::from(align_up(texture.data.len() as u32, atom_size)), 312 | ..Default::default() 313 | }; 314 | unsafe { device.flush_mapped_memory_ranges(slice::from_ref(&mapped_memory_range)) }.unwrap(); 315 | } 316 | 317 | Self { 318 | descriptor_set_layout, 319 | pipeline_layout, 320 | vertex_shader, 321 | fragment_shader, 322 | linear_sampler, 323 | vertex_buffers, 324 | vertex_mem_offsets, 325 | index_buffers, 326 | index_mem_offsets, 327 | image_buffer, 328 | host_mem, 329 | host_mapping, 330 | image_width: texture.width, 331 | image_height: texture.height, 332 | image, 333 | image_view, 334 | local_mem, 335 | descriptor_pool, 336 | descriptor_set, 337 | atom_size, 338 | frame_index: 0, 339 | image_needs_copy: true, 340 | } 341 | } 342 | 343 | pub fn delete(&mut self, device: &Device) { 344 | unsafe { 345 | device.destroy_descriptor_pool(Some(self.descriptor_pool), None); 346 | 347 | device.destroy_image_view(Some(self.image_view), None); 348 | device.destroy_image(Some(self.image), None); 349 | device.free_memory(Some(self.local_mem), None); 350 | 351 | for buffer in self 352 | .index_buffers 353 | .iter() 354 | .chain(self.vertex_buffers.iter()) 355 | .chain(slice::from_ref(&self.image_buffer).iter()) 356 | { 357 | device.destroy_buffer(Some(*buffer), None); 358 | } 359 | device.unmap_memory(self.host_mem); 360 | device.free_memory(Some(self.host_mem), None); 361 | 362 | device.destroy_pipeline_layout(Some(self.pipeline_layout), None); 363 | device.destroy_descriptor_set_layout(Some(self.descriptor_set_layout), None); 364 | 365 | device.destroy_sampler(Some(self.linear_sampler), None); 366 | device.destroy_shader_module(Some(self.vertex_shader), None); 367 | device.destroy_shader_module(Some(self.fragment_shader), None); 368 | } 369 | } 370 | 371 | pub fn begin_frame(&mut self, device: &Device, command_buffer: vk::CommandBuffer) { 372 | self.frame_index = (1 + self.frame_index) % Self::FRAME_COUNT; 373 | 374 | if self.image_needs_copy { 375 | let transfer_from_undef = vk::ImageMemoryBarrier { 376 | dst_access_mask: vk::AccessFlags::TRANSFER_WRITE, 377 | new_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 378 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 379 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 380 | image: Some(self.image), 381 | subresource_range: vk::ImageSubresourceRange { 382 | aspect_mask: vk::ImageAspectFlags::COLOR, 383 | level_count: 1, 384 | layer_count: 1, 385 | ..Default::default() 386 | }, 387 | ..Default::default() 388 | }; 389 | unsafe { 390 | device.cmd_pipeline_barrier( 391 | command_buffer, 392 | vk::PipelineStageFlags::HOST, 393 | vk::PipelineStageFlags::TRANSFER, 394 | vk::DependencyFlags::empty(), 395 | &[], 396 | &[], 397 | slice::from_ref(&transfer_from_undef), 398 | ) 399 | }; 400 | 401 | let buffer_image_copy = vk::BufferImageCopy { 402 | image_subresource: vk::ImageSubresourceLayers { 403 | aspect_mask: vk::ImageAspectFlags::COLOR, 404 | layer_count: 1, 405 | ..Default::default() 406 | }, 407 | image_extent: vk::Extent3D { 408 | width: self.image_width, 409 | height: self.image_height, 410 | depth: 1, 411 | }, 412 | ..Default::default() 413 | }; 414 | unsafe { 415 | device.cmd_copy_buffer_to_image( 416 | command_buffer, 417 | self.image_buffer, 418 | self.image, 419 | vk::ImageLayout::TRANSFER_DST_OPTIMAL, 420 | slice::from_ref(&buffer_image_copy), 421 | ) 422 | }; 423 | 424 | let shader_from_transfer = vk::ImageMemoryBarrier { 425 | src_access_mask: vk::AccessFlags::TRANSFER_WRITE, 426 | dst_access_mask: vk::AccessFlags::SHADER_READ, 427 | old_layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, 428 | new_layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 429 | src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 430 | dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, 431 | image: Some(self.image), 432 | subresource_range: vk::ImageSubresourceRange { 433 | aspect_mask: vk::ImageAspectFlags::COLOR, 434 | level_count: 1, 435 | layer_count: 1, 436 | ..Default::default() 437 | }, 438 | ..Default::default() 439 | }; 440 | unsafe { 441 | device.cmd_pipeline_barrier( 442 | command_buffer, 443 | vk::PipelineStageFlags::TRANSFER, 444 | vk::PipelineStageFlags::FRAGMENT_SHADER, 445 | vk::DependencyFlags::empty(), 446 | &[], 447 | &[], 448 | slice::from_ref(&shader_from_transfer), 449 | ) 450 | }; 451 | 452 | self.image_needs_copy = false; 453 | } 454 | } 455 | 456 | pub fn create_pipeline( 457 | &self, 458 | device: &Device, 459 | render_pass: vk::RenderPass, 460 | samples: vk::SampleCountFlags, 461 | ) -> vk::Pipeline { 462 | let shader_entry_name = CStr::from_bytes_with_nul(b"main\0").unwrap(); 463 | let shader_stage_create_info = [ 464 | vk::PipelineShaderStageCreateInfo { 465 | stage: vk::ShaderStageFlags::VERTEX, 466 | module: Some(self.vertex_shader), 467 | p_name: shader_entry_name.as_ptr(), 468 | ..Default::default() 469 | }, 470 | vk::PipelineShaderStageCreateInfo { 471 | stage: vk::ShaderStageFlags::FRAGMENT, 472 | module: Some(self.fragment_shader), 473 | p_name: shader_entry_name.as_ptr(), 474 | ..Default::default() 475 | }, 476 | ]; 477 | 478 | let vertex_input_binding = vk::VertexInputBindingDescription { 479 | binding: 0, 480 | stride: mem::size_of::() as u32, 481 | input_rate: vk::VertexInputRate::VERTEX, 482 | }; 483 | let vertex_input_attributes = [ 484 | vk::VertexInputAttributeDescription { 485 | location: 0, 486 | binding: 0, 487 | format: vk::Format::R32G32_SFLOAT, 488 | offset: offset_of!(DrawVert, pos) as u32, 489 | }, 490 | vk::VertexInputAttributeDescription { 491 | location: 1, 492 | binding: 0, 493 | format: vk::Format::R32G32_SFLOAT, 494 | offset: offset_of!(DrawVert, uv) as u32, 495 | }, 496 | vk::VertexInputAttributeDescription { 497 | location: 2, 498 | binding: 0, 499 | format: vk::Format::R8G8B8A8_UNORM, 500 | offset: offset_of!(DrawVert, col) as u32, 501 | }, 502 | ]; 503 | 504 | let vertex_input_state_create_info = vk::PipelineVertexInputStateCreateInfo::builder() 505 | .p_vertex_binding_descriptions(slice::from_ref(&vertex_input_binding)) 506 | .p_vertex_attribute_descriptions(&vertex_input_attributes); 507 | 508 | let input_assembly_state_create_info = vk::PipelineInputAssemblyStateCreateInfo { 509 | topology: vk::PrimitiveTopology::TRIANGLE_LIST, 510 | ..Default::default() 511 | }; 512 | 513 | let viewport_state_create_info = vk::PipelineViewportStateCreateInfo { 514 | viewport_count: 1, 515 | scissor_count: 1, 516 | ..Default::default() 517 | }; 518 | 519 | let rasterization_state_create_info = vk::PipelineRasterizationStateCreateInfo { 520 | polygon_mode: vk::PolygonMode::FILL, 521 | cull_mode: vk::CullModeFlags::NONE, 522 | front_face: vk::FrontFace::CLOCKWISE, 523 | line_width: 1.0, 524 | ..Default::default() 525 | }; 526 | let multisample_state_create_info = vk::PipelineMultisampleStateCreateInfo { 527 | rasterization_samples: samples, 528 | ..Default::default() 529 | }; 530 | 531 | let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo { ..Default::default() }; 532 | 533 | let color_blend_attachment_state = vk::PipelineColorBlendAttachmentState { 534 | blend_enable: vk::TRUE, 535 | src_color_blend_factor: vk::BlendFactor::SRC_ALPHA, 536 | dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA, 537 | color_blend_op: vk::BlendOp::ADD, 538 | src_alpha_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA, 539 | dst_alpha_blend_factor: vk::BlendFactor::ZERO, 540 | alpha_blend_op: vk::BlendOp::ADD, 541 | color_write_mask: vk::ColorComponentFlags::all(), 542 | }; 543 | let color_blend_state_create_info = vk::PipelineColorBlendStateCreateInfo::builder() 544 | .p_attachments(slice::from_ref(&color_blend_attachment_state)); 545 | 546 | let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR]; 547 | let pipeline_dynamic_state_create_info = 548 | vk::PipelineDynamicStateCreateInfo::builder().p_dynamic_states(&dynamic_states); 549 | 550 | let pipeline_create_info = vk::GraphicsPipelineCreateInfo::builder() 551 | .p_stages(&shader_stage_create_info) 552 | .p_vertex_input_state(Some(&vertex_input_state_create_info)) 553 | .p_input_assembly_state(Some(&input_assembly_state_create_info)) 554 | .p_viewport_state(Some(&viewport_state_create_info)) 555 | .p_rasterization_state(Some(&rasterization_state_create_info)) 556 | .p_multisample_state(Some(&multisample_state_create_info)) 557 | .p_depth_stencil_state(Some(&depth_stencil_state)) 558 | .p_color_blend_state(Some(&color_blend_state_create_info)) 559 | .p_dynamic_state(Some(&pipeline_dynamic_state_create_info)) 560 | .layout(Some(self.pipeline_layout)) 561 | .render_pass(Some(render_pass)); 562 | 563 | unsafe { device.create_graphics_pipelines_single(None, &pipeline_create_info, None) }.unwrap() 564 | } 565 | 566 | pub fn render( 567 | &mut self, 568 | draw_data: &DrawData, 569 | device: &Device, 570 | command_buffer: vk::CommandBuffer, 571 | pipeline: vk::Pipeline, 572 | ) { 573 | { 574 | let vertex_buffer = self.vertex_buffers[self.frame_index]; 575 | let vertex_mem_offset = self.vertex_mem_offsets[self.frame_index]; 576 | let index_buffer = self.index_buffers[self.frame_index]; 577 | let index_mem_offset = self.index_mem_offsets[self.frame_index]; 578 | 579 | unsafe { 580 | device.cmd_bind_pipeline(command_buffer, vk::PipelineBindPoint::GRAPHICS, pipeline); 581 | device.cmd_bind_descriptor_sets( 582 | command_buffer, 583 | vk::PipelineBindPoint::GRAPHICS, 584 | self.pipeline_layout, 585 | 0, 586 | slice::from_ref(&self.descriptor_set), 587 | &[], 588 | ); 589 | } 590 | 591 | let batch_data = BatchData { 592 | dims_rcp: (1.0 / draw_data.display_size[0], 1.0 / draw_data.display_size[1]), 593 | }; 594 | unsafe { 595 | device.cmd_push_constants( 596 | command_buffer, 597 | self.pipeline_layout, 598 | vk::ShaderStageFlags::VERTEX, 599 | 0, 600 | slice::from_ref(&batch_data), 601 | ) 602 | }; 603 | 604 | let viewport = vk::Viewport { 605 | width: draw_data.display_size[0] * draw_data.framebuffer_scale[0], 606 | height: draw_data.display_size[1] * draw_data.framebuffer_scale[1], 607 | max_depth: 1.0, 608 | ..Default::default() 609 | }; 610 | unsafe { device.cmd_set_viewport(command_buffer, 0, slice::from_ref(&viewport)) }; 611 | 612 | unsafe { 613 | device.cmd_bind_vertex_buffers(command_buffer, 0, slice::from_ref(&vertex_buffer), &[0]); 614 | device.cmd_bind_index_buffer(command_buffer, Some(index_buffer), 0, vk::IndexType::UINT16); 615 | } 616 | 617 | let clip_off = draw_data.display_pos; 618 | let clip_scale = draw_data.framebuffer_scale; 619 | let vertex_base = unsafe { (self.host_mapping as *mut u8).add(vertex_mem_offset) } as *mut DrawVert; 620 | let index_base = unsafe { (self.host_mapping as *mut u8).add(index_mem_offset) } as *mut DrawIdx; 621 | let mut vertex_offset = 0; 622 | let mut index_offset = 0; 623 | for draw_list in draw_data.draw_lists() { 624 | let vtx_buffer = draw_list.vtx_buffer(); 625 | let idx_buffer = draw_list.idx_buffer(); 626 | let next_vertex_offset = vertex_offset + vtx_buffer.len(); 627 | let next_index_offset = index_offset + idx_buffer.len(); 628 | if next_vertex_offset > Self::VERTEX_COUNT_PER_FRAME || next_index_offset > Self::INDEX_COUNT_PER_FRAME 629 | { 630 | break; 631 | } 632 | 633 | unsafe { 634 | vertex_base 635 | .add(vertex_offset) 636 | .copy_from_nonoverlapping(vtx_buffer.as_ptr(), vtx_buffer.len()); 637 | index_base 638 | .add(index_offset) 639 | .copy_from_nonoverlapping(idx_buffer.as_ptr(), idx_buffer.len()); 640 | } 641 | 642 | for cmd in draw_list.commands() { 643 | match cmd { 644 | DrawCmd::Elements { 645 | count, 646 | cmd_params: DrawCmdParams { clip_rect, .. }, 647 | } => { 648 | let clip_rect = [ 649 | (clip_rect[0] - clip_off[0]) * clip_scale[0], 650 | (clip_rect[1] - clip_off[1]) * clip_scale[1], 651 | (clip_rect[2] - clip_off[0]) * clip_scale[0], 652 | (clip_rect[3] - clip_off[1]) * clip_scale[1], 653 | ]; 654 | let scissor = vk::Rect2D { 655 | offset: vk::Offset2D { 656 | x: clip_rect[0].floor() as i32, 657 | y: clip_rect[1].floor() as i32, 658 | }, 659 | extent: vk::Extent2D { 660 | width: (clip_rect[2] - clip_rect[0]).ceil() as u32, 661 | height: (clip_rect[3] - clip_rect[1]).ceil() as u32, 662 | }, 663 | }; 664 | let count = count as u32; 665 | unsafe { 666 | device.cmd_set_scissor(command_buffer, 0, slice::from_ref(&scissor)); 667 | device.cmd_draw_indexed( 668 | command_buffer, 669 | count, 670 | 1, 671 | index_offset as u32, 672 | vertex_offset as i32, 673 | 0, 674 | ); 675 | } 676 | index_offset += count as usize; 677 | } 678 | DrawCmd::ResetRenderState => {} 679 | DrawCmd::RawCallback { callback, raw_cmd } => unsafe { callback(draw_list.raw(), raw_cmd) }, 680 | } 681 | } 682 | 683 | vertex_offset = next_vertex_offset; 684 | assert_eq!(index_offset, next_index_offset); 685 | } 686 | 687 | let mapped_ranges = [ 688 | vk::MappedMemoryRange { 689 | memory: Some(self.host_mem), 690 | offset: vertex_mem_offset as vk::DeviceSize, 691 | size: vk::DeviceSize::from(align_up( 692 | (vertex_offset * mem::size_of::()) as u32, 693 | self.atom_size, 694 | )), 695 | ..Default::default() 696 | }, 697 | vk::MappedMemoryRange { 698 | memory: Some(self.host_mem), 699 | offset: index_mem_offset as vk::DeviceSize, 700 | size: vk::DeviceSize::from(align_up( 701 | (index_offset * mem::size_of::()) as u32, 702 | self.atom_size, 703 | )), 704 | ..Default::default() 705 | }, 706 | ]; 707 | unsafe { device.flush_mapped_memory_ranges(&mapped_ranges) }.unwrap(); 708 | } 709 | } 710 | } 711 | -------------------------------------------------------------------------------- /spark/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spark" 3 | version = "0.1.0" 4 | authors = ["Simon Brown "] 5 | edition = "2018" 6 | description = "Convenience wrapper for Vulkan in Rust" 7 | repository = "https://github.com/sjb3d/spark" 8 | readme = "../README.md" 9 | keywords = ["vulkan", "graphics"] 10 | license = "MIT" 11 | publish = false 12 | 13 | [dependencies] 14 | lazy_static = "1.4.0" 15 | shared_library = "0.1.9" 16 | --------------------------------------------------------------------------------