use alloc::{
borrow::Cow,
boxed::Box,
string::{String, ToString as _},
sync::{Arc, Weak},
vec::Vec,
}; use core::{
fmt,
mem::{self, ManuallyDrop},
num::NonZeroU32,
sync::atomic::{AtomicBool, Ordering},
}; use hal::ShouldBeNonZeroExt;
use arrayvec::ArrayVec; use bitflags::Flags; use smallvec::SmallVec; use wgt::{
math::align_to, DeviceLostReason, TextureFormat, TextureSampleType, TextureSelector,
TextureViewDimension,
};
#[cfg(supports_64bit_atomics)] use core::sync::atomic::AtomicU64; #[cfg(not(supports_64bit_atomics))] use portable_atomic::AtomicU64;
pub(crate) struct CommandIndices { /// The index of the last command submission that was attempted. /// /// Note that `fence` may never be signalled with this value, if the command /// submission failed. If you need to wait for everything running on a /// `Queue` to complete, wait for [`last_successful_submission_index`]. /// /// [`last_successful_submission_index`]: Device::last_successful_submission_index pub(crate) active_submission_index: hal::FenceValue, pub(crate) next_acceleration_structure_build_command_index: u64,
}
/// Parameters provided to shaders via a uniform buffer of the type /// [`NagaExternalTextureParams`], describing an [`ExternalTexture`] resource /// binding. /// /// [`NagaExternalTextureParams`]: naga::SpecialTypes::external_texture_params /// [`ExternalTexture`]: binding_model::BindingResource::ExternalTexture #[repr(C)] #[derive(Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)] pubstruct ExternalTextureParams { /// 4x4 column-major matrix with which to convert sampled YCbCr values /// to RGBA. /// /// This is ignored when `num_planes` is 1. pub yuv_conversion_matrix: [f32; 16],
/// 3x3 column-major matrix to transform linear RGB values in the source /// color space to linear RGB values in the destination color space. In /// combination with [`Self::src_transfer_function`] and /// [`Self::dst_transfer_function`] this can be used to ensure that /// [`ImageSample`] and [`ImageLoad`] operations return values in the /// desired destination color space rather than the source color space of /// the underlying planes. /// /// Includes a padding element after each column. /// /// [`ImageSample`]: naga::ir::Expression::ImageSample /// [`ImageLoad`]: naga::ir::Expression::ImageLoad pub gamut_conversion_matrix: [f32; 12],
/// Transfer function for the source color space. The *inverse* of this /// will be applied to decode non-linear RGB to linear RGB in the source /// color space. pub src_transfer_function: wgt::ExternalTextureTransferFunction,
/// Transfer function for the destination color space. This will be applied /// to encode linear RGB to non-linear RGB in the destination color space. pub dst_transfer_function: wgt::ExternalTextureTransferFunction,
/// Transform to apply to [`ImageSample`] coordinates. /// /// This is a 3x2 column-major matrix representing an affine transform from /// normalized texture coordinates to the normalized coordinates that should /// be sampled from the external texture's underlying plane(s). /// /// This transform may scale, translate, flip, and rotate in 90-degree /// increments, but the result of transforming the rectangle (0,0)..(1,1) /// must be an axis-aligned rectangle that falls within the bounds of /// (0,0)..(1,1). /// /// [`ImageSample`]: naga::ir::Expression::ImageSample pub sample_transform: [f32; 6],
/// Transform to apply to [`ImageLoad`] coordinates. /// /// This is a 3x2 column-major matrix representing an affine transform from /// non-normalized texel coordinates to the non-normalized coordinates of /// the texel that should be loaded from the external texture's underlying /// plane 0. For planes 1 and 2, if present, plane 0's coordinates are /// scaled according to the textures' relative sizes. /// /// This transform may scale, translate, flip, and rotate in 90-degree /// increments, but the result of transforming the rectangle (0,0)..[`size`] /// must be an axis-aligned rectangle that falls within the bounds of /// (0,0)..[`size`]. /// /// [`ImageLoad`]: naga::ir::Expression::ImageLoad /// [`size`]: Self::size pub load_transform: [f32; 6],
/// Size of the external texture. /// /// This is the value that should be returned by size queries in shader /// code; it does not necessarily match the dimensions of the underlying /// texture(s). As a special case, if this is `[0, 0]`, the actual size of /// plane 0 should be used instead. /// /// This must be consistent with [`sample_transform`]: it should be the size /// in texels of the rectangle covered by the square (0,0)..(1,1) after /// [`sample_transform`] has been applied to it. /// /// [`sample_transform`]: Self::sample_transform pub size: [u32; 2],
/// Number of planes. 1 indicates a single RGBA plane. 2 indicates a Y /// plane and an interleaved CbCr plane. 3 indicates separate Y, Cb, and Cr /// planes. pub num_planes: u32, // Ensure the size of this struct matches the type generated by Naga. pub _padding: [u8; 4],
}
/// Structure describing a logical device. Some members are internally mutable, /// stored behind mutexes. pubstruct Device {
raw: Box<dyn hal::DynDevice>, pub(crate) adapter: Arc<Adapter>, pub(crate) queue: OnceCellOrLock<Weak<Queue>>, pub(crate) zero_buffer: ManuallyDrop<Box<dyn hal::DynBuffer>>, pub(crate) empty_bgl: ManuallyDrop<Box<dyn hal::DynBindGroupLayout>>, /// The `label` from the descriptor used to create the resource.
label: String,
/// The index of the last successful submission to this device's /// [`hal::Queue`]. /// /// Unlike [`active_submission_index`], which is incremented each time /// submission is attempted, this is updated only when submission succeeds, /// so waiting for this value won't hang waiting for work that was never /// submitted. /// /// [`active_submission_index`]: CommandIndices::active_submission_index pub(crate) last_successful_submission_index: hal::AtomicFenceValue,
/// Is this device valid? Valid is closely associated with "lose the device", /// which can be triggered by various methods, including at the end of device /// destroy, and by any GPU errors that cause us to no longer trust the state /// of the device. Ideally we would like to fold valid into the storage of /// the device itself (for example as an Error enum), but unfortunately we /// need to continue to be able to retrieve the device in poll_devices to /// determine if it can be dropped. If our internal accesses of devices were /// done through ref-counted references and external accesses checked for /// Error enums, we wouldn't need this. For now, we need it. All the call /// sites where we check it are areas that should be revisited if we start /// using ref-counted references for internal access. pub(crate) valid: AtomicBool,
/// Closure to be called on "lose the device". This is invoked directly by /// device.lose or by the UserCallbacks returned from maintain when the device /// has been destroyed and its queues are empty. pub(crate) device_lost_closure: Mutex<Option<DeviceLostClosure>>,
/// Stores the state of buffers and textures. pub(crate) trackers: Mutex<DeviceTracker>, pub(crate) tracker_indices: TrackerIndexAllocators, /// Pool of bind group layouts, allowing deduplication. pub(crate) bgl_pool: ResourcePool<bgl::EntryMap, BindGroupLayout>, pub(crate) alignments: hal::Alignments, pub(crate) limits: wgt::Limits, pub(crate) features: wgt::Features, pub(crate) downlevel: wgt::DownlevelCapabilities, /// Buffer uses listed here, are expected to be ordered by the underlying hardware. /// If a usage is ordered, then if the buffer state doesn't change between draw calls, /// there are no barriers needed for synchronization. /// See the implementations of [`hal::Adapter::get_ordered_buffer_usages`] for hardware specific info pub(crate) ordered_buffer_usages: wgt::BufferUses, /// Texture uses listed here, are expected to be ordered by the underlying hardware. /// If a usage is ordered, then if the buffer state doesn't change between draw calls, /// there are no barriers needed for synchronization. /// See the implementations of [`hal::Adapter::get_ordered_texture_usages`] for hardware specific info pub(crate) ordered_texture_usages: wgt::TextureUses, pub(crate) instance_flags: wgt::InstanceFlags, pub(crate) deferred_destroy: Mutex<Vec<DeferredDestroy>>, pub(crate) usage_scopes: UsageScopePool, pub(crate) indirect_validation: Option<crate::indirect_validation::IndirectValidation>, // Optional so that we can late-initialize this after the queue is created. pub(crate) timestamp_normalizer:
OnceCellOrLock<crate::timestamp_normalization::TimestampNormalizer>, /// Uniform buffer containing [`ExternalTextureParams`] with values such /// that a [`TextureView`] bound to a [`wgt::BindingType::ExternalTexture`] /// binding point will be rendered correctly. Intended to be used as the /// [`hal::ExternalTextureBinding::params`] field. pub(crate) default_external_texture_params_buffer: ManuallyDrop<Box<dyn hal::DynBuffer>>, // needs to be dropped last #[cfg(feature = "trace")] pub(crate) trace: Mutex<Option<Box<dyn trace::Trace + Send + Sync + 'static>>>,
}
impl Drop for Device { fn drop(&mutself) {
resource_log!("Drop {}", self.error_ident());
// SAFETY: We are in the Drop impl and we don't use self.zero_buffer anymore after this // point. let zero_buffer = unsafe { ManuallyDrop::take(&mutself.zero_buffer) }; // SAFETY: We are in the Drop impl and we don't use self.empty_bgl anymore after this point. let empty_bgl = unsafe { ManuallyDrop::take(&mutself.empty_bgl) }; // SAFETY: We are in the Drop impl and we don't use // self.default_external_texture_params_buffer anymore after this point. let default_external_texture_params_buffer = unsafe { ManuallyDrop::take(&mutself.default_external_texture_params_buffer) }; // SAFETY: We are in the Drop impl and we don't use self.fence anymore after this point. let fence = unsafe { ManuallyDrop::take(&mutself.fence) }; iflet Some(indirect_validation) = self.indirect_validation.take() {
indirect_validation.dispose(self.raw.as_ref());
} iflet Some(timestamp_normalizer) = self.timestamp_normalizer.take() {
timestamp_normalizer.dispose(self.raw.as_ref());
} unsafe { self.raw.destroy_buffer(zero_buffer); self.raw.destroy_bind_group_layout(empty_bgl); self.raw
.destroy_buffer(default_external_texture_params_buffer); self.raw.destroy_fence(fence);
}
}
}
impl Device { pub(crate) fn new(
raw_device: Box<dyn hal::DynDevice>,
adapter: &Arc<Adapter>,
desc: &DeviceDescriptor,
instance_flags: wgt::InstanceFlags,
) -> Result<Self, DeviceError> { #[cfg(not(feature = "trace"))] match &desc.trace {
wgt::Trace::Off => {}
_ => {
log::error!("wgpu-core feature 'trace' is not enabled");
}
}; #[cfg(feature = "trace")] let trace: Option<Box<dyn trace::Trace + Send + Sync + 'static>> = match &desc.trace {
wgt::Trace::Off => None,
wgt::Trace::Directory(dir) => match trace::DiskTrace::new(dir.clone()) {
Ok(mut trace) => {
trace::Trace::add(
&mut trace,
trace::Action::Init {
desc: wgt::DeviceDescriptor {
trace: wgt::Trace::Off,
..desc.clone()
},
backend: adapter.backend(),
},
);
Some(Box::new(trace))
}
Err(e) => {
log::error!("Unable to start a trace in '{dir:?}': {e}");
None
}
},
wgt::Trace::Memory => { letmut trace = trace::MemoryTrace::new();
trace::Trace::add(
&mut trace,
trace::Action::Init {
desc: wgt::DeviceDescriptor {
trace: wgt::Trace::Off,
..desc.clone()
},
backend: adapter.backend(),
},
);
Some(Box::new(trace))
} // The enum is non_exhaustive, so we must have a fallback arm (that should be // unreachable in practice).
t => {
log::error!("unimplemented wgpu_types::Trace variant {t:?}");
None
}
};
let ordered_buffer_usages = adapter.raw.adapter.get_ordered_buffer_usages(); let ordered_texture_usages = adapter.raw.adapter.get_ordered_texture_usages();
let fence = unsafe { raw_device.create_fence() }.map_err(DeviceError::from_hal)?;
let command_allocator = command::CommandAllocator::new();
let rt_uses = if desc
.required_features
.intersects(wgt::Features::EXPERIMENTAL_RAY_QUERY)
{
wgt::BufferUses::TOP_LEVEL_ACCELERATION_STRUCTURE_INPUT
} else {
wgt::BufferUses::empty()
};
// Create zeroed buffer used for texture clears (and raytracing if required). let zero_buffer = unsafe {
raw_device.create_buffer(&hal::BufferDescriptor {
label: hal_label(Some("(wgpu internal) zero init buffer"), instance_flags),
size: ZERO_BUFFER_SIZE,
usage: wgt::BufferUses::COPY_SRC | wgt::BufferUses::COPY_DST | rt_uses,
memory_flags: hal::MemoryFlags::empty(),
})
}
.map_err(DeviceError::from_hal)?;
// Cloned as we need them below anyway. let alignments = adapter.raw.capabilities.alignments.clone(); let downlevel = adapter.raw.capabilities.downlevel.clone(); let limits = &adapter.raw.capabilities.limits;
/// Stop tracing and return the trace object. /// /// This is mostly useful for in-memory traces. #[cfg(feature = "trace")] pubfn take_trace(&self) -> Option<Box<dyn trace::Trace + Send + Sync + 'static>> { self.trace.lock().take()
}
/// Checks that we are operating within the memory budget reported by the native APIs. /// /// If we are not, the device gets invalidated. /// /// The budget might fluctuate over the lifetime of the application, so it should be checked /// somewhat frequently. pubfn lose_if_oom(&self) { let _ = self
.raw()
.check_if_oom()
.map_err(|e| self.handle_hal_error(e));
}
/// Run some destroy operations that were deferred. /// /// Destroying the resources requires taking a write lock on the device's snatch lock, /// so a good reason for deferring resource destruction is when we don't know for sure /// how risky it is to take the lock (typically, it shouldn't be taken from the drop /// implementation of a reference-counted structure). /// The snatch lock must not be held while this function is called. pub(crate) fn deferred_resource_destruction(&self) { // Note that the deferred_destroy list may contain duplicate entries. let deferred_destroy = mem::take(&mut *self.deferred_destroy.lock()); for item in deferred_destroy { match item {
DeferredDestroy::TextureViews(views) => { for view in views { let Some(view) = view.upgrade() else { continue;
}; let Some(raw_view) = view.raw.snatch(&mutself.snatchable_lock.write()) else { continue;
};
resource_log!("Destroy raw {}", view.error_ident());
unsafe { self.raw().destroy_texture_view(raw_view);
}
}
}
DeferredDestroy::BindGroups(bind_groups) => { for bind_group in bind_groups { let Some(bind_group) = bind_group.upgrade() else { continue;
}; let Some(raw_bind_group) =
bind_group.raw.snatch(&mutself.snatchable_lock.write()) else { continue;
};
resource_log!("Destroy raw {}", bind_group.error_ident());
pubfn poll(
&self,
poll_type: wgt::PollType<crate::SubmissionIndex>,
) -> Result<wgt::PollStatus, WaitIdleError> { let (user_closures, result) = self.poll_and_return_closures(poll_type);
user_closures.fire();
result
}
/// Poll the device, returning any `UserClosures` that need to be executed. /// /// The caller must invoke the `UserClosures` even if this function returns /// an error. This is an internal helper, used by `Device::poll` and /// `Global::poll_all_devices`, so that `poll_all_devices` can invoke /// closures once after all devices have been polled. pub(crate) fn poll_and_return_closures(
&self,
poll_type: wgt::PollType<crate::SubmissionIndex>,
) -> (UserClosures, Result<wgt::PollStatus, WaitIdleError>) { let snatch_guard = self.snatchable_lock.read(); let maintain_result = self.maintain(poll_type, snatch_guard);
self.lose_if_oom();
// Some deferred destroys are scheduled in maintain so run this right after // to avoid holding on to them until the next device poll. self.deferred_resource_destruction();
maintain_result
}
/// Check the current status of the GPU and process any submissions that have /// finished. /// /// The `poll_type` argument tells if this function should wait for a particular /// submission index to complete, or if it should just poll the current status. /// /// This will process _all_ completed submissions, even if the caller only asked /// us to poll to a given submission index. /// /// Return a pair `(closures, result)`, where: /// /// - `closures` is a list of callbacks that need to be invoked informing the user /// about various things occurring. These happen and should be handled even if /// this function returns an error, hence they are outside of the result. /// /// - `results` is a boolean indicating the result of the wait operation, including /// if there was a timeout or a validation error. pub(crate) fn maintain<'this>(
&'this self,
poll_type: wgt::PollType<crate::SubmissionIndex>,
snatch_guard: SnatchGuard,
) -> (UserClosures, Result<wgt::PollStatus, WaitIdleError>) {
profiling::scope!("Device::maintain");
letmut user_closures = UserClosures::default();
// If a wait was requested, determine which submission index to wait for. let wait_submission_index = match poll_type {
wgt::PollType::Wait {
submission_index: Some(submission_index),
..
} => { let last_successful_submission_index = self
.last_successful_submission_index
.load(Ordering::Acquire);
if submission_index > last_successful_submission_index { let result = Err(WaitIdleError::WrongSubmissionIndex(
submission_index,
last_successful_submission_index,
));
// Wait for the submission index if requested. iflet Some(target_submission_index) = wait_submission_index {
log::trace!("Device::maintain: waiting for submission index {target_submission_index}");
let wait_timeout = match poll_type {
wgt::PollType::Wait { timeout, .. } => timeout,
wgt::PollType::Poll => unreachable!( "`wait_submission_index` index for poll type `Poll` should be None"
),
};
let wait_result = unsafe { self.raw()
.wait(self.fence.as_ref(), target_submission_index, wait_timeout)
};
// This error match is only about `DeviceErrors`. At this stage we do not care if // the wait succeeded or not, and the `Ok(bool)`` variant is ignored. iflet Err(e) = wait_result { let hal_error: WaitIdleError = self.handle_hal_error(e).into(); return (user_closures, Err(hal_error));
}
}
// Get the currently finished submission index. This may be higher than the requested // wait, or it may be less than the requested wait if the wait failed. let fence_value_result = unsafe { self.raw().get_fence_value(self.fence.as_ref()) }; let current_finished_submission = match fence_value_result {
Ok(fence_value) => fence_value,
Err(e) => { let hal_error: WaitIdleError = self.handle_hal_error(e).into(); return (user_closures, Err(hal_error));
}
};
// Prevent new commands from being submitted as we want to act on `queue_empty`. let command_indices = self.command_indices.read(); // Check that the device is valid. This is combined with queue empty to decide whether // to destroy all resources. Queue.submit blocks on command indices being writable // and rejects if invalid so if the device in now invalid, and all submissions are // finished, there will be no more submissions. let device_valid = self.is_valid();
drop(command_indices);
// Maintain all finished submissions on the queue, updating the relevant user closures and // collecting if the queue is empty. // // We don't use the result of the wait here, as we want to progress forward as far as // possible and the wait could have been for submissions that finished long ago. letmut queue_empty = false; iflet Some(queue) = self.get_queue() { let queue_result = queue.maintain(current_finished_submission, &snatch_guard);
(
user_closures.submissions,
user_closures.mappings,
user_closures.blas_compact_ready,
queue_empty,
) = queue_result; // DEADLOCK PREVENTION: We must drop `snatch_guard` before `queue` goes out of scope. // // `Queue::drop` acquires the snatch guard. If we still hold it when `queue` is dropped // at the end of this block, we would deadlock. This can happen in the following // scenario: // // - Thread A calls `Device::maintain` while Thread B holds the last strong ref to the // queue. // - Thread A calls `self.get_queue()`, obtaining a new strong ref, and enters this // branch. // - Thread B drops its strong ref, making Thread A's ref the last one. // - When `queue` goes out of scope here, `Queue::drop` runs and tries to acquire the // snatch guard — but Thread A (this thread) still holds it, causing a deadlock.
drop(snatch_guard);
} else {
drop(snatch_guard);
};
// Based on the queue empty status, and the current finished submission index, determine // the result of the poll. let result = if queue_empty { iflet Some(wait_submission_index) = wait_submission_index { // Assert to ensure that if we received a queue empty status, the fence shows the // correct value. This is defensive, as this should never be hit.
assert!(
current_finished_submission >= wait_submission_index,
concat!( "If the queue is empty, the current submission index ", "({}) should be at least the wait submission index ({})",
),
current_finished_submission,
wait_submission_index,
);
}
Ok(wgt::PollStatus::QueueEmpty)
} elseiflet Some(wait_submission_index) = wait_submission_index { // This is theoretically possible to succeed more than checking on the poll result // as submissions could have finished in the time between the timeout resolving, // the thread getting scheduled again, and us checking the fence value. if current_finished_submission >= wait_submission_index {
Ok(wgt::PollStatus::WaitSucceeded)
} else {
Err(WaitIdleError::Timeout)
}
} else {
Ok(wgt::PollStatus::Poll)
};
// Detect if we have been destroyed and now need to lose the device. // // If we are invalid (set at start of destroy) and our queue is empty, // and we have a DeviceLostClosure, return the closure to be called by // our caller. This will complete the steps for both destroy and for // "lose the device". letmut should_release_gpu_resource = false; if !device_valid && queue_empty { // We can release gpu resources associated with this device (but not // while holding the life_tracker lock).
should_release_gpu_resource = true;
// If we have a DeviceLostClosure, build an invocation with the // reason DeviceLostReason::Destroyed and no message. iflet Some(device_lost_closure) = self.device_lost_closure.lock().take() {
user_closures
.device_lost_invocations
.push(DeviceLostInvocation {
closure: device_lost_closure,
reason: DeviceLostReason::Destroyed,
message: String::new(),
});
}
}
if should_release_gpu_resource { self.release_gpu_resources();
}
if desc.usage.contains(wgt::BufferUsages::INDIRECT) { self.require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?; // We are going to be reading from it, internally; // when validating the content of the buffer
usage |= wgt::BufferUses::STORAGE_READ_ONLY | wgt::BufferUses::STORAGE_READ_WRITE;
}
if desc.usage.contains(wgt::BufferUsages::QUERY_RESOLVE) {
usage |= TIMESTAMP_NORMALIZATION_BUFFER_USES;
}
if desc.mapped_at_creation { if !desc.size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err(resource::CreateBufferError::UnalignedSize);
} if !desc.usage.contains(wgt::BufferUsages::MAP_WRITE) { // we are going to be copying into it, internally
usage |= wgt::BufferUses::COPY_DST;
}
} else { // We are required to zero out (initialize) all memory. This is done // on demand using clear_buffer which requires write transfer usage!
usage |= wgt::BufferUses::COPY_DST;
}
let actual_size = if desc.size == 0 {
wgt::COPY_BUFFER_ALIGNMENT
} elseif desc.usage.contains(wgt::BufferUsages::VERTEX) { // Bumping the size by 1 so that we can bind an empty range at the // end of the buffer.
desc.size + 1
} else {
desc.size
}; let clear_remainder = actual_size % wgt::COPY_BUFFER_ALIGNMENT; let aligned_size = if clear_remainder != 0 {
actual_size + wgt::COPY_BUFFER_ALIGNMENT - clear_remainder
} else {
actual_size
};
let timestamp_normalization_bind_group = Snatchable::new(unsafe { // SAFETY: The size passed here must not overflow the buffer. self.timestamp_normalizer
.get()
.unwrap()
.create_normalization_bind_group( self,
&*buffer,
desc.label.as_deref(),
wgt::BufferSize::new(hal_desc.size).unwrap(),
desc.usage,
)
}?);
let indirect_validation_bind_groups = self.create_indirect_validation_bind_groups(buffer.as_ref(), desc.size, desc.usage)?;
let buffer_use = if !desc.mapped_at_creation {
wgt::BufferUses::empty()
} elseif desc.usage.contains(wgt::BufferUsages::MAP_WRITE) { // buffer is mappable, so we are just doing that at start let map_size = buffer.size; let mapping = if map_size == 0 {
hal::BufferMapping {
ptr: core::ptr::NonNull::dangling(),
is_coherent: true,
}
} else { let snatch_guard: SnatchGuard = self.snatchable_lock.read();
map_buffer(&buffer, 0, map_size, HostMap::Write, &snatch_guard)?
};
*buffer.map_state.lock() = resource::BufferMapState::Active {
mapping,
range: 0..map_size,
host: HostMap::Write,
};
wgt::BufferUses::MAP_WRITE
} else { letmut staging_buffer =
StagingBuffer::new(self, wgt::BufferSize::new(aligned_size).unwrap())?;
// Zero initialize memory and then mark the buffer as initialized // (it's guaranteed that this is the case by the time the buffer is usable)
staging_buffer.write_zeros();
buffer.initialization_status.write().drain(0..aligned_size);
/// # Safety /// /// - `hal_buffer` must have been created on this device. /// - `hal_buffer` must have been created respecting `desc` (in particular, the size). /// - `hal_buffer` must be initialized. /// - `hal_buffer` must not have zero size. pub(crate) unsafefn create_buffer_from_hal( self: &Arc<Self>,
hal_buffer: Box<dyn hal::DynBuffer>,
desc: &resource::BufferDescriptor,
) -> (Fallible<Buffer>, Option<resource::CreateBufferError>) { let timestamp_normalization_bind_group = unsafe { matchself
.timestamp_normalizer
.get()
.unwrap()
.create_normalization_bind_group( self,
&*hal_buffer,
desc.label.as_deref(),
wgt::BufferSize::new(desc.size).unwrap(),
desc.usage,
) {
Ok(bg) => Snatchable::new(bg),
Err(e) => { return (
Fallible::Invalid(Arc::new(desc.label.to_string())),
Some(e.into()),
)
}
}
};
if desc.dimension != wgt::TextureDimension::D2 { // Depth textures can only be 2D if desc.format.is_depth_stencil_format() { return Err(CreateTextureError::InvalidDepthDimension(
desc.dimension,
desc.format,
));
}
}
if desc.dimension != wgt::TextureDimension::D2
&& desc.dimension != wgt::TextureDimension::D3
{ // Compressed textures can only be 2D or 3D if desc.format.is_compressed() { return Err(CreateTextureError::InvalidCompressedDimension(
desc.dimension,
desc.format,
));
}
// Renderable textures can only be 2D or 3D if desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { return Err(CreateTextureError::InvalidDimensionUsages(
wgt::TextureUsages::RENDER_ATTACHMENT,
desc.dimension,
));
}
}
if desc.format.is_compressed() { let (block_width, block_height) = desc.format.block_dimensions();
if desc.dimension == wgt::TextureDimension::D3 { // Only BCn formats with Sliced 3D feature can be used for 3D textures if desc.format.is_bcn() { self.require_features(wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D)
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;
} elseif desc.format.is_astc() { self.require_features(wgt::Features::TEXTURE_COMPRESSION_ASTC_SLICED_3D)
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;
} else { return Err(CreateTextureError::InvalidCompressedDimension(
desc.dimension,
desc.format,
));
}
}
}
let mips = desc.mip_level_count; let max_levels_allowed = desc.size.max_mips(desc.dimension).min(hal::MAX_MIP_LEVELS); if mips == 0 || mips > max_levels_allowed { return Err(CreateTextureError::InvalidMipLevelCount {
requested: mips,
maximum: max_levels_allowed,
});
}
{ let (mut width_multiple, mut height_multiple) = desc.format.size_multiple_requirement();
if desc.format.is_multi_planar_format() { // TODO(https://github.com/gfx-rs/wgpu/issues/8491): fix // `mip_level_size` calculation for these formats and relax this // restriction.
width_multiple <<= desc.mip_level_count.saturating_sub(1);
height_multiple <<= desc.mip_level_count.saturating_sub(1);
}
let missing_allowed_usages = match desc.format.planes() {
Some(planes) => { letmut planes_usages = wgt::TextureUsages::all(); for plane in0..planes { let aspect = wgt::TextureAspect::from_plane(plane).unwrap(); let format = desc.format.aspect_specific_format(aspect).unwrap(); let format_features = self
.describe_format_features(format)
.map_err(|error| CreateTextureError::MissingFeatures(desc.format, error))?;
// check if multisampled texture is seen as anything but 2D if texture.desc.sample_count > 1 && resolved_dimension != TextureViewDimension::D2 { // Multisample is allowed on 2D arrays, only if explicitly supported let multisample_array_exception = resolved_dimension == TextureViewDimension::D2Array
&& self.features.contains(wgt::Features::MULTISAMPLE_ARRAY);
// check if the dimension is compatible with the texture if texture.desc.dimension != resolved_dimension.compatible_texture_dimension() { return Err(
resource::CreateTextureViewError::InvalidTextureViewDimension {
view: resolved_dimension,
texture: texture.desc.dimension,
},
);
}
// filter the usages based on the other criteria let usage = { let resolved_hal_usage = conv::map_texture_usage(
resolved_usage,
resolved_format.into(),
format_features.flags,
); let mask_copy = !(wgt::TextureUses::COPY_SRC | wgt::TextureUses::COPY_DST); let mask_dimension = match resolved_dimension {
TextureViewDimension::Cube | TextureViewDimension::CubeArray => {
wgt::TextureUses::RESOURCE
}
TextureViewDimension::D3 => {
wgt::TextureUses::RESOURCE
| wgt::TextureUses::STORAGE_READ_ONLY
| wgt::TextureUses::STORAGE_WRITE_ONLY
| wgt::TextureUses::STORAGE_READ_WRITE
}
_ => wgt::TextureUses::all(),
}; let mask_mip_level = if resolved_mip_level_count == 1 {
wgt::TextureUses::all()
} else {
wgt::TextureUses::RESOURCE
};
resolved_hal_usage & mask_copy & mask_dimension & mask_mip_level
};
// use the combined depth-stencil format for the view let format = if resolved_format.is_depth_stencil_component(texture.desc.format) {
texture.desc.format
} else {
resolved_format
};
let anisotropy_clamp = ifself
.downlevel
.flags
.contains(wgt::DownlevelFlags::ANISOTROPIC_FILTERING)
{ // Clamp anisotropy clamp to [1, 16] per the wgpu-hal interface
desc.anisotropy_clamp.min(16)
} else { // If it isn't supported, set this unconditionally to 1 1
};
//TODO: check for wgt::DownlevelFlags::COMPARISON_SAMPLERS
/// Not a public API. For use by `player` only. #[allow(unused_unsafe)] #[doc(hidden)] pubunsafefn create_shader_module_passthrough<'a>( self: &Arc<Self>,
descriptor: &pipeline::ShaderModuleDescriptorPassthrough<'a>,
) -> Result<Arc<pipeline::ShaderModule>, pipeline::CreateShaderModuleError> { self.check_is_valid()?; self.require_features(wgt::Features::PASSTHROUGH_SHADERS)?;
// Mainly important for GLSL or SPIR-V or DXIL, which each take exactly 1 entry point. if (descriptor.dxil.is_some() || descriptor.glsl.is_some())
&& descriptor.entry_points.len() != 1
{ return Err(pipeline::CreateShaderModuleError::IncorrectPassthroughEntryPointCount);
}
let encoder = self
.command_allocator
.acquire_encoder(self.raw(), queue.raw())
.map_err(|e| self.handle_hal_error(e))?;
let cmd_enc = command::CommandEncoder::new(encoder, self, label);
let cmd_enc = Arc::new(cmd_enc);
Ok(cmd_enc)
}
/// Generate information about late-validated buffer bindings for pipelines. //TODO: should this be combined with `get_introspection_bind_group_layouts` in some way? fn make_late_sized_buffer_groups(
shader_binding_sizes: &FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
layout: &binding_model::PipelineLayout,
) -> ArrayVec<pipeline::LateSizedBufferGroup, { hal::MAX_BIND_GROUPS }> { // Given the shader-required binding sizes and the pipeline layout, // return the filtered list of them in the layout order, // removing those with given `min_binding_size`.
layout
.bind_group_layouts
.iter()
.enumerate()
.map(|(group_index, bgl)| { let Some(bgl) = bgl else { return pipeline::LateSizedBufferGroup::default();
};
let shader_sizes = bgl
.entries
.values()
.filter_map(|entry| match entry.ty {
wgt::BindingType::Buffer {
min_binding_size: None,
..
} => { let rb = naga::ResourceBinding {
group: group_index as u32,
binding: entry.binding,
}; let shader_size =
shader_binding_sizes.get(&rb).map_or(0, |nz| nz.get());
Some(shader_size)
}
_ => None,
})
.collect();
pipeline::LateSizedBufferGroup { shader_sizes }
})
.collect()
}
(
Some(wgt::Features::TEXTURE_BINDING_ARRAY),
WritableStorage::No,
)
}
Bt::StorageTexture {
access,
view_dimension,
format,
} => { use wgt::{StorageTextureAccess as Access, TextureFormatFeatureFlags as Flags};
let bgl_flags = conv::bind_group_layout_flags(self.features);
let hal_bindings = entry_map.values().copied().collect::<Vec<_>>(); let hal_desc = hal::BindGroupLayoutDescriptor {
label: label.to_hal(self.instance_flags),
flags: bgl_flags,
entries: &hal_bindings,
};
letmut count_validator = binding_model::BindingTypeMaxCountValidator::default(); for entry in entry_map.values() {
count_validator.add_binding(entry);
} // If a single bind group layout violates limits, the pipeline layout is // definitely going to violate limits too, lets catch it now.
count_validator
.validate(&self.limits)
.map_err(CreateBindGroupLayoutError::TooManyBindings)?;
// Validate that binding arrays don't conflict with dynamic offsets.
count_validator.validate_binding_arrays()?;
let raw = unsafe { self.raw().create_bind_group_layout(&hal_desc) }
.map_err(|e| self.handle_hal_error(e))?;
// This was checked against the device's alignment requirements above, // which should always be a multiple of `COPY_BUFFER_ALIGNMENT`.
assert_eq!(bb.offset % wgt::COPY_BUFFER_ALIGNMENT, 0);
let init_range = if dynamic { // We don't know what part of the buffer will be bound, so require that it // is fully initialized. 0..buffer.size
} else { // `wgpu_hal` only restricts shader access to bound buffer regions with // a certain resolution. For the sake of lazy initialization, round up // the size of the bound range to reflect how much of the buffer is // actually going to be visible to the shader. let bounds_check_alignment = binding_model::buffer_binding_type_bounds_check_alignment(
&self.alignments,
binding_ty,
); let visible_size = align_to(bind_size, bounds_check_alignment);
let planes = (0..3)
.map(|i| { // We always need 3 bindings. If we have fewer than 3 planes // just bind plane 0 multiple times. The shader will only // sample from valid planes anyway. let plane = external_texture
.planes
.get(i)
.unwrap_or(&external_texture.planes[0]); let internal_use = wgt::TextureUses::RESOURCE;
used.views.insert_single(plane.clone(), internal_use); let view = plane.try_raw(snatch_guard)?;
Ok(hal::TextureBinding {
view,
usage: internal_use,
})
}) // We can remove this intermediate Vec by using // array::try_from_fn() above, once it stabilizes.
.collect::<Result<Vec<_>, Error>>()?; let planes = planes.try_into().unwrap();
used.buffers
.insert_single(external_texture.params.clone(), wgt::BufferUses::UNIFORM); let params = external_texture.params.binding(0, None, snatch_guard)?.0;
// This function expects the provided bind group layout to be resolved // (not passing a duplicate) beforehand. pubfn create_bind_group( self: &Arc<Self>,
desc: binding_model::ResolvedBindGroupDescriptor,
) -> Result<Arc<BindGroup>, CreateBindGroupError> { usecrate::binding_model::{CreateBindGroupError as Error, ResolvedBindingResource as Br};
{ // Check that the number of entries in the descriptor matches // the number of entries in the layout. let actual = desc.entries.len(); let expected = layout.entries.len(); if actual != expected { return Err(Error::BindingsNumMismatch { expected, actual });
}
}
// TODO: arrayvec/smallvec, or re-use allocations // Record binding info for dynamic offset validation letmut dynamic_binding_info = Vec::new(); // Map of binding -> shader reflected size //Note: we can't collect into a vector right away because // it needs to be in BGL iteration order, not BG entry order. letmut late_buffer_binding_sizes = FastHashMap::default(); // fill out the descriptors letmut used = BindGroupStates::new();
letmut buffer_init_actions = Vec::new(); letmut texture_init_actions = Vec::new(); letmut hal_entries = Vec::with_capacity(desc.entries.len()); letmut hal_buffers = Vec::new(); letmut hal_samplers = Vec::new(); letmut hal_textures = Vec::new(); letmut hal_tlas_s = Vec::new(); letmut hal_external_textures = Vec::new(); let snatch_guard = self.snatchable_lock.read(); for entry in desc.entries.iter() { let binding = entry.binding; // Find the corresponding declaration in the layout let decl = layout
.entries
.get(binding)
.ok_or(Error::MissingBindingDeclaration(binding))?; let (res_index, count) = match entry.resource {
Br::Buffer(ref bb) => { let bb = self.create_buffer_binding(
bb,
binding,
decl,
&mut buffer_init_actions,
&mut dynamic_binding_info,
&mut late_buffer_binding_sizes,
&mut used,
&snatch_guard,
)?;
let res_index = hal_buffers.len();
hal_buffers.push(bb);
(res_index, 1)
}
Br::BufferArray(ref bindings_array) => { let num_bindings = bindings_array.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?;
let res_index = hal_buffers.len(); for bb in bindings_array.iter() { let bb = self.create_buffer_binding(
bb,
binding,
decl,
&mut buffer_init_actions,
&mut dynamic_binding_info,
&mut late_buffer_binding_sizes,
&mut used,
&snatch_guard,
)?;
hal_buffers.push(bb);
}
(res_index, num_bindings)
}
Br::Sampler(ref sampler) => { let sampler = self.create_sampler_binding(&mut used, binding, decl, sampler)?;
let res_index = hal_samplers.len();
hal_samplers.push(sampler);
(res_index, 1)
}
Br::SamplerArray(ref samplers) => { let num_bindings = samplers.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?;
let res_index = hal_samplers.len(); for sampler in samplers.iter() { let sampler = self.create_sampler_binding(&mut used, binding, decl, sampler)?;
hal_samplers.push(sampler);
}
(res_index, num_bindings)
}
Br::TextureView(ref view) => match decl.ty {
wgt::BindingType::ExternalTexture => { let et = self.create_external_texture_binding_from_view(
binding,
decl,
view,
&mut used,
&snatch_guard,
)?; let res_index = hal_external_textures.len();
hal_external_textures.push(et);
(res_index, 1)
}
_ => { let tb = self.create_texture_binding(
binding,
decl,
view,
&mut used,
&mut texture_init_actions,
&snatch_guard,
)?; let res_index = hal_textures.len();
hal_textures.push(tb);
(res_index, 1)
}
},
Br::TextureViewArray(ref views) => { let num_bindings = views.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?;
let res_index = hal_textures.len(); for view in views.iter() { let tb = self.create_texture_binding(
binding,
decl,
view,
&mut used,
&mut texture_init_actions,
&snatch_guard,
)?;
hal_textures.push(tb);
}
(res_index, num_bindings)
}
Br::AccelerationStructure(ref tlas) => { let tlas = self.create_tlas_binding(&mut used, binding, decl, tlas, &snatch_guard)?; let res_index = hal_tlas_s.len();
hal_tlas_s.push(tlas);
(res_index, 1)
}
Br::AccelerationStructureArray(ref tlas_array) => { // Feature validation for TLAS binding arrays happens at bind group layout // creation time (mirroring other binding-array resource types). By the time we // get here, `decl.count` has already been validated against device features. let num_bindings = tlas_array.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?;
let res_index = hal_tlas_s.len(); for tlas in tlas_array.iter() { let tlas = self.create_tlas_binding(
&mut used,
binding,
decl,
tlas,
&snatch_guard,
)?;
hal_tlas_s.push(tlas);
}
(res_index, num_bindings)
}
Br::ExternalTexture(ref et) => { let et = self.create_external_texture_binding(
binding,
decl,
et,
&mut used,
&snatch_guard,
)?; let res_index = hal_external_textures.len();
hal_external_textures.push(et);
(res_index, 1)
}
};
hal_entries.push(hal::BindGroupEntry {
binding,
resource_index: res_index as u32,
count: count as u32,
});
}
used.optimize();
hal_entries.sort_by_key(|entry| entry.binding); for (a, b) in hal_entries.iter().zip(hal_entries.iter().skip(1)) { if a.binding == b.binding { return Err(Error::DuplicateBinding(a.binding));
}
}
// collect in the order of BGL iteration let late_buffer_binding_infos = layout
.entries
.indices()
.flat_map(|binding| { let size = late_buffer_binding_sizes.get(&binding).cloned()?;
Some(BindGroupLateBufferBindingInfo {
binding_index: binding,
size,
})
})
.collect();
fn create_derived_pipeline_layout( self: &Arc<Self>, mut derived_group_layouts: Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>,
immediate_size: u32,
) -> Result<Arc<binding_model::PipelineLayout>, pipeline::ImplicitLayoutError> { while derived_group_layouts
l(
./// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
zlib.data.reset()
}
let java.lang.StringIndexOutOfBoundsException: Index 14 out of bounds for length 5
/
.(
+ java.lang.StringIndexOutOfBoundsException: Range [47, 46) out of bounds for length 51
/// This structure implements a [`Read`] interface. When read from, it reads return Ok(/// recover the underlying reader.
}/// # use flate2::Compression; /// // Here &[u8] implements BufRead
java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 1 // stream, using the given `decompression` settings.
data =Decompress:newtrue
fn reset(&mutself, r: R) -> R {
create_bind_group_layout_internal
.
.).clone(),
java.lang.StringIndexOutOfBoundsException: Range [4, 1) out of bounds for length 66
) {
Ok(bgl) => {
&,&]>:usize
.get_mut().flush)
}
Err>()
}
})
.collect:<Result<Vec<_>, >>);
letjava.lang.StringIndexOutOfBoundsException: Range [25, 23) out of bounds for length 75
l =read_to_end(m )unwrap)java.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70
:java.lang.StringIndexOutOfBoundsException: Index 63 out of bounds for length 63
immediate_size,
;
let layout = self.create_pipeline_layout_impl(&layout_desc, true)?;
Ok(layout)
}
// Get the pipeline layout from the desc if it is provided. let pipeline_layout = match desc.layout {
Some(pipeline_layout) => {
pipeline_layout.same_device(self)?;
Some(pipeline_layout)
}
None => None,
};
if is_auto_layout { for bgl in pipeline.layout.bind_group_layouts.iter() { let Some(bgl) = bgl else { continue;
};
// `bind_group_layouts` might contain duplicate entries, so we need to ignore the // result. let _ = bgl.exclusive_pipeline.set((&pipeline).into());
}
}
Ok(pipeline)
}
pubfn create_render_pipeline( self: &Arc<Self>,
desc: pipeline::ResolvedGeneralRenderPipelineDescriptor,
) -> Result<Arc<pipeline::RenderPipeline>, pipeline::CreateRenderPipelineError> { use wgt::TextureFormatFeatureFlags as Tfff;
if vb_state.array_stride > self.limits.max_vertex_buffer_array_stride as u64 { return Err(pipeline::CreateRenderPipelineError::VertexStrideTooLarge {
index: i as u32,
given: vb_state.array_stride as u32,
limit: self.limits.max_vertex_buffer_array_stride,
});
} if vb_state.array_stride % wgt::VERTEX_ALIGNMENT != 0 { return Err(pipeline::CreateRenderPipelineError::UnalignedVertexStride {
index: i as u32,
stride: vb_state.array_stride,
});
}
let max_stride = if vb_state.array_stride == 0 { self.limits.max_vertex_buffer_array_stride as u64
} else {
vb_state.array_stride
}; letmut last_stride = 0; for attribute in vb_state.attributes.iter() { let attribute_stride = attribute.offset + attribute.format.size(); if attribute_stride > max_stride { return Err(
pipeline::CreateRenderPipelineError::VertexAttributeStrideTooLarge {
location: attribute.shader_location,
given: attribute_stride as u32,
limit: max_stride as u32,
},
);
}
for (i, cs) in color_targets.iter().enumerate() { iflet Some(cs) = cs.as_ref() {
target_specified = true; let error = 'error: { // This is expected to be the operative check for illegal write mask // values (larger than 15), because WebGPU requires that it be validated // on the device timeline. if cs.write_mask.contains_unknown_bits() { break'error Some(ColorStateError::InvalidWriteMask(cs.write_mask));
}
let format_features = self.describe_format_features(cs.format)?; if !format_features
.allowed_usages
.contains(wgt::TextureUsages::RENDER_ATTACHMENT)
{ break'error Some(ColorStateError::FormatNotRenderable(cs.format));
} if cs.blend.is_some() && !format_features.flags.contains(Tfff::BLENDABLE) { break'error Some(ColorStateError::FormatNotBlendable(cs.format));
} if !hal::FormatAspects::from(cs.format).contains(hal::FormatAspects::COLOR) { break'error Some(ColorStateError::FormatNotColor(cs.format));
}
iflet Some(blend_mode) = cs.blend { for component in [&blend_mode.color, &blend_mode.alpha] { for factor in [component.src_factor, component.dst_factor] { if factor.ref_second_blend_source() { self.require_features(wgt::Features::DUAL_SOURCE_BLENDING)?; if i == 0 {
dual_source_blending = true;
} else { break'error Some(
ColorStateError::BlendFactorOnUnsupportedTarget {
factor,
target: i as u32,
},
);
}
}
if [wgt::BlendOperation::Min, wgt::BlendOperation::Max]
.contains(&component.operation)
&& factor != wgt::BlendFactor::One
{ break'error Some(ColorStateError::InvalidMinMaxBlendFactor {
factor,
target: i as u32,
});
}
}
}
}
iflet Some(ds) = depth_stencil_state { // See <https://gpuweb.github.io/gpuweb/#abstract-opdef-validating-gpudepthstencilstate>.
target_specified = true; let error = 'error: { if !ds.format.is_depth_stencil_format() { // This error case is not redundant with the aspect check below when // neither depth nor stencil is enabled at all. break'error Some(pipeline::DepthStencilStateError::FormatNotDepthOrStencil(
ds.format,
));
}
let format_features = self.describe_format_features(ds.format)?; if !format_features
.allowed_usages
.contains(wgt::TextureUsages::RENDER_ATTACHMENT)
{ break'error Some(pipeline::DepthStencilStateError::FormatNotRenderable(
ds.format,
));
}
let aspect = hal::FormatAspects::from(ds.format); if aspect.contains(hal::FormatAspects::DEPTH) {
has_depth_attachment = true;
} elseif ds.is_depth_enabled() { break'error Some(pipeline::DepthStencilStateError::FormatNotDepth(ds.format));
} if has_depth_attachment { let Some(depth_write_enabled) = ds.depth_write_enabled else { break'error Some(
pipeline::DepthStencilStateError::MissingDepthWriteEnabled(ds.format),
);
};
if !target_specified { return Err(pipeline::CreateRenderPipelineError::NoTargetSpecified);
}
let is_auto_layout = desc.layout.is_none();
// Get the pipeline layout from the desc if it is provided. let pipeline_layout = match desc.layout {
Some(pipeline_layout) => {
pipeline_layout.same_device(self)?;
Some(pipeline_layout)
}
None => None,
};
let fragment_entry_point_name; let fragment_stage = match desc.fragment {
Some(ref fragment_state) => { let stage = validation::ShaderStageForValidation::Fragment {
dual_source_blending,
has_depth_attachment,
}; let stage_bit = stage.to_wgt_bit();
let shader_module = &fragment_state.stage.module;
shader_module.same_device(self)?;
let stage_err = |error| pipeline::CreateRenderPipelineError::Stage {
stage: stage_bit,
error,
};
// Multiview is only supported if the feature is enabled iflet Some(mv_mask) = desc.multiview_mask { self.require_features(wgt::Features::MULTIVIEW)?; if !(mv_mask.get() + 1).is_power_of_two() { self.require_features(wgt::Features::SELECTIVE_MULTIVIEW)?;
}
}
if !self
.downlevel
.flags
.contains(wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED)
{ for (binding, size) in shader_binding_sizes.iter() { if size.get() % 16 != 0 { return Err(pipeline::CreateRenderPipelineError::UnalignedShader {
binding: binding.binding,
group: binding.group,
size: size.get(),
});
}
}
}
let late_sized_buffer_groups =
Device::make_late_sized_buffer_groups(&shader_binding_sizes, &pipeline_layout);
let cache = match desc.cache {
Some(cache) => {
cache.same_device(self)?;
Some(cache)
}
None => None,
};
if is_auto_layout { for bgl in pipeline.layout.bind_group_layouts.iter() { let Some(bgl) = bgl else { continue;
};
// `bind_group_layouts` might contain duplicate entries, so we need to ignore the // result. let _ = bgl.exclusive_pipeline.set((&pipeline).into());
}
}
Ok(pipeline)
}
/// # Safety /// The `data` field on `desc` must have previously been returned from /// [`crate::global::Global::pipeline_cache_get_data`] pubunsafefn create_pipeline_cache( self: &Arc<Self>,
desc: &pipeline::PipelineCacheDescriptor,
) -> Result<Arc<pipeline::PipelineCache>, pipeline::CreatePipelineCacheError> { usecrate::pipeline_cache;
self.check_is_valid()?;
self.require_features(wgt::Features::PIPELINE_CACHE)?; let data = iflet Some((data, validation_key)) = desc
.data
.as_ref()
.zip(self.raw().pipeline_cache_validation_key())
{ let data = pipeline_cache::validate_pipeline_cache(
data,
&self.adapter.raw.info,
validation_key,
); match data {
Ok(data) => Some(data),
Err(e) if e.was_avoidable() || !desc.fallback => return Err(e.into()), // If the error was unavoidable and we are asked to fallback, do so
Err(_) => None,
}
} else {
None
}; let cache_desc = hal::PipelineCacheDescriptor {
data,
label: desc.label.to_hal(self.instance_flags),
}; let raw = matchunsafe { self.raw().create_pipeline_cache(&cache_desc) } {
Ok(raw) => raw,
Err(e) => match e {
hal::PipelineCacheError::Device(e) => return Err(self.handle_hal_error(e).into()),
},
}; let cache = pipeline::PipelineCache {
device: self.clone(),
label: desc.label.to_string(), // This would be none in the error condition, which we don't implement yet
raw: ManuallyDrop::new(raw),
};
let cache = Arc::new(cache);
Ok(cache)
}
fn get_texture_format_features(&self, format: TextureFormat) -> wgt::TextureFormatFeatures { // Variant of adapter.get_texture_format_features that takes device features into account use wgt::TextureFormatFeatureFlags as tfsc; letmut format_features = self.adapter.get_texture_format_features(format); if (format == TextureFormat::R32Float
|| format == TextureFormat::Rg32Float
|| format == TextureFormat::Rgba32Float)
&& !self.features.contains(wgt::Features::FLOAT32_FILTERABLE)
{
format_features.flags.set(tfsc::FILTERABLE, false);
}
format_features
}
let using_device_features = self
.features
.contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES); // If we're running downlevel, we need to manually ask the backend what // we can use as we can't trust WebGPU. let downlevel = !self
.downlevel
.flags
.contains(wgt::DownlevelFlags::WEBGPU_TEXTURE_FORMAT_SUPPORT);
for &fallback in fallbacks { if caps.composite_alpha_modes.contains(&fallback) { break'alpha fallback;
}
}
unreachable!( "Fallback system failed to choose alpha mode. This is a bug. \
AlphaMode: {:?}, Options: {:?}",
config.composite_alpha_mode, &caps.composite_alpha_modes
);
};
log::debug!("configuring surface with {config:?}");
let error = 'error: { // User callbacks must not be called while we are holding locks. let user_callbacks;
{ iflet Err(e) = self.check_is_valid() { break'error e.into();
}
let caps = match surface.get_capabilities(&self.adapter) {
Ok(caps) => caps,
Err(_) => break'error E::UnsupportedQueueFamily,
};
letmut hal_view_formats = Vec::new(); for format in config.view_formats.iter() { if *format == config.format { continue;
} if !caps.formats.contains(&config.format) { break'error E::UnsupportedFormat {
requested: config.format,
available: caps.formats,
};
} if config.format.remove_srgb_suffix() != format.remove_srgb_suffix() { break'error E::InvalidViewFormat(*format, config.format);
}
hal_view_formats.push(*format);
}
// Wait for all work to finish before configuring the surface. let snatch_guard = self.snatchable_lock.read();
let maintain_result;
(user_callbacks, maintain_result) = self.maintain(wgt::PollType::wait_indefinitely(), snatch_guard);
match maintain_result { // We're happy
Ok(wgt::PollStatus::QueueEmpty) => {}
Ok(wgt::PollStatus::WaitSucceeded) => { // After the wait, the queue should be empty. It can only be non-empty // if another thread is submitting at the same time. break'error E::GpuWaitTimeout;
}
Ok(wgt::PollStatus::Poll) => {
unreachable!("Cannot get a Poll result from a Wait action.")
}
Err(WaitIdleError::Timeout) if cfg!(target_arch = "wasm32") => { // On wasm, you cannot actually successfully wait for the surface. // However WebGL does not actually require you do this, so ignoring // the failure is totally fine. See // https://github.com/gfx-rs/wgpu/issues/7363
}
Err(e) => { break'error e.into();
}
}
// All textures must be destroyed before the surface can be re-configured. iflet Some(present) = surface.presentation.lock().take() { if present.acquired_texture.is_some() { break'error E::PreviousOutputExists;
}
}
// TODO: Texture views may still be alive that point to the texture. // this will allow the user to render to the surface texture, long after // it has been removed. // // https://github.com/gfx-rs/wgpu/issues/4105
// Mark the device explicitly as invalid. This is checked in various // places to prevent new work from being submitted. self.valid.store(false, Ordering::Release);
// 2) Complete any outstanding mapAsync() steps. // 3) Complete any outstanding onSubmittedWorkDone() steps.
// These parts are passively accomplished by setting valid to false, // since that will prevent any new work from being added to the queues. // Future calls to poll_devices will continue to check the work queues // until they are cleared, and then drop the device.
}
fn release_gpu_resources(&self) { // This is called when the device is lost, which makes every associated // resource invalid and unusable. This is an opportunity to release all of // the underlying gpu resources, even though the objects remain visible to // the user agent. We purge this memory naturally when resources have been // moved into the appropriate buckets, so this function just needs to // initiate movement into those buckets, and it can do that by calling // "destroy" on all the resources we know about.
// During these iterations, we discard all errors. We don't care! let trackers = self.trackers.lock(); for buffer in trackers.buffers.used_resources() { iflet Some(buffer) = Weak::upgrade(buffer) {
buffer.destroy();
}
} for texture in trackers.textures.used_resources() { iflet Some(texture) = Weak::upgrade(texture) {
texture.destroy();
}
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.188Bemerkung:
(vorverarbeitet am 2026-08-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.