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

栏目: 后端 · 发布时间: 5年前

内容简介:文档列表见: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),
                }
            }))
        }
    }
}
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机动画的算法基础

计算机动画的算法基础

鲍虎军 金小刚 彭群生 / 浙江大学出版社 / 2000-12 / 60.00元

《计算机应用技术前沿丛书:计算机动画的算法基础》主要内容简介:20世纪是一个科技、经济空前发展的时代,从世纪初相对论、量子理论的创立到今天以信息产业为龙头的高科技产业成为经济发展的第一支柱,人类社会发生了根本性的变革。而在这场以科学技术为社会发展直接动因的变革中,意义最深远、影响最广泛的就是计算机及其相关技术的发展和应用。在过去的50年里,计算机已从最初的协助人类进行精密和复杂运算的单一功能的运算......一起来看看 《计算机动画的算法基础》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具