1use crate::{Context, GameError, GameResult};
2use miniquad::{
3 BlendFactor, BlendState, BlendValue, BufferLayout, Equation, PipelineParams, VertexAttribute,
4 VertexFormat, VertexStep,
5};
6use std::cell::RefCell;
7use std::io::Read;
8use std::path::Path;
9use std::rc::Rc;
10
11use crate::graphics::context::default_shader;
12use bytemuck::Pod;
13use cgmath::Matrix4;
14pub use miniquad::{ShaderMeta, UniformBlockLayout, UniformDesc, UniformType};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20pub enum BlendMode {
21 Add,
24 Subtract,
27 Alpha,
32 Multiply,
34 Replace,
36 Premultiplied,
42}
43
44impl From<BlendMode> for (BlendState, BlendState) {
45 fn from(bm: BlendMode) -> Self {
46 match bm {
47 BlendMode::Add => (
48 BlendState::new(
49 Equation::Add,
50 BlendFactor::Value(BlendValue::SourceAlpha),
51 BlendFactor::One,
52 ),
53 BlendState::new(
54 Equation::Add,
55 BlendFactor::Value(BlendValue::SourceAlpha),
56 BlendFactor::One,
57 ),
58 ),
59 BlendMode::Subtract => (
60 BlendState::new(
61 Equation::ReverseSubtract,
62 BlendFactor::Value(BlendValue::SourceAlpha),
63 BlendFactor::One,
64 ),
65 BlendState::new(Equation::Add, BlendFactor::Zero, BlendFactor::One),
66 ),
67 BlendMode::Alpha => (
68 BlendState::new(
69 Equation::Add,
70 BlendFactor::Value(BlendValue::SourceAlpha),
71 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
72 ),
73 BlendState::new(
74 Equation::Add,
75 BlendFactor::OneMinusValue(BlendValue::DestinationAlpha),
76 BlendFactor::One,
77 ),
78 ),
79 BlendMode::Premultiplied => (
80 BlendState::new(
81 Equation::Add,
82 BlendFactor::One,
83 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
84 ),
85 BlendState::new(
86 Equation::Add,
87 BlendFactor::OneMinusValue(BlendValue::DestinationAlpha),
88 BlendFactor::One,
89 ),
90 ),
91 BlendMode::Multiply => (
92 BlendState::new(
93 Equation::Add,
94 BlendFactor::Value(BlendValue::DestinationColor),
95 BlendFactor::Zero,
96 ),
97 BlendState::new(
98 Equation::Add,
99 BlendFactor::Value(BlendValue::DestinationAlpha),
100 BlendFactor::Zero,
101 ),
102 ),
103 BlendMode::Replace => (
104 BlendState::new(Equation::Add, BlendFactor::One, BlendFactor::Zero),
105 BlendState::new(Equation::Add, BlendFactor::One, BlendFactor::Zero),
106 ),
107 }
108 }
109}
110
111pub type ShaderId = usize;
113
114const MATRIX_SIZE: isize = std::mem::size_of::<Matrix4<f32>>() as isize;
115
116#[derive(Debug)]
117pub struct Shader {
118 pub(crate) pipeline: miniquad::Pipeline,
119 pub(crate) uniforms: Vec<u8>,
120}
121
122impl Shader {
123 pub(crate) fn from_mini_shader(
126 quad_ctx: &mut miniquad::Context,
127 mini_shader: miniquad::Shader,
128 blend_mode: Option<BlendMode>,
129 ) -> Self {
130 let (color_blend, alpha_blend) = blend_mode.unwrap_or(BlendMode::Alpha).into();
131
132 let new_shader_pipeline = miniquad::Pipeline::with_params(
133 quad_ctx,
134 &[
135 BufferLayout::default(),
136 BufferLayout {
137 step_func: VertexStep::PerInstance,
138 ..Default::default()
139 },
140 ],
141 &[
142 VertexAttribute::with_buffer("position", VertexFormat::Float2, 0),
143 VertexAttribute::with_buffer("texcoord", VertexFormat::Float2, 0),
144 VertexAttribute::with_buffer("color0", VertexFormat::Float4, 0),
145 VertexAttribute::with_buffer("Source", VertexFormat::Float4, 1),
146 VertexAttribute::with_buffer("Color", VertexFormat::Float4, 1),
147 VertexAttribute::with_buffer("Model", VertexFormat::Mat4, 1),
148 ],
149 mini_shader,
150 PipelineParams {
151 color_blend: Some(color_blend),
152 alpha_blend: Some(alpha_blend),
153 ..Default::default()
154 },
155 );
156 Shader {
157 pipeline: new_shader_pipeline,
158 uniforms: vec![0u8; MATRIX_SIZE as usize], }
160 }
161
162 #[allow(clippy::new_ret_no_self)]
163 pub fn new<P: AsRef<Path>>(
165 ctx: &mut Context,
166 quad_ctx: &mut miniquad::graphics::GraphicsContext,
167 vertex_path: P,
168 pixel_path: P,
169 shader_meta: ShaderMeta,
170 blend_mode: Option<BlendMode>,
171 ) -> GameResult<ShaderId> {
172 let vertex_source = {
173 let mut buf = Vec::new();
174 let mut reader = ctx.filesystem.open(vertex_path)?;
175 let _ = reader.read_to_end(&mut buf)?;
176 buf
177 };
178 let pixel_source = {
179 let mut buf = Vec::new();
180 let mut reader = ctx.filesystem.open(pixel_path)?;
181 let _ = reader.read_to_end(&mut buf)?;
182 buf
183 };
184 Self::from_u8(
185 ctx,
186 quad_ctx,
187 &vertex_source,
188 &pixel_source,
189 shader_meta,
190 blend_mode,
191 )
192 }
193
194 pub fn from_u8(
196 ctx: &mut Context,
197 quad_ctx: &mut miniquad::graphics::GraphicsContext,
198 vertex_source: &[u8],
199 pixel_source: &[u8],
200 shader_meta: ShaderMeta,
201 blend_mode: Option<BlendMode>,
202 ) -> GameResult<ShaderId> {
203 fn to_shader_error(e: std::str::Utf8Error) -> GameError {
204 GameError::ShaderProgramError(e.to_string())
205 }
206
207 let vertex_source = std::str::from_utf8(vertex_source).map_err(to_shader_error)?;
208 let pixel_source = std::str::from_utf8(pixel_source).map_err(to_shader_error)?;
209
210 Self::from_str(
211 ctx,
212 quad_ctx,
213 vertex_source,
214 pixel_source,
215 shader_meta,
216 blend_mode,
217 )
218 }
219
220 pub fn from_str(
222 ctx: &mut Context,
223 quad_ctx: &mut miniquad::graphics::GraphicsContext,
224 vertex_source: &str,
225 pixel_source: &str,
226 shader_meta: ShaderMeta,
227 blend_mode: Option<BlendMode>,
228 ) -> GameResult<ShaderId> {
229 let miniquad_shader =
230 miniquad::graphics::Shader::new(quad_ctx, vertex_source, pixel_source, shader_meta)?;
231
232 let shader = Self::from_mini_shader(quad_ctx, miniquad_shader, blend_mode);
233
234 let id = ctx.gfx_context.shaders.len();
235 ctx.gfx_context.shaders.push(shader);
236
237 Ok(id)
238 }
239}
240
241#[derive(Debug, Clone)]
248pub struct ShaderLock {
249 cell: Rc<RefCell<ShaderId>>,
250 previous_shader: ShaderId,
251}
252
253impl Drop for ShaderLock {
254 fn drop(&mut self) {
255 *self.cell.borrow_mut() = self.previous_shader;
256 }
257}
258
259pub fn use_shader(ctx: &mut Context, ps: ShaderId) -> ShaderLock {
261 let cell = Rc::clone(&ctx.gfx_context.current_shader);
262 let previous_shader = *cell.borrow();
263 set_shader(ctx, ps);
264 ShaderLock {
265 cell,
266 previous_shader,
267 }
268}
269
270pub fn set_shader(ctx: &mut Context, ps: ShaderId) {
272 *ctx.gfx_context.current_shader.borrow_mut() = ps;
273}
274
275pub fn clear_shader(ctx: &mut Context) {
280 *ctx.gfx_context.current_shader.borrow_mut() = default_shader::SHADER_ID;
281}
282
283pub fn set_uniforms<U: Pod>(ctx: &mut Context, id: ShaderId, extra_uniforms: U) {
287 let shader = &mut ctx.gfx_context.shaders[id];
288 shader.uniforms.clear();
289 shader
290 .uniforms
291 .extend_from_slice(bytemuck::bytes_of(&extra_uniforms));
292 shader
293 .uniforms
294 .extend_from_slice(&[0u8; std::mem::size_of::<Matrix4<f32>>()]);
295}
296
297pub(crate) fn apply_uniforms(
299 ctx: &mut Context,
300 quad_ctx: &mut miniquad::graphics::GraphicsContext,
301 shader_id: ShaderId,
302 batch_model: Option<Matrix4<f32>>,
303) {
304 let projection = if let Some(model) = batch_model {
305 ctx.gfx_context.projection * model
306 } else {
307 ctx.gfx_context.projection
308 };
309 let projection_bytes = &projection as *const _ as *const u8;
310
311 let current_shader = &mut ctx.gfx_context.shaders[shader_id];
312 unsafe {
313 let after_last_byte = current_shader
314 .uniforms
315 .as_mut_ptr_range()
316 .end
317 .offset(-MATRIX_SIZE);
318 for offset in 0..MATRIX_SIZE {
319 *after_last_byte.offset(offset) = *projection_bytes.offset(offset);
320 }
321 }
322 quad_ctx.apply_uniforms_from_bytes(
323 current_shader.uniforms.as_ptr(),
324 current_shader.uniforms.len(),
325 );
326}