1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
//! `golem` is an opinionated mostly-safe graphics API
//!
//! When possible, `golem` should make simple things safe (bind objects before acting on them, or
//! check if they're bound for objects that are expensive to bind.) However, when not possible or
//! convenient (bounds checking the indices in an element buffer, for example), `golem` provides
//! unsafe APIs with well-defined safety conditions.
//!
//! A minimal example to display a triangle:
//!
//! ```rust
//! # use golem::*;
//! # use golem::Dimension::*;
//! # fn func(ctx: &Context) -> Result<(), GolemError> {
//! let vertices = [
//!     // Position         Color
//!     -0.5, -0.5,         1.0, 0.0, 0.0, 1.0,
//!     0.5, -0.5,          0.0, 1.0, 0.0, 1.0,
//!     0.0, 0.5,           0.0, 0.0, 1.0, 1.0
//! ];
//! let indices = [0, 1, 2];
//!
//! let mut shader = ShaderProgram::new(
//!     ctx,
//!     ShaderDescription {
//!         vertex_input: &[
//!             Attribute::new("vert_position", AttributeType::Vector(D2)),
//!             Attribute::new("vert_color", AttributeType::Vector(D4)),
//!         ],
//!         fragment_input: &[Attribute::new("frag_color", AttributeType::Vector(D4))],
//!         uniforms: &[],
//!         vertex_shader: r#" void main() {
//!         gl_Position = vec4(vert_position, 0, 1);
//!         frag_color = vert_color;
//!     }"#,
//!         fragment_shader: r#" void main() {
//!         gl_FragColor = frag_color;
//!     }"#,
//!     },
//! )?;
//!
//! let mut vb = VertexBuffer::new(ctx)?;
//! let mut eb = ElementBuffer::new(ctx)?;
//! vb.set_data(&vertices);
//! eb.set_data(&indices);
//! shader.bind();
//!
//! ctx.clear();
//! unsafe {
//!     shader.draw(&vb, &eb, 0..indices.len(), GeometryMode::Triangles)?;
//! }
//! # Ok(()) }
//! ```
//!
//! The core type of `golem` is the [`Context`], which is constructed from the [`glow Context`].
//! From the [`Context`], [`ShaderProgram`]s are created, which take in data from [`Buffer`]s. Once
//! the data is uploaded to the GPU via [`Buffer::set_data`], it can be drawn via [`ShaderProgram::draw`].
//!
//! ## Initializing
//!
//! The user is responsible for windowing and providing a valid [`glow Context`] to create a
//! [`Context`]. You can try out the [`blinds`](https://crates.io/crates/blinds) crate, which works
//! well with `golem`, but using `winit` directly or other windowing solutions like `sdl2` are also
//! options.
//!
//! ## OpenGL Versions
//! It currently is implemented via glow, and it targets OpenGL 3.2 on desktop and WebGL 1 (so it
//! should run on a wide range of hardware.) GL 3.2 is selected for maximum desktop availability,
//! and WebGL 1 is available on 97% of clients to WebGL's 75% (taken from caniuse.com at time of
//! writing.)
//!
//! [`Context`]: crate::Context
//! [`glow Context`]: glow::Context

#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

use alloc::fmt::{Display, Formatter, Result as FmtResult};
use alloc::string::String;

// TODO: add out-of-memory to GolemError?
// TODO: unsafe audit: check for possible GL error conditions, and track them

use glow::HasContext;

type GlTexture = <glow::Context as HasContext>::Texture;
type GlProgram = <glow::Context as HasContext>::Program;
type GlShader = <glow::Context as HasContext>::Shader;
type GlFramebuffer = <glow::Context as HasContext>::Framebuffer;
type GlBuffer = <glow::Context as HasContext>::Buffer;
type GlVertexArray = <glow::Context as HasContext>::VertexArray;

mod attribute;
mod buffer;
mod context;
mod shader;
mod surface;
mod texture;
mod uniform;

pub mod blend;
pub mod depth;

pub use self::attribute::{Attribute, AttributeType};
pub use self::buffer::{Buffer, ElementBuffer, VertexBuffer};
pub use self::context::Context;
pub use self::shader::{ShaderDescription, ShaderProgram};
pub use self::surface::Surface;
pub use self::texture::{Texture, TextureFilter, TextureWrap};
pub use self::uniform::{Uniform, UniformType, UniformValue};

pub use glow;

pub(crate) enum Position {
    Input,
    Output,
}

/// Used to determine whether shader uniforms are ints or floats
pub enum NumberType {
    Int,
    Float,
}

/// How a pixel's color is laid out in memory
pub enum ColorFormat {
    /// One red pixel byte, followed by one blue, and one green
    RGB,
    /// One red, blue, green, then alpha (transparency)
    RGBA,
}

impl ColorFormat {
    pub fn bytes_per_pixel(&self) -> u32 {
        match self {
            ColorFormat::RGBA => 4,
            ColorFormat::RGB => 3,
        }
    }

    fn gl_format(&self) -> u32 {
        match self {
            ColorFormat::RGB => glow::RGB,
            ColorFormat::RGBA => glow::RGBA,
        }
    }
}

#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
/// The dimensionality of a vector or matrix shader input
///
/// D2 indicates a Vector2 or Matrix2x2, etc.
pub enum Dimension {
    D2 = 2,
    D3 = 3,
    D4 = 4,
}

#[derive(Copy, Clone, Hash, PartialEq, Eq)]
/// The GeometryMode determines how the data is drawn during [`ShaderProgram::draw`]
pub enum GeometryMode {
    /// Each element forms a single point
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1), (2), (3), (4), (5), (6)]`
    Points,
    /// Each pair of elements forms a thin line
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2), (3, 4), (5, 6)]`
    Lines,
    /// Each pair of elements forms a chain of lines
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]`
    LineStrip,
    /// Each pair of elements forms a chain of lines, connected to the original
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)]`
    LineLoop,
    /// Each trio of elements forms a distinct triangle
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2, 3), (4, 5, 6)]`
    Triangles,
    /// Each trio of elements forms a triangle, with the next vertex taking the previous two
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)]`
    TriangleStrip,
    /// The first elements forms the center of a fan, with each pair of vertices forming a triangle
    ///
    /// `[1, 2, 3, 4, 5, 6] -> [(1, 2, 3), (1, 3, 4), (1, 4, 5), (1, 5, 6)]`
    TriangleFan,
}

#[derive(Debug)]
/// The library's error conditions
pub enum GolemError {
    /// The OpenGL Shader compilation failed, with the given error message
    ///
    /// This may be during vertex-time, fragment-time, or link-time
    ShaderCompilationError(String),
    /// Some general error bubbling up from the GL context
    ContextError(String),
    /// An attempt was made to bind to an illegal uniform
    NoSuchUniform(String),
    /// An operation was performed on a shader that wasn't bound
    ///
    /// Shader operations include setting uniforms and drawing
    NotCurrentProgram,
    /// A texture filter requiring mipmaps was used when mipmaps were unavailable
    ///
    /// Mipmaps are only available for minification, and only for power-of-two sized textures (2x2, 4x4, etc.)
    MipMapsUnavailable,
    /// A wrap option was set for a Texture that isn't available
    ///
    /// Texture repeats are currently only supported for power-of-2 sized textures (2x2, 4x4, etc.)
    IllegalWrapOption,
}

impl From<String> for GolemError {
    fn from(other: String) -> Self {
        GolemError::ContextError(other)
    }
}

impl Display for GolemError {
    fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
        match self {
            GolemError::ShaderCompilationError(e) => write!(fmt, "Shader compilation: {}", e),
            GolemError::ContextError(e) => write!(fmt, "OpenGL: {}", e),
            GolemError::NoSuchUniform(e) => write!(fmt, "Illegal uniform: {}", e),
            GolemError::NotCurrentProgram => write!(fmt, "Shader program not bound"),
            GolemError::MipMapsUnavailable => write!(fmt, "Mipmaps are unavailable"),
            GolemError::IllegalWrapOption => write!(fmt, "An illegal texture wrap"),
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for GolemError {}