use std::io::Read;
use std::marker::PhantomData;
use crate::{context::Has, Context, GameError, GameResult};
use super::{
context::GraphicsContext,
gpu::{
arc::{ArcBindGroup, ArcBindGroupLayout, ArcSampler, ArcShaderModule, ArcTextureView},
bind_group::BindGroupBuilder,
growing::GrowingBufferArena,
},
image::Image,
sampler::Sampler,
};
use crevice::std140::Std140;
#[derive(Debug, PartialEq, Eq)]
enum ShaderSource<'a> {
None,
Path(&'a str),
Code(&'a str),
}
#[derive(Debug)]
pub struct ShaderBuilder<'a> {
fs: ShaderSource<'a>,
vs: ShaderSource<'a>,
}
impl<'a> ShaderBuilder<'a> {
pub fn new() -> Self {
ShaderBuilder {
fs: ShaderSource::None,
vs: ShaderSource::None,
}
}
pub fn from_code(source: &'a str) -> Self {
ShaderBuilder {
fs: ShaderSource::Code(source),
vs: ShaderSource::Code(source),
}
}
pub fn from_path(path: &'a str) -> Self {
ShaderBuilder {
fs: ShaderSource::Path(path),
vs: ShaderSource::Path(path),
}
}
#[must_use]
pub fn fragment_code(self, source: &'a str) -> Self {
ShaderBuilder {
fs: ShaderSource::Code(source),
vs: self.vs,
}
}
#[must_use]
pub fn fragment_path(self, path: &'a str) -> Self {
ShaderBuilder {
fs: ShaderSource::Path(path),
vs: self.vs,
}
}
#[must_use]
pub fn vertex_code(self, source: &'a str) -> Self {
ShaderBuilder {
fs: self.vs,
vs: ShaderSource::Code(source),
}
}
#[must_use]
pub fn vertex_path(self, path: &'a str) -> Self {
ShaderBuilder {
fs: self.vs,
vs: ShaderSource::Path(path),
}
}
pub fn build(self, gfx: &impl Has<GraphicsContext>) -> GameResult<Shader> {
let gfx = gfx.retrieve();
let load = |s: &str| {
Some(ArcShaderModule::new(gfx.wgpu.device.create_shader_module(
wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(s.into()),
},
)))
};
let load_resource = |path: &str| -> GameResult<Option<ArcShaderModule>> {
let mut encoded = Vec::new();
_ = gfx.fs.open(path)?.read_to_end(&mut encoded)?;
Ok(load(
&String::from_utf8(encoded).map_err(GameError::ShaderEncodingError)?,
))
};
let load_any = |source| -> GameResult<Option<ArcShaderModule>> {
Ok(match source {
ShaderSource::Code(source) => load(source),
ShaderSource::Path(source) => load_resource(source)?,
ShaderSource::None => None,
})
};
Ok(if self.vs == self.fs {
let module = load_any(self.vs)?;
Shader {
vs_module: module.clone(),
fs_module: module,
}
} else {
Shader {
vs_module: load_any(self.vs)?,
fs_module: load_any(self.fs)?,
}
})
}
}
impl Default for ShaderBuilder<'_> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Shader {
pub(crate) vs_module: Option<ArcShaderModule>,
pub(crate) fs_module: Option<ArcShaderModule>,
}
use crevice::std140::AsStd140;
#[derive(Debug)]
pub struct ShaderParamsBuilder<'a, Uniforms: AsStd140> {
uniforms: &'a Uniforms,
images: &'a [&'a Image],
samplers: &'a [Sampler],
images_vs_visible: bool,
}
impl<'a, Uniforms: AsStd140> ShaderParamsBuilder<'a, Uniforms> {
pub fn new(uniforms: &'a Uniforms) -> Self {
ShaderParamsBuilder {
uniforms,
images: &[],
samplers: &[],
images_vs_visible: false,
}
}
#[must_use]
pub fn images(
self,
images: &'a [&'a Image],
samplers: &'a [Sampler],
vs_visible: bool,
) -> Self {
ShaderParamsBuilder {
uniforms: self.uniforms,
images,
samplers,
images_vs_visible: vs_visible,
}
}
pub fn build(self, ctx: &mut Context) -> ShaderParams<Uniforms> {
let images = self.images.iter().map(|image| image.view.clone()).collect();
let samplers = self
.samplers
.iter()
.map(|&sampler| ctx.gfx.sampler_cache.get(&ctx.gfx.wgpu.device, sampler))
.collect();
let mut params = ShaderParams {
uniform_arena: GrowingBufferArena::new(
&ctx.gfx.wgpu.device,
u64::from(
ctx.gfx
.wgpu
.device
.limits()
.min_uniform_buffer_offset_alignment,
),
wgpu::BufferDescriptor {
label: None,
size: ShaderParams::<Uniforms>::UPDATES_PER_ARENA
* Uniforms::std140_size_static() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
},
),
layout: None,
bind_group: None,
buffer_offset: 0,
images,
samplers,
images_vs_visible: self.images_vs_visible,
last_tick: 0,
_marker: PhantomData,
};
params.set_uniforms(ctx, self.uniforms);
params
}
}
#[derive(Debug)]
pub struct ShaderParams<Uniforms: AsStd140> {
uniform_arena: GrowingBufferArena,
pub(crate) layout: Option<ArcBindGroupLayout>,
pub(crate) bind_group: Option<ArcBindGroup>,
pub(crate) buffer_offset: u32,
images: Vec<ArcTextureView>,
samplers: Vec<ArcSampler>,
images_vs_visible: bool,
last_tick: usize,
_marker: PhantomData<Uniforms>,
}
impl<Uniforms: AsStd140> ShaderParams<Uniforms> {
const UPDATES_PER_ARENA: u64 = 16;
pub fn set_uniforms(&mut self, ctx: &mut Context, uniforms: &Uniforms) {
if ctx.time.ticks() != self.last_tick {
self.uniform_arena.free();
self.last_tick = ctx.time.ticks();
}
let alloc = self
.uniform_arena
.allocate(&ctx.gfx.wgpu.device, Uniforms::std140_size_static() as u64);
ctx.gfx.wgpu.queue.write_buffer(
&alloc.buffer,
alloc.offset,
uniforms.as_std140().as_bytes(),
);
self.buffer_offset = alloc.offset as u32;
let mut builder = BindGroupBuilder::new();
builder = builder.buffer(
&alloc.buffer,
0,
wgpu::ShaderStages::VERTEX_FRAGMENT,
wgpu::BufferBindingType::Uniform,
true,
None,
);
let vis = if self.images_vs_visible {
wgpu::ShaderStages::VERTEX_FRAGMENT
} else {
wgpu::ShaderStages::FRAGMENT
};
for view in &self.images {
builder = builder.image(view, vis);
}
for sampler in &self.samplers {
builder = builder.sampler(sampler, vis);
}
let (bind_group, layout) =
builder.create(&ctx.gfx.wgpu.device, &mut ctx.gfx.bind_group_cache);
self.layout = Some(layout);
self.bind_group = Some(bind_group);
}
}
pub use wgpu::{BlendComponent, BlendFactor, BlendOperation};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlendMode {
pub color: BlendComponent,
pub alpha: BlendComponent,
}
impl BlendMode {
pub const ADD: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const SUBTRACT: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::ReverseSubtract,
},
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const ALPHA: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const INVERT: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::Constant,
dst_factor: BlendFactor::Src,
operation: BlendOperation::Subtract,
},
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const MULTIPLY: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::Dst,
dst_factor: BlendFactor::Zero,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::DstAlpha,
dst_factor: BlendFactor::Zero,
operation: BlendOperation::Add,
},
};
pub const REPLACE: Self = BlendMode {
color: wgpu::BlendState::REPLACE.color,
alpha: wgpu::BlendState::REPLACE.alpha,
};
pub const LIGHTEN: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Max,
},
alpha: BlendComponent {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const DARKEN: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Min,
},
alpha: BlendComponent {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
pub const PREMULTIPLIED: Self = BlendMode {
color: BlendComponent {
src_factor: BlendFactor::One,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::OneMinusDstAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
}