limelight 0.1.3

WebGL2 wrapper with a focus on making high-performance graphics code easier to write and maintain
Documentation
use std::collections::{BTreeMap, HashMap};

use crate::{
    attribute::Attribute,
    buffer::BufferLike,
    program::ProgramLike,
    shadow_gpu::{AttributeInfo, BufferBinding, BufferHandle, GpuState, ShadowGpu},
};
use anyhow::Result;
use web_sys::WebGl2RenderingContext;

pub struct Renderer {
    gpu: ShadowGpu,
}

enum DrawCall {
    DrawArrays {
        first: usize,
        count: usize,
    },
    DrawArraysInstanced {
        first: usize,
        count: usize,
        instances: usize,
    },
}

struct BufferBindingGroup {
    bindings: BTreeMap<BufferHandle, Vec<BufferBinding>>,
    attributes: HashMap<String, AttributeInfo>,
}

impl BufferBindingGroup {
    fn new(attributes: HashMap<String, AttributeInfo>) -> Self {
        Self {
            attributes,
            bindings: BTreeMap::new(),
        }
    }

    fn add_buffer<T: Attribute>(&mut self, buffer: &impl BufferLike<T>, divisor: u32) {
        if let Some(buffer) = buffer.get_buffer() {
            let stride = T::describe().into_iter().map(|d| d.kind.byte_size()).sum();
            let mut offset = 0;
            let mut bindings = Vec::new();

            for attribute in T::describe() {
                if let Some(program_binding) = self.attributes.get(&attribute.variable_name) {
                    if attribute.kind != program_binding.kind.as_sized_type() {
                        log::warn!(
                            "The variable {} has type {:?} as an attribute, \
                            but {:?} in the program definition.",
                            attribute.variable_name,
                            attribute.kind,
                            program_binding.kind
                        );
                    }

                    bindings.push(BufferBinding {
                        kind: attribute.kind,
                        divisor,
                        location: program_binding.location as _,
                        normalized: false,
                        offset,
                        stride,
                    });

                    offset += attribute.kind.byte_size();
                } else {
                    log::warn!(
                        "Attribute has variable {}, which isn't used in the program.",
                        attribute.variable_name
                    );
                }
            }

            if bindings.is_empty() {
                log::warn!("No attributes in the buffer overlapped with the program.");
            } else {
                self.bindings.insert(buffer, bindings);
            }
        }
    }
}

impl Renderer {
    pub fn new(gl: WebGl2RenderingContext) -> Self {
        let gpu = ShadowGpu::new(gl);
        Renderer { gpu }
    }

    fn render_impl<T: Attribute, I: Attribute>(
        &mut self,
        draw_call: DrawCall,
        program: &mut impl ProgramLike<T, I>,

        buffers: BTreeMap<BufferHandle, Vec<BufferBinding>>,
    ) -> Result<()> {
        let bound_program = program.get_program(&self.gpu)?;

        let mut uniforms = HashMap::new();
        for (uniform_handle, uniform) in &bound_program.uniforms {
            uniforms.insert(uniform_handle.clone(), uniform.get_value());
        }

        let state: GpuState = GpuState {
            program: Some(bound_program.handle()),
            buffers,
            uniforms,
            globals: program.globals(),
        };

        match draw_call {
            DrawCall::DrawArrays { count, first } => {
                self.gpu
                    .draw_arrays(&state, program.draw_mode(), first as _, count as _)?
            }
            DrawCall::DrawArraysInstanced {
                first,
                count,
                instances,
            } => self.gpu.draw_arrays_instanced(
                &state,
                program.draw_mode(),
                first as _,
                count as _,
                instances as _,
            )?,
        }

        Ok(())
    }

    pub fn render<T: Attribute>(
        &mut self,
        program: &mut impl ProgramLike<T, ()>,
        vertex_buffer: &impl BufferLike<T>,
    ) -> Result<()> {
        let bound_program = program.get_program(&self.gpu)?;
        let program_attributes = bound_program.attributes();

        let mut bg = BufferBindingGroup::new(program_attributes.clone());
        bg.add_buffer(vertex_buffer, 0);

        self.render_impl(
            DrawCall::DrawArrays {
                first: 0,
                count: vertex_buffer.len(),
            },
            program,
            bg.bindings,
        )
    }

    pub fn render_instanced<T: Attribute, I: Attribute>(
        &mut self,
        program: &mut impl ProgramLike<T, I>,
        vertex_buffer: &impl BufferLike<T>,
        instance_buffer: &impl BufferLike<I>,
    ) -> Result<()> {
        let bound_program = program.get_program(&self.gpu)?;
        let program_attributes = bound_program.attributes();

        let mut bg = BufferBindingGroup::new(program_attributes.clone());
        bg.add_buffer(vertex_buffer, 0);
        bg.add_buffer(instance_buffer, 1);

        self.render_impl(
            DrawCall::DrawArraysInstanced {
                first: 0,
                count: vertex_buffer.len(),
                instances: instance_buffer.len(),
            },
            program,
            bg.bindings,
        )
    }
}

pub trait Drawable {
    fn draw(&mut self, renderer: &mut Renderer) -> Result<()>;
}