1use miniquad::*;
4
5pub use miniquad::{FilterMode, TextureId as MiniquadTexture, UniformDesc};
6
7use crate::{color::Color, logging::warn, telemetry, texture::Texture2D, tobytes::ToBytes, Error};
8
9use std::collections::BTreeMap;
10
11pub(crate) use crate::models::Vertex;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum DrawMode {
15 Triangles,
16 Lines,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct GlPipeline(usize);
21
22struct DrawCall {
23 vertices_count: usize,
24 indices_count: usize,
25 vertices_start: usize,
26 indices_start: usize,
27
28 clip: Option<(i32, i32, i32, i32)>,
29 viewport: Option<(i32, i32, i32, i32)>,
30 texture: Option<miniquad::TextureId>,
31
32 model: glam::Mat4,
33
34 draw_mode: DrawMode,
35 pipeline: GlPipeline,
36 uniforms: Option<Vec<u8>>,
37 render_pass: Option<RenderPass>,
38 capture: bool,
39}
40
41impl DrawCall {
42 const fn new(
43 texture: Option<miniquad::TextureId>,
44 model: glam::Mat4,
45 draw_mode: DrawMode,
46 pipeline: GlPipeline,
47 uniforms: Option<Vec<u8>>,
48 render_pass: Option<RenderPass>,
49 ) -> DrawCall {
50 DrawCall {
51 vertices_start: 0,
52 indices_start: 0,
53 vertices_count: 0,
54 indices_count: 0,
55 viewport: None,
56 clip: None,
57 texture,
58 model,
59 draw_mode,
60 pipeline,
61 uniforms,
62 render_pass,
63 capture: false,
64 }
65 }
66}
67
68struct MagicSnapshotter {
69 pipeline: Pipeline,
70 bindings: Bindings,
71 pass: Option<RenderPass>,
72
73 screen_texture: Option<miniquad::TextureId>,
74}
75
76mod snapshotter_shader {
77 use miniquad::{ShaderMeta, UniformBlockLayout};
78
79 pub const VERTEX: &str = r#"#version 100
80 attribute vec2 position;
81 attribute vec2 texcoord;
82
83 varying lowp vec2 uv;
84
85 void main() {
86 gl_Position = vec4(position, 0, 1);
87 uv = texcoord;
88 }"#;
89
90 pub const FRAGMENT: &str = r#"#version 100
91 varying lowp vec2 uv;
92
93 uniform sampler2D Texture;
94
95 void main() {
96 gl_FragColor = texture2D(Texture, uv);
97 }"#;
98
99 pub const METAL: &str = r#"#include <metal_stdlib>
100 using namespace metal;
101
102 struct Vertex
103 {
104 float2 position [[attribute(0)]];
105 float2 texcoord [[attribute(1)]];
106 };
107
108 struct RasterizerData
109 {
110 float4 position [[position]];
111 float2 uv [[user(locn1)]];
112 };
113
114 vertex RasterizerData vertexShader(Vertex v [[stage_in]])
115 {
116 RasterizerData out;
117
118 out.position = float4(v.position, 0, 1);
119 out.uv = v.texcoord;
120
121 return out;
122 }
123
124 fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]])
125 {
126 return tex.sample(texSmplr, in.uv);
127 }"#;
128
129 pub fn meta() -> ShaderMeta {
130 ShaderMeta {
131 images: vec!["Texture".to_string()],
132 uniforms: UniformBlockLayout { uniforms: vec![] },
133 }
134 }
135}
136
137impl MagicSnapshotter {
138 fn new(ctx: &mut dyn RenderingBackend) -> MagicSnapshotter {
139 let shader = ctx
140 .new_shader(
141 match ctx.info().backend {
142 Backend::OpenGl => ShaderSource::Glsl {
143 vertex: snapshotter_shader::VERTEX,
144 fragment: snapshotter_shader::FRAGMENT,
145 },
146 Backend::Metal => ShaderSource::Msl {
147 program: snapshotter_shader::METAL,
148 },
149 },
150 snapshotter_shader::meta(),
151 )
152 .unwrap_or_else(|e| panic!("Failed to load shader: {e}"));
153
154 let pipeline = ctx.new_pipeline(
155 &[BufferLayout::default()],
156 &[
157 VertexAttribute::new("position", VertexFormat::Float2),
158 VertexAttribute::new("texcoord", VertexFormat::Float2),
159 ],
160 shader,
161 PipelineParams::default(),
162 );
163
164 #[rustfmt::skip]
165 let vertices: [f32; 16] = [
166 -1.0, -1.0, 0., 0.,
167 1.0, -1.0, 1., 0. ,
168 1.0, 1.0, 1., 1. ,
169 -1.0, 1.0, 0., 1. ,
170 ];
171 let vertex_buffer = ctx.new_buffer(
172 BufferType::VertexBuffer,
173 BufferUsage::Immutable,
174 BufferSource::slice(&vertices),
175 );
176
177 let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
178 let index_buffer = ctx.new_buffer(
179 BufferType::IndexBuffer,
180 BufferUsage::Immutable,
181 BufferSource::slice(&indices),
182 );
183
184 let bindings = Bindings {
185 vertex_buffers: vec![vertex_buffer],
186 index_buffer,
187 images: vec![ctx.new_texture_from_rgba8(1, 1, &[0, 0, 0, 0])],
188 };
189
190 MagicSnapshotter {
191 pipeline,
192 bindings,
193 pass: None,
194 screen_texture: None,
195 }
196 }
197
198 fn snapshot(&mut self, ctx: &mut dyn RenderingBackend, camera_render_pass: Option<RenderPass>) {
199 if let Some(camera_render_pass) = camera_render_pass {
200 let texture = ctx.render_pass_texture(camera_render_pass);
201 if self.pass.is_none() {
202 let miniquad::TextureParams {
203 width,
204 height,
205 format,
206 ..
207 } = ctx.texture_params(texture);
208 let color_img = ctx.new_render_texture(TextureParams {
209 width,
210 height,
211 format,
212 ..Default::default()
213 });
214
215 self.pass = Some(ctx.new_render_pass(color_img, None));
216 self.screen_texture = Some(color_img);
217 }
218
219 if self.bindings.images.len() == 0 {
220 self.bindings.images.push(texture);
221 } else {
222 self.bindings.images[0] = texture;
223 }
224 ctx.begin_pass(
225 Some(self.pass.unwrap()),
226 PassAction::clear_color(1.0, 0.0, 1.0, 1.),
227 );
228 ctx.apply_pipeline(&self.pipeline);
229 ctx.apply_bindings(&self.bindings);
230 ctx.draw(0, 6, 1);
231 ctx.end_render_pass();
232 } else {
233 let (screen_width, screen_height) = miniquad::window::screen_size();
234 if self.screen_texture.is_none()
235 || self
236 .screen_texture
237 .map(|t| {
238 let (w, h) = ctx.texture_size(t);
239 w != screen_width as _ || h != screen_height as _
240 })
241 .unwrap_or(false)
242 {
243 self.screen_texture = Some(ctx.new_render_texture(TextureParams {
244 width: screen_width as _,
245 height: screen_height as _,
246 ..Default::default()
247 }));
248 }
249
250 let texture = self.screen_texture.unwrap();
251 Texture2D::unmanaged(texture).grab_screen();
252 }
253 }
254}
255
256struct GlState {
257 texture: Option<miniquad::TextureId>,
258 draw_mode: DrawMode,
259 clip: Option<(i32, i32, i32, i32)>,
260 viewport: Option<(i32, i32, i32, i32)>,
261 model_stack: Vec<glam::Mat4>,
262 pipeline: Option<GlPipeline>,
263 depth_test_enable: bool,
264
265 break_batching: bool,
266 snapshotter: MagicSnapshotter,
267
268 render_pass: Option<RenderPass>,
269 capture: bool,
270}
271
272impl GlState {
273 fn model(&self) -> glam::Mat4 {
274 *self.model_stack.last().unwrap()
275 }
276}
277
278#[derive(Clone, Debug)]
279struct Uniform {
280 name: String,
281 uniform_type: UniformType,
282 byte_offset: usize,
283 byte_size: usize,
284}
285
286#[derive(Clone)]
287struct PipelineExt {
288 pipeline: miniquad::Pipeline,
289 wants_screen_texture: bool,
290 uniforms: Vec<Uniform>,
291 uniforms_data: Vec<u8>,
292 textures: Vec<String>,
293 textures_data: BTreeMap<String, MiniquadTexture>,
294}
295
296impl PipelineExt {
297 fn set_uniform<T>(&mut self, name: &str, uniform: T) {
298 let uniform_meta = self.uniforms.iter().find(
299 |Uniform {
300 name: uniform_name, ..
301 }| uniform_name == name,
302 );
303 if uniform_meta.is_none() {
304 warn!("Trying to set non-existing uniform: {}", name);
305 return;
306 }
307 let uniform_meta = uniform_meta.unwrap();
308 let uniform_format = uniform_meta.uniform_type;
309 let uniform_byte_size = uniform_format.size();
310 let uniform_byte_offset = uniform_meta.byte_offset;
311
312 if size_of::<T>() != uniform_byte_size {
313 warn!(
314 "Trying to set uniform {} sized {} bytes value of {} bytes",
315 name,
316 uniform_byte_size,
317 size_of::<T>()
318 );
319 return;
320 }
321 if uniform_byte_size != uniform_meta.byte_size {
322 warn!("set_uniform do not support uniform arrays");
323 return;
324 }
325 macro_rules! transmute_uniform {
326 ($uniform_size:expr, $byte_offset:expr, $n:expr) => {
327 if $uniform_size == $n {
328 let data: [u8; $n] = unsafe { std::mem::transmute_copy(&uniform) };
329
330 for i in 0..$uniform_size {
331 self.uniforms_data[$byte_offset + i] = data[i];
332 }
333 }
334 };
335 }
336 transmute_uniform!(uniform_byte_size, uniform_byte_offset, 4);
337 transmute_uniform!(uniform_byte_size, uniform_byte_offset, 8);
338 transmute_uniform!(uniform_byte_size, uniform_byte_offset, 12);
339 transmute_uniform!(uniform_byte_size, uniform_byte_offset, 16);
340 transmute_uniform!(uniform_byte_size, uniform_byte_offset, 64);
341 }
342
343 fn set_uniform_array<T: ToBytes>(&mut self, name: &str, uniform: &[T]) {
344 let uniform_meta = self.uniforms.iter().find(
345 |Uniform {
346 name: uniform_name, ..
347 }| uniform_name == name,
348 );
349 if uniform_meta.is_none() {
350 warn!("Trying to set non-existing uniform: {}", name);
351 return;
352 }
353 let uniform_meta = uniform_meta.unwrap();
354 let uniform_byte_size = uniform_meta.byte_size;
355 let uniform_byte_offset = uniform_meta.byte_offset;
356
357 let data = uniform.to_bytes();
358 if data.len() != uniform_byte_size {
359 warn!(
360 "Trying to set uniform {} sized {} bytes value of {} bytes",
361 name,
362 uniform_byte_size,
363 size_of::<T>()
364 );
365 return;
366 }
367 for i in 0..uniform_byte_size {
368 self.uniforms_data[uniform_byte_offset + i] = data[i];
369 }
370 }
371}
372
373struct PipelinesStorage {
374 pipelines: [Option<PipelineExt>; Self::MAX_PIPELINES],
375 pipelines_amount: usize,
376}
377
378impl PipelinesStorage {
379 const MAX_PIPELINES: usize = 32;
380 const TRIANGLES_PIPELINE: GlPipeline = GlPipeline(0);
381 const LINES_PIPELINE: GlPipeline = GlPipeline(1);
382 const TRIANGLES_DEPTH_PIPELINE: GlPipeline = GlPipeline(2);
383 const LINES_DEPTH_PIPELINE: GlPipeline = GlPipeline(3);
384
385 fn new(ctx: &mut dyn RenderingBackend) -> PipelinesStorage {
386 let shader = ctx
387 .new_shader(
388 match ctx.info().backend {
389 Backend::OpenGl => ShaderSource::Glsl {
390 vertex: shader::VERTEX,
391 fragment: shader::FRAGMENT,
392 },
393 Backend::Metal => ShaderSource::Msl {
394 program: shader::METAL,
395 },
396 },
397 shader::meta(),
398 )
399 .unwrap_or_else(|e| panic!("Failed to load shader: {e}"));
400
401 let params = PipelineParams {
402 color_blend: Some(BlendState::new(
403 Equation::Add,
404 BlendFactor::Value(BlendValue::SourceAlpha),
405 BlendFactor::OneMinusValue(BlendValue::SourceAlpha),
406 )),
407 ..Default::default()
408 };
409
410 let mut storage = PipelinesStorage {
411 pipelines: Default::default(),
412 pipelines_amount: 0,
413 };
414
415 let triangles_pipeline = storage.make_pipeline(
416 ctx,
417 shader,
418 PipelineParams {
419 primitive_type: PrimitiveType::Triangles,
420 ..params
421 },
422 false,
423 vec![],
424 vec![],
425 );
426 assert_eq!(triangles_pipeline, Self::TRIANGLES_PIPELINE);
427
428 let lines_pipeline = storage.make_pipeline(
429 ctx,
430 shader,
431 PipelineParams {
432 primitive_type: PrimitiveType::Lines,
433 ..params
434 },
435 false,
436 vec![],
437 vec![],
438 );
439 assert_eq!(lines_pipeline, Self::LINES_PIPELINE);
440
441 let triangles_depth_pipeline = storage.make_pipeline(
442 ctx,
443 shader,
444 PipelineParams {
445 depth_write: true,
446 depth_test: Comparison::LessOrEqual,
447 primitive_type: PrimitiveType::Triangles,
448 ..params
449 },
450 false,
451 vec![],
452 vec![],
453 );
454 assert_eq!(triangles_depth_pipeline, Self::TRIANGLES_DEPTH_PIPELINE);
455
456 let lines_depth_pipeline = storage.make_pipeline(
457 ctx,
458 shader,
459 PipelineParams {
460 depth_write: true,
461 depth_test: Comparison::LessOrEqual,
462 primitive_type: PrimitiveType::Lines,
463 ..params
464 },
465 false,
466 vec![],
467 vec![],
468 );
469 assert_eq!(lines_depth_pipeline, Self::LINES_DEPTH_PIPELINE);
470
471 storage
472 }
473
474 fn make_pipeline(
475 &mut self,
476 ctx: &mut dyn RenderingBackend,
477 shader: ShaderId,
478 params: PipelineParams,
479 wants_screen_texture: bool,
480 mut uniforms: Vec<UniformDesc>,
481 textures: Vec<String>,
482 ) -> GlPipeline {
483 let pipeline = ctx.new_pipeline(
484 &[BufferLayout::default()],
485 &[
486 VertexAttribute::new("position", VertexFormat::Float3),
487 VertexAttribute::new("texcoord", VertexFormat::Float2),
488 VertexAttribute::new("color0", VertexFormat::Byte4),
489 VertexAttribute::new("normal", VertexFormat::Float4),
490 ],
491 shader,
492 params,
493 );
494
495 let id = self
496 .pipelines
497 .iter()
498 .position(|p| p.is_none())
499 .unwrap_or_else(|| panic!("Pipelines amount exceeded"));
500
501 let mut max_offset = 0;
502
503 for (name, kind) in shader::uniforms().into_iter().rev() {
504 uniforms.insert(0, UniformDesc::new(name, kind));
505 }
506
507 let uniforms = uniforms
508 .iter()
509 .scan(0, |offset, uniform| {
510 let byte_size = uniform.uniform_type.size() * uniform.array_count;
511 let uniform = Uniform {
512 name: uniform.name.clone(),
513 uniform_type: uniform.uniform_type,
514 byte_size,
515 byte_offset: *offset,
516 };
517 *offset += byte_size;
518 max_offset = *offset;
519
520 Some(uniform)
521 })
522 .collect();
523
524 self.pipelines[id] = Some(PipelineExt {
525 pipeline,
526 wants_screen_texture,
527 uniforms,
528 uniforms_data: vec![0; max_offset],
529 textures,
530 textures_data: BTreeMap::new(),
531 });
532 self.pipelines_amount += 1;
533
534 GlPipeline(id)
535 }
536
537 const fn get(&self, draw_mode: DrawMode, depth_enabled: bool) -> GlPipeline {
538 match (draw_mode, depth_enabled) {
539 (DrawMode::Triangles, false) => Self::TRIANGLES_PIPELINE,
540 (DrawMode::Triangles, true) => Self::TRIANGLES_DEPTH_PIPELINE,
541 (DrawMode::Lines, false) => Self::LINES_PIPELINE,
542 (DrawMode::Lines, true) => Self::LINES_DEPTH_PIPELINE,
543 }
544 }
545
546 fn get_quad_pipeline_mut(&mut self, pip: GlPipeline) -> &mut PipelineExt {
547 self.pipelines[pip.0].as_mut().unwrap()
548 }
549
550 fn delete_pipeline(&mut self, pip: GlPipeline) {
551 self.pipelines[pip.0] = None;
552 }
553}
554
555pub struct QuadGl {
556 pipelines: PipelinesStorage,
557
558 draw_calls: Vec<DrawCall>,
559 draw_calls_bindings: Vec<Bindings>,
560 draw_calls_count: usize,
561 state: GlState,
562 start_time: f64,
563
564 pub(crate) white_texture: miniquad::TextureId,
565 max_vertices: usize,
566 max_indices: usize,
567
568 batch_vertex_buffer: Vec<Vertex>,
569 batch_index_buffer: Vec<u16>,
570}
571
572impl QuadGl {
573 pub fn new(
574 ctx: &mut dyn miniquad::RenderingBackend,
575 max_vertices: usize,
576 max_indices: usize,
577 ) -> QuadGl {
578 let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]);
579
580 QuadGl {
581 pipelines: PipelinesStorage::new(ctx),
582 state: GlState {
583 clip: None,
584 viewport: None,
585 texture: None,
586 model_stack: vec![glam::Mat4::IDENTITY],
587 draw_mode: DrawMode::Triangles,
588 pipeline: None,
589 break_batching: false,
590 depth_test_enable: false,
591 snapshotter: MagicSnapshotter::new(ctx),
592 render_pass: None,
593 capture: false,
594 },
595 draw_calls: Vec::with_capacity(200),
596 draw_calls_bindings: Vec::with_capacity(200),
597 draw_calls_count: 0,
598 start_time: miniquad::date::now(),
599
600 white_texture,
601 batch_vertex_buffer: Vec::with_capacity(max_vertices),
602 batch_index_buffer: Vec::with_capacity(max_indices),
603 max_vertices,
604 max_indices,
605 }
606 }
607
608 pub fn make_pipeline(
609 &mut self,
610 ctx: &mut dyn miniquad::RenderingBackend,
611 shader: miniquad::ShaderSource,
612 params: PipelineParams,
613 uniforms: Vec<UniformDesc>,
614 textures: Vec<String>,
615 ) -> Result<GlPipeline, Error> {
616 let mut shader_meta: ShaderMeta = shader::meta();
617
618 for uniform in &uniforms {
619 shader_meta.uniforms.uniforms.push(uniform.clone());
620 }
621
622 for texture in &textures {
623 if texture == "Texture" {
624 panic!(
625 "you can't use name `Texture` for your texture. This name is reserved for the texture that will be drawn with that material"
626 );
627 }
628 if texture == "_ScreenTexture" {
629 panic!(
630 "you can't use name `_ScreenTexture` for your texture in shaders. This name is reserved for screen texture"
631 );
632 }
633 shader_meta.images.push(texture.clone());
634 }
635
636 let source = match shader {
637 ShaderSource::Glsl { fragment, .. } => fragment,
638 ShaderSource::Msl { program } => program,
639 };
640 let wants_screen_texture = source.contains("_ScreenTexture");
641 let shader = ctx.new_shader(shader, shader_meta)?;
642 Ok(self.pipelines.make_pipeline(
643 ctx,
644 shader,
645 params,
646 wants_screen_texture,
647 uniforms,
648 textures,
649 ))
650 }
651
652 pub(crate) fn clear(&mut self, ctx: &mut dyn miniquad::RenderingBackend, color: Color) {
653 let clear = PassAction::clear_color(color.r, color.g, color.b, color.a);
654
655 if let Some(current_pass) = self.state.render_pass {
656 ctx.begin_pass(Some(current_pass), clear);
657 } else {
658 ctx.begin_default_pass(clear);
659 }
660 ctx.end_render_pass();
661
662 self.clear_draw_calls();
663 }
664
665 pub fn clear_draw_calls(&mut self) {
667 self.draw_calls_count = 0;
668 }
669
670 pub fn reset(&mut self) {
672 self.state.clip = None;
673 self.state.texture = None;
674 self.state.model_stack = vec![glam::Mat4::IDENTITY];
675
676 self.draw_calls_count = 0;
677 }
678
679 pub fn draw(&mut self, ctx: &mut dyn miniquad::RenderingBackend, projection: glam::Mat4) {
680 let white_texture = self.white_texture;
681
682 for _ in 0..self.draw_calls.len() - self.draw_calls_bindings.len() {
683 let vertex_buffer = ctx.new_buffer(
684 BufferType::VertexBuffer,
685 BufferUsage::Stream,
686 BufferSource::empty::<Vertex>(self.max_vertices),
687 );
688 let index_buffer = ctx.new_buffer(
689 BufferType::IndexBuffer,
690 BufferUsage::Stream,
691 BufferSource::empty::<u16>(self.max_indices),
692 );
693 let bindings = Bindings {
694 vertex_buffers: vec![vertex_buffer],
695 index_buffer,
696 images: vec![white_texture, white_texture],
697 };
698
699 self.draw_calls_bindings.push(bindings);
700 }
701 assert_eq!(self.draw_calls_bindings.len(), self.draw_calls.len());
702
703 let (screen_width, screen_height) = miniquad::window::screen_size();
704 let time = (miniquad::date::now() - self.start_time) as f32;
705 let time = glam::vec4(time, time.sin(), time.cos(), 0.);
706
707 for (dc, bindings) in self.draw_calls[0..self.draw_calls_count]
708 .iter_mut()
709 .zip(self.draw_calls_bindings.iter_mut())
710 {
711 let pipeline = self.pipelines.get_quad_pipeline_mut(dc.pipeline);
712
713 let (width, height) = if let Some(render_pass) = dc.render_pass {
714 let render_texture = ctx.render_pass_texture(render_pass);
715 let (width, height) = ctx.texture_size(render_texture);
716 (width, height)
717 } else {
718 (screen_width as u32, screen_height as u32)
719 };
720
721 if pipeline.wants_screen_texture {
722 self.state.snapshotter.snapshot(ctx, dc.render_pass);
723 }
724
725 if let Some(render_pass) = dc.render_pass {
726 ctx.begin_pass(Some(render_pass), PassAction::Nothing);
727 } else {
728 ctx.begin_default_pass(PassAction::Nothing);
729 }
730
731 ctx.buffer_update(
732 bindings.vertex_buffers[0],
733 BufferSource::slice(
734 &self.batch_vertex_buffer
735 [dc.vertices_start..(dc.vertices_start + dc.vertices_count)],
736 ),
737 );
738 ctx.buffer_update(
739 bindings.index_buffer,
740 BufferSource::slice(
741 &self.batch_index_buffer
742 [dc.indices_start..(dc.indices_start + dc.indices_count)],
743 ),
744 );
745
746 bindings.images[0] = dc.texture.unwrap_or(white_texture);
747 bindings.images[1] = self
748 .state
749 .snapshotter
750 .screen_texture
751 .unwrap_or(white_texture);
752 bindings
753 .images
754 .resize(2 + pipeline.textures.len(), white_texture);
755
756 for (pos, name) in pipeline.textures.iter().enumerate() {
757 if let Some(texture) = pipeline.textures_data.get(name).copied() {
758 bindings.images[2 + pos] = texture;
759 }
760 }
761
762 ctx.apply_pipeline(&pipeline.pipeline);
763 if let Some((x, y, w, h)) = dc.viewport {
764 ctx.apply_viewport(x, y, w, h);
765 } else {
766 ctx.apply_viewport(0, 0, width as i32, height as i32);
767 }
768 if let Some(clip) = dc.clip {
769 ctx.apply_scissor_rect(clip.0, height as i32 - (clip.1 + clip.3), clip.2, clip.3);
770 } else {
771 ctx.apply_scissor_rect(0, 0, width as i32, height as i32);
772 }
773 ctx.apply_bindings(bindings);
774
775 if let Some(ref uniforms) = dc.uniforms {
776 for i in 0..uniforms.len() {
777 pipeline.uniforms_data[i] = uniforms[i];
778 }
779 }
780 pipeline.set_uniform("Projection", projection);
781 pipeline.set_uniform("Model", dc.model);
782 pipeline.set_uniform("_Time", time);
783 ctx.apply_uniforms_from_bytes(
784 pipeline.uniforms_data.as_ptr(),
785 pipeline.uniforms_data.len(),
786 );
787 ctx.draw(0, dc.indices_count as i32, 1);
788 ctx.end_render_pass();
789
790 if dc.capture {
791 telemetry::track_drawcall(&pipeline.pipeline, bindings, dc.indices_count);
792 }
793
794 dc.vertices_count = 0;
795 dc.indices_count = 0;
796 dc.vertices_start = 0;
797 dc.indices_start = 0;
798 }
799
800 self.draw_calls_count = 0;
801 self.batch_index_buffer.clear();
802 self.batch_vertex_buffer.clear();
803 }
804
805 pub(crate) fn capture(&mut self, capture: bool) {
806 self.state.capture = capture;
807 }
808
809 pub fn get_projection_matrix(&self) -> glam::Mat4 {
810 crate::get_context().projection_matrix()
815 }
816
817 pub const fn get_active_render_pass(&self) -> Option<RenderPass> {
818 self.state.render_pass
819 }
820
821 pub const fn is_depth_test_enabled(&self) -> bool {
822 self.state.depth_test_enable
823 }
824
825 pub fn render_pass(&mut self, render_pass: Option<RenderPass>) {
826 self.state.render_pass = render_pass;
827 }
828
829 pub fn depth_test(&mut self, enable: bool) {
830 self.state.depth_test_enable = enable;
831 }
832
833 pub fn texture(&mut self, texture: Option<&Texture2D>) {
834 let ctx = crate::get_context();
835 self.state.texture = texture.map(|t| ctx.raw_miniquad_id(&t.texture));
836 }
837
838 pub fn scissor(&mut self, clip: Option<(i32, i32, i32, i32)>) {
839 self.state.clip = clip;
840 }
841
842 pub fn viewport(&mut self, viewport: Option<(i32, i32, i32, i32)>) {
843 self.state.viewport = viewport;
844 }
845
846 pub fn get_viewport(&self) -> (i32, i32, i32, i32) {
847 self.state.viewport.unwrap_or((
848 0,
849 0,
850 crate::window::screen_width() as _,
851 crate::window::screen_height() as _,
852 ))
853 }
854
855 pub fn push_model_matrix(&mut self, matrix: glam::Mat4) {
856 self.state.model_stack.push(self.state.model() * matrix);
857 }
858
859 pub fn pop_model_matrix(&mut self) {
860 if self.state.model_stack.len() > 1 {
861 self.state.model_stack.pop();
862 }
863 }
864
865 pub fn pipeline(&mut self, pipeline: Option<GlPipeline>) {
866 if self.state.pipeline == pipeline {
867 return;
868 }
869
870 self.state.break_batching = true;
871 self.state.pipeline = pipeline;
872 }
873
874 pub fn draw_mode(&mut self, mode: DrawMode) {
875 self.state.draw_mode = mode;
876 }
877
878 pub fn geometry(&mut self, vertices: &[Vertex], indices: &[u16]) {
879 if vertices.len() >= self.max_vertices || indices.len() >= self.max_indices {
880 warn!("geometry() exceeded max drawcall size, clamping");
881 }
882
883 let vertices = &vertices[0..self.max_vertices.min(vertices.len())];
884 let indices = &indices[0..self.max_indices.min(indices.len())];
885
886 let pip = self.state.pipeline.unwrap_or(
887 self.pipelines
888 .get(self.state.draw_mode, self.state.depth_test_enable),
889 );
890
891 let previous_dc_ix = if self.draw_calls_count == 0 {
892 None
893 } else {
894 Some(self.draw_calls_count - 1)
895 };
896 let previous_dc = previous_dc_ix.and_then(|ix| self.draw_calls.get(ix));
897
898 if previous_dc.map_or(true, |draw_call| {
899 draw_call.texture != self.state.texture
900 || draw_call.clip != self.state.clip
901 || draw_call.viewport != self.state.viewport
902 || draw_call.model != self.state.model()
903 || draw_call.pipeline != pip
904 || draw_call.render_pass != self.state.render_pass
905 || draw_call.draw_mode != self.state.draw_mode
906 || draw_call.vertices_count >= self.max_vertices - vertices.len()
907 || draw_call.indices_count >= self.max_indices - indices.len()
908 || draw_call.capture != self.state.capture
909 || self.state.break_batching
910 }) {
911 let uniforms = self.state.pipeline.map_or(None, |pipeline| {
912 Some(
913 self.pipelines
914 .get_quad_pipeline_mut(pipeline)
915 .uniforms_data
916 .clone(),
917 )
918 });
919
920 if self.draw_calls_count >= self.draw_calls.len() {
921 self.draw_calls.push(DrawCall::new(
922 self.state.texture,
923 self.state.model(),
924 self.state.draw_mode,
925 pip,
926 uniforms.clone(),
927 self.state.render_pass,
928 ));
929 }
930 self.draw_calls[self.draw_calls_count].texture = self.state.texture;
931 self.draw_calls[self.draw_calls_count].uniforms = uniforms;
932 self.draw_calls[self.draw_calls_count].vertices_count = 0;
933 self.draw_calls[self.draw_calls_count].indices_count = 0;
934 self.draw_calls[self.draw_calls_count].clip = self.state.clip;
935 self.draw_calls[self.draw_calls_count].viewport = self.state.viewport;
936 self.draw_calls[self.draw_calls_count].model = self.state.model();
937 self.draw_calls[self.draw_calls_count].pipeline = pip;
938 self.draw_calls[self.draw_calls_count].render_pass = self.state.render_pass;
939 self.draw_calls[self.draw_calls_count].capture = self.state.capture;
940 self.draw_calls[self.draw_calls_count].indices_start = self.batch_index_buffer.len();
941 self.draw_calls[self.draw_calls_count].vertices_start = self.batch_vertex_buffer.len();
942
943 self.draw_calls_count += 1;
944 self.state.break_batching = false;
945 };
946 let dc = &mut self.draw_calls[self.draw_calls_count - 1];
947
948 self.batch_vertex_buffer.extend(vertices);
949 self.batch_index_buffer
950 .extend(indices.iter().map(|x| *x + dc.vertices_count as u16));
951
952 dc.vertices_count += vertices.len();
953 dc.indices_count += indices.len();
954
955 dc.texture = self.state.texture;
956 }
957
958 pub fn delete_pipeline(&mut self, pipeline: GlPipeline) {
959 self.pipelines.delete_pipeline(pipeline);
960 }
961
962 pub fn set_uniform<T>(&mut self, pipeline: GlPipeline, name: &str, uniform: T) {
963 self.state.break_batching = true;
964
965 self.pipelines
966 .get_quad_pipeline_mut(pipeline)
967 .set_uniform(name, uniform);
968 }
969 pub fn set_uniform_array<T: ToBytes>(
970 &mut self,
971 pipeline: GlPipeline,
972 name: &str,
973 uniform: &[T],
974 ) {
975 self.state.break_batching = true;
976
977 self.pipelines
978 .get_quad_pipeline_mut(pipeline)
979 .set_uniform_array(name, uniform);
980 }
981
982 pub fn set_texture(&mut self, pipeline: GlPipeline, name: &str, texture: Texture2D) {
983 let pipeline = self.pipelines.get_quad_pipeline_mut(pipeline);
984 pipeline
985 .textures
986 .iter()
987 .find(|x| *x == name)
988 .unwrap_or_else(|| {
989 panic!(
990 "can't find texture with name '{}', there is only this names: {:?}",
991 name, pipeline.textures
992 )
993 });
994 let quad_texture = texture.raw_miniquad_id();
995 *pipeline
996 .textures_data
997 .entry(name.to_owned())
998 .or_insert(quad_texture) = quad_texture;
999 }
1000
1001 pub(crate) fn update_drawcall_capacity(
1002 &mut self,
1003 ctx: &mut dyn miniquad::RenderingBackend,
1004 max_vertices: usize,
1005 max_indices: usize,
1006 ) {
1007 self.max_vertices = max_vertices;
1008 self.max_indices = max_indices;
1009 self.draw_calls_count = 0;
1010
1011 for draw_call in &mut self.draw_calls {
1012 draw_call.indices_start = 0;
1013 draw_call.vertices_start = 0;
1014 }
1015 for binding in &mut self.draw_calls_bindings {
1016 ctx.delete_buffer(binding.index_buffer);
1017 for vertex_buffer in &binding.vertex_buffers {
1018 ctx.delete_buffer(*vertex_buffer);
1019 }
1020 let vertex_buffer = ctx.new_buffer(
1021 BufferType::VertexBuffer,
1022 BufferUsage::Stream,
1023 BufferSource::empty::<Vertex>(self.max_vertices),
1024 );
1025 let index_buffer = ctx.new_buffer(
1026 BufferType::IndexBuffer,
1027 BufferUsage::Stream,
1028 BufferSource::empty::<u16>(self.max_indices),
1029 );
1030 *binding = Bindings {
1031 vertex_buffers: vec![vertex_buffer],
1032 index_buffer,
1033 images: vec![self.white_texture, self.white_texture],
1034 };
1035 }
1036 }
1037}
1038
1039mod shader {
1040 use miniquad::{ShaderMeta, UniformBlockLayout, UniformDesc, UniformType};
1041
1042 pub const VERTEX: &str = r#"#version 100
1043 attribute vec3 position;
1044 attribute vec2 texcoord;
1045 attribute vec4 color0;
1046 attribute vec4 normal;
1047
1048 varying lowp vec2 uv;
1049 varying lowp vec4 color;
1050
1051 uniform mat4 Model;
1052 uniform mat4 Projection;
1053
1054 void main() {
1055 gl_Position = Projection * Model * vec4(position, 1);
1056 color = color0 / 255.0;
1057 uv = texcoord;
1058 }"#;
1059
1060 pub const FRAGMENT: &str = r#"#version 100
1061 varying lowp vec4 color;
1062 varying lowp vec2 uv;
1063
1064 uniform sampler2D Texture;
1065
1066 void main() {
1067 gl_FragColor = color * texture2D(Texture, uv) ;
1068 }"#;
1069
1070 pub const METAL: &str = r#"
1071#include <metal_stdlib>
1072 using namespace metal;
1073
1074 struct Uniforms
1075 {
1076 float4x4 Model;
1077 float4x4 Projection;
1078 };
1079
1080 struct Vertex
1081 {
1082 float3 position [[attribute(0)]];
1083 float2 texcoord [[attribute(1)]];
1084 float4 color0 [[attribute(2)]];
1085 };
1086
1087 struct RasterizerData
1088 {
1089 float4 position [[position]];
1090 float4 color [[user(locn0)]];
1091 float2 uv [[user(locn1)]];
1092 };
1093
1094 vertex RasterizerData vertexShader(Vertex v [[stage_in]], constant Uniforms& uniforms [[buffer(0)]])
1095 {
1096 RasterizerData out;
1097
1098 out.position = uniforms.Model * uniforms.Projection * float4(v.position, 1);
1099 out.color = v.color0 / 255.0;
1100 out.uv = v.texcoord;
1101
1102 return out;
1103 }
1104
1105 fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<float> tex [[texture(0)]], sampler texSmplr [[sampler(0)]])
1106 {
1107 return in.color * tex.sample(texSmplr, in.uv);
1108 }
1109 "#;
1110 pub fn uniforms() -> Vec<(&'static str, UniformType)> {
1111 vec![
1112 ("Projection", UniformType::Mat4),
1113 ("Model", UniformType::Mat4),
1114 ("_Time", UniformType::Float4),
1115 ]
1116 }
1117
1118 pub fn meta() -> ShaderMeta {
1119 ShaderMeta {
1120 images: vec!["Texture".to_string(), "_ScreenTexture".to_string()],
1121 uniforms: UniformBlockLayout {
1122 uniforms: uniforms()
1123 .into_iter()
1124 .map(|(name, kind)| UniformDesc::new(name, kind))
1125 .collect(),
1126 },
1127 }
1128 }
1129}