1 | use std::{mem, rc::Rc}; |
2 | |
3 | #[cfg (not(target_arch = "wasm32" ))] |
4 | use std::ffi::c_void; |
5 | |
6 | #[cfg (all(feature = "glutin" , not(target_arch = "wasm32" )))] |
7 | use glutin::display::GlDisplay; |
8 | |
9 | use fnv::FnvHashMap; |
10 | use imgref::ImgVec; |
11 | use rgb::RGBA8; |
12 | |
13 | use crate::{ |
14 | renderer::{GlyphTexture, ImageId, Vertex}, |
15 | BlendFactor, Color, CompositeOperationState, ErrorKind, FillRule, ImageFilter, ImageInfo, ImageSource, ImageStore, |
16 | Scissor, |
17 | }; |
18 | |
19 | use glow::HasContext; |
20 | |
21 | use super::{Command, CommandType, Params, RenderTarget, Renderer, ShaderType, SurfacelessRenderer}; |
22 | |
23 | mod program; |
24 | use program::MainProgram; |
25 | |
26 | mod gl_texture; |
27 | use gl_texture::GlTexture; |
28 | |
29 | mod framebuffer; |
30 | use framebuffer::Framebuffer; |
31 | |
32 | mod uniform_array; |
33 | use uniform_array::UniformArray; |
34 | |
35 | /// Represents an OpenGL renderer. |
36 | pub struct OpenGl { |
37 | debug: bool, |
38 | antialias: bool, |
39 | is_opengles_2_0: bool, |
40 | view: [f32; 2], |
41 | screen_view: [f32; 2], |
42 | // All types of the vertex/fragment shader, indexed by shader_type when has_glyph_texture is true |
43 | main_programs_with_glyph_texture: [Option<MainProgram>; 7], |
44 | // Same shader programs but with has_glyph_texture being false |
45 | main_programs_without_glyph_texture: [Option<MainProgram>; 7], |
46 | current_program: u8, |
47 | current_program_needs_glyph_texture: bool, |
48 | vert_arr: Option<<glow::Context as glow::HasContext>::VertexArray>, |
49 | vert_buff: Option<<glow::Context as glow::HasContext>::Buffer>, |
50 | framebuffers: FnvHashMap<ImageId, Result<Framebuffer, ErrorKind>>, |
51 | context: Rc<glow::Context>, |
52 | screen_target: Option<Framebuffer>, |
53 | current_render_target: RenderTarget, |
54 | } |
55 | |
56 | impl OpenGl { |
57 | /// Creates a new OpenGL renderer from a function loader. |
58 | /// |
59 | /// # Safety |
60 | /// This function is unsafe because it requires a function loader that can load OpenGL functions |
61 | /// and create a valid OpenGL context. |
62 | #[cfg (not(target_arch = "wasm32" ))] |
63 | pub unsafe fn new_from_function<F>(load_fn: F) -> Result<Self, ErrorKind> |
64 | where |
65 | F: FnMut(&str) -> *const c_void, |
66 | { |
67 | let context = glow::Context::from_loader_function(load_fn); |
68 | let version = context.get_parameter_string(glow::VERSION); |
69 | let is_opengles_2_0 = version.starts_with("OpenGL ES 2." ); |
70 | Self::new_from_context(context, is_opengles_2_0) |
71 | } |
72 | |
73 | /// Creates a new OpenGL renderer from a function loader that takes C-style strings. |
74 | /// |
75 | /// # Safety |
76 | /// This function is unsafe because it requires a function loader that can load OpenGL functions |
77 | /// and create a valid OpenGL context. |
78 | #[cfg (not(target_arch = "wasm32" ))] |
79 | pub unsafe fn new_from_function_cstr<F>(load_fn: F) -> Result<Self, ErrorKind> |
80 | where |
81 | F: FnMut(&std::ffi::CStr) -> *const c_void, |
82 | { |
83 | let context = glow::Context::from_loader_function_cstr(load_fn); |
84 | let version = context.get_parameter_string(glow::VERSION); |
85 | let is_opengles_2_0 = version.starts_with("OpenGL ES 2." ); |
86 | Self::new_from_context(context, is_opengles_2_0) |
87 | } |
88 | |
89 | /// Creates a new OpenGL renderer from a Glutin display. |
90 | #[cfg (all(feature = "glutin" , not(target_arch = "wasm32" )))] |
91 | pub fn new_from_glutin_display(display: &impl GlDisplay) -> Result<Self, ErrorKind> { |
92 | unsafe { OpenGl::new_from_function_cstr(|s| display.get_proc_address(s).cast()) } |
93 | } |
94 | |
95 | /// Creates a new OpenGL renderer from an HTML canvas element in a WASM32 target. |
96 | #[cfg (target_arch = "wasm32" )] |
97 | pub fn new_from_html_canvas(canvas: &web_sys::HtmlCanvasElement) -> Result<Self, ErrorKind> { |
98 | let attrs = web_sys::WebGlContextAttributes::new(); |
99 | attrs.set_stencil(true); |
100 | attrs.set_antialias(false); |
101 | |
102 | use wasm_bindgen::JsCast; |
103 | let webgl2_context = match canvas.get_context_with_context_options("webgl2" , &attrs) { |
104 | Ok(Some(context)) => context.dyn_into::<web_sys::WebGl2RenderingContext>().unwrap(), |
105 | _ => { |
106 | return Err(ErrorKind::GeneralError( |
107 | "Canvas::getContext failed to retrieve WebGL 2 context" .to_owned(), |
108 | )) |
109 | } |
110 | }; |
111 | |
112 | let context = glow::Context::from_webgl2_context(webgl2_context); |
113 | Self::new_from_context(context, true) |
114 | } |
115 | |
116 | fn new_from_context(context: glow::Context, is_opengles_2_0: bool) -> Result<Self, ErrorKind> { |
117 | let debug = cfg!(debug_assertions); |
118 | let antialias = true; |
119 | |
120 | let context = Rc::new(context); |
121 | |
122 | let generate_shader_program_variants = |with_glyph_texture| -> Result<_, ErrorKind> { |
123 | Ok([ |
124 | Some(MainProgram::new( |
125 | &context, |
126 | antialias, |
127 | ShaderType::FillGradient, |
128 | with_glyph_texture, |
129 | )?), |
130 | Some(MainProgram::new( |
131 | &context, |
132 | antialias, |
133 | ShaderType::FillImage, |
134 | with_glyph_texture, |
135 | )?), |
136 | if with_glyph_texture { |
137 | // No stencil fill with glyph texture |
138 | None |
139 | } else { |
140 | Some(MainProgram::new(&context, antialias, ShaderType::Stencil, false)?) |
141 | }, |
142 | Some(MainProgram::new( |
143 | &context, |
144 | antialias, |
145 | ShaderType::FillImageGradient, |
146 | with_glyph_texture, |
147 | )?), |
148 | if with_glyph_texture { |
149 | // Image filter is unrelated to glyph rendering |
150 | None |
151 | } else { |
152 | Some(MainProgram::new(&context, antialias, ShaderType::FilterImage, false)?) |
153 | }, |
154 | Some(MainProgram::new( |
155 | &context, |
156 | antialias, |
157 | ShaderType::FillColor, |
158 | with_glyph_texture, |
159 | )?), |
160 | if with_glyph_texture { |
161 | // Texture blitting is unrelated to glyph rendering |
162 | None |
163 | } else { |
164 | Some(MainProgram::new( |
165 | &context, |
166 | antialias, |
167 | ShaderType::TextureCopyUnclipped, |
168 | false, |
169 | )?) |
170 | }, |
171 | ]) |
172 | }; |
173 | |
174 | let main_programs_with_glyph_texture = generate_shader_program_variants(true)?; |
175 | let main_programs_without_glyph_texture = generate_shader_program_variants(false)?; |
176 | |
177 | let mut opengl = Self { |
178 | debug, |
179 | antialias, |
180 | is_opengles_2_0: false, |
181 | view: [0.0, 0.0], |
182 | screen_view: [0.0, 0.0], |
183 | main_programs_with_glyph_texture, |
184 | main_programs_without_glyph_texture, |
185 | current_program: 0, |
186 | current_program_needs_glyph_texture: true, |
187 | vert_arr: None, |
188 | vert_buff: None, |
189 | framebuffers: Default::default(), |
190 | context, |
191 | screen_target: None, |
192 | current_render_target: RenderTarget::Screen, |
193 | }; |
194 | |
195 | unsafe { |
196 | opengl.is_opengles_2_0 = is_opengles_2_0; |
197 | |
198 | opengl.vert_arr = opengl.context.create_vertex_array().ok(); |
199 | opengl.vert_buff = opengl.context.create_buffer().ok(); |
200 | } |
201 | |
202 | Ok(opengl) |
203 | } |
204 | |
205 | /// Checks if the renderer is using OpenGL ES. |
206 | pub fn is_opengles(&self) -> bool { |
207 | self.is_opengles_2_0 |
208 | } |
209 | |
210 | fn check_error(&self, label: &str) { |
211 | if !self.debug { |
212 | return; |
213 | } |
214 | |
215 | let err = unsafe { self.context.get_error() }; |
216 | |
217 | if err == glow::NO_ERROR { |
218 | return; |
219 | } |
220 | |
221 | let message = match err { |
222 | glow::INVALID_ENUM => "Invalid enum" , |
223 | glow::INVALID_VALUE => "Invalid value" , |
224 | glow::INVALID_OPERATION => "Invalid operation" , |
225 | glow::OUT_OF_MEMORY => "Out of memory" , |
226 | glow::INVALID_FRAMEBUFFER_OPERATION => "Invalid framebuffer operation" , |
227 | _ => "Unknown error" , |
228 | }; |
229 | |
230 | log::error!("( {err}) Error on {label} - {message}" ); |
231 | } |
232 | |
233 | fn gl_factor(factor: BlendFactor) -> u32 { |
234 | match factor { |
235 | BlendFactor::Zero => glow::ZERO, |
236 | BlendFactor::One => glow::ONE, |
237 | BlendFactor::SrcColor => glow::SRC_COLOR, |
238 | BlendFactor::OneMinusSrcColor => glow::ONE_MINUS_SRC_COLOR, |
239 | BlendFactor::DstColor => glow::DST_COLOR, |
240 | BlendFactor::OneMinusDstColor => glow::ONE_MINUS_DST_COLOR, |
241 | BlendFactor::SrcAlpha => glow::SRC_ALPHA, |
242 | BlendFactor::OneMinusSrcAlpha => glow::ONE_MINUS_SRC_ALPHA, |
243 | BlendFactor::DstAlpha => glow::DST_ALPHA, |
244 | BlendFactor::OneMinusDstAlpha => glow::ONE_MINUS_DST_ALPHA, |
245 | BlendFactor::SrcAlphaSaturate => glow::SRC_ALPHA_SATURATE, |
246 | } |
247 | } |
248 | |
249 | fn set_composite_operation(&self, blend_state: CompositeOperationState) { |
250 | unsafe { |
251 | self.context.blend_func_separate( |
252 | Self::gl_factor(blend_state.src_rgb), |
253 | Self::gl_factor(blend_state.dst_rgb), |
254 | Self::gl_factor(blend_state.src_alpha), |
255 | Self::gl_factor(blend_state.dst_alpha), |
256 | ); |
257 | } |
258 | } |
259 | |
260 | fn convex_fill(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, gpu_paint: &Params) { |
261 | self.set_uniforms(images, gpu_paint, cmd.image, cmd.glyph_texture); |
262 | |
263 | for drawable in &cmd.drawables { |
264 | if let Some((start, count)) = drawable.fill_verts { |
265 | unsafe { |
266 | self.context.draw_arrays(glow::TRIANGLES, start as i32, count as i32); |
267 | } |
268 | } |
269 | |
270 | if let Some((start, count)) = drawable.stroke_verts { |
271 | unsafe { |
272 | self.context |
273 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
274 | } |
275 | } |
276 | } |
277 | |
278 | self.check_error("convex_fill" ); |
279 | } |
280 | |
281 | fn concave_fill( |
282 | &mut self, |
283 | images: &ImageStore<GlTexture>, |
284 | cmd: &Command, |
285 | stencil_paint: &Params, |
286 | fill_paint: &Params, |
287 | ) { |
288 | unsafe { |
289 | self.context.enable(glow::STENCIL_TEST); |
290 | self.context.stencil_mask(0xff); |
291 | self.context.stencil_func(glow::ALWAYS, 0, 0xff); |
292 | self.context.color_mask(false, false, false, false); |
293 | //glow::DepthMask(glow::FALSE); |
294 | } |
295 | |
296 | self.set_uniforms(images, stencil_paint, None, GlyphTexture::None); |
297 | |
298 | unsafe { |
299 | self.context |
300 | .stencil_op_separate(glow::FRONT, glow::KEEP, glow::KEEP, glow::INCR_WRAP); |
301 | self.context |
302 | .stencil_op_separate(glow::BACK, glow::KEEP, glow::KEEP, glow::DECR_WRAP); |
303 | self.context.disable(glow::CULL_FACE); |
304 | } |
305 | |
306 | for drawable in &cmd.drawables { |
307 | if let Some((start, count)) = drawable.fill_verts { |
308 | unsafe { |
309 | self.context.draw_arrays(glow::TRIANGLES, start as i32, count as i32); |
310 | } |
311 | } |
312 | } |
313 | |
314 | unsafe { |
315 | self.context.enable(glow::CULL_FACE); |
316 | // Draw anti-aliased pixels |
317 | self.context.color_mask(true, true, true, true); |
318 | //glow::DepthMask(glow::TRUE); |
319 | } |
320 | |
321 | self.set_uniforms(images, fill_paint, cmd.image, cmd.glyph_texture); |
322 | |
323 | if self.antialias { |
324 | unsafe { |
325 | match cmd.fill_rule { |
326 | FillRule::NonZero => self.context.stencil_func(glow::EQUAL, 0x0, 0xff), |
327 | FillRule::EvenOdd => self.context.stencil_func(glow::EQUAL, 0x0, 0x1), |
328 | } |
329 | |
330 | self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); |
331 | } |
332 | |
333 | // draw fringes |
334 | for drawable in &cmd.drawables { |
335 | if let Some((start, count)) = drawable.stroke_verts { |
336 | unsafe { |
337 | self.context |
338 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
339 | } |
340 | } |
341 | } |
342 | } |
343 | |
344 | unsafe { |
345 | match cmd.fill_rule { |
346 | FillRule::NonZero => self.context.stencil_func(glow::NOTEQUAL, 0x0, 0xff), |
347 | FillRule::EvenOdd => self.context.stencil_func(glow::NOTEQUAL, 0x0, 0x1), |
348 | } |
349 | |
350 | self.context.stencil_op(glow::ZERO, glow::ZERO, glow::ZERO); |
351 | |
352 | if let Some((start, count)) = cmd.triangles_verts { |
353 | self.context |
354 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
355 | } |
356 | |
357 | self.context.disable(glow::STENCIL_TEST); |
358 | } |
359 | |
360 | self.check_error("concave_fill" ); |
361 | } |
362 | |
363 | fn stroke(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint: &Params) { |
364 | self.set_uniforms(images, paint, cmd.image, cmd.glyph_texture); |
365 | |
366 | for drawable in &cmd.drawables { |
367 | if let Some((start, count)) = drawable.stroke_verts { |
368 | unsafe { |
369 | self.context |
370 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
371 | } |
372 | } |
373 | } |
374 | |
375 | self.check_error("stroke" ); |
376 | } |
377 | |
378 | fn stencil_stroke(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint1: &Params, paint2: &Params) { |
379 | unsafe { |
380 | self.context.enable(glow::STENCIL_TEST); |
381 | self.context.stencil_mask(0xff); |
382 | |
383 | // Fill the stroke base without overlap |
384 | self.context.stencil_func(glow::EQUAL, 0x0, 0xff); |
385 | self.context.stencil_op(glow::KEEP, glow::KEEP, glow::INCR); |
386 | } |
387 | |
388 | self.set_uniforms(images, paint2, cmd.image, cmd.glyph_texture); |
389 | |
390 | for drawable in &cmd.drawables { |
391 | if let Some((start, count)) = drawable.stroke_verts { |
392 | unsafe { |
393 | self.context |
394 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
395 | } |
396 | } |
397 | } |
398 | |
399 | // Draw anti-aliased pixels. |
400 | self.set_uniforms(images, paint1, cmd.image, cmd.glyph_texture); |
401 | |
402 | unsafe { |
403 | self.context.stencil_func(glow::EQUAL, 0x0, 0xff); |
404 | self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); |
405 | } |
406 | |
407 | for drawable in &cmd.drawables { |
408 | if let Some((start, count)) = drawable.stroke_verts { |
409 | unsafe { |
410 | self.context |
411 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
412 | } |
413 | } |
414 | } |
415 | |
416 | unsafe { |
417 | // Clear stencil buffer. |
418 | self.context.color_mask(false, false, false, false); |
419 | self.context.stencil_func(glow::ALWAYS, 0x0, 0xff); |
420 | self.context.stencil_op(glow::ZERO, glow::ZERO, glow::ZERO); |
421 | } |
422 | |
423 | for drawable in &cmd.drawables { |
424 | if let Some((start, count)) = drawable.stroke_verts { |
425 | unsafe { |
426 | self.context |
427 | .draw_arrays(glow::TRIANGLE_STRIP, start as i32, count as i32); |
428 | } |
429 | } |
430 | } |
431 | |
432 | unsafe { |
433 | self.context.color_mask(true, true, true, true); |
434 | self.context.disable(glow::STENCIL_TEST); |
435 | } |
436 | |
437 | self.check_error("stencil_stroke" ); |
438 | } |
439 | |
440 | fn triangles(&mut self, images: &ImageStore<GlTexture>, cmd: &Command, paint: &Params) { |
441 | self.set_uniforms(images, paint, cmd.image, cmd.glyph_texture); |
442 | |
443 | if let Some((start, count)) = cmd.triangles_verts { |
444 | unsafe { |
445 | self.context.draw_arrays(glow::TRIANGLES, start as i32, count as i32); |
446 | } |
447 | } |
448 | |
449 | self.check_error("triangles" ); |
450 | } |
451 | |
452 | fn set_uniforms( |
453 | &mut self, |
454 | images: &ImageStore<GlTexture>, |
455 | paint: &Params, |
456 | image_tex: Option<ImageId>, |
457 | glyph_tex: GlyphTexture, |
458 | ) { |
459 | self.select_main_program(paint); |
460 | let arr = UniformArray::from(paint); |
461 | self.main_program().set_config(arr.as_slice()); |
462 | self.check_error("set_uniforms uniforms" ); |
463 | |
464 | let tex = image_tex.and_then(|id| images.get(id)).map(GlTexture::id); |
465 | |
466 | unsafe { |
467 | self.context.active_texture(glow::TEXTURE0); |
468 | self.context.bind_texture(glow::TEXTURE_2D, tex); |
469 | } |
470 | |
471 | let glyphtex = glyph_tex.image_id().and_then(|id| images.get(id).map(GlTexture::id)); |
472 | |
473 | unsafe { |
474 | self.context.active_texture(glow::TEXTURE0 + 1); |
475 | self.context.bind_texture(glow::TEXTURE_2D, glyphtex); |
476 | } |
477 | |
478 | self.check_error("set_uniforms texture" ); |
479 | } |
480 | |
481 | fn clear_rect(&self, x: u32, y: u32, width: u32, height: u32, color: Color) { |
482 | unsafe { |
483 | self.context.enable(glow::SCISSOR_TEST); |
484 | self.context.scissor( |
485 | x as i32, |
486 | self.view[1] as i32 - (height as i32 + y as i32), |
487 | width as i32, |
488 | height as i32, |
489 | ); |
490 | self.context.clear_color(color.r, color.g, color.b, color.a); |
491 | self.context.clear(glow::COLOR_BUFFER_BIT | glow::STENCIL_BUFFER_BIT); |
492 | self.context.disable(glow::SCISSOR_TEST); |
493 | } |
494 | } |
495 | |
496 | fn set_target(&mut self, images: &ImageStore<GlTexture>, target: RenderTarget) { |
497 | self.current_render_target = target; |
498 | match (target, &self.screen_target) { |
499 | (RenderTarget::Screen, None) => unsafe { |
500 | Framebuffer::unbind(&self.context); |
501 | self.view = self.screen_view; |
502 | self.context.viewport(0, 0, self.view[0] as i32, self.view[1] as i32); |
503 | }, |
504 | (RenderTarget::Screen, Some(framebuffer)) => { |
505 | framebuffer.bind(); |
506 | self.view = self.screen_view; |
507 | unsafe { |
508 | self.context.viewport(0, 0, self.view[0] as i32, self.view[1] as i32); |
509 | } |
510 | } |
511 | (RenderTarget::Image(id), _) => { |
512 | let context = self.context.clone(); |
513 | if let Some(texture) = images.get(id) { |
514 | if let Ok(fb) = self |
515 | .framebuffers |
516 | .entry(id) |
517 | .or_insert_with(|| Framebuffer::new(&context, texture)) |
518 | { |
519 | fb.bind(); |
520 | |
521 | self.view[0] = texture.info().width() as f32; |
522 | self.view[1] = texture.info().height() as f32; |
523 | |
524 | unsafe { |
525 | self.context |
526 | .viewport(0, 0, texture.info().width() as i32, texture.info().height() as i32); |
527 | } |
528 | } |
529 | } |
530 | } |
531 | } |
532 | } |
533 | |
534 | /// Make the "Screen" `RenderTarget` actually render to a framebuffer object. This is useful when |
535 | /// embedding femtovg into another program where final composition is handled by an external task. |
536 | /// The given `framebuffer_object` must refer to a Framebuffer Object created on the current OpenGL |
537 | /// Context, and must have a depth & stencil attachment. |
538 | /// |
539 | /// Pass `None` to clear any previous Framebuffer Object ID that was passed and target rendering to |
540 | /// the default target (normally the window). |
541 | pub fn set_screen_target(&mut self, framebuffer_object: Option<<glow::Context as glow::HasContext>::Framebuffer>) { |
542 | match framebuffer_object { |
543 | Some(fbo_id) => self.screen_target = Some(Framebuffer::from_external(&self.context, fbo_id)), |
544 | None => self.screen_target = None, |
545 | } |
546 | } |
547 | |
548 | fn render_filtered_image( |
549 | &mut self, |
550 | images: &mut ImageStore<GlTexture>, |
551 | cmd: Command, |
552 | target_image: ImageId, |
553 | filter: ImageFilter, |
554 | ) { |
555 | match filter { |
556 | ImageFilter::GaussianBlur { sigma } => self.render_gaussian_blur(images, cmd, target_image, sigma), |
557 | } |
558 | } |
559 | |
560 | fn render_gaussian_blur( |
561 | &mut self, |
562 | images: &mut ImageStore<GlTexture>, |
563 | mut cmd: Command, |
564 | target_image: ImageId, |
565 | sigma: f32, |
566 | ) { |
567 | let original_render_target = self.current_render_target; |
568 | |
569 | // The filtering happens in two passes, first a horizontal blur and then the vertial blur. The |
570 | // first pass therefore renders into an intermediate, temporarily allocated texture. |
571 | |
572 | let source_image_info = images.get(cmd.image.unwrap()).unwrap().info(); |
573 | |
574 | let image_paint = crate::Paint::image( |
575 | cmd.image.unwrap(), |
576 | 0., |
577 | 0., |
578 | source_image_info.width() as _, |
579 | source_image_info.height() as _, |
580 | 0., |
581 | 1., |
582 | ); |
583 | let mut blur_params = Params::new( |
584 | images, |
585 | &Default::default(), |
586 | &image_paint.flavor, |
587 | &Default::default(), |
588 | &Scissor::default(), |
589 | 0., |
590 | 0., |
591 | 0., |
592 | ); |
593 | blur_params.shader_type = ShaderType::FilterImage; |
594 | |
595 | let gauss_coeff_x = 1. / ((2. * std::f32::consts::PI).sqrt() * sigma); |
596 | let gauss_coeff_y = f32::exp(-0.5 / (sigma * sigma)); |
597 | let gauss_coeff_z = gauss_coeff_y * gauss_coeff_y; |
598 | |
599 | blur_params.image_blur_filter_coeff[0] = gauss_coeff_x; |
600 | blur_params.image_blur_filter_coeff[1] = gauss_coeff_y; |
601 | blur_params.image_blur_filter_coeff[2] = gauss_coeff_z; |
602 | |
603 | blur_params.image_blur_filter_direction = [1.0, 0.0]; |
604 | |
605 | // GLES 2.0 does not allow non-constant loop indices, so limit the standard devitation to allow for a upper fixed limit |
606 | // on the number of iterations in the fragment shader. |
607 | blur_params.image_blur_filter_sigma = sigma.min(8.); |
608 | |
609 | let horizontal_blur_buffer = images.alloc(self, source_image_info).unwrap(); |
610 | self.set_target(images, RenderTarget::Image(horizontal_blur_buffer)); |
611 | self.main_program().set_view(self.view); |
612 | |
613 | self.clear_rect( |
614 | 0, |
615 | 0, |
616 | source_image_info.width() as _, |
617 | source_image_info.height() as _, |
618 | Color::rgbaf(0., 0., 0., 0.), |
619 | ); |
620 | |
621 | self.triangles(images, &cmd, &blur_params); |
622 | |
623 | self.set_target(images, RenderTarget::Image(target_image)); |
624 | self.main_program().set_view(self.view); |
625 | |
626 | self.clear_rect( |
627 | 0, |
628 | 0, |
629 | source_image_info.width() as _, |
630 | source_image_info.height() as _, |
631 | Color::rgbaf(0., 0., 0., 0.), |
632 | ); |
633 | |
634 | blur_params.image_blur_filter_direction = [0.0, 1.0]; |
635 | |
636 | cmd.image = Some(horizontal_blur_buffer); |
637 | |
638 | self.triangles(images, &cmd, &blur_params); |
639 | |
640 | images.remove(self, horizontal_blur_buffer); |
641 | |
642 | // restore previous render target and view |
643 | self.set_target(images, original_render_target); |
644 | self.main_program().set_view(self.view); |
645 | } |
646 | |
647 | fn main_program(&self) -> &MainProgram { |
648 | let programs = if self.current_program_needs_glyph_texture { |
649 | &self.main_programs_with_glyph_texture |
650 | } else { |
651 | &self.main_programs_without_glyph_texture |
652 | }; |
653 | programs[self.current_program as usize] |
654 | .as_ref() |
655 | .expect("internal error: invalid shader program selected for given paint" ) |
656 | } |
657 | |
658 | fn select_main_program(&mut self, params: &Params) { |
659 | let program_index = params.shader_type.to_u8(); |
660 | if program_index != self.current_program |
661 | || params.uses_glyph_texture() != self.current_program_needs_glyph_texture |
662 | { |
663 | unsafe { |
664 | self.context.active_texture(glow::TEXTURE0); |
665 | self.context.bind_texture(glow::TEXTURE_2D, None); |
666 | self.context.active_texture(glow::TEXTURE0 + 1); |
667 | self.context.bind_texture(glow::TEXTURE_2D, None); |
668 | } |
669 | |
670 | self.main_program().unbind(); |
671 | self.current_program = program_index; |
672 | self.current_program_needs_glyph_texture = params.uses_glyph_texture(); |
673 | |
674 | let program = self.main_program(); |
675 | program.bind(); |
676 | // Bind the two uniform samplers to texture units |
677 | program.set_tex(0); |
678 | program.set_glyphtex(1); |
679 | program.set_view(self.view); |
680 | } |
681 | } |
682 | } |
683 | |
684 | impl Renderer for OpenGl { |
685 | type Image = GlTexture; |
686 | type NativeTexture = <glow::Context as glow::HasContext>::Texture; |
687 | type Surface = (); |
688 | type CommandBuffer = (); |
689 | |
690 | fn set_size(&mut self, width: u32, height: u32, _dpi: f32) { |
691 | self.view[0] = width as f32; |
692 | self.view[1] = height as f32; |
693 | |
694 | self.screen_view = self.view; |
695 | |
696 | unsafe { |
697 | self.context.viewport(0, 0, width as i32, height as i32); |
698 | } |
699 | } |
700 | |
701 | fn get_native_texture(&self, image: &Self::Image) -> Result<Self::NativeTexture, ErrorKind> { |
702 | Ok(image.id()) |
703 | } |
704 | |
705 | fn render( |
706 | &mut self, |
707 | _surface: &Self::Surface, |
708 | images: &mut ImageStore<Self::Image>, |
709 | verts: &[Vertex], |
710 | commands: Vec<Command>, |
711 | ) { |
712 | self.current_program = 0; |
713 | self.main_program().bind(); |
714 | |
715 | unsafe { |
716 | self.context.enable(glow::CULL_FACE); |
717 | |
718 | self.context.cull_face(glow::BACK); |
719 | self.context.front_face(glow::CCW); |
720 | self.context.enable(glow::BLEND); |
721 | self.context.disable(glow::DEPTH_TEST); |
722 | self.context.disable(glow::SCISSOR_TEST); |
723 | self.context.color_mask(true, true, true, true); |
724 | self.context.stencil_mask(0xffff_ffff); |
725 | self.context.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); |
726 | self.context.stencil_func(glow::ALWAYS, 0, 0xffff_ffff); |
727 | self.context.active_texture(glow::TEXTURE0); |
728 | self.context.bind_texture(glow::TEXTURE_2D, None); |
729 | self.context.active_texture(glow::TEXTURE0 + 1); |
730 | self.context.bind_texture(glow::TEXTURE_2D, None); |
731 | |
732 | self.context.bind_vertex_array(self.vert_arr); |
733 | |
734 | let vertex_size = mem::size_of::<Vertex>(); |
735 | |
736 | self.context.bind_buffer(glow::ARRAY_BUFFER, self.vert_buff); |
737 | self.context |
738 | .buffer_data_u8_slice(glow::ARRAY_BUFFER, verts.align_to().1, glow::STREAM_DRAW); |
739 | |
740 | self.context.enable_vertex_attrib_array(0); |
741 | self.context.enable_vertex_attrib_array(1); |
742 | |
743 | self.context |
744 | .vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, vertex_size as i32, 0); |
745 | self.context.vertex_attrib_pointer_f32( |
746 | 1, |
747 | 2, |
748 | glow::FLOAT, |
749 | false, |
750 | vertex_size as i32, |
751 | 2 * mem::size_of::<f32>() as i32, |
752 | ); |
753 | } |
754 | |
755 | self.check_error("render prepare" ); |
756 | |
757 | for cmd in commands { |
758 | self.set_composite_operation(cmd.composite_operation); |
759 | |
760 | match cmd.cmd_type { |
761 | CommandType::ConvexFill { ref params } => self.convex_fill(images, &cmd, params), |
762 | CommandType::ConcaveFill { |
763 | ref stencil_params, |
764 | ref fill_params, |
765 | } => self.concave_fill(images, &cmd, stencil_params, fill_params), |
766 | CommandType::Stroke { ref params } => self.stroke(images, &cmd, params), |
767 | CommandType::StencilStroke { |
768 | ref params1, |
769 | ref params2, |
770 | } => self.stencil_stroke(images, &cmd, params1, params2), |
771 | CommandType::Triangles { ref params } => self.triangles(images, &cmd, params), |
772 | CommandType::ClearRect { color } => { |
773 | if let Some((start, _)) = cmd.triangles_verts { |
774 | let x = verts[start].x as _; |
775 | let y = verts[start].y as _; |
776 | let width = verts[start + 1].x as u32 - x; |
777 | let height = verts[start + 1].y as u32 - y; |
778 | self.clear_rect(x, y, width, height, color); |
779 | } |
780 | } |
781 | CommandType::SetRenderTarget(target) => { |
782 | self.set_target(images, target); |
783 | self.main_program().set_view(self.view); |
784 | } |
785 | CommandType::RenderFilteredImage { target_image, filter } => { |
786 | self.render_filtered_image(images, cmd, target_image, filter) |
787 | } |
788 | } |
789 | } |
790 | |
791 | unsafe { |
792 | self.context.disable_vertex_attrib_array(0); |
793 | self.context.disable_vertex_attrib_array(1); |
794 | self.context.bind_vertex_array(None); |
795 | |
796 | self.context.disable(glow::CULL_FACE); |
797 | self.context.bind_buffer(glow::ARRAY_BUFFER, None); |
798 | self.context.bind_texture(glow::TEXTURE_2D, None); |
799 | } |
800 | |
801 | self.main_program().unbind(); |
802 | |
803 | self.check_error("render done" ); |
804 | } |
805 | |
806 | fn alloc_image(&mut self, info: ImageInfo) -> Result<Self::Image, ErrorKind> { |
807 | Self::Image::new(&self.context, info, self.is_opengles_2_0) |
808 | } |
809 | |
810 | fn create_image_from_native_texture( |
811 | &mut self, |
812 | native_texture: Self::NativeTexture, |
813 | info: ImageInfo, |
814 | ) -> Result<Self::Image, ErrorKind> { |
815 | Ok(Self::Image::new_from_native_texture(native_texture, info)) |
816 | } |
817 | |
818 | fn update_image( |
819 | &mut self, |
820 | image: &mut Self::Image, |
821 | data: ImageSource, |
822 | x: usize, |
823 | y: usize, |
824 | ) -> Result<(), ErrorKind> { |
825 | image.update(&self.context, data, x, y, self.is_opengles_2_0) |
826 | } |
827 | |
828 | fn delete_image(&mut self, image: Self::Image, image_id: ImageId) { |
829 | self.framebuffers.remove(&image_id); |
830 | image.delete(&self.context); |
831 | } |
832 | |
833 | fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> { |
834 | //let mut image = image::RgbaImage::new(self.view[0] as u32, self.view[1] as u32); |
835 | let w = self.view[0] as usize; |
836 | let h = self.view[1] as usize; |
837 | |
838 | let mut image = ImgVec::new( |
839 | vec![ |
840 | RGBA8 { |
841 | r: 255, |
842 | g: 255, |
843 | b: 255, |
844 | a: 255 |
845 | }; |
846 | w * h |
847 | ], |
848 | w, |
849 | h, |
850 | ); |
851 | |
852 | unsafe { |
853 | self.context.read_pixels( |
854 | 0, |
855 | 0, |
856 | self.view[0] as i32, |
857 | self.view[1] as i32, |
858 | glow::RGBA, |
859 | glow::UNSIGNED_BYTE, |
860 | glow::PixelPackData::Slice(Some(image.buf_mut().align_to_mut().1)), |
861 | ); |
862 | } |
863 | |
864 | let mut flipped = Vec::with_capacity(w * h); |
865 | |
866 | for row in image.rows().rev() { |
867 | flipped.extend_from_slice(row); |
868 | } |
869 | |
870 | Ok(ImgVec::new(flipped, w, h)) |
871 | } |
872 | } |
873 | |
874 | impl SurfacelessRenderer for OpenGl { |
875 | fn render_surfaceless(&mut self, images: &mut ImageStore<Self::Image>, verts: &[Vertex], commands: Vec<Command>) { |
876 | self.render(&(), images, verts, commands) |
877 | } |
878 | } |
879 | |
880 | impl Drop for OpenGl { |
881 | fn drop(&mut self) { |
882 | if let Some(vert_arr: NativeVertexArray) = self.vert_arr { |
883 | unsafe { |
884 | self.context.delete_vertex_array(vert_arr); |
885 | } |
886 | } |
887 | |
888 | if let Some(vert_buff: NativeBuffer) = self.vert_buff { |
889 | unsafe { |
890 | self.context.delete_buffer(vert_buff); |
891 | } |
892 | } |
893 | } |
894 | } |
895 | |