From d1b9821fa84c995e889204a3b4f90b0c044f4a2e Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 2 Apr 2021 18:59:07 -0700 Subject: [PATCH 1/3] 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 | 8 +- piet-gpu/shader/kernel4.spv | Bin 38540 -> 38464 bytes piet-gpu/shader/kernel4_idx.spv | Bin 0 -> 38640 bytes piet-gpu/shader/setup.h | 3 +- piet-gpu/src/lib.rs | 70 ++++++---- piet-gpu/src/render_ctx.rs | 26 ++-- 13 files changed, 243 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 fc2523e..36140f4 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]; @@ -128,6 +130,10 @@ void main() { if (xy_uint.x < 1024 && xy_uint.y < 1024) { rgb[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint + chunk_offset(i))/4).rgb; } +#else + if (xy_uint.x < 1024 && xy_uint.y < 1024) { + rgb[i] = imageLoad(images[0], ivec2(xy_uint + chunk_offset(i))/4).rgb; + } #endif } diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 323bb2363765344a6d1c3b69e76a1835e1b28c90..5c4e11c1ba0ad546816e236916be1e624a412722 100644 GIT binary patch literal 38464 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 literal 38540 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
>%sNqyjmY@F7=#O8-UeQa$aqSmh;Ma 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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..8aba85e40eca984f6085a8e9efd13dcec9fda731 GIT binary patch literal 38640 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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) } From d89d0964ec24dc1d82ea1940e5cbe13bcdd0e451 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Sat, 3 Apr 2021 08:34:26 -0700 Subject: [PATCH 2/3] Clean up device create extensions --- piet-gpu-hal/src/vulkan.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index bddbedc..273d860 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -282,12 +282,14 @@ impl VkInstance { .descriptor_binding_variable_descriptor_count(true) .runtime_descriptor_array(true); - let mut extensions = match surface { - Some(_) => vec![khr::Swapchain::name().as_ptr()], - None => vec![], - }; - extensions.push(vk::ExtDescriptorIndexingFn::name().as_ptr()); - extensions.push(vk::KhrMaintenance3Fn::name().as_ptr()); + let mut extensions = Vec::new(); + if surface.is_some() { + extensions.push(khr::Swapchain::name().as_ptr()); + } + if has_descriptor_indexing { + extensions.push(vk::KhrMaintenance3Fn::name().as_ptr()); + extensions.push(vk::ExtDescriptorIndexingFn::name().as_ptr()); + } let mut create_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queue_create_infos) .enabled_extension_names(&extensions); From e6b2cc7b2b8e5b47e6dc68895a63210513e2f81d Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Sat, 3 Apr 2021 08:14:43 -0700 Subject: [PATCH 3/3] Android test application Adds an example binary that can be run with `cargo apk`. One thing that will still need manual tuning (for now) is the size of the canvas. A good followup is to sense that from the window size. --- Cargo.lock | 62 ++++++++++++-- piet-gpu/Cargo.toml | 11 +++ piet-gpu/bin/android.rs | 176 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 5 deletions(-) create mode 100644 piet-gpu/bin/android.rs diff --git a/Cargo.lock b/Cargo.lock index 30a5dc6..2a2d932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,7 +610,19 @@ checksum = "5eb167c1febed0a496639034d0c76b3b74263636045db5489eee52143c246e73" dependencies = [ "jni-sys", "ndk-sys", - "num_enum", + "num_enum 0.4.3", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8794322172319b972f528bf90c6b467be0079f1fa82780ffb431088e741a73ab" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum 0.5.1", "thiserror", ] @@ -623,7 +635,21 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk", + "ndk 0.2.1", + "ndk-macro", + "ndk-sys", +] + +[[package]] +name = "ndk-glue" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5caf0c24d51ac1c905c27d4eda4fa0635bbe0de596b8f79235e0b17a4d29385" +dependencies = [ + "lazy_static", + "libc", + "log", + "ndk 0.3.0", "ndk-macro", "ndk-sys", ] @@ -687,7 +713,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" dependencies = [ "derivative", - "num_enum_derive", + "num_enum_derive 0.4.3", +] + +[[package]] +name = "num_enum" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066" +dependencies = [ + "derivative", + "num_enum_derive 0.5.1", ] [[package]] @@ -702,6 +738,18 @@ dependencies = [ "syn", ] +[[package]] +name = "num_enum_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "objc" version = "0.2.7" @@ -773,11 +821,15 @@ name = "piet-gpu" version = "0.1.0" dependencies = [ "clap", + "ndk 0.3.0", + "ndk-glue 0.3.0", + "ndk-sys", "piet", "piet-gpu-hal", "piet-gpu-types", "png", "rand", + "raw-window-handle", "roxmltree", "winit", ] @@ -1298,8 +1350,8 @@ dependencies = [ "log", "mio", "mio-extras", - "ndk", - "ndk-glue", + "ndk 0.2.1", + "ndk-glue 0.2.1", "ndk-sys", "objc", "parking_lot", diff --git a/piet-gpu/Cargo.toml b/piet-gpu/Cargo.toml index 8057318..8334038 100644 --- a/piet-gpu/Cargo.toml +++ b/piet-gpu/Cargo.toml @@ -14,6 +14,11 @@ path = "bin/cli.rs" name = "winit" path = "bin/winit.rs" +[[example]] +name = "android" +path = "bin/android.rs" +crate-type = ["cdylib"] + [dependencies.piet-gpu-hal] path = "../piet-gpu-hal" @@ -27,3 +32,9 @@ rand = "0.7.3" roxmltree = "0.13" winit = "0.23" clap = "2.33" + +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.3" +ndk-sys = "0.2.0" +ndk-glue = "0.3" +raw-window-handle = "0.3" diff --git a/piet-gpu/bin/android.rs b/piet-gpu/bin/android.rs new file mode 100644 index 0000000..3bf3ca6 --- /dev/null +++ b/piet-gpu/bin/android.rs @@ -0,0 +1,176 @@ +//! Android example +//! +//! Run using `cargo apk run --example android` +//! +//! Requires the [cargo-apk] tool. +//! [cargo-apk]: https://crates.io/crates/cargo-apk + +use raw_window_handle::android::AndroidHandle; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; + +use ndk::native_window::NativeWindow; +use ndk_glue::Event; + +use piet_gpu_hal::hub; +use piet_gpu_hal::vulkan::{QueryPool, VkInstance, VkSurface, VkSwapchain}; +use piet_gpu_hal::{CmdBuf, Error, ImageLayout}; + +use piet_gpu::{render_scene, PietGpuRenderContext, Renderer}; + +#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] +fn main() { + my_main().unwrap(); +} + +struct MyHandle { + handle: AndroidHandle, +} + +// State required to render and present the contents +struct GfxState { + session: hub::Session, + renderer: Renderer, + swapchain: VkSwapchain, + current_frame: usize, + last_frame_idx: usize, + submitted: Option, + query_pools: Vec, + present_semaphores: Vec, +} + +const WIDTH: usize = 1080; +const HEIGHT: usize = 2280; +const NUM_FRAMES: usize = 2; + +fn my_main() -> Result<(), Error> { + let mut gfx_state = None; + loop { + for event in ndk_glue::poll_events() { + println!("got event {:?}", event); + match event { + Event::WindowCreated => { + let window = ndk_glue::native_window(); + if let Some(window) = &*window { + let handle = get_handle(window); + let (instance, surface) = VkInstance::new(Some(&handle))?; + gfx_state = Some(GfxState::new(&instance, surface.as_ref())?); + } else { + println!("native window is sadly none"); + } + } + Event::WindowRedrawNeeded => { + if let Some(gfx_state) = gfx_state.as_mut() { + for _ in 0..10 { + gfx_state.redraw(); + } + } + } + _ => (), + } + } + } +} + +fn get_handle(window: &NativeWindow) -> MyHandle { + println!( + "window = {:?}, {}x{}", + window.ptr(), + window.width(), + window.height() + ); + let mut handle = AndroidHandle::empty(); + handle.a_native_window = window.ptr().as_ptr() as *mut std::ffi::c_void; + MyHandle { handle } +} + +unsafe impl HasRawWindowHandle for MyHandle { + fn raw_window_handle(&self) -> RawWindowHandle { + RawWindowHandle::Android(self.handle) + } +} + +impl GfxState { + fn new(instance: &VkInstance, surface: Option<&VkSurface>) -> Result { + unsafe { + let device = instance.device(surface)?; + let mut swapchain = + instance.swapchain(WIDTH / 2, HEIGHT / 2, &device, surface.unwrap())?; + let session = hub::Session::new(device); + let mut current_frame = 0; + let present_semaphores = (0..NUM_FRAMES) + .map(|_| session.create_semaphore()) + .collect::, Error>>()?; + let query_pools = (0..NUM_FRAMES) + .map(|_| session.create_query_pool(8)) + .collect::, Error>>()?; + + let mut ctx = PietGpuRenderContext::new(); + render_scene(&mut ctx); + let n_paths = ctx.path_count(); + let n_pathseg = ctx.pathseg_count(); + let n_trans = ctx.pathseg_count(); + let scene = ctx.get_scene_buf(); + + let renderer = Renderer::new(&session, scene, n_paths, n_pathseg, n_trans)?; + + let submitted: Option = None; + let current_frame = 0; + let last_frame_idx = 0; + Ok(GfxState { + session, + renderer, + swapchain, + current_frame, + last_frame_idx, + submitted, + query_pools, + present_semaphores, + }) + } + } + + fn redraw(&mut self) { + println!("redraw"); + unsafe { + if let Some(submitted) = self.submitted.take() { + submitted.wait().unwrap(); + + let ts = self + .session + .fetch_query_pool(&self.query_pools[self.last_frame_idx]) + .unwrap(); + println!("render time: {:?}", ts); + } + let frame_idx = self.current_frame % NUM_FRAMES; + let (image_idx, acquisition_semaphore) = self.swapchain.next().unwrap(); + let swap_image = self.swapchain.image(image_idx); + let query_pool = &self.query_pools[frame_idx]; + let mut cmd_buf = self.session.cmd_buf().unwrap(); + cmd_buf.begin(); + self.renderer.record(&mut cmd_buf, &query_pool); + + // Image -> Swapchain + cmd_buf.image_barrier(&swap_image, ImageLayout::Undefined, ImageLayout::BlitDst); + cmd_buf.blit_image(self.renderer.image_dev.vk_image(), &swap_image); + cmd_buf.image_barrier(&swap_image, ImageLayout::BlitDst, ImageLayout::Present); + cmd_buf.finish(); + + self.submitted = Some( + self.session + .run_cmd_buf( + cmd_buf, + &[acquisition_semaphore], + &[self.present_semaphores[frame_idx]], + ) + .unwrap(), + ); + self.last_frame_idx = frame_idx; + + self.swapchain + .present(image_idx, &[self.present_semaphores[frame_idx]]) + .unwrap(); + + self.current_frame += 1; + } + } +}