Skip to main content

alpha2_engine/
capabilities.rs

1//! What a runtime can do, reported in one shape by every host: the native
2//! host's `capabilities` result and the browser bridge's `capabilities`.
3
4use std::collections::BTreeMap;
5
6use alpha2_gpu::CapabilityTier;
7use alpha2_render::Renderer;
8use serde::Serialize;
9
10use crate::PlaybackPolicy;
11
12/// The report's schema name.
13pub const CAPABILITY_SCHEMA: &str = "alpha2-capabilities";
14/// The report's schema version.
15pub const CAPABILITY_VERSION: u32 = 1;
16
17/// Where the engine runs.
18#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
19#[serde(rename_all = "snake_case")]
20pub enum Runtime {
21    /// A native host process.
22    Native,
23    /// A browser page.
24    Browser,
25}
26
27/// What a runtime can do.
28#[derive(Clone, Debug, PartialEq, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct CapabilityReport {
31    /// [`CAPABILITY_SCHEMA`].
32    pub schema: &'static str,
33    /// [`CAPABILITY_VERSION`].
34    pub version: u32,
35    /// Where the engine runs.
36    pub runtime: Runtime,
37    /// The GPU it renders with.
38    pub gpu: GpuReport,
39    /// How it handles color.
40    pub color: ColorReport,
41    /// How its real-time playback behaves.
42    pub playback: PlaybackReport,
43    /// How the runtime decodes video: a host-specific description, or
44    /// `null` when decoding is not the runtime's (the browser's page does
45    /// it).
46    pub decode: serde_json::Value,
47    /// What the runtime encodes: container, codecs, encoders; `null` when
48    /// it is only known at export.
49    pub encode: serde_json::Value,
50    /// Named features, each available or not, with why not.
51    pub features: BTreeMap<String, Feature>,
52}
53
54/// The GPU the renderer drives.
55#[derive(Clone, Debug, PartialEq, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct GpuReport {
58    /// The adapter's name.
59    pub adapter: String,
60    /// The graphics backend.
61    pub backend: String,
62    /// The driver's name.
63    pub driver: String,
64    /// The driver's version and details, where the platform gives them.
65    pub driver_info: String,
66    /// `a` (an adapter meeting all of WebGPU) or `b` (a downlevel one).
67    pub tier: &'static str,
68    /// The intermediate texture format every picture is stored in, decided
69    /// apart from the tier: `rgba16float` wherever the adapter renders,
70    /// filters, and copies it, `rgba8unorm` elsewhere.
71    pub intermediate_format: &'static str,
72    /// The largest 2D texture side.
73    pub max_texture_dimension_2d: u32,
74    /// Whether passes are timed on the GPU.
75    pub timestamp_queries: bool,
76    /// The idle-texture pool's byte budget.
77    pub texture_budget_bytes: u64,
78}
79
80/// How color is handled.
81#[derive(Clone, Debug, PartialEq, Serialize)]
82#[serde(rename_all = "camelCase")]
83pub struct ColorReport {
84    /// The working space: display-referred linear BT.709, 1.0 at SDR white.
85    pub working_space: &'static str,
86    /// Whether PQ and HLG sources are tone-mapped into it.
87    pub hdr_sources: bool,
88    /// Whether output can be HDR: always false, as exports are SDR BT.709.
89    pub hdr_output: bool,
90    /// Whether linear-light float sources (OpenEXR) keep values beyond SDR
91    /// until compositing: only with a half-float intermediate.
92    pub extended_range_sources: bool,
93}
94
95/// How real-time playback behaves.
96#[derive(Clone, Debug, PartialEq, Serialize)]
97#[serde(rename_all = "camelCase")]
98pub struct PlaybackReport {
99    /// The policies it runs.
100    pub policies: Vec<PlaybackPolicy>,
101    /// The one it runs unless asked otherwise.
102    pub default_policy: PlaybackPolicy,
103    /// Whether sound plays at any rate other than 1x: always false.
104    pub audio_at_non_unit_rate: bool,
105}
106
107/// One feature: whether it is available, and why not.
108#[derive(Clone, Debug, PartialEq, Serialize)]
109pub struct Feature {
110    /// Whether the feature works here.
111    pub available: bool,
112    /// Why not, when it does not.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub reason: Option<String>,
115}
116
117impl Feature {
118    /// An available feature.
119    #[must_use]
120    pub const fn available() -> Self {
121        Self {
122            available: true,
123            reason: None,
124        }
125    }
126
127    /// A feature that is not available, and why.
128    #[must_use]
129    pub fn unavailable(reason: impl Into<String>) -> Self {
130        Self {
131            available: false,
132            reason: Some(reason.into()),
133        }
134    }
135}
136
137impl CapabilityReport {
138    /// The report of `renderer` running on `runtime`, with no decode, encode,
139    /// or features yet: the host adds its own.
140    #[must_use]
141    pub fn new(runtime: Runtime, renderer: &Renderer) -> Self {
142        let capabilities = renderer.gpu_capabilities();
143        let half = matches!(
144            capabilities.intermediate_format,
145            alpha2_gpu::IntermediateFormat::Rgba16Float
146        );
147        Self {
148            schema: CAPABILITY_SCHEMA,
149            version: CAPABILITY_VERSION,
150            runtime,
151            gpu: GpuReport {
152                adapter: capabilities.adapter_name.clone(),
153                backend: capabilities.backend.clone(),
154                driver: capabilities.driver.clone(),
155                driver_info: capabilities.driver_info.clone(),
156                tier: match capabilities.tier {
157                    CapabilityTier::TierA => "a",
158                    CapabilityTier::TierB => "b",
159                    _ => "unknown",
160                },
161                intermediate_format: if half { "rgba16float" } else { "rgba8unorm" },
162                max_texture_dimension_2d: capabilities.max_texture_dimension_2d,
163                timestamp_queries: capabilities.timestamp_queries,
164                texture_budget_bytes: renderer.diagnostics().textures.budget_bytes,
165            },
166            color: ColorReport {
167                working_space: "linear_bt709_sdr",
168                hdr_sources: true,
169                hdr_output: false,
170                extended_range_sources: half,
171            },
172            playback: PlaybackReport {
173                policies: PlaybackPolicy::ALL.to_vec(),
174                default_policy: PlaybackPolicy::default(),
175                audio_at_non_unit_rate: false,
176            },
177            decode: serde_json::Value::Null,
178            encode: serde_json::Value::Null,
179            features: BTreeMap::new(),
180        }
181    }
182
183    /// Records a feature.
184    #[must_use]
185    pub fn with_feature(mut self, name: &str, feature: Feature) -> Self {
186        self.features.insert(name.to_owned(), feature);
187        self
188    }
189}