文件列表見:Rust 移動端跨平臺複雜圖形渲染專案開發系列總結(目錄)
上次更新:2019.1.21
Texture的使用已在以OpenGL/ES視角介紹gfx-hal(Vulkan) Texture介面使用介紹,本文件分析Vulkan介面如何對映到Metal,內容安排完全參考前一個文件,建議結合閱讀。
原始碼路徑入口: src/backend/metal/src
。
git commit id: c6fbead
。
Metal 仿 Vulkan API 的紋理建立流程分析
通過create_image()建立n::Image(即MTLTextureDescriptor)
create_image()
根據引數建立並配置MTLTextureDescriptor。usage: image::Usage
引數決定了應該申請什麼記憶體型別的紋理。記憶體型別影響效能。按照Metal開發流程,之後由此描述符向MTLDevice
申請MTLTexture
物件,MTLTextureDescriptor
物件可以複用。
// 位置:gfx/src/backend/metal/src/device.rs
// 呼叫示例:
// (width, height, 1 /* image::Layer */, 1 /* image::NumSamples */),
// 1 /* image::Level */,
// Rgba8Srgb::SELF,
// image::Tiling::Optimal,
// image::Usage::TRANSFER_DST | image::Usage::SAMPLED,
// image::ViewCapabilities::empty(),
unsafe fn create_image(
&self,
kind: image::Kind,
mip_levels: image::Level,
format: format::Format,
tiling: image::Tiling,
usage: image::Usage,
view_caps: image::ViewCapabilities,
) -> Result<n::Image, image::CreationError> {
debug!("create_image {:?} with {} mips of {:?} {:?} and usage {:?}",
kind, mip_levels, format, tiling, usage);
let is_cube = view_caps.contains(image::ViewCapabilities::KIND_CUBE);
let mtl_format = self.shared.private_caps
.map_format(format)
.ok_or(image::CreationError::Format(format))?;
// 建立MTLTextureDescriptor
let descriptor = metal::TextureDescriptor::new();
// 2D紋理(不是紋理陣列)對應的值為
// mtl_type = MTLTextureType::D2, num_layers = None
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 {
// 2D紋理(不是紋理陣列)執行此分支
(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 && !view_caps.contains(image::ViewCapabilities::KIND_2D_ARRAY));
(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);
// Metal文件:
// The default value is `MTLTextureUsageShaderRead`. You should
// always set specific texture usage values; do not rely on the
// `MTLTextureUsageUnknown` value for the best performance.
// Set this value to `MTLTextureUsageRenderTarget` if you
// intend to use the resulting `MTLTexture` object as a render target.
// This may significantly improve your app’s performance (with certain hardware).
//
// iOS 9才支援此API,gfx沒適配iOS 8
descriptor.set_usage(conv::map_texture_usage(usage, tiling));
let base = format.base_format();
let format_desc = base.0.desc();
let mip_sizes = (0 .. mip_levels)
.map(|level| {
// pitches為某一level的二維影像大小,以位元組表示。
// 比如,640x480 RGBA在mipmap level 1 的pitches = 1228800
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;
// 通過Tiling::Optimal引數建立的2D紋理只為GPU讀取優化,
// Tiling::Linear引數建立的2D紋理為CPU/GPU讀取優化,
// 最終得到host_visible = false
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::Image {
like: n::ImageLike::Unbound {
descriptor,
mip_sizes,
host_visible,
},
kind,
format_desc,
shader_channel: base.1.into(),
mtl_format,
mtl_type,
})
}
複製程式碼
Image
相關結構定義如下:
#[derive(Debug)]
pub struct Image {
pub(crate) like: ImageLike,
pub(crate) kind: image::Kind,
pub(crate) format_desc: FormatDesc,
pub(crate) shader_channel: Channel,
pub(crate) mtl_format: metal::MTLPixelFormat,
pub(crate) mtl_type: metal::MTLTextureType,
}
#[derive(Debug)]
pub enum ImageLike {
/// This image has not yet been bound to memory.
Unbound {
descriptor: metal::TextureDescriptor,
mip_sizes: Vec<buffer::Offset>,
host_visible: bool,
},
/// This is a linearly tiled HOST-visible image, which is represented by a buffer.
Buffer(Buffer),
/// This is a regular image represented by a texture.
Texture(metal::Texture),
}
/// Specifies the kind of an image to be allocated.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Kind {
/// A single one-dimensional row of texels.
D1(Size, Layer),
/// Two-dimensional image.
D2(Size, Size, Layer, NumSamples),
/// Volumetric image.
D3(Size, Size, Size),
}
複製程式碼
get_image_requirements()配置n::Image(即MTLTextureDescriptor)的儲存引數
get_image_requirements(&n::Image)
根據前面建立的n::Image
即MTLTextureDescriptor
屬性及當前GPU能力建立合適的Requirements
。
unsafe fn get_image_requirements(&self, image: &n::Image) -> memory::Requirements {
let (descriptor, mip_sizes, host_visible) = match image.like {
n::ImageLike::Unbound { ref descriptor, ref mip_sizes, host_visible } =>
(descriptor, mip_sizes, host_visible),
n::ImageLike::Texture(..) |
n::ImageLike::Buffer(..) => panic!("Expected Image::Unbound"),
};
// fixme 有待確認 MacBook Pro (Retina, 15-inch, Mid 2015) 是否真不支援Resource Heaps
if self.shared.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 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);
// Metal文件:
// iOS 9才支援,gfx沒做適配
// In iOS and tvOS, the default value is `MTLStorageModeShared`.
// In macOS, the default value is `MTLStorageModeManaged`.
descriptor.set_storage_mode(storage);
// API版本同上
// The CPU cache mode used for the CPU mapping of the texture.
// The default value is MTLCPUCacheModeDefaultCache.
descriptor.set_cpu_cache_mode(cache_mode);
let requirements = self.shared.device
.lock()
.heap_texture_size_and_align(descriptor);
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 host_visible {
assert_eq!(mip_sizes.len(), 1);
let mask = self.shared.private_caps.buffer_alignment - 1;
memory::Requirements {
size: (mip_sizes[0] + mask) & !mask,
alignment: self.shared.private_caps.buffer_alignment,
type_mask: MemoryTypes::all().bits(),
}
} else {
// 對於2D紋理,`create_image()`執行後host_visible = false,則macOS執行此分支
memory::Requirements {
size: mip_sizes.iter().sum(),
alignment: 4,
type_mask: MemoryTypes::PRIVATE.bits(),
}
}
}
複製程式碼
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,
}
複製程式碼
條件分配n::Image(即MTLTextureDescriptor)的後臺Buffer
let memory_types = adapter.physical_device.memory_properties().memory_types;
// 2D紋理對應的device_type = 0
let device_type = get_memory_type_id_from_properties(
memory_types,
image_req,
memory::Properties::DEVICE_LOCAL,
);
fn get_memory_type_id_from_properties(
memory_types: &[hal::MemoryType],
requirements: memory::Requirements,
memory_properties: memory::Properties,
) -> hal::MemoryTypeId {
memory_types
.iter()
.enumerate()
.position(|(id, mem_type)| {
// type_mask is a bit field where each bit represents a memory type.
// If the bit is set to 1 it means we can use that type for our buffer.
// So this code finds the first memory type that has a `1` (or, is allowed),
// and is visible to the CPU.
requirements.type_mask & (1 << id) != 0
&& mem_type.properties.contains(memory_properties)
})
.unwrap()
.into()
}
複製程式碼
allocate_memory()
根據 MTLStorageMode
型別分配 MTLBuffer
或 MTLHeap
(gfx目前用&& false
直接關閉了 MTLHeapDescriptor
分支。我問過他們,原因是他們還沒找到 MTLHeap
合適的使用場合。),如果 MTLStorageMode
為MTLStorageMode::Private
則在後面的bind_image_memory()
分配 MTLTexture
物件。
unsafe fn allocate_memory(&self, memory_type: hal::MemoryTypeId, size: u64)
-> Result<n::Memory, AllocationError> {
// 由於memory_type在前面執行為0,則2D紋理在macOS上執行結果為
// storage = Private, cache = DefaultCache
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.shared.private_caps.resource_heaps
&& storage != MTLStorageMode::Shared
&& false {
// iOS 10開始支援MTLHeapDescriptor,gfx沒做適配
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 {
// 由於前一分支有`&& false`且`storage = Private`,Image掛載的Memory執行此分支。
// 後續方法呼叫才分配實際儲存空間
n::MemoryHeap::Private
} else {
// Buffer掛載的Memory執行此分支,分配實際儲存空間
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))
}
複製程式碼
metal-backend的MemoryHeap
與Memory
定義如下所示:
#[derive(Debug)]
pub struct Memory {
pub(crate) heap: MemoryHeap,
pub(crate) size: u64,
}
#[derive(Debug)]
pub(crate) enum MemoryHeap {
Private,
Public(MemoryTypeId, metal::Buffer),
Native(metal::Heap),
}
複製程式碼
分配MTLTexture物件
bind_image_memory(memory: &n::Memory, offset: u64, image: &mut n::Image)
根據 MemoryHeap
決定從 MTLDevice
分配 MTLTexture
物件或基於 MTLHeap
建立 MTLTexture
別名,即共享 MTLHeap
儲存空間。
unsafe fn bind_image_memory(
&self, memory: &n::Memory, offset: u64, image: &mut n::Image
) -> Result<(), BindError> {
let like = {
let (descriptor, mip_sizes) = match image.like {
n::ImageLike::Unbound { ref descriptor, ref mip_sizes, .. } =>
(descriptor, mip_sizes),
n::ImageLike::Texture(..) |
n::ImageLike::Buffer(..) => panic!("Expected Image::Unbound"),
};
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());
descriptor.set_resource_options(resource_options);
n::ImageLike::Texture(
heap.new_texture(descriptor)
.unwrap_or_else(|| {
// heap.new_texture執行失敗的兜底方案
// 目前gfx邏輯不執行此 match 分支
// TODO: disable hazard tracking?
self.shared.device
.lock()
.new_texture(&descriptor)
})
)
},
n::MemoryHeap::Public(_memory_type, ref cpu_buffer) => {
assert_eq!(mip_sizes.len(), 1);
n::ImageLike::Buffer(
n::Buffer::Bound {
raw: cpu_buffer.clone(),
range: offset .. offset + mip_sizes[0] as u64,
options: MTLResourceOptions::StorageModeShared,
}
)
}
// 由前面n::MemoryHeap::Private可知,將執行下面支援,分配出MTLTexture物件。
n::MemoryHeap::Private => {
descriptor.set_storage_mode(MTLStorageMode::Private);
n::ImageLike::Texture(
self.shared.device
.lock()
.new_texture(descriptor)
)
}
}
};
Ok(image.like = like)
}
複製程式碼
上傳CPU資料到紋理
建立Staging Buffer
此部分參考Buffer的相關操作。
建立Fence
建立用於資料拷貝的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命令
pub trait Backend: 'static + Sized + Eq + Clone + Hash + fmt::Debug + Any + Send + Sync {
type CommandBuffer: command::RawCommandBuffer<Self>;
}
複製程式碼
/// A strongly-typed command buffer that will only implement methods
/// that are valid for the operations it supports.
pub struct CommandBuffer<B: Backend, C, S = OneShot, L = Primary, R = <B as Backend>::CommandBuffer>
{
pub(crate) raw: R,
pub(crate) _marker: PhantomData<(B, C, S, L)>,
}
複製程式碼
trait RawCommandBuffer
定義了一系列具體後端需要實現的介面。
/// Copies regions from the source buffer to the destination image.
unsafe fn copy_buffer_to_image<T>(
&mut self,
src: &B::Buffer,
dst: &B::Image,
dst_layout: Layout,
regions: T,
) where
T: IntoIterator,
T::Item: Borrow<BufferImageCopy>;
複製程式碼
hal/command/compute.rs
hal/command/graphics.rs
hal/command/transfer.rs
分別給 hal/command/mod.rs
中定義的 CommandBuffer
新增相應用途的API。傳輸資料用 copy_***
系列API,故呼叫了 hal/command/transfer.rs
的 copy_buffer_to_image()
,它內部轉發給具體圖形後端的同名方法。
/// Identical to the `RawCommandBuffer` method of the same name.
pub unsafe 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)
}
複製程式碼
metal-backend的 copy_buffer_to_image()
實現如下:
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) => {
// 由前面可知,從StagingBuffer通過BlitCommandBuffer拷貝資料到Texture執行此分支
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),
}
}))
}
}
}
複製程式碼
由上可知,Buffer到Buffer的拷貝執行 copy_buffer()
。
unsafe fn copy_buffer<T>(
&mut self,
src: &native::Buffer,
dst: &native::Buffer,
regions: T,
) where
T: IntoIterator,
T::Item: Borrow<com::BufferCopy>,
{
let pso = &*self.shared.service_pipes.copy_buffer;
let wg_size = MTLSize {
width: pso.thread_execution_width(),
height: 1,
depth: 1,
};
let (src_raw, src_range) = src.as_bound();
let (dst_raw, dst_range) = dst.as_bound();
let mut compute_datas = Vec::new();
let mut inner = self.inner.borrow_mut();
let mut blit_commands = Vec::new();
let mut compute_commands = vec![ //TODO: get rid of heap
soft::ComputeCommand::BindPipeline(pso),
];
for region in regions {
let r = region.borrow();
if r.size % WORD_SIZE as u64 == 0 &&
r.src % WORD_SIZE as u64 == 0 &&
r.dst % WORD_SIZE as u64 == 0
{
blit_commands.push(soft::BlitCommand::CopyBuffer {
src: AsNative::from(src_raw),
dst: AsNative::from(dst_raw),
region: com::BufferCopy {
src: r.src + src_range.start,
dst: r.dst + dst_range.start,
size: r.size,
},
});
} else {
// not natively supported, going through a compute shader
assert_eq!(0, r.size >> 32);
let src_aligned = r.src & !(WORD_SIZE as u64 - 1);
let dst_aligned = r.dst & !(WORD_SIZE as u64 - 1);
let offsets = (r.src - src_aligned) | ((r.dst - dst_aligned) << 16);
let size_and_offsets = [r.size as u32, offsets as u32];
compute_datas.push(Box::new(size_and_offsets));
let wg_count = MTLSize {
width: (r.size + wg_size.width - 1) / wg_size.width,
height: 1,
depth: 1,
};
compute_commands.push(soft::ComputeCommand::BindBuffer {
index: 0,
buffer: AsNative::from(dst_raw),
offset: dst_aligned + dst_range.start,
});
compute_commands.push(soft::ComputeCommand::BindBuffer {
index: 1,
buffer: AsNative::from(src_raw),
offset: src_aligned + src_range.start,
});
compute_commands.push(soft::ComputeCommand::BindBufferData {
index: 2,
words: unsafe {
// Rust doesn't see that compute_datas will not lose this item
// and the boxed contents can't be moved otherwise.
mem::transmute(&compute_datas.last().unwrap()[..])
},
});
compute_commands.push(soft::ComputeCommand::Dispatch {
wg_size,
wg_count,
});
}
}
let sink = inner.sink();
if !blit_commands.is_empty() {
sink.blit_commands(blit_commands.into_iter());
}
if compute_commands.len() > 1 { // first is bind PSO
sink.quick_compute("copy_buffer", compute_commands.into_iter());
}
}
複製程式碼
結束Command Buffer編碼
複製程式碼
下載紋理資料到CPU
假設用Vulkan介面模擬glReadPixels()
,大概分成以下幾步:
Sampler
取樣器表達了Shader讀取紋理(Image)的方式,比如U、V、W座標超出範圍時是採取重複還是截斷策略。
建立Sampler
使用Vulkan介面實現
VkSamplerCreateInfo samplerInfo;
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.pNext = nullptr;
samplerInfo.flags = 0;
samplerInfo.mipmapMode = mVkMipmapMode;
samplerInfo.minFilter = mVkMinFilter;
samplerInfo.magFilter = mVkMagFilter;
samplerInfo.addressModeU = mVkAddressModeU;
samplerInfo.addressModeV = mVkAddressModeV;
samplerInfo.addressModeW = mVkAddressModeW;
samplerInfo.minLod = mMinLod;
samplerInfo.maxLod = mMaxLod;
samplerInfo.mipLodBias = mMipLodBias;
samplerInfo.anisotropyEnable = mAnisotropyEnabled;
samplerInfo.maxAnisotropy = mMaxAnisotropy;
samplerInfo.compareEnable = mCompareEnabled;
samplerInfo.compareOp = mVkCompareOp;
samplerInfo.borderColor = mVkBorderColor;
samplerInfo.unnormalizedCoordinates = mUnnormalizedCoordinates;
VkResult err = vkCreateSampler(mVkDevice, &samplerInfo, nullptr, &mVkSampler);
// deal with (err != VK_ERROR_OUT_OF_HOST_MEMORY && err != VK_ERROR_OUT_OF_DEVICE_MEMORY && err != VK_ERROR_TOO_MANY_OBJECTS);
複製程式碼
使用gfx-hal介面實現
let sampler = match device.create_sampler(image::SamplerInfo::new(image::Filter::Linear, image::WrapMode::Clamp)) {
Ok(sampler) => Some(sampler),
Err(msg) => {
error!("{}", msg);
None
};
}
複製程式碼
使用gfx-hal介面原始碼
/// Specifies how to sample from an image.
// TODO: document the details of sampling.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SamplerInfo {
/// Minification filter method to use.
pub min_filter: Filter,
/// Magnification filter method to use.
pub mag_filter: Filter,
/// Mip filter method to use.
pub mip_filter: Filter,
/// Wrapping mode for each of the U, V, and W axis (S, T, and R in OpenGL
/// speak).
pub wrap_mode: (WrapMode, WrapMode, WrapMode),
/// This bias is added to every computed mipmap level (N + lod_bias). For
/// example, if it would select mipmap level 2 and lod_bias is 1, it will
/// use mipmap level 3.
pub lod_bias: Lod,
/// This range is used to clamp LOD level used for sampling.
pub lod_range: Range<Lod>,
/// Comparison mode, used primary for a shadow map.
pub comparison: Option<Comparison>,
/// Border color is used when one of the wrap modes is set to border.
pub border: PackedColor,
/// Anisotropic filtering.
pub anisotropic: Anisotropic,
}
impl SamplerInfo {
/// Create a new sampler description with a given filter method for all filtering operations
/// and a wrapping mode, using no LOD modifications.
pub fn new(filter: Filter, wrap: WrapMode) -> Self {
SamplerInfo {
min_filter: filter,
mag_filter: filter,
mip_filter: filter,
wrap_mode: (wrap, wrap, wrap),
lod_bias: Lod(0),
lod_range: Lod(-8000)..Lod(8000),
comparison: None,
border: PackedColor(0),
anisotropic: Anisotropic::Off,
}
}
}
複製程式碼
gfx-hal Vulkan模組原始碼
fn create_sampler(&self, sampler_info: image::SamplerInfo) -> Result<n::Sampler, d::AllocationError> {
use hal::pso::Comparison;
let (anisotropy_enable, max_anisotropy) = match sampler_info.anisotropic {
image::Anisotropic::Off => (vk::VK_FALSE, 1.0),
image::Anisotropic::On(aniso) => {
if self.raw.1.contains(Features::SAMPLER_ANISOTROPY) {
(vk::VK_TRUE, aniso as f32)
} else {
warn!("Anisotropy({}) was requested on a device with disabled feature", aniso);
(vk::VK_FALSE, 1.0)
}
},
};
let info = vk::SamplerCreateInfo {
s_type: vk::StructureType::SamplerCreateInfo,
p_next: ptr::null(),
flags: vk::SamplerCreateFlags::empty(),
mag_filter: conv::map_filter(sampler_info.mag_filter),
min_filter: conv::map_filter(sampler_info.min_filter),
mipmap_mode: conv::map_mip_filter(sampler_info.mip_filter),
address_mode_u: conv::map_wrap(sampler_info.wrap_mode.0),
address_mode_v: conv::map_wrap(sampler_info.wrap_mode.1),
address_mode_w: conv::map_wrap(sampler_info.wrap_mode.2),
mip_lod_bias: sampler_info.lod_bias.into(),
anisotropy_enable,
max_anisotropy,
compare_enable: if sampler_info.comparison.is_some() { vk::VK_TRUE } else { vk::VK_FALSE },
compare_op: conv::map_comparison(sampler_info.comparison.unwrap_or(Comparison::Never)),
min_lod: sampler_info.lod_range.start.into(),
max_lod: sampler_info.lod_range.end.into(),
border_color: match conv::map_border_color(sampler_info.border) {
Some(bc) => bc,
None => {
error!("Unsupported border color {:x}", sampler_info.border.0);
vk::BorderColor::FloatTransparentBlack
}
},
unnormalized_coordinates: vk::VK_FALSE,
};
let result = unsafe {
self.raw.0.create_sampler(&info, None)
};
match result {
Ok(sampler) => Ok(n::Sampler(sampler)),
Err(vk::Result::ErrorTooManyObjects) => Err(d::AllocationError::TooManyObjects),
Err(vk::Result::ErrorOutOfHostMemory) => Err(d::OutOfMemory::OutOfHostMemory.into()),
Err(vk::Result::ErrorOutOfDeviceMemory) => Err(d::OutOfMemory::OutOfDeviceMemory.into()),
_ => unreachable!(),
}
}
複製程式碼
gfx-hal Metal模組原始碼
To create a sampler, first create a
MTLSamplerDescriptor
object and configure the descriptor’s properties. Then call themakeSamplerState(descriptor:)
method on theMTLDevice
object that will use this sampler. Once the sampler is created, the descriptor can be disposed of or reconfigured to create other sampler objects.
fn create_sampler(&self, info: image::SamplerInfo) -> Result<n::Sampler, AllocationError> {
// todo 缺少supportArgumentBuffers API iOS 11
let descriptor = metal::SamplerDescriptor::new();
descriptor.set_min_filter(conv::map_filter(info.min_filter));
descriptor.set_mag_filter(conv::map_filter(info.mag_filter));
descriptor.set_mip_filter(match info.mip_filter {
// Note: this shouldn't be required, but Metal appears to be confused when mipmaps
// are provided even with trivial LOD bias.
image::Filter::Nearest if info.lod_range.end < image::Lod::from(0.5) =>
MTLSamplerMipFilter::NotMipmapped,
image::Filter::Nearest => MTLSamplerMipFilter::Nearest,
image::Filter::Linear => MTLSamplerMipFilter::Linear,
});
if let image::Anisotropic::On(aniso) = info.anisotropic {
descriptor.set_max_anisotropy(aniso as _);
}
let (s, t, r) = info.wrap_mode;
descriptor.set_address_mode_s(conv::map_wrap_mode(s));
descriptor.set_address_mode_t(conv::map_wrap_mode(t));
descriptor.set_address_mode_r(conv::map_wrap_mode(r));
unsafe { descriptor.set_lod_bias(info.lod_bias.into()) };
descriptor.set_lod_min_clamp(info.lod_range.start.into());
descriptor.set_lod_max_clamp(info.lod_range.end.into());
let caps = &self.private_caps;
// TODO: Clarify minimum macOS version with Apple (43707452)
if (caps.os_is_mac && caps.has_version_at_least(10, 13)) ||
(!caps.os_is_mac && caps.has_version_at_least(9, 0)) {
descriptor.set_lod_average(true); // optimization
}
if let Some(fun) = info.comparison {
// iOS 9.0 API
// The sampler comparison function used when sampling texels from a depth texture.
descriptor.set_compare_function(conv::map_compare_function(fun));
}
if [r, s, t].iter().any(|&am| am == image::WrapMode::Border) {
descriptor.set_border_color(match info.border.0 {
0x00000000 => MTLSamplerBorderColor::TransparentBlack,
0x000000FF => MTLSamplerBorderColor::OpaqueBlack,
0xFFFFFFFF => MTLSamplerBorderColor::OpaqueWhite,
other => {
error!("Border color 0x{:X} is not supported", other);
MTLSamplerBorderColor::TransparentBlack
}
});
}
Ok(n::Sampler(
self.shared.device
.lock()
.new_sampler(&descriptor)
))
}
複製程式碼
銷燬Sampler
使用Vulkan介面實現
if (mVkSampler != VK_NULL_HANDLE) {
vkDestroySampler(mVkDevice, mVkSampler, nullptr);
mVkSampler = VK_NULL_HANDLE;
}
複製程式碼
使用gfx-hal介面實現
device.destroy_sampler(sampler);
複製程式碼
gfx-hal Vulkan模組原始碼
fn destroy_sampler(&self, sampler: n::Sampler) {
unsafe { self.raw.0.destroy_sampler(sampler.0, None); }
}
複製程式碼
gfx-hal Metal模組原始碼
fn destroy_sampler(&self, _sampler: n::Sampler) {
}
複製程式碼