syrillian_render 0.7.1

Renderer of the Syrillian Game Engine
Documentation
use crate::cache::{AssetCache, CacheType, GpuTexture};
use std::collections::HashMap;
use std::sync::Arc;
use syrillian_asset::MaterialInstance;
use syrillian_asset::material_inputs::MaterialInputLayout;
use syrillian_asset::{HMaterial, MaterialShaderSet};
use syrillian_shadergen::generator::MeshSkinning;
use wgpu::{
    BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, Device, Queue, TextureFormat,
};

#[derive(Debug)]
pub struct RuntimeMaterial {
    pub immediates: Vec<u8>,
    pub bind_group: BindGroup,
    pub shader_unskinned: MaterialShaderSet,
    pub shader_skinned: MaterialShaderSet,
    pub transparent: bool,
    pub cast_shadows: bool,
}

#[derive(Debug)]
pub enum MaterialError {
    MaterialNotFound,
    DeviceNotInitialized,
    QueueNotInitialized,
}

fn material_textures(
    instance: &MaterialInstance,
    layout: &MaterialInputLayout,
    cache: &AssetCache,
) -> (Vec<Arc<GpuTexture>>, HashMap<String, Arc<GpuTexture>>) {
    let mut ordered = Vec::new();
    let mut by_name = HashMap::new();

    for tex in &layout.textures {
        let handle = instance
            .textures
            .get(&tex.name)
            .and_then(|v| *v)
            .unwrap_or(tex.default);
        let gpu = cache.texture(handle);
        ordered.push(gpu.clone());
        by_name.insert(tex.name.clone(), gpu);
    }

    (ordered, by_name)
}

impl CacheType for MaterialInstance {
    type Hot = Arc<RuntimeMaterial>;

    fn upload(mut self, device: &Device, _queue: &Queue, cache: &AssetCache) -> Self::Hot {
        let material_def = cache
            .store()
            .materials
            .try_get(self.material)
            .map(|m| m.clone())
            .unwrap_or_else(|| cache.store().materials.get(HMaterial::FALLBACK).clone());

        let layout = material_def.layout().clone();
        let shader_unskinned = material_def.shader_set(MeshSkinning::Unskinned);
        let shader_skinned = material_def.shader_set(MeshSkinning::Skinned);

        let (textures, texture_map) = material_textures(&self, &layout, cache);

        for tex in &layout.textures {
            let flag = format!("use_{}_texture", tex.name);
            let use_tex = self.textures.get(&tex.name).and_then(|v| *v).is_some();
            self.set_bool(&flag, use_tex, &layout);
        }

        if let Some(tex) = texture_map.get("diffuse") {
            let grayscale = tex.format == TextureFormat::Rg8Unorm;
            self.set_bool("grayscale_diffuse", grayscale, &layout);
        }

        let cast_shadows = self.value_bool("cast_shadows").unwrap_or(true);

        let alpha = self.value_f32("alpha").unwrap_or(1.0);
        let has_transparency_flag = self.value_bool("has_transparency").unwrap_or(false);
        let diffuse_has_transparency = texture_map
            .get("diffuse")
            .is_some_and(|t| t.has_transparency);
        let transparent = alpha < 1.0 || has_transparency_flag || diffuse_has_transparency;

        let immediates = layout.pack_immediates(&self.values);

        let bgl = cache.material_layout(&layout);
        let mut entries: Vec<BindGroupEntry> = Vec::new();
        let mut binding = 0u32;
        for tex in &textures {
            entries.push(BindGroupEntry {
                binding,
                resource: BindingResource::TextureView(&tex.view),
            });
            binding += 1;
            entries.push(BindGroupEntry {
                binding,
                resource: BindingResource::Sampler(&tex.sampler),
            });
            binding += 1;
        }

        let bind_group = device.create_bind_group(&BindGroupDescriptor {
            label: Some("Material Bind Group"),
            layout: &bgl,
            entries: &entries,
        });

        Arc::new(RuntimeMaterial {
            immediates,
            bind_group,
            shader_unskinned,
            shader_skinned,
            transparent,
            cast_shadows,
        })
    }
}