implsuper::DeviceShared { /// Set the name of `object` to `name`. /// /// If `name` contains an interior null byte, then the name set will be truncated to that byte. /// /// # Safety /// /// This method inherits the safety contract from [`vkSetDebugUtilsObjectName`]. In particular: /// /// - `object` must be a valid handle for one of the following: /// - An instance-level object from the same instance as this device. /// - A physical-device-level object that descends from the same physical device as this /// device. /// - A device-level object that descends from this device. /// - `object` must be externally synchronized—only the calling thread should access it during /// this call. /// /// [`vkSetDebugUtilsObjectName`]: https://registry.khronos.org/vulkan/specs/latest/man/html/vkSetDebugUtilsObjectNameEXT.html pub(super) unsafefn set_object_name(&self, object: impl vk::Handle, name: &str) { let Some(extension) = self.extension_fns.debug_utils.as_ref() else { return;
};
// Keep variables outside the if-else block to ensure they do not // go out of scope while we hold a pointer to them letmut buffer: [u8; 64] = [0u8; 64]; let buffer_vec: Vec<u8>;
// Append a null terminator to the string let name_bytes = if name.len() < buffer.len() { // Common case, string is very small. Allocate a copy on the stack.
buffer[..name.len()].copy_from_slice(name.as_bytes()); // Add null terminator
buffer[name.len()] = 0;
&buffer[..name.len() + 1]
} else { // Less common case, the string is large. // This requires a heap allocation.
buffer_vec = name
.as_bytes()
.iter()
.cloned()
.chain(core::iter::once(0))
.collect();
&buffer_vec
};
let name = CStr::from_bytes_until_nul(name_bytes).expect("We have added a null byte");
// On Vulkan 1.1 or later, this is an alias for core functionality
multiview_info = vk::RenderPassMultiviewCreateInfoKHR::default()
.view_masks(&mask)
.correlation_masks(&mask);
vk_info = vk_info.push_next(&mut multiview_info);
}
let raw = unsafe { self.raw
.create_render_pass(&vk_info, None)
.map_err(super::map_host_device_oom_err)?
};
implsuper::Device { /// # Safety /// /// - `vk_image` must be created respecting `desc` /// - If `drop_callback` is [`None`], wgpu-hal will take ownership of `vk_image`. If /// `drop_callback` is [`Some`], `vk_image` must be valid until the callback is called. /// - If the `ImageCreateFlags` does not contain `MUTABLE_FORMAT`, the `view_formats` of `desc` must be empty. /// - If `memory` is not [`super::TextureMemory::External`], wgpu-hal will take ownership of the /// memory (which is presumed to back `vk_image`). Otherwise, the memory must remain valid until /// `drop_callback` is called. pubunsafefn texture_from_raw(
&self,
vk_image: vk::Image,
desc: &crate::TextureDescriptor,
drop_callback: Option<crate::DropCallback>,
memory: super::TextureMemory,
) -> super::Texture { let identity = self.shared.texture_identity_factory.next(); let drop_guard = crate::DropGuard::from_option(drop_callback);
/// # Safety /// /// - Vulkan (with VK_KHR_external_memory_win32) /// - The `d3d11_shared_handle` must be valid and respecting `desc` /// - `VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT` flag is used because we need to hold a reference to the handle #[cfg(windows)] pubunsafefn texture_from_d3d11_shared_handle(
&self,
d3d11_shared_handle: windows::Win32::Foundation::HANDLE,
desc: &crate::TextureDescriptor,
) -> Result<super::Texture, crate::DeviceError> { if !self
.shared
.features
.contains(wgt::Features::VULKAN_EXTERNAL_MEMORY_WIN32)
{
log::error!("Vulkan driver does not support VK_KHR_external_memory_win32"); return Err(crate::DeviceError::Unexpected);
}
letmut import_memory_info = vk::ImportMemoryWin32HandleInfoKHR::default()
.handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE)
.handle(d3d11_shared_handle.0as _); // TODO: We should use `push_next` instead, but currently ash does not provide this method for the `ImportMemoryWin32HandleInfoKHR` type. #[allow(clippy::unnecessary_mut_passed)]
{
import_memory_info.p_next = <*const _>::cast(&mut dedicated_allocate_info);
}
let mem_type_index = self
.find_memory_type_index(
image.requirements.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)
.ok_or(crate::DeviceError::Unexpected)?;
let memory_allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(image.requirements.size)
.memory_type_index(mem_type_index as _)
.push_next(&mut import_memory_info); let memory = unsafe { self.shared.raw.allocate_memory(&memory_allocate_info, None) }
.map_err(super::map_host_device_oom_err)?;
/// Import a DMA-buf as a texture. Currently only supports single-plane DMA-bufs. /// /// # Safety /// /// - Requires `VULKAN_EXTERNAL_MEMORY_DMA_BUF` feature (implies VK_EXT_external_memory_dma_buf /// and VK_EXT_image_drm_format_modifier) /// - The `fd` must be a valid DMA-buf file descriptor matching `desc` /// - On success, Vulkan takes ownership of the file descriptor. On failure, /// the file descriptor is closed. /// - The `drm_modifier`, `stride`, and `offset` must match the DMA-buf layout #[cfg(unix)] pubunsafefn texture_from_dmabuf_fd(
&self,
fd: std::os::unix::io::OwnedFd,
desc: &crate::TextureDescriptor,
drm_modifier: u64,
stride: u64,
offset: u64,
) -> Result<super::Texture, crate::DeviceError> { use std::os::unix::io::IntoRawFd;
if !self
.shared
.features
.contains(wgt::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF)
{
log::error!( "Vulkan driver does not support VK_EXT_external_memory_dma_buf \
or VK_EXT_image_drm_format_modifier"
); return Err(crate::DeviceError::Unexpected);
}
let external_memory_fd_fn = self
.shared
.extension_fns
.external_memory_fd
.as_ref()
.ok_or_else(|| {
log::error!("VK_KHR_external_memory_fd extension not loaded"); crate::DeviceError::Unexpected
})?;
// Convert to raw fd. We must close it ourselves if any operation below // fails, since Vulkan only takes ownership on successful vkAllocateMemory. let fd_raw = fd.into_raw_fd();
let result = self.import_dmabuf_memory(
external_memory_fd_fn,
fd_raw,
image.raw,
&image.requirements,
);
match result {
Ok(memory) => Ok(unsafe { self.texture_from_raw(
image.raw,
desc,
None, super::TextureMemory::Dedicated(memory),
)
}),
Err(e) => { // Clean up the VkImage on failure. unsafe { self.shared.raw.destroy_image(image.raw, None) };
Err(e)
}
}
}
/// Import DMA-buf memory and bind it to the image. /// /// On failure, the raw fd is closed (if not yet consumed by Vulkan) and the /// caller is responsible for destroying the VkImage. #[cfg(unix)] fn import_dmabuf_memory(
&self,
external_memory_fd_fn: &ash::khr::external_memory_fd::Device,
fd_raw: i32,
image: vk::Image,
requirements: &vk::MemoryRequirements,
) -> Result<vk::DeviceMemory, crate::DeviceError> { letmut fd_props = vk::MemoryFdPropertiesKHR::default(); unsafe {
external_memory_fd_fn.get_memory_fd_properties(
vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT,
fd_raw,
&mut fd_props,
)
}
.map_err(|e| { unsafe { libc::close(fd_raw) }; super::map_host_device_oom_err(e)
})?;
let memory_allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(mem_type_index as _)
.push_next(&mut import_memory_info)
.push_next(&mut dedicated_allocate_info);
// vkAllocateMemory takes ownership of the fd on success. // On failure, the fd is NOT consumed and we must close it. let memory = unsafe { self.shared.raw.allocate_memory(&memory_allocate_info, None) }
.map_err(|e| { unsafe { libc::close(fd_raw) }; super::map_host_device_oom_err(e)
})?;
// From this point, the fd is consumed. Only VkDeviceMemory needs cleanup on error. unsafe { self.shared.raw.bind_image_memory(image, memory, 0) }.map_err(|e| { unsafe { self.shared.raw.free_memory(memory, None) }; super::map_host_device_oom_err(e)
})?;
/// Returns the queue family index of the device's internal queue. /// /// This is useful for constructing memory barriers needed for queue family ownership transfer when /// external memory is involved (from/to `VK_QUEUE_FAMILY_EXTERNAL_KHR` and `VK_QUEUE_FAMILY_FOREIGN_EXT` /// for example). pubfn queue_family_index(&self) -> u32 { self.shared.family_index
}
let memory_properties = memory_properties.memory_properties;
for i in0..memory_properties.memory_type_count { let memory_type = memory_properties.memory_types[i as usize]; let flags = memory_type.property_flags;
if flags.intersects(
vk::MemoryPropertyFlags::LAZILY_ALLOCATED | vk::MemoryPropertyFlags::PROTECTED,
) { continue; // not used by gpu-alloc
}
if flags.contains(vk::MemoryPropertyFlags::HOST_VISIBLE) {
host_visible_heaps[memory_type.heap_index as usize] = true;
}
if flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) {
device_local_heaps[memory_type.heap_index as usize] = true;
}
}
let heaps = if needs_host_access {
host_visible_heaps
} else {
device_local_heaps
};
// NOTE: We might end up checking multiple heaps since gpu-alloc doesn't have a way // for us to query the heap the resource will end up on. But this is unlikely, // there is usually only one heap on integrated GPUs and two on dedicated GPUs.
for (i, check) in heaps.iter().enumerate() { if !check { continue;
}
let heap_usage = memory_budget_properties.heap_usage[i]; let heap_budget = memory_budget_properties.heap_budget[i];
if heap_usage + size >= heap_budget / 100 * threshold as u64 { return Err(crate::DeviceError::OutOfMemory);
}
}
Ok(())
}
}
implcrate::Device forsuper::Device { type A = super::Api;
let name = desc.label.unwrap_or("Unlabeled buffer");
if desc
.usage
.contains(wgt::BufferUses::ACCELERATION_STRUCTURE_SCRATCH)
{ // There is no way to specify this usage to Vulkan so we must make sure the alignment requirement is large enough.
requirements.alignment = requirements
.alignment
.max(self.shared.private_caps.scratch_buffer_alignment as u64);
}
if desc.anisotropy_clamp != 1 { // We only enable anisotropy if it is supported, and wgpu-hal interface guarantees // the clamp is in the range [1, 16] which is always supported if anisotropy is.
create_info = create_info
.anisotropy_enable(true)
.max_anisotropy(desc.anisotropy_clamp as f32);
}
let raw = sampler_cache_guard.create_sampler(&self.shared.raw, create_info)?;
// Note: Cached samplers will just continually overwrite the label // // https://github.com/gfx-rs/wgpu/issues/6867 iflet Some(label) = desc.label { // SAFETY: we are holding a lock on the sampler cache, // so we can only be setting the name from one thread. unsafe { self.shared.set_object_name(raw, label) };
}
unsafefn create_bind_group_layout(
&self,
desc: &crate::BindGroupLayoutDescriptor,
) -> Result<super::BindGroupLayout, crate::DeviceError> { // Iterate through the entries and accumulate our Vulkan // DescriptorSetLayoutBindings and DescriptorBindingFlags, as well as // our binding map and our descriptor counts. // Note: not bothering with on stack arrays here as it's low frequency letmut vk_bindings = Vec::new(); letmut binding_flags = Vec::new(); letmut binding_map = Vec::new(); letmut next_binding = 0; letmut contains_binding_arrays = false; letmut desc_count = DescriptorCounts::default(); for entry in desc.entries { if entry.count.is_some() {
contains_binding_arrays = true;
}
let partially_bound = desc
.flags
.contains(crate::BindGroupLayoutFlags::PARTIALLY_BOUND); letmut flags = vk::DescriptorBindingFlags::empty(); if partially_bound && entry.count.is_some() {
flags |= vk::DescriptorBindingFlags::PARTIALLY_BOUND;
} if entry.count.is_some() {
flags |= vk::DescriptorBindingFlags::UPDATE_AFTER_BIND;
}
unsafefn create_pipeline_layout(
&self,
desc: &crate::PipelineLayoutDescriptor<super::BindGroupLayout>,
) -> Result<super::PipelineLayout, crate::DeviceError> { //Note: not bothering with on stack array here as it's low frequency let vk_set_layouts = desc
.bind_group_layouts
.iter()
.map(|bgl| match bgl {
Some(bgl) => bgl.raw,
None => { // `VUID-VkPipelineLayoutCreateInfo-pSetLayouts-parameter` // says `VK_NULL_HANDLE` is allowed but // `VUID-VkPipelineLayoutCreateInfo-graphicsPipelineLibrary-06753` // says it's not, unless the `graphicsPipelineLibrary` // feature is enabled. // // We use an empty descriptor set layout to work around this. self.shared.empty_descriptor_set_layout
}
})
.collect::<Vec<_>>(); let vk_immediates_ranges: Option<vk::PushConstantRange> = if desc.immediate_size != 0 {
Some(vk::PushConstantRange {
stage_flags: vk::ShaderStageFlags::ALL,
offset: 0,
size: desc.immediate_size,
})
} else {
None
};
let vk_info = vk::PipelineLayoutCreateInfo::default()
.flags(vk::PipelineLayoutCreateFlags::empty())
.set_layouts(&vk_set_layouts)
.push_constant_ranges(vk_immediates_ranges.as_slice());
let raw = {
profiling::scope!("vkCreatePipelineLayout"); unsafe { self.shared
.raw
.create_pipeline_layout(&vk_info, None)
.map_err(super::map_host_device_oom_err)?
}
};
letmut binding_map = BTreeMap::new(); for (group, layout) in desc.bind_group_layouts.iter().enumerate() { let Some(layout) = layout else { continue;
};
for &(binding, binding_info) in &layout.binding_map {
binding_map.insert(
naga::ResourceBinding {
group: group as u32,
binding,
},
naga::back::spv::BindingInfo {
descriptor_set: group as u32,
binding: binding_info.binding,
binding_array_size: binding_info.binding_array_size.map(NonZeroU32::get),
},
);
}
}
/// Helper for splitting off and initializing a given number of elements on a pre-allocated /// stack, based on items returned from an [`ExactSizeIterator`]. Typically created from a /// [`MaybeUninit`] slice (see [`Vec::spare_capacity_mut()`]). /// The updated [`ExtensionStack`] of remaining uninitialized elements is returned, safely /// representing that the initialized and remaining elements are two independent mutable /// borrows. struct ExtendStack<'a, T> {
remainder: &'a mut [MaybeUninit<T>],
}
for (value, to_init) in iter.into_iter().zip(to_init.iter_mut()) {
to_init.write(value);
}
// we can't use the safe (yet unstable) MaybeUninit::write_slice() here because of having an iterator to write
let init = { // SAFETY: The loop above has initialized exactly as many items as to_init is // long, so it is safe to cast away the MaybeUninit<T> wrapper into T.
// Additional safety docs from unstable slice_assume_init_mut // SAFETY: similar to safety notes for `slice_get_ref`, but we have a // mutable reference which is also guaranteed to be valid for writes. unsafe { mem::transmute::<&mut [MaybeUninit<T>], &mut [T]>(to_init) }
};
(Self { remainder }, init)
}
}
letmut writes = Vec::with_capacity(desc.entries.len()); letmut buffer_infos = Vec::with_capacity(desc.buffers.len()); letmut buffer_infos = ExtendStack::from_vec_capacity(&mut buffer_infos); letmut image_infos = Vec::with_capacity(desc.samplers.len() + desc.textures.len()); letmut image_infos = ExtendStack::from_vec_capacity(&mut image_infos); // TODO: This length could be reduced to just the number of top-level acceleration // structure bindings, where multiple consecutive TLAS bindings that are set via // one `WriteDescriptorSet` count towards one "info" struct, not the total number of // acceleration structure bindings to write: letmut acceleration_structure_infos =
Vec::with_capacity(desc.acceleration_structures.len()); letmut acceleration_structure_infos =
ExtendStack::from_vec_capacity(&mut acceleration_structure_infos); letmut raw_acceleration_structures =
Vec::with_capacity(desc.acceleration_structures.len()); letmut raw_acceleration_structures =
ExtendStack::from_vec_capacity(&mut raw_acceleration_structures);
let layout_and_entry_iter = desc.entries.iter().map(|entry| { let layout = desc
.layout
.entries
.iter()
.find(|layout_entry| layout_entry.binding == entry.binding)
.expect("internal error: no layout entry found with binding slot");
(layout, entry)
}); letmut next_binding = 0; for (layout, entry) in layout_and_entry_iter { let write = vk::WriteDescriptorSet::default().dst_set(set.raw());
match layout.ty {
wgt::BindingType::Sampler(_) => { let start = entry.resource_index; let end = start + entry.count; let local_image_infos;
(image_infos, local_image_infos) =
image_infos.extend(desc.samplers[start as usize..end as usize].iter().map(
|sampler| vk::DescriptorImageInfo::default().sampler(sampler.raw),
));
writes.push(
write
.dst_binding(next_binding)
.descriptor_type(conv::map_binding_type(layout.ty))
.image_info(local_image_infos),
);
next_binding += 1;
}
wgt::BindingType::Texture { .. } | wgt::BindingType::StorageTexture { .. } => { let start = entry.resource_index; let end = start + entry.count; let local_image_infos;
(image_infos, local_image_infos) =
image_infos.extend(desc.textures[start as usize..end as usize].iter().map(
|binding| { let layout =
conv::derive_image_layout(binding.usage, binding.view.format);
vk::DescriptorImageInfo::default()
.image_view(binding.view.raw)
.image_layout(layout)
},
));
writes.push(
write
.dst_binding(next_binding)
.descriptor_type(conv::map_binding_type(layout.ty))
.image_info(local_image_infos),
);
next_binding += 1;
}
wgt::BindingType::Buffer { .. } => { let start = entry.resource_index; let end = start + entry.count; let local_buffer_infos;
(buffer_infos, local_buffer_infos) =
buffer_infos.extend(desc.buffers[start as usize..end as usize].iter().map(
|binding| {
vk::DescriptorBufferInfo::default()
.buffer(binding.buffer.raw)
.offset(binding.offset)
.range(
binding.size.map_or(vk::WHOLE_SIZE, wgt::BufferSize::get),
)
},
));
writes.push(
write
.dst_binding(next_binding)
.descriptor_type(conv::map_binding_type(layout.ty))
.buffer_info(local_buffer_infos),
);
next_binding += 1;
}
wgt::BindingType::AccelerationStructure { .. } => { let start = entry.resource_index; let end = start + entry.count;
let local_raw_acceleration_structures;
(
raw_acceleration_structures,
local_raw_acceleration_structures,
) = raw_acceleration_structures.extend(
desc.acceleration_structures[start as usize..end as usize]
.iter()
.map(|acceleration_structure| acceleration_structure.raw),
);
if ds.is_depth_enabled() {
vk_depth_stencil = vk_depth_stencil
.depth_test_enable(true)
.depth_write_enable(ds.depth_write_enabled.unwrap_or_default())
.depth_compare_op(conv::map_comparison(ds.depth_compare.unwrap_or_default()));
} if ds.stencil.is_enabled() { let s = &ds.stencil; let front = conv::map_stencil_face(&s.front, s.read_mask, s.write_mask); let back = conv::map_stencil_face(&s.back, s.read_mask, s.write_mask);
vk_depth_stencil = vk_depth_stencil
.stencil_test_enable(true)
.front(front)
.back(back);
}
if ds.bias.is_enabled() {
vk_rasterization = vk_rasterization
.depth_bias_enable(true)
.depth_bias_constant_factor(ds.bias.constant as f32)
.depth_bias_clamp(ds.bias.clamp)
.depth_bias_slope_factor(ds.bias.slope_scale);
}
}
let vk_viewport = vk::PipelineViewportStateCreateInfo::default()
.flags(vk::PipelineViewportStateCreateFlags::empty())
.scissor_count(1)
.viewport_count(1);
let vk_sample_mask = [
desc.multisample.mask as u32,
(desc.multisample.mask >> 32) as u32,
]; let vk_multisample = vk::PipelineMultisampleStateCreateInfo::default()
.rasterization_samples(vk::SampleCountFlags::from_raw(desc.multisample.count))
.alpha_to_coverage_enable(desc.multisample.alpha_to_coverage_enabled)
.sample_mask(&vk_sample_mask);
for (_, raw) in active { unsafe { self.shared.raw.destroy_fence(Arc::into_inner(raw).expect("Fence should have its reference count be one by the end of each function"), None)
};
} for raw in free { unsafe { self.shared.raw.destroy_fence(raw, None) };
}
}
}
unsafefn start_graphics_debugger_capture(&self) -> bool { #[cfg(feature = "renderdoc")]
{ // Renderdoc requires us to give us the pointer that vkInstance _points to_. let raw_vk_instance =
vk::Handle::as_raw(self.shared.instance.raw.handle()) as *mut *mut _; let raw_vk_instance_dispatch_table = unsafe { *raw_vk_instance }; unsafe { self.render_doc
.start_frame_capture(raw_vk_instance_dispatch_table, ptr::null_mut())
}
} #[cfg(not(feature = "renderdoc"))] false
} unsafefn stop_graphics_debugger_capture(&self) { #[cfg(feature = "renderdoc")]
{ // Renderdoc requires us to give us the pointer that vkInstance _points to_. let raw_vk_instance =
vk::Handle::as_raw(self.shared.instance.raw.handle()) as *mut *mut _; let raw_vk_instance_dispatch_table = unsafe { *raw_vk_instance };
let ray_tracing_functions = self
.shared
.extension_fns
.ray_tracing
.as_ref()
.expect("Feature `RAY_TRACING` not enabled");
let (geometries, primitive_counts) = match *desc.entries { crate::AccelerationStructureEntries::Instances(ref instances) => { let instance_data = vk::AccelerationStructureGeometryInstancesDataKHR::default();
let geometry = vk::AccelerationStructureGeometryKHR::default()
.geometry_type(vk::GeometryTypeKHR::INSTANCES)
.geometry(vk::AccelerationStructureGeometryDataKHR {
instances: instance_data,
});
for triangles in in_geometries { letmut triangle_data =
vk::AccelerationStructureGeometryTrianglesDataKHR::default()
.index_type(vk::IndexType::NONE_KHR)
.vertex_format(conv::map_vertex_format(triangles.vertex_format))
.max_vertex(triangles.vertex_count)
.vertex_stride(triangles.vertex_stride) // The vulkan spec suggests we could pass a non-zero invalid address here if fetching // the real address has significant overhead, but we pass the real one to be on the // safe side for now. // from https://registry.khronos.org/vulkan/specs/latest/man/html/vkGetAccelerationStructureBuildSizesKHR.html // > The srcAccelerationStructure, dstAccelerationStructure, and mode members // > of pBuildInfo are ignored. Any VkDeviceOrHostAddressKHR or VkDeviceOrHostAddressConstKHR // > members of pBuildInfo are ignored by this command, except that the hostAddress // > member of VkAccelerationStructureGeometryTrianglesDataKHR::transformData will // > be examined to check if it is NULL.
.transform_data(vk::DeviceOrHostAddressConstKHR {
device_address: if desc
.flags
.contains(wgt::AccelerationStructureFlags::USE_TRANSFORM)
{ unsafe {
ray_tracing_functions
.buffer_device_address
.get_buffer_device_address(
&vk::BufferDeviceAddressInfo::default().buffer(
triangles
.transform
.as_ref()
.unwrap()
.buffer
.raw,
),
)
}
} else { 0
},
});
let ty = match *desc.entries { crate::AccelerationStructureEntries::Instances(_) => {
vk::AccelerationStructureTypeKHR::TOP_LEVEL
}
_ => vk::AccelerationStructureTypeKHR::BOTTOM_LEVEL,
};
let geometry_info = vk::AccelerationStructureBuildGeometryInfoKHR::default()
.ty(ty)
.flags(conv::map_acceleration_structure_flags(desc.flags))
.geometries(&geometries);
let pool = if desc.allow_compaction { let vk_info = vk::QueryPoolCreateInfo::default()
.query_type(vk::QueryType::ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR)
.query_count(1);
let memory_properties = memory_properties.memory_properties;
for i in0..memory_properties.memory_heap_count { let heap_usage = memory_budget_properties.heap_usage[i as usize]; let heap_budget = memory_budget_properties.heap_budget[i as usize];
if heap_usage >= heap_budget / 100 * threshold as u64 { return Err(crate::DeviceError::OutOfMemory);
}
}
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.