From 115cb855d9271a2135f9bbc7a70f15e910498e13 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 2 Apr 2021 18:59:07 -0700 Subject: [PATCH] Query extensions at runtime Don't run extensions unless they're available. This includes querying for descriptor indexing, and running one of two versions of kernel4 depending on whether it's enabled. Part of the support needed for #78 --- Cargo.lock | 1 - piet-gpu-derive/src/glsl.rs | 11 +- piet-gpu-hal/Cargo.toml | 1 - piet-gpu-hal/src/hub.rs | 7 + piet-gpu-hal/src/vulkan.rs | 240 ++++++++++++++++++++------------ piet-gpu/bin/winit.rs | 3 +- piet-gpu/shader/build.ninja | 5 +- piet-gpu/shader/kernel4.comp | 11 +- piet-gpu/shader/kernel4.spv | Bin 36716 -> 36004 bytes piet-gpu/shader/kernel4_idx.spv | Bin 0 -> 36108 bytes piet-gpu/shader/setup.h | 3 +- piet-gpu/src/lib.rs | 70 ++++++---- piet-gpu/src/render_ctx.rs | 26 ++-- 13 files changed, 246 insertions(+), 132 deletions(-) create mode 100644 piet-gpu/shader/kernel4_idx.spv diff --git a/Cargo.lock b/Cargo.lock index 8201647..30a5dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -797,7 +797,6 @@ version = "0.1.0" dependencies = [ "ash", "ash-window", - "once_cell", "raw-window-handle", ] diff --git a/piet-gpu-derive/src/glsl.rs b/piet-gpu-derive/src/glsl.rs index d9f08f3..2283ae6 100644 --- a/piet-gpu-derive/src/glsl.rs +++ b/piet-gpu-derive/src/glsl.rs @@ -157,7 +157,12 @@ fn gen_enum_read( writeln!(r, "{}Tag {}_tag({}Ref ref) {{", name, name, name).unwrap(); writeln!(r, " uint tag_and_flags = {}[ref.offset >> 2];", bufname).unwrap(); } - writeln!(r, " return {}Tag(tag_and_flags & 0xffff, tag_and_flags >> 16);", name).unwrap(); + writeln!( + r, + " return {}Tag(tag_and_flags & 0xffff, tag_and_flags >> 16);", + name + ) + .unwrap(); writeln!(r, "}}\n").unwrap(); for (var_name, payload) in variants { let payload_ix = if payload.len() == 1 { @@ -564,7 +569,9 @@ fn gen_enum_write( } writeln!(r, "}}\n").unwrap(); } - } else if payload.len() == 2 && matches!(payload[0].1.ty, GpuType::Scalar(GpuScalar::TagFlags)) { + } else if payload.len() == 2 + && matches!(payload[0].1.ty, GpuType::Scalar(GpuScalar::TagFlags)) + { if let GpuType::InlineStruct(structname) = &payload[1].1.ty { if is_mem { writeln!( diff --git a/piet-gpu-hal/Cargo.toml b/piet-gpu-hal/Cargo.toml index 9ebd772..bef3548 100644 --- a/piet-gpu-hal/Cargo.toml +++ b/piet-gpu-hal/Cargo.toml @@ -8,6 +8,5 @@ edition = "2018" [dependencies] ash = "0.31" -once_cell = "1.3.1" ash-window = "0.5" raw-window-handle = "0.3" diff --git a/piet-gpu-hal/src/hub.rs b/piet-gpu-hal/src/hub.rs index a52040f..5649546 100644 --- a/piet-gpu-hal/src/hub.rs +++ b/piet-gpu-hal/src/hub.rs @@ -209,6 +209,13 @@ impl Session { pub unsafe fn create_sampler(&self, params: SamplerParams) -> Result { self.0.device.create_sampler(params) } + + /// Report whether the device supports descriptor indexing. + /// + /// As we have more queries, we might replace this with a capabilities structure. + pub fn has_descriptor_indexing(&self) -> bool { + self.0.device.has_descriptor_indexing + } } impl CmdBuf { diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index 20d15e4..bddbedc 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -2,12 +2,12 @@ use std::borrow::Cow; use std::ffi::{CStr, CString}; +use std::os::raw::c_char; use std::sync::Arc; use ash::extensions::{ext::DebugUtils, khr}; use ash::version::{DeviceV1_0, EntryV1_0, InstanceV1_0}; use ash::{vk, Device, Entry, Instance}; -use once_cell::sync::Lazy; use crate::{Device as DeviceTrait, Error, ImageLayout, SamplerParams}; @@ -16,6 +16,7 @@ pub struct VkInstance { #[allow(unused)] entry: Entry, instance: Instance, + get_phys_dev_props: Option, _dbg_loader: Option, _dbg_callbk: Option, } @@ -27,6 +28,8 @@ pub struct VkDevice { queue: vk::Queue, qfi: u32, timestamp_period: f32, + /// Does the device support descriptor indexing? + pub has_descriptor_indexing: bool, } struct RawDevice { @@ -95,6 +98,7 @@ pub struct PipelineBuilder { bindings: Vec, binding_flags: Vec, max_textures: u32, + has_descriptor_indexing: bool, } pub struct DescriptorSetBuilder { @@ -104,6 +108,16 @@ pub struct DescriptorSetBuilder { sampler: vk::Sampler, } +struct Extensions { + exts: Vec<*const c_char>, + exist_exts: Vec, +} + +struct Layers { + layers: Vec<*const c_char>, + exist_layers: Vec, +} + unsafe extern "system" fn vulkan_debug_callback( message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, message_type: vk::DebugUtilsMessageTypeFlagsEXT, @@ -133,24 +147,6 @@ unsafe extern "system" fn vulkan_debug_callback( vk::FALSE } -static LAYERS: Lazy> = Lazy::new(|| { - let mut layers: Vec<&'static CStr> = vec![]; - if cfg!(debug_assertions) { - layers.push(CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap()); - } - layers -}); - -static EXTS: Lazy> = Lazy::new(|| { - let mut exts: Vec<&'static CStr> = vec![]; - if cfg!(debug_assertions) { - exts.push(DebugUtils::name()); - } - // We'll need this to do runtime query of descriptor indexing. - //exts.push(vk::KhrGetPhysicalDeviceProperties2Fn::name()); - exts -}); - impl VkInstance { /// Create a new instance. /// @@ -166,50 +162,24 @@ impl VkInstance { let app_name = CString::new("VkToy").unwrap(); let entry = Entry::new()?; - let exist_layers = entry.enumerate_instance_layer_properties()?; - let layers = LAYERS - .iter() - .filter_map(|&lyr| { - exist_layers - .iter() - .find(|x| CStr::from_ptr(x.layer_name.as_ptr()) == lyr) - .map(|_| lyr.as_ptr()) - .or_else(|| { - println!( - "Unable to find layer: {}, have you installed the Vulkan SDK?", - lyr.to_string_lossy() - ); - None - }) - }) - .collect::>(); - - let exist_exts = entry.enumerate_instance_extension_properties()?; - let mut exts = EXTS - .iter() - .filter_map(|&ext| { - exist_exts - .iter() - .find(|x| CStr::from_ptr(x.extension_name.as_ptr()) == ext) - .map(|_| ext.as_ptr()) - .or_else(|| { - println!( - "Unable to find extension: {}, have you installed the Vulkan SDK?", - ext.to_string_lossy() - ); - None - }) - }) - .collect::>(); - - let surface_extensions = match window_handle { - Some(ref handle) => ash_window::enumerate_required_extensions(*handle)?, - None => vec![], - }; - for extension in surface_extensions { - exts.push(extension.as_ptr()); + let mut layers = Layers::new(entry.enumerate_instance_layer_properties()?); + if cfg!(debug_assertions) { + layers + .try_add(CStr::from_bytes_with_nul(b"VK_LAYER_KHRONOS_validation\0").unwrap()); + } + + let mut exts = Extensions::new(entry.enumerate_instance_extension_properties()?); + let mut has_debug_ext = false; + if cfg!(debug_assertions) { + has_debug_ext = exts.try_add(DebugUtils::name()); + } + // We'll need this to do runtime query of descriptor indexing. + let has_phys_dev_props = exts.try_add(vk::KhrGetPhysicalDeviceProperties2Fn::name()); + if let Some(ref handle) = window_handle { + for ext in ash_window::enumerate_required_extensions(*handle)? { + exts.try_add(ext); + } } - exts.push(vk::KhrGetPhysicalDeviceProperties2Fn::name().as_ptr()); let instance = entry.create_instance( &vk::InstanceCreateInfo::builder() @@ -220,12 +190,12 @@ impl VkInstance { .engine_name(&app_name) .api_version(vk::make_version(1, 0, 0)), ) - .enabled_layer_names(&layers) - .enabled_extension_names(&exts), + .enabled_layer_names(layers.as_ptrs()) + .enabled_extension_names(exts.as_ptrs()), None, )?; - let (_dbg_loader, _dbg_callbk) = if cfg!(debug_assertions) { + let (_dbg_loader, _dbg_callbk) = if has_debug_ext { let dbg_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() .message_severity( vk::DebugUtilsMessageSeverityFlagsEXT::ERROR @@ -250,9 +220,20 @@ impl VkInstance { None => None, }; + let get_phys_dev_props = if has_phys_dev_props { + Some(vk::KhrGetPhysicalDeviceProperties2Fn::load(|name| { + std::mem::transmute( + entry.get_instance_proc_addr(instance.handle(), name.as_ptr()), + ) + })) + } else { + None + }; + let vk_instance = VkInstance { entry, instance, + get_phys_dev_props, _dbg_loader, _dbg_callbk, }; @@ -273,14 +254,30 @@ impl VkInstance { let (pdevice, qfi) = choose_compute_device(&self.instance, &devices, surface).ok_or("no suitable device")?; + let mut has_descriptor_indexing = false; + if let Some(ref get_phys_dev_props) = self.get_phys_dev_props { + let mut descriptor_indexing_features = + vk::PhysicalDeviceDescriptorIndexingFeatures::builder(); + // See https://github.com/MaikKlein/ash/issues/325 for why we do this workaround. + let mut features_v2 = vk::PhysicalDeviceFeatures2::default(); + features_v2.p_next = + &mut descriptor_indexing_features as *mut _ as *mut std::ffi::c_void; + get_phys_dev_props.get_physical_device_features2_khr(pdevice, &mut features_v2); + has_descriptor_indexing = descriptor_indexing_features + .shader_storage_image_array_non_uniform_indexing + == vk::TRUE + && descriptor_indexing_features.descriptor_binding_variable_descriptor_count + == vk::TRUE + && descriptor_indexing_features.runtime_descriptor_array == vk::TRUE; + } + let queue_priorities = [1.0]; let queue_create_infos = [vk::DeviceQueueCreateInfo::builder() .queue_family_index(qfi) .queue_priorities(&queue_priorities) .build()]; - // support for descriptor indexing (maybe should be optional for compatibility) - let descriptor_indexing = vk::PhysicalDeviceDescriptorIndexingFeatures::builder() + let mut descriptor_indexing = vk::PhysicalDeviceDescriptorIndexingFeatures::builder() .shader_storage_image_array_non_uniform_indexing(true) .descriptor_binding_variable_descriptor_count(true) .runtime_descriptor_array(true); @@ -291,11 +288,12 @@ impl VkInstance { }; extensions.push(vk::ExtDescriptorIndexingFn::name().as_ptr()); extensions.push(vk::KhrMaintenance3Fn::name().as_ptr()); - let create_info = vk::DeviceCreateInfo::builder() + let mut create_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queue_create_infos) - .enabled_extension_names(&extensions) - .push_next(&mut descriptor_indexing.build()) - .build(); + .enabled_extension_names(&extensions); + if has_descriptor_indexing { + create_info = create_info.push_next(&mut descriptor_indexing); + } let device = self.instance.create_device(pdevice, &create_info, None)?; let device_mem_props = self.instance.get_physical_device_memory_properties(pdevice); @@ -315,6 +313,7 @@ impl VkInstance { qfi, queue, timestamp_period, + has_descriptor_indexing, }) } @@ -569,6 +568,7 @@ impl crate::Device for VkDevice { bindings: Vec::new(), binding_flags: Vec::new(), max_textures: 0, + has_descriptor_indexing: self.has_descriptor_indexing, } } @@ -711,21 +711,23 @@ impl crate::Device for VkDevice { SamplerParams::Linear => vk::Filter::LINEAR, SamplerParams::Nearest => vk::Filter::NEAREST, }; - let sampler = device.create_sampler(&vk::SamplerCreateInfo::builder() - .mag_filter(filter) - .min_filter(filter) - .mipmap_mode(vk::SamplerMipmapMode::LINEAR) - .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_BORDER) - .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_BORDER) - .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_BORDER) - .mip_lod_bias(0.0) - .compare_op(vk::CompareOp::NEVER) - .min_lod(0.0) - .max_lod(0.0) - .border_color(vk::BorderColor::FLOAT_TRANSPARENT_BLACK) - .max_anisotropy(1.0) - .anisotropy_enable(false) - , None)?; + let sampler = device.create_sampler( + &vk::SamplerCreateInfo::builder() + .mag_filter(filter) + .min_filter(filter) + .mipmap_mode(vk::SamplerMipmapMode::LINEAR) + .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_BORDER) + .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_BORDER) + .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_BORDER) + .mip_lod_bias(0.0) + .compare_op(vk::CompareOp::NEVER) + .min_lod(0.0) + .max_lod(0.0) + .border_color(vk::BorderColor::FLOAT_TRANSPARENT_BLACK) + .max_anisotropy(1.0) + .anisotropy_enable(false), + None, + )?; Ok(sampler) } } @@ -1007,8 +1009,12 @@ impl crate::PipelineBuilder for PipelineBuilder { .stage_flags(vk::ShaderStageFlags::COMPUTE) .build(), ); - self.binding_flags - .push(vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT); + let flags = if self.has_descriptor_indexing { + vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT + } else { + Default::default() + }; + self.binding_flags.push(flags); self.max_textures += max_textures; } @@ -1231,6 +1237,64 @@ impl VkSwapchain { } } +impl Extensions { + fn new(exist_exts: Vec) -> Extensions { + Extensions { + exist_exts, + exts: vec![], + } + } + + fn try_add(&mut self, ext: &'static CStr) -> bool { + unsafe { + if self + .exist_exts + .iter() + .find(|x| CStr::from_ptr(x.extension_name.as_ptr()) == ext) + .is_some() + { + self.exts.push(ext.as_ptr()); + true + } else { + false + } + } + } + + fn as_ptrs(&self) -> &[*const c_char] { + &self.exts + } +} + +impl Layers { + fn new(exist_layers: Vec) -> Layers { + Layers { + exist_layers, + layers: vec![], + } + } + + fn try_add(&mut self, ext: &'static CStr) -> bool { + unsafe { + if self + .exist_layers + .iter() + .find(|x| CStr::from_ptr(x.layer_name.as_ptr()) == ext) + .is_some() + { + self.layers.push(ext.as_ptr()); + true + } else { + false + } + } + } + + fn as_ptrs(&self) -> &[*const c_char] { + &self.layers + } +} + unsafe fn choose_compute_device( instance: &Instance, devices: &[vk::PhysicalDevice], diff --git a/piet-gpu/bin/winit.rs b/piet-gpu/bin/winit.rs index 51004cd..420ffa5 100644 --- a/piet-gpu/bin/winit.rs +++ b/piet-gpu/bin/winit.rs @@ -25,7 +25,8 @@ fn main() -> Result<(), Error> { let (instance, surface) = VkInstance::new(Some(&window))?; unsafe { let device = instance.device(surface.as_ref())?; - let mut swapchain = instance.swapchain(WIDTH / 2, HEIGHT / 2, &device, surface.as_ref().unwrap())?; + let mut swapchain = + instance.swapchain(WIDTH / 2, HEIGHT / 2, &device, surface.as_ref().unwrap())?; let session = hub::Session::new(device); let mut current_frame = 0; diff --git a/piet-gpu/shader/build.ninja b/piet-gpu/shader/build.ninja index cce8615..22c9c78 100644 --- a/piet-gpu/shader/build.ninja +++ b/piet-gpu/shader/build.ninja @@ -5,7 +5,7 @@ glslang_validator = glslangValidator rule glsl - command = $glslang_validator -V -o $out $in + command = $glslang_validator $flags -V -o $out $in build elements.spv: glsl elements.comp | scene.h state.h annotated.h @@ -21,3 +21,6 @@ build backdrop.spv: glsl backdrop.comp | annotated.h tile.h setup.h build coarse.spv: glsl coarse.comp | annotated.h bins.h ptcl.h setup.h build kernel4.spv: glsl kernel4.comp | ptcl.h setup.h + +build kernel4_idx.spv: glsl kernel4.comp | ptcl.h setup.h + flags = -DENABLE_IMAGE_INDICES diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index b7834cf..cd31f78 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -8,7 +8,9 @@ #version 450 #extension GL_GOOGLE_include_directive : enable +#ifdef ENABLE_IMAGE_INDICES #extension GL_EXT_nonuniform_qualifier : enable +#endif #include "mem.h" #include "setup.h" @@ -26,7 +28,7 @@ layout(set = 0, binding = 1) readonly buffer ConfigBuf { layout(rgba8, set = 0, binding = 2) uniform writeonly image2D image; -#if GL_EXT_nonuniform_qualifier +#ifdef ENABLE_IMAGE_INDICES layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[]; #else layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[1]; @@ -100,10 +102,17 @@ void main() { vec4 rgba[CHUNK]; for (uint i = 0; i < CHUNK; i++) { rgba[i] = vec4(0.0); + // TODO: remove this debug image support when the actual image method is plumbed. +#ifdef DEBUG_IMAGES #ifdef ENABLE_IMAGE_INDICES if (xy_uint.x < 1024 && xy_uint.y < 1024) { rgba[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint + chunk_offset(i))/4); } +#else + if (xy_uint.x < 1024 && xy_uint.y < 1024) { + rgb[i] = imageLoad(images[0], ivec2(xy_uint + chunk_offset(i))/4).rgb; + } +#endif #endif } diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index af7fe183c6b89ca9d5e0c89b172b53b55c6baba7..d9daddf9e7dcf7a66c4af766a9b6ab973b784e3b 100644 GIT binary patch literal 36004 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 diff --git a/piet-gpu/shader/kernel4_idx.spv b/piet-gpu/shader/kernel4_idx.spv new file mode 100644 index 0000000000000000000000000000000000000000..106a9fc13473ffa776091bf8d2cd3894c592ddde GIT binary patch literal 36108 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, } -#[derive(Clone,Copy,PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum FillMode { // Fill path according to the non-zero winding rule. Nonzero = 0, @@ -121,8 +121,9 @@ impl PietGpuRenderContext { fn set_fill_mode(ctx: &mut PietGpuRenderContext, fill_mode: FillMode) { if ctx.fill_mode != fill_mode { - ctx.elements - .push(Element::SetFillMode(SetFillMode { fill_mode: fill_mode as u32 })); + ctx.elements.push(Element::SetFillMode(SetFillMode { + fill_mode: fill_mode as u32, + })); ctx.fill_mode = fill_mode; } } @@ -322,14 +323,17 @@ impl PietGpuRenderContext { fn encode_path(&mut self, path: impl Iterator, is_fill: bool) { if is_fill { - self.encode_path_inner(path.flat_map(|el| { - match el { - PathEl::MoveTo(..) => { - Some(PathEl::ClosePath) + self.encode_path_inner( + path.flat_map(|el| { + match el { + PathEl::MoveTo(..) => Some(PathEl::ClosePath), + _ => None, } - _ => None - }.into_iter().chain(Some(el)) - }).chain(Some(PathEl::ClosePath))) + .into_iter() + .chain(Some(el)) + }) + .chain(Some(PathEl::ClosePath)), + ) } else { self.encode_path_inner(path) }