use arrayvec::ArrayVec; use smallvec::SmallVec; use wgt::{
math::align_to, DeviceLostReason, TextureFormat, TextureSampleType, TextureViewDimension,
};
/// 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: OnceLock<Weak<Queue>>, pub(crate) zero_buffer: ManuallyDrop<Box<dyn hal::DynBuffer>>, /// The `label` from the descriptor used to create the resource.
label: String,
/// 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::AtomicFenceValue,
/// 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`]: Device::active_submission_index pub(crate) last_successful_submission_index: hal::AtomicFenceValue,
// NOTE: if both are needed, the `snatchable_lock` must be consistently acquired before the // `fence` lock to avoid deadlocks. pub(crate) fence: RwLock<ManuallyDrop<Box<dyn hal::DynFence>>>, pub(crate) snatchable_lock: SnatchLock,
/// 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, pub(crate) instance_flags: wgt::InstanceFlags, pub(crate) deferred_destroy: Mutex<Vec<DeferredDestroy>>, pub(crate) usage_scopes: UsageScopePool, pub(crate) last_acceleration_structure_build_command_index: AtomicU64, #[cfg(feature = "indirect-validation")] pub(crate) indirect_validation: Option<crate::indirect_validation::IndirectValidation>, // needs to be dropped last #[cfg(feature = "trace")] pub(crate) trace: Mutex<Option<trace::Trace>>,
}
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.fence anymore after this point. let fence = unsafe { ManuallyDrop::take(&mutself.fence.write()) }; #[cfg(feature = "indirect-validation")] iflet Some(indirect_validation) = self.indirect_validation.take() {
indirect_validation.dispose(self.raw.as_ref());
} unsafe { self.raw.destroy_buffer(zero_buffer); self.raw.destroy_fence(fence);
}
}
}
/// 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) { 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());
/// Check this device for completed commands. /// /// The `maintain` argument tells how the maintenance function should behave, either /// blocking or just polling the current state of the gpu. /// /// Return a pair `(closures, queue_empty)`, where: /// /// - `closures` is a list of actions to take: mapping buffers, notifying the user /// /// - `queue_empty` is a boolean indicating whether there are more queue /// submissions still in flight. (We have to take the locks needed to /// produce this information for other reasons, so we might as well just /// return it to our callers.) pub(crate) fn maintain<'this>(
&'this self,
fence: crate::lock::RwLockReadGuard<ManuallyDrop<Box<dyn hal::DynFence>>>,
maintain: wgt::Maintain<crate::SubmissionIndex>,
snatch_guard: SnatchGuard,
) -> Result<(UserClosures, bool), WaitIdleError> {
profiling::scope!("Device::maintain");
// Determine which submission index `maintain` represents. let submission_index = match maintain {
wgt::Maintain::WaitForSubmissionIndex(submission_index) => { let last_successful_submission_index = self
.last_successful_submission_index
.load(Ordering::Acquire);
// If necessary, wait for that submission to complete. if maintain.is_wait() {
log::trace!("Device::maintain: waiting for submission index {submission_index}"); unsafe { self.raw()
.wait(fence.as_ref(), submission_index, CLEANUP_WAIT_MS)
}
.map_err(|e| self.handle_hal_error(e))?;
}
// 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 device_lost_invocations = SmallVec::new(); letmut should_release_gpu_resource = false; if !self.is_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() {
device_lost_invocations.push(DeviceLostInvocation {
closure: device_lost_closure,
reason: DeviceLostReason::Destroyed,
message: String::new(),
});
}
}
// Don't hold the locks while calling release_gpu_resources.
drop(fence);
drop(snatch_guard);
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 |= hal::BufferUses::STORAGE_READ_ONLY | hal::BufferUses::STORAGE_READ_WRITE;
}
if desc.mapped_at_creation { if desc.size % wgt::COPY_BUFFER_ALIGNMENT != 0 { return Err(resource::CreateBufferError::UnalignedSize);
} if !desc.usage.contains(wgt::BufferUsages::MAP_WRITE) { // we are going to be copying into it, internally
usage |= hal::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 |= hal::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 buffer_use = if !desc.mapped_at_creation {
hal::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: std::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,
};
hal::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);
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,
));
} // Renderable textures can only be 2D if desc.usage.contains(wgt::TextureUsages::RENDER_ATTACHMENT) { return Err(CreateTextureError::InvalidDimensionUsages(
wgt::TextureUsages::RENDER_ATTACHMENT,
desc.dimension,
));
}
}
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,
));
}
}
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))?;
} else { return Err(CreateTextureError::InvalidCompressedDimension(
desc.dimension,
desc.format,
));
}
}
}
{ let (width_multiple, height_multiple) = desc.format.size_multiple_requirement();
// check if multisampled texture is seen as anything but 2D if texture.desc.sample_count > 1 && resolved_dimension != TextureViewDimension::D2 { return Err(
resource::CreateTextureViewError::InvalidMultisampledTextureViewDimension(
resolved_dimension,
),
);
}
// 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 mask_copy = !(hal::TextureUses::COPY_SRC | hal::TextureUses::COPY_DST); let mask_dimension = match resolved_dimension {
TextureViewDimension::Cube | TextureViewDimension::CubeArray => {
hal::TextureUses::RESOURCE
}
TextureViewDimension::D3 => {
hal::TextureUses::RESOURCE
| hal::TextureUses::STORAGE_READ_ONLY
| hal::TextureUses::STORAGE_WRITE_ONLY
| hal::TextureUses::STORAGE_READ_WRITE
}
_ => hal::TextureUses::all(),
}; let mask_mip_level = if resolved_mip_level_count == 1 {
hal::TextureUses::all()
} else {
hal::TextureUses::RESOURCE
};
texture.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
let encoder = self
.command_allocator
.acquire_encoder(self.raw(), queue.raw())
.map_err(|e| self.handle_hal_error(e))?;
let command_buffer = command::CommandBuffer::new(encoder, self, label);
let command_buffer = Arc::new(command_buffer);
Ok(command_buffer)
}
/// 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)| pipeline::LateSizedBufferGroup {
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(),
})
.collect()
}
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,
};
let raw = unsafe { self.raw().create_bind_group_layout(&hal_desc) }
.map_err(|e| self.handle_hal_error(e))?;
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(binding_model::CreateBindGroupLayoutError::TooManyBindings)?;
// 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);
// `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);
// This function expects the provided bind group layout to be resolved // (not passing a duplicate) beforehand. pub(crate) fn create_bind_group( self: &Arc<Self>,
desc: binding_model::ResolvedBindGroupDescriptor,
) -> Result<Arc<BindGroup>, binding_model::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 used_buffer_ranges = Vec::new(); letmut used_texture_ranges = 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(); 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 used_buffer_ranges,
&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 used_buffer_ranges,
&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_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));
}
} let hal_desc = hal::BindGroupDescriptor {
label: desc.label.to_hal(self.instance_flags),
layout: layout.raw(),
entries: &hal_entries,
buffers: &hal_buffers,
samplers: &hal_samplers,
textures: &hal_textures,
acceleration_structures: &hal_tlas_s,
}; let raw = unsafe { self.raw().create_bind_group(&hal_desc) }
.map_err(|e| self.handle_hal_error(e))?;
// collect in the order of BGL iteration let late_buffer_binding_sizes = layout
.entries
.indices()
.flat_map(|binding| late_buffer_binding_sizes.get(&binding).cloned())
.collect();
let weak_ref = Arc::downgrade(&bind_group); for range in &bind_group.used_texture_ranges { letmut bind_groups = range.texture.bind_groups.lock();
bind_groups.push(weak_ref.clone());
} for range in &bind_group.used_buffer_ranges { letmut bind_groups = range.buffer.bind_groups.lock();
bind_groups.push(weak_ref.clone());
}
// 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() { // `bind_group_layouts` might contain duplicate entries, so we need to ignore the result. let _ = bgl
.exclusive_pipeline
.set(binding_model::ExclusivePipeline::Compute(Arc::downgrade(
&pipeline,
)));
}
}
Ok(pipeline)
}
pub(crate) fn create_render_pipeline( self: &Arc<Self>,
desc: pipeline::ResolvedRenderPipelineDescriptor,
) -> 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_STRIDE_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: { if cs.write_mask | wgt::ColorWrites::all() != wgt::ColorWrites::all() { break'error Some(pipeline::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(pipeline::ColorStateError::FormatNotRenderable(
cs.format,
));
} let blendable = format_features.flags.contains(Tfff::BLENDABLE); let filterable = format_features.flags.contains(Tfff::FILTERABLE); let adapter_specific = self
.features
.contains(wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES); // according to WebGPU specifications the texture needs to be // [`TextureFormatFeatureFlags::FILTERABLE`] if blending is set - use // [`Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES`] to elude // this limitation if cs.blend.is_some() && (!blendable || (!filterable && !adapter_specific)) { break'error Some(pipeline::ColorStateError::FormatNotBlendable(
cs.format,
));
} if !hal::FormatAspects::from(cs.format).contains(hal::FormatAspects::COLOR) { break'error Some(pipeline::ColorStateError::FormatNotColor(cs.format));
}
if ds.bias.clamp != 0.0 { self.require_downlevel_flags(wgt::DownlevelFlags::DEPTH_BIAS_CLAMP)?;
}
}
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,
};
if is_auto_layout { for bgl in pipeline.layout.bind_group_layouts.iter() { // `bind_group_layouts` might contain duplicate entries, so we need to ignore the result. let _ = bgl
.exclusive_pipeline
.set(binding_model::ExclusivePipeline::Render(Arc::downgrade(
&pipeline,
)));
}
}
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);
// 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.
pub(crate) 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) { let _ = buffer.destroy();
}
} for texture in trackers.textures.used_resources() { iflet Some(texture) = Weak::upgrade(texture) { let _ = 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.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.