summaryrefslogtreecommitdiff
path: root/engine/src/renderer/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/renderer/opengl')
-rw-r--r--engine/src/renderer/opengl/glsl/fragment.glsl73
-rw-r--r--engine/src/renderer/opengl/glsl/light.glsl133
-rw-r--r--engine/src/renderer/opengl/glsl/vertex.glsl24
-rw-r--r--engine/src/renderer/opengl/glsl/vertex_data.glsl11
-rw-r--r--engine/src/renderer/opengl/glutin_compat.rs268
-rw-r--r--engine/src/renderer/opengl/graphics_mesh.rs136
-rw-r--r--engine/src/renderer/opengl/vertex.rs65
7 files changed, 469 insertions, 241 deletions
diff --git a/engine/src/renderer/opengl/glsl/fragment.glsl b/engine/src/renderer/opengl/glsl/fragment.glsl
deleted file mode 100644
index 5bf5ff2..0000000
--- a/engine/src/renderer/opengl/glsl/fragment.glsl
+++ /dev/null
@@ -1,73 +0,0 @@
-#version 330 core
-
-#preinclude "light.glsl"
-#preinclude "vertex_data.glsl"
-
-#define MAX_LIGHT_CNT 64
-
-out vec4 FragColor;
-
-in VertexData vertex_data;
-
-uniform vec3 view_pos;
-uniform sampler2D input_texture;
-uniform Material material;
-
-uniform PointLight point_lights[MAX_LIGHT_CNT];
-uniform int point_light_cnt;
-
-uniform DirectionalLight directional_lights[MAX_LIGHT_CNT];
-uniform int directional_light_cnt;
-
-void main()
-{
- vec3 ambient_light = calc_ambient_light(material, vertex_data.texture_coords);
-
- vec3 directional_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int dl_index = 0; dl_index < directional_light_cnt; dl_index++) {
- CalculatedLight calculated_dir_light;
-
- calc_light(
- // Negated since we want the light to point from the light direction
- normalize(-directional_lights[dl_index].direction),
- directional_lights[dl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_dir_light
- );
-
- directional_light_sum +=
- calculated_dir_light.diffuse + calculated_dir_light.specular;
- }
-
- vec3 point_light_sum = vec3(0.0, 0.0, 0.0);
-
- for (int pl_index = 0; pl_index < point_light_cnt; pl_index++) {
- vec3 light_direction =
- normalize(point_lights[pl_index].position - vertex_data.world_space_pos);
-
- CalculatedLight calculated_point_light;
-
- calc_light(
- light_direction,
- point_lights[pl_index].phong,
- vertex_data,
- view_pos,
- material,
- calculated_point_light
- );
-
- float attenuation =
- calc_attenuation(point_lights[pl_index], vertex_data.world_space_pos);
-
- calculated_point_light.diffuse *= attenuation;
- calculated_point_light.specular *= attenuation;
-
- point_light_sum +=
- calculated_point_light.diffuse + calculated_point_light.specular;
- }
-
- FragColor = vec4((ambient_light + directional_light_sum + point_light_sum), 1.0);
-}
diff --git a/engine/src/renderer/opengl/glsl/light.glsl b/engine/src/renderer/opengl/glsl/light.glsl
deleted file mode 100644
index 1bc23a4..0000000
--- a/engine/src/renderer/opengl/glsl/light.glsl
+++ /dev/null
@@ -1,133 +0,0 @@
-#version 330 core
-
-#ifndef LIGHT_GLSL
-#define LIGHT_GLSL
-
-#preinclude "vertex_data.glsl"
-
-struct Material
-{
- vec3 ambient;
- vec3 diffuse;
- vec3 specular;
- sampler2D ambient_map;
- sampler2D diffuse_map;
- sampler2D specular_map;
- float shininess;
-};
-
-struct LightPhong
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-struct AttenuationProperties
-{
- float constant;
- float linear;
- float quadratic;
-};
-
-struct PointLight
-{
- LightPhong phong;
- vec3 position;
- AttenuationProperties attenuation_props;
-};
-
-struct DirectionalLight
-{
- LightPhong phong;
- vec3 direction;
-};
-
-struct CalculatedLight
-{
- vec3 diffuse;
- vec3 specular;
-};
-
-vec3 calc_ambient_light(in Material material, in vec2 texture_coords)
-{
- return vec3(texture(material.ambient_map, texture_coords)) * material.ambient;
-}
-
-vec3 calc_diffuse_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec2 texture_coords
-)
-{
- float diff = max(dot(norm, light_dir), 0.0);
-
- return light_phong.diffuse * (
- diff * (vec3(texture(material.diffuse_map, texture_coords)) * material.diffuse)
- );
-}
-
-vec3 calc_specular_light(
- in Material material,
- in LightPhong light_phong,
- in vec3 light_dir,
- in vec3 norm,
- in vec3 view_pos,
- in vec3 frag_pos,
- in vec2 texture_coords
-)
-{
- vec3 view_direction = normalize(view_pos - frag_pos);
-
- vec3 reflect_direction = reflect(-light_dir, norm);
-
- float spec =
- pow(max(dot(view_direction, reflect_direction), 0.0), material.shininess);
-
- return light_phong.specular * (
- spec * (vec3(texture(material.specular_map, texture_coords)) * material.specular)
- );
-}
-
-float calc_attenuation(in PointLight point_light, in vec3 position)
-{
- float light_distance = length(point_light.position - position);
-
- return 1.0 / (point_light.attenuation_props.constant
- + point_light.attenuation_props.linear
- * light_distance + point_light.attenuation_props.quadratic
- * pow(light_distance, 2));
-}
-
-void calc_light(
- in vec3 light_direction,
- in LightPhong light_phong,
- in VertexData vertex_data,
- in vec3 view_pos,
- in Material material,
- out CalculatedLight calculated_light
-)
-{
- vec3 norm = normalize(vertex_data.world_space_normal);
-
- calculated_light.diffuse = calc_diffuse_light(
- material,
- light_phong,
- light_direction,
- norm,
- vertex_data.texture_coords
- );
-
- calculated_light.specular = calc_specular_light(
- material,
- light_phong,
- light_direction,
- norm,
- view_pos,
- vertex_data.world_space_pos,
- vertex_data.texture_coords
- );
-}
-
-#endif
diff --git a/engine/src/renderer/opengl/glsl/vertex.glsl b/engine/src/renderer/opengl/glsl/vertex.glsl
deleted file mode 100644
index b57caa6..0000000
--- a/engine/src/renderer/opengl/glsl/vertex.glsl
+++ /dev/null
@@ -1,24 +0,0 @@
-#version 330 core
-
-#preinclude "vertex_data.glsl"
-
-layout (location = 0) in vec3 pos;
-layout (location = 1) in vec2 texture_coords;
-layout (location = 2) in vec3 normal;
-
-out VertexData vertex_data;
-
-uniform mat4 model;
-uniform mat4 view;
-uniform mat4 projection;
-
-void main()
-{
- gl_Position = projection * view * model * vec4(pos, 1.0);
-
- vertex_data.world_space_pos = vec3(model * vec4(pos, 1.0));
- vertex_data.texture_coords = texture_coords;
-
- // TODO: Do this using CPU for performance increase
- vertex_data.world_space_normal = mat3(transpose(inverse(model))) * normal;
-}
diff --git a/engine/src/renderer/opengl/glsl/vertex_data.glsl b/engine/src/renderer/opengl/glsl/vertex_data.glsl
deleted file mode 100644
index 486d445..0000000
--- a/engine/src/renderer/opengl/glsl/vertex_data.glsl
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef VERTEX_DATA_GLSL
-#define VERTEX_DATA_GLSL
-
-struct VertexData
-{
- vec2 texture_coords;
- vec3 world_space_pos;
- vec3 world_space_normal;
-};
-
-#endif
diff --git a/engine/src/renderer/opengl/glutin_compat.rs b/engine/src/renderer/opengl/glutin_compat.rs
new file mode 100644
index 0000000..cfd6ea7
--- /dev/null
+++ b/engine/src/renderer/opengl/glutin_compat.rs
@@ -0,0 +1,268 @@
+// Original file:
+// https://github.com/rust-windowing/glutin/blob/
+// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs
+//
+// Copyright © 2022 Kirill Chibisov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the “Software”), to deal
+// in the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+//! This library provides helpers for cross-platform [`glutin`] bootstrapping
+//! with [`winit`].
+
+#![deny(rust_2018_idioms)]
+#![deny(rustdoc::broken_intra_doc_links)]
+#![deny(clippy::all)]
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
+#![cfg_attr(clippy, deny(warnings))]
+
+use glutin::config::{Config, ConfigTemplateBuilder};
+use glutin::display::{Display, DisplayApiPreference};
+use glutin::error::Error as GlutinError;
+#[cfg(x11_platform)]
+use glutin::platform::x11::X11GlConfigExt;
+use glutin::prelude::*;
+use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle};
+
+use crate::windowing::window::CreationAttributes as WindowCreationAttributes;
+
+#[cfg(all(not(egl_backend), not(glx_backend), not(wgl_backend), not(cgl_backend)))]
+compile_error!("Please select at least one api backend");
+
+/// The helper to perform [`Display`] creation and OpenGL platform
+/// bootstrapping with the help of [`winit`] with little to no platform specific
+/// code.
+///
+/// This is only required for the initial setup. If you want to create
+/// additional windows just use the [`finalize_window`] function and the
+/// configuration you've used either for the original window or picked with the
+/// existing [`Display`].
+///
+/// [`winit`]: winit
+/// [`Display`]: glutin::display::Display
+#[derive(Default, Debug, Clone)]
+pub struct DisplayBuilder
+{
+ preference: ApiPreference,
+ window_attributes: WindowCreationAttributes,
+}
+
+impl DisplayBuilder
+{
+ /// Create new display builder.
+ pub fn new() -> Self
+ {
+ Default::default()
+ }
+
+ /// The preference in picking the configuration.
+ #[allow(dead_code)]
+ pub fn with_preference(mut self, preference: ApiPreference) -> Self
+ {
+ self.preference = preference;
+ self
+ }
+
+ /// The window attributes to use when building a window.
+ ///
+ /// By default no window is created.
+ pub fn with_window_attributes(
+ mut self,
+ window_creation_attrs: WindowCreationAttributes,
+ ) -> Self
+ {
+ self.window_attributes = window_creation_attrs;
+ self
+ }
+
+ /// Initialize the OpenGL platform and create a compatible window to use
+ /// with it when the [`WindowAttributes`] was passed with
+ /// [`Self::with_window_attributes()`]. It's optional, since on some
+ /// platforms like `Android` it is not available early on, so you want to
+ /// find configuration and later use it with the [`finalize_window`].
+ /// But if you don't care about such platform you can always pass
+ /// [`WindowAttributes`].
+ ///
+ /// # Api-specific
+ ///
+ /// **WGL:** - [`WindowAttributes`] **must** be passed in
+ /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired,
+ /// otherwise only builtin functions like `glClear` will be available.
+ pub fn build<ConfigPickerFn>(
+ self,
+ window_handle: Option<WindowHandle<'_>>,
+ display_handle: &DisplayHandle<'_>,
+ template_builder: ConfigTemplateBuilder,
+ config_picker_fn: ConfigPickerFn,
+ ) -> Result<(WindowCreationAttributes, Config), Error>
+ where
+ ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>,
+ {
+ // XXX with WGL backend window should be created first.
+ let raw_window_handle = if cfg!(wgl_backend) {
+ let Some(window_handle) = window_handle else {
+ return Err(Error::WindowRequired);
+ };
+
+ Some(window_handle.as_raw())
+ } else {
+ None
+ };
+
+ let gl_display =
+ create_display(display_handle, self.preference, raw_window_handle)
+ .map_err(Error::CreateDisplayFailed)?;
+
+ // XXX the native window must be passed to config picker when WGL is used
+ // otherwise very limited OpenGL features will be supported.
+ #[cfg(wgl_backend)]
+ let template_builder = if let Some(raw_window_handle) = raw_window_handle {
+ template_builder.compatible_with_native_window(raw_window_handle)
+ } else {
+ template_builder
+ };
+
+ let template = template_builder.build();
+
+ // SAFETY: The RawWindowHandle passed on the config template
+ // (when cfg(wgl_backend)) will always point to a valid object since it is
+ // derived from the window_handle argument which when Some is a WindowHandle and
+ // WindowHandles always point to a valid object
+ let gl_configs = unsafe { gl_display.find_configs(template) }
+ .map_err(Error::FindConfigsFailed)?;
+
+ let picked_gl_config =
+ config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?;
+
+ #[cfg(not(wgl_backend))]
+ let window_attrs =
+ { finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) };
+
+ #[cfg(wgl_backend)]
+ let window_attrs = self.window_attributes;
+
+ Ok((window_attrs, picked_gl_config))
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to create display")]
+ CreateDisplayFailed(#[source] GlutinError),
+
+ #[error("Failed to find configs")]
+ FindConfigsFailed(#[source] GlutinError),
+
+ #[error("No config was picked by config picker function")]
+ NoConfigPicked,
+
+ #[error("Window required for building display on current platform")]
+ WindowRequired,
+}
+
+fn create_display(
+ display_handle: &DisplayHandle<'_>,
+ _api_preference: ApiPreference,
+ _raw_window_handle: Option<RawWindowHandle>,
+) -> Result<Display, GlutinError>
+{
+ #[cfg(egl_backend)]
+ let _preference = DisplayApiPreference::Egl;
+
+ #[cfg(glx_backend)]
+ let _preference = DisplayApiPreference::Glx(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ ));
+
+ #[cfg(cgl_backend)]
+ let _preference = DisplayApiPreference::Cgl;
+
+ #[cfg(wgl_backend)]
+ let _preference = DisplayApiPreference::Wgl(_raw_window_handle);
+
+ #[cfg(all(egl_backend, glx_backend))]
+ let _preference = match _api_preference {
+ ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ )),
+ ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new(
+ crate::windowing::window::platform::x11::register_xlib_error_hook,
+ )),
+ };
+
+ #[cfg(all(wgl_backend, egl_backend))]
+ let _preference = match _api_preference {
+ ApiPreference::PreferEgl => DisplayApiPreference::EglThenWgl(_raw_window_handle),
+ ApiPreference::FallbackEgl => {
+ DisplayApiPreference::WglThenEgl(_raw_window_handle)
+ }
+ };
+
+ let handle = display_handle.as_raw();
+ unsafe { Ok(Display::new(handle, _preference)?) }
+}
+
+/// Finalize [`Window`] creation by applying the options from the [`Config`], be
+/// aware that it could remove incompatible options from the window builder like
+/// `transparency`, when the provided config doesn't support it.
+///
+/// [`Window`]: winit::window::Window
+/// [`Config`]: glutin::config::Config
+#[cfg(not(wgl_backend))]
+fn finalize_window_creation_attrs(
+ mut attributes: WindowCreationAttributes,
+ gl_config: &Config,
+) -> WindowCreationAttributes
+{
+ // Disable transparency if the end config doesn't support it.
+ if gl_config.supports_transparency() == Some(false) {
+ attributes = attributes.with_transparent(false);
+ }
+
+ #[cfg(x11_platform)]
+ let attributes = if let Some(x11_visual) = gl_config.x11_visual() {
+ attributes.with_x11_visual(x11_visual.visual_id() as _)
+ } else {
+ attributes
+ };
+
+ attributes
+}
+
+/// Simplified version of the [`DisplayApiPreference`] which is used to simplify
+/// cross platform window creation.
+///
+/// To learn about platform differences the [`DisplayApiPreference`] variants.
+///
+/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference
+#[allow(dead_code)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum ApiPreference
+{
+ /// Prefer `EGL` over system provider like `GLX` and `WGL`.
+ PreferEgl,
+
+ /// Fallback to `EGL` when failed to create the system profile.
+ ///
+ /// This behavior is used by default. However consider using
+ /// [`Self::PreferEgl`] if you don't care about missing EGL features.
+ #[default]
+ FallbackEgl,
+}
diff --git a/engine/src/renderer/opengl/graphics_mesh.rs b/engine/src/renderer/opengl/graphics_mesh.rs
new file mode 100644
index 0000000..62dadb8
--- /dev/null
+++ b/engine/src/renderer/opengl/graphics_mesh.rs
@@ -0,0 +1,136 @@
+use opengl_bindings::CurrentContextWithFns as GlCurrentContextWithFns;
+use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage};
+use opengl_bindings::vertex_array::{
+ AttributeFormat as GlVertexArrayAttributeFormat,
+ DataType as GlVertexArrayDataType,
+ VertexArray as GlVertexArray,
+};
+
+use crate::mesh::Mesh;
+use crate::renderer::opengl::vertex::{
+ AttributeComponentType as VertexAttributeComponentType,
+ Vertex as RendererVertex,
+};
+
+#[derive(Debug)]
+pub struct GraphicsMesh
+{
+ /// Vertex and index buffer has to live as long as the vertex array
+ vertex_buffer: GlBuffer<RendererVertex>,
+ pub index_buffer: Option<GlBuffer<u32>>,
+ pub element_cnt: u32,
+ pub vertex_arr: GlVertexArray,
+}
+
+impl GraphicsMesh
+{
+ #[tracing::instrument(skip_all)]
+ pub fn new(
+ current_context: &GlCurrentContextWithFns<'_>,
+ mesh: &Mesh,
+ ) -> Result<Self, Error>
+ {
+ tracing::info!(
+ "Creating vertex array, vertex buffer{}",
+ if mesh.indices().is_some() {
+ " and index buffer"
+ } else {
+ ""
+ }
+ );
+
+ let vertex_arr = GlVertexArray::new(current_context);
+ let vertex_buffer = GlBuffer::new(current_context);
+
+ vertex_buffer
+ .store_mapped(
+ current_context,
+ mesh.vertices(),
+ GlBufferUsage::Static,
+ |vertex| RendererVertex {
+ pos: vertex.pos.into(),
+ texture_coords: vertex.texture_coords.into(),
+ normal: vertex.normal.into(),
+ },
+ )
+ .map_err(Error::StoreVerticesFailed)?;
+
+ vertex_arr.bind_vertex_buffer(current_context, 0, &vertex_buffer, 0);
+
+ let mut offset = 0u32;
+
+ for attrib in RendererVertex::attrs() {
+ vertex_arr.enable_attrib(current_context, attrib.index);
+
+ vertex_arr.set_attrib_format(
+ current_context,
+ attrib.index,
+ GlVertexArrayAttributeFormat {
+ data_type: match attrib.component_type {
+ VertexAttributeComponentType::Float => {
+ GlVertexArrayDataType::Float
+ }
+ },
+ count: attrib.component_cnt as u8,
+ normalized: false,
+ offset,
+ },
+ );
+
+ vertex_arr.set_attrib_vertex_buf_binding(current_context, attrib.index, 0);
+
+ offset += attrib.component_size * attrib.component_cnt as u32;
+ }
+
+ if let Some(indices) = mesh.indices() {
+ let index_buffer = GlBuffer::new(current_context);
+
+ index_buffer
+ .store(current_context, indices, GlBufferUsage::Static)
+ .map_err(Error::StoreIndicesFailed)?;
+
+ vertex_arr.bind_element_buffer(current_context, &index_buffer);
+
+ return Ok(Self {
+ vertex_buffer: vertex_buffer,
+ index_buffer: Some(index_buffer),
+ element_cnt: indices
+ .len()
+ .try_into()
+ .expect("Mesh index count does not fit into a 32-bit unsigned int"),
+ vertex_arr,
+ });
+ }
+
+ Ok(Self {
+ vertex_buffer: vertex_buffer,
+ index_buffer: None,
+ element_cnt: mesh
+ .vertices()
+ .len()
+ .try_into()
+ .expect("Mesh vertex count does not fit into a 32-bit unsigned int"),
+ vertex_arr,
+ })
+ }
+
+ pub fn destroy(&mut self, curr_gl_ctx: &GlCurrentContextWithFns<'_>)
+ {
+ self.vertex_arr.delete(curr_gl_ctx);
+ self.vertex_buffer.delete(curr_gl_ctx);
+
+ if let Some(index_buffer) = &self.index_buffer {
+ index_buffer.delete(curr_gl_ctx);
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to store vertices in vertex buffer")]
+ StoreVerticesFailed(#[source] opengl_bindings::buffer::Error),
+
+ #[error("Failed to store indices in index buffer")]
+ StoreIndicesFailed(#[source] opengl_bindings::buffer::Error),
+}
diff --git a/engine/src/renderer/opengl/vertex.rs b/engine/src/renderer/opengl/vertex.rs
new file mode 100644
index 0000000..5a1593e
--- /dev/null
+++ b/engine/src/renderer/opengl/vertex.rs
@@ -0,0 +1,65 @@
+use safer_ffi::derive_ReprC;
+
+#[derive(Debug, Clone)]
+#[derive_ReprC]
+#[repr(C)]
+pub struct Vertex
+{
+ pub pos: opengl_bindings::data_types::Vec3<f32>,
+ pub texture_coords: opengl_bindings::data_types::Vec2<f32>,
+ pub normal: opengl_bindings::data_types::Vec3<f32>,
+}
+
+impl Vertex
+{
+ pub fn attrs() -> &'static [Attribute]
+ {
+ #[allow(clippy::cast_possible_truncation)]
+ &[
+ Attribute {
+ index: 0,
+ component_type: AttributeComponentType::Float,
+ component_cnt: AttributeComponentCnt::Three,
+ component_size: size_of::<f32>() as u32,
+ },
+ Attribute {
+ index: 1,
+ component_type: AttributeComponentType::Float,
+ component_cnt: AttributeComponentCnt::Two,
+ component_size: size_of::<f32>() as u32,
+ },
+ Attribute {
+ index: 2,
+ component_type: AttributeComponentType::Float,
+ component_cnt: AttributeComponentCnt::Three,
+ component_size: size_of::<f32>() as u32,
+ },
+ ]
+ }
+}
+
+#[derive(Debug)]
+pub struct Attribute
+{
+ pub index: u32,
+ pub component_type: AttributeComponentType,
+ pub component_cnt: AttributeComponentCnt,
+ pub component_size: u32,
+}
+
+#[derive(Debug)]
+pub enum AttributeComponentType
+{
+ Float,
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
+#[allow(dead_code)]
+pub enum AttributeComponentCnt
+{
+ One = 1,
+ Two = 2,
+ Three = 3,
+ Four = 4,
+}