1use khora_core::math::Mat4;
28use khora_core::renderer::api::command::BindGroupLayoutId;
29use khora_core::renderer::api::pipeline::{
30 LayoutKey, LayoutSpec, PipelineSpec, RenderPipelineId, ShaderVariantKey,
31};
32use khora_core::renderer::api::util::dynamic_uniform_buffer::DynamicUniformRingBuffer;
33use khora_core::renderer::api::util::uniform_ring_buffer::UniformRingBuffer;
34use khora_core::renderer::{
35 api::{
36 command::{
37 LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
38 RenderPassDescriptor, StoreOp,
39 },
40 core::RenderContext,
41 pipeline::enums::PrimitiveTopology,
42 scene::{
43 DirectionalLightUniform, GpuMesh, LightingUniforms, ModelUniforms, PointLightUniform,
44 SpotLightUniform, MAX_DIRECTIONAL_LIGHTS, MAX_POINT_LIGHTS, MAX_SPOT_LIGHTS,
45 },
46 },
47 traits::CommandEncoder,
48};
49use khora_data::assets::Assets;
50use khora_data::render::RenderWorld;
51use std::sync::{Mutex, OnceLock, RwLock};
52
53#[derive(Default)]
60pub struct StandardPbrLane {
61 pipeline: OnceLock<RenderPipelineId>,
62 pipeline_system: OnceLock<std::sync::Arc<dyn khora_core::renderer::traits::PipelineSystem>>,
65 camera_layout: OnceLock<BindGroupLayoutId>,
66 model_layout: OnceLock<BindGroupLayoutId>,
67 material_layout: OnceLock<BindGroupLayoutId>,
68 light_layout: OnceLock<BindGroupLayoutId>,
70 lighting_buffer_layout: OnceLock<BindGroupLayoutId>,
72 camera_ring: Mutex<Option<UniformRingBuffer>>,
73 lighting_ring: Mutex<Option<UniformRingBuffer>>,
74 model_ring: Mutex<Option<DynamicUniformRingBuffer>>,
77}
78
79fn pipeline_spec(
87 device: &dyn khora_core::renderer::GraphicsDevice,
88 variant: ShaderVariantKey,
89 double_sided: bool,
90 blend: bool,
91) -> PipelineSpec {
92 use khora_core::renderer::api::pipeline::enums::{
93 CompareFunction, CullMode, VertexFormat, VertexStepMode,
94 };
95 use khora_core::renderer::api::pipeline::state::{
96 BlendStateDescriptor, ColorWrites, DepthBiasState, StencilFaceState,
97 };
98 use khora_core::renderer::api::pipeline::{
99 ColorTargetStateDescriptor, DepthStencilStateDescriptor, MultisampleStateDescriptor,
100 PrimitiveStateDescriptor, VertexAttributeDescriptor, VertexBufferLayoutDescriptor,
101 };
102 use khora_core::renderer::api::util::{SampleCount, TextureFormat};
103 use std::borrow::Cow;
104
105 PipelineSpec {
106 label: "StandardPbr Pipeline",
107 shader: "khora::pipelines::standard_pbr",
108 variant,
109 bind_group_layouts: vec![
110 LayoutSpec::Named(LayoutKey::Camera),
111 LayoutSpec::Named(LayoutKey::Model),
112 LayoutSpec::Named(LayoutKey::Material),
113 LayoutSpec::Named(LayoutKey::Lighting),
114 ],
115 vertex_buffers: vec![VertexBufferLayoutDescriptor {
116 array_stride: 32,
117 step_mode: VertexStepMode::Vertex,
118 attributes: Cow::Owned(vec![
119 VertexAttributeDescriptor {
120 format: VertexFormat::Float32x3,
121 offset: 0,
122 shader_location: 0,
123 },
124 VertexAttributeDescriptor {
125 format: VertexFormat::Float32x3,
126 offset: 12,
127 shader_location: 1,
128 },
129 VertexAttributeDescriptor {
130 format: VertexFormat::Float32x2,
131 offset: 24,
132 shader_location: 2,
133 },
134 ]),
135 }],
136 vs_entry: "vs_main",
137 fs_entry: Some("fs_main"),
138 primitive: PrimitiveStateDescriptor {
139 topology: PrimitiveTopology::TriangleList,
140 cull_mode: if double_sided {
143 None
144 } else {
145 Some(CullMode::Back)
146 },
147 ..Default::default()
148 },
149 depth_stencil: Some(DepthStencilStateDescriptor {
150 format: TextureFormat::Depth32Float,
151 depth_write_enabled: !blend,
156 depth_compare: CompareFunction::Less,
157 stencil_front: StencilFaceState::default(),
158 stencil_back: StencilFaceState::default(),
159 stencil_read_mask: 0,
160 stencil_write_mask: 0,
161 bias: DepthBiasState::default(),
162 }),
163 color_targets: vec![ColorTargetStateDescriptor {
164 format: device
165 .get_surface_format()
166 .unwrap_or(TextureFormat::Rgba8UnormSrgb),
167 blend: blend.then(BlendStateDescriptor::alpha_blending),
168 write_mask: ColorWrites::ALL,
169 }],
170 multisample: MultisampleStateDescriptor {
171 count: SampleCount::X1,
172 mask: !0,
173 alpha_to_coverage_enabled: false,
174 },
175 }
176}
177
178fn init_gpu_resources(
179 lane: &StandardPbrLane,
180 device: &dyn khora_core::renderer::GraphicsDevice,
181 pipeline_system: &dyn khora_core::renderer::traits::PipelineSystem,
182) -> Result<(), khora_core::renderer::error::RenderError> {
183 use khora_core::renderer::api::resource::CameraUniformData;
184
185 let variant = ShaderVariantKey::empty();
186 let camera_layout = pipeline_system.layout(device, LayoutKey::Camera, &variant)?;
189 let model_layout = pipeline_system.layout(device, LayoutKey::Model, &variant)?;
190 let material_layout = pipeline_system.layout(device, LayoutKey::Material, &variant)?;
191 let light_layout = pipeline_system.layout(device, LayoutKey::Lighting, &variant)?;
192 let lighting_buffer_layout =
193 pipeline_system.layout(device, LayoutKey::LightingBuffer, &variant)?;
194
195 let pipeline_id = pipeline_system.pipeline(
199 device,
200 &pipeline_spec(device, ShaderVariantKey::empty(), false, false),
201 )?;
202
203 let _ = lane.camera_layout.set(camera_layout);
204 let _ = lane.model_layout.set(model_layout);
205 let _ = lane.material_layout.set(material_layout);
206 let _ = lane.light_layout.set(light_layout);
207 let _ = lane.pipeline.set(pipeline_id);
208
209 let camera_ring = UniformRingBuffer::new(
211 device,
212 camera_layout,
213 0,
214 std::mem::size_of::<CameraUniformData>() as u64,
215 "StandardPbr Camera Ring",
216 )
217 .map_err(khora_core::renderer::error::RenderError::ResourceError)?;
218
219 let _ = lane.lighting_buffer_layout.set(lighting_buffer_layout);
220
221 let lighting_ring = UniformRingBuffer::new(
222 device,
223 lighting_buffer_layout,
224 0,
225 std::mem::size_of::<LightingUniforms>() as u64,
226 "StandardPbr Lighting Ring",
227 )
228 .map_err(khora_core::renderer::error::RenderError::ResourceError)?;
229
230 let model_ring = DynamicUniformRingBuffer::new(
231 device,
232 model_layout,
233 0,
234 std::mem::size_of::<ModelUniforms>() as u32,
235 khora_core::renderer::api::util::dynamic_uniform_buffer::DEFAULT_MAX_ELEMENTS,
236 khora_core::renderer::api::util::dynamic_uniform_buffer::MIN_UNIFORM_ALIGNMENT,
237 "StandardPbr Model Ring",
238 )
239 .map_err(khora_core::renderer::error::RenderError::ResourceError)?;
240
241 use crate::render_lane::util::lock::mutex_lock_render;
242 *mutex_lock_render(&lane.camera_ring, "StandardPbr init.camera_ring")? = Some(camera_ring);
243 *mutex_lock_render(&lane.lighting_ring, "StandardPbr init.lighting_ring")? =
244 Some(lighting_ring);
245 *mutex_lock_render(&lane.model_ring, "StandardPbr init.model_ring")? = Some(model_ring);
246 Ok(())
247}
248
249#[allow(clippy::too_many_arguments)]
250fn render_pbr(
251 lane: &StandardPbrLane,
252 render_world: &RenderWorld,
253 shadow_entries: &khora_data::render::ShadowEntries,
254 shadow_bindings: Option<khora_data::render::ShadowGpuBindings>,
255 ibl_bindings: Option<khora_core::renderer::api::ibl::IblGpuBindings>,
256 device: &dyn khora_core::renderer::GraphicsDevice,
257 encoder: &mut dyn CommandEncoder,
258 render_ctx: &RenderContext,
259 gpu_meshes: &RwLock<Assets<GpuMesh>>,
260) {
261 use khora_core::renderer::api::{
262 command::{BindGroupDescriptor, BindGroupEntry, BindingResource, BufferBinding},
263 resource::CameraUniformData,
264 };
265
266 let Some(view) = render_world.views.first() else {
267 let color_attachment = RenderPassColorAttachment {
270 view: render_ctx.color_target,
271 resolve_target: None,
272 ops: Operations {
273 load: LoadOp::Clear(render_ctx.clear_color),
274 store: StoreOp::Store,
275 },
276 base_array_layer: 0,
277 base_mip_level: 0,
278 };
279 let clear_desc = RenderPassDescriptor {
280 label: Some("StandardPbr Clear-Only Pass"),
281 color_attachments: &[color_attachment],
282 depth_stencil_attachment: render_ctx.depth_target.map(|d| {
283 RenderPassDepthStencilAttachment {
284 view: d,
285 depth_ops: Some(Operations {
286 load: LoadOp::Clear(1.0),
287 store: StoreOp::Store,
288 }),
289 stencil_ops: None,
290 base_array_layer: 0,
291 }
292 }),
293 };
294 let _ = encoder.begin_render_pass(&clear_desc);
295 return;
296 };
297
298 let camera_uniforms = CameraUniformData {
300 view_projection: view.view_proj.to_cols_array_2d(),
301 camera_position: [view.position.x, view.position.y, view.position.z, 1.0],
302 };
303 let camera_bind_group = {
304 let mut lock = crate::lock_or_log!(
305 lane.camera_ring.lock(),
306 "StandardPbrLane::render camera_ring"
307 );
308 let ring = match lock.as_mut() {
309 Some(r) => r,
310 None => {
311 log::warn!("StandardPbrLane: camera ring buffer not initialized");
312 return;
313 }
314 };
315 ring.advance();
316 if let Err(e) = ring.write(device, bytemuck::bytes_of(&camera_uniforms)) {
317 log::error!("Failed to write camera ring buffer: {:?}", e);
318 return;
319 }
320 *ring.current_bind_group()
321 };
322
323 let mut lighting_uniforms = LightingUniforms {
325 directional_lights: [DirectionalLightUniform {
326 direction: [0.0; 4],
327 color: khora_core::math::LinearRgba::BLACK,
328 shadow_view_proj: [[0.0; 4]; 4],
329 shadow_params: [0.0; 4],
330 }; MAX_DIRECTIONAL_LIGHTS],
331 point_lights: [PointLightUniform {
332 position: [0.0; 4],
333 color: khora_core::math::LinearRgba::BLACK,
334 shadow_params: [0.0; 4],
335 }; MAX_POINT_LIGHTS],
336 spot_lights: [SpotLightUniform {
337 position: [0.0; 4],
338 direction: [0.0; 4],
339 color: khora_core::math::LinearRgba::BLACK,
340 params: [0.0; 4],
341 shadow_view_proj: [[0.0; 4]; 4],
342 shadow_params: [0.0; 4],
343 }; MAX_SPOT_LIGHTS],
344 num_directional_lights: 0,
345 num_point_lights: 0,
346 num_spot_lights: 0,
347 _padding: 0,
348 };
349
350 for (light_index, light) in render_world.lights.iter().enumerate() {
351 let shadow = shadow_entries.get(light_index);
352 let (atlas2d_view_proj, atlas2d_index) = match shadow {
353 Some(khora_data::render::ShadowEntry::Atlas2D {
354 view_proj,
355 atlas_index,
356 }) => (*view_proj, *atlas_index as f32),
357 _ => (Mat4::IDENTITY, -1.0),
358 };
359 let (cube_layer, cube_far_plane) = match shadow {
360 Some(khora_data::render::ShadowEntry::Cube {
361 cube_array_index,
362 far_plane,
363 ..
364 }) => (*cube_array_index as f32, *far_plane),
365 _ => (-1.0, 0.0),
366 };
367
368 match light.light_type {
369 khora_core::renderer::light::LightType::Directional(ref d) => {
370 if (lighting_uniforms.num_directional_lights as usize) < MAX_DIRECTIONAL_LIGHTS {
371 let idx = lighting_uniforms.num_directional_lights as usize;
372 lighting_uniforms.directional_lights[idx] = DirectionalLightUniform {
373 direction: [light.direction.x, light.direction.y, light.direction.z, 0.0],
374 color: d.color.with_alpha(d.intensity),
375 shadow_view_proj: atlas2d_view_proj.to_cols_array_2d(),
376 shadow_params: [atlas2d_index, d.shadow_bias, d.shadow_normal_bias, 0.0],
377 };
378 lighting_uniforms.num_directional_lights += 1;
379 }
380 }
381 khora_core::renderer::light::LightType::Point(ref p) => {
382 if (lighting_uniforms.num_point_lights as usize) < MAX_POINT_LIGHTS {
383 let idx = lighting_uniforms.num_point_lights as usize;
384 lighting_uniforms.point_lights[idx] = PointLightUniform {
385 position: [
386 light.position.x,
387 light.position.y,
388 light.position.z,
389 p.range,
390 ],
391 color: p.color.with_alpha(p.intensity),
392 shadow_params: [
393 cube_layer,
394 p.shadow_bias,
395 p.shadow_normal_bias,
396 cube_far_plane,
397 ],
398 };
399 lighting_uniforms.num_point_lights += 1;
400 }
401 }
402 khora_core::renderer::light::LightType::Spot(ref s) => {
403 if (lighting_uniforms.num_spot_lights as usize) < MAX_SPOT_LIGHTS {
404 let idx = lighting_uniforms.num_spot_lights as usize;
405 lighting_uniforms.spot_lights[idx] = SpotLightUniform {
406 position: [
407 light.position.x,
408 light.position.y,
409 light.position.z,
410 s.range,
411 ],
412 direction: [
413 light.direction.x,
414 light.direction.y,
415 light.direction.z,
416 s.inner_cone_angle.cos(),
417 ],
418 color: s.color.with_alpha(s.intensity),
419 params: [s.outer_cone_angle.cos(), 0.0, 0.0, 0.0],
420 shadow_view_proj: atlas2d_view_proj.to_cols_array_2d(),
421 shadow_params: [atlas2d_index, s.shadow_bias, s.shadow_normal_bias, 0.0],
422 };
423 lighting_uniforms.num_spot_lights += 1;
424 }
425 }
426 }
427 }
428
429 let (_lighting_bind_group, lighting_ring_buffer_id) = {
430 let mut lock = crate::lock_or_log!(
431 lane.lighting_ring.lock(),
432 "StandardPbrLane::render lighting_ring"
433 );
434 let ring = match lock.as_mut() {
435 Some(r) => r,
436 None => {
437 log::warn!("StandardPbrLane: lighting ring buffer not initialized");
438 return;
439 }
440 };
441 ring.advance();
442 if let Err(e) = ring.write(device, bytemuck::bytes_of(&lighting_uniforms)) {
443 log::error!("Failed to write lighting ring buffer: {:?}", e);
444 return;
445 }
446 (*ring.current_bind_group(), ring.current_buffer())
447 };
448
449 let gpu_mesh_assets =
450 crate::lock_or_log!(gpu_meshes.read(), "StandardPbrLane::render gpu_meshes");
451 let fallback_pipeline = lane
454 .pipeline
455 .get()
456 .copied()
457 .unwrap_or(khora_core::renderer::api::pipeline::RenderPipelineId(0));
458
459 let mut draw_commands = Vec::with_capacity(render_world.meshes.len());
463 let mut transparent_draws: Vec<(f32, khora_core::renderer::api::command::DrawCommand)> =
464 Vec::new();
465 let mut temp_bind_groups = Vec::new();
466
467 let mut model_ring_lock =
470 crate::lock_or_log!(lane.model_ring.lock(), "StandardPbrLane::render model_ring");
471 let model_ring = match model_ring_lock.as_mut() {
472 Some(r) => r,
473 None => {
474 log::warn!("StandardPbrLane: model ring buffer not initialized");
475 return;
476 }
477 };
478 model_ring.advance();
479
480 for extracted_mesh in &render_world.meshes {
481 let Some(gpu_mesh_handle) = gpu_mesh_assets.get(&extracted_mesh.cpu_mesh_uuid) else {
482 continue;
483 };
484 let Some(gpu_material) = &extracted_mesh.gpu_material else {
488 continue;
489 };
490 let pipeline_id = lane
494 .pipeline_system
495 .get()
496 .map(|ps| {
497 ps.pipeline(
498 device,
499 &pipeline_spec(
500 device,
501 gpu_material.variant.clone(),
502 gpu_material.double_sided,
503 gpu_material.blend,
504 ),
505 )
506 })
507 .transpose()
508 .unwrap_or_else(|e| {
509 log::error!("StandardPbrLane: pipeline resolve failed: {:?}", e);
510 None
511 })
512 .unwrap_or(fallback_pipeline);
513 let model_mat = extracted_mesh.transform.to_matrix();
514 let normal_mat = match model_mat.inverse() {
515 Some(inv) => inv.transpose(),
516 None => continue,
517 };
518 let model_uniforms = ModelUniforms {
519 model_matrix: model_mat.to_cols_array_2d(),
520 normal_matrix: normal_mat.to_cols_array_2d(),
521 };
522 let model_offset = match model_ring.push(device, bytemuck::bytes_of(&model_uniforms)) {
523 Ok(offset) => offset,
524 Err(e) => {
525 log::error!("StandardPbrLane: failed to push model uniforms: {:?}", e);
526 continue;
527 }
528 };
529 let model_bg = *model_ring.current_bind_group();
530
531 let command = khora_core::renderer::api::command::DrawCommand {
532 pipeline: pipeline_id,
533 vertex_buffer: gpu_mesh_handle.vertex_buffer,
534 index_buffer: gpu_mesh_handle.index_buffer,
535 index_format: gpu_mesh_handle.index_format,
536 index_count: gpu_mesh_handle.index_count,
537 model_bind_group: Some(model_bg),
538 model_offset,
539 material_bind_group: Some(gpu_material.bind_group),
540 material_offset: 0,
541 };
542 if gpu_material.blend {
543 transparent_draws.push((
544 crate::render_lane::camera_distance_sq(&model_mat, view.position),
545 command,
546 ));
547 } else {
548 draw_commands.push(command);
549 }
550 }
551
552 draw_commands.sort_by_key(|cmd| cmd.pipeline.0);
555 transparent_draws.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
559
560 let color_attachment = RenderPassColorAttachment {
561 view: render_ctx.color_target,
562 resolve_target: None,
563 ops: Operations {
564 load: LoadOp::Clear(render_ctx.clear_color),
565 store: StoreOp::Store,
566 },
567 base_array_layer: 0,
568 base_mip_level: 0,
569 };
570 let render_pass_desc = RenderPassDescriptor {
571 label: Some("Standard PBR Pass"),
572 color_attachments: &[color_attachment],
573 depth_stencil_attachment: render_ctx.depth_target.map(|d| {
574 RenderPassDepthStencilAttachment {
575 view: d,
576 depth_ops: Some(Operations {
577 load: LoadOp::Clear(1.0),
578 store: StoreOp::Store,
579 }),
580 stencil_ops: None,
581 base_array_layer: 0,
582 }
583 }),
584 };
585
586 let final_lighting_bind_group = if let Some(layout) = lane.light_layout.get().copied() {
588 let Some(shadow_bindings) = shadow_bindings else {
589 log::warn!("StandardPbrLane: ShadowGpuBindings not available, skipping render");
590 return;
591 };
592 let mut entries = vec![BindGroupEntry {
593 binding: khora_data::render::shadow_bindings::binding::LIGHTING_UNIFORMS,
594 resource: BindingResource::Buffer(BufferBinding {
595 buffer: lighting_ring_buffer_id,
596 offset: 0,
597 size: None,
598 }),
599 _phantom: std::marker::PhantomData,
600 }];
601 khora_data::render::shadow_bindings::fill_shadow_bind_group_entries(
602 &shadow_bindings,
603 &mut entries,
604 );
605 let Some(ibl) = ibl_bindings else {
607 log::warn!("StandardPbrLane: IBL bindings not available, skipping render");
608 return;
609 };
610 khora_core::renderer::api::ibl::fill_ibl_bind_group_entries(&ibl, 4, &mut entries);
611 match device.create_bind_group(&BindGroupDescriptor {
612 label: Some("standard_pbr_lighting_bind_group_dynamic"),
613 layout,
614 entries: &entries,
615 }) {
616 Ok(bg) => {
617 temp_bind_groups.push(bg);
618 bg
619 }
620 Err(e) => {
621 log::error!(
622 "StandardPbrLane: failed to create lighting bind group: {:?}",
623 e
624 );
625 return;
626 }
627 }
628 } else {
629 log::warn!("StandardPbrLane: light layout not initialized");
630 return;
631 };
632
633 let mut render_pass = encoder.begin_render_pass(&render_pass_desc);
634 render_pass.set_bind_group(0, &camera_bind_group, &[]);
635 render_pass.set_bind_group(3, &final_lighting_bind_group, &[]);
636
637 let mut current_pipeline = None;
640 for cmd in draw_commands
641 .iter()
642 .chain(transparent_draws.iter().map(|(_, cmd)| cmd))
643 {
644 if current_pipeline != Some(cmd.pipeline) {
645 render_pass.set_pipeline(&cmd.pipeline);
646 current_pipeline = Some(cmd.pipeline);
647 }
648 if let Some(bg) = &cmd.model_bind_group {
649 render_pass.set_bind_group(1, bg, &[cmd.model_offset]);
650 }
651 if let Some(bg) = &cmd.material_bind_group {
652 render_pass.set_bind_group(2, bg, &[]);
653 }
654 render_pass.set_vertex_buffer(0, &cmd.vertex_buffer, 0);
655 render_pass.set_index_buffer(&cmd.index_buffer, 0, cmd.index_format);
656 render_pass.draw_indexed(0..cmd.index_count, 0, 0..1);
657 }
658
659 drop(render_pass);
660 for bg in temp_bind_groups {
663 let _ = device.destroy_bind_group(bg);
664 }
665}
666
667impl khora_core::lane::Lane for StandardPbrLane {
668 fn strategy_name(&self) -> &'static str {
669 "StandardPbr"
670 }
671
672 fn lane_kind(&self) -> khora_core::lane::LaneKind {
673 khora_core::lane::LaneKind::Render
674 }
675
676 fn on_initialize(
677 &self,
678 ctx: &mut khora_core::lane::LaneContext,
679 ) -> Result<(), khora_core::lane::LaneError> {
680 let device = ctx
681 .get::<std::sync::Arc<dyn khora_core::renderer::GraphicsDevice>>()
682 .ok_or(khora_core::lane::LaneError::missing(
683 "Arc<dyn GraphicsDevice>",
684 ))?
685 .clone();
686 let pipeline_system = ctx
687 .get::<std::sync::Arc<dyn khora_core::renderer::traits::PipelineSystem>>()
688 .ok_or(khora_core::lane::LaneError::missing(
689 "Arc<dyn PipelineSystem>",
690 ))?
691 .clone();
692 let _ = self.pipeline_system.set(pipeline_system.clone());
695 init_gpu_resources(self, device.as_ref(), pipeline_system.as_ref())
696 .map_err(|e| khora_core::lane::LaneError::InitializationFailed(Box::new(e)))
697 }
698
699 fn execute(
700 &self,
701 ctx: &mut khora_core::lane::LaneContext,
702 ) -> Result<(), khora_core::lane::LaneError> {
703 use khora_core::lane::{LaneError, Ref, Slot};
704 let device = ctx
705 .get::<std::sync::Arc<dyn khora_core::renderer::GraphicsDevice>>()
706 .ok_or(LaneError::missing("Arc<dyn GraphicsDevice>"))?
707 .clone();
708 let gpu_meshes = ctx
709 .get::<std::sync::Arc<
710 std::sync::RwLock<
711 khora_data::assets::Assets<khora_core::renderer::api::scene::GpuMesh>,
712 >,
713 >>()
714 .ok_or(LaneError::missing("Arc<RwLock<Assets<GpuMesh>>>"))?
715 .clone();
716 let encoder = ctx
717 .get::<Slot<dyn khora_core::renderer::traits::CommandEncoder>>()
718 .ok_or(LaneError::missing("Slot<dyn CommandEncoder>"))?
719 .get();
720 let render_world = ctx
721 .get::<Ref<khora_data::render::RenderWorld>>()
722 .ok_or(LaneError::missing("Ref<RenderWorld>"))?
723 .get();
724 let color_target = ctx
725 .get::<khora_core::lane::ColorTarget>()
726 .ok_or(LaneError::missing("ColorTarget"))?
727 .0;
728 let depth_target = ctx
729 .get::<khora_core::lane::DepthTarget>()
730 .ok_or(LaneError::missing("DepthTarget"))?
731 .0;
732 let clear_color = ctx
733 .get::<khora_core::lane::ClearColor>()
734 .ok_or(LaneError::missing("ClearColor"))?
735 .0;
736 let render_ctx = khora_core::renderer::api::core::RenderContext::new(
737 &color_target,
738 Some(&depth_target),
739 clear_color,
740 );
741
742 let (shadow_entries, shadow_bindings) = ctx
743 .get::<Slot<khora_core::lane::OutputDeck>>()
744 .map(|s| {
745 let frame = s
746 .get()
747 .slot::<khora_core::renderer::api::shadow::ShadowFrame>();
748 (frame.entries.clone(), frame.bindings)
749 })
750 .unwrap_or_default();
751
752 let ibl_bindings = ctx
755 .get::<khora_core::renderer::api::ibl::IblGpuBindings>()
756 .copied();
757
758 render_pbr(
759 self,
760 render_world,
761 &shadow_entries,
762 shadow_bindings,
763 ibl_bindings,
764 device.as_ref(),
765 encoder,
766 &render_ctx,
767 &gpu_meshes,
768 );
769 Ok(())
770 }
771
772 fn as_any(&self) -> &dyn std::any::Any {
773 self
774 }
775
776 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
777 self
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use super::*;
784 use khora_core::lane::Lane;
785
786 #[test]
787 fn standard_pbr_lane_strategy_name() {
788 let lane = StandardPbrLane::default();
789 assert_eq!(lane.strategy_name(), "StandardPbr");
790 assert_eq!(lane.lane_kind(), khora_core::lane::LaneKind::Render);
791 }
792}