Vulkan (gfx-hal) 实现 OpenGL / ES Framebuffer

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

上次修改:2019.6.4 删除CommandBufferSubmission等无关内容。android

Vulkan(gfx-hal) 接口模拟实现 OpenGL / ES Framebuffer 功能的过程比较复杂,涉及多个组件:SurfaceSwapchainRenderPassFramebufferImageView。其中,SurfaceSwapchain只用于实现EGL提供的 System Framebuffer,即 Default Framebuffer,对于用户建立(User defined)的 Framebuffer是非必需的。app

Surface

/// A `Surface` abstracts the surface of a native window, which will be presented
/// on the display.
pub trait Surface<B: Backend>: fmt::Debug + Any + Send + Sync {
    /// Retrieve the surface image kind.
    fn kind(&self) -> image::Kind;

    /// Check if the queue family supports presentation to this surface.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # surface.supports_queue_family(family)
    /// ```
    fn supports_queue_family(&self, family: &B::QueueFamily) -> bool;

    /// Query surface capabilities, formats, and present modes for this physical device.
    ///
    /// Use this function for configuring swapchain creation.
    ///
    /// Returns a tuple of surface capabilities and formats.
    /// If formats is `None` than the surface has no preferred format and the
    /// application may use any desired format.
    fn compatibility(
        &self,
        physical_device: &B::PhysicalDevice,
    ) -> (SurfaceCapabilities, Option<Vec<Format>>, Vec<PresentMode>);
}
复制代码

函数compatibility(...) -> (SurfaceCapabilities...)从返回值看是歧义的,所以我提议重命名以便于理解,如下是讨论记录。ide

Michael(LAI) @Michael-Lfx: Should we rename Surface::compatibility() to Surface::capabilities()?函数

Dzmitry Malyshau @kvark: the idea was that it's a compatibility between the surface and the physical device... I think we should just split the function into multiple smaller ones insteadpost

(as suggested by someone before)ui

Surface建立流程

Vulkan定义Surface为VkSurfaceKHR,从KHR后缀可知它是平台相关的,所以gfx-hal让具体backend提供建立Surface实例的接口,好比instance.create_surface_android(...),若是开发跨平台应用,可以使用统一接口instance.create_surface(&window);,此接口须要winit::Windowthis

第三方winit库提供了管理macOS/Android/Windows等平台的窗口建立、事件响应等,功能相似SDL。idea

SurfaceCapabilities

/// Describes information about what a `Surface`'s properties are.
/// Fetch this with `surface.compatibility(device)`.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SurfaceCapabilities {
    /// Number of presentable images supported by the adapter for a swapchain
    /// created from this surface.
    ///
    /// - `image_count.start` must be at least 1.
    /// - `image_count.end` must be larger of equal to `image_count.start`.
    pub image_count: Range<SwapImageIndex>,

    /// Current extent of the surface.
    ///
    /// `None` if the surface has no explicit size, depending on the swapchain extent.
    pub current_extent: Option<Extent2D>,

    /// Range of supported extents.
    ///
    /// `current_extent` must be inside this range.
    pub extents: Range<Extent2D>,

    /// Maximum number of layers supported for presentable images.
    ///
    /// Must be at least 1.
    pub max_image_layers: image::Layer,

    /// Supported image usage flags.
    pub usage: image::Usage,

    /// A bitmask of supported alpha composition modes.
    pub composite_alpha: CompositeAlpha,
}
复制代码

PresentMode

/// Specifies the mode regulating how a swapchain presents frames.
#[repr(C)]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub enum PresentMode {
    /// Don't ever wait for v-sync.
    Immediate = 0,
    /// Wait for v-sync, overwrite the last rendered frame.
    Mailbox = 1,
    /// Present frames in the same order they are rendered.
    Fifo = 2,
    /// Don't wait for the next v-sync if we just missed it.
    Relaxed = 3,
}
复制代码

Swapchain

/// The `Swapchain` is the backend representation of the surface.
/// It consists of multiple buffers, which will be presented on the surface.
pub trait Swapchain<B: Backend>: fmt::Debug + Any + Send + Sync {
    /// Acquire a new swapchain image for rendering. This needs to be called before presenting.
    ///
    /// May fail according to one of the reasons indicated in `AcquireError` enum.
    ///
    /// # Synchronization
    ///
    /// The acquired image will not be immediately available when the function returns.
    /// Once available the provided [`Semaphore`](../trait.Resources.html#associatedtype.Semaphore)
    /// and [`Fence`](../trait.Resources.html#associatedtype.Fence) will be signaled.
    ///
    /// # Examples
    ///
    /// ```no_run
    ///
    /// ```
    unsafe fn acquire_image(
        &mut self,
        timeout_ns: u64,
        semaphore: Option<&B::Semaphore>,
        fence: Option<&B::Fence>,
    ) -> Result<(SwapImageIndex, Option<Suboptimal>), AcquireError>;

    /// Present one acquired image.
    ///
    /// # Safety
    ///
    /// The passed queue _must_ support presentation on the surface, which is
    /// used for creating this swapchain.
    ///
    /// # Examples
    ///
    /// ```no_run
    ///
    /// ```
    unsafe fn present<'a, C, S, Iw>(
        &'a self,
        present_queue: &mut CommandQueue<B, C>,
        image_index: SwapImageIndex,
        wait_semaphores: Iw,
    ) -> Result<Option<Suboptimal>, PresentError>
    where
        Self: 'a + Sized + Borrow<B::Swapchain>,
        C: Capability,
        S: 'a + Borrow<B::Semaphore>,
        Iw: IntoIterator<Item = &'a S>,
    {
        present_queue.present(iter::once((self, image_index)), wait_semaphores)
    }

    /// Present one acquired image without any semaphore synchronization.
    unsafe fn present_without_semaphores<C>(
        &self,
        present_queue: &mut CommandQueue<B, C>,
        image_index: SwapImageIndex,
    ) -> Result<Option<Suboptimal>, PresentError>
    where
        Self: Sized + Borrow<B::Swapchain>,
        C: Capability,
    {
        self.present::<_, B::Semaphore, _>(present_queue, image_index, iter::empty())
    }
}
复制代码

Swapchain支持两个操做,先acquire_imagepresentpresent_without_semaphores提供了便捷操做。spa

Backbuffer

/// Swapchain backbuffer type
#[derive(Debug)]
pub enum Backbuffer<B: Backend> {
    /// Color image chain
    Images(Vec<B::Image>),
    /// A single opaque framebuffer
    Framebuffer(B::Framebuffer),
}
复制代码

Images用于Metal、Vulkan等非OpenGL图形库,Framebuffer只为兼容OpenGL而存在。这种划分在设计上显示有些分裂,最近gfx-hal会统一这一行为。

建立Swapchain时获得当前底层支持的Backbuffer类型,Vukan同一时期的API将建立ImageViewFramebuffer,OpenGL则直接返回Framebuffer

let (mut swapchain, mut backbuffer) =
    unsafe { device.create_swapchain(&mut surface, swapchain_config, Some(old_swapchain)) }
        .expect("Can't create swapchain");
        
let (mut frame_images, mut framebuffers) = match backbuffer {
    Backbuffer::Images(images) => {
        let pairs = images
            .into_iter()
            .map(|image| unsafe {
                let rtv = device
                    .create_image_view(
                        &image,
                        i::ViewKind::D2,
                        format,
                        Swizzle::NO,
                        Extent {
                            width: swapchain.extent.width as _,
                            height: swapchain.extent.height as _,
                            depth: 1,
                        },
                    )
                    .unwrap();
                (image, rtv)
            })
            .collect::<Vec<_>>();
        let fbos = pairs
            .iter()
            .map(|&(_, ref rtv)| unsafe {
                device
                    .create_framebuffer(&render_pass, Some(rtv), extent)
                    .unwrap()
            })
            .collect();
        (pairs, fbos)
    }
    Backbuffer::Framebuffer(fbo) => (Vec::new(), vec![fbo]),
};
复制代码

Suboptimal

/// Marker value returned if the swapchain no longer matches the surface properties exactly,
/// but can still be used to present to the surface successfully.
pub struct Suboptimal;
复制代码

什么状况下会出现Suboptimal

好比,当拖到窗口从非HDR到HDR显示器时出现Suboptimal,此时可处理这个错误,也可不处理。不处理则发挥不了HDR的色彩优点。

SwapchainConfig

/// Contains all the data necessary to create a new `Swapchain`:
/// color, depth, and number of images.
///
/// # Examples
///
/// This type implements the builder pattern, method calls can be
/// easily chained.
///
/// ```no_run
/// # extern crate gfx_hal;
/// # fn main() {
/// # use gfx_hal::{SwapchainConfig};
/// # use gfx_hal::format::Format;
/// let config = SwapchainConfig::new(100, 100, Format::Bgra8Unorm, 2);
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct SwapchainConfig {
    /// Presentation mode.
    pub present_mode: PresentMode,
    /// Alpha composition mode.
    pub composite_alpha: CompositeAlpha,
    /// Format of the backbuffer images.
    pub format: Format,
    /// Requested image extent. Must be in
    /// `SurfaceCapabilities::extents` range.
    pub extent: Extent2D,
    /// Number of images in the swapchain. Must be in
    /// `SurfaceCapabilities::image_count` range.
    pub image_count: SwapImageIndex,
    /// Number of image layers. Must be lower or equal to
    /// `SurfaceCapabilities::max_image_layers`.
    pub image_layers: image::Layer,
    /// Image usage of the backbuffer images.
    pub image_usage: image::Usage,
}
复制代码

SwapchainConfig初始化流程以下所示:

let (caps, formats, present_modes) = surface.compatibility(&physical_device);
println!("formats: {:?}", formats);
let format = formats
    .map_or(format::Format::Rgba8Srgb, |formats| {
        formats
            .iter()
            .find(|format| format.base_format().1 == ChannelType::Srgb)
            .map(|format| *format)
            .unwrap_or(formats[0])
    });

println!("Surface format: {:?}", format);
let swap_config = SwapchainConfig::from_caps(&caps, format);
复制代码

RenderPass

A render pass represents a collection of attachments, subpasses, and dependencies between the subpasses, and describes how the attachments are used over the course of the subpasses. The use of a render pass in a command buffer is a render pass instance.

www.khronos.org/registry/vu…

RenderPass包含AttachmentSubpassDescSubpassDependency。RTT场景因为渲染到ImageAttachment::format得用输出纹理Image生成的ImageViewformat值。

初始化RenderPass

根据前面可知,建立RenderPass须要先建立它的子组件,下面逐次描述。

  1. 建立Attachment

    let attachment = pass::Attachment {
        format: Some(image.format),
        samples: 1,
        ops: pass::AttachmentOps::new(
            pass::AttachmentLoadOp::Clear,
            pass::AttachmentStoreOp::Store,
        ),
        stencil_ops: pass::AttachmentOps::DONT_CARE,
        layouts: image::Layout::Undefined..image::Layout::Present,
    };
    复制代码

    值得注意的是,渲染到View和RTT须要配置不一样的layouts值。同理,AttachmentOps也要根据Blend等操做进行相应配置。若是支持Depth/Stencil,则须要建立新的Attachment,即须要两个Attachment分别表示颜色与深度数据。

  2. 建立SubpassDesc

    let subpass = pass::SubpassDesc {
        colors: &[(0, image::Layout::ColorAttachmentOptimal)],
        depth_stencil: None,
        inputs: &[],
        resolves: &[],
        preserves: &[],
    };
    复制代码
  3. 建立SubpassDependency

    let dependency = pass::SubpassDependency {
        passes: pass::SubpassRef::External..pass::SubpassRef::Pass(0),
        stages: PipelineStage::COLOR_ATTACHMENT_OUTPUT
            ..PipelineStage::COLOR_ATTACHMENT_OUTPUT,
        accesses: image::Access::empty()
            ..(image::Access::COLOR_ATTACHMENT_READ | image::Access::COLOR_ATTACHMENT_WRITE),
    };
    复制代码
  4. 建立RenderPass 前面组件就绪后,可从Device建立一个RenderPass

    let render_pass = device.create_render_pass(&[attachment], &[subpass], &[dependency]);
    复制代码
相关文章
相关标签/搜索