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
use blinds::*;
use golem::{
Attribute, AttributeType, Context,
Dimension::{D2, D4},
ElementBuffer, GeometryMode, GolemError, ShaderDescription, ShaderProgram, VertexBuffer,
};
// The application loop, powered by the blinds crate
async fn app(
window: Window,
ctx: golem::glow::Context,
mut events: EventStream,
) -> Result<(), GolemError> {
// Create a context from 'glow', GL On Whatever
let ctx = &Context::from_glow(ctx)?;
#[rustfmt::skip]
// This is the data that represents the triangle
// It's arranged how it will be passed to the GPU: each position represented as two f32 values,
// followed by each color represented as 4 f32 values. The positions are on a scale from -1.0
// to 1.0, which represents the viewport in OpenGL. The colors are represented as R, G, B, A,
// on a scale from 0.0 to 1.0
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
];
// This is the data that indicates how to draw the vertices
// For a simple example of one triangle, we don't gain much from this. Any order of these three
// points will give us the same triangle. However, if we add more points (to draw a square, for
// example), then we can write each point once while using it in multiple triangles.
let indices = [0, 1, 2];
// Here we create the ShaderProgram, which is some code that runs on the GPU. It determines how
// to turn our vertex data into an actual vertex that GL understands, and how to color each
// 'fragment' (essentially a pixel). These are each their own little program, where the
// information from the vertex shader is fed into the fragment shader.
// For the purposes of making sure the shaders match, and for ensuring compatibility on desktop
// and web, the inputs are represented as data structures and then converted to shader
// declarations at runtime.
// The input to the shader program is fed to the vertex_input, so your vertex data's format
// needs to match what you define in vertex_input
let mut shader = ShaderProgram::new(
ctx,
ShaderDescription {
// Take in to the shader a position (as a vector with 2 components) and a color (as a
// vector with 4 components). This is the same format as 'vertices' above
vertex_input: &[
Attribute::new("vert_position", AttributeType::Vector(D2)),
Attribute::new("vert_color", AttributeType::Vector(D4)),
],
// Pass to the fragment shader the color
// OpenGL will actually smoothly interpolate between different vertex values for us, so
// a red vertex and a blue vertex will have a gradient between them
fragment_input: &[Attribute::new("frag_color", AttributeType::Vector(D4))],
// Uniforms represent a value that's the same for the entire shader; we don't need any
// here. If you're rendering images or applying transformations to your entire draw
// call, use uniforms!
uniforms: &[],
// A program written in GLSL that uses the inputs and outputs defined above
// There's also a hard-coded output called gl_Position
vertex_shader: r#" void main() {
gl_Position = vec4(vert_position, 0, 1);
frag_color = vert_color;
}"#,
// The fragment shader has a hard-coded output: gl_FragColor
fragment_shader: r#" void main() {
gl_FragColor = frag_color;
}"#,
},
)?;
// Create buffer objects, which we use to transfer data from the CPU to the GPU
let mut vb = VertexBuffer::new(ctx)?;
let mut eb = ElementBuffer::new(ctx)?;
// Set the data of the buffer to be our vertices and indices from earlier
vb.set_data(&vertices);
eb.set_data(&indices);
// Prepare the shader for operations: shaders will raise errors if you forget to bind them
shader.bind();
// Clear the screen
ctx.clear();
unsafe {
// Using our buffers, draw our triangle
// We could also interpert our indices as Lines or a variety of other shape options:
// nothing binds us to necessarily using Triangles, even though they're the most common
// shape in graphics
shader.draw(&vb, &eb, 0..indices.len(), GeometryMode::Triangles)?;
}
// Show our data to the window
window.present();
// Keep the window open and responsive until the user exits
loop {
events.next_event().await;
}
}
// Run our application!
fn main() {
run_gl(Settings::default(), |window, gfx, events| async move {
app(window, gfx, events).await.unwrap()
});
}