704 lines
20 KiB
Rust
704 lines
20 KiB
Rust
// Copyright (c) 2016 The vulkano developers
|
|
// Licensed under the Apache License, Version 2.0
|
|
// <LICENSE-APACHE or
|
|
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
|
|
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
|
|
// at your option. All files in the project carrying such
|
|
// notice may not be copied, modified, or distributed except
|
|
// according to those terms.
|
|
|
|
use crate::buffer::BufferAccess;
|
|
use crate::buffer::BufferUsage;
|
|
use crate::buffer::CpuAccessibleBuffer;
|
|
use crate::buffer::TypedBufferAccess;
|
|
use crate::command_buffer::AutoCommandBufferBuilder;
|
|
use crate::command_buffer::CommandBufferExecFuture;
|
|
use crate::command_buffer::CommandBufferUsage;
|
|
use crate::command_buffer::PrimaryAutoCommandBuffer;
|
|
use crate::command_buffer::PrimaryCommandBuffer;
|
|
use crate::device::physical::QueueFamily;
|
|
use crate::device::Device;
|
|
use crate::device::Queue;
|
|
use crate::format::Format;
|
|
use crate::format::Pixel;
|
|
use crate::image::sys::ImageCreationError;
|
|
use crate::image::sys::UnsafeImage;
|
|
use crate::image::traits::ImageAccess;
|
|
use crate::image::traits::ImageContent;
|
|
use crate::image::ImageCreateFlags;
|
|
use crate::image::ImageDescriptorLayouts;
|
|
use crate::image::ImageDimensions;
|
|
use crate::image::ImageInner;
|
|
use crate::image::ImageLayout;
|
|
use crate::image::ImageUsage;
|
|
use crate::image::MipmapsCount;
|
|
use crate::image::SampleCount;
|
|
use crate::memory::pool::AllocFromRequirementsFilter;
|
|
use crate::memory::pool::AllocLayout;
|
|
use crate::memory::pool::MappingRequirement;
|
|
use crate::memory::pool::MemoryPool;
|
|
use crate::memory::pool::MemoryPoolAlloc;
|
|
use crate::memory::pool::PotentialDedicatedAllocation;
|
|
use crate::memory::pool::StdMemoryPoolAlloc;
|
|
use crate::memory::DedicatedAlloc;
|
|
use crate::sampler::Filter;
|
|
use crate::sync::AccessError;
|
|
use crate::sync::NowFuture;
|
|
use crate::sync::Sharing;
|
|
use smallvec::SmallVec;
|
|
use std::hash::Hash;
|
|
use std::hash::Hasher;
|
|
use std::sync::atomic::AtomicBool;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
|
|
/// Image whose purpose is to be used for read-only purposes. You can write to the image once,
|
|
/// but then you must only ever read from it.
|
|
// TODO: type (2D, 3D, array, etc.) as template parameter
|
|
#[derive(Debug)]
|
|
pub struct ImmutableImage<A = PotentialDedicatedAllocation<StdMemoryPoolAlloc>> {
|
|
image: UnsafeImage,
|
|
dimensions: ImageDimensions,
|
|
memory: A,
|
|
format: Format,
|
|
initialized: AtomicBool,
|
|
layout: ImageLayout,
|
|
}
|
|
|
|
/// Image whose purpose is to access only a part of one image, for any kind of access
|
|
/// We define a part of one image here by a level of mipmap, or a layer of an array
|
|
/// The image attribute must be an implementation of ImageAccess
|
|
/// The mip_levels_access must be a range showing which mipmaps will be accessed
|
|
/// The layer_levels_access must be a range showing which layers will be accessed
|
|
/// The layout must be the layout of the image at the beginning and at the end of the command buffer
|
|
pub struct SubImage {
|
|
image: Arc<dyn ImageAccess + Sync + Send>,
|
|
mip_levels_access: std::ops::Range<u32>,
|
|
layer_levels_access: std::ops::Range<u32>,
|
|
layout: ImageLayout,
|
|
}
|
|
|
|
impl SubImage {
|
|
pub fn new(
|
|
image: Arc<dyn ImageAccess + Sync + Send>,
|
|
mip_level: u32,
|
|
mip_level_count: u32,
|
|
layer_level: u32,
|
|
layer_level_count: u32,
|
|
layout: ImageLayout,
|
|
) -> Arc<SubImage> {
|
|
debug_assert!(mip_level + mip_level_count <= image.mipmap_levels());
|
|
debug_assert!(layer_level + layer_level_count <= image.dimensions().array_layers());
|
|
|
|
let last_level = mip_level + mip_level_count;
|
|
let mip_levels_access = mip_level..last_level;
|
|
|
|
let last_level = layer_level + layer_level_count;
|
|
let layer_levels_access = layer_level..last_level;
|
|
|
|
Arc::new(SubImage {
|
|
image,
|
|
mip_levels_access,
|
|
layer_levels_access,
|
|
layout: ImageLayout::ShaderReadOnlyOptimal,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Must not implement Clone, as that would lead to multiple `used` values.
|
|
pub struct ImmutableImageInitialization<A = PotentialDedicatedAllocation<StdMemoryPoolAlloc>> {
|
|
image: Arc<ImmutableImage<A>>,
|
|
used: AtomicBool,
|
|
mip_levels_access: std::ops::Range<u32>,
|
|
layer_levels_access: std::ops::Range<u32>,
|
|
}
|
|
|
|
fn has_mipmaps(mipmaps: MipmapsCount) -> bool {
|
|
match mipmaps {
|
|
MipmapsCount::One => false,
|
|
MipmapsCount::Log2 => true,
|
|
MipmapsCount::Specific(x) => x > 1,
|
|
}
|
|
}
|
|
|
|
fn generate_mipmaps<L, Img>(
|
|
cbb: &mut AutoCommandBufferBuilder<L>,
|
|
image: Arc<Img>,
|
|
dimensions: ImageDimensions,
|
|
layout: ImageLayout,
|
|
) where
|
|
Img: ImageAccess + Send + Sync + 'static,
|
|
{
|
|
for level in 1..image.mipmap_levels() {
|
|
let [xs, ys, ds] = dimensions
|
|
.mipmap_dimensions(level - 1)
|
|
.unwrap()
|
|
.width_height_depth();
|
|
let [xd, yd, dd] = dimensions
|
|
.mipmap_dimensions(level)
|
|
.unwrap()
|
|
.width_height_depth();
|
|
|
|
let src = SubImage::new(
|
|
image.clone(),
|
|
level - 1,
|
|
1,
|
|
0,
|
|
dimensions.array_layers(),
|
|
layout,
|
|
);
|
|
|
|
let dst = SubImage::new(
|
|
image.clone(),
|
|
level,
|
|
1,
|
|
0,
|
|
dimensions.array_layers(),
|
|
layout,
|
|
);
|
|
|
|
cbb.blit_image(
|
|
src, //source
|
|
[0, 0, 0], //source_top_left
|
|
[xs as i32, ys as i32, ds as i32], //source_bottom_right
|
|
0, //source_base_array_layer
|
|
level - 1, //source_mip_level
|
|
dst, //destination
|
|
[0, 0, 0], //destination_top_left
|
|
[xd as i32, yd as i32, dd as i32], //destination_bottom_right
|
|
0, //destination_base_array_layer
|
|
level, //destination_mip_level
|
|
1, //layer_count
|
|
Filter::Linear, //filter
|
|
)
|
|
.expect("failed to blit a mip map to image!");
|
|
}
|
|
}
|
|
|
|
impl ImmutableImage {
|
|
#[deprecated(note = "use ImmutableImage::uninitialized instead")]
|
|
#[inline]
|
|
pub fn new<'a, I>(
|
|
device: Arc<Device>,
|
|
dimensions: ImageDimensions,
|
|
format: Format,
|
|
queue_families: I,
|
|
) -> Result<Arc<ImmutableImage>, ImageCreationError>
|
|
where
|
|
I: IntoIterator<Item = QueueFamily<'a>>,
|
|
{
|
|
#[allow(deprecated)]
|
|
ImmutableImage::with_mipmaps(
|
|
device,
|
|
dimensions,
|
|
format,
|
|
MipmapsCount::One,
|
|
queue_families,
|
|
)
|
|
}
|
|
|
|
#[deprecated(note = "use ImmutableImage::uninitialized instead")]
|
|
#[inline]
|
|
pub fn with_mipmaps<'a, I, M>(
|
|
device: Arc<Device>,
|
|
dimensions: ImageDimensions,
|
|
format: Format,
|
|
mipmaps: M,
|
|
queue_families: I,
|
|
) -> Result<Arc<ImmutableImage>, ImageCreationError>
|
|
where
|
|
I: IntoIterator<Item = QueueFamily<'a>>,
|
|
M: Into<MipmapsCount>,
|
|
{
|
|
let usage = ImageUsage {
|
|
transfer_source: true, // for blits
|
|
transfer_destination: true,
|
|
sampled: true,
|
|
..ImageUsage::none()
|
|
};
|
|
|
|
let flags = ImageCreateFlags::none();
|
|
|
|
let (image, _) = ImmutableImage::uninitialized(
|
|
device,
|
|
dimensions,
|
|
format,
|
|
mipmaps,
|
|
usage,
|
|
flags,
|
|
ImageLayout::ShaderReadOnlyOptimal,
|
|
queue_families,
|
|
)?;
|
|
image.initialized.store(true, Ordering::Relaxed); // Allow uninitialized access for backwards compatibility
|
|
Ok(image)
|
|
}
|
|
|
|
/// Builds an uninitialized immutable image.
|
|
///
|
|
/// Returns two things: the image, and a special access that should be used for the initial upload to the image.
|
|
pub fn uninitialized<'a, I, M>(
|
|
device: Arc<Device>,
|
|
dimensions: ImageDimensions,
|
|
format: Format,
|
|
mipmaps: M,
|
|
usage: ImageUsage,
|
|
flags: ImageCreateFlags,
|
|
layout: ImageLayout,
|
|
queue_families: I,
|
|
) -> Result<(Arc<ImmutableImage>, ImmutableImageInitialization), ImageCreationError>
|
|
where
|
|
I: IntoIterator<Item = QueueFamily<'a>>,
|
|
M: Into<MipmapsCount>,
|
|
{
|
|
let queue_families = queue_families
|
|
.into_iter()
|
|
.map(|f| f.id())
|
|
.collect::<SmallVec<[u32; 4]>>();
|
|
|
|
let (image, mem_reqs) = unsafe {
|
|
let sharing = if queue_families.len() >= 2 {
|
|
Sharing::Concurrent(queue_families.iter().cloned())
|
|
} else {
|
|
Sharing::Exclusive
|
|
};
|
|
|
|
UnsafeImage::new(
|
|
device.clone(),
|
|
usage,
|
|
format,
|
|
flags,
|
|
dimensions,
|
|
SampleCount::Sample1,
|
|
mipmaps,
|
|
sharing,
|
|
false,
|
|
false,
|
|
)?
|
|
};
|
|
|
|
let memory = MemoryPool::alloc_from_requirements(
|
|
&Device::standard_pool(&device),
|
|
&mem_reqs,
|
|
AllocLayout::Optimal,
|
|
MappingRequirement::DoNotMap,
|
|
DedicatedAlloc::Image(&image),
|
|
|t| {
|
|
if t.is_device_local() {
|
|
AllocFromRequirementsFilter::Preferred
|
|
} else {
|
|
AllocFromRequirementsFilter::Allowed
|
|
}
|
|
},
|
|
)?;
|
|
debug_assert!((memory.offset() % mem_reqs.alignment) == 0);
|
|
unsafe {
|
|
image.bind_memory(memory.memory(), memory.offset())?;
|
|
}
|
|
|
|
let image = Arc::new(ImmutableImage {
|
|
image,
|
|
memory,
|
|
dimensions,
|
|
format,
|
|
initialized: AtomicBool::new(false),
|
|
layout,
|
|
});
|
|
|
|
let init = ImmutableImageInitialization {
|
|
image: image.clone(),
|
|
used: AtomicBool::new(false),
|
|
mip_levels_access: 0..image.mipmap_levels(),
|
|
layer_levels_access: 0..image.dimensions().array_layers(),
|
|
};
|
|
|
|
Ok((image, init))
|
|
}
|
|
|
|
/// Construct an ImmutableImage from the contents of `iter`.
|
|
#[inline]
|
|
pub fn from_iter<Px, I>(
|
|
iter: I,
|
|
dimensions: ImageDimensions,
|
|
mipmaps: MipmapsCount,
|
|
format: Format,
|
|
queue: Arc<Queue>,
|
|
) -> Result<
|
|
(
|
|
Arc<Self>,
|
|
CommandBufferExecFuture<NowFuture, PrimaryAutoCommandBuffer>,
|
|
),
|
|
ImageCreationError,
|
|
>
|
|
where
|
|
Px: Pixel + Send + Sync + Clone + 'static,
|
|
I: ExactSizeIterator<Item = Px>,
|
|
{
|
|
let source = CpuAccessibleBuffer::from_iter(
|
|
queue.device().clone(),
|
|
BufferUsage::transfer_source(),
|
|
false,
|
|
iter,
|
|
)?;
|
|
ImmutableImage::from_buffer(source, dimensions, mipmaps, format, queue)
|
|
}
|
|
|
|
/// Construct an ImmutableImage containing a copy of the data in `source`.
|
|
pub fn from_buffer<B, Px>(
|
|
source: B,
|
|
dimensions: ImageDimensions,
|
|
mipmaps: MipmapsCount,
|
|
format: Format,
|
|
queue: Arc<Queue>,
|
|
) -> Result<
|
|
(
|
|
Arc<Self>,
|
|
CommandBufferExecFuture<NowFuture, PrimaryAutoCommandBuffer>,
|
|
),
|
|
ImageCreationError,
|
|
>
|
|
where
|
|
B: BufferAccess + TypedBufferAccess<Content = [Px]> + 'static + Clone + Send + Sync,
|
|
Px: Pixel + Send + Sync + Clone + 'static,
|
|
{
|
|
let need_to_generate_mipmaps = has_mipmaps(mipmaps);
|
|
let usage = ImageUsage {
|
|
transfer_destination: true,
|
|
transfer_source: need_to_generate_mipmaps,
|
|
sampled: true,
|
|
..ImageUsage::none()
|
|
};
|
|
let flags = ImageCreateFlags::none();
|
|
let layout = ImageLayout::ShaderReadOnlyOptimal;
|
|
|
|
let (image, initializer) = ImmutableImage::uninitialized(
|
|
source.device().clone(),
|
|
dimensions,
|
|
format,
|
|
mipmaps,
|
|
usage,
|
|
flags,
|
|
layout,
|
|
source.device().active_queue_families(),
|
|
)?;
|
|
|
|
let init = SubImage::new(
|
|
Arc::new(initializer),
|
|
0,
|
|
1,
|
|
0,
|
|
1,
|
|
ImageLayout::ShaderReadOnlyOptimal,
|
|
);
|
|
|
|
let mut cbb = AutoCommandBufferBuilder::primary(
|
|
source.device().clone(),
|
|
queue.family(),
|
|
CommandBufferUsage::MultipleSubmit,
|
|
)?;
|
|
cbb.copy_buffer_to_image_dimensions(
|
|
source,
|
|
init,
|
|
[0, 0, 0],
|
|
dimensions.width_height_depth(),
|
|
0,
|
|
dimensions.array_layers(),
|
|
0,
|
|
)
|
|
.unwrap();
|
|
|
|
if need_to_generate_mipmaps {
|
|
generate_mipmaps(
|
|
&mut cbb,
|
|
image.clone(),
|
|
image.dimensions,
|
|
ImageLayout::ShaderReadOnlyOptimal,
|
|
);
|
|
}
|
|
|
|
let cb = cbb.build().unwrap();
|
|
|
|
let future = match cb.execute(queue) {
|
|
Ok(f) => f,
|
|
Err(e) => unreachable!("{:?}", e),
|
|
};
|
|
|
|
image.initialized.store(true, Ordering::Relaxed);
|
|
|
|
Ok((image, future))
|
|
}
|
|
}
|
|
|
|
impl<A> ImmutableImage<A> {
|
|
/// Returns the dimensions of the image.
|
|
#[inline]
|
|
pub fn dimensions(&self) -> ImageDimensions {
|
|
self.dimensions
|
|
}
|
|
|
|
/// Returns the number of mipmap levels of the image.
|
|
#[inline]
|
|
pub fn mipmap_levels(&self) -> u32 {
|
|
self.image.mipmap_levels()
|
|
}
|
|
}
|
|
|
|
unsafe impl<A> ImageAccess for ImmutableImage<A> {
|
|
#[inline]
|
|
fn inner(&self) -> ImageInner {
|
|
ImageInner {
|
|
image: &self.image,
|
|
first_layer: 0,
|
|
num_layers: self.image.dimensions().array_layers() as usize,
|
|
first_mipmap_level: 0,
|
|
num_mipmap_levels: self.image.mipmap_levels() as usize,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn initial_layout_requirement(&self) -> ImageLayout {
|
|
self.layout
|
|
}
|
|
|
|
#[inline]
|
|
fn final_layout_requirement(&self) -> ImageLayout {
|
|
self.layout
|
|
}
|
|
|
|
#[inline]
|
|
fn descriptor_layouts(&self) -> Option<ImageDescriptorLayouts> {
|
|
Some(ImageDescriptorLayouts {
|
|
storage_image: self.layout,
|
|
combined_image_sampler: self.layout,
|
|
sampled_image: self.layout,
|
|
input_attachment: self.layout,
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
fn conflict_key(&self) -> u64 {
|
|
self.image.key()
|
|
}
|
|
|
|
#[inline]
|
|
fn try_gpu_lock(
|
|
&self,
|
|
exclusive_access: bool,
|
|
uninitialized_safe: bool,
|
|
expected_layout: ImageLayout,
|
|
) -> Result<(), AccessError> {
|
|
if expected_layout != self.layout && expected_layout != ImageLayout::Undefined {
|
|
return Err(AccessError::UnexpectedImageLayout {
|
|
requested: expected_layout,
|
|
allowed: self.layout,
|
|
});
|
|
}
|
|
|
|
if exclusive_access {
|
|
return Err(AccessError::ExclusiveDenied);
|
|
}
|
|
|
|
if !self.initialized.load(Ordering::Relaxed) {
|
|
return Err(AccessError::BufferNotInitialized);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn increase_gpu_lock(&self) {}
|
|
|
|
#[inline]
|
|
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
|
|
debug_assert!(new_layout.is_none());
|
|
}
|
|
|
|
#[inline]
|
|
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
|
|
0..self.mipmap_levels()
|
|
}
|
|
|
|
#[inline]
|
|
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
|
|
0..self.dimensions().array_layers()
|
|
}
|
|
}
|
|
|
|
unsafe impl<P, A> ImageContent<P> for ImmutableImage<A> {
|
|
#[inline]
|
|
fn matches_format(&self) -> bool {
|
|
true // FIXME:
|
|
}
|
|
}
|
|
|
|
unsafe impl ImageAccess for SubImage {
|
|
#[inline]
|
|
fn inner(&self) -> ImageInner {
|
|
self.image.inner()
|
|
}
|
|
|
|
#[inline]
|
|
fn initial_layout_requirement(&self) -> ImageLayout {
|
|
self.image.initial_layout_requirement()
|
|
}
|
|
|
|
#[inline]
|
|
fn final_layout_requirement(&self) -> ImageLayout {
|
|
self.image.final_layout_requirement()
|
|
}
|
|
|
|
#[inline]
|
|
fn descriptor_layouts(&self) -> Option<ImageDescriptorLayouts> {
|
|
None
|
|
}
|
|
|
|
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
|
|
self.mip_levels_access.clone()
|
|
}
|
|
|
|
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
|
|
self.layer_levels_access.clone()
|
|
}
|
|
|
|
#[inline]
|
|
fn conflict_key(&self) -> u64 {
|
|
self.image.conflict_key()
|
|
}
|
|
|
|
#[inline]
|
|
fn try_gpu_lock(
|
|
&self,
|
|
exclusive_access: bool,
|
|
uninitialized_safe: bool,
|
|
expected_layout: ImageLayout,
|
|
) -> Result<(), AccessError> {
|
|
if expected_layout != self.layout && expected_layout != ImageLayout::Undefined {
|
|
return Err(AccessError::UnexpectedImageLayout {
|
|
requested: expected_layout,
|
|
allowed: self.layout,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn increase_gpu_lock(&self) {
|
|
self.image.increase_gpu_lock()
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
|
|
self.image.unlock(new_layout)
|
|
}
|
|
}
|
|
|
|
impl<A> PartialEq for ImmutableImage<A> {
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
ImageAccess::inner(self) == ImageAccess::inner(other)
|
|
}
|
|
}
|
|
|
|
impl<A> Eq for ImmutableImage<A> {}
|
|
|
|
impl<A> Hash for ImmutableImage<A> {
|
|
#[inline]
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
ImageAccess::inner(self).hash(state);
|
|
}
|
|
}
|
|
|
|
unsafe impl<A> ImageAccess for ImmutableImageInitialization<A> {
|
|
#[inline]
|
|
fn inner(&self) -> ImageInner {
|
|
ImageAccess::inner(&self.image)
|
|
}
|
|
|
|
#[inline]
|
|
fn initial_layout_requirement(&self) -> ImageLayout {
|
|
ImageLayout::Undefined
|
|
}
|
|
|
|
#[inline]
|
|
fn final_layout_requirement(&self) -> ImageLayout {
|
|
self.image.layout
|
|
}
|
|
|
|
#[inline]
|
|
fn descriptor_layouts(&self) -> Option<ImageDescriptorLayouts> {
|
|
None
|
|
}
|
|
|
|
#[inline]
|
|
fn conflict_key(&self) -> u64 {
|
|
self.image.image.key()
|
|
}
|
|
|
|
#[inline]
|
|
fn try_gpu_lock(
|
|
&self,
|
|
_: bool,
|
|
uninitialized_safe: bool,
|
|
expected_layout: ImageLayout,
|
|
) -> Result<(), AccessError> {
|
|
if expected_layout != ImageLayout::Undefined {
|
|
return Err(AccessError::UnexpectedImageLayout {
|
|
requested: expected_layout,
|
|
allowed: ImageLayout::Undefined,
|
|
});
|
|
}
|
|
|
|
if self.image.initialized.load(Ordering::Relaxed) {
|
|
return Err(AccessError::AlreadyInUse);
|
|
}
|
|
|
|
// FIXME: Mipmapped textures require multiple writes to initialize
|
|
if !self
|
|
.used
|
|
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
|
.unwrap_or_else(|e| e)
|
|
{
|
|
Ok(())
|
|
} else {
|
|
Err(AccessError::AlreadyInUse)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn increase_gpu_lock(&self) {
|
|
debug_assert!(self.used.load(Ordering::Relaxed));
|
|
}
|
|
|
|
#[inline]
|
|
unsafe fn unlock(&self, new_layout: Option<ImageLayout>) {
|
|
assert_eq!(new_layout, Some(self.image.layout));
|
|
self.image.initialized.store(true, Ordering::Relaxed);
|
|
}
|
|
|
|
#[inline]
|
|
fn current_miplevels_access(&self) -> std::ops::Range<u32> {
|
|
self.mip_levels_access.clone()
|
|
}
|
|
|
|
#[inline]
|
|
fn current_layer_levels_access(&self) -> std::ops::Range<u32> {
|
|
self.layer_levels_access.clone()
|
|
}
|
|
}
|
|
|
|
impl<A> PartialEq for ImmutableImageInitialization<A> {
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
ImageAccess::inner(self) == ImageAccess::inner(other)
|
|
}
|
|
}
|
|
|
|
impl<A> Eq for ImmutableImageInitialization<A> {}
|
|
|
|
impl<A> Hash for ImmutableImageInitialization<A> {
|
|
#[inline]
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
ImageAccess::inner(self).hash(state);
|
|
}
|
|
}
|