#![allow(missing_docs)]
use crate::renderer::resources::RendererResources;
use crate::{
core::{
algebra::{Matrix4, Point3, Vector2, Vector3, Vector4},
arrayvec::ArrayVec,
color::{self, Color},
err_once,
log::Log,
math::{frustum::Frustum, Matrix4Ext, Rect},
pool::Handle,
sstorage::ImmutableString,
},
graph::SceneGraph,
graphics::{
error::FrameworkError,
framebuffer::{GpuFrameBuffer, ResourceBindGroup, ResourceBinding},
gpu_program::{
SamplerFallback, ShaderProperty, ShaderPropertyKind, ShaderResourceDefinition,
ShaderResourceKind,
},
gpu_texture::GpuTexture,
server::GraphicsServer,
uniform::{ByteStorage, StaticUniformBuffer, UniformBuffer},
ElementRange,
},
material::{self, shader::ShaderDefinition, Material, MaterialPropertyRef, MaterialResource},
renderer::{
cache::{
geometry::GeometryCache,
shader::ShaderCache,
texture::TextureCache,
uniform::{UniformBlockLocation, UniformMemoryAllocator},
DynamicSurfaceCache, TimeToLive,
},
observer::ObserverPosition,
RenderPassStatistics,
},
resource::texture::TextureResource,
scene::{
collider::BitMask,
graph::Graph,
light::{
directional::{CsmOptions, DirectionalLight},
point::PointLight,
spot::SpotLight,
BaseLight,
},
mesh::{
buffer::{TriangleBufferRefMut, VertexAttributeDescriptor, VertexBufferRefMut},
surface::SurfaceResource,
RenderPath,
},
node::{Node, NodeTrait, RdcControlFlow},
probe::ReflectionProbe,
},
};
use fxhash::{FxBuildHasher, FxHashMap, FxHasher};
use fyrox_graph::SceneGraphNode;
use fyrox_resource::manager::ResourceManager;
use std::{
fmt::{Debug, Formatter},
hash::{Hash, Hasher},
};
pub struct RenderContext<'a> {
pub render_mask: BitMask,
pub elapsed_time: f32,
pub observer_position: &'a ObserverPosition,
pub frustum: Option<&'a Frustum>,
pub storage: &'a mut dyn RenderDataBundleStorageTrait,
pub graph: &'a Graph,
pub render_pass_name: &'a ImmutableString,
pub dynamic_surface_cache: &'a mut DynamicSurfaceCache,
}
impl RenderContext<'_> {
pub fn calculate_sorting_index(&self, global_position: Vector3<f32>) -> u64 {
const RANGE_CENTER: u64 = u64::MAX / 2;
const GRANULARITY: f32 = 1000.0;
let view_matrix = &self.observer_position.view_matrix;
let world_space_point = Point3::from(global_position);
let view_space_point = view_matrix.transform_point(&world_space_point);
RANGE_CENTER.saturating_add_signed((view_space_point.z * GRANULARITY) as i64)
}
}
#[allow(missing_docs)] pub struct BundleRenderContext<'a> {
pub texture_cache: &'a mut TextureCache,
pub render_pass_name: &'a ImmutableString,
pub frame_buffer: &'a GpuFrameBuffer,
pub viewport: Rect<i32>,
pub uniform_memory_allocator: &'a mut UniformMemoryAllocator,
pub resource_manager: &'a ResourceManager,
pub use_pom: bool,
pub light_position: &'a Vector3<f32>,
pub ambient_light: Color,
pub scene_depth: Option<&'a GpuTexture>,
pub renderer_resources: &'a RendererResources,
}
pub struct SurfaceInstanceData {
pub world_transform: Matrix4<f32>,
pub bone_matrices: Vec<Matrix4<f32>>,
pub blend_shapes_weights: Vec<f32>,
pub element_range: ElementRange,
pub node_handle: Handle<Node>,
}
impl Default for SurfaceInstanceData {
fn default() -> Self {
Self {
world_transform: Matrix4::identity(),
bone_matrices: Default::default(),
blend_shapes_weights: Default::default(),
element_range: Default::default(),
node_handle: Default::default(),
}
}
}
pub struct RenderDataBundle {
pub data: SurfaceResource,
pub time_to_live: TimeToLive,
pub instances: Vec<SurfaceInstanceData>,
pub material: MaterialResource,
pub render_path: RenderPath,
sort_index: u64,
}
impl Debug for RenderDataBundle {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Bundle {}: {} instances",
self.data.key(),
self.instances.len()
)
}
}
pub struct InstanceUniformData {
pub instance_block: UniformBlockLocation,
pub bone_matrices_block: Option<UniformBlockLocation>,
}
pub struct BundleUniformData {
pub material_property_group_blocks: Vec<(usize, UniformBlockLocation)>,
pub light_data_block: UniformBlockLocation,
pub instance_blocks: Vec<InstanceUniformData>,
}
pub struct GlobalUniformData {
pub camera_block: UniformBlockLocation,
pub lights_block: UniformBlockLocation,
pub graphics_settings_block: UniformBlockLocation,
}
pub fn write_with_material<T, C, G>(
shader_property_group: &[ShaderProperty],
material_property_group: &C,
getter: G,
buf: &mut UniformBuffer<T>,
) where
T: ByteStorage,
G: for<'a> Fn(&'a C, &ImmutableString) -> Option<MaterialPropertyRef<'a>>,
{
for shader_property in shader_property_group {
let material_property = getter(material_property_group, &shader_property.name);
macro_rules! push_value {
($variant:ident, $shader_value:ident) => {
if let Some(property) = material_property {
if let MaterialPropertyRef::$variant(material_value) = property {
buf.push(material_value);
} else {
buf.push($shader_value);
Log::err(format!(
"Unable to use material property {} because of mismatching types.\
Expected {:?} got {:?}. Fallback to shader default value.",
shader_property.name, shader_property, property
));
}
} else {
buf.push($shader_value);
}
};
}
macro_rules! push_slice {
($variant:ident, $shader_value:ident, $max_size:ident) => {
if let Some(property) = material_property {
if let MaterialPropertyRef::$variant(material_value) = property {
buf.push_slice_with_max_size(material_value, *$max_size);
} else {
buf.push_slice_with_max_size($shader_value, *$max_size);
Log::err(format!(
"Unable to use material property {} because of mismatching types.\
Expected {:?} got {:?}. Fallback to shader default value.",
shader_property.name, shader_property, property
))
}
} else {
buf.push_slice_with_max_size($shader_value, *$max_size);
}
};
}
use ShaderPropertyKind::*;
match &shader_property.kind {
Float { value } => push_value!(Float, value),
FloatArray { value, max_len } => push_slice!(FloatArray, value, max_len),
Int { value } => push_value!(Int, value),
IntArray { value, max_len } => push_slice!(IntArray, value, max_len),
UInt { value } => push_value!(UInt, value),
UIntArray { value, max_len } => push_slice!(UIntArray, value, max_len),
Vector2 { value } => push_value!(Vector2, value),
Vector2Array { value, max_len } => push_slice!(Vector2Array, value, max_len),
Vector3 { value } => push_value!(Vector3, value),
Vector3Array { value, max_len } => push_slice!(Vector3Array, value, max_len),
Vector4 { value: default } => push_value!(Vector4, default),
Vector4Array { value, max_len } => push_slice!(Vector4Array, value, max_len),
Matrix2 { value: default } => push_value!(Matrix2, default),
Matrix2Array { value, max_len } => push_slice!(Matrix2Array, value, max_len),
Matrix3 { value: default } => push_value!(Matrix3, default),
Matrix3Array { value, max_len } => push_slice!(Matrix3Array, value, max_len),
Matrix4 { value: default } => push_value!(Matrix4, default),
Matrix4Array { value, max_len } => push_slice!(Matrix4Array, value, max_len),
Bool { value } => push_value!(Bool, value),
Color { r, g, b, a } => {
let value = &color::Color::from_rgba(*r, *g, *b, *a);
push_value!(Color, value)
}
};
}
}
pub fn write_shader_values<T: ByteStorage>(
shader_property_group: &[ShaderProperty],
buf: &mut UniformBuffer<T>,
) {
for property in shader_property_group {
use ShaderPropertyKind::*;
match &property.kind {
Float { value } => buf.push(value),
FloatArray { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Int { value } => buf.push(value),
IntArray { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
UInt { value } => buf.push(value),
UIntArray { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Vector2 { value } => buf.push(value),
Vector2Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Vector3 { value } => buf.push(value),
Vector3Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Vector4 { value: default } => buf.push(default),
Vector4Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Matrix2 { value: default } => buf.push(default),
Matrix2Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Matrix3 { value: default } => buf.push(default),
Matrix3Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Matrix4 { value: default } => buf.push(default),
Matrix4Array { value, max_len } => buf.push_slice_with_max_size(value, *max_len),
Bool { value } => buf.push(value),
Color { r, g, b, a } => buf.push(&color::Color::from_rgba(*r, *g, *b, *a)),
};
}
}
pub fn make_texture_binding(
server: &dyn GraphicsServer,
material: &Material,
resource_definition: &ShaderResourceDefinition,
renderer_resources: &RendererResources,
fallback: SamplerFallback,
resource_manager: &ResourceManager,
texture_cache: &mut TextureCache,
) -> ResourceBinding {
let fallback = renderer_resources.sampler_fallback(fallback);
let fallback = (fallback, &renderer_resources.linear_wrap_sampler);
let texture_sampler_pair =
if let Some(binding) = material.binding_ref(resource_definition.name.clone()) {
if let material::MaterialResourceBinding::Texture(binding) = binding {
binding
.value
.as_ref()
.and_then(|t| {
texture_cache
.get(server, resource_manager, t)
.map(|t| (&t.gpu_texture, &t.gpu_sampler))
})
.unwrap_or(fallback)
} else {
Log::err(format!(
"Unable to use texture binding {}, types mismatch! Expected \
{:?} got {:?}",
resource_definition.name, resource_definition.kind, binding
));
fallback
}
} else {
fallback
};
ResourceBinding::texture(
texture_sampler_pair.0,
texture_sampler_pair.1,
resource_definition.binding,
)
}
impl RenderDataBundle {
pub fn write_uniforms(
&self,
view_projection_matrix: &Matrix4<f32>,
render_context: &mut BundleRenderContext,
) -> Option<BundleUniformData> {
let mut material_state = self.material.state();
let material = material_state.data()?;
let mut material_property_group_blocks = Vec::new();
let shader_state = material.shader().state();
let shader = shader_state.data_ref()?;
for resource_definition in shader.definition.resources.iter() {
if resource_definition.is_built_in() {
continue;
}
let ShaderResourceKind::PropertyGroup(ref shader_property_group) =
resource_definition.kind
else {
continue;
};
let mut buf = StaticUniformBuffer::<16384>::new();
if let Some(material_property_group) =
material.property_group_ref(resource_definition.name.clone())
{
write_with_material(
shader_property_group,
material_property_group,
|c, n| c.property_ref(n.clone()).map(|p| p.as_ref()),
&mut buf,
);
} else {
write_shader_values(shader_property_group, &mut buf)
}
if buf.is_empty() {
continue;
}
material_property_group_blocks.push((
resource_definition.binding,
render_context.uniform_memory_allocator.allocate(buf),
))
}
let light_data = StaticUniformBuffer::<256>::new()
.with(render_context.light_position)
.with(&render_context.ambient_light.as_frgba());
let light_data_block = render_context.uniform_memory_allocator.allocate(light_data);
let mut instance_blocks = Vec::with_capacity(self.instances.len());
for instance in self.instances.iter() {
let mut packed_blend_shape_weights =
[Vector4::<f32>::default(); ShaderDefinition::MAX_BLEND_SHAPE_WEIGHT_GROUPS];
for (i, blend_shape_weight) in instance.blend_shapes_weights.iter().enumerate() {
let n = i / 4;
let c = i % 4;
packed_blend_shape_weights[n][c] = *blend_shape_weight;
}
let instance_buffer = StaticUniformBuffer::<1024>::new()
.with(&instance.world_transform)
.with(&(view_projection_matrix * instance.world_transform))
.with(&(instance.blend_shapes_weights.len() as i32))
.with(&(!instance.bone_matrices.is_empty()))
.with_slice_with_max_size(
&packed_blend_shape_weights,
ShaderDefinition::MAX_BLEND_SHAPE_WEIGHT_GROUPS,
);
let mut instance_uniform_data = InstanceUniformData {
instance_block: render_context
.uniform_memory_allocator
.allocate(instance_buffer),
bone_matrices_block: None,
};
if !instance.bone_matrices.is_empty() {
const INIT: Matrix4<f32> = Matrix4::new(
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
);
let mut matrices = [INIT; ShaderDefinition::MAX_BONE_MATRICES];
const SIZE: usize = ShaderDefinition::MAX_BONE_MATRICES * size_of::<Matrix4<f32>>();
matrices[0..instance.bone_matrices.len()].copy_from_slice(&instance.bone_matrices);
let bone_matrices_block = render_context
.uniform_memory_allocator
.allocate(StaticUniformBuffer::<SIZE>::new().with(&matrices));
instance_uniform_data.bone_matrices_block = Some(bone_matrices_block);
}
instance_blocks.push(instance_uniform_data);
}
Some(BundleUniformData {
material_property_group_blocks,
light_data_block,
instance_blocks,
})
}
pub fn render_to_frame_buffer<F>(
&self,
server: &dyn GraphicsServer,
geometry_cache: &mut GeometryCache,
shader_cache: &mut ShaderCache,
instance_filter: &mut F,
render_context: &mut BundleRenderContext,
bundle_uniform_data: BundleUniformData,
global_uniform_data: &GlobalUniformData,
) -> Result<RenderPassStatistics, FrameworkError>
where
F: FnMut(&SurfaceInstanceData) -> bool,
{
let mut stats = RenderPassStatistics::default();
let mut material_state = self.material.state();
let Some(material) = material_state.data() else {
err_once!(
self.data.key() as usize,
"Unable to use material {}, because it is in invalid state \
(failed to load or still loading)!",
material_state.kind()
);
return Ok(stats);
};
let geometry = match geometry_cache.get(server, &self.data, self.time_to_live) {
Ok(geometry) => geometry,
Err(err) => {
err_once!(
self.data.key() as usize,
"Unable to get geometry for rendering! Reason: {err:?}"
);
return Ok(stats);
}
};
let Some(shader_set) = shader_cache.get(server, material.shader()) else {
err_once!(
self.data.key() as usize,
"Unable to get a compiled shader set for material {:?}!",
material.shader().resource_uuid()
);
return Ok(stats);
};
let Some(render_pass) = shader_set
.render_passes
.get(render_context.render_pass_name)
else {
let shader_state = material.shader().state();
if let Some(shader_data) = shader_state.data_ref() {
if !shader_data
.definition
.disabled_passes
.iter()
.any(|pass_name| pass_name.as_str() == render_context.render_pass_name.as_str())
{
err_once!(
self.data.key() as usize,
"There's no render pass {} in {:?} shader! \
If it is not needed, add it to disabled passes.",
render_context.render_pass_name,
shader_data.definition.name,
);
}
}
return Ok(stats);
};
let mut material_bindings = ArrayVec::<ResourceBinding, 32>::new();
let shader_state = material.shader().state();
let shader = shader_state
.data_ref()
.ok_or_else(|| FrameworkError::Custom("Invalid shader!".to_string()))?;
for resource_definition in shader.definition.resources.iter() {
let name = resource_definition.name.as_str();
match name {
"fyrox_sceneDepth" => {
material_bindings.push(ResourceBinding::texture(
if let Some(scene_depth) = render_context.scene_depth.as_ref() {
scene_depth
} else {
&render_context.renderer_resources.black_dummy
},
&render_context.renderer_resources.nearest_clamp_sampler,
resource_definition.binding,
));
}
"fyrox_cameraData" => {
material_bindings.push(
render_context.uniform_memory_allocator.block_to_binding(
global_uniform_data.camera_block,
resource_definition.binding,
),
);
}
"fyrox_lightData" => {
material_bindings.push(
render_context.uniform_memory_allocator.block_to_binding(
bundle_uniform_data.light_data_block,
resource_definition.binding,
),
);
}
"fyrox_graphicsSettings" => {
material_bindings.push(
render_context.uniform_memory_allocator.block_to_binding(
global_uniform_data.graphics_settings_block,
resource_definition.binding,
),
);
}
"fyrox_lightsBlock" => {
material_bindings.push(
render_context.uniform_memory_allocator.block_to_binding(
global_uniform_data.lights_block,
resource_definition.binding,
),
);
}
_ => match resource_definition.kind {
ShaderResourceKind::Texture { fallback, .. } => {
material_bindings.push(make_texture_binding(
server,
material,
resource_definition,
render_context.renderer_resources,
fallback,
render_context.resource_manager,
render_context.texture_cache,
));
}
ShaderResourceKind::PropertyGroup(_) => {
if let Some((_, block_location)) = bundle_uniform_data
.material_property_group_blocks
.iter()
.find(|(binding, _)| *binding == resource_definition.binding)
{
material_bindings.push(
render_context
.uniform_memory_allocator
.block_to_binding(*block_location, resource_definition.binding),
);
}
}
},
}
}
for (instance, uniform_data) in self
.instances
.iter()
.zip(bundle_uniform_data.instance_blocks)
{
if !instance_filter(instance) {
continue;
}
let mut instance_bindings = ArrayVec::<ResourceBinding, 32>::new();
for resource_definition in shader.definition.resources.iter() {
let name = resource_definition.name.as_str();
match name {
"fyrox_instanceData" => {
instance_bindings.push(
render_context.uniform_memory_allocator.block_to_binding(
uniform_data.instance_block,
resource_definition.binding,
),
);
}
"fyrox_boneMatrices" => {
match uniform_data.bone_matrices_block {
Some(block) => {
instance_bindings.push(
render_context
.uniform_memory_allocator
.block_to_binding(block, resource_definition.binding),
);
}
None => {
instance_bindings.push(ResourceBinding::Buffer {
buffer: render_context
.renderer_resources
.bone_matrices_stub_uniform_buffer
.clone(),
binding: resource_definition.binding,
data_usage: Default::default(),
});
}
}
}
_ => (),
};
}
stats += render_context.frame_buffer.draw(
geometry,
render_context.viewport,
&render_pass.program,
&render_pass.draw_params,
&[
ResourceBindGroup {
bindings: &material_bindings,
},
ResourceBindGroup {
bindings: &instance_bindings,
},
],
instance.element_range,
)?;
}
Ok(stats)
}
}
pub trait RenderDataBundleStorageTrait {
fn push_triangles(
&mut self,
dynamic_surface_cache: &mut DynamicSurfaceCache,
layout: &[VertexAttributeDescriptor],
material: &MaterialResource,
render_path: RenderPath,
sort_index: u64,
node_handle: Handle<Node>,
func: &mut dyn FnMut(VertexBufferRefMut, TriangleBufferRefMut),
);
fn push(
&mut self,
data: &SurfaceResource,
material: &MaterialResource,
render_path: RenderPath,
sort_index: u64,
instance_data: SurfaceInstanceData,
);
}
pub enum LightSourceKind {
Spot {
full_cone_angle: f32,
hotspot_cone_angle: f32,
distance: f32,
shadow_bias: f32,
cookie_texture: Option<TextureResource>,
},
Point {
radius: f32,
shadow_bias: f32,
},
Directional {
csm_options: CsmOptions,
},
Unknown,
}
#[allow(missing_docs)] pub struct LightData<const N: usize = 16> {
pub count: usize,
pub color_radius: [Vector4<f32>; N],
pub position: [Vector3<f32>; N],
pub direction: [Vector3<f32>; N],
pub parameters: [Vector2<f32>; N],
}
impl<const N: usize> Default for LightData<N> {
fn default() -> Self {
Self {
count: 0,
color_radius: [Default::default(); N],
position: [Default::default(); N],
direction: [Default::default(); N],
parameters: [Default::default(); N],
}
}
}
pub struct LightSource {
pub handle: Handle<Node>,
pub global_transform: Matrix4<f32>,
pub kind: LightSourceKind,
pub position: Vector3<f32>,
pub up_vector: Vector3<f32>,
pub side_vector: Vector3<f32>,
pub look_vector: Vector3<f32>,
pub cast_shadows: bool,
pub local_scale: Vector3<f32>,
pub color: Color,
pub intensity: f32,
pub scatter_enabled: bool,
pub scatter: Vector3<f32>,
}
pub struct RenderDataBundleStorage {
bundle_map: FxHashMap<u64, usize>,
pub observer_position: ObserverPosition,
pub bundles: Vec<RenderDataBundle>,
pub light_sources: Vec<LightSource>,
pub environment_map: Option<TextureResource>,
}
pub struct RenderDataBundleStorageOptions {
pub collect_lights: bool,
}
impl Default for RenderDataBundleStorageOptions {
fn default() -> Self {
Self {
collect_lights: true,
}
}
}
impl RenderDataBundleStorage {
pub fn new_empty(observer_position: ObserverPosition) -> Self {
Self {
bundle_map: Default::default(),
observer_position,
bundles: Default::default(),
light_sources: Default::default(),
environment_map: None,
}
}
pub fn from_graph(
graph: &Graph,
render_mask: BitMask,
elapsed_time: f32,
observer_position: &ObserverPosition,
render_pass_name: ImmutableString,
options: RenderDataBundleStorageOptions,
dynamic_surface_cache: &mut DynamicSurfaceCache,
) -> Self {
let capacity = graph.node_count() as usize;
let mut storage = Self {
bundle_map: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher::default()),
observer_position: observer_position.clone(),
bundles: Vec::with_capacity(capacity),
light_sources: Default::default(),
environment_map: None,
};
let frustum = Frustum::from_view_projection_matrix(
observer_position.projection_matrix * observer_position.view_matrix,
)
.unwrap_or_default();
let mut lod_filter = vec![true; graph.capacity() as usize];
for (node_handle, node) in graph.pair_iter() {
if let Some(lod_group) = node.lod_group() {
for level in lod_group.levels.iter() {
for &object in level.objects.iter() {
if let Ok(object_ref) = graph.try_get_node(object) {
let distance = observer_position
.translation
.metric_distance(&object_ref.global_position());
let z_range = observer_position.z_far - observer_position.z_near;
let normalized_distance =
(distance - observer_position.z_near) / z_range;
let visible = normalized_distance >= level.begin()
&& normalized_distance <= level.end();
lod_filter[object.index() as usize] = visible;
}
}
}
}
if let Some(reflection_probe) = node.component_ref::<ReflectionProbe>() {
if (reflection_probe as &dyn NodeTrait)
.world_bounding_box()
.is_contains_point(observer_position.translation)
{
storage.environment_map = Some(reflection_probe.render_target().clone());
}
}
if options.collect_lights {
if let Some(base_light) = node.component_ref::<BaseLight>() {
if frustum.is_intersects_aabb(&node.world_bounding_box())
&& base_light.global_visibility()
&& base_light.is_globally_enabled()
{
let kind = if let Some(spot_light) = node.cast::<SpotLight>() {
LightSourceKind::Spot {
full_cone_angle: spot_light.full_cone_angle(),
hotspot_cone_angle: spot_light.hotspot_cone_angle(),
distance: spot_light.distance(),
shadow_bias: spot_light.shadow_bias(),
cookie_texture: spot_light.cookie_texture(),
}
} else if let Some(point_light) = node.cast::<PointLight>() {
LightSourceKind::Point {
radius: point_light.radius(),
shadow_bias: point_light.shadow_bias(),
}
} else if let Some(directional_light) = node.cast::<DirectionalLight>() {
LightSourceKind::Directional {
csm_options: (*directional_light.csm_options).clone(),
}
} else {
LightSourceKind::Unknown
};
let source = LightSource {
handle: node_handle,
global_transform: base_light.global_transform(),
kind,
position: base_light.global_position(),
up_vector: base_light.up_vector(),
side_vector: base_light.side_vector(),
look_vector: base_light.look_vector(),
cast_shadows: base_light.cast_shadows(),
local_scale: **base_light.local_transform().scale(),
color: base_light.color(),
intensity: base_light.intensity(),
scatter_enabled: base_light.is_scatter_enabled(),
scatter: base_light.scatter(),
};
storage.light_sources.push(source);
}
}
}
}
let mut ctx = RenderContext {
render_mask,
elapsed_time,
observer_position,
frustum: Some(&frustum),
storage: &mut storage,
graph,
render_pass_name: &render_pass_name,
dynamic_surface_cache,
};
#[inline(always)]
fn iterate_recursive(
node_handle: Handle<Node>,
graph: &Graph,
lod_filter: &[bool],
ctx: &mut RenderContext,
) {
if lod_filter[node_handle.index() as usize] {
let node = graph.node(node_handle);
if let RdcControlFlow::Continue = node.collect_render_data(ctx) {
for child in node.children() {
iterate_recursive(*child, graph, lod_filter, ctx);
}
}
}
}
iterate_recursive(graph.root(), graph, &lod_filter, &mut ctx);
storage.sort();
storage
}
pub fn sort(&mut self) {
self.bundles.sort_unstable_by_key(|b| b.sort_index);
}
pub fn write_global_uniform_blocks(
&self,
render_context: &mut BundleRenderContext,
) -> GlobalUniformData {
let mut light_data = LightData::<{ ShaderDefinition::MAX_LIGHTS }>::default();
for (i, light) in self
.light_sources
.iter()
.enumerate()
.take(ShaderDefinition::MAX_LIGHTS)
{
let color = light.color.as_frgb();
light_data.color_radius[i] = Vector4::new(color.x, color.y, color.z, 0.0);
light_data.position[i] = light.position;
light_data.direction[i] = light.up_vector;
match light.kind {
LightSourceKind::Spot {
full_cone_angle,
hotspot_cone_angle,
distance,
..
} => {
light_data.color_radius[i].w = distance;
light_data.parameters[i].x = (hotspot_cone_angle * 0.5).cos();
light_data.parameters[i].y = (full_cone_angle * 0.5).cos();
}
LightSourceKind::Point { radius, .. } => {
light_data.color_radius[i].w = radius;
light_data.parameters[i].x = std::f32::consts::PI.cos();
light_data.parameters[i].y = std::f32::consts::PI.cos();
}
LightSourceKind::Directional { .. } => {
light_data.color_radius[i].w = f32::INFINITY;
light_data.parameters[i].x = std::f32::consts::PI.cos();
light_data.parameters[i].y = std::f32::consts::PI.cos();
}
LightSourceKind::Unknown => {}
}
light_data.count += 1;
}
let lights_data = StaticUniformBuffer::<2048>::new()
.with(&(light_data.count as i32))
.with(&light_data.color_radius)
.with(&light_data.parameters)
.with(&light_data.position)
.with(&light_data.direction);
let lights_block = render_context
.uniform_memory_allocator
.allocate(lights_data);
let inv_view = self
.observer_position
.view_matrix
.try_inverse()
.unwrap_or_default();
let view_projection =
self.observer_position.projection_matrix * self.observer_position.view_matrix;
let camera_up = inv_view.up();
let camera_side = inv_view.side();
let camera_uniforms = StaticUniformBuffer::<512>::new()
.with(&view_projection)
.with(&self.observer_position.translation)
.with(&camera_up)
.with(&camera_side)
.with(&self.observer_position.z_near)
.with(&self.observer_position.z_far)
.with(&(self.observer_position.z_far - self.observer_position.z_near));
let camera_block = render_context
.uniform_memory_allocator
.allocate(camera_uniforms);
let graphics_settings = StaticUniformBuffer::<256>::new().with(&render_context.use_pom);
let graphics_settings_block = render_context
.uniform_memory_allocator
.allocate(graphics_settings);
GlobalUniformData {
camera_block,
lights_block,
graphics_settings_block,
}
}
pub fn render_to_frame_buffer<BundleFilter, InstanceFilter>(
&self,
server: &dyn GraphicsServer,
geometry_cache: &mut GeometryCache,
shader_cache: &mut ShaderCache,
mut bundle_filter: BundleFilter,
mut instance_filter: InstanceFilter,
mut render_context: BundleRenderContext,
) -> Result<RenderPassStatistics, FrameworkError>
where
BundleFilter: FnMut(&RenderDataBundle) -> bool,
InstanceFilter: FnMut(&SurfaceInstanceData) -> bool,
{
let global_uniforms = self.write_global_uniform_blocks(&mut render_context);
let view_projection =
self.observer_position.projection_matrix * self.observer_position.view_matrix;
let mut bundle_uniform_data_set = Vec::with_capacity(self.bundles.len());
for bundle in self.bundles.iter() {
if !bundle_filter(bundle) {
continue;
}
bundle_uniform_data_set
.push(bundle.write_uniforms(&view_projection, &mut render_context));
}
render_context.uniform_memory_allocator.upload(server)?;
let mut stats = RenderPassStatistics::default();
for (bundle, bundle_uniform_data) in self
.bundles
.iter()
.filter(|bundle| bundle_filter(bundle))
.zip(bundle_uniform_data_set)
{
if let Some(bundle_uniform_data) = bundle_uniform_data {
stats += bundle.render_to_frame_buffer(
server,
geometry_cache,
shader_cache,
&mut instance_filter,
&mut render_context,
bundle_uniform_data,
&global_uniforms,
)?
}
}
Ok(stats)
}
}
impl RenderDataBundleStorageTrait for RenderDataBundleStorage {
fn push_triangles(
&mut self,
dynamic_surface_cache: &mut DynamicSurfaceCache,
layout: &[VertexAttributeDescriptor],
material: &MaterialResource,
render_path: RenderPath,
sort_index: u64,
node_handle: Handle<Node>,
func: &mut dyn FnMut(VertexBufferRefMut, TriangleBufferRefMut),
) {
let mut hasher = FxHasher::default();
hasher.write_u64(material.key());
layout.hash(&mut hasher);
hasher.write_u64(sort_index);
hasher.write_u32(render_path as u32);
let key = hasher.finish();
let bundle = if let Some(&bundle_index) = self.bundle_map.get(&key) {
self.bundles.get_mut(bundle_index).unwrap()
} else {
self.bundle_map.insert(key, self.bundles.len());
self.bundles.push(RenderDataBundle {
data: dynamic_surface_cache.get_or_create(key, layout),
sort_index,
instances: vec![
SurfaceInstanceData {
node_handle,
..Default::default()
},
],
material: material.clone(),
render_path,
time_to_live: Default::default(),
});
self.bundles.last_mut().unwrap()
};
let mut data = bundle.data.data_ref();
let data = &mut *data;
let vertex_buffer = data.vertex_buffer.modify();
let triangle_buffer = data.geometry_buffer.modify();
func(vertex_buffer, triangle_buffer);
}
fn push(
&mut self,
data: &SurfaceResource,
material: &MaterialResource,
render_path: RenderPath,
sort_index: u64,
instance_data: SurfaceInstanceData,
) {
let mut hasher = FxHasher::default();
hasher.write_u64(material.key());
hasher.write_u64(data.key());
hasher.write_u32(render_path as u32);
let key = hasher.finish();
let bundle = if let Some(&bundle_index) = self.bundle_map.get(&key) {
self.bundles.get_mut(bundle_index).unwrap()
} else {
self.bundle_map.insert(key, self.bundles.len());
self.bundles.push(RenderDataBundle {
data: data.clone(),
sort_index,
instances: Default::default(),
material: material.clone(),
render_path,
time_to_live: Default::default(),
});
self.bundles.last_mut().unwrap()
};
bundle.instances.push(instance_data)
}
}
#[cfg(test)]
mod test {
use crate::renderer::bundle::{RenderContext, RenderDataBundleStorage};
use crate::renderer::observer::ObserverPosition;
use fyrox_core::algebra::{Matrix4, Vector3};
#[test]
fn test_calculate_sorting_index() {
let observer_position = ObserverPosition {
translation: Default::default(),
z_near: 0.0,
z_far: 0.0,
view_matrix: Matrix4::identity(),
projection_matrix: Matrix4::identity(),
view_projection_matrix: Matrix4::identity(),
};
let render_context = RenderContext {
render_mask: Default::default(),
elapsed_time: 0.0,
observer_position: &observer_position.clone(),
frustum: None,
storage: &mut RenderDataBundleStorage::new_empty(observer_position),
graph: &Default::default(),
render_pass_name: &Default::default(),
dynamic_surface_cache: &mut Default::default(),
};
let center = u64::MAX / 2;
assert_eq!(
render_context.calculate_sorting_index(Vector3::repeat(0.0)),
center
);
assert_eq!(
render_context.calculate_sorting_index(Vector3::new(0.0, 0.0, 1.0)),
center + 1000
);
assert_eq!(
render_context.calculate_sorting_index(Vector3::new(0.0, 0.0, 2.0)),
center + 2000
);
assert_eq!(
render_context.calculate_sorting_index(Vector3::new(0.0, 0.0, -3.0)),
center - 3000
);
}
}