Skip to main content

teksilo_render/
instance.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! The flags every wgpu instance in this workspace is built with.
5//!
6//! Two instances exist: the one [`crate::test_support`] opens for offscreen
7//! work, and the one `teksilo-platform` opens for windows. They have to agree
8//! about this, so the rule lives here, in the crate both can reach, rather
9//! than at either call site.
10
11/// The `InstanceFlags` to build a wgpu instance with: wgpu's own defaults,
12/// read from the environment, minus `VALIDATION_INDIRECT_CALL`.
13///
14/// That flag is set in **release** builds too — it comes from
15/// `InstanceFlags::from_build_config`'s non-debug branch, so it is not a
16/// debug-only cost. It makes `Device::new` build a set of compute and render
17/// pipelines that validate the arguments of indirect draws. This renderer
18/// issues no indirect draws at all — not one `draw_indirect`,
19/// `dispatch_indirect` or `multi_draw_*` anywhere in the workspace — so those
20/// pipelines check nothing we will ever submit.
21///
22/// That alone would only be wasted startup work. The reason the flag is
23/// cleared is that building those pipelines is also a way for device creation
24/// to *fail*, and failing there is not survivable.
25/// `wgpu_core::device::resource::Device::new` creates the hal device, then its
26/// `empty_bgl` — which registers a bind-group layout with the Vulkan backend's
27/// `DescriptorAllocator` — and only then calls `IndirectValidation::new(..)?`.
28/// A driver that cannot build them takes that `?`, and the early return drops
29/// the hal device *without* unregistering `empty_bgl`, because hal objects are
30/// not RAII and need an explicit destroy. `Drop for DescriptorAllocator` then
31/// finds a non-empty bucket and panics — "buckets are not empty, at least one
32/// BGL has not been unregistered" — from an ordinary, non-unwinding drop, so
33/// its own `thread::panicking()` guard does not suppress it.
34///
35/// The process therefore dies *inside* `request_device`, and neither caller
36/// can do anything about it there: a backend search cannot search past a
37/// panic, and an offscreen renderer cannot fall back to a software adapter.
38/// Reported from the field on an older Windows 10 machine where an app never
39/// opened a window, and confirmed there by setting
40/// `WGPU_VALIDATION_INDIRECT_CALL=0`, which let the window open. D3D12 has no
41/// `DescriptorAllocator` and never runs the assertion, which is why forcing
42/// `WGPU_BACKEND=dx12` looked like a graphics fix when it was really a way of
43/// not reaching this code.
44///
45/// `WGPU_VALIDATION_INDIRECT_CALL` is honoured in both directions, so the flag
46/// stays reachable for anyone debugging wgpu itself.
47#[must_use]
48pub fn instance_flags() -> wgpu::InstanceFlags {
49    without_unused_indirect_validation(
50        wgpu::InstanceFlags::from_env_or_default(),
51        std::env::var_os("WGPU_VALIDATION_INDIRECT_CALL").is_some(),
52    )
53}
54
55/// [`instance_flags`]'s decision, as a pure function of its two inputs.
56///
57/// Split out because the alternative is a test that writes a process-wide
58/// environment variable: unsafe since the 2024 edition, and racy against every
59/// other test in the binary regardless.
60///
61/// `asked_for` is whether the variable is *present*, not whether it is true.
62/// wgpu's `with_env` has already read its value into `base` by this point — it
63/// sets the flag for any value but `0`, and clears it for `0` — so all this
64/// has to decide is whether the user expressed an opinion at all. Testing the
65/// resulting bit instead cannot tell "wgpu's default" from "a developer asked
66/// for it", and testing the value again would re-implement `with_env`.
67fn without_unused_indirect_validation(
68    base: wgpu::InstanceFlags,
69    asked_for: bool,
70) -> wgpu::InstanceFlags {
71    if asked_for {
72        return base;
73    }
74    let mut flags = base;
75    flags.remove(wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL);
76    flags
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    const INDIRECT: wgpu::InstanceFlags = wgpu::InstanceFlags::VALIDATION_INDIRECT_CALL;
84
85    #[test]
86    fn an_unset_variable_clears_the_flag_wgpu_defaults_to() {
87        // What a release build gets: `from_build_config` sets the flag and
88        // nobody asked for it.
89        assert!(!without_unused_indirect_validation(INDIRECT, false).contains(INDIRECT));
90    }
91
92    #[test]
93    fn an_explicit_request_is_honoured_in_both_directions() {
94        // `with_env` has already applied the value, so the flag as it arrives
95        // is the answer — set for `WGPU_VALIDATION_INDIRECT_CALL=1`, cleared
96        // for `=0`. Clearing a `=0` again would be right by luck; re-setting a
97        // `=1` would silently ignore the developer.
98        assert!(without_unused_indirect_validation(INDIRECT, true).contains(INDIRECT));
99        assert!(
100            !without_unused_indirect_validation(wgpu::InstanceFlags::empty(), true)
101                .contains(INDIRECT)
102        );
103    }
104
105    #[test]
106    fn no_other_flag_is_touched() {
107        let base = wgpu::InstanceFlags::DEBUG | wgpu::InstanceFlags::VALIDATION | INDIRECT;
108        let got = without_unused_indirect_validation(base, false);
109        assert_eq!(got, base.difference(INDIRECT));
110    }
111}