Skip to content

Commit 50f5f74

Browse files
author
Roy Lin
committed
feat: integrate pinned graphics reference renderer
1 parent 56c6d26 commit 50f5f74

8 files changed

Lines changed: 231 additions & 12 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ name = "winui_dogfood"
8888
required-features = ["winui-native"]
8989

9090
[features]
91-
default = ["headless", "authoring", "design-system"]
91+
default = ["headless", "authoring", "design-system", "software-reference"]
9292
headless = []
93+
graphics = ["dep:a3s-graphics"]
94+
software-reference = ["graphics", "a3s-graphics/software"]
9395
authoring = [
9496
"dep:swc_atoms",
9597
"dep:swc_common",
@@ -117,6 +119,7 @@ gtk4 = []
117119
gtk4-native = ["gtk4", "dep:gtk4_crate"]
118120

119121
[dependencies]
122+
a3s-graphics = { git = "https://github.com/A3S-Lab/Graphics", rev = "2cad948189eec816d38f0df01ea38993f92118a5", optional = true, default-features = false }
120123
fixed_decimal = { version = "0.7.2", features = ["ryu"] }
121124
icu_collator = "2.2.1"
122125
icu_datetime = "2.2.0"

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ describe semantic UI in Rust function components or `.rsx` modules, update
1717
state through reducers, and lower the same portable native IR into layout,
1818
paint, interaction, and accessibility records. Application pixels are moving
1919
to the shared [`a3s-graphics`](https://github.com/A3S-Lab/Graphics) engine,
20-
whose production path uses `wgpu` directly for Metal, Direct3D 12, and Vulkan.
20+
whose production GPU path is being built on `wgpu` directly for Metal,
21+
Direct3D 12, and Vulkan.
2122

2223
The current AppKit, GTK4, WinUI, and deterministic headless hosts remain as the
2324
migration baseline. New rendering work targets the self-drawn Graphics path;
@@ -62,9 +63,10 @@ The crate is currently consumed from its Git repository:
6263
a3s-gui = { git = "https://github.com/A3S-Lab/GUI" }
6364
```
6465

65-
The default feature set includes the headless runtime, RSX authoring, and the
66-
built-in design system. During the renderer migration, the matching legacy
67-
native feature still opens the platform-control baseline:
66+
The default feature set includes the headless runtime, RSX authoring, the
67+
built-in design system, and the deterministic Graphics software reference.
68+
During the renderer migration, the matching legacy native feature still opens
69+
the platform-control baseline:
6870

6971
```toml
7072
# macOS
@@ -126,6 +128,8 @@ just playground
126128
| Feature | Purpose |
127129
| --- | --- |
128130
| `headless` | Deterministic runtime and host behavior without an OS GUI |
131+
| `graphics` | Pinned A3S Graphics scene vocabulary without a renderer backend |
132+
| `software-reference` | Deterministic retained reference renderer; implies `graphics` |
129133
| `authoring` | SWC-backed RSX parsing, `ComponentCx`, and explicit component registries |
130134
| `design-system` | Built-in `rsx_ui` registry; implies `authoring` |
131135
| `appkit`, `gtk4`, `winui` | Legacy planning adapters retained for migration evidence |
@@ -138,6 +142,8 @@ Runtime and protocol consumers can exclude the authoring stack:
138142
```sh
139143
cargo check --locked --no-default-features --lib
140144
cargo check --locked --no-default-features --features authoring --lib
145+
cargo check --locked --no-default-features --features graphics --lib
146+
cargo check --locked --no-default-features --features software-reference --lib
141147
```
142148

143149
## Runtime Architecture

docs/architecture.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,17 @@ lowering pipeline.
142142

143143
## Dependency Direction
144144

145-
Cargo features enforce the authoring/runtime split inside the current crate.
146-
`authoring` enables SWC-backed RSX parsing and `ComponentCx`; `design-system`
147-
depends on `authoring` and enables the built-in `rsx_ui` registry. The default
148-
feature set keeps the existing authoring experience, while
149-
`cargo check --no-default-features --lib` proves that the runtime, protocol,
150-
semantic mapper, renderer, and planning core compile without SWC or `rsx_ui`.
145+
Cargo features enforce both the authoring/runtime split and the graphics
146+
backend boundary inside the current crate. `authoring` enables SWC-backed RSX
147+
parsing and `ComponentCx`; `design-system` depends on `authoring` and enables
148+
the built-in `rsx_ui` registry. `graphics` enables only the pinned engine scene
149+
contract, while `software-reference` adds deterministic retained
150+
rasterization. The GPU feature is introduced only when the M2 backend is real;
151+
there is no placeholder backend. The default feature set keeps the existing
152+
authoring experience plus software reference evidence, while
153+
`cargo check --no-default-features --lib` proves that protocol, semantic,
154+
interaction, focus, selection, i18n, and accessibility remain usable without
155+
Graphics, SWC, or `rsx_ui`.
151156

152157
The remaining dependencies stay one-way:
153158

docs/roadmap.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ another catch-all file.
198198

199199
### M0 - Graphics boundary and deterministic core
200200

201-
Status: Graphics foundation pushed; GUI integration pending.
201+
Status: complete at Graphics commit `2cad948` and the pinned GUI boundary.
202202

203203
Deliverables:
204204

@@ -221,6 +221,22 @@ Acceptance gates:
221221

222222
Status: current.
223223

224+
Landed evidence:
225+
226+
- architecture and public docs now select A3S Graphics with no framework-owned
227+
renderer residue
228+
- the engine dependency is pinned to full commit
229+
`2cad948189eec816d38f0df01ea38993f92118a5`
230+
- `graphics` and `software-reference` separate scene consumers from reference
231+
rasterization while no-default remains semantic-only
232+
- `ReferenceRenderer` preserves frame fingerprints and retained damage behind a
233+
GUI-owned error boundary
234+
- the first compatibility cleanup removed class-name widget mapping shims and
235+
replaced broad dead-code allowances with target-accurate compilation
236+
237+
Remaining work is the field inventory, dependency-direction automation, and
238+
the first `NativeElement` layout-to-scene adapter.
239+
224240
Deliverables:
225241

226242
- replace obsolete architecture, README, and roadmap claims with this boundary

src/drawing.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! Boundary between GUI scene production and the standalone graphics engine.
2+
//!
3+
//! This module exposes the engine's platform-neutral scene vocabulary without
4+
//! leaking `wgpu` or window-system types into GUI semantics. The optional
5+
//! software reference renderer is deterministic and intended for tests,
6+
//! snapshots, and GPU parity checks.
7+
8+
pub use a3s_graphics::{
9+
Affine, Color, CornerRadii, Damage, DrawCommand, DrawId, EdgeWidths, FillRect, FillRoundedRect,
10+
Primitive, Rect, Scene, SceneBuilder, Size, StrokeRect, SCENE_SCHEMA_VERSION,
11+
};
12+
13+
#[cfg(feature = "software-reference")]
14+
use a3s_graphics::{FramePlanner, SoftwareRenderer};
15+
16+
#[cfg(feature = "software-reference")]
17+
use crate::GuiResult;
18+
19+
/// Metadata and pixels produced by one deterministic reference frame.
20+
#[cfg(feature = "software-reference")]
21+
#[derive(Debug, Clone, Copy, PartialEq)]
22+
pub struct ReferenceFrame<'a> {
23+
index: u64,
24+
fingerprint: u64,
25+
damage: Damage,
26+
width: u32,
27+
height: u32,
28+
rgba8: &'a [u8],
29+
}
30+
31+
#[cfg(feature = "software-reference")]
32+
impl<'a> ReferenceFrame<'a> {
33+
pub const fn index(self) -> u64 {
34+
self.index
35+
}
36+
37+
pub const fn fingerprint(self) -> u64 {
38+
self.fingerprint
39+
}
40+
41+
pub const fn damage(self) -> Damage {
42+
self.damage
43+
}
44+
45+
pub const fn width(self) -> u32 {
46+
self.width
47+
}
48+
49+
pub const fn height(self) -> u32 {
50+
self.height
51+
}
52+
53+
pub const fn rgba8(self) -> &'a [u8] {
54+
self.rgba8
55+
}
56+
}
57+
58+
/// Stateful reference renderer used to verify GUI scene extraction.
59+
///
60+
/// Stable draw IDs let the internal planner calculate retained damage across
61+
/// calls. Use [`Self::reset`] when the associated surface or document is
62+
/// discarded.
63+
#[cfg(feature = "software-reference")]
64+
#[derive(Debug, Default)]
65+
pub struct ReferenceRenderer {
66+
planner: FramePlanner,
67+
renderer: SoftwareRenderer,
68+
}
69+
70+
#[cfg(feature = "software-reference")]
71+
impl ReferenceRenderer {
72+
pub fn new() -> Self {
73+
Self::default()
74+
}
75+
76+
pub fn render(&mut self, scene: Scene) -> GuiResult<ReferenceFrame<'_>> {
77+
let frame = self.planner.plan(scene)?;
78+
let index = frame.index;
79+
let fingerprint = frame.fingerprint;
80+
let damage = frame.damage;
81+
let pixels = self.renderer.render(&frame)?;
82+
Ok(ReferenceFrame {
83+
index,
84+
fingerprint,
85+
damage,
86+
width: pixels.width(),
87+
height: pixels.height(),
88+
rgba8: pixels.as_rgba8(),
89+
})
90+
}
91+
92+
pub fn reset(&mut self) {
93+
self.planner.reset();
94+
self.renderer = SoftwareRenderer::new();
95+
}
96+
}
97+
98+
#[cfg(all(test, feature = "software-reference"))]
99+
mod tests {
100+
use super::*;
101+
use crate::GuiError;
102+
103+
fn scene() -> Scene {
104+
let mut builder = SceneBuilder::new(Size::new(4.0, 3.0), 1.0, Color::WHITE);
105+
builder
106+
.push(DrawCommand::new(
107+
DrawId::new(1).unwrap(),
108+
Primitive::FillRect(FillRect {
109+
rect: Rect::new(1.0, 1.0, 2.0, 1.0),
110+
color: Color::BLACK,
111+
}),
112+
))
113+
.unwrap();
114+
builder.finish().unwrap()
115+
}
116+
117+
#[test]
118+
fn reference_renderer_retains_identical_scenes() {
119+
let mut renderer = ReferenceRenderer::new();
120+
let first = renderer.render(scene()).unwrap();
121+
assert_eq!((first.width(), first.height()), (4, 3));
122+
assert!(first.damage().full_repaint);
123+
let fingerprint = first.fingerprint();
124+
let pixels = first.rgba8().to_vec();
125+
126+
let second = renderer.render(scene()).unwrap();
127+
assert_eq!(second.index(), 1);
128+
assert_eq!(second.fingerprint(), fingerprint);
129+
assert!(!second.damage().requires_render());
130+
assert_eq!(second.rgba8(), pixels);
131+
}
132+
133+
#[test]
134+
fn reset_forces_a_full_reference_frame_without_rewinding_diagnostics() {
135+
let mut renderer = ReferenceRenderer::new();
136+
renderer.render(scene()).unwrap();
137+
renderer.reset();
138+
139+
let frame = renderer.render(scene()).unwrap();
140+
assert_eq!(frame.index(), 1);
141+
assert!(frame.damage().full_repaint);
142+
}
143+
144+
#[test]
145+
fn graphics_validation_errors_keep_the_gui_boundary() {
146+
let mut renderer = ReferenceRenderer::new();
147+
let invalid = Scene::new(Size::new(0.0, 3.0), 1.0, Color::WHITE);
148+
149+
let error = renderer.render(invalid).unwrap_err();
150+
assert!(matches!(error, GuiError::Graphics { .. }));
151+
assert!(error.to_string().contains("logical surface size"));
152+
}
153+
154+
#[test]
155+
fn reference_renderer_is_send_and_sync() {
156+
fn assert_send_sync<T: Send + Sync>() {}
157+
assert_send_sync::<ReferenceRenderer>();
158+
}
159+
}

src/error.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub enum GuiError {
2222
Internationalization {
2323
message: String,
2424
},
25+
Graphics {
26+
message: String,
27+
},
2528
Host {
2629
message: String,
2730
},
@@ -47,6 +50,7 @@ impl Display for GuiError {
4750
GuiError::Internationalization { message } => {
4851
write!(f, "internationalization error: {message}")
4952
}
53+
GuiError::Graphics { message } => write!(f, "graphics error: {message}"),
5054
GuiError::Host { message } => write!(f, "native host error: {message}"),
5155
}
5256
}
@@ -72,4 +76,17 @@ impl GuiError {
7276
message: message.into(),
7377
}
7478
}
79+
80+
pub fn graphics(message: impl Into<String>) -> Self {
81+
GuiError::Graphics {
82+
message: message.into(),
83+
}
84+
}
85+
}
86+
87+
#[cfg(feature = "graphics")]
88+
impl From<a3s_graphics::GraphicsError> for GuiError {
89+
fn from(error: a3s_graphics::GraphicsError) -> Self {
90+
Self::graphics(error.to_string())
91+
}
7592
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub mod compiler;
2020
mod css_text;
2121
#[cfg(feature = "authoring")]
2222
mod default_components;
23+
#[cfg(feature = "graphics")]
24+
pub mod drawing;
2325
pub mod effect;
2426
pub mod error;
2527
pub mod event;
@@ -101,6 +103,8 @@ pub use compiler::{
101103
CompiledBinding, CompiledBindingSource, CompiledProps, CompiledRsxNode, ComponentClassVariants,
102104
RsxCompilerBridge,
103105
};
106+
#[cfg(feature = "software-reference")]
107+
pub use drawing::{ReferenceFrame, ReferenceRenderer};
104108
pub use effect::{
105109
Effect, EffectCancellation, EffectCompletion, EffectExecutor, EffectId, EffectRuntime,
106110
EffectWaker, EffectWorker, ThreadEffectExecutor, DEFAULT_EFFECT_IN_FLIGHT_LIMIT,

0 commit comments

Comments
 (0)