Skip to main content

macroquad_ply/
quad_gl.rs

1//! Legacy module, code should be either removed or moved to different modules
2
3use 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    /// Reset only draw calls state
666    pub fn clear_draw_calls(&mut self) {
667        self.draw_calls_count = 0;
668    }
669
670    /// Reset internal state to known default
671    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        // get_projection_matrix is a way plugins used to get macroquad's current projection
811        // back in the days when projection was a part of static batcher
812        // now it is not, so here we go with this hack
813
814        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}