1use std::{f32::consts::PI, fmt::Debug, sync::Arc};
2
3use ambient_core::{
4 asset_cache, async_ecs::async_run, gpu_components, gpu_ecs::{ComponentToGpuSystem, GpuComponentFormat, GpuWorldShaderModuleKey, GpuWorldSyncEvent}, mesh, runtime, transform::get_world_rotation
5};
6use ambient_ecs::{components, query_mut, Debuggable, Entity, EntityId, Resource, SystemGroup, World};
7use ambient_gpu::{
8 mesh_buffer::GpuMesh, shader_module::{BindGroupDesc, Shader, ShaderIdent, ShaderModule}, wgsl_utils::wgsl_interpolate
9};
10use ambient_std::{asset_cache::*, asset_url::AbsAssetUrl, cb, include_file, Cb};
11use derive_more::*;
12use downcast_rs::{impl_downcast, DowncastSync};
13use glam::{uvec4, UVec2, UVec4, Vec3};
14use serde::{Deserialize, Serialize};
15
16pub mod bind_groups;
17mod collect;
18mod culling;
19mod globals;
20pub mod lod;
21pub mod materials;
22mod outlines;
23mod overlay_renderer;
24mod renderer;
25mod shaders;
26mod shadow_renderer;
27pub mod skinning;
28mod target;
29mod transparent_renderer;
30mod tree_renderer;
31use ambient_ecs::{query, Component};
32pub use collect::*;
33pub use culling::*;
34pub use globals::*;
35use materials::pbr_material::PbrMaterialFromUrl;
36pub use materials::*;
37use ordered_float::OrderedFloat;
38pub use outlines::*;
39pub use renderer::*;
40pub use shaders::*;
41pub use shadow_renderer::*;
42pub use target::*;
43pub use transparent_renderer::*;
44pub use tree_renderer::*;
45
46pub const MAX_PRIMITIVE_COUNT: usize = 16;
47
48pub use ambient_ecs::generated::components::core::rendering::{
49 cast_shadows, color, double_sided, fog_color, fog_density, fog_height_falloff, light_ambient, light_diffuse, overlay, pbr_material_from_url, sun, transparency_group
50};
51
52components!("rendering", {
53 @[Debuggable]
54 primitives: Vec<RenderPrimitive>,
55
56 gpu_primitives_mesh: [u32; MAX_PRIMITIVE_COUNT],
58 gpu_primitives_lod: [u32; MAX_PRIMITIVE_COUNT],
59
60 renderer_shader: RendererShaderProducer,
61 material: SharedMaterial,
62 @[Resource]
63 renderer_stats: String,
64});
65gpu_components! {
66 color() => color: GpuComponentFormat::Vec4,
67 gpu_primitives_mesh() => gpu_primitives_mesh: GpuComponentFormat::Mat4,
68 gpu_primitives_lod() => gpu_primitives_lod: GpuComponentFormat::Mat4,
69}
70pub fn init_all_components() {
71 init_components();
72 init_gpu_components();
73 outlines::init_gpu_components();
74 culling::init_gpu_components();
75 lod::init_components();
76 lod::init_gpu_components();
77 skinning::init_components();
78 skinning::init_gpu_components();
79}
80
81pub fn systems() -> SystemGroup {
82 SystemGroup::new(
83 "renderer",
84 vec![
85 query(pbr_material_from_url().changed()).to_system(|q, world, qs, _| {
86 for (id, url) in q.collect_cloned(world, qs) {
87 let url = match AbsAssetUrl::parse(url) {
88 Ok(value) => value,
89 Err(err) => {
90 log::warn!("Failed to parse pbr_material_from_url url: {:?}", err);
91 continue;
92 }
93 };
94 let assets = world.resource(asset_cache()).clone();
95 let async_run = world.resource(async_run()).clone();
96 world.resource(runtime()).spawn(async move {
97 match PbrMaterialFromUrl(url).get(&assets).await {
98 Err(err) => {
99 log::warn!("Failed to load pbr material from url: {:?}", err);
100 }
101 Ok(mat) => {
102 async_run.run(move |world| {
103 world
104 .add_components(
105 id,
106 Entity::new()
107 .with(renderer_shader(), cb(pbr_material::get_pbr_shader))
108 .with(material(), mat.into()),
109 )
110 .ok();
111 });
112 }
113 }
114 });
115 }
116 }),
117 query_mut((primitives(),), (renderer_shader().changed(), material().changed(), mesh().changed())).to_system(
118 |q, world, qs, _| {
119 for (_, (primitives,), (shader, material, mesh)) in q.iter(world, qs) {
120 *primitives =
121 vec![RenderPrimitive { shader: shader.clone(), material: material.clone(), mesh: mesh.clone(), lod: 0 }];
122 }
123 },
124 ),
125 query_mut((gpu_primitives_mesh(), gpu_primitives_lod()), (primitives().changed(),)).to_system(|q, world, qs, _| {
126 for (id, (p_mesh, p_lod), (primitives,)) in q.iter(world, qs) {
127 if primitives.len() > MAX_PRIMITIVE_COUNT {
128 log::warn!("Entity {} has more than {MAX_PRIMITIVE_COUNT} primitives", id);
129 }
130 for (i, p) in primitives.iter().enumerate().take(MAX_PRIMITIVE_COUNT) {
131 p_mesh[i] = p.mesh.index() as u32;
132 p_lod[i] = p.lod as u32;
133 }
134 }
135 }),
136 Box::new(outlines::systems()),
137 ],
138 )
139}
140
141pub fn gpu_world_systems() -> SystemGroup<GpuWorldSyncEvent> {
142 SystemGroup::new(
143 "renderer/gpu_world_update",
144 vec![
145 Box::new(outlines::gpu_world_systems()),
146 Box::new(ComponentToGpuSystem::new(GpuComponentFormat::Vec4, color(), gpu_components::color())),
147 Box::new(ComponentToGpuSystem::new(GpuComponentFormat::Mat4, gpu_primitives_mesh(), gpu_components::gpu_primitives_mesh())),
148 Box::new(ComponentToGpuSystem::new(GpuComponentFormat::Mat4, gpu_primitives_lod(), gpu_components::gpu_primitives_lod())),
149 Box::new(lod::gpu_world_system()),
150 Box::new(skinning::gpu_world_systems()),
151 ],
152 )
153}
154
155pub fn get_active_sun(world: &World, scene: Component<()>) -> Option<EntityId> {
156 query((scene, sun())).iter(world, None).max_by_key(|(_, (_, x))| OrderedFloat(**x)).map(|(id, _)| id)
157}
158pub fn get_sun_light_direction(world: &World, scene: Component<()>) -> Vec3 {
159 get_active_sun(world, scene)
160 .and_then(|sun| get_world_rotation(world, sun).ok())
161 .map(|rot| rot.mul_vec3(Vec3::X))
162 .unwrap_or(default_sun_direction())
163}
164
165#[derive(Clone, Debug)]
166pub struct RenderPrimitive {
167 pub material: SharedMaterial,
168 pub shader: RendererShaderProducer,
169 pub mesh: Arc<GpuMesh>,
170 pub lod: usize,
171}
172pub type PrimitiveIndex = usize;
173pub fn get_gpu_primitive_id(world: &World, id: EntityId, primitive_index: PrimitiveIndex, material_index: u32) -> UVec4 {
174 let loc = world.entity_loc(id).unwrap();
175 uvec4(loc.archetype as u32, loc.index as u32, primitive_index as u32, material_index)
176}
177
178#[repr(C)]
179#[derive(Debug, Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
180pub struct GpuRenderPrimitive {
181 pub mesh: u32,
182 pub lod: u32,
183 pub _padding: UVec2,
184}
185
186#[derive(Clone, Debug, Deref, DerefMut)]
187pub struct SharedMaterial(Arc<dyn Material + 'static>);
188
189impl<T: Material + 'static> From<Arc<T>> for SharedMaterial {
190 fn from(v: Arc<T>) -> Self {
191 Self(v as Arc<dyn Material>)
192 }
193}
194
195impl SharedMaterial {
196 pub fn replace(&mut self, value: impl Material + 'static) {
197 self.0 = Arc::new(value)
198 }
199
200 pub fn new(value: impl Material + 'static) -> Self {
201 Self(Arc::new(value))
202 }
203
204 pub fn borrow_downcast<T: Material>(&self) -> &T {
205 self.0.downcast_ref::<T>().unwrap()
206 }
207}
208
209impl<T> From<T> for SharedMaterial
210where
211 T: Material + 'static,
212{
213 fn from(v: T) -> Self {
214 Self::new(v)
215 }
216}
217
218pub fn get_defs_module() -> Arc<ShaderModule> {
220 let iter = [("PI", PI)].iter();
221 #[cfg(not(target_os = "unknown"))]
222 let iter = iter.map(|(k, v)| format!("const {k}: f32 = {v};\n"));
223 #[cfg(target_os = "unknown")]
224 let iter = iter.map(|(k, v)| format!("const {k}: f32 = {v};\n"));
225
226 let iter = iter.chain([wgsl_interpolate(), include_file!("polyfill.wgsl")]).collect::<String>();
227
228 Arc::new(ShaderModule::new("defs", iter))
229}
230
231pub fn get_mesh_meta_module(bind_group_offset: u32) -> Arc<ShaderModule> {
232 Arc::new(
233 ShaderModule::new("mesh_meta", include_file!("mesh_meta.wgsl"))
234 .with_ident(ShaderIdent::constant("MESH_METADATA_BINDING", bind_group_offset + MESH_METADATA_BINDING))
235 .with_binding_desc(get_mesh_meta_layout(bind_group_offset)),
236 )
237}
238
239pub fn get_mesh_data_module(bind_group_offset: u32) -> Arc<ShaderModule> {
240 Arc::new(
241 ShaderModule::new("mesh_data", include_file!("mesh_data.wgsl"))
242 .with_ident(ShaderIdent::constant("MESH_BASE_BINDING", bind_group_offset + MESH_BASE_BINDING))
243 .with_ident(ShaderIdent::constant("MESH_SKIN_BINDING", bind_group_offset + MESH_SKIN_BINDING))
244 .with_ident(ShaderIdent::constant("SKINS_BINDING", bind_group_offset + SKINS_BINDING))
245 .with_binding_desc(get_mesh_data_layout(bind_group_offset))
246 .with_dependency(get_mesh_meta_module(bind_group_offset)),
247 )
248}
249
250pub fn primitives_layout() -> BindGroupDesc<'static> {
251 BindGroupDesc {
252 entries: vec![wgpu::BindGroupLayoutEntry {
253 binding: 0,
254 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
255 ty: wgpu::BindingType::Buffer {
256 ty: wgpu::BufferBindingType::Storage { read_only: true },
257 has_dynamic_offset: false,
258 min_binding_size: None,
259 },
260 count: None,
261 }],
262 label: PRIMITIVES_BIND_GROUP.into(),
263 }
264}
265
266pub fn get_common_layout() -> BindGroupDesc<'static> {
267 BindGroupDesc {
268 entries: vec![wgpu::BindGroupLayoutEntry {
269 binding: 0,
270 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
271 ty: wgpu::BindingType::Buffer {
272 ty: wgpu::BufferBindingType::Storage { read_only: true },
273 has_dynamic_offset: false,
274 min_binding_size: None,
275 },
276 count: None,
277 }],
278 label: PRIMITIVES_BIND_GROUP.into(),
279 }
280}
281
282pub fn get_common_module(_: &AssetCache) -> Arc<ShaderModule> {
284 Arc::new(
285 ShaderModule::new("renderer_common", include_file!("renderer_common.wgsl"))
286 .with_binding_desc(get_common_layout())
287 .with_dependency(get_mesh_data_module(GLOBALS_BIND_GROUP_SIZE)),
288 )
289}
290
291pub fn get_globals_module(_assets: &AssetCache, shadow_cascades: u32) -> Arc<ShaderModule> {
293 Arc::new(
294 ShaderModule::new("globals", include_file!("globals.wgsl"))
295 .with_ident(ShaderIdent::constant("SHADOW_CASCADES", shadow_cascades))
296 .with_binding_desc(globals_layout()),
297 )
298}
299
300pub fn get_forward_modules(assets: &AssetCache, shadow_cascades: u32) -> Vec<Arc<ShaderModule>> {
301 vec![
302 get_defs_module(),
303 get_mesh_data_module(GLOBALS_BIND_GROUP_SIZE),
304 get_globals_module(assets, shadow_cascades),
305 GpuWorldShaderModuleKey { read_only: true }.get(assets),
306 get_common_module(assets),
307 ]
308}
309
310pub fn get_overlay_modules(assets: &AssetCache, shadow_cascades: u32) -> Vec<Arc<ShaderModule>> {
311 vec![get_defs_module(), get_globals_module(assets, shadow_cascades), get_mesh_data_module(GLOBALS_BIND_GROUP_SIZE)]
312}
313
314pub struct MaterialShader {
315 pub id: String,
316 pub shader: Arc<ShaderModule>,
317}
318
319pub trait Material: Debug + Sync + Send + DowncastSync {
320 fn id(&self) -> &str;
321
322 fn name(&self) -> &str {
323 self.id()
324 }
325
326 fn update(&self, _: &World) {}
327
328 fn bind_group(&self) -> &wgpu::BindGroup;
329
330 fn transparent(&self) -> Option<bool> {
331 None
332 }
333
334 fn double_sided(&self) -> Option<bool> {
335 None
336 }
337 fn depth_write_enabled(&self) -> Option<bool> {
339 None
340 }
341 fn transparency_group(&self) -> Option<i32> {
342 None
343 }
344}
345
346impl_downcast!(sync Material);
347
348#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
349#[repr(u32)]
350pub enum AlphaMode {
351 Opaque,
352 Mask,
353 Blend,
354}
355impl Default for AlphaMode {
356 fn default() -> Self {
357 Self::Opaque
358 }
359}
360
361#[derive(Debug, Clone, Copy)]
362pub enum FSMain {
363 Forward,
364 Shadow,
365 Outline,
366}
367
368pub struct RendererShader {
369 pub id: String,
370 pub shader: Arc<Shader>,
371 pub vs_main: String,
372 pub fs_shadow_main: String,
373 pub fs_forward_main: String,
374 pub fs_outline_main: String,
375 pub transparent: bool,
376 pub double_sided: bool,
377 pub depth_write_enabled: bool,
379 pub transparency_group: i32,
380}
381impl RendererShader {
382 fn get_fs_main_name(&self, main: FSMain) -> &str {
383 match main {
384 FSMain::Forward => &self.fs_forward_main,
385 FSMain::Shadow => &self.fs_shadow_main,
386 FSMain::Outline => &self.fs_outline_main,
387 }
388 }
389}
390impl Debug for RendererShader {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392 f.debug_struct("RendererShader").field("id", &self.id).finish()
393 }
394}
395
396pub type RendererShaderProducer = Cb<dyn Fn(&AssetCache, &RendererConfig) -> Arc<RendererShader> + Sync + Send>;
397
398#[repr(C)]
399#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
400pub struct DrawIndexedIndirect {
401 pub vertex_count: u32,
402 pub instance_count: u32,
403 pub base_index: u32,
404 pub vertex_offset: i32,
405 pub base_instance: u32,
406}
407
408fn is_transparent(world: &World, id: EntityId, material: &SharedMaterial, shader: &RendererShader) -> bool {
409 world.get(id, transparency_group()).is_ok() || material.transparent().unwrap_or(shader.transparent)
410}