| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::ash_extensions::fuchsia; |
| use crate::render::Renderer; |
| use anyhow::Error; |
| use ash::extensions::ext; |
| use ash::vk; |
| use fidl::endpoints::{ClientEnd, Proxy, create_endpoints}; |
| use fuchsia_component::client::connect_to_protocol; |
| use fuchsia_scenic::{ |
| BufferCollectionTokenPair, duplicate_buffer_collection_import_token, |
| duplicate_buffer_collection_token, |
| }; |
| use std::ffi::{CString, c_char, c_void}; |
| use { |
| fidl_fuchsia_images2 as fimages2, fidl_fuchsia_sysmem2 as fsysmem2, |
| fidl_fuchsia_ui_composition as fland, |
| }; |
| |
| // Constants |
| pub const APPLICATION_VERSION: u32 = vk::make_api_version(0, 1, 0, 0); |
| pub const ENGINE_VERSION: u32 = vk::make_api_version(0, 1, 0, 0); |
| pub const API_VERSION: u32 = vk::make_api_version(0, 1, 3, 92); |
| // SRGB is the only color space compatible with R8G8B8A8_UNORM. |
| pub const FRAMEBUFFER_FORMAT: vk::Format = vk::Format::R8G8B8A8_UNORM; |
| pub const FRAMEBUFFER_COLOR_SPACE: fimages2::ColorSpace = fimages2::ColorSpace::Srgb; |
| |
| // Holds the state needed to implement the `crate::render::Renderer` trait. |
| pub struct VulkanRenderer { |
| _entry: ash::Entry, |
| instance: ash::Instance, |
| device: ash::Device, |
| queue: vk::Queue, |
| queue_family_index: u32, |
| command_pool: vk::CommandPool, |
| command_buffers: Vec<vk::CommandBuffer>, |
| framebuffers: Vec<ImageMemory>, |
| fence: vk::Fence, |
| import_token: fland::BufferCollectionImportToken, |
| } |
| |
| // Encapsulates a `VkImage` and the `VkDeviceMemory` that it is bound to. This makes it obvious to |
| // destroy them both at the same time. |
| struct ImageMemory { |
| image: vk::Image, |
| device_memory: vk::DeviceMemory, |
| } |
| |
| // Represents a `VkPhysicalDevice` which has all of the necessary characteristics to support this |
| // application, and also a suitable queue family associated with the device. |
| struct SuitablePhysicalDevice { |
| device: vk::PhysicalDevice, |
| graphics_queue_family_index: u32, |
| } |
| |
| impl SuitablePhysicalDevice { |
| // Considers the candidate physical device. If it meets all criteria, returns a fully-initialized |
| // `SuitablePhysicalDevice`. If it fails to meet even one of the criteria, return None. |
| fn consider( |
| instance: &ash::Instance, |
| physical_device: vk::PhysicalDevice, |
| ) -> Option<SuitablePhysicalDevice> { |
| // Pick the first queue family which supports graphics. Return None if none exists. |
| let graphics_queue_family_index = { |
| let queue_families = |
| unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; |
| |
| let mut graphics_queue_family_index: Option<u32> = None; |
| let mut index = 0; |
| for family in queue_families.iter() { |
| if family.queue_count > 0 && family.queue_flags.contains(vk::QueueFlags::GRAPHICS) { |
| graphics_queue_family_index = Some(index); |
| break; |
| } |
| index += 1; |
| } |
| // If it's still `None` then the candidate device is unsuitable. |
| graphics_queue_family_index? |
| }; |
| |
| // Check if the device supports the desired format, and bail if it isn't. |
| // TODO(https://fxbug.dev/42055867): instead of hardcoding a single format, we should filter a list of |
| // acceptable formats, and only bail if none of them are supported. |
| let format_properties = unsafe { |
| instance.get_physical_device_format_properties(physical_device, FRAMEBUFFER_FORMAT) |
| }; |
| if !format_properties.linear_tiling_features.contains(vk::FormatFeatureFlags::TRANSFER_SRC) |
| || !format_properties |
| .optimal_tiling_features |
| .contains(vk::FormatFeatureFlags::TRANSFER_DST) |
| { |
| return None; |
| } |
| |
| // More sophisticated apps might want to consider other criteria here. |
| let _device_features = unsafe { instance.get_physical_device_features(physical_device) }; |
| |
| // Success! This physical device meets all criteria. |
| Some(SuitablePhysicalDevice { device: physical_device, graphics_queue_family_index }) |
| } |
| } |
| |
| impl Drop for VulkanRenderer { |
| fn drop(&mut self) { |
| let device = &self.device; |
| unsafe { |
| device.device_wait_idle().expect("failed to wait for idle device"); |
| for im in &self.framebuffers { |
| device.destroy_image(im.image, None); |
| device.free_memory(im.device_memory, None); |
| } |
| device.free_command_buffers(self.command_pool, self.command_buffers.as_slice()); |
| device.destroy_command_pool(self.command_pool, None); |
| device.destroy_fence(self.fence, None); |
| device.destroy_device(None); |
| self.instance.destroy_instance(None); |
| } |
| } |
| } |
| |
| impl Renderer for VulkanRenderer { |
| fn duplicate_buffer_collection_import_token(&self) -> fland::BufferCollectionImportToken { |
| duplicate_buffer_collection_import_token(&self.import_token).unwrap() |
| } |
| |
| // TODO(https://fxbug.dev/42055867): there are a number of shortcomings in the implementation of this |
| // function; see the bug report for more details. The main shortcoming is that this fills the |
| // whole view with a single color, instead of filling each quadrant with a different color. |
| // This is a temporary hack which avoids the need to create pipelines and draw geometry. |
| // The "hacky" part is that we don't consider whether it is necessary to use semaphores. |
| fn render_rgba(&self, buffer_index: usize, colors: [[u8; 4]; 4]) { |
| let device = &self.device; |
| let command_buffer = self.command_buffers[buffer_index]; |
| |
| let command_buffer_begin_info = vk::CommandBufferBeginInfo { |
| flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT, |
| ..Default::default() |
| }; |
| unsafe { |
| device |
| .begin_command_buffer(command_buffer, &command_buffer_begin_info) |
| .expect("failed to begin command buffer"); |
| } |
| |
| let first_color = &colors[0]; |
| let first_color = [ |
| first_color[0] as f32 / 255.0, |
| first_color[1] as f32 / 255.0, |
| first_color[2] as f32 / 255.0, |
| first_color[3] as f32 / 255.0, |
| ]; |
| let clear_color_value = vk::ClearColorValue { float32: first_color }; |
| let image = self.framebuffers[buffer_index].image; |
| let image_barrier_for_clear = vk::ImageMemoryBarrier::builder() |
| // empty() because it was last used by foreign queue (i.e. Scenic). |
| .src_access_mask(vk::AccessFlags::empty()) |
| .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE) |
| .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) |
| .src_queue_family_index(vk::QUEUE_FAMILY_FOREIGN_EXT) |
| .dst_queue_family_index(self.queue_family_index) |
| .image(image) |
| .subresource_range(VulkanRenderer::standard_image_subresource_range()) |
| .build(); |
| let image_barrier_for_present = vk::ImageMemoryBarrier::builder() |
| .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) |
| // empty() because next use will be by foreign queue (i.e. Scenic). |
| .dst_access_mask(vk::AccessFlags::empty()) |
| .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) |
| .new_layout(vk::ImageLayout::GENERAL) |
| .src_queue_family_index(self.queue_family_index) |
| .dst_queue_family_index(vk::QUEUE_FAMILY_FOREIGN_EXT) |
| .image(image) |
| .subresource_range(VulkanRenderer::standard_image_subresource_range()) |
| .build(); |
| unsafe { |
| device.cmd_pipeline_barrier( |
| command_buffer, |
| vk::PipelineStageFlags::TRANSFER, |
| vk::PipelineStageFlags::TRANSFER, |
| vk::DependencyFlags::empty(), |
| &[], |
| &[], |
| &[image_barrier_for_clear], |
| ); |
| |
| device.cmd_clear_color_image( |
| command_buffer, |
| image, |
| vk::ImageLayout::TRANSFER_DST_OPTIMAL, |
| &clear_color_value, |
| &[VulkanRenderer::standard_image_subresource_range()], |
| ); |
| |
| device.cmd_pipeline_barrier( |
| command_buffer, |
| vk::PipelineStageFlags::TRANSFER, |
| vk::PipelineStageFlags::TRANSFER, |
| vk::DependencyFlags::empty(), |
| &[], |
| &[], |
| &[image_barrier_for_present], |
| ); |
| |
| device.end_command_buffer(command_buffer).expect("failed to end command buffer"); |
| } |
| |
| // Submit the command buffer. |
| // TODO(https://fxbug.dev/42055867): we wait on the CPU for the fence to finish. It would be more |
| // optimal to pass a signal semaphore here, which corresponds to a `zx::event` which would |
| // be passed to Flatland via `PresentArgs.server_wait_fences`. |
| let submit_info = [vk::SubmitInfo::builder().command_buffers(&[command_buffer]).build()]; |
| unsafe { |
| device |
| .queue_submit(self.queue, &submit_info, self.fence) |
| .expect("failed to submit command buffer"); |
| |
| device |
| .wait_for_fences(&[self.fence], false, 10000000000) |
| .expect("failed to wait for fence in 10 seconds"); |
| |
| device.reset_fences(&[self.fence]).expect("failed to reset fence"); |
| } |
| } |
| } |
| |
| impl VulkanRenderer { |
| pub async fn new(width: u32, height: u32, image_count: usize) -> VulkanRenderer { |
| let entry = ash::Entry::linked(); |
| let instance = VulkanRenderer::create_instance(&entry); |
| |
| let suitable_physical_device = VulkanRenderer::find_suitable_physical_device(&instance); |
| |
| let (device, queue) = VulkanRenderer::create_device(&instance, &suitable_physical_device); |
| |
| let (command_pool, command_buffers) = VulkanRenderer::create_command_buffers( |
| &device, |
| suitable_physical_device.graphics_queue_family_index, |
| image_count as u32, |
| ); |
| |
| let (framebuffers, import_token) = VulkanRenderer::allocate_framebuffers( |
| &instance, |
| &suitable_physical_device.device, |
| &device, |
| width, |
| height, |
| image_count, |
| ) |
| .await |
| .expect("failed to allocated sysmem-backed VkImages"); |
| |
| let fence_create_info = vk::FenceCreateInfo::default(); |
| let fence = unsafe { |
| device.create_fence(&fence_create_info, None).expect("couldn't create fence") |
| }; |
| VulkanRenderer { |
| _entry: entry, |
| instance, |
| device, |
| queue, |
| queue_family_index: suitable_physical_device.graphics_queue_family_index, |
| command_pool, |
| command_buffers, |
| framebuffers, |
| fence, |
| import_token, |
| } |
| } |
| |
| fn create_instance(entry: &ash::Entry) -> ash::Instance { |
| let app_name = CString::new("flatland-rainbow example").unwrap(); |
| let engine_name = CString::new("(no engine)").unwrap(); |
| let app_info = vk::ApplicationInfo::builder() |
| .application_name(&app_name) |
| .application_version(APPLICATION_VERSION) |
| .engine_name(&engine_name) |
| .engine_version(ENGINE_VERSION) |
| .api_version(API_VERSION) |
| .build(); |
| |
| let pp_enabled_extension_names: Vec<*const c_char> = vec![ |
| ext::DebugUtils::name().as_ptr(), |
| vk::KhrExternalMemoryCapabilitiesFn::name().as_ptr(), |
| vk::KhrExternalSemaphoreCapabilitiesFn::name().as_ptr(), |
| ]; |
| |
| let enabled_layer_names: Vec<CString> = ["VK_LAYER_KHRONOS_validation"] |
| .iter() |
| .map(|layer_name| CString::new(*layer_name).unwrap()) |
| .collect(); |
| let pp_enabled_layer_names: Vec<*const c_char> = |
| enabled_layer_names.iter().map(|layer_name| layer_name.as_ptr()).collect(); |
| |
| // Enable synchronization validation in the `VkInstance` we're about to create. |
| let mut validation_features = vk::ValidationFeaturesEXT::builder() |
| .enabled_validation_features(&[ |
| vk::ValidationFeatureEnableEXT::SYNCHRONIZATION_VALIDATION, |
| ]) |
| .build(); |
| |
| let create_info = vk::InstanceCreateInfo::builder() |
| .push_next(&mut validation_features) |
| .application_info(&app_info) |
| .enabled_layer_names(&pp_enabled_layer_names) |
| .enabled_extension_names(&pp_enabled_extension_names); |
| |
| let instance: ash::Instance = unsafe { |
| entry.create_instance(&create_info, None).expect("Failed to create instance!") |
| }; |
| |
| instance |
| } |
| |
| fn find_suitable_physical_device(instance: &ash::Instance) -> SuitablePhysicalDevice { |
| let physical_devices = unsafe { |
| instance |
| .enumerate_physical_devices() |
| .expect("Failed to enumerate Vulkan physical devices.") |
| }; |
| |
| physical_devices |
| .iter() |
| .find_map(|physical_device| { |
| SuitablePhysicalDevice::consider(instance, *physical_device) |
| }) |
| .expect("Failed to find a suitable Vulkan physical device.") |
| } |
| |
| fn create_device( |
| instance: &ash::Instance, |
| physical_device: &SuitablePhysicalDevice, |
| ) -> (ash::Device, vk::Queue) { |
| let queue_priorities = [1.0_f32]; |
| let queue_create_info = vk::DeviceQueueCreateInfo::builder() |
| .queue_family_index(physical_device.graphics_queue_family_index) |
| .queue_priorities(&queue_priorities) |
| .build(); |
| |
| let enabled_layer_names: Vec<CString> = ["VK_LAYER_KHRONOS_validation"] |
| .iter() |
| .map(|layer_name| CString::new(*layer_name).unwrap()) |
| .collect(); |
| let pp_enabled_layer_names: Vec<*const c_char> = |
| enabled_layer_names.iter().map(|layer_name| layer_name.as_ptr()).collect(); |
| |
| let pp_enabled_extension_names: Vec<*const c_char> = vec![ |
| vk::ExtQueueFamilyForeignFn::name().as_ptr(), |
| vk::FuchsiaBufferCollectionFn::name().as_ptr(), |
| vk::FuchsiaExternalMemoryFn::name().as_ptr(), |
| vk::KhrExternalMemoryFn::name().as_ptr(), |
| // TODO(https://fxbug.dev/42055867): see the comment in `render_rgba()` about using semaphores |
| // instead of a VkFence. To this, we need to add the external semaphore extensions. |
| // vk::KhrExternalSemaphoreFn::name().as_ptr(), |
| // vk::FuchsiaExternalSemaphoreFn::name().as_ptr(), |
| ]; |
| |
| let device_create_info = vk::DeviceCreateInfo::builder() |
| .queue_create_infos(&[queue_create_info]) |
| .enabled_layer_names(&pp_enabled_layer_names) |
| .enabled_extension_names(&pp_enabled_extension_names) |
| .build(); |
| |
| let device: ash::Device = unsafe { |
| instance |
| .create_device(physical_device.device, &device_create_info, None) |
| .expect("Failed to create logical Device!") |
| }; |
| |
| let graphics_queue = |
| unsafe { device.get_device_queue(physical_device.graphics_queue_family_index, 0) }; |
| |
| (device, graphics_queue) |
| } |
| |
| fn create_command_buffers( |
| device: &ash::Device, |
| queue_family_index: u32, |
| command_buffer_count: u32, |
| ) -> (vk::CommandPool, Vec<vk::CommandBuffer>) { |
| let command_pool_create_info = vk::CommandPoolCreateInfo { |
| queue_family_index, |
| flags: vk::CommandPoolCreateFlags::TRANSIENT |
| | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER, |
| ..Default::default() |
| }; |
| |
| let command_pool = unsafe { |
| device |
| .create_command_pool(&command_pool_create_info, None) |
| .expect("failed to create VkCommandPool") |
| }; |
| |
| let command_buffer_allocate_info = vk::CommandBufferAllocateInfo { |
| command_buffer_count, |
| command_pool, |
| level: vk::CommandBufferLevel::PRIMARY, |
| ..Default::default() |
| }; |
| |
| let command_buffers = unsafe { |
| device |
| .allocate_command_buffers(&command_buffer_allocate_info) |
| .expect("Failed to allocate Command Buffers!") |
| }; |
| |
| (command_pool, command_buffers) |
| } |
| |
| async fn allocate_framebuffers( |
| instance: &ash::Instance, |
| _physical_device: &vk::PhysicalDevice, |
| device: &ash::Device, |
| width: u32, |
| height: u32, |
| image_count: usize, |
| ) -> Result<(Vec<ImageMemory>, fland::BufferCollectionImportToken), Error> { |
| let sysmem_allocator = connect_to_protocol::<fsysmem2::AllocatorMarker>()?; |
| sysmem_allocator.set_debug_client_info(&fsysmem2::AllocatorSetDebugClientInfoRequest { |
| name: Some("flatland-rainbow example".to_string()), |
| id: Some(fuchsia_runtime::process_self().koid()?.raw_koid()), |
| ..Default::default() |
| })?; |
| |
| let (buffer_collection_token, buffer_collection_token_server_end) = |
| create_endpoints::<fidl_fuchsia_sysmem2::BufferCollectionTokenMarker>(); |
| sysmem_allocator.allocate_shared_collection( |
| fsysmem2::AllocatorAllocateSharedCollectionRequest { |
| token_request: Some(buffer_collection_token_server_end), |
| ..Default::default() |
| }, |
| )?; |
| |
| // Temporarily transform this from a `ClientEnd` to a `Proxy` in order to make a duplicate. |
| let mut buffer_collection_token = buffer_collection_token.into_proxy(); |
| let buffer_collection_token_for_vulkan = |
| duplicate_buffer_collection_token(&mut buffer_collection_token).await?; |
| |
| let buffer_collection_token = ClientEnd::<fsysmem2::BufferCollectionTokenMarker>::new( |
| buffer_collection_token.into_channel().unwrap().into_zx_channel(), |
| ); |
| |
| let import_export_tokens = BufferCollectionTokenPair::new(); |
| { |
| let scenic_allocator = connect_to_protocol::<fland::AllocatorMarker>()?; |
| |
| let args = fland::RegisterBufferCollectionArgs { |
| export_token: Some(import_export_tokens.export_token), |
| // Sysmem token channels serve both sysmem(1) and sysmem2 token protocols, so we can |
| // convert here until flatland has a sysmem2 token field. |
| buffer_collection_token2: Some(buffer_collection_token), |
| ..Default::default() |
| }; |
| // Two possible errors here: |
| // - FIDL transport error (outer error) |
| // - Sysmem error (inner error) |
| scenic_allocator |
| .register_buffer_collection(args) |
| .await? |
| .expect("failed to register BufferCollection"); |
| } |
| |
| // This is used for two purposes: |
| // - to specify constraints to set on the `VkBufferCollectionFUCHSIA` |
| // - to create `VkImages` once the buffer collection has been allocated; the only change |
| // is chaining a `VkBufferCollectionImageCreateInfoFUCHSIA` via the `p_next` slot. |
| let vk_image_create_info = vk::ImageCreateInfo::builder() |
| .image_type(vk::ImageType::TYPE_2D) |
| .format(FRAMEBUFFER_FORMAT) |
| .extent(vk::Extent3D { width, height, depth: 1 }) |
| .mip_levels(1) |
| .array_layers(1) |
| .samples(vk::SampleCountFlags::TYPE_1) |
| .tiling(vk::ImageTiling::OPTIMAL) |
| // - TRANSFER_DST required to clear framebuffers via `vkCmdClearColorImage()` |
| // TODO(https://fxbug.dev/42055867): if we start using shader pipelines to render into the image, |
| // we'll need to add COLOR_ATTACHMENT to the usage flags. |
| .usage(vk::ImageUsageFlags::TRANSFER_DST) |
| .sharing_mode(vk::SharingMode::EXCLUSIVE) |
| .initial_layout(vk::ImageLayout::UNDEFINED) |
| .build(); |
| |
| // Also used to set buffer collection constraints. |
| let p_color_spaces = [vk::SysmemColorSpaceFUCHSIA::builder() |
| .color_space(FRAMEBUFFER_COLOR_SPACE.into_primitive()) |
| .build()]; |
| |
| // Also used to set buffer collection constraints. |
| let vk_image_format_constraints_info = [vk::ImageFormatConstraintsInfoFUCHSIA::builder() |
| .image_create_info(vk_image_create_info) |
| // TODO(https://fxbug.dev/42055867): if we start using shader pipelines to render into the image, |
| // we'll need to add COLOR_ATTACHMENT to the usage flags. |
| .required_format_features( |
| vk::FormatFeatureFlags::TRANSFER_SRC | vk::FormatFeatureFlags::TRANSFER_DST, |
| ) |
| .flags(Default::default()) |
| // Although the docs for VkImageFormatConstraintsInfoFUCHSIA don't mention it, 'vk.xml' |
| // declares this as optional, so 0 is an acceptable value. This is interpreted as us |
| // not having an opinion about the pixel format type. |
| .sysmem_pixel_format(0) |
| .color_spaces(&p_color_spaces) |
| .build()]; |
| |
| // Also used to set buffer collection constraints. |
| let vk_image_constraints_info = vk::ImageConstraintsInfoFUCHSIA::builder() |
| .format_constraints(&vk_image_format_constraints_info) |
| .buffer_collection_constraints( |
| vk::BufferCollectionConstraintsInfoFUCHSIA::builder() |
| .min_buffer_count(image_count as u32) |
| .max_buffer_count(image_count as u32) |
| .build(), |
| ) |
| // No `ImageConstraintsInfoFlagsFUCHSIA` required, because we have no need for protected |
| // memory; if we wanted to display DRM-protected video this would be different. |
| .build(); |
| |
| let vk_buffer_collection_create_info = vk::BufferCollectionCreateInfoFUCHSIA { |
| collection_token: buffer_collection_token_for_vulkan.into_channel().into_raw(), |
| ..Default::default() |
| }; |
| |
| // Instantiate Ash wrapper to call Fuchsia/Vulkan buffer collection APIs. |
| let ext_buffer_collection = fuchsia::BufferCollection::new(instance, device); |
| let vk_buffer_collection = unsafe { |
| ext_buffer_collection.create_buffer_collection(&vk_buffer_collection_create_info, None) |
| }?; |
| unsafe { |
| ext_buffer_collection.set_buffer_collection_image_constraints( |
| vk_buffer_collection, |
| &vk_image_constraints_info, |
| ) |
| }?; |
| |
| // This provides the `memory_type_index` that we will use to allocate memory for the image. |
| let vk_buffer_collection_properties = unsafe { |
| ext_buffer_collection.get_buffer_collection_properties(vk_buffer_collection) |
| }?; |
| |
| // Since we obtained the properties anyway, might as well verify that we allocated the |
| // expected number of images. |
| assert_eq!(image_count, vk_buffer_collection_properties.buffer_count as usize); |
| |
| // Iterate over the buffer collection indices to: |
| // - allocate new images |
| // - allocate memory for each new image |
| // - bind the image to the allocated memory |
| let mut framebuffers = Vec::new(); |
| for index in 0..image_count as u32 { |
| let vk_buffer_collection_image_create_info = |
| vk::BufferCollectionImageCreateInfoFUCHSIA { |
| collection: vk_buffer_collection, |
| index, |
| ..Default::default() |
| }; |
| |
| // Repurpose the struct from above. Pretty much all of the properties in it are good, |
| // we just need to extend it with appropriate `PNext` structs. |
| let vk_image_create_info = vk::ImageCreateInfo { |
| p_next: &vk_buffer_collection_image_create_info as *const _ as *const c_void, |
| // Copy the rest of the fields from the previously-created VkImageCreteInfo. |
| ..vk_image_create_info |
| }; |
| |
| let image = unsafe { device.create_image(&vk_image_create_info, None) }?; |
| |
| // Import memory from the buffer collection to bind to the image we just created. |
| let allocation_size = unsafe { device.get_image_memory_requirements(image).size }; |
| |
| let mut import_memory_extension = vk::ImportMemoryBufferCollectionFUCHSIA::builder() |
| .collection(vk_buffer_collection) |
| .index(index) |
| .build(); |
| let memory_allocate_info = vk::MemoryAllocateInfo::builder() |
| .push_next(&mut import_memory_extension) |
| .allocation_size(allocation_size) |
| .memory_type_index( |
| first_bit_index(vk_buffer_collection_properties.memory_type_bits) |
| .expect("no valid memory types available"), |
| ) |
| .build(); |
| let device_memory = unsafe { |
| device |
| .allocate_memory(&memory_allocate_info, None) |
| .expect("failed to allocate memory") |
| }; |
| |
| unsafe { |
| // Offset is zero because any offset within the buffer collection VMO is taken into |
| // account when allocating the device memory above. |
| let offset = 0; |
| device |
| .bind_image_memory(image, device_memory, offset) |
| .expect("failed to bind image memory"); |
| } |
| |
| framebuffers.push(ImageMemory { image, device_memory }); |
| } |
| |
| // According to the spec, images created using the buffer collection are allowed to outlive |
| // it. We have no further use for the buffer collection; now is a good time to destroy it. |
| unsafe { |
| ext_buffer_collection.destroy_buffer_collection(vk_buffer_collection, None); |
| } |
| |
| Ok((framebuffers, import_export_tokens.import_token)) |
| } |
| |
| fn standard_image_subresource_range() -> vk::ImageSubresourceRange { |
| vk::ImageSubresourceRange::builder() |
| .aspect_mask(vk::ImageAspectFlags::COLOR) |
| .base_mip_level(0) |
| .level_count(1) |
| .base_array_layer(0) |
| .layer_count(1) |
| .build() |
| } |
| } |
| |
| // Return the 0-index of the lowest-order bit in `num`. For example, if `num == 6` then the two |
| // lowest-order bit is at index 1. |
| fn first_bit_index(num: u32) -> Option<u32> { |
| for shift in 0..32 { |
| if 1 == (num >> shift) & 1 { |
| return Some(shift); |
| } |
| } |
| None |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::render_vk::first_bit_index; |
| |
| #[fuchsia::test] |
| fn no_bits() { |
| assert_eq!(None, first_bit_index(0)); |
| } |
| |
| #[fuchsia::test] |
| fn correct_bit() { |
| assert_eq!(Some(0), first_bit_index(1)); |
| assert_eq!(Some(1), first_bit_index(2)); |
| assert_eq!(Some(2), first_bit_index(4)); |
| assert_eq!(Some(3), first_bit_index(8)); |
| assert_eq!(Some(4), first_bit_index(16)); |
| assert_eq!(Some(5), first_bit_index(32)); |
| assert_eq!(Some(6), first_bit_index(64)); |
| assert_eq!(Some(7), first_bit_index(128)); |
| assert_eq!(Some(8), first_bit_index(256)); |
| assert_eq!(Some(9), first_bit_index(512)); |
| assert_eq!(Some(10), first_bit_index(1024)); |
| assert_eq!(Some(11), first_bit_index(2048)); |
| assert_eq!(Some(12), first_bit_index(4096)); |
| assert_eq!(Some(13), first_bit_index(8192)); |
| assert_eq!(Some(14), first_bit_index(16384)); |
| assert_eq!(Some(15), first_bit_index(32768)); |
| assert_eq!(Some(16), first_bit_index(65536)); |
| } |
| |
| #[fuchsia::test] |
| fn first_bit() { |
| assert_eq!(Some(0), first_bit_index(0b11)); |
| assert_eq!(Some(0), first_bit_index(0b101)); |
| assert_eq!(Some(0), first_bit_index(0b1101)); |
| assert_eq!(Some(0), first_bit_index(0b1111)); |
| assert_eq!(Some(0), first_bit_index(0b11101)); |
| |
| assert_eq!(Some(1), first_bit_index(0b110)); |
| assert_eq!(Some(1), first_bit_index(0b1010)); |
| assert_eq!(Some(1), first_bit_index(0b11010)); |
| assert_eq!(Some(1), first_bit_index(0b11110)); |
| assert_eq!(Some(1), first_bit_index(0b111010)); |
| |
| assert_eq!(Some(2), first_bit_index(0b1100)); |
| assert_eq!(Some(2), first_bit_index(0b10100)); |
| assert_eq!(Some(2), first_bit_index(0b110100)); |
| assert_eq!(Some(2), first_bit_index(0b111100)); |
| assert_eq!(Some(2), first_bit_index(0b1110100)); |
| |
| assert_eq!(Some(3), first_bit_index(0b11000)); |
| assert_eq!(Some(3), first_bit_index(0b101000)); |
| assert_eq!(Some(3), first_bit_index(0b1101000)); |
| assert_eq!(Some(3), first_bit_index(0b1111000)); |
| assert_eq!(Some(3), first_bit_index(0b11101000)); |
| } |
| } |