Skip to main content

optic_render/handles/
mesh.rs

1use cgmath;
2use optic_core::{ATTRType, DrawMode, OpticError, OpticErrorKind, OpticResult};
3
4use crate::GL;
5
6use gl::types::{GLenum, GLint, GLsizei, GLsizeiptr};
7use std::ffi::c_void;
8use std::ptr;
9
10use crate::asset::attr::ATTRInfo;
11use crate::asset::attr::DataType;
12use crate::handles::instance::InstanceBuffer;
13use crate::handles::Shader;
14
15/// Low-level OpenGL mesh handle wrapping VAO, VBO, IBO, and instance state.
16///
17/// Created by [`Mesh3DFile::ship`](crate::asset::Mesh3DFile::ship) or
18/// [`Mesh2DFile::ship`](crate::asset::Mesh2DFile::ship). Contains the vertex
19/// layouts, draw mode, index state, and instance buffer binding.
20#[derive(Clone, Debug)]
21pub struct MeshHandle {
22    pub layouts: Vec<(ATTRInfo, u32)>,
23    pub draw_mode: DrawMode,
24    pub has_indices: bool,
25    pub vert_count: u32,
26    pub ind_count: u32,
27    pub vao_id: u32,
28    pub buf_id: u32,
29    pub ind_id: u32,
30    pub vert_stride: u32,
31    pub instance_buf_id: u32,
32    pub instance_count: u32,
33}
34
35impl MeshHandle {
36    /// Issues the draw call for this mesh (instanced or non-instanced, indexed or array).
37    pub fn draw(&self) {
38        GL::bind_vao(self.vao_id);
39        if self.instance_buf_id != 0 && self.instance_count > 0 {
40            match self.has_indices {
41                false => self.draw_array_instanced(),
42                true => {
43                    GL::bind_ebo(self.ind_id);
44                    self.draw_indexed_instanced();
45                }
46            }
47        } else {
48            match self.has_indices {
49                false => self.draw_array(),
50                true => {
51                    GL::bind_ebo(self.ind_id);
52                    self.draw_indexed();
53                }
54            }
55        }
56    }
57
58    fn draw_indexed(&self) {
59        unsafe {
60            gl::DrawElements(
61                match_draw_mode(&self.draw_mode),
62                self.ind_count as GLsizei,
63                gl::UNSIGNED_INT,
64                ptr::null(),
65            );
66        }
67    }
68
69    fn draw_array(&self) {
70        unsafe {
71            gl::DrawArrays(match_draw_mode(&self.draw_mode), 0, self.vert_count as GLsizei);
72        }
73    }
74
75    fn draw_indexed_instanced(&self) {
76        unsafe {
77            gl::DrawElementsInstanced(
78                match_draw_mode(&self.draw_mode),
79                self.ind_count as GLsizei,
80                gl::UNSIGNED_INT,
81                ptr::null(),
82                self.instance_count as GLsizei,
83            );
84        }
85    }
86
87    fn draw_array_instanced(&self) {
88        unsafe {
89            gl::DrawArraysInstanced(
90                match_draw_mode(&self.draw_mode),
91                0,
92                self.vert_count as GLsizei,
93                self.instance_count as GLsizei,
94            );
95        }
96    }
97
98    /// Binds an instance buffer to this mesh for instanced rendering.
99    ///
100    /// Instance attributes are appended after the vertex attributes using
101    /// `glVertexAttribDivisor(1)`.
102    pub fn set_instances(&mut self, buffer: &InstanceBuffer) {
103        if buffer.count() == 0 {
104            self.instance_buf_id = 0;
105            self.instance_count = 0;
106            return;
107        }
108
109        GL::bind_vao(self.vao_id);
110        GL::bind_buffer(buffer.buf_id);
111
112        let base_attr = self.layouts.len() as u32;
113        let mut offset = 0usize;
114        for (i, (info, _)) in buffer.layouts.iter().enumerate() {
115            let location = base_attr + i as u32;
116            let attr_size = info.elem_count * info.byte_count;
117            set_attr_layout(info, location, buffer.stride as usize, offset);
118            unsafe { gl::VertexAttribDivisor(location, 1); }
119            offset += attr_size;
120        }
121
122        self.instance_buf_id = buffer.buf_id;
123        self.instance_count = buffer.count();
124    }
125
126    /// Updates a single vertex attribute value on the GPU.
127    ///
128    /// The type `D` must match the attribute's declared type at creation time.
129    pub fn update_vertex<D: DataType>(&self, index: u32, attr_index: usize, value: D) -> OpticResult<()> {
130        if index >= self.vert_count {
131            return Err(OpticError::new(
132                OpticErrorKind::Custom,
133                &format!("vertex index {index} out of bounds (count: {})", self.vert_count),
134            ));
135        }
136        if attr_index >= self.layouts.len() {
137            return Err(OpticError::new(
138                OpticErrorKind::Custom,
139                &format!("attr index {attr_index} out of bounds (layout count: {})", self.layouts.len()),
140            ));
141        }
142        let slot_info = &self.layouts[attr_index].0;
143        if slot_info.byte_count != D::BYTE_COUNT || slot_info.elem_count != D::ELEM_COUNT || slot_info.typ != D::ATTR_FORMAT {
144            return Err(OpticError::new(
145                OpticErrorKind::Custom,
146                &format!(
147                    "type mismatch: attribute {} expects {:?}[{}], got {:?}[{}]",
148                    slot_info.name.as_string(),
149                    slot_info.typ,
150                    slot_info.elem_count,
151                    D::ATTR_FORMAT,
152                    D::ELEM_COUNT,
153                ),
154            ));
155        }
156        let bytes = value.u8ify();
157        let off = self.compute_vert_attr_offset(attr_index, index);
158        subfill_buffer(self.buf_id, off, &bytes);
159        Ok(())
160    }
161
162    /// Reads a single vertex attribute value from the GPU.
163    pub fn get_vertex<D: DataType>(&self, index: u32, attr_index: usize) -> OpticResult<D> {
164        if index >= self.vert_count {
165            return Err(OpticError::new(
166                OpticErrorKind::Custom,
167                &format!("vertex index {index} out of bounds (count: {})", self.vert_count),
168            ));
169        }
170        if attr_index >= self.layouts.len() {
171            return Err(OpticError::new(
172                OpticErrorKind::Custom,
173                &format!("attr index {attr_index} out of bounds (layout count: {})", self.layouts.len()),
174            ));
175        }
176        let slot_info = &self.layouts[attr_index].0;
177        if slot_info.byte_count != D::BYTE_COUNT || slot_info.elem_count != D::ELEM_COUNT || slot_info.typ != D::ATTR_FORMAT {
178            return Err(OpticError::new(
179                OpticErrorKind::Custom,
180                &format!(
181                    "type mismatch: attribute {} expects {:?}[{}], got {:?}[{}]",
182                    slot_info.name.as_string(),
183                    slot_info.typ,
184                    slot_info.elem_count,
185                    D::ATTR_FORMAT,
186                    D::ELEM_COUNT,
187                ),
188            ));
189        }
190        let off = self.compute_vert_attr_offset(attr_index, index);
191        let size = slot_info.elem_count * slot_info.byte_count;
192        let mut data = vec![0u8; size];
193        unsafe {
194            gl::BindBuffer(gl::ARRAY_BUFFER, self.buf_id);
195            gl::GetBufferSubData(
196                gl::ARRAY_BUFFER,
197                off as isize,
198                size as isize,
199                data.as_mut_ptr() as *mut c_void,
200            );
201        }
202        Ok(unsafe { std::ptr::read_unaligned(data.as_ptr() as *const D) })
203    }
204
205    /// Writes raw vertex data starting at `start_vertex`.
206    ///
207    /// `data` length must be a multiple of the vertex stride.
208    pub fn write_range(&self, start_vertex: u32, data: &[u8]) -> OpticResult<()> {
209        let stride = self.vert_stride as usize;
210        if data.len() % stride != 0 {
211            return Err(OpticError::new(
212                OpticErrorKind::Custom,
213                "write_range data length must be a multiple of vertex stride",
214            ));
215        }
216        let vertex_count = data.len() / stride;
217        if start_vertex + vertex_count as u32 > self.vert_count {
218            return Err(OpticError::new(
219                OpticErrorKind::Custom,
220                "write_range extends past the vertex count",
221            ));
222        }
223        let start_off = start_vertex as usize * stride;
224        subfill_buffer(self.buf_id, start_off, data);
225        Ok(())
226    }
227
228    fn compute_vert_attr_offset(&self, attr_index: usize, vertex_index: u32) -> usize {
229        let mut offset = vertex_index as usize * self.vert_stride as usize;
230        for i in 0..attr_index {
231            let si = &self.layouts[i].0;
232            offset += si.elem_count * si.byte_count;
233        }
234        offset
235    }
236
237    /// Deletes the VAO, VBO, and IBO from the GPU.
238    pub fn delete(self) {
239        unsafe {
240            gl::DeleteVertexArrays(1, &self.vao_id);
241            gl::DeleteBuffers(1, &self.buf_id);
242            if self.has_indices {
243                gl::DeleteBuffers(1, &self.ind_id);
244            }
245        }
246    }
247}
248
249fn match_draw_mode(dm: &DrawMode) -> GLenum {
250    match dm {
251        DrawMode::Points => gl::POINTS,
252        DrawMode::Lines => gl::LINES,
253        DrawMode::Triangles => gl::TRIANGLES,
254        DrawMode::Strip => gl::TRIANGLE_STRIP,
255    }
256}
257
258macro_rules! mesh_struct {
259    ($mesh:ident, $transform:ty) => {
260        /// High-level mesh with visibility, shader, transform, and draw mode.
261        ///
262        /// Cloning is cheap — the handle shares the same GPU buffers.
263        #[derive(Clone, Debug)]
264        pub struct $mesh {
265            pub visibility: bool,
266            pub handle: MeshHandle,
267            pub shader: Option<Shader>,
268            pub transform: $transform,
269            pub draw_mode: DrawMode,
270        }
271
272        impl $mesh {
273            /// Attaches a shader to this mesh.
274            pub fn set_shader(&mut self, shader: Shader) { self.shader = Some(shader); }
275            /// Detaches the current shader.
276            pub fn remove_shader(&mut self) { self.shader = None; }
277            /// Returns the current draw mode.
278            pub fn get_draw_mode(&self) -> DrawMode { self.handle.draw_mode }
279            /// Sets the draw mode.
280            pub fn set_draw_mode(&mut self, draw_mode: DrawMode) { self.handle.draw_mode = draw_mode; }
281            /// Returns the index count.
282            pub fn index_count(&self) -> u32 { self.handle.ind_count }
283            /// Returns the vertex count.
284            pub fn vertex_count(&self) -> u32 { self.handle.vert_count }
285            /// Returns `true` if this mesh uses indexed drawing.
286            pub fn has_indices(&self) -> bool { self.handle.has_indices }
287            /// Returns `true` when the mesh has no vertices.
288            pub fn is_empty(&self) -> bool { self.vertex_count() == 0 }
289            /// Returns `true` when the mesh is both visible and non-empty.
290            pub fn is_visible(&self) -> bool { self.visibility && !self.is_empty() }
291            /// Shows or hides this mesh.
292            pub fn set_visibility(&mut self, enable: bool) { self.visibility = enable; }
293            /// Toggles visibility.
294            pub fn toggle_visibility(&mut self) { self.visibility = !self.visibility; }
295            /// Recomputes the transformation matrix.
296            pub fn update(&mut self) { self.transform.calc_matrix(); }
297            /// Deletes the GPU resources for this mesh's handle.
298            pub fn delete(self) { self.handle.delete(); }
299        }
300    };
301}
302
303// A 3D mesh with position, rotation, and scale.
304mesh_struct!(Mesh3D, crate::util::transform::Transform3D);
305// A 2D mesh with position, rotation, scale, and layer.
306mesh_struct!(Mesh2D, crate::util::transform::Transform2D);
307
308impl Mesh3D {
309    /// Prints debug information about this mesh to stdout.
310    pub fn log_info(&self) {
311        let shader_id = self.shader.as_ref().map(|s| s.id).unwrap_or(0);
312        let mode = format!("{:?}", self.get_draw_mode());
313        println!(
314            "[Mesh3D] vis={} verts={} inds={} has_idx={} shader={} mode={} vao={} buf={} ind={}",
315            self.visibility,
316            self.vertex_count(),
317            self.index_count(),
318            self.has_indices(),
319            shader_id,
320            mode,
321            self.handle.vao_id,
322            self.handle.buf_id,
323            self.handle.ind_id,
324        );
325    }
326
327    /// Renders this mesh with the given view and projection matrices.
328    ///
329    /// Binds the shader, sets `uView`, `uProj`, `uTfm` uniforms, binds
330    /// textures and storage buffers, then issues the draw call.
331    pub fn render(&self, view: &cgmath::Matrix4<f32>, proj: &cgmath::Matrix4<f32>) {
332        if !self.is_visible() { return; }
333        let shader = match &self.shader { None => return, Some(sh) => sh };
334        shader.bind();
335
336        shader.set_m4_f32("uView", *view);
337        shader.set_m4_f32("uProj", *proj);
338        shader.set_m4_f32("uTfm", self.transform.matrix());
339
340        shader.bind_textures();
341        shader.bind_storages();
342        self.handle.draw();
343    }
344}
345
346impl Mesh2D {
347    /// Prints debug information about this mesh to stdout.
348    pub fn log_info(&self) {
349        let shader_id = self.shader.as_ref().map(|s| s.id).unwrap_or(0);
350        let mode = format!("{:?}", self.get_draw_mode());
351        println!(
352            "[Mesh2D] vis={} verts={} inds={} has_idx={} shader={} mode={} vao={} buf={} ind={}",
353            self.visibility,
354            self.vertex_count(),
355            self.index_count(),
356            self.has_indices(),
357            shader_id,
358            mode,
359            self.handle.vao_id,
360            self.handle.buf_id,
361            self.handle.ind_id,
362        );
363    }
364
365    /// Renders this 2D mesh with an orthographic projection matrix.
366    ///
367    /// Sets `uProj`, `uTfm`, `uLayer` uniforms, binds textures and
368    /// storage buffers, then issues the draw call.
369    pub fn render(&self, proj: &cgmath::Matrix4<f32>) {
370        if !self.is_visible() { return; }
371        let shader = match &self.shader { None => return, Some(sh) => sh };
372        shader.bind();
373
374        shader.set_m4_f32("uProj", *proj);
375        let tfm = self.transform.matrix();
376        let layer = self.transform.layer() as u32;
377        shader.set_m4_f32("uTfm", tfm);
378        shader.set_u32("uLayer", layer);
379
380        shader.bind_textures();
381        shader.bind_storages();
382        self.handle.draw();
383    }
384}
385
386/// Creates a VAO + VBO pair and returns their IDs.
387pub fn create_mesh_buffer() -> (u32, u32) {
388    let (mut v_id, mut b_id) = (0u32, 0u32);
389    unsafe {
390        gl::GenVertexArrays(1, &mut v_id);
391        gl::GenBuffers(1, &mut b_id);
392    }
393    (v_id, b_id)
394}
395
396/// Configures a vertex attribute pointer for the given layout.
397pub fn set_attr_layout(attr: &ATTRInfo, attr_id: u32, stride: usize, local_offset: usize) {
398    unsafe {
399        gl::VertexAttribPointer(
400            attr_id,
401            attr.elem_count as GLint,
402            match_attr_type(&attr.typ),
403            gl::FALSE,
404            stride as GLsizei,
405            match local_offset {
406                0 => ptr::null(),
407                _ => local_offset as *const c_void,
408            },
409        );
410        gl::EnableVertexAttribArray(attr_id);
411    }
412}
413
414/// Uploads data to a VBO (full buffer replace).
415pub fn fill_buffer(id: u32, data: &[u8]) {
416    unsafe {
417        GL::bind_buffer(id);
418        gl::BufferData(
419            gl::ARRAY_BUFFER,
420            data.len() as GLsizeiptr,
421            data.as_ptr() as *const c_void,
422            gl::DYNAMIC_DRAW,
423        );
424    }
425}
426
427/// Uploads data to a sub-range of a VBO.
428pub fn subfill_buffer(id: u32, offset: usize, data: &[u8]) {
429    unsafe {
430        GL::bind_buffer(id);
431        gl::BufferSubData(
432            gl::ARRAY_BUFFER,
433            offset as isize,
434            data.len() as isize,
435            data.as_ptr() as *const c_void,
436        );
437    }
438}
439
440/// Resizes a VBO without uploading data (contents become undefined).
441pub fn resize_buffer(id: u32, size: usize) {
442    unsafe {
443        GL::bind_buffer(id);
444        gl::BufferData(
445            gl::ARRAY_BUFFER,
446            size as GLsizeiptr,
447            ptr::null(),
448            gl::DYNAMIC_DRAW,
449        );
450    }
451}
452
453/// Creates an IBO (element array buffer) and returns its ID.
454pub fn create_index_buffer() -> u32 {
455    let mut id = 0u32;
456    unsafe { gl::GenBuffers(1, &mut id); }
457    id
458}
459
460/// Uploads index data to an IBO.
461pub fn fill_index_buffer(id: u32, data: &[u32]) {
462    unsafe {
463        GL::bind_ebo(id);
464        gl::BufferData(
465            gl::ELEMENT_ARRAY_BUFFER,
466            (data.len() * size_of::<u32>()) as GLsizeiptr,
467            data.as_ptr() as *const c_void,
468            gl::DYNAMIC_DRAW,
469        );
470    }
471}
472
473/// A GPU-side shader storage buffer (SSBO) for compute or vertex pulling.
474///
475/// Supports dynamic resizing, filling, sub-range updates, and read-back.
476///
477/// # Example
478///
479/// ```ignore
480/// use optic_render::handles::StorageBuffer;
481///
482/// let mut ssbo = StorageBuffer::new(1024);
483/// ssbo.fill(&[1u8; 1024]);
484/// let data = ssbo.fetch();
485/// ```
486pub struct StorageBuffer {
487    pub id: u32,
488    pub size: usize,
489}
490
491impl StorageBuffer {
492    /// Creates a new SSBO with the given byte size (zero-initialised).
493    pub fn new(size: usize) -> Self {
494        let id = create_storage_buffer();
495        resize_storage_buffer(id, size);
496        Self { id, size }
497    }
498
499    /// Resizes the buffer (contents become undefined if size changed).
500    pub fn resize(&mut self, size: usize) {
501        self.bind();
502        if size != self.size {
503            self.size = size;
504            resize_storage_buffer(self.id, self.size);
505        }
506    }
507
508    /// Replaces the full buffer contents.
509    pub fn fill(&mut self, data: &[u8]) {
510        self.bind();
511        self.resize(data.len());
512        fill_storage_buffer(self.id, data);
513    }
514
515    /// Writes data at a byte offset, growing the buffer if needed.
516    pub fn subfill(&mut self, offset: usize, data: &[u8]) {
517        self.bind();
518        let len = data.len() + offset;
519        self.resize(len);
520        subfill_storage_buffer(self.id, offset, data);
521    }
522
523    /// Reads the entire buffer back to the CPU.
524    pub fn fetch(&self) -> Vec<u8> {
525        self.bind();
526        read_storage_buffer(self.id, self.size)
527    }
528
529    /// Deletes the GPU buffer.
530    pub fn delete(self) {
531        unsafe { gl::DeleteBuffers(1, &self.id); }
532    }
533
534    fn bind(&self) {
535        unsafe { gl::BindBuffer(gl::SHADER_STORAGE_BUFFER, self.id); }
536    }
537}
538
539fn create_storage_buffer() -> u32 {
540    let mut id = 0u32;
541    unsafe { gl::GenBuffers(1, &mut id); }
542    id
543}
544
545fn fill_storage_buffer(id: u32, data: &[u8]) {
546    unsafe {
547        gl::BindBuffer(gl::SHADER_STORAGE_BUFFER, id);
548        gl::BufferData(
549            gl::SHADER_STORAGE_BUFFER,
550            data.len() as GLsizeiptr,
551            data.as_ptr() as *const c_void,
552            gl::DYNAMIC_DRAW,
553        );
554    }
555}
556
557fn subfill_storage_buffer(id: u32, offset: usize, data: &[u8]) {
558    unsafe {
559        gl::BindBuffer(gl::SHADER_STORAGE_BUFFER, id);
560        gl::BufferSubData(
561            gl::SHADER_STORAGE_BUFFER,
562            offset as isize,
563            data.len() as isize,
564            data.as_ptr() as *const c_void,
565        );
566    }
567}
568
569fn resize_storage_buffer(id: u32, size: usize) {
570    unsafe {
571        gl::BindBuffer(gl::SHADER_STORAGE_BUFFER, id);
572        gl::BufferData(
573            gl::SHADER_STORAGE_BUFFER,
574            size as GLsizeiptr,
575            ptr::null(),
576            gl::DYNAMIC_DRAW,
577        );
578    }
579}
580
581fn read_storage_buffer(id: u32, size: usize) -> Vec<u8> {
582    let mut data = vec![0u8; size];
583    unsafe {
584        gl::BindBuffer(gl::SHADER_STORAGE_BUFFER, id);
585        gl::GetBufferSubData(
586            gl::SHADER_STORAGE_BUFFER,
587            0,
588            size as GLsizeiptr,
589            data.as_mut_ptr() as *mut c_void,
590        );
591    }
592    data
593}
594
595fn match_attr_type(attr_type: &ATTRType) -> GLenum {
596    match attr_type {
597        ATTRType::I8 => gl::BYTE,
598        ATTRType::U8 => gl::UNSIGNED_BYTE,
599        ATTRType::I16 => gl::SHORT,
600        ATTRType::U16 => gl::UNSIGNED_SHORT,
601        ATTRType::I32 => gl::INT,
602        ATTRType::U32 => gl::UNSIGNED_INT,
603        ATTRType::F32 => gl::FLOAT,
604        ATTRType::F64 => gl::DOUBLE,
605    }
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611
612    #[test]
613    fn mesh_handle_fields() {
614        let mh = MeshHandle {
615            layouts: vec![],
616            draw_mode: DrawMode::Triangles,
617            has_indices: false,
618            vert_count: 42,
619            ind_count: 0,
620            vao_id: 0,
621            buf_id: 0,
622            ind_id: 0,
623            vert_stride: 0,
624            instance_buf_id: 0,
625            instance_count: 0,
626        };
627        assert_eq!(mh.vert_count, 42);
628        assert!(!mh.has_indices);
629    }
630
631    #[test]
632    fn mesh3d_default_state() {
633        let mh = MeshHandle {
634            layouts: vec![],
635            draw_mode: DrawMode::Triangles,
636            has_indices: true,
637            vert_count: 3,
638            ind_count: 3,
639            vao_id: 0,
640            buf_id: 0,
641            ind_id: 0,
642            vert_stride: 0,
643            instance_buf_id: 0,
644            instance_count: 0,
645        };
646        let m3d = Mesh3D {
647            visibility: true,
648            handle: mh,
649            shader: None,
650            transform: crate::util::transform::Transform3D::default(),
651            draw_mode: DrawMode::Triangles,
652        };
653        assert!(m3d.is_visible());
654        assert!(!m3d.is_empty());
655        assert_eq!(m3d.vertex_count(), 3);
656        assert_eq!(m3d.index_count(), 3);
657        assert!(m3d.has_indices());
658        assert_eq!(m3d.get_draw_mode(), DrawMode::Triangles);
659        assert!(m3d.shader.is_none());
660    }
661
662    #[test]
663    fn mesh3d_visibility_toggle() {
664        let mh = MeshHandle {
665            layouts: vec![],
666            draw_mode: DrawMode::Triangles,
667            has_indices: false,
668            vert_count: 3,
669            ind_count: 0,
670            vao_id: 0,
671            buf_id: 0,
672            ind_id: 0,
673            vert_stride: 0,
674            instance_buf_id: 0,
675            instance_count: 0,
676        };
677        let mut m3d = Mesh3D {
678            visibility: true,
679            handle: mh,
680            shader: None,
681            transform: crate::util::transform::Transform3D::default(),
682            draw_mode: DrawMode::Triangles,
683        };
684        assert!(m3d.is_visible());
685        m3d.set_visibility(false);
686        assert!(!m3d.is_visible());
687        m3d.toggle_visibility();
688        assert!(m3d.is_visible());
689    }
690
691    #[test]
692    fn mesh3d_set_draw_mode() {
693        let mh = MeshHandle {
694            layouts: vec![],
695            draw_mode: DrawMode::Triangles,
696            has_indices: false,
697            vert_count: 3,
698            ind_count: 0,
699            vao_id: 0,
700            buf_id: 0,
701            ind_id: 0,
702            vert_stride: 0,
703            instance_buf_id: 0,
704            instance_count: 0,
705        };
706        let mut m3d = Mesh3D {
707            visibility: true,
708            handle: mh,
709            shader: None,
710            transform: crate::util::transform::Transform3D::default(),
711            draw_mode: DrawMode::Triangles,
712        };
713        m3d.set_draw_mode(DrawMode::Points);
714        assert_eq!(m3d.get_draw_mode(), DrawMode::Points);
715    }
716
717    #[test]
718    fn mesh3d_shader_management() {
719        let mh = MeshHandle {
720            layouts: vec![],
721            draw_mode: DrawMode::Triangles,
722            has_indices: false,
723            vert_count: 3,
724            ind_count: 0,
725            vao_id: 0,
726            buf_id: 0,
727            ind_id: 0,
728            vert_stride: 0,
729            instance_buf_id: 0,
730            instance_count: 0,
731        };
732        let mut m3d = Mesh3D {
733            visibility: true,
734            handle: mh,
735            shader: None,
736            transform: crate::util::transform::Transform3D::default(),
737            draw_mode: DrawMode::Triangles,
738        };
739        assert!(m3d.shader.is_none());
740        let s = Shader::new(99, false);
741        m3d.set_shader(s);
742        assert!(m3d.shader.is_some());
743        assert_eq!(m3d.shader.as_ref().unwrap().id, 99);
744        m3d.remove_shader();
745        assert!(m3d.shader.is_none());
746    }
747
748    #[test]
749    fn mesh3d_update_calc_matrix() {
750        let mh = MeshHandle {
751            layouts: vec![],
752            draw_mode: DrawMode::Triangles,
753            has_indices: false,
754            vert_count: 3,
755            ind_count: 0,
756            vao_id: 0,
757            buf_id: 0,
758            ind_id: 0,
759            vert_stride: 0,
760            instance_buf_id: 0,
761            instance_count: 0,
762        };
763        let mut m3d = Mesh3D {
764            visibility: true,
765            handle: mh,
766            shader: None,
767            transform: crate::util::transform::Transform3D::default(),
768            draw_mode: DrawMode::Triangles,
769        };
770        let ident = m3d.transform.matrix();
771        m3d.transform.set_pos_all(10.0, 20.0, 30.0);
772        m3d.update();
773        let m = m3d.transform.matrix();
774        assert!(ident != m);
775    }
776
777    #[test]
778    fn mesh3d_is_empty_true() {
779        let mh = MeshHandle {
780            layouts: vec![],
781            draw_mode: DrawMode::Triangles,
782            has_indices: false,
783            vert_count: 0,
784            ind_count: 0,
785            vao_id: 0,
786            buf_id: 0,
787            ind_id: 0,
788            vert_stride: 0,
789            instance_buf_id: 0,
790            instance_count: 0,
791        };
792        let m3d = Mesh3D {
793            visibility: true,
794            handle: mh,
795            shader: None,
796            transform: crate::util::transform::Transform3D::default(),
797            draw_mode: DrawMode::Triangles,
798        };
799        assert!(m3d.is_empty());
800        assert!(!m3d.is_visible());
801    }
802
803    #[test]
804    fn mesh2d_default_state() {
805        let mh = MeshHandle {
806            layouts: vec![],
807            draw_mode: DrawMode::Triangles,
808            has_indices: true,
809            vert_count: 4,
810            ind_count: 6,
811            vao_id: 0,
812            buf_id: 0,
813            ind_id: 0,
814            vert_stride: 0,
815            instance_buf_id: 0,
816            instance_count: 0,
817        };
818        let m2d = Mesh2D {
819            visibility: true,
820            handle: mh,
821            shader: None,
822            transform: crate::util::transform::Transform2D::default(),
823            draw_mode: DrawMode::Triangles,
824        };
825        assert!(m2d.is_visible());
826        assert_eq!(m2d.vertex_count(), 4);
827        assert!(m2d.has_indices());
828    }
829
830    #[test]
831    fn storage_buffer_create() {
832        // Only test the struct fields, not GL creation
833        let sb = StorageBuffer { id: 0, size: 0 };
834        assert_eq!(sb.id, 0);
835        assert_eq!(sb.size, 0);
836    }
837}