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