| 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 | |