Skip to main content

alpha2_engine/
capabilities.rs

1//! What a runtime can do, in one shape shared by the native host and the browser bridge.
2
3use std::collections::BTreeMap;
4
5use alpha2_gpu::CapabilityTier;
6use alpha2_render::Renderer;
7use serde::Serialize;
8
9use crate::PlaybackPolicy;
10
11/// The report's schema name.
12pub const CAPABILITY_SCHEMA: &str = "alpha2-capabilities";
13/// The report's schema version.
14pub const CAPABILITY_VERSION: u32 = 2;
15
16/// Where the engine runs.
17#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
18#[serde(rename_all = "snake_case")]
19pub enum Runtime {
20    /// A native host process.
21    Native,
22    /// A browser page.
23    Browser,
24}
25
26/// What a runtime can do.
27#[derive(Clone, Debug, PartialEq, Serialize)]
28#[serde(rename_all = "camelCase")]
29pub struct CapabilityReport {
30    /// [`CAPABILITY_SCHEMA`].
31    pub schema: &'static str,
32    /// [`CAPABILITY_VERSION`].
33    pub version: u32,
34    /// Where the engine runs.
35    pub runtime: Runtime,
36    /// The GPU it renders with.
37    pub gpu: GpuReport,
38    /// How it handles color.
39    pub color: ColorReport,
40    /// How its real-time playback behaves.
41    pub playback: PlaybackReport,
42    /// Host-specific video decode description, or `null` when the browser page decodes.
43    pub decode: serde_json::Value,
44    /// Containers, codecs and encoders; `null` when only known at export.
45    pub encode: serde_json::Value,
46    /// Named features, each available or not, with why not.
47    pub features: BTreeMap<String, Feature>,
48    /// Host-specific adapter choice under `ALPHA2_GPU_ADAPTER` and what each candidate supports;
49    /// `null` where no choice was made (browser, CPU renderer).
50    pub adapters: serde_json::Value,
51    /// Host-specific machine and process memory; `null` where not read (browser).
52    pub memory: serde_json::Value,
53}
54
55/// The GPU the renderer drives.
56#[derive(Clone, Debug, PartialEq, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct GpuReport {
59    /// The adapter's name.
60    pub adapter: String,
61    /// The graphics backend.
62    pub backend: String,
63    /// The driver's name.
64    pub driver: String,
65    /// The driver's version and details, where the platform gives them.
66    pub driver_info: String,
67    /// The DX12 shader compiler (`FXC`, `DXC <path>`, ...); empty elsewhere.
68    pub shader_compiler: String,
69    /// `a` (an adapter meeting all of WebGPU) or `b` (a downlevel one).
70    pub tier: &'static str,
71    /// Intermediate texture format, independent of tier: `rgba16float` where the adapter
72    /// renders, filters and copies it, else `rgba8unorm`.
73    pub intermediate_format: &'static str,
74    /// The largest 2D texture side.
75    pub max_texture_dimension_2d: u32,
76    /// Whether passes are timed on the GPU.
77    pub timestamp_queries: bool,
78    /// The idle-texture pool's byte budget.
79    pub texture_budget_bytes: u64,
80    /// The adapter's device memory.
81    pub memory: GpuMemoryReport,
82}
83
84/// The device memory the adapter may use.
85#[derive(Clone, Debug, Default, PartialEq, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct GpuMemoryReport {
88    /// The budget the graphics API reports (DXGI local segment, Vulkan `VK_EXT_memory_budget`
89    /// device-local heaps, Metal recommended working set), or `None`. Idle pools use a quarter.
90    pub budget_bytes: Option<u64>,
91}
92
93/// How color is handled.
94#[derive(Clone, Debug, PartialEq, Serialize)]
95#[serde(rename_all = "camelCase")]
96pub struct ColorReport {
97    /// The working space: display-referred linear BT.709, 1.0 at SDR white.
98    pub working_space: &'static str,
99    /// Whether PQ and HLG sources are tone-mapped into it.
100    pub hdr_sources: bool,
101    /// Whether output can be HDR (always false; exports are SDR BT.709).
102    pub hdr_output: bool,
103    /// Whether linear float sources (`OpenEXR`) keep values beyond SDR until compositing, which
104    /// needs a half-float intermediate.
105    pub extended_range_sources: bool,
106}
107
108/// How real-time playback behaves.
109#[derive(Clone, Debug, PartialEq, Serialize)]
110#[serde(rename_all = "camelCase")]
111pub struct PlaybackReport {
112    /// The policies it runs.
113    pub policies: Vec<PlaybackPolicy>,
114    /// The one it runs unless asked otherwise.
115    pub default_policy: PlaybackPolicy,
116    /// Whether sound plays at rates other than 1x (always false).
117    pub audio_at_non_unit_rate: bool,
118}
119
120/// One feature: whether it is available, and why not.
121#[derive(Clone, Debug, PartialEq, Serialize)]
122pub struct Feature {
123    /// Whether the feature works here.
124    pub available: bool,
125    /// Why not, when it does not.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub reason: Option<String>,
128}
129
130impl Feature {
131    /// An available feature.
132    #[must_use]
133    pub const fn available() -> Self {
134        Self {
135            available: true,
136            reason: None,
137        }
138    }
139
140    /// A feature that is not available, and why.
141    #[must_use]
142    pub fn unavailable(reason: impl Into<String>) -> Self {
143        Self {
144            available: false,
145            reason: Some(reason.into()),
146        }
147    }
148}
149
150impl CapabilityReport {
151    /// The report of `renderer` on `runtime`, with decode, encode and features left to the host.
152    #[must_use]
153    pub fn new(runtime: Runtime, renderer: &Renderer) -> Self {
154        let capabilities = renderer.gpu_capabilities();
155        let half = matches!(
156            capabilities.intermediate_format,
157            alpha2_gpu::IntermediateFormat::Rgba16Float
158        );
159        Self {
160            schema: CAPABILITY_SCHEMA,
161            version: CAPABILITY_VERSION,
162            runtime,
163            gpu: GpuReport {
164                adapter: capabilities.adapter_name.clone(),
165                backend: capabilities.backend.clone(),
166                driver: capabilities.driver.clone(),
167                driver_info: capabilities.driver_info.clone(),
168                shader_compiler: capabilities.shader_compiler.clone(),
169                tier: match capabilities.tier {
170                    CapabilityTier::TierA => "a",
171                    CapabilityTier::TierB => "b",
172                    _ => "unknown",
173                },
174                intermediate_format: if half { "rgba16float" } else { "rgba8unorm" },
175                max_texture_dimension_2d: capabilities.max_texture_dimension_2d,
176                timestamp_queries: capabilities.timestamp_queries,
177                texture_budget_bytes: renderer.diagnostics().textures.budget_bytes,
178                memory: GpuMemoryReport {
179                    budget_bytes: renderer.gpu_memory_budget(),
180                },
181            },
182            color: ColorReport {
183                working_space: "linear_bt709_sdr",
184                hdr_sources: true,
185                hdr_output: false,
186                extended_range_sources: half,
187            },
188            playback: PlaybackReport {
189                policies: PlaybackPolicy::ALL.to_vec(),
190                default_policy: PlaybackPolicy::default(),
191                audio_at_non_unit_rate: false,
192            },
193            decode: serde_json::Value::Null,
194            encode: serde_json::Value::Null,
195            features: BTreeMap::new(),
196            adapters: serde_json::Value::Null,
197            memory: serde_json::Value::Null,
198        }
199    }
200
201    /// The report for the CPU renderer on `runtime`: tier A with a half-float intermediate.
202    #[must_use]
203    pub fn cpu(runtime: Runtime, renderer: &alpha2_render::CpuRenderer) -> Self {
204        Self {
205            schema: CAPABILITY_SCHEMA,
206            version: CAPABILITY_VERSION,
207            runtime,
208            gpu: GpuReport {
209                adapter: format!("alpha2 CPU renderer ({} threads)", renderer.threads()),
210                backend: "cpu".to_owned(),
211                driver: "alpha2".to_owned(),
212                driver_info: env!("CARGO_PKG_VERSION").to_owned(),
213                shader_compiler: String::new(),
214                tier: "a",
215                intermediate_format: "rgba16float",
216                max_texture_dimension_2d: alpha2_render::RenderSize::MAX_DIMENSION,
217                timestamp_queries: false,
218                texture_budget_bytes: 0,
219                memory: GpuMemoryReport::default(),
220            },
221            color: ColorReport {
222                working_space: "linear_bt709_sdr",
223                hdr_sources: true,
224                hdr_output: false,
225                extended_range_sources: true,
226            },
227            playback: PlaybackReport {
228                policies: PlaybackPolicy::ALL.to_vec(),
229                default_policy: PlaybackPolicy::default(),
230                audio_at_non_unit_rate: false,
231            },
232            decode: serde_json::Value::Null,
233            encode: serde_json::Value::Null,
234            features: BTreeMap::new(),
235            adapters: serde_json::Value::Null,
236            memory: serde_json::Value::Null,
237        }
238    }
239
240    /// Records a feature.
241    #[must_use]
242    pub fn with_feature(mut self, name: &str, feature: Feature) -> Self {
243        self.features.insert(name.to_owned(), feature);
244        self
245    }
246}