Skip to main content

frust_shell_common/
surface_mode.rs

1//! HOST-declared translucent-surface slot:
2//! [`declare_host_translucent_surface`]/[`SurfaceModeWatcher::current`], plus
3//! its outward-facing sibling — the RESOLVED slot
4//! ([`publish_resolved_surface_mode`]/[`resolved_surface_mode`]) an app or
5//! plugin reads to learn what the platform actually gave us, including a
6//! refusal (`translucencyRefused`). See `docs/SHELLS_ARCHITECTURE.md`'s
7//! surface-mode resolution flow.
8//!
9//! # The gap this closes
10//!
11//! Platform-view compositing (Mode B) needs the GPU surface itself to be
12//! created with an alpha channel (Android's `EGLConfig`, iOS's
13//! `CAMetalLayer.isOpaque`) so a native sibling view placed *behind* it can
14//! show through wherever frust paints nothing. That surface-format choice
15//! happens once, at surface-creation time, well before any app code runs — so
16//! there is no "widget asks for translucency" moment the way there is for,
17//! say, `set_app_theme`.
18//!
19//! # Callers: host glue only
20//!
21//! [`declare_host_translucent_surface`] is called **only** by
22//! `frust-shell-android::jni_glue`'s `native_set_surface_mode` and
23//! `frust-shell-ios::ffi_glue`'s `set_surface_mode` — the fixed JNI/C-ABI
24//! exports the generated host template's Kotlin/Swift calls during startup,
25//! from the same branch that sets `SurfaceHolder`'s `PixelFormat.TRANSLUCENT` /
26//! `CAMetalLayer.isOpaque = false` on the native window itself and arranges the
27//! native-sibling z-order; each shell's surface-creation path then reads
28//! [`SurfaceModeWatcher::current`] **before** configuring the surface. Setting
29//! the latch is a claim that the window is *already* configured translucent, so
30//! **calling it from anywhere else, or without that configuration in place, is
31//! a host-template bug**: an app opting in from Rust alone with no matching
32//! host window config is a reachable black-rectangle vector, which is why the
33//! call is deliberately not re-exported past `frust-shell-common`.
34//!
35//! # This latch is a HOST DECLARATION, not the outcome
36//!
37//! Declaring it doesn't decide the outcome. Each shell translates this latch
38//! into a `frust_render::SurfaceAlphaRequest`, and `frust-render` resolves
39//! *that* against the platform's advertised `CompositeAlphaMode`s at configure
40//! time — falling back to an opaque swapchain (with a `log::warn!`) when the
41//! platform advertises no translucent mode, and the GPU-tier blit fallback can
42//! further refuse a premultiply-expecting mode it can't reproduce. **The
43//! resolved truth lives at a different seam**:
44//! `frust_render::SurfaceRenderer::surface_resolved_translucent`, read by each
45//! shell after every surface (re)install and threaded into
46//! `RenderRoot::set_surface_translucent` — that seam governs paint.
47//!
48//! So: read this latch to decide what to *request*; never to decide whether to
49//! paint the Mode B contract (a transparent base clear, a `platform_view` hole
50//! punch). Keying paint off the declaration alone (ignoring the resolved seam)
51//! means a fallback clears to `TRANSPARENT` and `DestOut`-punches every slot
52//! rect on an OPAQUE swapchain — black rectangles instead of a graceful degrade
53//! to Mode A.
54//!
55//! # Latch contract (one-way, v1)
56//!
57//! [`declare_host_translucent_surface`] only ever moves the slot from
58//! [`SurfaceMode::Opaque`] to [`SurfaceMode::Translucent`] — there is no
59//! "undo" call, and once observed as `Translucent` it never reverts. The
60//! surface format is fixed at creation (the platform APIs above expose no
61//! supported runtime toggle), so "reverting" would mean destroying and
62//! recreating the whole surface — out of scope for v1, and no current use case
63//! needs it (an app either wants platform-view compositing for the process's
64//! lifetime, or it doesn't). A live flip would have to plumb a full
65//! surface-recreation round-trip through each shell's `SurfacePhase` state
66//! machine (`docs/ARCHITECTURE.md`'s frame pipelines) — not a slot-shape
67//! change.
68//!
69//! One-way applies to the DECLARATION only. The *resolved* state below is not
70//! one-way and is not fixed before the surface exists: every (re)install
71//! re-resolves it, and a failed install clears it — which is exactly why the
72//! shells re-read it per frame rather than caching it at construction.
73//!
74//! # The RESOLVED slot
75//!
76//! Everything above is the *inward* half: what the host declared, read by the
77//! shells to decide what to request. [`publish_resolved_surface_mode`]/
78//! [`resolved_surface_mode`] are the *outward* half — the resolved verdict
79//! ([`ResolvedSurfaceMode`]) travelling back out to app/plugin code, whose
80//! whole reason to exist is
81//! [`ResolvedSurfaceMode::RefusedTranslucent`]: the host declared Mode B and
82//! the platform resolved opaque anyway (its compositor offered no translucent
83//! `CompositeAlphaMode`; see `docs/NATIVE_WIDGETS_ARCHITECTURE.md`'s Mode-B
84//! paragraph). Without it that case is invisible *and* unsignalled — a
85//! native sibling arranged behind a now-opaque frust surface simply vanishes,
86//! with no way for app code to fall back deliberately.
87//!
88//! Publishing is again shell-glue-only (pinned by the same
89//! `crates/frust/tests/surface_mode_conformance.rs` scan): each mobile shell
90//! publishes whatever
91//! `frust_render::SurfaceRenderer::surface_resolved_translucent` reports for
92//! the live surface, on the same UI-thread beat it pushes
93//! `RenderRoot::set_surface_translucent` — seeded at handle construction,
94//! re-published on every later (re)install. Reading is open to anyone; the
95//! `frust` facade re-exports [`resolved_surface_mode`] +
96//! [`ResolvedSurfaceMode`] (never the publisher).
97//!
98//! **Polling contract, not a reactive one.** Nothing here wakes a frame: this
99//! slot is a plain process-global read, exactly like
100//! [`crate::theme_override::theme_override_active`]. App code reads it *during
101//! a rebuild* (or during paint/an event handler) and branches on what it finds;
102//! since both mobile shells run a continuous per-frame loop and re-publish
103//! every frame, a downgrade is observed on the rebuild after the install that
104//! caused it. A value read on a frame where nothing else is dirty does **not**
105//! by itself schedule another frame — pair a fallback decision with an actual
106//! state write (a signal set, a component-state change) if the UI must change
107//! shape because of it.
108//!
109//! # Layering and thread contract
110//!
111//! Same shape as [`crate::theme_override`]/[`crate::system_ui`]: process-global
112//! `Mutex` slots living in `frust-shell-common`, the crate every shell already
113//! polls this kind of state from, each callable from any thread with no
114//! ordering enforced. Unlike those two, neither slot is a per-frame
115//! generation/poll pair, so [`SurfaceModeWatcher`] carries no per-instance
116//! "last seen" cursor — `current` is an associated function, a plain peek.
117//!
118//! In practice [`declare_host_translucent_surface`] must be called before the
119//! shell's surface-creation path reads [`SurfaceModeWatcher::current`]
120//! (startup-time only — see the module docs above), and each shell publishes
121//! the resolved slot from its UI thread (the one owning the `RenderRoot`) while
122//! app code reads it from that same thread during a rebuild. Both orderings are
123//! caller responsibilities, not something this module enforces; the read is a
124//! plain lock-load either way, so an off-thread reader sees a consistent value,
125//! just possibly one frame old.
126
127use std::sync::Mutex;
128
129/// Whether a shell's GPU surface should be created with an alpha channel.
130/// See the module docs' Latch contract — this only ever moves
131/// `Opaque` → `Translucent`, never back.
132#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
133pub enum SurfaceMode {
134    /// Default: an opaque surface, matching every shell's pre-platform-views
135    /// behavior.
136    #[default]
137    Opaque,
138    /// Create the surface with an alpha channel so a native sibling view
139    /// placed behind it can show through unpainted regions.
140    Translucent,
141}
142
143/// The process-wide latch. No generation counter (unlike
144/// [`crate::theme_override`]/[`crate::system_ui`]'s slots) — see the module
145/// docs' Layering and thread contract for why a per-frame "changed since last
146/// poll" concept doesn't apply here.
147static SURFACE_MODE: Mutex<SurfaceMode> = Mutex::new(SurfaceMode::Opaque);
148
149/// Declare that the native host window has already been configured
150/// translucent (`PixelFormat.TRANSLUCENT`/`isOpaque = false`) so this
151/// shell's next GPU surface should be created with an alpha channel too.
152///
153/// **Called only by the generated host glue** (`jni_glue::native_set_surface_mode`
154/// on Android, `ffi_glue::set_surface_mode` on iOS), from the same branch
155/// that actually configured the window — see the module docs' Callers
156/// section. Calling this without that window configuration in place is a
157/// host-template bug, not a supported app-Rust opt-in.
158///
159/// Callable from any thread (see the module docs' Layering and thread
160/// contract), and
161/// idempotent — calling it more than once, or after the surface already
162/// latched translucent, has no additional effect.
163///
164/// Must be called before the running shell's surface-creation path reads
165/// [`SurfaceModeWatcher::current`] (see the module docs) — calling it after
166/// the surface already exists has no effect on that surface.
167///
168/// Declaring translucency does not guarantee the resolved outcome: the
169/// platform may refuse (see the module docs' *This latch is a HOST
170/// DECLARATION, not the outcome*), in which case the app degrades to the
171/// opaque Mode A contract.
172pub fn declare_host_translucent_surface() {
173    let mut slot = SURFACE_MODE.lock().unwrap_or_else(|e| e.into_inner());
174    *slot = SurfaceMode::Translucent;
175}
176
177/// Per-shell-instance reader over the process-wide latch. Kept as a type
178/// (mirroring [`crate::theme_override::ThemeOverrideWatcher`]/
179/// [`crate::system_ui::SystemUiWatcher`]'s shape) even though it carries no
180/// state of its own — [`current`](Self::current) is a plain peek, not a
181/// diffed poll, per the module docs' Layering and thread contract.
182#[derive(Debug, Default)]
183pub struct SurfaceModeWatcher;
184
185impl SurfaceModeWatcher {
186    /// A fresh (stateless) watcher.
187    pub fn new() -> Self {
188        Self
189    }
190
191    /// Read the latch's current value. Not a "since last call" diff — a
192    /// shell's surface-creation path calls this once, at surface-creation
193    /// time, and applies whatever it reads.
194    pub fn current() -> SurfaceMode {
195        *SURFACE_MODE.lock().unwrap_or_else(|e| e.into_inner())
196    }
197}
198
199/// What the platform **actually gave us**, as opposed to what the host
200/// declared ([`SurfaceMode`]) — see the module docs' *The RESOLVED slot*.
201///
202/// Read via [`resolved_surface_mode`] (re-exported by the `frust` facade);
203/// published only by the two mobile shells, once per frame, from the same
204/// value that drives `RenderRoot::set_surface_translucent`.
205#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
206pub enum ResolvedSurfaceMode {
207    /// No shell has published a resolution yet: no surface exists, or the
208    /// running shell doesn't participate (the desktop preview shell never
209    /// publishes — it has no Mode B host seam). **Not** a synonym for
210    /// `Opaque`: app code that must distinguish "opaque" from "don't know yet"
211    /// (e.g. deferring a fallback decision to the next frame) can.
212    #[default]
213    Unknown,
214    /// The live surface came up opaque, and that is what the host asked for —
215    /// the ordinary Mode A contract every pre-platform-views app runs under.
216    Opaque,
217    /// The live surface came up translucent: frust clears to a transparent
218    /// base and `platform_view` punches its slot rects (Mode B).
219    Translucent,
220    /// **The refusal** (`translucencyRefused`): the host declared Mode B, and
221    /// the platform resolved the surface opaque anyway — no matching
222    /// `CompositeAlphaMode` (the engine's premultiplied output has no other
223    /// refusal case; see `docs/NATIVE_WIDGETS_ARCHITECTURE.md`'s Mode-B
224    /// paragraph).
225    ///
226    /// frust's own paint side degrades correctly (opaque clear, no hole
227    /// punch), but the host's native-sibling z-order was fixed at build time:
228    /// a sibling arranged *behind* the now-opaque surface is invisible and
229    /// untappable. That is what this variant exists to tell app/plugin code —
230    /// so it can render its own fallback content instead of a dead rect.
231    RefusedTranslucent,
232}
233
234impl ResolvedSurfaceMode {
235    /// The pure mapping [`publish_resolved_surface_mode`] applies, split out so
236    /// it is testable without touching the process-global slots: a resolved
237    /// translucent surface is [`Translucent`](Self::Translucent); a resolved
238    /// opaque one is [`RefusedTranslucent`](Self::RefusedTranslucent) when the
239    /// host declared translucency and [`Opaque`](Self::Opaque) when it didn't.
240    ///
241    /// Never returns [`Unknown`](Self::Unknown) — that variant means "nobody
242    /// published", which is the slot's initial state, not a resolution.
243    pub const fn resolve(declared: SurfaceMode, resolved_translucent: bool) -> Self {
244        match (declared, resolved_translucent) {
245            (_, true) => Self::Translucent,
246            (SurfaceMode::Translucent, false) => Self::RefusedTranslucent,
247            (SurfaceMode::Opaque, false) => Self::Opaque,
248        }
249    }
250
251    /// `true` only for [`RefusedTranslucent`](Self::RefusedTranslucent) — the
252    /// one-liner a native-widget fallback branches on
253    /// (`if frust::resolved_surface_mode().translucency_refused() { ... }`).
254    pub const fn translucency_refused(self) -> bool {
255        matches!(self, Self::RefusedTranslucent)
256    }
257
258    /// `true` only for [`Translucent`](Self::Translucent), i.e. the frames
259    /// being painted right now really do follow the Mode B contract. Both
260    /// refusal and plain opacity answer `false`; so does
261    /// [`Unknown`](Self::Unknown), since no surface has reported in.
262    pub const fn is_translucent(self) -> bool {
263        matches!(self, Self::Translucent)
264    }
265}
266
267/// The process-wide RESOLVED slot, beside [`SURFACE_MODE`]'s declaration
268/// latch. Unlike that latch this is **not** one-way: every surface (re)install
269/// re-resolves, and a failed install downgrades — see the module docs.
270static RESOLVED_SURFACE_MODE: Mutex<ResolvedSurfaceMode> = Mutex::new(ResolvedSurfaceMode::Unknown);
271
272/// Publish the live surface's **resolved** translucency, mapped against the
273/// host declaration into a [`ResolvedSurfaceMode`] (see
274/// [`ResolvedSurfaceMode::resolve`]).
275///
276/// **Called only by the two mobile shells' `app.rs`** — the UI-thread beat
277/// that already reads the resolved flag and pushes
278/// `RenderRoot::set_surface_translucent` — pinned by
279/// `crates/frust/tests/surface_mode_conformance.rs` the same way
280/// [`declare_host_translucent_surface`] is. `resolved_translucent` is what
281/// `frust_render::SurfaceRenderer::surface_resolved_translucent` reports for
282/// the surface that is live *now*; a failed install reports `false`, which is
283/// the honest answer (nothing to punch a hole in).
284///
285/// Idempotent and re-callable in any direction: publishing the same value
286/// every frame is the expected usage, and a later install may legitimately
287/// move the slot back (`Translucent` → `RefusedTranslucent`, or the reverse
288/// once a re-created surface comes up capable again).
289pub fn publish_resolved_surface_mode(resolved_translucent: bool) {
290    let declared = SurfaceModeWatcher::current();
291    let mut slot = RESOLVED_SURFACE_MODE
292        .lock()
293        .unwrap_or_else(|e| e.into_inner());
294    *slot = ResolvedSurfaceMode::resolve(declared, resolved_translucent);
295}
296
297/// Read the resolved surface mode — [`ResolvedSurfaceMode::Unknown`] until a
298/// shell publishes one (no surface yet, or a shell with no Mode B seam, e.g.
299/// the desktop preview).
300///
301/// A **poll**, not a subscription: reading this never wakes a frame, and a
302/// change here never marks anything dirty on its own. Read it during a rebuild
303/// (the same place `theme_override`-style process-global state is read) and
304/// branch; see the module docs' *The RESOLVED slot* for the full contract.
305pub fn resolved_surface_mode() -> ResolvedSurfaceMode {
306    *RESOLVED_SURFACE_MODE
307        .lock()
308        .unwrap_or_else(|e| e.into_inner())
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use std::sync::Mutex as StdMutex;
315    use std::thread;
316
317    // Serializes every test in this module against the shared process-wide
318    // `SURFACE_MODE` static — mirrors `theme_override`'s `TEST_LOCK` pattern.
319    static TEST_LOCK: StdMutex<()> = StdMutex::new(());
320
321    fn reset_slot() {
322        let mut slot = SURFACE_MODE.lock().unwrap_or_else(|e| e.into_inner());
323        *slot = SurfaceMode::Opaque;
324        let mut resolved = RESOLVED_SURFACE_MODE
325            .lock()
326            .unwrap_or_else(|e| e.into_inner());
327        *resolved = ResolvedSurfaceMode::Unknown;
328    }
329
330    #[test]
331    fn defaults_to_opaque() {
332        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
333        reset_slot();
334        assert_eq!(SurfaceModeWatcher::current(), SurfaceMode::Opaque);
335    }
336
337    /// The default is Opaque
338    /// with no host call at all — same assertion as `defaults_to_opaque`
339    /// above, spelled out explicitly since it's the important
340    /// case (no `declare_host_translucent_surface()` call anywhere in this
341    /// test body).
342    #[test]
343    fn opaque_by_default_with_no_host_declaration() {
344        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
345        reset_slot();
346        assert_eq!(SurfaceModeWatcher::current(), SurfaceMode::Opaque);
347    }
348
349    #[test]
350    fn declaration_latches_translucent() {
351        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
352        reset_slot();
353
354        declare_host_translucent_surface();
355        assert_eq!(SurfaceModeWatcher::current(), SurfaceMode::Translucent);
356    }
357
358    #[test]
359    fn repeated_declarations_are_idempotent() {
360        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
361        reset_slot();
362
363        declare_host_translucent_surface();
364        declare_host_translucent_surface();
365        assert_eq!(SurfaceModeWatcher::current(), SurfaceMode::Translucent);
366    }
367
368    #[test]
369    fn declaration_from_a_spawned_thread_is_observed() {
370        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
371        reset_slot();
372
373        thread::spawn(|| {
374            declare_host_translucent_surface();
375        })
376        .join()
377        .unwrap();
378
379        assert_eq!(SurfaceModeWatcher::current(), SurfaceMode::Translucent);
380    }
381
382    // --- The RESOLVED slot ---------------------------------------------------
383
384    /// Nothing published yet reads `Unknown` — the desktop/host
385    /// case, and every mobile launch before the first surface resolution.
386    #[test]
387    fn resolved_defaults_to_unknown() {
388        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
389        reset_slot();
390
391        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Unknown);
392    }
393
394    /// No host declaration + an opaque resolution is plain `Opaque`, never a
395    /// refusal — a Mode A app must not look like it was refused anything.
396    #[test]
397    fn undeclared_opaque_resolution_is_opaque_not_refused() {
398        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
399        reset_slot();
400
401        publish_resolved_surface_mode(false);
402        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Opaque);
403        assert!(!resolved_surface_mode().translucency_refused());
404    }
405
406    #[test]
407    fn declared_translucent_resolution_is_translucent() {
408        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
409        reset_slot();
410
411        declare_host_translucent_surface();
412        publish_resolved_surface_mode(true);
413
414        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Translucent);
415        assert!(resolved_surface_mode().is_translucent());
416        assert!(!resolved_surface_mode().translucency_refused());
417    }
418
419    /// The whole point of the slot: declared Mode B, resolved opaque (the
420    /// platform offered no translucent `CompositeAlphaMode`).
421    #[test]
422    fn declared_but_opaque_resolution_is_refused() {
423        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
424        reset_slot();
425
426        declare_host_translucent_surface();
427        publish_resolved_surface_mode(false);
428
429        assert_eq!(
430            resolved_surface_mode(),
431            ResolvedSurfaceMode::RefusedTranslucent
432        );
433        assert!(resolved_surface_mode().translucency_refused());
434        assert!(!resolved_surface_mode().is_translucent());
435    }
436
437    /// Unlike the declaration latch, the resolved slot is **not** one-way: a
438    /// later (re)install may downgrade it, and a recovered surface may take it
439    /// back up. Both directions must land.
440    #[test]
441    fn resolved_slot_moves_in_both_directions() {
442        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
443        reset_slot();
444
445        declare_host_translucent_surface();
446
447        publish_resolved_surface_mode(true);
448        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Translucent);
449
450        // A failed/incapable reinstall downgrades …
451        publish_resolved_surface_mode(false);
452        assert_eq!(
453            resolved_surface_mode(),
454            ResolvedSurfaceMode::RefusedTranslucent
455        );
456
457        // … and a recreated, capable surface takes it back up.
458        publish_resolved_surface_mode(true);
459        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Translucent);
460    }
461
462    /// Re-publishing the same value every frame (the shells' actual usage) is
463    /// a no-op beyond the store.
464    #[test]
465    fn repeated_publishes_are_idempotent() {
466        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
467        reset_slot();
468
469        publish_resolved_surface_mode(true);
470        publish_resolved_surface_mode(true);
471        publish_resolved_surface_mode(true);
472
473        assert_eq!(resolved_surface_mode(), ResolvedSurfaceMode::Translucent);
474    }
475
476    #[test]
477    fn publish_from_a_spawned_thread_is_observed() {
478        let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
479        reset_slot();
480        declare_host_translucent_surface();
481
482        thread::spawn(|| {
483            publish_resolved_surface_mode(false);
484        })
485        .join()
486        .unwrap();
487
488        assert_eq!(
489            resolved_surface_mode(),
490            ResolvedSurfaceMode::RefusedTranslucent
491        );
492    }
493
494    /// The pure mapping, exhaustively — no globals touched, so this one needs
495    /// no `TEST_LOCK`.
496    #[test]
497    fn resolve_maps_every_declaration_resolution_pair() {
498        assert_eq!(
499            ResolvedSurfaceMode::resolve(SurfaceMode::Opaque, false),
500            ResolvedSurfaceMode::Opaque
501        );
502        assert_eq!(
503            ResolvedSurfaceMode::resolve(SurfaceMode::Opaque, true),
504            ResolvedSurfaceMode::Translucent
505        );
506        assert_eq!(
507            ResolvedSurfaceMode::resolve(SurfaceMode::Translucent, true),
508            ResolvedSurfaceMode::Translucent
509        );
510        assert_eq!(
511            ResolvedSurfaceMode::resolve(SurfaceMode::Translucent, false),
512            ResolvedSurfaceMode::RefusedTranslucent
513        );
514    }
515
516    /// `Unknown` is the default and is never produced by a resolution — a
517    /// published value always says something definite.
518    #[test]
519    fn unknown_is_the_default_and_never_a_resolution() {
520        assert_eq!(ResolvedSurfaceMode::default(), ResolvedSurfaceMode::Unknown);
521        for declared in [SurfaceMode::Opaque, SurfaceMode::Translucent] {
522            for resolved in [false, true] {
523                assert_ne!(
524                    ResolvedSurfaceMode::resolve(declared, resolved),
525                    ResolvedSurfaceMode::Unknown
526                );
527            }
528        }
529        assert!(!ResolvedSurfaceMode::Unknown.is_translucent());
530        assert!(!ResolvedSurfaceMode::Unknown.translucency_refused());
531    }
532}