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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use crate::blend::{BlendEquation, BlendFunction, BlendMode};
use crate::depth::DepthTestMode;
use crate::{GlFramebuffer, GlProgram, GlVertexArray, GolemError};
use core::cell::RefCell;
#[cfg(not(target_arch = "wasm32"))]
use core::ffi::{c_void, CStr};
use glow::HasContext;
use std::rc::Rc;
#[cfg(target_arch = "wasm32")]
use web_sys::WebGl2RenderingContext;
/// The context required to interact with the GPU
pub struct Context(pub(crate) Rc<ContextContents>);
pub(crate) struct ContextContents {
pub(crate) gl: glow::Context,
pub(crate) current_program: RefCell<Option<GlProgram>>,
pub(crate) current_surface: RefCell<Option<GlFramebuffer>>,
vao: GlVertexArray,
max_vertex_attrib_index: RefCell<u32>,
}
impl Drop for ContextContents {
fn drop(&mut self) {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDeleteVertexArrays.xhtml
// glow handles passing in the pointer to our value, and GL will silently ignore invalid
// values
unsafe {
self.gl.delete_vertex_array(self.vao);
}
}
}
impl Context {
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn from_loader_function_cstr<F>(loader_function: F) -> Result<Context, GolemError>
where
F: FnMut(&CStr) -> *const c_void,
{
let context = unsafe { glow::Context::from_loader_function_cstr(loader_function) };
Self::from_glow(context)
}
#[cfg(not(target_arch = "wasm32"))]
pub unsafe fn from_loader_function<F>(loader_function: F) -> Result<Context, GolemError>
where
F: FnMut(&str) -> *const c_void,
{
let context = unsafe { glow::Context::from_loader_function(loader_function) };
Self::from_glow(context)
}
#[cfg(target_arch = "wasm32")]
pub fn from_webgl2_context(ctx: WebGl2RenderingContext) -> Result<Context, GolemError> {
let context = glow::Context::from_webgl2_context(ctx);
Self::from_glow(context)
}
fn from_glow(gl: glow::Context) -> Result<Context, GolemError> {
let vao = unsafe {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGenVertexArrays.xhtml
// glow handles passing in '1' and returning the value to us
let vao = gl.create_vertex_array()?;
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBindVertexArray.xhtml
// In this case, we know 'vao' must be a valid vao because we just constructed it
gl.bind_vertex_array(Some(vao));
vao
};
let contents = Context(Rc::new(ContextContents {
gl,
current_program: RefCell::new(None),
current_surface: RefCell::new(None),
vao,
max_vertex_attrib_index: RefCell::new(0),
}));
contents.set_clear_color(0.0, 0.0, 0.0, 1.0);
Ok(contents)
}
/// Set the section of the framebuffer that will be rendered to
///
/// By default, this is the entire internal area of the window. When switching to a
/// [`Surface`], it's generally important to set the viewport to its area.
///
/// [`Surface`]: crate::Surface
pub fn set_viewport(&self, x: u32, y: u32, width: u32, height: u32) {
unsafe {
self.0
.gl
.viewport(x as i32, y as i32, width as i32, height as i32);
}
}
/// Set the section of the framebuffer that will be affected by rendering operations.
/// Rendering operations won't have any effect on pixels outside this section. Unlike the
/// `set_viewport` method, this will *not* affect the scale of the rendered content.
///
/// By default, the scissor is disabled, which means that rendering operations can draw on
/// the entire viewport. You can use `disable_scissor` to disable it again.
pub fn set_scissor(&self, x: u32, y: u32, width: u32, height: u32) {
unsafe {
self.0.gl.enable(glow::SCISSOR_TEST);
self.0
.gl
.scissor(x as i32, y as i32, width as i32, height as i32);
}
}
/// Disables the scissor (see the `set_scissor` method).
///
/// This method has no effect if the scissor is already disabled.
pub fn disable_scissor(&self) {
unsafe {
self.0.gl.disable(glow::SCISSOR_TEST);
}
}
/// Set the color the render target will be cleared to by [`clear`]
///
/// [`clear`]: Context::clear
pub fn set_clear_color(&self, r: f32, g: f32, b: f32, a: f32) {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glClearColor.xhtml
// Set the clear color to (r, g, b, a)
unsafe {
self.0.gl.clear_color(r, g, b, a);
}
}
/// Clear the current render target to the render color (see [`set_clear_color`])
///
/// [`set_clear_color`]: Context::set_clear_color
pub fn clear(&self) {
let gl = &self.0.gl;
unsafe {
gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT);
}
}
/// Set the blend mode, with `None` disabling blending
///
/// By default, this is `None`
///
/// See the documentation for [`BlendMode`] for the various blending options
pub fn set_blend_mode(&self, blend_state: Option<BlendMode>) {
let gl = &self.0.gl;
match blend_state {
Some(BlendMode {
equation,
function,
global_color: [r, g, b, a],
}) => unsafe {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glEnable.xhtml
// gl::BLEND is on the whitelist
gl.enable(glow::BLEND);
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlendEquation.xhtml
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlendEquationSeparate.xhtml
// The to_gl() function only produces valid values
match equation {
BlendEquation::Same(eq) => gl.blend_equation(eq.to_gl()),
BlendEquation::Separate { color, alpha } => {
gl.blend_equation_separate(color.to_gl(), alpha.to_gl());
}
}
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlendFunc.xhtml
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlendFuncSeparate.xhtml
// The to_gl() function only produces valid values
match function {
BlendFunction::Same {
source,
destination,
} => {
gl.blend_func(source.to_gl(), destination.to_gl());
}
BlendFunction::Separate {
source_color,
source_alpha,
destination_alpha,
destination_color,
} => {
gl.blend_func_separate(
source_color.to_gl(),
source_alpha.to_gl(),
destination_alpha.to_gl(),
destination_color.to_gl(),
);
}
}
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glBlendColor.xhtml
gl.blend_color(r, g, b, a);
},
None => unsafe {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glEnable.xhtml
// gl::BLEND is on the whitelist
gl.disable(glow::BLEND);
},
}
}
/// Set the depth test mode, with `None` disabling depth testing
///
/// By default, this is `None`
///
/// See the documentation for [`DepthTestMode`](depth/struct.DepthTestMode.html)
/// for the various depth testing options
pub fn set_depth_test_mode(&self, depth_test_state: Option<DepthTestMode>) {
let gl = &self.0.gl;
match depth_test_state {
Some(DepthTestMode {
function,
range_near,
range_far,
depth_mask,
}) => unsafe {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glEnable.xhtml
gl.enable(glow::DEPTH_TEST);
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDepthFunc.xhtml
// The to_gl() function only produces valid values
gl.depth_func(function.to_gl());
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDepthRange.xhtml
#[cfg(not(target_arch = "wasm32"))]
gl.depth_range_f64(range_near as f64, range_far as f64);
// https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glDepthRangef.xhtml
#[cfg(target_arch = "wasm32")]
gl.depth_range_f32(range_near, range_far);
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDepthMask.xhtml
gl.depth_mask(depth_mask);
},
None => unsafe {
// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glEnable.xhtml
gl.disable(glow::DEPTH_TEST);
},
}
}
/// Set the new max attribute, clear the old one
pub(crate) fn max_attrib(&self, index: u32) -> u32 {
let mut attrib_ptr = self.0.max_vertex_attrib_index.borrow_mut();
let attrib = *attrib_ptr;
*attrib_ptr = index;
attrib
}
}