use awsm_renderer_core::command::{
color::Color,
render_pass::{
ColorAttachment, DepthStencilAttachment, RenderPassDescriptor, RenderPassEncoder,
},
CommandEncoder, LoadOp, StoreOp,
};
use awsm_renderer_core::renderer::AwsmRendererWebGpu;
use awsm_renderer_core::texture::blit::blit_tex;
use crate::anti_alias::AntiAliasing;
use crate::bind_groups::{BindGroupCreate, BindGroupRecreateContext, BindGroups};
use crate::error::{AwsmError, Result};
use crate::instances::Instances;
use crate::materials::Materials;
use crate::meshes::Meshes;
use crate::pipelines::Pipelines;
use crate::post_process::PostProcessing;
use crate::render_passes::RenderPasses;
use crate::render_textures::{RenderTextureViews, RenderTextures};
use crate::transforms::Transforms;
use crate::{AwsmRenderer, AwsmRendererLogging};
#[derive(Default)]
pub struct RenderHooks {
pub pre_render: Option<Box<dyn Fn(&mut AwsmRenderer) -> Result<()>>>,
pub first_pass: Option<Box<dyn Fn(&RenderContext) -> Result<()>>>,
pub after_geometry_pass: Option<Box<dyn Fn(&RenderContext) -> Result<()>>>,
pub before_transparent_pass: Option<Box<dyn Fn(&RenderContext) -> Result<()>>>,
pub after_transparent_pass: Option<Box<dyn Fn(&RenderContext) -> Result<()>>>,
pub last_pass: Option<Box<dyn Fn(&RenderContext) -> Result<()>>>,
pub post_render: Option<Box<dyn Fn(&mut AwsmRenderer) -> Result<()>>>,
}
impl AwsmRenderer {
pub fn render(&mut self, hooks: Option<&RenderHooks>) -> Result<()> {
if let Some(hook) = hooks.and_then(|h| h.pre_render.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "PreRender Hook").entered())
} else {
None
};
hook(self)?;
}
}
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Render").entered())
} else {
None
};
self.render_textures.next_frame();
self.transforms
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.materials
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.lights
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.instances.write_gpu(&self.logging, &self.gpu)?;
self.meshes
.skins
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.meshes
.morphs
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.meshes
.meta
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.textures.write_texture_transforms_gpu(
&self.logging,
&self.gpu,
&mut self.bind_groups,
)?;
self.meshes
.write_gpu(&self.logging, &self.gpu, &mut self.bind_groups)?;
self.camera
.write_gpu(&self.logging, &self.gpu, &self.bind_groups)?;
let render_texture_views = self
.render_textures
.views(&self.gpu, self.anti_aliasing.clone())?;
if render_texture_views.size_changed {
self.bind_groups
.mark_create(BindGroupCreate::TextureViewRecreate);
}
self.bind_groups.recreate(
BindGroupRecreateContext {
gpu: &self.gpu,
render_texture_views: &render_texture_views,
textures: &self.textures,
materials: &self.materials,
bind_group_layouts: &mut self.bind_group_layouts,
meshes: &self.meshes,
camera: &self.camera,
environment: &self.environment,
lights: &self.lights,
transforms: &self.transforms,
anti_aliasing: &self.anti_aliasing,
},
&mut self.render_passes,
&mut self.picker,
)?;
let ctx = RenderContext {
gpu: &self.gpu,
command_encoder: self.gpu.create_command_encoder(Some("Rendering")),
render_texture_views,
logging: &self.logging,
render_textures: &self.render_textures,
transforms: &self.transforms,
meshes: &self.meshes,
materials: &self.materials,
pipelines: &self.pipelines,
instances: &self.instances,
bind_groups: &self.bind_groups,
render_passes: &self.render_passes,
anti_aliasing: &self.anti_aliasing,
post_processing: &self.post_processing,
clear_color: &self._clear_color,
};
let renderables = self.collect_renderables(&ctx)?;
if let Some(hook) = hooks.and_then(|h| h.first_pass.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "FirstPass Hook").entered())
} else {
None
};
hook(&ctx)?;
}
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Geometry RenderPass").entered())
} else {
None
};
self.render_passes
.geometry
.render(&ctx, &renderables.opaque, false)?;
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "HUD Geometry RenderPass").entered())
} else {
None
};
self.render_passes
.geometry
.render(&ctx, &renderables.hud, true)?;
}
if let Some(hook) = hooks.and_then(|h| h.after_geometry_pass.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "AfterGeometryPass Hook").entered())
} else {
None
};
hook(&ctx)?;
}
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Light Culling RenderPass").entered())
} else {
None
};
self.render_passes.light_culling.render(&ctx)?;
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Clear opaque").entered())
} else {
None
};
self.render_textures.clear_opaque(&self.gpu)?;
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Material Opaque RenderPass").entered())
} else {
None
};
self.render_passes
.material_opaque
.render(&ctx, renderables.opaque)?;
}
{
let _maybe_span_guard = if ctx.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Opaque to Transparent Blit").entered())
} else {
None
};
blit_tex(
match &ctx.anti_aliasing.msaa_sample_count {
Some(sample_count) if *sample_count == 4 => {
&ctx.render_textures
.opaque_to_transparent_blit_pipeline_msaa_4
}
None => {
&ctx.render_textures
.opaque_to_transparent_blit_pipeline_no_anti_alias
}
Some(count) => {
return Err(AwsmError::UnsupportedMsaaCount(*count));
}
},
match &ctx.anti_aliasing.msaa_sample_count {
Some(sample_count) if *sample_count == 4 => {
&ctx.render_texture_views
.opaque_to_transparent_blit_bind_group_msaa_4
}
None => {
&ctx.render_texture_views
.opaque_to_transparent_blit_bind_group_no_anti_alias
}
Some(count) => {
return Err(AwsmError::UnsupportedMsaaCount(*count));
}
},
&ctx.render_texture_views.transparent,
&ctx.command_encoder,
)?;
}
if let Some(hook) = hooks.and_then(|h| h.before_transparent_pass.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(
tracing::span!(tracing::Level::INFO, "BeforeTransparentPass Hook")
.entered(),
)
} else {
None
};
hook(&ctx)?;
}
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(
tracing::span!(tracing::Level::INFO, "Material Transparent RenderPass")
.entered(),
)
} else {
None
};
self.render_passes
.material_transparent
.render(&ctx, renderables.transparent, false)?;
}
if let Some(hook) = hooks.and_then(|h| h.after_transparent_pass.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(
tracing::span!(tracing::Level::INFO, "AfterTransparentPass Hook").entered(),
)
} else {
None
};
hook(&ctx)?;
}
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "HUD RenderPass").entered())
} else {
None
};
self.render_passes
.material_transparent
.render(&ctx, renderables.hud, true)?;
}
if let Some(bind_group) = &ctx
.render_texture_views
.transparent_to_composite_blit_bind_group_no_anti_alias
{
let _maybe_span_guard = if self.logging.render_timings {
Some(
tracing::span!(tracing::Level::INFO, "Non-antialised composite blit").entered(),
)
} else {
None
};
blit_tex(
&ctx.render_textures
.transparent_to_composite_blit_pipeline_no_anti_alias,
bind_group,
&ctx.render_texture_views.composite,
&ctx.command_encoder,
)?;
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Effects RenderPass").entered())
} else {
None
};
self.render_passes.effects.render(&ctx)?;
}
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Display RenderPass").entered())
} else {
None
};
self.render_passes.display.render(&ctx)?;
}
if let Some(hook) = hooks.and_then(|h| h.last_pass.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "LastPass Hook").entered())
} else {
None
};
hook(&ctx)?;
}
}
self.gpu.submit_commands(&ctx.command_encoder.finish());
if let Some(hook) = hooks.and_then(|h| h.post_render.as_ref()) {
{
let _maybe_span_guard = if self.logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "PostRender Hook").entered())
} else {
None
};
hook(self)?;
}
}
Ok(())
}
}
pub struct RenderContext<'a> {
pub gpu: &'a AwsmRendererWebGpu,
pub command_encoder: CommandEncoder,
pub render_texture_views: RenderTextureViews,
pub logging: &'a AwsmRendererLogging,
pub render_textures: &'a RenderTextures,
pub transforms: &'a Transforms,
pub meshes: &'a Meshes,
pub pipelines: &'a Pipelines,
pub materials: &'a Materials,
pub instances: &'a Instances,
pub bind_groups: &'a BindGroups,
pub render_passes: &'a RenderPasses,
pub anti_aliasing: &'a AntiAliasing,
pub post_processing: &'a PostProcessing,
pub clear_color: &'a Color,
}
impl<'a> RenderContext<'a> {
pub fn begin_world_geometry_extension_pass(
&'a self,
label: Option<&'a str>,
) -> Result<RenderPassEncoder> {
self.command_encoder
.begin_render_pass(
&RenderPassDescriptor {
label,
color_attachments: vec![
ColorAttachment::new(
&self.render_texture_views.visibility_data,
LoadOp::Load,
StoreOp::Store,
),
ColorAttachment::new(
&self.render_texture_views.barycentric,
LoadOp::Load,
StoreOp::Store,
),
ColorAttachment::new(
&self.render_texture_views.normal_tangent,
LoadOp::Load,
StoreOp::Store,
),
ColorAttachment::new(
&self.render_texture_views.barycentric_derivatives,
LoadOp::Load,
StoreOp::Store,
),
],
depth_stencil_attachment: Some(
DepthStencilAttachment::new(&self.render_texture_views.depth)
.with_depth_load_op(LoadOp::Load)
.with_depth_store_op(StoreOp::Store),
),
..Default::default()
}
.into(),
)
.map_err(Into::into)
}
pub fn begin_world_transparent_pass(
&'a self,
label: Option<&'a str>,
) -> Result<RenderPassEncoder> {
let mut color_attachment = ColorAttachment::new(
&self.render_texture_views.transparent,
LoadOp::Load,
StoreOp::Store,
);
if self.anti_aliasing.msaa_sample_count.is_some() {
color_attachment =
color_attachment.with_resolve_target(&self.render_texture_views.composite);
}
self.command_encoder
.begin_render_pass(
&RenderPassDescriptor {
label,
color_attachments: vec![color_attachment],
depth_stencil_attachment: Some(
DepthStencilAttachment::new(&self.render_texture_views.depth)
.with_depth_load_op(LoadOp::Load)
.with_depth_store_op(StoreOp::Store),
),
..Default::default()
}
.into(),
)
.map_err(Into::into)
}
pub fn begin_hud_transparent_pass(
&'a self,
label: Option<&'a str>,
) -> Result<RenderPassEncoder> {
let mut color_attachment = ColorAttachment::new(
&self.render_texture_views.transparent,
LoadOp::Load,
StoreOp::Store,
);
if self.anti_aliasing.msaa_sample_count.is_some() {
color_attachment =
color_attachment.with_resolve_target(&self.render_texture_views.composite);
}
self.command_encoder
.begin_render_pass(
&RenderPassDescriptor {
label,
color_attachments: vec![color_attachment],
depth_stencil_attachment: Some(
DepthStencilAttachment::new(&self.render_texture_views.hud_depth)
.with_depth_load_op(LoadOp::Clear)
.with_depth_clear_value(1.0)
.with_depth_store_op(StoreOp::Store),
),
..Default::default()
}
.into(),
)
.map_err(Into::into)
}
pub fn begin_display_overlay_pass(
&'a self,
label: Option<&'a str>,
) -> Result<RenderPassEncoder> {
self.command_encoder
.begin_render_pass(
&RenderPassDescriptor {
label,
color_attachments: vec![ColorAttachment::new(
&self.gpu.current_context_texture_view()?,
LoadOp::Load,
StoreOp::Store,
)],
..Default::default()
}
.into(),
)
.map_err(Into::into)
}
}