2020-11-18 03:04:25 +11:00
|
|
|
//! A convenience layer on top of raw hal.
|
|
|
|
//!
|
|
|
|
//! This layer takes care of some lifetime and synchronization bookkeeping.
|
|
|
|
//! It is likely that it will also take care of compile time and runtime
|
|
|
|
//! negotiation of backends (Vulkan, DX12), but right now it's Vulkan-only.
|
|
|
|
|
|
|
|
use std::any::Any;
|
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
|
|
|
|
|
|
|
use crate::vulkan;
|
2020-11-25 07:36:27 +11:00
|
|
|
use crate::DescriptorSetBuilder as DescriptorSetBuilderTrait;
|
|
|
|
use crate::PipelineBuilder as PipelineBuilderTrait;
|
2020-11-26 07:43:42 +11:00
|
|
|
use crate::{Device, Error, SamplerParams};
|
2020-11-18 03:04:25 +11:00
|
|
|
|
|
|
|
pub type MemFlags = <vulkan::VkDevice as Device>::MemFlags;
|
|
|
|
pub type Semaphore = <vulkan::VkDevice as Device>::Semaphore;
|
|
|
|
pub type Pipeline = <vulkan::VkDevice as Device>::Pipeline;
|
|
|
|
pub type DescriptorSet = <vulkan::VkDevice as Device>::DescriptorSet;
|
|
|
|
pub type QueryPool = <vulkan::VkDevice as Device>::QueryPool;
|
2020-11-26 07:43:42 +11:00
|
|
|
pub type Sampler = <vulkan::VkDevice as Device>::Sampler;
|
2020-11-18 03:04:25 +11:00
|
|
|
|
|
|
|
type Fence = <vulkan::VkDevice as Device>::Fence;
|
|
|
|
|
|
|
|
type VkImage = <vulkan::VkDevice as Device>::Image;
|
|
|
|
type VkBuffer = <vulkan::VkDevice as Device>::Buffer;
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Session(Arc<SessionInner>);
|
|
|
|
|
|
|
|
struct SessionInner {
|
|
|
|
device: vulkan::VkDevice,
|
|
|
|
cmd_buf_pool: Mutex<Vec<(vulkan::CmdBuf, Fence)>>,
|
|
|
|
/// Command buffers that are still pending (so resources can't be freed).
|
|
|
|
pending: Mutex<Vec<SubmittedCmdBufInner>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct CmdBuf {
|
|
|
|
cmd_buf: vulkan::CmdBuf,
|
|
|
|
fence: Fence,
|
|
|
|
resources: Vec<Box<dyn Any>>,
|
|
|
|
session: Weak<SessionInner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Maybe "pending" is a better name?
|
|
|
|
pub struct SubmittedCmdBuf(Option<SubmittedCmdBufInner>, Weak<SessionInner>);
|
|
|
|
|
|
|
|
struct SubmittedCmdBufInner {
|
|
|
|
cmd_buf: vulkan::CmdBuf,
|
|
|
|
fence: Fence,
|
|
|
|
resources: Vec<Box<dyn Any>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Image(Arc<ImageInner>);
|
|
|
|
|
|
|
|
struct ImageInner {
|
|
|
|
image: VkImage,
|
|
|
|
session: Weak<SessionInner>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Buffer(Arc<BufferInner>);
|
|
|
|
|
|
|
|
struct BufferInner {
|
|
|
|
buffer: VkBuffer,
|
|
|
|
session: Weak<SessionInner>,
|
|
|
|
}
|
|
|
|
|
2020-11-25 07:36:27 +11:00
|
|
|
pub struct PipelineBuilder(vulkan::PipelineBuilder);
|
|
|
|
|
|
|
|
pub struct DescriptorSetBuilder(vulkan::DescriptorSetBuilder);
|
|
|
|
|
2020-11-18 03:04:25 +11:00
|
|
|
impl Session {
|
|
|
|
pub fn new(device: vulkan::VkDevice) -> Session {
|
|
|
|
Session(Arc::new(SessionInner {
|
|
|
|
device,
|
|
|
|
cmd_buf_pool: Default::default(),
|
|
|
|
pending: Default::default(),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cmd_buf(&self) -> Result<CmdBuf, Error> {
|
|
|
|
self.poll_cleanup();
|
|
|
|
let (cmd_buf, fence) = if let Some(cf) = self.0.cmd_buf_pool.lock().unwrap().pop() {
|
|
|
|
cf
|
|
|
|
} else {
|
|
|
|
let cmd_buf = self.0.device.create_cmd_buf()?;
|
|
|
|
let fence = unsafe { self.0.device.create_fence(false)? };
|
|
|
|
(cmd_buf, fence)
|
|
|
|
};
|
|
|
|
Ok(CmdBuf {
|
|
|
|
cmd_buf,
|
|
|
|
fence,
|
|
|
|
resources: Vec::new(),
|
|
|
|
session: Arc::downgrade(&self.0),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll_cleanup(&self) {
|
|
|
|
let mut pending = self.0.pending.lock().unwrap();
|
|
|
|
unsafe {
|
|
|
|
let mut i = 0;
|
|
|
|
while i < pending.len() {
|
|
|
|
if let Ok(true) = self.0.device.get_fence_status(pending[i].fence) {
|
|
|
|
let item = pending.swap_remove(i);
|
|
|
|
// TODO: wait is superfluous, can just reset
|
|
|
|
let _ = self.0.device.wait_and_reset(&[item.fence]);
|
|
|
|
self.0
|
|
|
|
.cmd_buf_pool
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.push((item.cmd_buf, item.fence));
|
|
|
|
std::mem::drop(item.resources);
|
|
|
|
} else {
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn run_cmd_buf(
|
|
|
|
&self,
|
|
|
|
cmd_buf: CmdBuf,
|
|
|
|
wait_semaphores: &[Semaphore],
|
|
|
|
signal_semaphores: &[Semaphore],
|
|
|
|
) -> Result<SubmittedCmdBuf, Error> {
|
|
|
|
self.0.device.run_cmd_buf(
|
|
|
|
&cmd_buf.cmd_buf,
|
|
|
|
wait_semaphores,
|
|
|
|
signal_semaphores,
|
|
|
|
Some(&cmd_buf.fence),
|
|
|
|
)?;
|
|
|
|
Ok(SubmittedCmdBuf(
|
|
|
|
Some(SubmittedCmdBufInner {
|
|
|
|
cmd_buf: cmd_buf.cmd_buf,
|
|
|
|
fence: cmd_buf.fence,
|
|
|
|
resources: cmd_buf.resources,
|
|
|
|
}),
|
|
|
|
cmd_buf.session,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_buffer(&self, size: u64, mem_flags: MemFlags) -> Result<Buffer, Error> {
|
|
|
|
let buffer = self.0.device.create_buffer(size, mem_flags)?;
|
|
|
|
Ok(Buffer(Arc::new(BufferInner {
|
|
|
|
buffer,
|
|
|
|
session: Arc::downgrade(&self.0),
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn create_image2d(
|
|
|
|
&self,
|
|
|
|
width: u32,
|
|
|
|
height: u32,
|
|
|
|
mem_flags: MemFlags,
|
|
|
|
) -> Result<Image, Error> {
|
|
|
|
let image = self.0.device.create_image2d(width, height, mem_flags)?;
|
|
|
|
Ok(Image(Arc::new(ImageInner {
|
|
|
|
image,
|
|
|
|
session: Arc::downgrade(&self.0),
|
|
|
|
})))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn create_semaphore(&self) -> Result<Semaphore, Error> {
|
|
|
|
self.0.device.create_semaphore()
|
|
|
|
}
|
|
|
|
|
2020-11-25 07:36:27 +11:00
|
|
|
/// This creates a pipeline that operates on some buffers and images.
|
2020-11-18 03:04:25 +11:00
|
|
|
///
|
|
|
|
/// The descriptor set layout is just some number of storage buffers and storage images (this might change).
|
|
|
|
pub unsafe fn create_simple_compute_pipeline(
|
|
|
|
&self,
|
|
|
|
code: &[u8],
|
|
|
|
n_buffers: u32,
|
|
|
|
) -> Result<Pipeline, Error> {
|
2020-11-25 07:36:27 +11:00
|
|
|
self.pipeline_builder()
|
|
|
|
.add_buffers(n_buffers)
|
|
|
|
.create_compute_pipeline(self, code)
|
2020-11-18 03:04:25 +11:00
|
|
|
}
|
|
|
|
|
2020-11-25 07:36:27 +11:00
|
|
|
/// Create a descriptor set for a simple pipeline that just references buffers.
|
|
|
|
pub unsafe fn create_simple_descriptor_set<'a>(
|
2020-11-18 03:04:25 +11:00
|
|
|
&self,
|
|
|
|
pipeline: &Pipeline,
|
2020-11-25 07:36:27 +11:00
|
|
|
buffers: impl IntoRefs<'a, Buffer>,
|
2020-11-18 03:04:25 +11:00
|
|
|
) -> Result<DescriptorSet, Error> {
|
2020-11-25 07:36:27 +11:00
|
|
|
self.descriptor_set_builder()
|
|
|
|
.add_buffers(buffers)
|
|
|
|
.build(self, pipeline)
|
2020-11-18 03:04:25 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a query pool for timestamp queries.
|
|
|
|
pub fn create_query_pool(&self, n_queries: u32) -> Result<QueryPool, Error> {
|
|
|
|
self.0.device.create_query_pool(n_queries)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn fetch_query_pool(&self, pool: &QueryPool) -> Result<Vec<f64>, Error> {
|
|
|
|
self.0.device.fetch_query_pool(pool)
|
|
|
|
}
|
2020-11-25 07:36:27 +11:00
|
|
|
|
|
|
|
pub unsafe fn pipeline_builder(&self) -> PipelineBuilder {
|
|
|
|
PipelineBuilder(self.0.device.pipeline_builder())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn descriptor_set_builder(&self) -> DescriptorSetBuilder {
|
|
|
|
DescriptorSetBuilder(self.0.device.descriptor_set_builder())
|
|
|
|
}
|
2020-11-26 07:43:42 +11:00
|
|
|
|
|
|
|
pub unsafe fn create_sampler(&self, params: SamplerParams) -> Result<Sampler, Error> {
|
|
|
|
self.0.device.create_sampler(params)
|
|
|
|
}
|
2021-04-03 12:59:07 +11:00
|
|
|
|
|
|
|
/// Report whether the device supports descriptor indexing.
|
|
|
|
///
|
|
|
|
/// As we have more queries, we might replace this with a capabilities structure.
|
|
|
|
pub fn has_descriptor_indexing(&self) -> bool {
|
|
|
|
self.0.device.has_descriptor_indexing
|
|
|
|
}
|
2020-11-18 03:04:25 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl CmdBuf {
|
2020-11-19 10:16:12 +11:00
|
|
|
/// Make sure the resource lives until the command buffer completes.
|
|
|
|
///
|
|
|
|
/// The submitted command buffer will hold this reference until the corresponding
|
|
|
|
/// fence is signaled.
|
|
|
|
///
|
|
|
|
/// There are two choices for upholding the lifetime invariant: this function, or
|
|
|
|
/// the caller can manually hold the reference. The latter is appropriate when it's
|
|
|
|
/// part of retained state.
|
2020-11-18 03:04:25 +11:00
|
|
|
pub fn add_resource<T: Clone + 'static>(&mut self, resource: &T) {
|
|
|
|
self.resources.push(Box::new(resource.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SubmittedCmdBuf {
|
|
|
|
pub fn wait(mut self) -> Result<(), Error> {
|
|
|
|
let item = self.0.take().unwrap();
|
|
|
|
if let Some(session) = Weak::upgrade(&self.1) {
|
|
|
|
unsafe {
|
|
|
|
session.device.wait_and_reset(&[item.fence])?;
|
|
|
|
}
|
|
|
|
session
|
|
|
|
.cmd_buf_pool
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.push((item.cmd_buf, item.fence));
|
|
|
|
std::mem::drop(item.resources);
|
|
|
|
}
|
|
|
|
// else session dropped error?
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for SubmittedCmdBuf {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(inner) = self.0.take() {
|
|
|
|
if let Some(session) = Weak::upgrade(&self.1) {
|
|
|
|
session.pending.lock().unwrap().push(inner);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for BufferInner {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(session) = Weak::upgrade(&self.session) {
|
|
|
|
unsafe {
|
|
|
|
let _ = session.device.destroy_buffer(&self.buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for ImageInner {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(session) = Weak::upgrade(&self.session) {
|
|
|
|
unsafe {
|
|
|
|
let _ = session.device.destroy_image(&self.image);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// For now, we deref, but for runtime backend switching we'll need to wrap
|
|
|
|
/// all methods.
|
|
|
|
impl std::ops::Deref for CmdBuf {
|
|
|
|
type Target = vulkan::CmdBuf;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.cmd_buf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl std::ops::DerefMut for CmdBuf {
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.cmd_buf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
|
|
|
pub fn vk_image(&self) -> &vulkan::Image {
|
|
|
|
&self.0.image
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Buffer {
|
|
|
|
pub fn vk_buffer(&self) -> &vulkan::Buffer {
|
|
|
|
&self.0.buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
pub unsafe fn write<T: Sized>(&mut self, contents: &[T]) -> Result<(), Error> {
|
|
|
|
if let Some(session) = Weak::upgrade(&self.0.session) {
|
|
|
|
session.device.write_buffer(&self.0.buffer, contents)?;
|
|
|
|
}
|
|
|
|
// else session lost error?
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
pub unsafe fn read<T: Sized>(&self, result: &mut Vec<T>) -> Result<(), Error> {
|
|
|
|
if let Some(session) = Weak::upgrade(&self.0.session) {
|
|
|
|
session.device.read_buffer(&self.0.buffer, result)?;
|
|
|
|
}
|
|
|
|
// else session lost error?
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2020-11-25 07:36:27 +11:00
|
|
|
|
|
|
|
impl PipelineBuilder {
|
|
|
|
/// Add buffers to the pipeline. Each has its own binding.
|
|
|
|
pub fn add_buffers(mut self, n_buffers: u32) -> Self {
|
|
|
|
self.0.add_buffers(n_buffers);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Add storage images to the pipeline. Each has its own binding.
|
|
|
|
pub fn add_images(mut self, n_images: u32) -> Self {
|
|
|
|
self.0.add_images(n_images);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-19 10:54:11 +11:00
|
|
|
/// Add a binding with a variable-size array of textures.
|
|
|
|
pub fn add_textures(mut self, max_textures: u32) -> Self {
|
|
|
|
self.0.add_textures(max_textures);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-25 07:36:27 +11:00
|
|
|
pub unsafe fn create_compute_pipeline(
|
|
|
|
self,
|
|
|
|
session: &Session,
|
|
|
|
code: &[u8],
|
|
|
|
) -> Result<Pipeline, Error> {
|
|
|
|
self.0.create_compute_pipeline(&session.0.device, code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DescriptorSetBuilder {
|
|
|
|
pub fn add_buffers<'a>(mut self, buffers: impl IntoRefs<'a, Buffer>) -> Self {
|
|
|
|
let vk_buffers = buffers
|
|
|
|
.into_refs()
|
|
|
|
.map(|b| b.vk_buffer())
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
self.0.add_buffers(&vk_buffers);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_images<'a>(mut self, images: impl IntoRefs<'a, Image>) -> Self {
|
|
|
|
let vk_images = images.into_refs().map(|i| i.vk_image()).collect::<Vec<_>>();
|
|
|
|
self.0.add_images(&vk_images);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
implement FillImage command and sRGB support
FillImage is like Fill, except that it takes its color from one or
more image atlases.
kernel4 uses a single image for non-Vulkan hosts, and the dynamic sized array
of image descriptors on Vulkan.
A previous version of this commit used textures. I think images are a better
choice for piet-gpu, for several reasons:
- Texture sampling, in particular textureGrad, is slow on lower spec devices
such as Google Pixel. Texture sampling is particularly slow and difficult to
implement for CPU fallbacks.
- Texture sampling need more parameters, in particular the full u,v
transformation matrix, leading to a large increase in the command size. Since
all commands use the same size, that memory penalty is paid by all scenes, not
just scenes with textures.
- It is unlikely that piet-gpu will support every kind of fill for every
client, because each kind must be added to kernel4.
With FillImage, a client will prepare the image(s) in separate shader stages,
sampling and applying transformations and special effects as needed. Textures
that align with the output pixel grid can be used directly, without
pre-processing.
Note that the pre-processing step can run concurrently with the piet-gpu pipeline;
Only the last stage, kernel4, needs the images.
Pre-processing most likely uses fixed function vertex/fragment programs,
which on some GPUs may run in parallel with piet-gpu's compute programs.
While here, fix a few validation errors:
- Explicitly enable EXT_descriptor_indexing, KHR_maintenance3,
KHR_get_physical_device_properties2.
- Specify a vkDescriptorSetVariableDescriptorCountAllocateInfo for
vkAllocateDescriptorSets. Otherwise, variable image2D arrays won't work (but
sampler2D arrays do, at least on my setup).
Updates #38
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2020-12-29 08:02:39 +11:00
|
|
|
pub fn add_textures<'a>(mut self, images: impl IntoRefs<'a, Image>) -> Self {
|
2020-11-19 10:54:11 +11:00
|
|
|
let vk_images = images.into_refs().map(|i| i.vk_image()).collect::<Vec<_>>();
|
implement FillImage command and sRGB support
FillImage is like Fill, except that it takes its color from one or
more image atlases.
kernel4 uses a single image for non-Vulkan hosts, and the dynamic sized array
of image descriptors on Vulkan.
A previous version of this commit used textures. I think images are a better
choice for piet-gpu, for several reasons:
- Texture sampling, in particular textureGrad, is slow on lower spec devices
such as Google Pixel. Texture sampling is particularly slow and difficult to
implement for CPU fallbacks.
- Texture sampling need more parameters, in particular the full u,v
transformation matrix, leading to a large increase in the command size. Since
all commands use the same size, that memory penalty is paid by all scenes, not
just scenes with textures.
- It is unlikely that piet-gpu will support every kind of fill for every
client, because each kind must be added to kernel4.
With FillImage, a client will prepare the image(s) in separate shader stages,
sampling and applying transformations and special effects as needed. Textures
that align with the output pixel grid can be used directly, without
pre-processing.
Note that the pre-processing step can run concurrently with the piet-gpu pipeline;
Only the last stage, kernel4, needs the images.
Pre-processing most likely uses fixed function vertex/fragment programs,
which on some GPUs may run in parallel with piet-gpu's compute programs.
While here, fix a few validation errors:
- Explicitly enable EXT_descriptor_indexing, KHR_maintenance3,
KHR_get_physical_device_properties2.
- Specify a vkDescriptorSetVariableDescriptorCountAllocateInfo for
vkAllocateDescriptorSets. Otherwise, variable image2D arrays won't work (but
sampler2D arrays do, at least on my setup).
Updates #38
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2020-12-29 08:02:39 +11:00
|
|
|
self.0.add_textures(&vk_images);
|
2020-11-19 10:54:11 +11:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-25 07:36:27 +11:00
|
|
|
pub unsafe fn build(
|
|
|
|
self,
|
|
|
|
session: &Session,
|
|
|
|
pipeline: &Pipeline,
|
|
|
|
) -> Result<DescriptorSet, Error> {
|
|
|
|
self.0.build(&session.0.device, pipeline)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This lets us use either a slice or a vector. The type is clunky but it
|
|
|
|
// seems fine enough to use.
|
|
|
|
pub trait IntoRefs<'a, T: 'a> {
|
|
|
|
type Iterator: Iterator<Item = &'a T>;
|
|
|
|
|
|
|
|
fn into_refs(self) -> Self::Iterator;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [T] {
|
|
|
|
type Iterator = std::slice::Iter<'a, T>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: this will benefit from const generics!
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 1] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 2] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 3] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 4] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 5] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for &'a [&'a T; 6] {
|
|
|
|
type Iterator = std::iter::Copied<std::slice::Iter<'a, &'a T>>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter().copied()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> IntoRefs<'a, T> for Vec<&'a T> {
|
|
|
|
type Iterator = std::vec::IntoIter<&'a T>;
|
|
|
|
fn into_refs(self) -> Self::Iterator {
|
|
|
|
self.into_iter()
|
|
|
|
}
|
|
|
|
}
|