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#[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 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 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 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 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 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 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 #[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 pub fn set_shader(&mut self, shader: Shader) { self.shader = Some(shader); }
275 pub fn remove_shader(&mut self) { self.shader = None; }
277 pub fn get_draw_mode(&self) -> DrawMode { self.handle.draw_mode }
279 pub fn set_draw_mode(&mut self, draw_mode: DrawMode) { self.handle.draw_mode = draw_mode; }
281 pub fn index_count(&self) -> u32 { self.handle.ind_count }
283 pub fn vertex_count(&self) -> u32 { self.handle.vert_count }
285 pub fn has_indices(&self) -> bool { self.handle.has_indices }
287 pub fn is_empty(&self) -> bool { self.vertex_count() == 0 }
289 pub fn is_visible(&self) -> bool { self.visibility && !self.is_empty() }
291 pub fn set_visibility(&mut self, enable: bool) { self.visibility = enable; }
293 pub fn toggle_visibility(&mut self) { self.visibility = !self.visibility; }
295 pub fn update(&mut self) { self.transform.calc_matrix(); }
297 pub fn delete(self) { self.handle.delete(); }
299 }
300 };
301}
302
303mesh_struct!(Mesh3D, crate::util::transform::Transform3D);
305mesh_struct!(Mesh2D, crate::util::transform::Transform2D);
307
308impl Mesh3D {
309 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 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 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 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
386pub 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
396pub 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
414pub 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
427pub 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
440pub 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
453pub fn create_index_buffer() -> u32 {
455 let mut id = 0u32;
456 unsafe { gl::GenBuffers(1, &mut id); }
457 id
458}
459
460pub 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
473pub struct StorageBuffer {
487 pub id: u32,
488 pub size: usize,
489}
490
491impl StorageBuffer {
492 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 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 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 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 pub fn fetch(&self) -> Vec<u8> {
525 self.bind();
526 read_storage_buffer(self.id, self.size)
527 }
528
529 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 let sb = StorageBuffer { id: 0, size: 0 };
834 assert_eq!(sb.id, 0);
835 assert_eq!(sb.size, 0);
836 }
837}