文档列表见: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类型(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编码
复制代码