gfx-hal Metal模块Texture操作源码分析

文档列表见:Rust 移动端跨平台复杂图形渲染项目开发系列总结(目录)

Texture的使用已在以OpenGL/ES视角介绍gfx-hal(Vulkan) Texture接口使用介绍,本文档分析Vulkan接口如何映射到Metal,内容安排完全参考前一个文档,建议结合阅读。源码总路径为src/backend/metal/src。

创建纹理背后的故事

Metal如何创建Image

fn create_image根据传递的Texture信息配置MTLTextureDescriptor,按照Metal开发套路,之后通过这个描述符向MTLDevice申请具体的资源,细节见文件: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类型(NativePublicPrivate)决定是从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编码


复制代码

猜你喜欢

转载自juejin.im/post/5bfe55f2f265da6165014765
HAL
今日推荐