Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a6990f4
It looks ok
inner-daemons May 16, 2026
6cc5e16
Most tests pass??
inner-daemons May 19, 2026
c6c9ba7
Apparently passes more tests
inner-daemons May 20, 2026
90833d7
Logs
inner-daemons May 20, 2026
8c9c7e9
Fixed cargo stuff
inner-daemons May 20, 2026
18f1c3a
Fixed it up again
inner-daemons May 20, 2026
9e9fa8b
Uggh finally
inner-daemons May 20, 2026
58ebc76
Use local wgpu-native path dep to unify naga version
inner-daemons May 20, 2026
d373dbc
Claude claims it passes tests, I haven't verified if the harness was …
inner-daemons May 26, 2026
cf7a1aa
Removed fail_logs
inner-daemons May 26, 2026
a6016c9
Removed local patch
inner-daemons May 26, 2026
0e87ca6
Add env var to disable custom backend
inner-daemons May 26, 2026
36ddf3c
Thing
inner-daemons May 27, 2026
d2d3ed8
Fixed more stuff
inner-daemons May 27, 2026
4e7a87f
Wire up all remaining wgpu-c-backend APIs
inner-daemons May 27, 2026
0a29f27
Add pipeline statistics query support and AccelerationStructure/array…
inner-daemons May 27, 2026
bcd606a
Fix staging buffer protocol and extend passthrough shader format support
inner-daemons May 27, 2026
5d85b61
Wire up WgslLanguageFeatures, texture_discard, and AccelerationStruct…
inner-daemons May 27, 2026
c689cc3
FIxed test
inner-daemons May 27, 2026
6a9edae
Implement poll_all_devices, downlevel_capabilities, and generate_allo…
inner-daemons May 27, 2026
64f00b3
Add wgpu-c-backend dependency to examples and benchmarks
inner-daemons May 27, 2026
e7c30c7
Update Cargo.lock
inner-daemons May 27, 2026
a4a8cef
Make more examples use the c backend
inner-daemons May 27, 2026
5c247d4
Fied a bunch of random issues
inner-daemons May 27, 2026
52db16d
Fixed clippy things
inner-daemons May 27, 2026
4f29382
Fixed a windowing issue
inner-daemons May 27, 2026
d668911
Have we done it?
inner-daemons May 27, 2026
275f9bc
Audit again
inner-daemons May 27, 2026
113d963
Fix audit findings in wgpu-c-backend and custom backend plumbing
inner-daemons May 28, 2026
a37ffdb
Update cargo.lock
inner-daemons May 28, 2026
80da4ca
Fixed compile errors
inner-daemons May 28, 2026
52bfd38
Prettier TM readme
inner-daemons May 28, 2026
3e6e7e4
Change ctor dep
inner-daemons May 28, 2026
943c558
Reset cargo.lock
inner-daemons May 28, 2026
0c6faab
Fix 3 test failures in wgpu-c-backend vs Metal trunk
inner-daemons May 28, 2026
841688c
Update Cargo.lock: wgpu-native exposes wgpuDevicePollWithTimeout in h…
inner-daemons May 28, 2026
ab26c29
Update wgpuDevicePoll call sites for new timeout_ns parameter
inner-daemons May 28, 2026
ce5cb31
Updated to latest wgpu-native
inner-daemons May 28, 2026
0dfb91b
Small build fix
inner-daemons May 28, 2026
bea4061
Dynamically link wgpu-core and wgpu
inner-daemons May 28, 2026
f15e414
Updated how the test works
inner-daemons May 28, 2026
b6fb18d
Moved wgpu-c-backend out
inner-daemons May 28, 2026
683e98c
Remove unnecessary table
inner-daemons May 28, 2026
c9c6232
Reverted some stuff
inner-daemons May 28, 2026
41dacc3
Fixed doc issue
inner-daemons May 28, 2026
3be1a53
Fixed another warning
inner-daemons May 28, 2026
6992775
Merge branch 'trunk' into c-backend
inner-daemons May 28, 2026
84b3fc7
Fix deny issue
inner-daemons May 28, 2026
89c6006
Fixed some things
inner-daemons May 28, 2026
26744d4
Merge branch 'trunk' into c-backend
inner-daemons May 28, 2026
67606ed
Cleaned some stuff up
inner-daemons May 28, 2026
2ede015
Some human cleanup
inner-daemons May 28, 2026
f7106cf
Undo cargo.lock changes
inner-daemons May 28, 2026
9cb0588
Updated to use atomics to avoid requirement of std
inner-daemons May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deno_webgpu/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ impl GPU {
},
gl: wgpu_types::GlBackendOptions::default(),
noop: wgpu_types::NoopBackendOptions::default(),
// Noop in wgpu-core
skip_custom_backend_library: true,
},
display: None,
},
Expand Down
1 change: 1 addition & 0 deletions tests/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) ->
enable: !cfg!(target_arch = "wasm32"),
..Default::default()
},
skip_custom_backend_library: false,
}
.with_env(),
#[cfg(not(all(
Expand Down
5 changes: 4 additions & 1 deletion tests/tests/wgpu-gpu/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ static DEVICE_LIFETIME_CHECK: GpuTestConfiguration = GpuTestConfiguration::new()
.run_sync(|ctx| {
ctx.instance.poll_all(false);

let pre_report = ctx.instance.generate_report().unwrap();
let pre_report = ctx.instance.generate_report();

let TestingContext {
instance,
Expand All @@ -73,6 +73,9 @@ static DEVICE_LIFETIME_CHECK: GpuTestConfiguration = GpuTestConfiguration::new()
drop(queue);
drop(device);

let Some(pre_report) = pre_report else {
return;
};
let post_report = instance.generate_report().unwrap();

assert_ne!(
Expand Down
5 changes: 4 additions & 1 deletion tests/tests/wgpu-gpu/mem_leaks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ async fn draw_test_with_reports(

use wgpu::util::DeviceExt;

let global_report = ctx.instance.generate_report().unwrap();
// Custom backends may fail here but should still be testable.
let Some(global_report) = ctx.instance.generate_report() else {
return;
};
let report = global_report.hub_report();
assert_eq!(report.devices.num_allocated, 1);
assert_eq!(report.queues.num_allocated, 1);
Expand Down
2 changes: 1 addition & 1 deletion wgpu-info/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub fn main() -> anyhow::Result<()> {
crate::report::GpuReport::from_json(&json).context("Could not parse JSON")?
}
// Generate the report natively
None => crate::report::GpuReport::generate(),
None => crate::report::GpuReport::generate(false),
};

// Setup output writer
Expand Down
3 changes: 2 additions & 1 deletion wgpu-info/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ pub struct GpuReport {
}

impl GpuReport {
pub fn generate() -> Self {
pub fn generate(skip_custom: bool) -> Self {
let instance = wgpu::Instance::new({
let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
desc.backend_options.skip_custom_backend_library = skip_custom;
desc.backend_options.dx12.shader_compiler = Dx12Compiler::StaticDxc;
desc.flags = wgpu::InstanceFlags::debugging();
desc.with_env()
Expand Down
116 changes: 114 additions & 2 deletions wgpu-info/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,116 @@
use std::{fs::File, io::BufWriter};
use std::{collections::BTreeMap, fs::File, io::BufWriter};

/// Prints a pretty diff of 2 json strings
fn unified_diff(label: &str, a: &str, b: &str) -> String {
use std::{fs, process::Command};
let dir = std::env::temp_dir();
let a_path = dir.join(format!("wgpu-info-{label}-custom.json"));
let b_path = dir.join(format!("wgpu-info-{label}-core.json"));
fs::write(&a_path, a).unwrap();
fs::write(&b_path, b).unwrap();
let out = Command::new("diff")
.args(["-u", "--label", "with-custom", "--label", "without-custom"])
.args([&a_path, &b_path])
.output()
.unwrap();
String::from_utf8(out.stdout).unwrap()
}

fn adapter_key(info: &wgpu::AdapterInfo) -> String {
format!("{:?}/{}", info.backend, info.name)
}

// Normalized view of an adapter for comparison purposes.
// Texture format features sorted by format name for deterministic JSON output
// (HashMap iteration order is random).
#[derive(serde::Serialize)]
struct NormalizedAdapter<'a> {
info: &'a wgpu::AdapterInfo,
features: &'a wgpu::Features,
limits: &'a wgpu::Limits,
downlevel_caps: &'a wgpu::DownlevelCapabilities,
texture_format_features: BTreeMap<String, &'a wgpu::TextureFormatFeatures>,
}

fn normalize(dev: &crate::report::AdapterReport) -> NormalizedAdapter<'_> {
let texture_format_features = dev
.texture_format_features
.iter()
.map(|(fmt, feats)| (format!("{fmt:?}"), feats))
.collect();
NormalizedAdapter {
info: &dev.info,
features: &dev.features,
limits: &dev.limits,
downlevel_caps: &dev.downlevel_caps,
texture_format_features,
}
}

fn to_json(value: &impl serde::Serialize) -> String {
serde_json::to_string_pretty(value).unwrap()
}

#[test]
fn custom_backend_matches_wgpu_core() {
let with_custom = crate::report::GpuReport::generate(false);
let without_custom = crate::report::GpuReport::generate(true);

let custom_map: std::collections::HashMap<String, &crate::report::AdapterReport> = with_custom
.devices
.iter()
.map(|d| (adapter_key(&d.info), d))
.collect();

let without_map: std::collections::HashMap<String, &crate::report::AdapterReport> =
without_custom
.devices
.iter()
.map(|d| (adapter_key(&d.info), d))
.collect();

let mut failures: Vec<String> = Vec::new();

// Every custom-backend adapter must exist in wgpu-core and match it.
for custom_dev in &with_custom.devices {
let key = adapter_key(&custom_dev.info);
let Some(core_dev) = without_map.get(&key) else {
failures.push(format!(
"Adapter '{key}' present in custom-backend run but missing from wgpu-core run"
));
continue;
};

let a = to_json(&normalize(custom_dev));
let b = to_json(&normalize(core_dev));

if a != b {
failures.push(format!(
"Adapter '{}' differs:\n{}",
key,
unified_diff(&key.replace('/', "_"), &a, &b)
));
}
}

// Every wgpu-core adapter must also be present in the custom-backend run.
for core_dev in &without_custom.devices {
let key = adapter_key(&core_dev.info);
if !custom_map.contains_key(&key) {
failures.push(format!(
"Adapter '{key}' present in wgpu-core run but missing from custom-backend run"
));
}
}

if !failures.is_empty() {
panic!(
"GpuReport differs for {} adapter(s):\n{}",
failures.len(),
failures.join("\n")
);
}
}

const ENV_VAR_SAVE: &str = "WGPU_INFO_SAVE_GPUCONFIG_REPORT";

Expand All @@ -10,7 +122,7 @@ const ENV_VAR_SAVE: &str = "WGPU_INFO_SAVE_GPUCONFIG_REPORT";
// Needs to be kept in sync with the test in xtask/src/test.rs
#[test]
fn generate_gpuconfig_report() {
let report = crate::report::GpuReport::generate();
let report = crate::report::GpuReport::generate(false);

// If we don't get the env var, just test that we can generate the report, but don't save it
// to avoid a race condition when other tests are reading the file.
Expand Down
9 changes: 9 additions & 0 deletions wgpu-types/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ pub struct BackendOptions {
pub dx12: Dx12BackendOptions,
/// Options for the noop backend, [`Backend::Noop`].
pub noop: NoopBackendOptions,
/// If false and the `custom` feature is enabled for wgpu, and if an override
/// instance factory was setup, wgpu will return a custom instance created from
/// that factory.
///
/// Noop on wgpu-core.
pub skip_custom_backend_library: bool,
}

impl BackendOptions {
Expand All @@ -227,6 +233,8 @@ impl BackendOptions {
gl: GlBackendOptions::from_env_or_default(),
dx12: Dx12BackendOptions::from_env_or_default(),
noop: NoopBackendOptions::from_env_or_default(),
skip_custom_backend_library: crate::env::var("WGPU_NO_CUSTOM_BACKEND").as_deref()
== Some("1"),
}
}

Expand All @@ -239,6 +247,7 @@ impl BackendOptions {
gl: self.gl.with_env(),
dx12: self.dx12.with_env(),
noop: self.noop.with_env(),
skip_custom_backend_library: false,
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions wgpu/src/api/blas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ impl TlasInstance {
pub fn set_blas(&mut self, blas: &Blas) {
self.blas = blas.inner.clone();
}

/// Returns custom implementation of the BLAS referenced by this instance
/// (if the custom backend is active and the BLAS is internally of type T).
#[cfg(custom)]
pub fn blas_as_custom<T: crate::custom::BlasInterface>(&self) -> Option<&T> {
self.blas.as_custom()
}
}

#[derive(Debug)]
Expand Down
19 changes: 19 additions & 0 deletions wgpu/src/api/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,17 @@ impl Device {
pub struct RequestDeviceError {
pub(crate) inner: RequestDeviceErrorKind,
}

impl RequestDeviceError {
/// Construct an error from a custom backend message. This is mainly useful for custom backends.
#[cfg(custom)]
pub fn from_message(message: String) -> Self {
RequestDeviceError {
inner: RequestDeviceErrorKind::Custom(message),
}
}
}

#[derive(Clone, Debug)]
pub(crate) enum RequestDeviceErrorKind {
/// Error from [`wgpu_core`].
Expand All @@ -730,6 +741,10 @@ pub(crate) enum RequestDeviceErrorKind {
/// (This is currently never used by the webgl backend, but it could be.)
#[cfg(webgpu)]
WebGpu(String),

/// Error from a custom backend.
#[cfg(custom)]
Custom(String),
}

static_assertions::assert_impl_all!(RequestDeviceError: Send, Sync);
Expand All @@ -743,6 +758,8 @@ impl fmt::Display for RequestDeviceError {
RequestDeviceErrorKind::WebGpu(error) => {
write!(_f, "{error}")
}
#[cfg(custom)]
RequestDeviceErrorKind::Custom(msg) => write!(_f, "{msg}"),
#[cfg(not(any(webgpu, wgpu_core)))]
_ => unimplemented!("unknown `RequestDeviceErrorKind`"),
}
Expand All @@ -756,6 +773,8 @@ impl error::Error for RequestDeviceError {
RequestDeviceErrorKind::Core(error) => error.source(),
#[cfg(webgpu)]
RequestDeviceErrorKind::WebGpu(_) => None,
#[cfg(custom)]
RequestDeviceErrorKind::Custom(_) => None,
#[cfg(not(any(webgpu, wgpu_core)))]
_ => unimplemented!("unknown `RequestDeviceErrorKind`"),
}
Expand Down
6 changes: 6 additions & 0 deletions wgpu/src/api/external_texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ impl ExternalTexture {
pub fn destroy(&self) {
self.inner.destroy();
}

/// Returns custom implementation of ExternalTexture (if custom backend and is internally T)
#[cfg(custom)]
pub fn as_custom<T: custom::ExternalTextureInterface>(&self) -> Option<&T> {
self.inner.as_custom()
}
}

/// Describes an [`ExternalTexture`].
Expand Down
42 changes: 40 additions & 2 deletions wgpu/src/api/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@ use core::future::Future;

use crate::{dispatch::InstanceInterface, util::Mutex, *};

#[cfg(custom)]
static INSTANCE_FACTORY: core::sync::atomic::AtomicUsize =
core::sync::atomic::AtomicUsize::new(0);

/// Register a factory that can intercept [`Instance::new`] for custom backends.
///
/// The factory receives the [`InstanceDescriptor`] and returns `Ok(Instance)` to
/// take ownership of the request, or `Err(desc)` to fall through to the built-in backend.
///
/// Only the first call takes effect; subsequent calls are ignored.
///
/// This can be safely called from a constructor.
#[cfg(custom)]
pub fn set_instance_factory(f: fn(InstanceDescriptor) -> Result<Instance, InstanceDescriptor>) {
// 0 is the sentinel for "not set"; fn pointers are never null.
let _ = INSTANCE_FACTORY.compare_exchange(
0,
f as usize,
core::sync::atomic::Ordering::Release,
core::sync::atomic::Ordering::Relaxed,
);
}

bitflags::bitflags! {
/// WGSL language extensions.
///
Expand Down Expand Up @@ -59,8 +82,23 @@ impl Instance {
///
/// - If no backend feature for the active target platform is enabled,
/// this method will panic; see [`Instance::enabled_backend_features()`].
#[allow(clippy::allow_attributes, unreachable_code)]
pub fn new(desc: InstanceDescriptor) -> Self {
#[allow(clippy::allow_attributes, unreachable_code, unused_mut)]
pub fn new(mut desc: InstanceDescriptor) -> Self {
#[cfg(custom)]
if !desc.backend_options.skip_custom_backend_library {
let addr = INSTANCE_FACTORY.load(core::sync::atomic::Ordering::Acquire);
if addr != 0 {
// SAFETY: addr was written by set_instance_factory via `f as usize`,
// where f is a valid fn pointer of this exact type.
let factory: fn(InstanceDescriptor) -> Result<Instance, InstanceDescriptor> =
unsafe { core::mem::transmute(addr) };
match factory(desc) {
Ok(inst) => return inst,
Err(returned_desc) => desc = returned_desc,
}
}
}

if Self::enabled_backend_features().is_empty() {
panic!(
"No wgpu backend feature that is implemented for the target platform was enabled. \
Expand Down
2 changes: 1 addition & 1 deletion wgpu/src/api/render_bundle_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl<'a> RenderBundleEncoder<'a> {
#[cfg(webgpu)]
dispatch::DispatchRenderBundleEncoder::WebGPU(b) => b.finish(desc),
#[cfg(custom)]
dispatch::DispatchRenderBundleEncoder::Custom(_) => unimplemented!(),
dispatch::DispatchRenderBundleEncoder::Custom(b) => b.finish_boxed(desc),
};

RenderBundle { inner: bundle }
Expand Down
9 changes: 8 additions & 1 deletion wgpu/src/api/tlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,19 @@ impl Tlas {
unsafe { tlas.context.tlas_as_hal::<A>(tlas) }
}

#[cfg(custom)]
/// Returns custom implementation of Tlas (if custom backend and is internally T)
#[cfg(custom)]
pub fn as_custom<T: crate::custom::TlasInterface>(&self) -> Option<&T> {
self.inner.as_custom()
}

/// Returns the index of the lowest instance that has been modified since the last build.
/// Custom backends use this to perform partial TLAS updates.
#[cfg(custom)]
pub fn lowest_unmodified(&self) -> u32 {
self.lowest_unmodified
}

/// Get a reference to all instances.
pub fn get(&self) -> &[Option<TlasInstance>] {
&self.instances
Expand Down
Loading
Loading