use std::future::Future;
use std::pin::Pin;
use awsm_renderer_core::renderer::AwsmRendererWebGpu;
use slotmap::SlotMap;
use wasm_bindgen::JsValue;
use crate::{
bind_group_layout::BindGroupLayouts,
error::Result,
pipeline_layouts::PipelineLayouts,
pipeline_scheduler::warn_pipeline_not_compiled,
pipelines::{
render_pipeline::{RenderPipelineKey, RenderPipelines, RenderPipelinesPrep},
Pipelines,
},
render::RenderContext,
render_textures::RenderTextureFormats,
shaders::Shaders,
};
use super::pipelines::{LinePipelines, LinePipelinesDescriptors, LineVariantKey};
use super::shader::cache_key::ShaderCacheKeyLine;
use super::types::{GpuLineSegment, LineEntry, LineKey, LINE_UNIFORM_BYTES};
struct LineInflightCompile {
prep: RenderPipelinesPrep,
#[allow(clippy::type_complexity)]
joined: Pin<
Box<dyn Future<Output = Vec<std::result::Result<web_sys::GpuRenderPipeline, JsValue>>>>,
>,
}
pub struct LineRenderer {
pub(super) pipelines: LinePipelines,
pub(super) entries: SlotMap<LineKey, LineEntry>,
pub(super) pack_buf: Vec<GpuLineSegment>,
pub(super) pipelines_compile_requested: bool,
inflight: Option<LineInflightCompile>,
}
pub struct LineRendererDescriptors {
pub(super) inner: LinePipelinesDescriptors,
}
impl LineRendererDescriptors {
pub fn pipeline_cache_keys(
&self,
) -> &[crate::pipelines::render_pipeline::RenderPipelineCacheKey] {
&self.inner.pipeline_cache_keys
}
}
impl LineRenderer {
pub async fn load(
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &mut BindGroupLayouts,
pipeline_layouts: &mut PipelineLayouts,
pipelines: &mut Pipelines,
shaders: &mut Shaders,
formats: &RenderTextureFormats,
) -> Result<Self> {
let descs =
Self::build_descriptors(gpu, bind_group_layouts, pipeline_layouts, shaders, formats)
.await?;
let resolved = pipelines
.render
.ensure_keys(
gpu,
shaders,
pipeline_layouts,
descs.inner.pipeline_cache_keys.clone(),
)
.await?;
Ok(Self::from_resolved(descs, resolved))
}
pub fn new_deferred(
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &mut BindGroupLayouts,
) -> Result<Self> {
Ok(Self {
pipelines: LinePipelines::register_layouts_only(gpu, bind_group_layouts)?,
entries: SlotMap::with_key(),
pack_buf: Vec::new(),
pipelines_compile_requested: false,
inflight: None,
})
}
pub async fn ensure_pipelines_compiled(
&mut self,
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &mut BindGroupLayouts,
pipeline_layouts: &mut PipelineLayouts,
pipelines: &mut Pipelines,
shaders: &mut Shaders,
formats: &RenderTextureFormats,
) -> Result<()> {
if self.pipelines.variants.is_some() {
self.pipelines_compile_requested = false;
return Ok(());
}
let descs = LinePipelines::build_descriptors(
gpu,
bind_group_layouts,
pipeline_layouts,
shaders,
formats,
)
.await?;
let resolved = pipelines
.render
.ensure_keys(
gpu,
shaders,
pipeline_layouts,
descs.pipeline_cache_keys.clone(),
)
.await?;
self.pipelines.install_resolved(resolved);
self.pipelines_compile_requested = false;
Ok(())
}
pub fn pipelines_compile_requested(&self) -> bool {
self.pipelines_compile_requested
}
pub fn kick_compile(
&mut self,
gpu: &AwsmRendererWebGpu,
shaders: &Shaders,
bind_group_layouts: &mut BindGroupLayouts,
pipeline_layouts: &mut PipelineLayouts,
formats: &RenderTextureFormats,
) -> Result<()> {
if !self.pipelines_compile_requested
|| self.pipelines.variants.is_some()
|| self.inflight.is_some()
{
return Ok(());
}
let Some(shader_key) = shaders.get_cached_key(ShaderCacheKeyLine) else {
return Ok(());
};
let cache_keys = self.pipelines.build_cache_keys_sync(
gpu,
bind_group_layouts,
pipeline_layouts,
shader_key,
formats,
)?;
let mut prepped =
RenderPipelines::ensure_keys_prepare(gpu, shaders, pipeline_layouts, cache_keys)?;
let promises = std::mem::take(&mut prepped.promises);
let joined = Box::pin(futures::future::join_all(promises));
self.inflight = Some(LineInflightCompile {
prep: prepped.prep,
joined,
});
Ok(())
}
pub fn poll_compile(&mut self, render_pipelines: &mut RenderPipelines) -> Result<()> {
use futures::FutureExt;
let ready = match self.inflight.as_mut() {
Some(inflight) => inflight.joined.as_mut().now_or_never(),
None => return Ok(()),
};
let Some(results) = ready else {
return Ok(());
};
let inflight = self
.inflight
.take()
.expect("inflight present (just polled Some)");
let keys = render_pipelines.ensure_keys_install(inflight.prep, results)?;
self.pipelines.install_resolved(keys);
self.pipelines_compile_requested = false;
Ok(())
}
pub async fn build_descriptors(
gpu: &AwsmRendererWebGpu,
bind_group_layouts: &mut BindGroupLayouts,
pipeline_layouts: &mut PipelineLayouts,
shaders: &mut Shaders,
formats: &RenderTextureFormats,
) -> Result<LineRendererDescriptors> {
let inner = LinePipelines::build_descriptors(
gpu,
bind_group_layouts,
pipeline_layouts,
shaders,
formats,
)
.await?;
Ok(LineRendererDescriptors { inner })
}
pub fn from_resolved(descs: LineRendererDescriptors, resolved: Vec<RenderPipelineKey>) -> Self {
Self {
pipelines: LinePipelines::from_resolved(descs.inner, resolved),
entries: SlotMap::with_key(),
pack_buf: Vec::new(),
pipelines_compile_requested: false,
inflight: None,
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl LineRenderer {
pub fn render(&self, ctx: &RenderContext) -> Result<()> {
if self.entries.is_empty() {
return Ok(());
}
if self.pipelines.variants.is_none() {
warn_pipeline_not_compiled("line_pass", "all_variants");
return Ok(());
}
let msaa = ctx.anti_aliasing.has_msaa_checked()?;
let viewport_w = ctx.render_texture_views.width as f32;
let viewport_h = ctx.render_texture_views.height as f32;
let render_pass = ctx.begin_world_transparent_pass(Some("Line Render Pass"))?;
let mut current_variant: Option<LineVariantKey> = None;
for entry in self.entries.values() {
if entry.segment_count == 0 {
continue;
}
let mut uniform_bytes = [0u8; LINE_UNIFORM_BYTES];
uniform_bytes[0..4].copy_from_slice(&entry.width_px.to_le_bytes());
uniform_bytes[4..8].copy_from_slice(&viewport_w.to_le_bytes());
uniform_bytes[8..12].copy_from_slice(&viewport_h.to_le_bytes());
entry.uniform_uploader.lock().unwrap().write_dirty_ranges(
ctx.gpu,
&entry.uniform_buffer,
LINE_UNIFORM_BYTES,
&uniform_bytes[..],
&[(0, LINE_UNIFORM_BYTES)],
)?;
let variant = LineVariantKey {
depth_test_always: entry.depth_test_always,
msaa,
};
if current_variant != Some(variant) {
let Some(pipeline_key) = self.pipelines.get(variant) else {
warn_pipeline_not_compiled("line_pass", "variant");
continue;
};
render_pass.set_pipeline(ctx.pipelines.render.get(pipeline_key)?);
current_variant = Some(variant);
}
render_pass.set_bind_group(0, &entry.bind_group, None)?;
render_pass.draw_with_instance_count(4, entry.segment_count);
}
render_pass.end();
Ok(())
}
}