gfx-hal Texture操作原始碼簡析

熊皮皮發表於2018-11-28

文件列表見: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()根據引數建立並配置MTLTextureDescriptorusage: 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::ImageMTLTextureDescriptor屬性及當前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 型別分配 MTLBufferMTLHeap(gfx目前用&& false直接關閉了 MTLHeapDescriptor 分支。我問過他們,原因是他們還沒找到 MTLHeap 合適的使用場合。),如果 MTLStorageModeMTLStorageMode::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的MemoryHeapMemory定義如下所示:

#[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.rscopy_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 the makeSamplerState(descriptor:) method on the MTLDevice object that will use this sampler. Once the sampler is created, the descriptor can be disposed of or reconfigured to create other sampler objects.

developer.apple.com/documentati…

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) {
}
複製程式碼

相關文章