1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! `unlit3d` — the high-level rendering API for `unlit_wgpu`.
//!
//! This crate bridges [`unlit_ecs`] and [`unlit_wgpu`]: it provides the
//! component types needed to describe a renderable 3D scene in an ECS world and
//! a [`Renderer`](crate::renderer::Renderer) component that turns that data into GPU draw commands every
//! frame. The two foundational crates stay direct dependencies — their items
//! are reached through their own paths, and the ones most callers need are
//! re-exported by [`prelude`].
//!
//! # Quick start
//!
//! A frame is assembled out of frame sources. [`Renderer`](crate::renderer::Renderer) owns the frame's
//! render target and records the sources it is given; the built-in mesh
//! rendering is one source, [`MeshSource`](crate::mesh_source::MeshSource), and a caller's own pass over the
//! frame is another, with no less privilege. Each source builds its own scene
//! and declares where in the frame it belongs through [`FrameOrder`](crate::source::FrameOrder).
//!
//! The GPU state a frame draws with — the device, the queue and the resource
//! graph — lives in the ECS world as resource components, addressed by a
//! [`RenderContext`](crate::source::RenderContext). [`spawn_context`](crate::source::spawn_context) spawns them and returns their
//! addresses.
//!
//! Each source draws with whatever pipeline families are registered on it. The
//! built-in unlit shader is one: [`MeshSource::register_unlit_family`](crate::mesh_source::MeshSource::register_unlit_family)
//! registers it, and [`MeshSource::register_family`](crate::mesh_source::MeshSource::register_family) registers a caller's own.
//! A family is identified by its key type, the type its entities'
//! [`GpuPipeline`](crate::components::GpuPipeline) components carry.
//!
//! # Input and UI
//!
//! [`input`] is the portable half of input handling: the event types and the
//! behaviour components that react to them, with no windowing library or UI
//! toolkit involved. A caller feeds a frame's events into the [`InputState`](crate::input::InputState)
//! resource, runs [`dispatch_input`](crate::input::dispatch_input) to drive the behaviours, and clears the
//! events once every consumer has read them.
//!
//! [`ui::UiSource`] is a frame source that draws the world's
//! [`ui::UiPanel`] components as an overlay. A UI is a behaviour component, so
//! a frame can hold as many panels as it has entities, and the source only
//! drives the ones it finds. It reads the same [`InputState`](crate::input::InputState) the caller's own
//! behaviours do, and publishes what the UI claimed as an [`InputCapture`](crate::source::InputCapture)
//! resource for game logic to consult.
//!
//! A [`GpuPipeline`](crate::components::GpuPipeline) carries a key, not a compiled pipeline: a concrete
//! pipeline is resolved per key against the frame's render target and the
//! entity's vertex layout. Every renderable entity must carry one.
//!
//! ```
//! use unlit3d::prelude::*;
//! use unlit_wgpu::pipeline::UnlitOptions;
//! use unlit_wgpu::resources::ResourceGraph;
//!
//! let (device, queue) =
//! wgpu::Device::noop(&wgpu::DeviceDescriptor::default());
//! let mut world = LocalWorld::new();
//!
//! // 1. Spawn the frame's GPU context, the built-in mesh source and the
//! // frame driver, and register the built-in unlit family.
//! let ctx = spawn_context(&mut world, device, queue, ResourceGraph::new());
//! let mut mesh_source = MeshSource::new(&world, ctx);
//! mesh_source.register_unlit_family(&world);
//! let key = UnlitPipelineKey::new(UnlitOptions::standard(&mesh_source.device(&world)));
//! let source = spawn_source(&mut world, mesh_source);
//! let renderer = world.spawn((Renderer::new(ctx),));
//!
//! // 2. Allocate geometry and a material through the mesh source.
//! let (mesh, material) = world
//! .with_mut::<Source, _>(source, |source| {
//! let source = source.as_mut::<MeshSource>().unwrap();
//! let positions = [[0.0; 3]; 3];
//! let uvs = [[0.0; 2]; 3];
//! let colors = [[255u8; 4]; 3];
//! let indices = [0u32, 1, 2];
//! let mesh = source.allocate_unlit_mesh(
//! &world,
//! &key,
//! UnlitMeshDesc {
//! positions: &positions,
//! uvs: Some(&uvs),
//! colors: Some(&colors),
//! indices: Some(&indices),
//! ..Default::default()
//! },
//! );
//! let device = source.device(&world);
//! let texture = device.create_texture(&wgpu::TextureDescriptor {
//! label: Some("example::texture"),
//! size: wgpu::Extent3d { width: 256, height: 256, depth_or_array_layers: 1 },
//! mip_level_count: 1,
//! sample_count: 1,
//! dimension: wgpu::TextureDimension::D2,
//! format: wgpu::TextureFormat::Rgba8UnormSrgb,
//! usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
//! view_formats: &[],
//! });
//! // The material reads a view of the texture and a sampler, both of
//! // which the graph keeps for it: only ids cross the boundary.
//! let view = source.register_texture_and_default_view(&world, texture).1;
//! let sampler = source.register_sampler(&world, None);
//! let material = source.allocate_unlit_material(&world, &key, view, sampler);
//! (mesh, material)
//! })
//! .unwrap();
//!
//! // 3. Spawn a renderable entity, carrying the unlit key.
//! world.spawn((
//! Transform::default(),
//! mesh,
//! material.unwrap(),
//! UnlitPipeline::new(key),
//! ));
//!
//! // 4. Bind a render target from the frame's resource graph and render one
//! // frame (uses the noop device, so it produces a valid command buffer
//! // without touching a GPU).
//! let ft = create_render_target(
//! &world.get::<wgpu::Device>(ctx.device).unwrap(),
//! wgpu::TextureFormat::Rgba8UnormSrgb,
//! 1280, 720, 1,
//! );
//! let (color_view, depth_view) = world
//! .with_mut::<Source, _>(source, |source| {
//! let source = source.as_mut::<MeshSource>().unwrap();
//! let color_view = source.register_texture_and_default_view(&world, ft.color).1;
//! let depth_view = MeshSource::graph(&world, ctx)
//! .insert_strong(
//! ft.depth.create_view(&wgpu::TextureViewDescriptor::default()),
//! &[],
//! )
//! .unwrap();
//! (color_view, depth_view)
//! })
//! .unwrap();
//! world
//! .with_mut::<Renderer, _>(renderer, |r| {
//! r.set_render_target(&world, Some(color_view), Some(depth_view), None);
//! r.render(&world);
//! })
//! .unwrap();
//! ```
//!
//! [`unlit_ecs`]: unlit_ecs
//! [`unlit_wgpu`]: unlit_wgpu
/// The types most callers need for typical usage.