/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use wgc::id; use wgc::{pipeline::CreateShaderModuleError, resource::BufferAccessError}; #[allow(unused_imports)] use wgh::Instance; use wgt::error::{ErrorType, WebGpuError};
use std::borrow::Cow; use std::collections::HashMap; #[allow(unused_imports)] use std::mem; #[cfg(target_os = "linux")] use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd}; use std::os::raw::c_char; use std::ptr; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Duration;
#[allow(unused_imports)] use std::ffi::CString;
#[cfg(target_os = "windows")] use windows::Win32::{Foundation, Graphics::Direct3D12};
/// We limit the size of buffer allocations for stability reason. /// We can reconsider this limit in the future. Note that some drivers (mesa for example), /// have issues when the size of a buffer, mapping or copy command does not fit into a /// signed 32 bits integer, so beyond a certain size, large allocations will need some form /// of driver allow/blocklist. pubconst MAX_BUFFER_SIZE: wgt::BufferAddress = 1u64 << 30u64;
// Mesa has issues with height/depth that don't fit in a 16 bits signed integers. const MAX_TEXTURE_EXTENT: u32 = std::i16::MAX as u32; // We have to restrict the number of bindings for any given resource type so that // the sum of these limits multiplied by the number of shader stages fits // maxBindingsPerBindGroup (1000). This restriction is arbitrary and is likely to // change eventually. See github.com/gpuweb/gpuweb/pull/4484 // For now it's impractical for users to have very large numbers of bindings so this // limit should not be too restrictive until we add support for a bindless API. // Then we may have to ignore the spec or get it changed. const MAX_BINDINGS_PER_RESOURCE_TYPE: u32 = 64;
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))] fn emit_critical_invalid_note(what: &'static str) { // SAFETY: We ensure that the pointer provided is not null. let msg = CString::new(format!("{what} is invalid")).unwrap(); unsafe { gfx_critical_note(msg.as_ptr()) }
}
// hide wgc's global in private pubstruct Global {
owner: WebGPUParentPtr,
global: wgc::global::Global,
swap_chain_configs: Mutex<HashMap<SwapChainId, SwapChainConfig>>,
}
/// Values for the descriptor when creating textures for an active swap chain. #[derive(Clone)] struct SwapChainConfig {
size: wgt::Extent3d,
format: wgt::TextureFormat,
usage: wgt::TextureUsages,
view_formats: Vec<wgt::TextureFormat>,
}
/// # Safety /// /// This function is unsafe because improper use may lead to memory /// problems. For example, a double-free may occur if the function is called /// twice on the same raw pointer. #[no_mangle] pubunsafeextern"C"fn wgpu_server_delete(global: *mut Global) {
log::info!("Terminating WGPU server"); let _ = Box::from_raw(global);
}
#[allow(unreachable_code)] #[allow(unused_variables)] fn support_use_shared_texture_in_swap_chain(
global: &Global,
self_id: id::AdapterId,
backend: wgt::Backend,
is_hardware: bool,
) -> bool { #[cfg(target_os = "windows")]
{ if backend != wgt::Backend::Dx12 {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain: \n", "wgpu backend is not Dx12"
)); returnfalse;
} if !is_hardware {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain: \n", "Dx12 backend is not hardware"
)); returnfalse;
} returntrue;
}
#[cfg(target_os = "linux")]
{ if backend != wgt::Backend::Vulkan {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain: \n", "wgpu backend is not Vulkan"
)); returnfalse;
}
let Some(hal_adapter) = (unsafe { global.adapter_as_hal::<wgc::api::Vulkan>(self_id) }) else {
unreachable!("given adapter ID was actually for a different backend");
};
let capabilities = hal_adapter.physical_device_capabilities(); static REQUIRED: &[&'static std::ffi::CStr] = &[
khr::external_memory_fd::NAME,
ash::ext::external_memory_dma_buf::NAME,
ash::ext::image_drm_format_modifier::NAME,
khr::external_semaphore_fd::NAME,
]; let all_extensions_supported = REQUIRED.iter().all(|&extension| { let supported = capabilities.supports_extension(extension); if !supported {
log::info!(
concat!( "WebGPU: disabling SharedTexture swapchain: \n", "Vulkan extension not supported: {:?}",
),
extension.to_string_lossy()
);
}
supported
}); if !all_extensions_supported { returnfalse;
}
// We need to be able to export the semaphore that gets signalled // when the GPU is done drawing on the ExternalTextureDMABuf. let semaphore_info = vk::PhysicalDeviceExternalSemaphoreInfo::default()
.handle_type(vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_FD); letmut semaphore_props = vk::ExternalSemaphoreProperties::default(); unsafe {
hal_adapter
.shared_instance()
.raw_instance()
.get_physical_device_external_semaphore_properties(
hal_adapter.raw_physical_device(),
&semaphore_info,
&mut semaphore_props,
);
} if !semaphore_props
.external_semaphore_features
.contains(vk::ExternalSemaphoreFeatureFlags::EXPORTABLE)
{
log::info!( "WebGPU: disabling ExternalTexture swapchain: \n\
device can't export opaque file descriptor semaphores"
); returnfalse;
}
returntrue;
}
#[cfg(target_os = "macos")]
{ use objc2_foundation::NSProcessInfo;
if backend != wgt::Backend::Metal {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain: \n", "wgpu backend is not Metal"
)); returnfalse;
} if !is_hardware {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain: \n", "Metal backend is not hardware"
)); returnfalse;
}
let version = NSProcessInfo::processInfo().operatingSystemVersion();
if !ns_os_version_at_least(&version, (10, 14), (12, 0), /* os_is_mac */ true) {
log::info!(concat!( "WebGPU: disabling SharedTexture swapchain:\n", "operating system version is not at least 10.14 (macOS) or 12.0 (iOS)\n", "shared event not supported"
)); returnfalse;
}
returntrue;
}
false
}
fn create_next_numbered_dir(dir: &std::path::Path) -> std::io::Result<std::path::PathBuf> { use std::fs;
loop { let next = match fs::read_dir(dir) {
Ok(entries) => entries
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.file_name().to_str()?.parse::<u64>().ok())
.max()
.map(|n| n + 1),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
Err(e) => return Err(e),
};
let path = dir.join(next.unwrap_or(0).to_string()); match fs::create_dir_all(&path) {
Ok(()) => return Ok(path),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(e) => return Err(e),
}
}
}
fn sanitize_limits(limits: &mut wgt::Limits) { // Copy the value of a WebGPU-defined limit without modifying it.
macro_rules! transfer_limit {
($limit:tt) => { let $limit = limits.$limit;
};
} // Ignore or `debug_assert` the requested value of a limit related to `wgpu` extensions, // then use the default value. (In most cases the default value of `wgpu`-defined limits // is zero, but `max_non_sampler_bindings` is a special case.)
macro_rules! sanitize_limit {
($limit:tt) => {
debug_assert_eq!(limits.$limit, wgt::Limits::default().$limit); let $limit = wgt::Limits::default().$limit;
};
}
assert_eq!(required_features.features_wgpu, wgt::FeaturesWGPU::empty()); // TODO: compare to DeviceDescriptor::default() once wgpu offers `Eq` // for the necessary types.
assert!(!experimental_features.is_enabled());
assert!(matches!(memory_hints, wgt::MemoryHints::Performance));
assert!(matches!(trace, wgt::Trace::Off));
// Note that there is logic below that adds back some native features that // are used internally to implement external textures.
required_features.features_wgpu = wgt::FeaturesWGPU::empty();
sanitize_limits(&mut required_limits);
wgc::device::DeviceDescriptor {
label,
required_features,
required_limits,
experimental_features: wgt::ExperimentalFeatures::disabled(),
memory_hints: wgt::MemoryHints::MemoryUsage,
trace: wgt::Trace::Off, // may be overridden below
}
};
iflet Some(env_dir) = std::env::var_os("WGPU_TRACE") { match create_next_numbered_dir(&std::path::PathBuf::from(env_dir)) {
Ok(path) => sanitized_desc.trace = wgt::Trace::Directory(path),
Err(err) => log::warn!("Failed to create directory for wgpu recording: {err:?}"),
}
}
if wgpu_parent_is_external_texture_enabled() { // Enable features used for external texture support, if available. We // avoid adding unsupported features to required_features so that we // can still create a device in their absence, and will only fail when // performing an operation that actually requires the feature. for feature in [
wgt::Features::EXTERNAL_TEXTURE,
wgt::Features::TEXTURE_FORMAT_NV12,
wgt::Features::TEXTURE_FORMAT_P010,
wgt::Features::TEXTURE_FORMAT_16BIT_NORM,
] { if global.adapter_features(self_id).contains(feature) {
sanitized_desc.required_features.insert(feature);
}
}
}
#[cfg(target_os = "linux")]
{ let hal_adapter = global.adapter_as_hal::<wgc::api::Vulkan>(self_id);
let support_dma_buf = hal_adapter.as_ref().is_some_and(|hal_adapter| { let capabilities = hal_adapter.physical_device_capabilities();
let raw_instance = hal_adapter.shared_instance().raw_instance(); let raw_physical_device = hal_adapter.raw_physical_device();
let queue_family_index = raw_instance
.get_physical_device_queue_family_properties(raw_physical_device)
.into_iter()
.enumerate()
.find_map(|(queue_family_index, info)| { if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
Some(queue_family_index as u32)
} else {
None
}
});
let Some(queue_family_index) = queue_family_index else { let msg = c"Vulkan device has no graphics queue";
gfx_critical_note(msg.as_ptr()); return Some(format!("Internal Error: Failed to create ash::Device"));
};
let family_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(queue_family_index)
.queue_priorities(&[1.0]); let family_infos = [family_info];
let str_pointers = enabled_extensions
.iter()
.map(|&s| { // Safe because `enabled_extensions` entries have static lifetime.
s.as_ptr()
})
.collect::<Vec<_>>();
let pre_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&family_infos)
.enabled_extension_names(&str_pointers); let info = enabled_phd_features.add_to_device_create(pre_info);
let raw_device = match raw_instance.create_device(raw_physical_device, &info, None)
{
Err(err) => { let msg =
CString::new(format!("create_device() failed: {:?}", err)).unwrap();
gfx_critical_note(msg.as_ptr()); return Some(format!("Internal Error: Failed to create ash::Device"));
}
Ok(raw_device) => raw_device,
};
let hal_device = match hal_adapter.device_from_raw(
raw_device,
None,
&enabled_extensions,
sanitized_desc.required_features,
&sanitized_desc.required_limits,
&sanitized_desc.memory_hints,
family_info.queue_family_index, 0,
) {
Err(err) => { let msg =
CString::new(format!("device_from_raw() failed: {:?}", err)).unwrap();
gfx_critical_note(msg.as_ptr()); return Some(format!("Internal Error: Failed to create ash::Device"));
}
Ok(hal_device) => hal_device,
};
let res = global.create_device_from_hal(
self_id,
hal_device.into(),
&sanitized_desc,
Some(new_device_id),
Some(new_queue_id),
); iflet Err(err) = res { return Some(format!("{err}"));
} return None;
}
}
}
let res = global.adapter_request_device(
self_id,
&sanitized_desc,
Some(new_device_id),
Some(new_queue_id),
); iflet Err(err) = res { return Some(format!("{err}"));
} else { return None;
}
}
impl DeviceLostClosure { fn call(self, reason: wgt::DeviceLostReason, message: String) { // Ensure message is structured as a null-terminated C string. It only // needs to live as long as the callback invocation. let message = std::ffi::CString::new(message).unwrap(); unsafe {
(self.callback)(self.user_data, reason as u8, message.as_ptr());
}
core::mem::forget(self);
}
}
impl Drop for DeviceLostClosure { fn drop(&mutself) { unsafe {
(self.cleanup_callback)(self.user_data);
}
}
}
#[no_mangle] pubunsafeextern"C"fn wgpu_server_set_device_lost_callback(
global: &Global,
self_id: id::DeviceId,
closure: DeviceLostClosure,
) { // Create a one-shot channel that the `wgpu_core` callback can use to report // the device loss to the calling thread. let (device_lost_sender, device_lost_receiver) = futures_channel::oneshot::channel();
// Spawn a task on the calling thread to wait for such a report.
moz_task::spawn_local("device lost callback", asyncmove { match device_lost_receiver.await {
Ok((reason, message)) => {
closure.call(reason, message);
}
Err(futures_channel::oneshot::Canceled) => { // The device loss closure was never invoked, so // `device_lost_sender` was dropped. Exit this task without // doing anything.
}
}
})
.detach();
impl ShaderModuleCompilationMessage { fn new(error: &CreateShaderModuleError, source: &str) -> Self { // The WebGPU spec says that if the message doesn't point to a particular position in // the source, the line number, position, offset and lengths should be zero. let line_number; let line_pos; let utf16_offset; let utf16_length;
let location = match error {
CreateShaderModuleError::Parsing(e) => e.inner.location(source),
CreateShaderModuleError::Validation(e) => e.inner.location(source),
_ => None,
};
iflet Some(location) = location { let len_utf16 = |s: &str| s.chars().map(|c| c.len_utf16() as u64).sum(); let start = location.offset as usize; let end = start + location.length as usize;
utf16_offset = len_utf16(&source[0..start]);
utf16_length = len_utf16(&source[start..end]);
line_number = location.line_number as u64; // Naga reports a `line_pos` using UTF-8 bytes, so we cannot use it. let line_start = source[0..start].rfind('\n').map(|pos| pos + 1).unwrap_or(0);
line_pos = len_utf16(&source[line_start..start]) + 1;
} else {
line_number = 0;
line_pos = 0;
utf16_offset = 0;
utf16_length = 0;
}
/// The status code provided to the buffer mapping closure. /// /// This is very similar to `BufferAccessResult`, except that this is FFI-friendly. #[repr(C)] pubenum BufferMapAsyncStatus { /// The Buffer is successfully mapped, `get_mapped_range` can be called. /// /// All other variants of this enum represent failures to map the buffer.
Success, /// The buffer is already mapped. /// /// While this is treated as an error, it does not prevent mapped range from being accessed.
AlreadyMapped, /// Mapping was already requested.
MapAlreadyPending, /// An unknown error.
Error, /// The context is Lost.
ContextLost, /// The buffer is in an invalid state.
Invalid, /// The range isn't fully contained in the buffer.
InvalidRange, /// The range isn't properly aligned.
InvalidAlignment, /// Incompatible usage flags.
InvalidUsageFlags,
}
/// # Safety /// /// Callers are responsible for ensuring `closure` is well-formed. #[no_mangle] pubunsafeextern"C"fn wgpu_server_buffer_map(
global: &Global,
device_id: id::DeviceId,
buffer_id: id::BufferId,
start: wgt::BufferAddress,
size: wgt::BufferAddress,
map_mode: wgc::device::HostMap,
closure: BufferMapClosure, mut error_buf: ErrorBuffer,
) { // Create a one-shot channel to carry the map result from the // `buffer_map_async` callback to this thread. let (map_result_sender, map_result_receiver) = futures_channel::oneshot::channel();
// Spawn a task on this thread to wait for that map result.
moz_task::spawn_local("wgpu_server_buffer_map callback", asyncmove { let result = map_result_receiver.await.unwrap();
(closure.callback)(closure.user_data, BufferMapAsyncStatus::from(result));
})
.detach();
let operation = wgc::resource::BufferMapOperation {
host: map_mode,
callback: Some(Box::new(move |result| { // Send the map result from whatever thread this callback is running // on to the task we spawned above.
map_result_sender.send(result).unwrap();
})),
}; let result = global.buffer_map_async(buffer_id, start, Some(size), operation);
iflet Err(error) = result {
error_buf.init(error, device_id);
}
}
/// Map a buffer, blocking until it is ready for access. /// /// Map the `size` bytes starting at `offset` in `buffer_id` to be accessed /// according to `map_mode`, blocking the calling thread until the mapping is /// ready. /// /// This function actually blocks the calling thread until the GPU has completed /// all previously submitted work, even if the buffer is actually available /// right now. In practice, this function is generally used immediately after /// submitted work that writes data to the buffer, so this shouldn't be much of /// a problem. /// /// All ids are looked up using `global`. The buffer `buffer_id` must belong to /// `device_id`. /// /// Return a `BufferMapAsyncStatus` to indicate success or failure. #[no_mangle] pubextern"C"fn wgpu_server_buffer_map_blocking(
global: &Global,
device_id: id::DeviceId,
buffer_id: id::BufferId,
offset: wgt::BufferAddress,
size: wgt::BufferAddress,
map_mode: wgc::device::HostMap,
) -> BufferMapAsyncStatus { // Arrange to pass the map status back to this function. The whole Arc/OnceLock // song and dance is required because `buffer_map_async` assumes that the // poll might happen on another thread. let status_passback = Arc::new(OnceLock::new()); let op = wgc::resource::BufferMapOperation {
host: map_mode,
callback: Some(Box::new({ let status_passback = Arc::clone(&status_passback); move |status| { // unwrap: This callback is the only place that initializes the // `OnceLock`, and it should only be invoked once.
status_passback.set(status).unwrap();
}
})),
};
// Submit the map request, and note its submission index. let submission_index; match global.buffer_map_async(buffer_id, offset, Some(size), op) {
Ok(i) => {
submission_index = i;
}
Err(err) => { return BufferMapAsyncStatus::from(Err(err));
}
}
// Wait until the map request submission is done. let poll_type = wgt::PollType::Wait {
submission_index: Some(submission_index),
timeout: Some(Duration::from_secs(60)),
}; iflet Err(err) = global.device_poll(device_id, poll_type) { return BufferMapAsyncStatus::from(Err(err));
}
// We could just lock the mutex and unwrap the status, but let's take it // step by step and check everything is as we expect.
// unwrap: `status_passback` should be the only owner of the `Arc`. let status_oncelock = Arc::into_inner(status_passback).unwrap();
// unwrap: the `OnceLock` should have been initialized. let status_result = status_oncelock.into_inner().unwrap();
/// # Safety /// /// This function is unsafe as there is no guarantee that the given pointer is /// valid for `size` elements. #[no_mangle] pubunsafeextern"C"fn wgpu_server_buffer_get_mapped_range(
global: &Global,
device_id: id::DeviceId,
buffer_id: id::BufferId,
start: wgt::BufferAddress,
size: wgt::BufferAddress, mut error_buf: ErrorBuffer,
) -> MappedBufferSlice { let result = global.buffer_get_mapped_range(buffer_id, start, Some(size));
#[no_mangle] pubextern"C"fn wgpu_server_buffer_unmap(
global: &Global,
device_id: id::DeviceId,
buffer_id: id::BufferId, mut error_buf: ErrorBuffer,
) { iflet Err(e) = global.buffer_unmap(buffer_id) { match e { // NOTE: This is presumed by CTS test cases, and was even formally specified in the // WebGPU spec. previously, but this doesn't seem formally specified now. :confused: // // TODO: upstream this; see <https://bugzilla.mozilla.org/show_bug.cgi?id=1842297>.
BufferAccessError::InvalidResource(_) => (),
other => error_buf.init(other, device_id),
}
}
}
let device = hal_device.raw_device(); let physical_device = hal_device.raw_physical_device(); let instance = hal_device.shared_instance().raw_instance();
modifier_props.retain(|modifier_prop| { let support = is_dmabuf_supported(
instance,
physical_device,
vk::Format::B8G8R8A8_UNORM,
modifier_prop.drm_format_modifier,
usage_flags,
);
support
});
if modifier_props.is_empty() { let msg = c"format not supported for dmabuf import";
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
}
let modifiers: Vec<u64> = modifier_props
.iter()
.map(|modifier_prop| modifier_prop.drm_format_modifier)
.collect();
let vk_info = vk::ImageCreateInfo::default()
.flags(vk::ImageCreateFlags::ALIAS)
.image_type(vk::ImageType::TYPE_2D) // Bug 1971883: Rather than hard-coding this format, we should use // whatever format was negotiated between `GPUCanvasContext.configure` // and the GPU process.
.format(vk::Format::B8G8R8A8_UNORM)
.extent(extent)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
.usage(usage_flags)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.push_next(&mut modifier_list)
.push_next(&mut external_image_create_info);
let image = match device.create_image(&vk_info, None) {
Err(err) => { let msg = CString::new(format!("create_image() failed: {:?}", err)).unwrap();
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
}
Ok(image) => image,
};
letmut image_modifier_properties = vk::ImageDrmFormatModifierPropertiesEXT::default(); let image_drm_format_modifier =
ash::ext::image_drm_format_modifier::Device::new(instance, device); let ret = image_drm_format_modifier
.get_image_drm_format_modifier_properties(image, &mut image_modifier_properties); if ret.is_err() { let msg = CString::new(format!( "get_image_drm_format_modifier_properties() failed: {:?}",
ret
))
.unwrap();
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
}
let memory_req = device.get_image_memory_requirements(image);
let mem_properties = instance.get_physical_device_memory_properties(physical_device);
let memory_allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(memory_req.size)
.memory_type_index(index as u32)
.push_next(&mut dedicated_memory_info)
.push_next(&mut export_memory_alloc_info);
let memory = match device.allocate_memory(&memory_allocate_info, None) {
Err(err) => { let msg = CString::new(format!("allocate_memory() failed: {:?}", err)).unwrap();
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
}
Ok(memory) => memory,
};
let result = device.bind_image_memory(image, memory, /* offset */ 0); if result.is_err() { let msg = CString::new(format!("bind_image_memory() failed: {:?}", result)).unwrap();
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
}
*out_memory_size = memory_req.size;
let modifier_prop = modifier_props
.iter()
.find(|prop| prop.drm_format_modifier == image_modifier_properties.drm_format_modifier); let Some(modifier_prop) = modifier_prop else { let msg = c"failed to find modifier_prop";
gfx_critical_note(msg.as_ptr()); return ptr::null_mut();
};
let plane_count = modifier_prop.drm_format_modifier_plane_count;
letmut layouts = Vec::new(); for i in0..plane_count { // VUID-vkGetImageSubresourceLayout-tiling-09433: For // `DMA_BUF` images, the planes must be identified using the // `MEMORY_PLANE_i_EXT bits, not the `PLANE_i` bits. let flag = match i { 0 => vk::ImageAspectFlags::MEMORY_PLANE_0_EXT, 1 => vk::ImageAspectFlags::MEMORY_PLANE_1_EXT, 2 => vk::ImageAspectFlags::MEMORY_PLANE_2_EXT,
_ => unreachable!(),
}; let subresource = vk::ImageSubresource::default().aspect_mask(flag); let layout = device.get_image_subresource_layout(image, subresource);
layouts.push(layout);
}
let image_handle = VkImageHandle {
device: device.handle(),
image,
memory,
memory_size: memory_req.size,
memory_type_index: index as u32,
modifier: image_modifier_properties.drm_format_modifier,
layouts,
};
#[cfg(target_os = "linux")] fn create_texture_with_shared_texture_dmabuf(
&self,
device_id: id::DeviceId,
texture_id: id::TextureId,
desc: &wgc::resource::TextureDescriptor,
swap_chain_id: Option<SwapChainId>,
) -> bool { unsafe { let ret = wgpu_server_ensure_shared_texture_for_swap_chain( self.owner,
swap_chain_id.unwrap(),
device_id,
texture_id,
desc.size.width,
desc.size.height,
desc.format,
desc.usage,
); if ret != true { let msg = c"Failed to create shared texture";
gfx_critical_note(msg.as_ptr()); returnfalse;
}
let handle = wgpu_server_get_vk_image_handle(self.owner, texture_id); if handle.is_null() { let msg = c"Failed to get VkImageHandle";
gfx_critical_note(msg.as_ptr()); returnfalse;
}
let vk_image_wrapper = &*handle;
let fd = wgpu_server_get_dma_buf_fd(self.owner, texture_id); if fd < 0 { let msg = c"Failed to get DMABuf fd";
gfx_critical_note(msg.as_ptr()); returnfalse;
}
// Ensure to close file descriptor let owned_fd = OwnedFd::from_raw_fd(fd as RawFd);
let Some(hal_device) = self.device_as_hal::<wgc::api::Vulkan>(device_id) else {
emit_critical_invalid_note("Vulkan device"); returnfalse;
};
// Surprising rule: // // > VUID-VkImageDrmFormatModifierExplicitCreateInfoEXT-size-02267: // > For each element of pPlaneLayouts, size must be 0 // // Rationale: // // > In each element of pPlaneLayouts, the implementation must ignore // > size. The implementation calculates the size of each plane, which // > the application can query with vkGetImageSubresourceLayout. // // So, make a temporary copy of the plane layouts and zero // out their sizes. let memory_plane_layouts: Vec<_> = vk_image_wrapper
.layouts
.iter()
.map(|layout| vk::SubresourceLayout { size: 0, ..*layout })
.collect();
// VUID-VkImageCreateInfo-pNext-00990 // // Since `wgpu_vkimage_create_with_dma_buf` above succeeded in // creating the original DMABuf image, if we pass the same // parameters, including the DRM format modifier and plane layouts, // we can assume that this call will succeed too. // // The only thing we're adding is the `ALIAS` flag, because this // aliases the original image. letmut modifier_list = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
.drm_format_modifier(vk_image_wrapper.modifier)
.plane_layouts(&memory_plane_layouts);
let vk_info = vk::ImageCreateInfo::default()
.flags(vk::ImageCreateFlags::ALIAS)
.image_type(vk::ImageType::TYPE_2D) // Bug 1971883: Rather than hard-coding this format, we should use // whatever format was negotiated between `GPUCanvasContext.configure` // and the GPU process.
.format(vk::Format::B8G8R8A8_UNORM)
.extent(extent)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
.usage(usage_flags)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.push_next(&mut modifier_list)
.push_next(&mut external_image_create_info);
let image = match device.create_image(&vk_info, None) {
Err(err) => { let msg = CString::new(format!( "Failed to get vk::Image: create_image() failed: {:?}",
err
))
.unwrap();
gfx_critical_note(msg.as_ptr()); returnfalse;
}
Ok(image) => image,
};
let memory_req = device.get_image_memory_requirements(image); if memory_req.size > vk_image_wrapper.memory_size { let msg = c"Invalid memory size";
gfx_critical_note(msg.as_ptr()); returnfalse;
}
let shmem_data = unsafe { shmem_mappings.as_slice()[shmem_handle_index].as_slice() };
let shmem_size = shmem_data.len();
// If we requested a non-zero mappable buffer and get a size of zero, it // indicates that the shmem allocation failed on the client side or // mapping failed in the parent process. let shmem_allocation_failed = needs_shmem && (shmem_size as u64) < desc.size; if shmem_allocation_failed {
assert_eq!(shmem_size, 0);
}
// Don't trust the graphics driver with buffer sizes larger than our conservative max buffer size. if shmem_allocation_failed || desc.size > MAX_BUFFER_SIZE {
error_buf.init(ErrMsg::oom(), device_id); self.create_buffer_error(Some(buffer_id), &desc); return;
}
let max = MAX_TEXTURE_EXTENT; if desc.size.width > max
|| desc.size.height > max
|| desc.size.depth_or_array_layers > max
{ self.create_texture_error(Some(id), &desc);
error_buf.init(ErrMsg::oom(), device_id); return;
}
let (_, error) = self.device_create_texture(device_id, &desc, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreateExternalTexture(id, desc) => { // Obtain the descriptor from the source. A source ID of `None` // indicates the client-side encountered an error when // importing the source. let source_desc = desc.source.and_then(|source| { let source_desc = unsafe {
wgpu_parent_external_texture_source_get_external_texture_descriptor( self.owner,
source,
desc.color_space,
)
}; let planes = unsafe { source_desc.planes.as_slice() }; // The source having no planes indicates we encountered an // error on the server side when importing the source if planes.is_empty() {
None
} else {
Some(source_desc)
}
}); match source_desc {
Some(source_desc) => { let planes = unsafe { source_desc.planes.as_slice() }; let desc = wgt::ExternalTextureDescriptor {
label: desc.label,
width: source_desc.width,
height: source_desc.height,
format: source_desc.format,
yuv_conversion_matrix: source_desc.yuv_conversion_matrix,
gamut_conversion_matrix: source_desc.gamut_conversion_matrix,
src_transfer_function: source_desc.src_transfer_function,
dst_transfer_function: source_desc.dst_transfer_function,
sample_transform: source_desc.sample_transform,
load_transform: source_desc.load_transform,
}; let (_, error) = self.device_create_external_texture(device_id, &desc, planes, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
None => { // Create the external texture in an error state. let desc = wgt::ExternalTextureDescriptor {
label: desc.label,
width: 0,
height: 0,
format: wgt::ExternalTextureFormat::Rgba,
yuv_conversion_matrix: Default::default(),
gamut_conversion_matrix: Default::default(),
src_transfer_function: Default::default(),
dst_transfer_function: Default::default(),
sample_transform: Default::default(),
load_transform: Default::default(),
}; self.create_external_texture_error(Some(id), &desc);
}
}
}
DeviceAction::CreateSampler(id, desc) => { let (_, error) = self.device_create_sampler(device_id, &desc, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreateBindGroupLayout(id, desc) => { let (_, error) = self.device_create_bind_group_layout(device_id, &desc, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreateBindGroupLayoutError(id, label) => { self.create_bind_group_layout_error(Some(id), label);
}
DeviceAction::RenderPipelineGetBindGroupLayout(pipeline_id, index, bgl_id) => { let (_, error) = self.render_pipeline_get_bind_group_layout(pipeline_id, index, Some(bgl_id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::ComputePipelineGetBindGroupLayout(pipeline_id, index, bgl_id) => { let (_, error) = self.compute_pipeline_get_bind_group_layout(pipeline_id, index, Some(bgl_id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreatePipelineLayout(id, desc) => { let (_, error) = self.device_create_pipeline_layout(device_id, &desc, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreateBindGroup(id, desc) => { let (_, error) = self.device_create_bind_group(device_id, &desc, Some(id)); iflet Some(err) = error {
error_buf.init(err, device_id);
}
}
DeviceAction::CreateShaderModule(id, label, code) => { let desc = wgc::pipeline::ShaderModuleDescriptor {
label,
runtime_checks: wgt::ShaderRuntimeChecks::checked(),
}; let source = wgc::pipeline::ShaderModuleSource::Wgsl(Cow::Borrowed(code.as_ref())); let (_, error) = self.device_create_shader_module(device_id, &desc, source, Some(id));
let compilation_messages = iflet Some(err) = error { // Per spec: "User agents should not include detailed compiler error messages or // shader text in the message text of validation errors arising here: these details // are accessible via getCompilationInfo()" let message = match &err {
CreateShaderModuleError::Parsing(_) => "Parsing error".to_string(),
CreateShaderModuleError::Validation(_) => { "Shader validation error".to_string()
}
CreateShaderModuleError::Device(device_err) => format!("{device_err:?}"),
_ => format!("{err:?}"),
};
// Synthesize the `BufferMapResponse` that is normally // generated in the callback set up below. let response = BufferMapResult::Error(message.into());
*response_byte_buf =
make_byte_buf(&ServerMessage::BufferMapResponse(buffer_id, response)); return;
}
};
// Create a one-shot channel to carry the map result from the // `buffer_map_async` callback to this thread. let (map_result_sender, map_result_receiver) = futures_channel::oneshot::channel();
// Spawn a task on this thread to wait for that map result.
moz_task::spawn_local("process_buffer_map callback", asyncmove { let result = map_result_receiver.await.unwrap(); unsafe {
(closure.callback)(closure.user_data, BufferMapAsyncStatus::from(result));
}
})
.detach();
let operation = wgc::resource::BufferMapOperation {
host: mode,
callback: Some(Box::new(move |result| { // Send the map result from whatever thread this callback is running // on to the task we spawned above.
map_result_sender.send(result).unwrap();
})),
}; let result = global.buffer_map_async(buffer_id, offset, Some(size), operation);
iflet Err(error) = result {
error_buf.init(error, device_id);
}
}
match message {
Message::RequestAdapter {
adapter_id,
power_preference,
force_fallback_adapter,
} => { letmut result = None;
// Prefer to use the dx12 backend, if one exists, and use the same DXGI adapter as WebRender. // If wgpu uses a different adapter than WebRender, textures created by // webgpu::SharedTexture do not work with wgpu. #[cfg(target_os = "windows")]
{ letmut adapter_luid = core::mem::MaybeUninit::<crate::FfiLUID>::uninit();
wgpu_parent_get_compositor_device_luid(adapter_luid.as_mut_ptr()); let adapter_luid = if adapter_luid.as_ptr().is_null() {
None
} else {
Some(adapter_luid.assume_init())
};
if adapter_luid.is_some() && !force_fallback_adapter { iflet Some(instance) = global.global.instance_as_hal::<wgc::api::Dx12>() { for adapter in instance.enumerate_adapters(None) { let raw_adapter = adapter.adapter.raw_adapter(); let desc = unsafe { raw_adapter.GetDesc() }; iflet Ok(desc) = desc { if desc.AdapterLuid.LowPart == adapter_luid.unwrap().low_part
&& desc.AdapterLuid.HighPart == adapter_luid.unwrap().high_part
{
global.create_adapter_from_hal(
wgh::DynExposedAdapter::from(adapter),
Some(adapter_id),
);
result = Some(true); break;
}
}
} if result.is_none() {
log::error!(concat!( "Failed to find D3D12 adapter with the same LUID ", "that the compositor is using!"
));
result = Some(false);
}
}
}
}
let desc = wgt::RequestAdapterOptions {
power_preference,
force_fallback_adapter,
compatible_surface: None,
apply_limit_buckets: false,
}; if result.is_none() { let created = match global.request_adapter(&desc, wgt::Backends::PRIMARY, Some(adapter_id)) {
Ok(_) => true,
Err(e) => {
log::warn!("{e}"); false
}
};
result = Some(created);
}
let response = if result.unwrap() { let wgt::AdapterInfo {
name,
vendor,
device,
device_type,
driver,
driver_info,
backend,
transient_saves_memory,
device_pci_bus_id: _,
subgroup_min_size,
subgroup_max_size,
limit_bucket: _,
} = global.adapter_get_info(adapter_id);
let is_hardware = match device_type {
wgt::DeviceType::IntegratedGpu | wgt::DeviceType::DiscreteGpu => true,
_ => false,
};
if static_prefs::pref!("dom.webgpu.testing.assert-hardware-adapter")
&& !desc.force_fallback_adapter
{
assert!(
is_hardware, "Expected a hardware gpu adapter, got {:?}",
device_type
);
}
let support_use_shared_texture_in_swap_chain =
support_use_shared_texture_in_swap_chain(
global,
adapter_id,
backend,
is_hardware,
);
// From [Wikipedia](https://en.wikipedia.org/wiki/File_descriptor):
//
// > File descriptors typically have non-negative integer values, with negative values
// > being reserved to indicate "no value" or error conditions.
file_descriptor.unwrap_or(-1)
}
#[no_mangle]
#[cfg(target_os = "linux")]
pub unsafe extern "C" fn wgpu_vksemaphore_destroy(
global: &Global,
device_id: id::DeviceId,
handle: &VkSemaphoreHandle,
) {
unsafe {
if let Some(hal_queue) = global.queue_as_hal::<wgc::api::Vulkan>(handle.queue_id) {
if !hal_queue.remove_signal_semaphore(handle.semaphore) {
let _ = hal_queue.raw_device().queue_wait_idle(hal_queue.as_raw());
}
}
let Some(hal_device) = global.device_as_hal::<wgc::api::Vulkan>(device_id) else {
emit_critical_invalid_note("Vulkan device");
return;
};
let device = hal_device.raw_device();
device.destroy_semaphore(handle.semaphore, None);
};
}
let Some(hal_device) = global.device_as_hal::<wgc::api::Dx12>(device_id) else {
emit_critical_invalid_note("dx12 device");
global.create_texture_error(Some(id_in), &desc);
return;
};
let dx12_device = hal_device.raw_device();
let mut resource: Option<Direct3D12::ID3D12Resource> = None;
let res = dx12_device.OpenSharedHandle(Foundation::HANDLE(handle), &mut resource);
if res.is_err() || resource.is_none() {
error_buf.init(
ErrMsg {
message: "Failed to import texture from shared handle".into(),
r#type: ErrorType::Internal,
},
device_id,
);
global.create_texture_error(Some(id_in), &desc);
return;
}
let hal_texture = <wgh::api::Dx12 as wgh::Api>::Device::texture_from_raw(
resource.unwrap(),
desc.format,
desc.dimension,
desc.size,
desc.mip_level_count,
desc.sample_count,
);
let (_, error) = global.create_texture_from_hal(
Box::new(hal_texture),
device_id,
&desc,
wgt::TextureUses::UNINITIALIZED,
Some(id_in),
);
if let Some(err) = error {
error_buf.init(err, device_id);
}
}
/// Imports a fence from a shared handle and queues a GPU-side wait on the
/// specified queue for the fence to reach a specific value.
#[cfg(target_os = "windows")]
#[no_mangle]
pub unsafe extern "C" fn wgpu_server_device_wait_fence_from_shared_handle(
global: &Global,
device_id: id::DeviceId,
queue_id: id::QueueId,
fence_handle: *mut core::ffi::c_void,
fence_value: wgh::FenceValue,
) -> bool {
let Some(hal_device) = global.device_as_hal::<wgc::api::Dx12>(device_id) else {
emit_critical_invalid_note("dx12 device");
return false;
};
let Some(hal_queue) = global.queue_as_hal::<wgc::api::Dx12>(queue_id) else {
emit_critical_invalid_note("dx12 queue");
return false;
};
let mut fence: Option<Direct3D12::ID3D12Fence> = None;
let res = hal_device
.raw_device()
.OpenSharedHandle(Foundation::HANDLE(fence_handle), &mut fence);
let fence = match (res, fence) {
(Ok(_), Some(fence)) => fence,
_ => return false,
};
let res = hal_queue.as_raw().Wait(&fence, fence_value);
res.is_ok()
}
#[cfg(target_os = "macos")]
mod macos {
use std::ffi::CString;
use super::{emit_critical_invalid_note, gfx_critical_note, Global};
use crate::{
error::ErrorBuffer,
server::{
wgpu_server_ensure_shared_texture_for_swap_chain,
wgpu_server_get_external_io_surface_id,
},
wgpu_string, SwapChainId,
};
use nsstring::nsACString;
use objc2::{
rc::{autoreleasepool, Retained},
runtime::ProtocolObject,
};
use objc2_foundation::NSString;
use objc2_io_surface::IOSurfaceRef;
use objc2_metal::{
MTLDevice as _, MTLPixelFormat, MTLResource, MTLStorageMode, MTLTexture,
MTLTextureDescriptor, MTLTextureType, MTLTextureUsage,
};
use wgc::id;
/// Imports a Metal texture from the specified plane of an IOSurface.
#[no_mangle]
pub unsafe extern "C" fn wgpu_server_device_import_texture_from_iosurface(
global: &Global,
device_id: id::DeviceId,
id_in: id::TextureId,
desc: &wgt::TextureDescriptor<Option<&nsACString>, crate::FfiSlice<wgt::TextureFormat>>,
io_surface_id: u32,
plane: usize,
mut error_buf: ErrorBuffer,
) {
let desc = desc.map_label_and_view_formats(|l| wgpu_string(*l), |v| v.as_slice().to_vec());
let raw_texture: Retained<ProtocolObject<dyn MTLTexture>> = unsafe {
let Some(hal_device) = self.device_as_hal::<wgc::api::Metal>(device_id) else {
emit_critical_invalid_note("metal device");
return false;
};
let device = hal_device.raw_device();
let maybe_texture = autoreleasepool(|_| {
let descriptor = MTLTextureDescriptor::new();
let usage = MTLTextureUsage::RenderTarget
| MTLTextureUsage::ShaderRead
| MTLTextureUsage::PixelFormatView;
descriptor.setTextureType(MTLTextureType::Type2D);
descriptor.setWidth(desc_ref.size.width as usize);
descriptor.setHeight(desc_ref.size.height as usize);
descriptor.setMipmapLevelCount(desc_ref.mip_level_count as usize);
descriptor.setPixelFormat(MTLPixelFormat::BGRA8Unorm);
descriptor.setUsage(usage);
descriptor.setStorageMode(MTLStorageMode::Private);
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.