文档列表见: Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)
Texture 的使用已在以 OpenGL/ES 视角介绍 gfx-hal(Vulkan) Texture 接口使用介绍, 本文档分析 Vulkan 接口如何映射到 Metal, 内容安排完全参考前一个文档, 建议结合阅读. 源码总路径为 src/backend/metal/src.
创建纹理背后的故事
Metal 如何创建 Image
fn create_image 根据传递的 Texture 信息配置 MTLTextureDescriptor, 按照 Metal 开发套路, 之后通过这个描述符向 MTLDevice 申请具体的资源, 细节见文件: http://device.rs .
- fn create_image(
- &self,
- kind: image::Kind,
- mip_levels: image::Level,
- format: format::Format,
- tiling: image::Tiling,
- usage: image::Usage,
- flags: image::StorageFlags,
- ) -> Result<n::UnboundImage, image::CreationError> {
- debug!("create_image {:?} with {} mips of {:?} {:?} and usage {:?}",
- kind, mip_levels, format, tiling, usage);
- let is_cube = flags.contains(image::StorageFlags::CUBE_VIEW);
- let mtl_format = self.private_caps
- .map_format(format)
- .ok_or(image::CreationError::Format(format))?;
- let descriptor = metal::TextureDescriptor::new();
- let (mtl_type, num_layers) = match kind {
- image::Kind::D1(_, 1) => {
- assert!(!is_cube);
- (MTLTextureType::D1, None)
- }
- image::Kind::D1(_, layers) => {
- assert!(!is_cube);
- (MTLTextureType::D1Array, Some(layers))
- }
- image::Kind::D2(_, _, layers, 1) => {
- if is_cube && layers> 6 {
- assert_eq!(layers % 6, 0);
- (MTLTextureType::CubeArray, Some(layers / 6))
- } else if is_cube {
- assert_eq!(layers, 6);
- (MTLTextureType::Cube, None)
- } else if layers> 1 {
- (MTLTextureType::D2Array, Some(layers))
- } else {
- (MTLTextureType::D2, None)
- }
- }
- image::Kind::D2(_, _, 1, samples) if !is_cube => {
- descriptor.set_sample_count(samples as u64);
- (MTLTextureType::D2Multisample, None)
- }
- image::Kind::D2(..) => {
- error!("Multi-sampled array textures or cubes are not supported: {:?}", kind);
- return Err(image::CreationError::Kind)
- }
- image::Kind::D3(..) => {
- assert!(!is_cube);
- (MTLTextureType::D3, None)
- }
- };
- descriptor.set_texture_type(mtl_type);
- if let Some(count) = num_layers {
- descriptor.set_array_length(count as u64);
- }
- let extent = kind.extent();
- descriptor.set_width(extent.width as u64);
- descriptor.set_height(extent.height as u64);
- descriptor.set_depth(extent.depth as u64);
- descriptor.set_mipmap_level_count(mip_levels as u64);
- descriptor.set_pixel_format(mtl_format);
- descriptor.set_usage(conv::map_texture_usage(usage, tiling));
- let format_desc = format.surface_desc();
- let mip_sizes = (0 .. mip_levels)
- .map(|level| {
- let pitches = n::Image::pitches_impl(extent.at_level(level), format_desc);
- num_layers.unwrap_or(1) as buffer::Offset * pitches[3]
- })
- .collect();
- let host_usage = image::Usage::TRANSFER_SRC | image::Usage::TRANSFER_DST;
- let host_visible = mtl_type == MTLTextureType::D2 &&
- mip_levels == 1 && num_layers.is_none() &&
- format_desc.aspects.contains(format::Aspects::COLOR) &&
- tiling == image::Tiling::Linear &&
- host_usage.contains(usage);
- Ok(n::UnboundImage {
- texture_desc: descriptor,
- format,
- kind,
- mip_sizes,
- host_visible,
- })
- }
获取 Image 的存储要求
fn get_image_requirements 根据前面创建的 MTLTextureDescriptor 属性及设备能力创建合适的 Requirements, 它包含大小, 对齐, 内存类型信息, 这些属性决定了内存的访问速度.
- /// Memory requirements for a certain resource (buffer/image).
- #[derive(Clone, Copy, Debug)]
- pub struct Requirements {
- /// Size in the memory.
- pub size: u64,
- /// Memory alignment.
- pub alignment: u64,
- /// Supported memory types.
- pub type_mask: u64,
- }
- fn get_image_requirements(&self, image: &n::UnboundImage) -> memory::Requirements {
- if self.private_caps.resource_heaps {
- // We don't know what memory type the user will try to allocate the image with, so we test them
- // all get the most stringent ones. Note we don't check Shared because heaps can't use it
- let mut max_size = 0;
- let mut max_alignment = 0;
- let types = if image.host_visible {
- MemoryTypes::all()
- } else {
- MemoryTypes::PRIVATE
- };
- for (i, _) in self.memory_types.iter().enumerate() {
- if !types.contains(MemoryTypes::from_bits(1 <<i).unwrap()) {
- continue
- }
- let (storage, cache_mode) = MemoryTypes::describe(i);
- image.texture_desc.set_storage_mode(storage);
- image.texture_desc.set_cpu_cache_mode(cache_mode);
- let requirements = self.shared.device
- .lock()
- .heap_texture_size_and_align(&image.texture_desc);
- max_size = cmp::max(max_size, requirements.size);
- max_alignment = cmp::max(max_alignment, requirements.align);
- }
- memory::Requirements {
- size: max_size,
- alignment: max_alignment,
- type_mask: types.bits(),
- }
- } else if image.host_visible {
- assert_eq!(image.mip_sizes.len(), 1);
- let mask = self.private_caps.buffer_alignment - 1;
- memory::Requirements {
- size: (image.mip_sizes[0] + mask) & !mask,
- alignment: self.private_caps.buffer_alignment,
- type_mask: MemoryTypes::all().bits(),
- }
- } else {
- memory::Requirements {
- size: image.mip_sizes.iter().sum(),
- alignment: 4,
- type_mask: MemoryTypes::PRIVATE.bits(),
- }
- }
- }
分配 Texture 的支持存储空间
fn allocate_memory 根据前面的存储模式 MTLStorageMode 要求决定申请 Metal 设备分配 Buffer 还是 MTLHeap(gfx 目前有 bug, 执行不到这个分支), 如果 StorageMode 为 MTLStorageMode::Private 则在后面的 fn bind_image_memory 分配 MTLTexture 对象.
- fn allocate_memory(&self, memory_type: hal::MemoryTypeId, size: u64) -> Result<n::Memory, OutOfMemory> {
- let (storage, cache) = MemoryTypes::describe(memory_type.0);
- let device = self.shared.device.lock();
- debug!("allocate_memory type {:?} of size {}", memory_type, size);
- // Heaps cannot be used for CPU coherent resources
- //TEMP: MacOS supports Private only, iOS and tvOS can do private/shared
- let heap = if self.private_caps.resource_heaps && storage != MTLStorageMode::Shared && false {
- let descriptor = metal::HeapDescriptor::new();
- descriptor.set_storage_mode(storage);
- descriptor.set_cpu_cache_mode(cache);
- descriptor.set_size(size);
- let heap_raw = device.new_heap(&descriptor);
- n::MemoryHeap::Native(heap_raw)
- } else if storage == MTLStorageMode::Private {
- n::MemoryHeap::Private
- } else {
- let options = conv::resource_options_from_storage_and_cache(storage, cache);
- let cpu_buffer = device.new_buffer(size, options);
- debug!("\tbacked by cpu buffer {:?}", cpu_buffer.as_ptr());
- n::MemoryHeap::Public(memory_type, cpu_buffer)
- };
- Ok(n::Memory::new(heap, size))
- }
分配实际的 Texture 对象
fn bind_image_memory 根据 MemoryHeap 类型 (Native,Public,Private) 决定是从 MTLDevice 直接分配 MTLTexture 对象还是从 MTLHeap 中分配. 从目前来看 MTLHeap 似乎会多占一些内存, 有待进一步确认.
- fn bind_image_memory(
- &self, memory: &n::Memory, offset: u64, image: n::UnboundImage
- ) -> Result<n::Image, BindError> {
- let base = image.format.base_format();
- let format_desc = base.0.desc();
- let like = match memory.heap {
- n::MemoryHeap::Native(ref heap) => {
- let resource_options = conv::resource_options_from_storage_and_cache(
- heap.storage_mode(),
- heap.cpu_cache_mode());
- image.texture_desc.set_resource_options(resource_options);
- n::ImageLike::Texture(
- heap.new_texture(&image.texture_desc)
- .unwrap_or_else(|| {
- // TODO: disable hazard tracking?
- self.shared.device
- .lock()
- .new_texture(&image.texture_desc)
- })
- )
- },
- n::MemoryHeap::Public(_memory_type, ref cpu_buffer) => {
- assert_eq!(image.mip_sizes.len(), 1);
- n::ImageLike::Buffer(n::Buffer {
- raw: cpu_buffer.clone(),
- range: offset .. offset + image.mip_sizes[0] as u64,
- options: MTLResourceOptions::StorageModeShared,
- })
- }
- n::MemoryHeap::Private => {
- image.texture_desc.set_storage_mode(MTLStorageMode::Private);
- n::ImageLike::Texture(
- self.shared.device
- .lock()
- .new_texture(&image.texture_desc)
- )
- }
- };
- Ok(n::Image {
- like,
- kind: image.kind,
- format_desc,
- shader_channel: base.1.into(),
- mtl_format: match self.private_caps.map_format(image.format) {
- Some(format) => format,
- None => {
- error!("failed to find corresponding Metal format for {:?}", image.format);
- return Err(BindError::OutOfBounds);
- },
- },
- mtl_type: image.texture_desc.texture_type(),
- })
- }
上传数据到纹理背后的故事
创建 Staging Buffer
此部分参考 Buffer 的相关操作.
创建 Fence
Metal 目前没提供类似 Vulkan 的 Fence 数据结构, gfx 在此利用 MTLCommandBuffer 模拟这一行为.
- fn create_fence(&self, signaled: bool) -> n::Fence {
- n::Fence(RefCell::new(n::FenceInner::Idle { signaled }))
- }
创建用于数据拷贝的 Submmit
创建带类型的 Command Pool
- let mut staging_pool = device.borrow().device.create_command_pool_typed(
- &device.borrow().queues,
- pool::CommandPoolCreateFlags::empty(),
- 16,
- );
- /// Create a new command pool for a given queue family.
- ///
- ///*Note*: the family has to be associated by one as the `Gpu::queue_groups`.
- fn create_command_pool(&self, family: QueueFamilyId, create_flags: CommandPoolCreateFlags) -> B::CommandPool;
- /// Create a strongly typed command pool wrapper.
- fn create_command_pool_typed<C>(
- &self,
- group: &QueueGroup<B, C>,
- flags: CommandPoolCreateFlags,
- max_buffers: usize,
- ) -> CommandPool<B, C> {
- let raw = self.create_command_pool(group.family(), flags);
- let mut pool = unsafe { CommandPool::new(raw) };
- pool.reserve(max_buffers);
- pool
- }
- fn create_command_pool(
- &self, _family: QueueFamilyId, _flags: CommandPoolCreateFlags
- ) -> command::CommandPool {
- command::CommandPool::new(&self.shared, self.online_recording.clone())
- }
- pub struct CommandPool {
- shared: Arc<Shared>,
- allocated: Vec<CommandBufferInnerPtr>,
- pool_shared: PoolSharedPtr,
- }
- impl CommandPool {
- pub(crate) fn new(
- shared: &Arc<Shared>,
- online_recording: OnlineRecording,
- ) -> Self {
- let pool_shared = PoolShared {
- #[cfg(feature = "dispatch")]
- dispatch_queue: match online_recording {
- OnlineRecording::Immediate |
- OnlineRecording::Deferred => None,
- OnlineRecording::Remote(priority) => Some(dispatch::Queue::global(priority.clone())),
- },
- online_recording,
- };
- CommandPool {
- shared: Arc::clone(shared),
- allocated: Vec::new(),
- pool_shared: Arc::new(RefCell::new(pool_shared)),
- }
- }
- }
创建 Command Buffer
- /// Get a primary command buffer for recording.
- ///
- /// You can only record to one command buffer per pool at the same time.
- /// If more command buffers are requested than allocated, new buffers will be reserved.
- /// The command buffer will be returned in 'recording' state.
- pub fn acquire_command_buffer<S: Shot>(
- &mut self, allow_pending_resubmit: bool
- ) -> CommandBuffer<B, C, S> {
- self.reserve(1);
- let buffer = &mut self.buffers[self.next_buffer];
- let mut flags = S::FLAGS;
- if allow_pending_resubmit {
- flags |= CommandBufferFlags::SIMULTANEOUS_USE;
- }
- buffer.begin(flags, CommandBufferInheritanceInfo::default());
- self.next_buffer += 1;
- unsafe {
- CommandBuffer::new(buffer)
- }
- }
创建 Barrier
向 Command Buffer 提交 Barrier
向 Command Buffer 提交 Copy Buffer to Image 命令
- /// Identical to the `RawCommandBuffer` method of the same name.
- pub fn copy_buffer_to_image<T>(
- &mut self,
- src: &B::Buffer,
- dst: &B::Image,
- dst_layout: image::Layout,
- regions: T,
- ) where
- T: IntoIterator,
- T::Item: Borrow<BufferImageCopy>,
- {
- self.raw.copy_buffer_to_image(src, dst, dst_layout, regions)
- }
- fn copy_buffer_to_image<T>(
- &mut self,
- src: &native::Buffer,
- dst: &native::Image,
- _dst_layout: Layout,
- regions: T,
- ) where
- T: IntoIterator,
- T::Item: Borrow<com::BufferImageCopy>,
- {
- match dst.like {
- native::ImageLike::Texture(ref dst_raw) => {
- let commands = regions.into_iter().filter_map(|region| {
- let r = region.borrow();
- if r.image_extent.is_empty() {
- None
- } else {
- Some(soft::BlitCommand::CopyBufferToImage {
- src: AsNative::from(src.raw.as_ref()),
- dst: AsNative::from(dst_raw.as_ref()),
- dst_desc: dst.format_desc,
- region: com::BufferImageCopy {
- buffer_offset: r.buffer_offset + src.range.start,
- .. r.clone()
- },
- })
- }
- });
- self.inner
- .borrow_mut()
- .sink()
- .blit_commands(commands);
- }
- native::ImageLike::Buffer(ref dst_buffer) => {
- self.copy_buffer(src, dst_buffer, regions.into_iter().map(|region| {
- let r = region.borrow();
- com::BufferCopy {
- src: r.buffer_offset,
- dst: dst.byte_offset(r.image_offset),
- size: dst.byte_extent(r.image_extent),
- }
- }))
- }
- }
- }
结束 Command Buffer 编码
来源: https://juejin.im/post/5bfe55f2f265da6165014765