| // Copyright (c) 2021 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. |
| |
| //! Image views. |
| //! |
| //! This module contains types related to image views. An image view wraps around |
| //! an image and describes how the GPU should interpret the data. It is needed when an image is |
| //! to be used in a shader descriptor or as a framebuffer attachment. |
| |
| use super::{ |
| sys::Image, ImageAccess, ImageDimensions, ImageFormatInfo, ImageSubresourceRange, ImageUsage, |
| }; |
| use crate::{ |
| device::{Device, DeviceOwned}, |
| format::{ChromaSampling, Format, FormatFeatures}, |
| image::{ImageAspects, ImageCreateFlags, ImageTiling, ImageType, SampleCount}, |
| macros::{impl_id_counter, vulkan_enum}, |
| sampler::{ycbcr::SamplerYcbcrConversion, ComponentMapping}, |
| OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject, |
| }; |
| use std::{ |
| error::Error, |
| fmt::{Debug, Display, Error as FmtError, Formatter}, |
| hash::{Hash, Hasher}, |
| mem::MaybeUninit, |
| num::NonZeroU64, |
| ptr, |
| sync::Arc, |
| }; |
| |
| /// A wrapper around an image that makes it available to shaders or framebuffers. |
| #[derive(Debug)] |
| pub struct ImageView<I> |
| where |
| I: ImageAccess + ?Sized, |
| { |
| handle: ash::vk::ImageView, |
| image: Arc<I>, |
| id: NonZeroU64, |
| |
| component_mapping: ComponentMapping, |
| format: Option<Format>, |
| format_features: FormatFeatures, |
| sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>, |
| subresource_range: ImageSubresourceRange, |
| usage: ImageUsage, |
| view_type: ImageViewType, |
| |
| filter_cubic: bool, |
| filter_cubic_minmax: bool, |
| } |
| |
| impl<I> ImageView<I> |
| where |
| I: ImageAccess + ?Sized, |
| { |
| /// Creates a new `ImageView`. |
| /// |
| /// # Panics |
| /// |
| /// - Panics if `create_info.array_layers` is empty. |
| /// - Panics if `create_info.mip_levels` is empty. |
| /// - Panics if `create_info.aspects` contains any aspects other than `color`, `depth`, |
| /// `stencil`, `plane0`, `plane1` or `plane2`. |
| /// - Panics if `create_info.aspects` contains more more than one aspect, unless `depth` and |
| /// `stencil` are the only aspects selected. |
| pub fn new( |
| image: Arc<I>, |
| create_info: ImageViewCreateInfo, |
| ) -> Result<Arc<ImageView<I>>, ImageViewCreationError> { |
| let format_features = Self::validate_new(&image, &create_info)?; |
| |
| unsafe { |
| Ok(Self::new_unchecked_with_format_features( |
| image, |
| create_info, |
| format_features, |
| )?) |
| } |
| } |
| |
| fn validate_new( |
| image: &I, |
| create_info: &ImageViewCreateInfo, |
| ) -> Result<FormatFeatures, ImageViewCreationError> { |
| let &ImageViewCreateInfo { |
| view_type, |
| format, |
| component_mapping, |
| ref subresource_range, |
| mut usage, |
| ref sampler_ycbcr_conversion, |
| _ne: _, |
| } = create_info; |
| |
| let image_inner = image.inner().image; |
| let device = image_inner.device(); |
| let format = format.unwrap(); |
| |
| let level_count = subresource_range.mip_levels.end - subresource_range.mip_levels.start; |
| let layer_count = subresource_range.array_layers.end - subresource_range.array_layers.start; |
| |
| // VUID-VkImageSubresourceRange-aspectMask-requiredbitmask |
| assert!(!subresource_range.aspects.is_empty()); |
| |
| // VUID-VkImageSubresourceRange-levelCount-01720 |
| assert!(level_count != 0); |
| |
| // VUID-VkImageSubresourceRange-layerCount-01721 |
| assert!(layer_count != 0); |
| |
| let default_usage = Self::get_default_usage(subresource_range.aspects, image_inner); |
| |
| let has_non_default_usage = if usage.is_empty() { |
| usage = default_usage; |
| false |
| } else { |
| usage == default_usage |
| }; |
| |
| // VUID-VkImageViewCreateInfo-viewType-parameter |
| view_type.validate_device(device)?; |
| |
| // VUID-VkImageViewCreateInfo-format-parameter |
| format.validate_device(device)?; |
| |
| // VUID-VkComponentMapping-r-parameter |
| component_mapping.r.validate_device(device)?; |
| |
| // VUID-VkComponentMapping-g-parameter |
| component_mapping.g.validate_device(device)?; |
| |
| // VUID-VkComponentMapping-b-parameter |
| component_mapping.b.validate_device(device)?; |
| |
| // VUID-VkComponentMapping-a-parameter |
| component_mapping.a.validate_device(device)?; |
| |
| // VUID-VkImageSubresourceRange-aspectMask-parameter |
| subresource_range.aspects.validate_device(device)?; |
| |
| assert!(!subresource_range.aspects.intersects( |
| ImageAspects::METADATA |
| | ImageAspects::MEMORY_PLANE_0 |
| | ImageAspects::MEMORY_PLANE_1 |
| | ImageAspects::MEMORY_PLANE_2 |
| )); |
| assert!({ |
| subresource_range.aspects.count() == 1 |
| || subresource_range |
| .aspects |
| .contains(ImageAspects::DEPTH | ImageAspects::STENCIL) |
| && !subresource_range.aspects.intersects( |
| ImageAspects::COLOR |
| | ImageAspects::PLANE_0 |
| | ImageAspects::PLANE_1 |
| | ImageAspects::PLANE_2, |
| ) |
| }); |
| |
| // Get format features |
| let format_features = unsafe { Self::get_format_features(format, image_inner) }; |
| |
| // No VUID apparently, but this seems like something we want to check? |
| if !image_inner |
| .format() |
| .unwrap() |
| .aspects() |
| .contains(subresource_range.aspects) |
| { |
| return Err(ImageViewCreationError::ImageAspectsNotCompatible { |
| aspects: subresource_range.aspects, |
| image_aspects: image_inner.format().unwrap().aspects(), |
| }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-None-02273 |
| if format_features == FormatFeatures::default() { |
| return Err(ImageViewCreationError::FormatNotSupported); |
| } |
| |
| // Check for compatibility with the image |
| let image_type = image.dimensions().image_type(); |
| |
| // VUID-VkImageViewCreateInfo-subResourceRange-01021 |
| if !view_type.is_compatible_with(image_type) { |
| return Err(ImageViewCreationError::ImageTypeNotCompatible); |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-01003 |
| if (view_type == ImageViewType::Cube || view_type == ImageViewType::CubeArray) |
| && !image_inner |
| .flags() |
| .intersects(ImageCreateFlags::CUBE_COMPATIBLE) |
| { |
| return Err(ImageViewCreationError::ImageNotCubeCompatible); |
| } |
| |
| // VUID-VkImageViewCreateInfo-viewType-01004 |
| if view_type == ImageViewType::CubeArray && !device.enabled_features().image_cube_array { |
| return Err(ImageViewCreationError::RequirementNotMet { |
| required_for: "`create_info.viewtype` is `ImageViewType::CubeArray`", |
| requires_one_of: RequiresOneOf { |
| features: &["image_cube_array"], |
| ..Default::default() |
| }, |
| }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-subresourceRange-01718 |
| if subresource_range.mip_levels.end > image_inner.mip_levels() { |
| return Err(ImageViewCreationError::MipLevelsOutOfRange { |
| range_end: subresource_range.mip_levels.end, |
| max: image_inner.mip_levels(), |
| }); |
| } |
| |
| if image_type == ImageType::Dim3d |
| && (view_type == ImageViewType::Dim2d || view_type == ImageViewType::Dim2dArray) |
| { |
| // VUID-VkImageViewCreateInfo-image-01005 |
| if !image_inner |
| .flags() |
| .intersects(ImageCreateFlags::ARRAY_2D_COMPATIBLE) |
| { |
| return Err(ImageViewCreationError::ImageNotArray2dCompatible); |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-04970 |
| if level_count != 1 { |
| return Err(ImageViewCreationError::Array2dCompatibleMultipleMipLevels); |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-02724 |
| // VUID-VkImageViewCreateInfo-subresourceRange-02725 |
| // We're using the depth dimension as array layers, but because of mip scaling, the |
| // depth, and therefore number of layers available, shrinks as the mip level gets |
| // higher. |
| let max = image_inner |
| .dimensions() |
| .mip_level_dimensions(subresource_range.mip_levels.start) |
| .unwrap() |
| .depth(); |
| if subresource_range.array_layers.end > max { |
| return Err(ImageViewCreationError::ArrayLayersOutOfRange { |
| range_end: subresource_range.array_layers.end, |
| max, |
| }); |
| } |
| } else { |
| // VUID-VkImageViewCreateInfo-image-01482 |
| // VUID-VkImageViewCreateInfo-subresourceRange-01483 |
| if subresource_range.array_layers.end > image_inner.dimensions().array_layers() { |
| return Err(ImageViewCreationError::ArrayLayersOutOfRange { |
| range_end: subresource_range.array_layers.end, |
| max: image_inner.dimensions().array_layers(), |
| }); |
| } |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-04972 |
| if image_inner.samples() != SampleCount::Sample1 |
| && !(view_type == ImageViewType::Dim2d || view_type == ImageViewType::Dim2dArray) |
| { |
| return Err(ImageViewCreationError::MultisamplingNot2d); |
| } |
| |
| /* Check usage requirements */ |
| |
| if has_non_default_usage { |
| if !(device.api_version() >= Version::V1_1 |
| || device.enabled_extensions().khr_maintenance2) |
| { |
| return Err(ImageViewCreationError::RequirementNotMet { |
| required_for: "`create_info.usage` is not the default value", |
| requires_one_of: RequiresOneOf { |
| api_version: Some(Version::V1_1), |
| device_extensions: &["khr_maintenance2"], |
| ..Default::default() |
| }, |
| }); |
| } |
| |
| // VUID-VkImageViewUsageCreateInfo-usage-parameter |
| usage.validate_device(device)?; |
| |
| // VUID-VkImageViewUsageCreateInfo-usage-requiredbitmask |
| assert!(!usage.is_empty()); |
| |
| // VUID-VkImageViewCreateInfo-pNext-02662 |
| // VUID-VkImageViewCreateInfo-pNext-02663 |
| // VUID-VkImageViewCreateInfo-pNext-02664 |
| if !default_usage.contains(usage) { |
| return Err(ImageViewCreationError::UsageNotSupportedByImage { |
| usage, |
| supported_usage: default_usage, |
| }); |
| } |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-04441 |
| if !image_inner.usage().intersects( |
| ImageUsage::SAMPLED |
| | ImageUsage::STORAGE |
| | ImageUsage::COLOR_ATTACHMENT |
| | ImageUsage::DEPTH_STENCIL_ATTACHMENT |
| | ImageUsage::INPUT_ATTACHMENT |
| | ImageUsage::TRANSIENT_ATTACHMENT, |
| ) { |
| return Err(ImageViewCreationError::ImageMissingUsage); |
| } |
| |
| // VUID-VkImageViewCreateInfo-usage-02274 |
| if usage.intersects(ImageUsage::SAMPLED) |
| && !format_features.intersects(FormatFeatures::SAMPLED_IMAGE) |
| { |
| return Err(ImageViewCreationError::FormatUsageNotSupported { usage: "sampled" }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-usage-02275 |
| if usage.intersects(ImageUsage::STORAGE) |
| && !format_features.intersects(FormatFeatures::STORAGE_IMAGE) |
| { |
| return Err(ImageViewCreationError::FormatUsageNotSupported { usage: "storage" }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-usage-02276 |
| if usage.intersects(ImageUsage::COLOR_ATTACHMENT) |
| && !format_features.intersects(FormatFeatures::COLOR_ATTACHMENT) |
| { |
| return Err(ImageViewCreationError::FormatUsageNotSupported { |
| usage: "color_attachment", |
| }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-usage-02277 |
| if usage.intersects(ImageUsage::DEPTH_STENCIL_ATTACHMENT) |
| && !format_features.intersects(FormatFeatures::DEPTH_STENCIL_ATTACHMENT) |
| { |
| return Err(ImageViewCreationError::FormatUsageNotSupported { |
| usage: "depth_stencil_attachment", |
| }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-usage-02652 |
| if usage.intersects(ImageUsage::INPUT_ATTACHMENT) |
| && !format_features.intersects( |
| FormatFeatures::COLOR_ATTACHMENT | FormatFeatures::DEPTH_STENCIL_ATTACHMENT, |
| ) |
| { |
| return Err(ImageViewCreationError::FormatUsageNotSupported { |
| usage: "input_attachment", |
| }); |
| } |
| |
| /* Check flags requirements */ |
| |
| if Some(format) != image_inner.format() { |
| // VUID-VkImageViewCreateInfo-image-01762 |
| if !image_inner |
| .flags() |
| .intersects(ImageCreateFlags::MUTABLE_FORMAT) |
| || !image_inner.format().unwrap().planes().is_empty() |
| && subresource_range.aspects.intersects(ImageAspects::COLOR) |
| { |
| return Err(ImageViewCreationError::FormatNotCompatible); |
| } |
| |
| // VUID-VkImageViewCreateInfo-imageViewFormatReinterpretation-04466 |
| // TODO: it is unclear what the number of bits is for compressed formats. |
| // See https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2361 |
| if device.enabled_extensions().khr_portability_subset |
| && !device.enabled_features().image_view_format_reinterpretation |
| && format.components() != image_inner.format().unwrap().components() |
| { |
| return Err(ImageViewCreationError::RequirementNotMet { |
| required_for: "this device is a portability subset device, and the format of \ |
| the image view does not have the same components and number of bits per \ |
| component as the parent image", |
| requires_one_of: RequiresOneOf { |
| features: &["image_view_format_reinterpretation"], |
| ..Default::default() |
| }, |
| }); |
| } |
| |
| if image_inner |
| .flags() |
| .intersects(ImageCreateFlags::BLOCK_TEXEL_VIEW_COMPATIBLE) |
| { |
| // VUID-VkImageViewCreateInfo-image-01583 |
| if !(format.compatibility() == image_inner.format().unwrap().compatibility() |
| || format.block_size() == image_inner.format().unwrap().block_size()) |
| { |
| return Err(ImageViewCreationError::FormatNotCompatible); |
| } |
| |
| if format.compression().is_none() { |
| // VUID-VkImageViewCreateInfo-image-01584 |
| if layer_count != 1 { |
| return Err( |
| ImageViewCreationError::BlockTexelViewCompatibleMultipleArrayLayers, |
| ); |
| } |
| |
| // VUID-VkImageViewCreateInfo-image-01584 |
| if level_count != 1 { |
| return Err( |
| ImageViewCreationError::BlockTexelViewCompatibleMultipleMipLevels, |
| ); |
| } |
| } |
| } else { |
| if image_inner.format().unwrap().planes().is_empty() { |
| // VUID-VkImageViewCreateInfo-image-01761 |
| if format.compatibility() != image_inner.format().unwrap().compatibility() { |
| return Err(ImageViewCreationError::FormatNotCompatible); |
| } |
| } else { |
| let plane = if subresource_range.aspects.intersects(ImageAspects::PLANE_0) { |
| 0 |
| } else if subresource_range.aspects.intersects(ImageAspects::PLANE_1) { |
| 1 |
| } else if subresource_range.aspects.intersects(ImageAspects::PLANE_2) { |
| 2 |
| } else { |
| unreachable!() |
| }; |
| let plane_format = image_inner.format().unwrap().planes()[plane]; |
| |
| // VUID-VkImageViewCreateInfo-image-01586 |
| if format.compatibility() != plane_format.compatibility() { |
| return Err(ImageViewCreationError::FormatNotCompatible); |
| } |
| } |
| } |
| } |
| |
| // VUID-VkImageViewCreateInfo-imageViewType-04973 |
| if (view_type == ImageViewType::Dim1d |
| || view_type == ImageViewType::Dim2d |
| || view_type == ImageViewType::Dim3d) |
| && layer_count != 1 |
| { |
| return Err(ImageViewCreationError::TypeNonArrayedMultipleArrayLayers); |
| } |
| // VUID-VkImageViewCreateInfo-viewType-02960 |
| else if view_type == ImageViewType::Cube && layer_count != 6 { |
| return Err(ImageViewCreationError::TypeCubeNot6ArrayLayers); |
| } |
| // VUID-VkImageViewCreateInfo-viewType-02961 |
| else if view_type == ImageViewType::CubeArray && layer_count % 6 != 0 { |
| return Err(ImageViewCreationError::TypeCubeArrayNotMultipleOf6ArrayLayers); |
| } |
| |
| // VUID-VkImageViewCreateInfo-imageViewFormatSwizzle-04465 |
| if device.enabled_extensions().khr_portability_subset |
| && !device.enabled_features().image_view_format_swizzle |
| && !component_mapping.is_identity() |
| { |
| return Err(ImageViewCreationError::RequirementNotMet { |
| required_for: "this device is a portability subset device, and \ |
| `create_info.component_mapping` is not the identity mapping", |
| requires_one_of: RequiresOneOf { |
| features: &["image_view_format_swizzle"], |
| ..Default::default() |
| }, |
| }); |
| } |
| |
| // VUID-VkImageViewCreateInfo-format-04714 |
| // VUID-VkImageViewCreateInfo-format-04715 |
| match format.ycbcr_chroma_sampling() { |
| Some(ChromaSampling::Mode422) => { |
| if image_inner.dimensions().width() % 2 != 0 { |
| return Err( |
| ImageViewCreationError::FormatChromaSubsamplingInvalidImageDimensions, |
| ); |
| } |
| } |
| Some(ChromaSampling::Mode420) => { |
| if image_inner.dimensions().width() % 2 != 0 |
| || image_inner.dimensions().height() % 2 != 0 |
| { |
| return Err( |
| ImageViewCreationError::FormatChromaSubsamplingInvalidImageDimensions, |
| ); |
| } |
| } |
| _ => (), |
| } |
| |
| // Don't need to check features because you can't create a conversion object without the |
| // feature anyway. |
| if let Some(conversion) = &sampler_ycbcr_conversion { |
| assert_eq!(device, conversion.device()); |
| |
| // VUID-VkImageViewCreateInfo-pNext-01970 |
| if !component_mapping.is_identity() { |
| return Err( |
| ImageViewCreationError::SamplerYcbcrConversionComponentMappingNotIdentity { |
| component_mapping, |
| }, |
| ); |
| } |
| } else { |
| // VUID-VkImageViewCreateInfo-format-06415 |
| if format.ycbcr_chroma_sampling().is_some() { |
| return Err( |
| ImageViewCreationError::FormatRequiresSamplerYcbcrConversion { format }, |
| ); |
| } |
| } |
| |
| Ok(format_features) |
| } |
| |
| #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] |
| pub unsafe fn new_unchecked( |
| image: Arc<I>, |
| create_info: ImageViewCreateInfo, |
| ) -> Result<Arc<Self>, VulkanError> { |
| let format_features = |
| Self::get_format_features(create_info.format.unwrap(), image.inner().image); |
| Self::new_unchecked_with_format_features(image, create_info, format_features) |
| } |
| |
| unsafe fn new_unchecked_with_format_features( |
| image: Arc<I>, |
| create_info: ImageViewCreateInfo, |
| format_features: FormatFeatures, |
| ) -> Result<Arc<Self>, VulkanError> { |
| let &ImageViewCreateInfo { |
| view_type, |
| format, |
| component_mapping, |
| ref subresource_range, |
| mut usage, |
| ref sampler_ycbcr_conversion, |
| _ne: _, |
| } = &create_info; |
| |
| let image_inner = image.inner().image; |
| let device = image_inner.device(); |
| |
| let default_usage = Self::get_default_usage(subresource_range.aspects, image_inner); |
| |
| let has_non_default_usage = if usage.is_empty() { |
| usage = default_usage; |
| false |
| } else { |
| usage == default_usage |
| }; |
| |
| let mut info_vk = ash::vk::ImageViewCreateInfo { |
| flags: ash::vk::ImageViewCreateFlags::empty(), |
| image: image_inner.handle(), |
| view_type: view_type.into(), |
| format: format.unwrap().into(), |
| components: component_mapping.into(), |
| subresource_range: subresource_range.clone().into(), |
| ..Default::default() |
| }; |
| let mut image_view_usage_info_vk = None; |
| let mut sampler_ycbcr_conversion_info_vk = None; |
| |
| if has_non_default_usage { |
| let next = image_view_usage_info_vk.insert(ash::vk::ImageViewUsageCreateInfo { |
| usage: usage.into(), |
| ..Default::default() |
| }); |
| |
| next.p_next = info_vk.p_next; |
| info_vk.p_next = next as *const _ as *const _; |
| } |
| |
| if let Some(conversion) = sampler_ycbcr_conversion { |
| let next = |
| sampler_ycbcr_conversion_info_vk.insert(ash::vk::SamplerYcbcrConversionInfo { |
| conversion: conversion.handle(), |
| ..Default::default() |
| }); |
| |
| next.p_next = info_vk.p_next; |
| info_vk.p_next = next as *const _ as *const _; |
| } |
| |
| let handle = { |
| let fns = device.fns(); |
| let mut output = MaybeUninit::uninit(); |
| (fns.v1_0.create_image_view)( |
| device.handle(), |
| &info_vk, |
| ptr::null(), |
| output.as_mut_ptr(), |
| ) |
| .result() |
| .map_err(VulkanError::from)?; |
| output.assume_init() |
| }; |
| |
| Self::from_handle_with_format_features(image, handle, create_info, format_features) |
| } |
| |
| /// Creates a default `ImageView`. Equivalent to |
| /// `ImageView::new(image, ImageViewCreateInfo::from_image(image))`. |
| pub fn new_default(image: Arc<I>) -> Result<Arc<ImageView<I>>, ImageViewCreationError> { |
| let create_info = ImageViewCreateInfo::from_image(&image); |
| Self::new(image, create_info) |
| } |
| |
| /// Creates a new `ImageView` from a raw object handle. |
| /// |
| /// # Safety |
| /// |
| /// - `handle` must be a valid Vulkan object handle created from `image`. |
| /// - `create_info` must match the info used to create the object. |
| pub unsafe fn from_handle( |
| image: Arc<I>, |
| handle: ash::vk::ImageView, |
| create_info: ImageViewCreateInfo, |
| ) -> Result<Arc<Self>, VulkanError> { |
| let format_features = |
| Self::get_format_features(create_info.format.unwrap(), image.inner().image); |
| Self::from_handle_with_format_features(image, handle, create_info, format_features) |
| } |
| |
| unsafe fn from_handle_with_format_features( |
| image: Arc<I>, |
| handle: ash::vk::ImageView, |
| create_info: ImageViewCreateInfo, |
| format_features: FormatFeatures, |
| ) -> Result<Arc<Self>, VulkanError> { |
| let ImageViewCreateInfo { |
| view_type, |
| format, |
| component_mapping, |
| subresource_range, |
| mut usage, |
| sampler_ycbcr_conversion, |
| _ne: _, |
| } = create_info; |
| |
| let image_inner = image.inner().image; |
| let device = image_inner.device(); |
| |
| if usage.is_empty() { |
| usage = Self::get_default_usage(subresource_range.aspects, image_inner); |
| } |
| |
| let mut filter_cubic = false; |
| let mut filter_cubic_minmax = false; |
| |
| if device |
| .physical_device() |
| .supported_extensions() |
| .ext_filter_cubic |
| { |
| // Use unchecked, because all validation has been done above or is validated by the |
| // image. |
| let properties = |
| device |
| .physical_device() |
| .image_format_properties_unchecked(ImageFormatInfo { |
| flags: image_inner.flags(), |
| format: image_inner.format(), |
| image_type: image.dimensions().image_type(), |
| tiling: image_inner.tiling(), |
| usage: image_inner.usage(), |
| image_view_type: Some(view_type), |
| ..Default::default() |
| })?; |
| |
| if let Some(properties) = properties { |
| filter_cubic = properties.filter_cubic; |
| filter_cubic_minmax = properties.filter_cubic_minmax; |
| } |
| } |
| |
| Ok(Arc::new(ImageView { |
| handle, |
| image, |
| id: Self::next_id(), |
| view_type, |
| format, |
| format_features, |
| component_mapping, |
| subresource_range, |
| usage, |
| sampler_ycbcr_conversion, |
| filter_cubic, |
| filter_cubic_minmax, |
| })) |
| } |
| |
| // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkImageViewCreateInfo.html#_description |
| fn get_default_usage(aspects: ImageAspects, image: &Image) -> ImageUsage { |
| let has_stencil_aspect = aspects.intersects(ImageAspects::STENCIL); |
| let has_non_stencil_aspect = !(aspects - ImageAspects::STENCIL).is_empty(); |
| |
| if has_stencil_aspect && has_non_stencil_aspect { |
| image.usage() & image.stencil_usage() |
| } else if has_stencil_aspect { |
| image.stencil_usage() |
| } else if has_non_stencil_aspect { |
| image.usage() |
| } else { |
| unreachable!() |
| } |
| } |
| |
| // https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/chap12.html#resources-image-view-format-features |
| unsafe fn get_format_features(format: Format, image: &Image) -> FormatFeatures { |
| let device = image.device(); |
| |
| let mut format_features = if Some(format) != image.format() { |
| // Use unchecked, because all validation should have been done before calling. |
| let format_properties = device.physical_device().format_properties_unchecked(format); |
| |
| match image.tiling() { |
| ImageTiling::Optimal => format_properties.optimal_tiling_features, |
| ImageTiling::Linear => format_properties.linear_tiling_features, |
| ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, |
| } |
| } else { |
| image.format_features() |
| }; |
| |
| if !device.enabled_extensions().khr_format_feature_flags2 { |
| if format.type_color().is_none() |
| && format_features.intersects(FormatFeatures::SAMPLED_IMAGE) |
| { |
| format_features |= FormatFeatures::SAMPLED_IMAGE_DEPTH_COMPARISON; |
| } |
| |
| if format.shader_storage_image_without_format() { |
| if device |
| .enabled_features() |
| .shader_storage_image_read_without_format |
| { |
| format_features |= FormatFeatures::STORAGE_READ_WITHOUT_FORMAT; |
| } |
| |
| if device |
| .enabled_features() |
| .shader_storage_image_write_without_format |
| { |
| format_features |= FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT; |
| } |
| } |
| } |
| |
| format_features |
| } |
| |
| /// Returns the wrapped image that this image view was created from. |
| pub fn image(&self) -> &Arc<I> { |
| &self.image |
| } |
| } |
| |
| impl<I> Drop for ImageView<I> |
| where |
| I: ImageAccess + ?Sized, |
| { |
| fn drop(&mut self) { |
| unsafe { |
| let device = self.device(); |
| let fns = device.fns(); |
| (fns.v1_0.destroy_image_view)(device.handle(), self.handle, ptr::null()); |
| } |
| } |
| } |
| |
| unsafe impl<I> VulkanObject for ImageView<I> |
| where |
| I: ImageAccess + ?Sized, |
| { |
| type Handle = ash::vk::ImageView; |
| |
| fn handle(&self) -> Self::Handle { |
| self.handle |
| } |
| } |
| |
| unsafe impl<I> DeviceOwned for ImageView<I> |
| where |
| I: ImageAccess + ?Sized, |
| { |
| fn device(&self) -> &Arc<Device> { |
| self.image.inner().image.device() |
| } |
| } |
| |
| impl_id_counter!(ImageView<I: ImageAccess + ?Sized>); |
| |
| /// Parameters to create a new `ImageView`. |
| #[derive(Debug)] |
| pub struct ImageViewCreateInfo { |
| /// The image view type. |
| /// |
| /// The view type must be compatible with the dimensions of the image and the selected array |
| /// layers. |
| /// |
| /// The default value is [`ImageViewType::Dim2d`]. |
| pub view_type: ImageViewType, |
| |
| /// The format of the image view. |
| /// |
| /// If this is set to a format that is different from the image, the image must be created with |
| /// the `mutable_format` flag. |
| /// |
| /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag) |
| /// devices, if `format` does not have the same number of components and bits per component as |
| /// the parent image's format, the |
| /// [`image_view_format_reinterpretation`](crate::device::Features::image_view_format_reinterpretation) |
| /// feature must be enabled on the device. |
| /// |
| /// The default value is `None`, which must be overridden. |
| pub format: Option<Format>, |
| |
| /// How to map components of each pixel. |
| /// |
| /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag) |
| /// devices, if `component_mapping` is not the identity mapping, the |
| /// [`image_view_format_swizzle`](crate::device::Features::image_view_format_swizzle) |
| /// feature must be enabled on the device. |
| /// |
| /// The default value is [`ComponentMapping::identity()`]. |
| pub component_mapping: ComponentMapping, |
| |
| /// The subresource range of the image that the view should cover. |
| /// |
| /// The default value is empty, which must be overridden. |
| pub subresource_range: ImageSubresourceRange, |
| |
| /// How the image view is going to be used. |
| /// |
| /// If `usage` is empty, then a default value is used based on the parent image's usages. |
| /// Depending on the image aspects selected in `subresource_range`, |
| /// the default `usage` will be equal to the parent image's `usage`, its `stencil_usage`, |
| /// or the intersection of the two. |
| /// |
| /// If you set `usage` to a different value from the default, then the device API version must |
| /// be at least 1.1, or the [`khr_maintenance2`](crate::device::DeviceExtensions::khr_maintenance2) |
| /// extension must be enabled on the device. The specified `usage` must be a subset of the |
| /// default value; usages that are not set for the parent image are not allowed. |
| /// |
| /// The default value is [`ImageUsage::empty()`]. |
| pub usage: ImageUsage, |
| |
| /// The sampler YCbCr conversion to be used with the image view. |
| /// |
| /// If set to `Some`, several restrictions apply: |
| /// - The `component_mapping` must be the identity swizzle for all components. |
| /// - If the image view is to be used in a shader, it must be in a combined image sampler |
| /// descriptor, a separate sampled image descriptor is not allowed. |
| /// - The corresponding sampler must have the same sampler YCbCr object or an identically |
| /// created one, and must be used as an immutable sampler within a descriptor set layout. |
| /// |
| /// The default value is `None`. |
| pub sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>, |
| |
| pub _ne: crate::NonExhaustive, |
| } |
| |
| impl Default for ImageViewCreateInfo { |
| #[inline] |
| fn default() -> Self { |
| Self { |
| view_type: ImageViewType::Dim2d, |
| format: None, |
| component_mapping: ComponentMapping::identity(), |
| subresource_range: ImageSubresourceRange { |
| aspects: ImageAspects::empty(), |
| array_layers: 0..0, |
| mip_levels: 0..0, |
| }, |
| usage: ImageUsage::empty(), |
| sampler_ycbcr_conversion: None, |
| _ne: crate::NonExhaustive(()), |
| } |
| } |
| } |
| |
| impl ImageViewCreateInfo { |
| /// Returns an `ImageViewCreateInfo` with the `view_type` determined from the image type and |
| /// array layers, and `subresource_range` determined from the image format and covering the |
| /// whole image. |
| pub fn from_image(image: &(impl ImageAccess + ?Sized)) -> Self { |
| Self { |
| view_type: match image.dimensions() { |
| ImageDimensions::Dim1d { |
| array_layers: 1, .. |
| } => ImageViewType::Dim1d, |
| ImageDimensions::Dim1d { .. } => ImageViewType::Dim1dArray, |
| ImageDimensions::Dim2d { |
| array_layers: 1, .. |
| } => ImageViewType::Dim2d, |
| ImageDimensions::Dim2d { .. } => ImageViewType::Dim2dArray, |
| ImageDimensions::Dim3d { .. } => ImageViewType::Dim3d, |
| }, |
| format: Some(image.format()), |
| subresource_range: image.subresource_range(), |
| ..Default::default() |
| } |
| } |
| } |
| |
| /// Error that can happen when creating an image view. |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| pub enum ImageViewCreationError { |
| /// Allocating memory failed. |
| OomError(OomError), |
| |
| RequirementNotMet { |
| required_for: &'static str, |
| requires_one_of: RequiresOneOf, |
| }, |
| |
| /// A 2D image view was requested from a 3D image, but a range of multiple mip levels was |
| /// specified. |
| Array2dCompatibleMultipleMipLevels, |
| |
| /// The specified range of array layers was not a subset of those in the image. |
| ArrayLayersOutOfRange { range_end: u32, max: u32 }, |
| |
| /// The image has the `block_texel_view_compatible` flag, but a range of multiple array layers |
| /// was specified. |
| BlockTexelViewCompatibleMultipleArrayLayers, |
| |
| /// The image has the `block_texel_view_compatible` flag, but a range of multiple mip levels |
| /// was specified. |
| BlockTexelViewCompatibleMultipleMipLevels, |
| |
| /// The requested format has chroma subsampling, but the width and/or height of the image was |
| /// not a multiple of 2. |
| FormatChromaSubsamplingInvalidImageDimensions, |
| |
| /// The requested format was not compatible with the image. |
| FormatNotCompatible, |
| |
| /// The given format was not supported by the device. |
| FormatNotSupported, |
| |
| /// The format requires a sampler YCbCr conversion, but none was provided. |
| FormatRequiresSamplerYcbcrConversion { format: Format }, |
| |
| /// A requested usage flag was not supported by the given format. |
| FormatUsageNotSupported { usage: &'static str }, |
| |
| /// An aspect was selected that was not present in the image. |
| ImageAspectsNotCompatible { |
| aspects: ImageAspects, |
| image_aspects: ImageAspects, |
| }, |
| |
| /// The image was not created with |
| /// [one of the required usages](https://registry.khronos.org/vulkan/specs/1.2-extensions/html/vkspec.html#valid-imageview-imageusage) |
| /// for image views. |
| ImageMissingUsage, |
| |
| /// A 2D image view was requested from a 3D image, but the image was not created with the |
| /// `array_2d_compatible` flag. |
| ImageNotArray2dCompatible, |
| |
| /// A cube image view type was requested, but the image was not created with the |
| /// `cube_compatible` flag. |
| ImageNotCubeCompatible, |
| |
| /// The given image view type was not compatible with the type of the image. |
| ImageTypeNotCompatible, |
| |
| /// The requested [`ImageViewType`] was not compatible with the image, or with the specified |
| /// ranges of array layers and mipmap levels. |
| IncompatibleType, |
| |
| /// The specified range of mip levels was not a subset of those in the image. |
| MipLevelsOutOfRange { range_end: u32, max: u32 }, |
| |
| /// The image has multisampling enabled, but the image view type was not `Dim2d` or |
| /// `Dim2dArray`. |
| MultisamplingNot2d, |
| |
| /// Sampler YCbCr conversion was enabled, but `component_mapping` was not the identity mapping. |
| SamplerYcbcrConversionComponentMappingNotIdentity { component_mapping: ComponentMapping }, |
| |
| /// The `CubeArray` image view type was specified, but the range of array layers did not have a |
| /// size that is a multiple 6. |
| TypeCubeArrayNotMultipleOf6ArrayLayers, |
| |
| /// The `Cube` image view type was specified, but the range of array layers did not have a size |
| /// of 6. |
| TypeCubeNot6ArrayLayers, |
| |
| /// A non-arrayed image view type was specified, but a range of multiple array layers was |
| /// specified. |
| TypeNonArrayedMultipleArrayLayers, |
| |
| /// The provided `usage` is not supported by the parent image. |
| UsageNotSupportedByImage { |
| usage: ImageUsage, |
| supported_usage: ImageUsage, |
| }, |
| } |
| |
| impl Error for ImageViewCreationError { |
| fn source(&self) -> Option<&(dyn Error + 'static)> { |
| match self { |
| ImageViewCreationError::OomError(err) => Some(err), |
| _ => None, |
| } |
| } |
| } |
| |
| impl Display for ImageViewCreationError { |
| fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { |
| match self { |
| Self::OomError(_) => write!(f, "allocating memory failed",), |
| Self::RequirementNotMet { |
| required_for, |
| requires_one_of, |
| } => write!( |
| f, |
| "a requirement was not met for: {}; requires one of: {}", |
| required_for, requires_one_of, |
| ), |
| Self::Array2dCompatibleMultipleMipLevels => write!( |
| f, |
| "a 2D image view was requested from a 3D image, but a range of multiple mip levels \ |
| was specified", |
| ), |
| Self::ArrayLayersOutOfRange { .. } => write!( |
| f, |
| "the specified range of array layers was not a subset of those in the image", |
| ), |
| Self::BlockTexelViewCompatibleMultipleArrayLayers => write!( |
| f, |
| "the image has the `block_texel_view_compatible` flag, but a range of multiple \ |
| array layers was specified", |
| ), |
| Self::BlockTexelViewCompatibleMultipleMipLevels => write!( |
| f, |
| "the image has the `block_texel_view_compatible` flag, but a range of multiple mip \ |
| levels was specified", |
| ), |
| Self::FormatChromaSubsamplingInvalidImageDimensions => write!( |
| f, |
| "the requested format has chroma subsampling, but the width and/or height of the \ |
| image was not a multiple of 2", |
| ), |
| Self::FormatNotCompatible => { |
| write!(f, "the requested format was not compatible with the image") |
| } |
| Self::FormatNotSupported => { |
| write!(f, "the given format was not supported by the device") |
| } |
| Self::FormatRequiresSamplerYcbcrConversion { .. } => write!( |
| f, |
| "the format requires a sampler YCbCr conversion, but none was provided", |
| ), |
| Self::FormatUsageNotSupported { .. } => write!( |
| f, |
| "a requested usage flag was not supported by the given format", |
| ), |
| Self::ImageAspectsNotCompatible { .. } => write!( |
| f, |
| "an aspect was selected that was not present in the image", |
| ), |
| Self::ImageMissingUsage => write!( |
| f, |
| "the image was not created with one of the required usages for image views", |
| ), |
| Self::ImageNotArray2dCompatible => write!( |
| f, |
| "a 2D image view was requested from a 3D image, but the image was not created with \ |
| the `array_2d_compatible` flag", |
| ), |
| Self::ImageNotCubeCompatible => write!( |
| f, |
| "a cube image view type was requested, but the image was not created with the \ |
| `cube_compatible` flag", |
| ), |
| Self::ImageTypeNotCompatible => write!( |
| f, |
| "the given image view type was not compatible with the type of the image", |
| ), |
| Self::IncompatibleType => write!( |
| f, |
| "image view type is not compatible with image, array layers or mipmap levels", |
| ), |
| Self::MipLevelsOutOfRange { .. } => write!( |
| f, |
| "the specified range of mip levels was not a subset of those in the image", |
| ), |
| Self::MultisamplingNot2d => write!( |
| f, |
| "the image has multisampling enabled, but the image view type was not `Dim2d` or \ |
| `Dim2dArray`", |
| ), |
| Self::SamplerYcbcrConversionComponentMappingNotIdentity { .. } => write!( |
| f, |
| "sampler YCbCr conversion was enabled, but `component_mapping` was not the \ |
| identity mapping", |
| ), |
| Self::TypeCubeArrayNotMultipleOf6ArrayLayers => write!( |
| f, |
| "the `CubeArray` image view type was specified, but the range of array layers did \ |
| not have a size that is a multiple 6", |
| ), |
| Self::TypeCubeNot6ArrayLayers => write!( |
| f, |
| "the `Cube` image view type was specified, but the range of array layers did not \ |
| have a size of 6", |
| ), |
| Self::TypeNonArrayedMultipleArrayLayers => write!( |
| f, |
| "a non-arrayed image view type was specified, but a range of multiple array layers \ |
| was specified", |
| ), |
| Self::UsageNotSupportedByImage { |
| usage: _, |
| supported_usage: _, |
| } => write!( |
| f, |
| "the provided `usage` is not supported by the parent image", |
| ), |
| } |
| } |
| } |
| |
| impl From<OomError> for ImageViewCreationError { |
| fn from(err: OomError) -> ImageViewCreationError { |
| ImageViewCreationError::OomError(err) |
| } |
| } |
| |
| impl From<VulkanError> for ImageViewCreationError { |
| fn from(err: VulkanError) -> ImageViewCreationError { |
| match err { |
| err @ VulkanError::OutOfHostMemory => OomError::from(err).into(), |
| err @ VulkanError::OutOfDeviceMemory => OomError::from(err).into(), |
| _ => panic!("unexpected error: {:?}", err), |
| } |
| } |
| } |
| |
| impl From<RequirementNotMet> for ImageViewCreationError { |
| fn from(err: RequirementNotMet) -> Self { |
| Self::RequirementNotMet { |
| required_for: err.required_for, |
| requires_one_of: err.requires_one_of, |
| } |
| } |
| } |
| |
| vulkan_enum! { |
| #[non_exhaustive] |
| |
| /// The geometry type of an image view. |
| ImageViewType impl { |
| /// Returns whether the type is arrayed. |
| #[inline] |
| pub fn is_arrayed(self) -> bool { |
| match self { |
| Self::Dim1d | Self::Dim2d | Self::Dim3d | Self::Cube => false, |
| Self::Dim1dArray | Self::Dim2dArray | Self::CubeArray => true, |
| } |
| } |
| |
| /// Returns whether `self` is compatible with the given `image_type`. |
| #[inline] |
| pub fn is_compatible_with(self, image_type: ImageType) -> bool { |
| matches!( |
| (self, image_type,), |
| ( |
| ImageViewType::Dim1d | ImageViewType::Dim1dArray, |
| ImageType::Dim1d |
| ) | ( |
| ImageViewType::Dim2d | ImageViewType::Dim2dArray, |
| ImageType::Dim2d | ImageType::Dim3d |
| ) | ( |
| ImageViewType::Cube | ImageViewType::CubeArray, |
| ImageType::Dim2d |
| ) | (ImageViewType::Dim3d, ImageType::Dim3d) |
| ) |
| } |
| } |
| = ImageViewType(i32); |
| |
| // TODO: document |
| Dim1d = TYPE_1D, |
| |
| // TODO: document |
| Dim2d = TYPE_2D, |
| |
| // TODO: document |
| Dim3d = TYPE_3D, |
| |
| // TODO: document |
| Cube = CUBE, |
| |
| // TODO: document |
| Dim1dArray = TYPE_1D_ARRAY, |
| |
| // TODO: document |
| Dim2dArray = TYPE_2D_ARRAY, |
| |
| // TODO: document |
| CubeArray = CUBE_ARRAY, |
| } |
| |
| /// Trait for types that represent the GPU can access an image view. |
| pub unsafe trait ImageViewAbstract: |
| VulkanObject<Handle = ash::vk::ImageView> + DeviceOwned + Debug + Send + Sync |
| { |
| /// Returns the wrapped image that this image view was created from. |
| fn image(&self) -> Arc<dyn ImageAccess>; |
| |
| /// Returns the component mapping of this view. |
| fn component_mapping(&self) -> ComponentMapping; |
| |
| /// Returns the dimensions of this view. |
| #[inline] |
| fn dimensions(&self) -> ImageDimensions { |
| let subresource_range = self.subresource_range(); |
| let array_layers = |
| subresource_range.array_layers.end - subresource_range.array_layers.start; |
| |
| match self.image().dimensions() { |
| ImageDimensions::Dim1d { width, .. } => ImageDimensions::Dim1d { |
| width, |
| array_layers, |
| }, |
| ImageDimensions::Dim2d { width, height, .. } => ImageDimensions::Dim2d { |
| width, |
| height, |
| array_layers, |
| }, |
| ImageDimensions::Dim3d { |
| width, |
| height, |
| depth, |
| } => ImageDimensions::Dim3d { |
| width, |
| height, |
| depth, |
| }, |
| } |
| } |
| |
| /// Returns whether the image view supports sampling with a |
| /// [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`. |
| fn filter_cubic(&self) -> bool; |
| |
| /// Returns whether the image view supports sampling with a |
| /// [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`, and with a |
| /// [`Min`](crate::sampler::SamplerReductionMode::Min) or |
| /// [`Max`](crate::sampler::SamplerReductionMode::Max) `reduction_mode`. |
| fn filter_cubic_minmax(&self) -> bool; |
| |
| /// Returns the format of this view. This can be different from the parent's format. |
| fn format(&self) -> Option<Format>; |
| |
| /// Returns the features supported by the image view's format. |
| fn format_features(&self) -> FormatFeatures; |
| |
| /// Returns the sampler YCbCr conversion that this image view was created with, if any. |
| fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>>; |
| |
| /// Returns the subresource range of the wrapped image that this view exposes. |
| fn subresource_range(&self) -> &ImageSubresourceRange; |
| |
| /// Returns the usage of the image view. |
| fn usage(&self) -> ImageUsage; |
| |
| /// Returns the [`ImageViewType`] of this image view. |
| fn view_type(&self) -> ImageViewType; |
| } |
| |
| unsafe impl<I> ImageViewAbstract for ImageView<I> |
| where |
| I: ImageAccess + Debug + 'static, |
| { |
| fn image(&self) -> Arc<dyn ImageAccess> { |
| self.image.clone() |
| } |
| |
| fn component_mapping(&self) -> ComponentMapping { |
| self.component_mapping |
| } |
| |
| fn filter_cubic(&self) -> bool { |
| self.filter_cubic |
| } |
| |
| fn filter_cubic_minmax(&self) -> bool { |
| self.filter_cubic_minmax |
| } |
| |
| fn format(&self) -> Option<Format> { |
| self.format |
| } |
| |
| fn format_features(&self) -> FormatFeatures { |
| self.format_features |
| } |
| |
| fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>> { |
| self.sampler_ycbcr_conversion.as_ref() |
| } |
| |
| fn subresource_range(&self) -> &ImageSubresourceRange { |
| &self.subresource_range |
| } |
| |
| fn usage(&self) -> ImageUsage { |
| self.usage |
| } |
| |
| fn view_type(&self) -> ImageViewType { |
| self.view_type |
| } |
| } |
| |
| unsafe impl ImageViewAbstract for ImageView<dyn ImageAccess> { |
| #[inline] |
| fn image(&self) -> Arc<dyn ImageAccess> { |
| self.image.clone() |
| } |
| |
| #[inline] |
| fn component_mapping(&self) -> ComponentMapping { |
| self.component_mapping |
| } |
| |
| #[inline] |
| fn filter_cubic(&self) -> bool { |
| self.filter_cubic |
| } |
| |
| #[inline] |
| fn filter_cubic_minmax(&self) -> bool { |
| self.filter_cubic_minmax |
| } |
| |
| #[inline] |
| fn format(&self) -> Option<Format> { |
| self.format |
| } |
| |
| #[inline] |
| fn format_features(&self) -> FormatFeatures { |
| self.format_features |
| } |
| |
| #[inline] |
| fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>> { |
| self.sampler_ycbcr_conversion.as_ref() |
| } |
| |
| #[inline] |
| fn subresource_range(&self) -> &ImageSubresourceRange { |
| &self.subresource_range |
| } |
| |
| #[inline] |
| fn usage(&self) -> ImageUsage { |
| self.usage |
| } |
| |
| #[inline] |
| fn view_type(&self) -> ImageViewType { |
| self.view_type |
| } |
| } |
| |
| impl PartialEq for dyn ImageViewAbstract { |
| #[inline] |
| fn eq(&self, other: &Self) -> bool { |
| self.handle() == other.handle() && self.device() == other.device() |
| } |
| } |
| |
| impl Eq for dyn ImageViewAbstract {} |
| |
| impl Hash for dyn ImageViewAbstract { |
| fn hash<H: Hasher>(&self, state: &mut H) { |
| self.handle().hash(state); |
| self.device().hash(state); |
| } |
| } |