hal/vk: textures and samplers

This commit is contained in:
Dzmitry Malyshau 2021-06-11 19:14:41 -04:00
parent b4380e4576
commit 61e2e242cd
10 changed files with 295 additions and 103 deletions

View File

@ -513,6 +513,7 @@ impl<A: HalApi> Device<A> {
return Err(resource::CreateTextureError::InvalidMipLevelCount(mips));
}
let hal_usage = conv::map_texture_usage(desc.usage, desc.format.into());
let hal_desc = hal::TextureDescriptor {
label: desc.label.borrow_option(),
size: desc.size,
@ -520,7 +521,7 @@ impl<A: HalApi> Device<A> {
sample_count: desc.sample_count,
dimension: desc.dimension,
format: desc.format,
usage: conv::map_texture_usage(desc.usage, desc.format.into()),
usage: hal_usage,
memory_flags: hal::MemoryFlag::empty(),
};
let raw = unsafe {
@ -536,6 +537,7 @@ impl<A: HalApi> Device<A> {
ref_count: self.life_guard.add_ref(),
},
desc: desc.map_label(|_| ()),
hal_usage,
format_features,
full_range: TextureSelector {
levels: 0..desc.mip_level_count,
@ -677,6 +679,7 @@ impl<A: HalApi> Device<A> {
label: desc.label.borrow_option(),
format,
dimension: view_dim,
usage: texture.hal_usage, // pass-through
range: desc.range.clone(),
};

View File

@ -165,6 +165,7 @@ pub struct Texture<A: hal::Api> {
pub(crate) raw: Option<A::Texture>,
pub(crate) device_id: Stored<DeviceId>,
pub(crate) desc: wgt::TextureDescriptor<()>,
pub(crate) hal_usage: hal::TextureUse,
pub(crate) format_features: wgt::TextureFormatFeatures,
pub(crate) full_range: TextureSelector,
pub(crate) life_guard: LifeGuard,

View File

@ -174,6 +174,7 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
label: Some("_Frame"),
format: sc.desc.format,
dimension: wgt::TextureViewDimension::D2,
usage: hal::TextureUse::COLOR_TARGET,
range: wgt::ImageSubresourceRange::default(),
};

23
wgpu-hal/README.md Normal file
View File

@ -0,0 +1,23 @@
*wgpu-hal* is an explicit low-level GPU abstraction powering *wgpu-core*.
It's a spiritual successor to [gfx-hal](https://github.com/gfx-rs/gfx),
but with reduced scope, and oriented towards WebGPU implementation goals.
It has no overhead for validation or tracking, and the API translation overhead is kept to the bare minimum by the design of WebGPU.
This API can be used for resource-demaninging applications and engines.
# Usage notes
All of the API is `unsafe`. Documenting the exact safety requirements for the
state and function arguments is desired, but will likely be incomplete while the library is in early development.
The returned errors are only for cases that the user can't anticipate,
such as running out-of-memory, or losing the device.
For the counter-example, there is no error for mapping a buffer that's not mappable.
As the buffer creator, the user should already know if they can map it.
The API accept iterators in order to avoid forcing the user to store data in particular containers. The implementation doesn't guarantee that any of the iterators are drained, unless stated otherwise by the function documentation.
For this reason, we recommend that iterators don't do any mutating work.
# Debugging
Most of the information in https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications still applies to this API, with an exception of API tracing infrastructure.

View File

@ -12,7 +12,10 @@
* - Resource transitions are explicit.
* - All layouts are explicit. Binding model has compatibility.
*
* General design direction: follow 2/3 major target APIs.
* General design direction is to follow the majority by the following weights:
* - wgpu-core: 1.5
* - primary backends (Vulkan/Metal/DX12): 1.0 each
* - secondary backends (DX11/GLES): 0.5 each
*/
#![allow(
@ -197,16 +200,12 @@ pub trait Device<A: Api>: Send + Sync {
range: MemoryRange,
) -> Result<BufferMapping, DeviceError>;
unsafe fn unmap_buffer(&self, buffer: &A::Buffer) -> Result<(), DeviceError>;
unsafe fn flush_mapped_ranges<I: Iterator<Item = MemoryRange>>(
&self,
buffer: &A::Buffer,
ranges: I,
);
unsafe fn invalidate_mapped_ranges<I: Iterator<Item = MemoryRange>>(
&self,
buffer: &A::Buffer,
ranges: I,
);
unsafe fn flush_mapped_ranges<I>(&self, buffer: &A::Buffer, ranges: I)
where
I: Iterator<Item = MemoryRange>;
unsafe fn invalidate_mapped_ranges<I>(&self, buffer: &A::Buffer, ranges: I)
where
I: Iterator<Item = MemoryRange>;
/// Creates a new texture.
///
@ -696,11 +695,19 @@ pub struct TextureDescriptor<'a> {
pub memory_flags: MemoryFlag,
}
/// TextureView descriptor.
///
/// Valid usage:
///. - `format` has to be the same as `TextureDescriptor::format`
///. - `dimension` has to be compatible with `TextureDescriptor::dimension`
///. - `usage` has to be a subset of `TextureDescriptor::usage`
///. - `range` has to be a subset of parent texture
#[derive(Clone, Debug)]
pub struct TextureViewDescriptor<'a> {
pub label: Label<'a>,
pub format: wgt::TextureFormat,
pub dimension: wgt::TextureViewDimension,
pub usage: TextureUse,
pub range: wgt::ImageSubresourceRange,
}

View File

@ -585,7 +585,6 @@ impl super::Instance {
| vk::MemoryPropertyFlags::LAZILY_ALLOCATED,
phd_capabilities,
phd_features,
available_features,
downlevel_flags,
private_caps,
};
@ -750,6 +749,7 @@ impl crate::Adapter<super::Api> for super::Adapter {
},
features,
vendor_id: self.phd_capabilities.properties.vendor_id,
downlevel_flags: self.downlevel_flags,
private_caps: self.private_caps.clone(),
timestamp_period: self.phd_capabilities.properties.limits.timestamp_period,
}),

View File

@ -1,4 +1,5 @@
use ash::vk;
use std::num::NonZeroU32;
impl super::PrivateCapabilities {
pub fn map_texture_format(&self, format: wgt::TextureFormat) -> vk::Format {
@ -303,3 +304,77 @@ pub fn map_buffer_usage(usage: crate::BufferUse) -> vk::BufferUsageFlags {
}
flags
}
pub fn map_view_dimension(dim: wgt::TextureViewDimension) -> vk::ImageViewType {
match dim {
wgt::TextureViewDimension::D1 => vk::ImageViewType::TYPE_1D,
wgt::TextureViewDimension::D2 => vk::ImageViewType::TYPE_2D,
wgt::TextureViewDimension::D2Array => vk::ImageViewType::TYPE_2D_ARRAY,
wgt::TextureViewDimension::Cube => vk::ImageViewType::CUBE,
wgt::TextureViewDimension::CubeArray => vk::ImageViewType::CUBE_ARRAY,
wgt::TextureViewDimension::D3 => vk::ImageViewType::TYPE_3D,
}
}
pub fn map_subresource_range(
range: &wgt::ImageSubresourceRange,
texture_aspect: crate::FormatAspect,
) -> vk::ImageSubresourceRange {
vk::ImageSubresourceRange {
aspect_mask: map_aspects(crate::FormatAspect::from(range.aspect) & texture_aspect),
base_mip_level: range.base_mip_level,
level_count: range
.mip_level_count
.map_or(vk::REMAINING_MIP_LEVELS, NonZeroU32::get),
base_array_layer: range.base_array_layer,
layer_count: range
.array_layer_count
.map_or(vk::REMAINING_ARRAY_LAYERS, NonZeroU32::get),
}
}
pub fn map_filter_mode(mode: wgt::FilterMode) -> vk::Filter {
match mode {
wgt::FilterMode::Nearest => vk::Filter::NEAREST,
wgt::FilterMode::Linear => vk::Filter::LINEAR,
}
}
pub fn map_mip_filter_mode(mode: wgt::FilterMode) -> vk::SamplerMipmapMode {
match mode {
wgt::FilterMode::Nearest => vk::SamplerMipmapMode::NEAREST,
wgt::FilterMode::Linear => vk::SamplerMipmapMode::LINEAR,
}
}
pub fn map_address_mode(mode: wgt::AddressMode) -> vk::SamplerAddressMode {
match mode {
wgt::AddressMode::ClampToEdge => vk::SamplerAddressMode::CLAMP_TO_EDGE,
wgt::AddressMode::Repeat => vk::SamplerAddressMode::REPEAT,
wgt::AddressMode::MirrorRepeat => vk::SamplerAddressMode::MIRRORED_REPEAT,
wgt::AddressMode::ClampToBorder => vk::SamplerAddressMode::CLAMP_TO_BORDER,
//wgt::AddressMode::MirrorClamp => vk::SamplerAddressMode::MIRROR_CLAMP_TO_EDGE,
}
}
pub fn map_border_color(border_color: wgt::SamplerBorderColor) -> vk::BorderColor {
match border_color {
wgt::SamplerBorderColor::TransparentBlack => vk::BorderColor::FLOAT_TRANSPARENT_BLACK,
wgt::SamplerBorderColor::OpaqueBlack => vk::BorderColor::FLOAT_OPAQUE_BLACK,
wgt::SamplerBorderColor::OpaqueWhite => vk::BorderColor::FLOAT_OPAQUE_WHITE,
}
}
pub fn map_comparison(fun: wgt::CompareFunction) -> vk::CompareOp {
use wgt::CompareFunction as Cf;
match fun {
Cf::Never => vk::CompareOp::NEVER,
Cf::Less => vk::CompareOp::LESS,
Cf::LessEqual => vk::CompareOp::LESS_OR_EQUAL,
Cf::Equal => vk::CompareOp::EQUAL,
Cf::GreaterEqual => vk::CompareOp::GREATER_OR_EQUAL,
Cf::Greater => vk::CompareOp::GREATER,
Cf::NotEqual => vk::CompareOp::NOT_EQUAL,
Cf::Always => vk::CompareOp::ALWAYS,
}
}

View File

@ -1,6 +1,7 @@
use super::conv;
use ash::{extensions::khr, version::DeviceV1_0, vk};
use parking_lot::Mutex;
use std::{ptr::NonNull, sync::Arc};
@ -71,57 +72,18 @@ impl gpu_alloc::MemoryDevice<vk::DeviceMemory> for super::DeviceShared {
unsafe fn invalidate_memory_ranges(
&self,
ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>],
_ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>],
) -> Result<(), gpu_alloc::OutOfMemory> {
let vk_ranges = ranges.iter().map(|range| {
vk::MappedMemoryRange::builder()
.memory(*range.memory)
.offset(range.offset)
.size(range.size)
.build()
});
let result = inplace_it::inplace_or_alloc_from_iter(vk_ranges, |array| {
self.raw.invalidate_mapped_memory_ranges(array)
});
result.map_err(|err| match err {
vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => gpu_alloc::OutOfMemory::OutOfDeviceMemory,
vk::Result::ERROR_OUT_OF_HOST_MEMORY => gpu_alloc::OutOfMemory::OutOfHostMemory,
err => panic!("Unexpected Vulkan error: `{}`", err),
})
// should never be called
unimplemented!()
}
unsafe fn flush_memory_ranges(
&self,
ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>],
_ranges: &[gpu_alloc::MappedMemoryRange<'_, vk::DeviceMemory>],
) -> Result<(), gpu_alloc::OutOfMemory> {
let vk_ranges = ranges.iter().map(|range| {
vk::MappedMemoryRange::builder()
.memory(*range.memory)
.offset(range.offset)
.size(range.size)
.build()
});
let result = inplace_it::inplace_or_alloc_from_iter(vk_ranges, |array| {
self.raw.flush_mapped_memory_ranges(array)
});
result.map_err(|err| match err {
vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => gpu_alloc::OutOfMemory::OutOfDeviceMemory,
vk::Result::ERROR_OUT_OF_HOST_MEMORY => gpu_alloc::OutOfMemory::OutOfHostMemory,
err => panic!("Unexpected Vulkan error: `{}`", err),
})
}
}
impl From<gpu_alloc::AllocationError> for crate::DeviceError {
fn from(error: gpu_alloc::AllocationError) -> Self {
use gpu_alloc::AllocationError as Ae;
match error {
Ae::OutOfDeviceMemory | Ae::OutOfHostMemory => Self::OutOfMemory,
_ => {
log::error!("memory allocation: {:?}", error);
Self::Lost
}
}
// should never be called
unimplemented!()
}
}
@ -189,16 +151,9 @@ impl super::Device {
.create_fence(&vk_info, None)
.map_err(crate::DeviceError::from)?;
let extent = vk::Extent3D {
width: config.extent.width,
height: config.extent.height,
depth: 1,
};
Ok(super::Swapchain {
raw,
functor,
extent,
device: Arc::clone(&self.shared),
fence,
images,
@ -257,27 +212,71 @@ impl crate::Device<super::Api> for super::Device {
.raw
.bind_buffer_memory(raw, *block.memory(), block.offset())?;
Ok(super::Buffer { raw, block })
Ok(super::Buffer {
raw,
block: Mutex::new(block),
})
}
unsafe fn destroy_buffer(&self, buffer: super::Buffer) {
self.shared.raw.destroy_buffer(buffer.raw, None);
self.mem_allocator
.lock()
.dealloc(&*self.shared, buffer.block);
.dealloc(&*self.shared, buffer.block.into_inner());
}
unsafe fn map_buffer(
&self,
buffer: &super::Buffer,
range: crate::MemoryRange,
) -> DeviceResult<crate::BufferMapping> {
Err(crate::DeviceError::Lost)
) -> Result<crate::BufferMapping, crate::DeviceError> {
let size = range.end - range.start;
let mut block = buffer.block.lock();
let ptr = block.map(&*self.shared, range.start, size as usize)?;
let is_coherent = block
.props()
.contains(gpu_alloc::MemoryPropertyFlags::HOST_COHERENT);
Ok(crate::BufferMapping { ptr, is_coherent })
}
unsafe fn unmap_buffer(&self, buffer: &super::Buffer) -> DeviceResult<()> {
unsafe fn unmap_buffer(&self, buffer: &super::Buffer) -> Result<(), crate::DeviceError> {
buffer.block.lock().unmap(&*self.shared);
Ok(())
}
unsafe fn flush_mapped_ranges<I>(&self, buffer: &super::Buffer, ranges: I) {}
unsafe fn invalidate_mapped_ranges<I>(&self, buffer: &super::Buffer, ranges: I) {}
unsafe fn flush_mapped_ranges<I>(&self, buffer: &super::Buffer, ranges: I)
where
I: Iterator<Item = crate::MemoryRange>,
{
let block = buffer.block.lock();
let vk_ranges = ranges.map(|range| {
vk::MappedMemoryRange::builder()
.memory(*block.memory())
.offset(block.offset() + range.start)
.size(range.end - range.start)
.build()
});
inplace_it::inplace_or_alloc_from_iter(vk_ranges, |array| {
self.shared.raw.flush_mapped_memory_ranges(array).unwrap()
});
}
unsafe fn invalidate_mapped_ranges<I>(&self, buffer: &super::Buffer, ranges: I)
where
I: Iterator<Item = crate::MemoryRange>,
{
let block = buffer.block.lock();
let vk_ranges = ranges.map(|range| {
vk::MappedMemoryRange::builder()
.memory(*block.memory())
.offset(block.offset() + range.start)
.size(range.end - range.start)
.build()
});
inplace_it::inplace_or_alloc_from_iter(vk_ranges, |array| {
self.shared
.raw
.invalidate_mapped_memory_ranges(array)
.unwrap()
});
}
unsafe fn create_texture(
&self,
@ -322,6 +321,7 @@ impl crate::Device<super::Api> for super::Device {
Ok(super::Texture {
raw,
block: Some(block),
aspects: crate::FormatAspect::from(desc.format),
})
}
unsafe fn destroy_texture(&self, texture: super::Texture) {
@ -335,14 +335,74 @@ impl crate::Device<super::Api> for super::Device {
&self,
texture: &super::Texture,
desc: &crate::TextureViewDescriptor,
) -> DeviceResult<Resource> {
Ok(Resource)
) -> Result<super::TextureView, crate::DeviceError> {
let mut vk_info = vk::ImageViewCreateInfo::builder()
.flags(vk::ImageViewCreateFlags::empty())
.image(texture.raw)
.view_type(conv::map_view_dimension(desc.dimension))
.format(self.shared.private_caps.map_texture_format(desc.format))
//.components(conv::map_swizzle(swizzle))
.subresource_range(conv::map_subresource_range(&desc.range, texture.aspects));
let mut image_view_info;
if self.shared.private_caps.image_view_usage {
image_view_info = vk::ImageViewUsageCreateInfo::builder()
.usage(conv::map_texture_usage(desc.usage))
.build();
vk_info = vk_info.push_next(&mut image_view_info);
}
let raw = self.shared.raw.create_image_view(&vk_info, None)?;
Ok(super::TextureView { raw })
}
unsafe fn destroy_texture_view(&self, view: Resource) {}
unsafe fn create_sampler(&self, desc: &crate::SamplerDescriptor) -> DeviceResult<Resource> {
Ok(Resource)
unsafe fn destroy_texture_view(&self, view: super::TextureView) {
self.shared.raw.destroy_image_view(view.raw, None);
}
unsafe fn create_sampler(
&self,
desc: &crate::SamplerDescriptor,
) -> Result<super::Sampler, crate::DeviceError> {
let mut vk_info = vk::SamplerCreateInfo::builder()
.flags(vk::SamplerCreateFlags::empty())
.mag_filter(conv::map_filter_mode(desc.mag_filter))
.min_filter(conv::map_filter_mode(desc.min_filter))
.mipmap_mode(conv::map_mip_filter_mode(desc.mipmap_filter))
.address_mode_u(conv::map_address_mode(desc.address_modes[0]))
.address_mode_v(conv::map_address_mode(desc.address_modes[1]))
.address_mode_w(conv::map_address_mode(desc.address_modes[2]));
if let Some(fun) = desc.compare {
vk_info = vk_info
.compare_enable(true)
.compare_op(conv::map_comparison(fun));
}
if let Some(ref range) = desc.lod_clamp {
vk_info = vk_info.min_lod(range.start).max_lod(range.end);
}
if let Some(aniso) = desc.anisotropy_clamp {
if self
.shared
.downlevel_flags
.contains(wgt::DownlevelFlags::ANISOTROPIC_FILTERING)
{
vk_info = vk_info
.anisotropy_enable(true)
.max_anisotropy(aniso.get() as f32);
}
}
if let Some(color) = desc.border_color {
vk_info = vk_info.border_color(conv::map_border_color(color));
}
let raw = self.shared.raw.create_sampler(&vk_info, None)?;
Ok(super::Sampler { raw })
}
unsafe fn destroy_sampler(&self, sampler: super::Sampler) {
self.shared.raw.destroy_sampler(sampler.raw, None);
}
unsafe fn destroy_sampler(&self, sampler: Resource) {}
unsafe fn create_command_buffer(
&self,
@ -422,3 +482,29 @@ impl crate::Device<super::Api> for super::Device {
}
unsafe fn stop_capture(&self) {}
}
impl From<gpu_alloc::AllocationError> for crate::DeviceError {
fn from(error: gpu_alloc::AllocationError) -> Self {
use gpu_alloc::AllocationError as Ae;
match error {
Ae::OutOfDeviceMemory | Ae::OutOfHostMemory => Self::OutOfMemory,
_ => {
log::error!("memory allocation: {:?}", error);
Self::Lost
}
}
}
}
impl From<gpu_alloc::MapError> for crate::DeviceError {
fn from(error: gpu_alloc::MapError) -> Self {
use gpu_alloc::MapError as Me;
match error {
Me::OutOfDeviceMemory | Me::OutOfHostMemory => Self::OutOfMemory,
_ => {
log::error!("memory mapping: {:?}", error);
Self::Lost
}
}
}
}

View File

@ -20,6 +20,7 @@ impl super::Swapchain {
}
impl super::Instance {
#[allow(dead_code)]
fn create_surface_from_xlib(
&self,
dpy: *mut vk::Display,
@ -43,6 +44,7 @@ impl super::Instance {
self.create_surface_from_vk_surface_khr(surface)
}
#[allow(dead_code)]
fn create_surface_from_xcb(
&self,
connection: *mut vk::xcb_connection_t,
@ -66,6 +68,7 @@ impl super::Instance {
self.create_surface_from_vk_surface_khr(surface)
}
#[allow(dead_code)]
fn create_surface_from_wayland(
&self,
display: *mut c_void,
@ -88,6 +91,7 @@ impl super::Instance {
self.create_surface_from_vk_surface_khr(surface)
}
#[allow(dead_code)]
fn create_surface_android(&self, window: *const c_void) -> super::Surface {
let surface = {
let a_loader = khr::AndroidSurface::new(&self.entry, &self.shared.raw);
@ -101,6 +105,7 @@ impl super::Instance {
self.create_surface_from_vk_surface_khr(surface)
}
#[allow(dead_code)]
fn create_surface_from_hwnd(
&self,
hinstance: *mut c_void,
@ -512,6 +517,7 @@ impl crate::Surface<super::Api> for super::Surface {
texture: super::Texture {
raw: sc.images[index as usize],
block: None,
aspects: crate::FormatAspect::COLOR,
},
};
Ok(Some(crate::AcquiredSurfaceTexture {

View File

@ -33,8 +33,8 @@ impl crate::Api for Api {
type Buffer = Buffer;
type Texture = Texture;
type SurfaceTexture = SurfaceTexture;
type TextureView = Resource;
type Sampler = Resource;
type TextureView = TextureView;
type Sampler = Sampler;
type QuerySet = Resource;
type Fence = Resource;
@ -46,21 +46,11 @@ impl crate::Api for Api {
type ComputePipeline = Resource;
}
struct RenderDocEntry {
api: renderdoc_sys::RENDERDOC_API_1_4_1,
lib: libloading::Library,
}
unsafe impl Send for RenderDocEntry {}
unsafe impl Sync for RenderDocEntry {}
struct InstanceShared {
raw: ash::Instance,
flags: crate::InstanceFlag,
get_physical_device_properties: Option<vk::KhrGetPhysicalDeviceProperties2Fn>,
//TODO
//debug_messenger: Option<DebugMessenger>,
//render_doc_entry: Result<RenderDocEntry, String>,
}
pub struct Instance {
@ -72,7 +62,6 @@ pub struct Instance {
struct Swapchain {
raw: vk::SwapchainKHR,
functor: khr::Swapchain,
extent: vk::Extent3D,
device: Arc<DeviceShared>,
fence: vk::Fence,
//semaphore: vk::Semaphore,
@ -105,7 +94,6 @@ pub struct Adapter {
known_memory_flags: vk::MemoryPropertyFlags,
phd_capabilities: adapter::PhysicalDeviceCapabilities,
phd_features: adapter::PhysicalDeviceFeatures,
available_features: wgt::Features,
downlevel_flags: wgt::DownlevelFlags,
private_caps: PrivateCapabilities,
}
@ -118,16 +106,6 @@ enum ExtensionFn<T> {
Promoted,
}
impl<T> ExtensionFn<T> {
/// Expect `self` to be `Self::Extension` and return the inner value.
fn unwrap_extension(&self) -> &T {
match *self {
Self::Extension(ref t) => t,
Self::Promoted => panic!(),
}
}
}
struct DeviceExtensionFunctions {
draw_indirect_count: Option<ExtensionFn<khr::DrawIndirectCount>>,
}
@ -153,6 +131,7 @@ struct DeviceShared {
features: wgt::Features,
vendor_id: u32,
timestamp_period: f32,
downlevel_flags: wgt::DownlevelFlags,
private_caps: PrivateCapabilities,
}
@ -172,13 +151,24 @@ pub struct Queue {
#[derive(Debug)]
pub struct Buffer {
raw: vk::Buffer,
block: gpu_alloc::MemoryBlock<vk::DeviceMemory>,
block: Mutex<gpu_alloc::MemoryBlock<vk::DeviceMemory>>,
}
#[derive(Debug)]
pub struct Texture {
raw: vk::Image,
block: Option<gpu_alloc::MemoryBlock<vk::DeviceMemory>>,
aspects: crate::FormatAspect,
}
#[derive(Debug)]
pub struct TextureView {
raw: vk::ImageView,
}
#[derive(Debug)]
pub struct Sampler {
raw: vk::Sampler,
}
impl crate::Queue<Api> for Queue {