From 5421525eaa0fed97e4eed4aefe6ddbbf94ad9cdd Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 22 Feb 2020 14:39:03 -0800 Subject: [PATCH] Implement the HSL filters (called "non-separable blend modes" in the spec). These cannot be implemented with the standard OpenGL blending functions, so we have to do them manually in a shader, which requires a good deal of machinery to create intermediate framebuffers and so forth. --- canvas/src/lib.rs | 8 + content/src/effects.rs | 12 +- examples/canvas_nanovg/src/main.rs | 3 +- gl/src/lib.rs | 3 + gpu/src/lib.rs | 1 + metal/src/lib.rs | 8 + renderer/src/builder.rs | 44 ++- renderer/src/gpu/renderer.rs | 327 ++++++++++++++---- renderer/src/gpu/shaders.rs | 108 ++++-- renderer/src/gpu_data.rs | 14 +- renderer/src/paint.rs | 9 +- resources/shaders/gl3/filter_basic.fs.glsl | 1 - resources/shaders/gl3/tile_alpha.fs.glsl | 3 +- resources/shaders/gl3/tile_alpha.vs.glsl | 1 - resources/shaders/gl3/tile_alpha_hsl.fs.glsl | 90 +++++ resources/shaders/gl3/tile_copy.fs.glsl | 26 ++ resources/shaders/gl3/tile_copy.vs.glsl | 26 ++ resources/shaders/gl3/tile_solid.fs.glsl | 1 - .../shaders/metal/tile_alpha_hsl.fs.metal | 127 +++++++ resources/shaders/metal/tile_copy.fs.metal | 26 ++ resources/shaders/metal/tile_copy.vs.metal | 30 ++ shaders/Makefile | 3 + shaders/filter_basic.fs.glsl | 1 - shaders/tile_alpha.fs.glsl | 3 +- shaders/tile_alpha.vs.glsl | 1 - shaders/tile_alpha_hsl.fs.glsl | 87 +++++ shaders/tile_copy.fs.glsl | 23 ++ shaders/tile_copy.vs.glsl | 23 ++ shaders/tile_solid.fs.glsl | 1 - 29 files changed, 904 insertions(+), 106 deletions(-) create mode 100644 resources/shaders/gl3/tile_alpha_hsl.fs.glsl create mode 100644 resources/shaders/gl3/tile_copy.fs.glsl create mode 100644 resources/shaders/gl3/tile_copy.vs.glsl create mode 100644 resources/shaders/metal/tile_alpha_hsl.fs.metal create mode 100644 resources/shaders/metal/tile_copy.fs.metal create mode 100644 resources/shaders/metal/tile_copy.vs.metal create mode 100644 shaders/tile_alpha_hsl.fs.glsl create mode 100644 shaders/tile_copy.fs.glsl create mode 100644 shaders/tile_copy.vs.glsl diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index bb1b62c5..52620d3a 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -531,6 +531,10 @@ pub enum CompositeOperation { Lighter, Lighten, Darken, + Hue, + Saturation, + Color, + Luminosity, } impl CompositeOperation { @@ -544,6 +548,10 @@ impl CompositeOperation { CompositeOperation::Lighter => BlendMode::Lighter, CompositeOperation::Lighten => BlendMode::Lighten, CompositeOperation::Darken => BlendMode::Darken, + CompositeOperation::Hue => BlendMode::Hue, + CompositeOperation::Saturation => BlendMode::Saturation, + CompositeOperation::Color => BlendMode::Color, + CompositeOperation::Luminosity => BlendMode::Luminosity, } } } diff --git a/content/src/effects.rs b/content/src/effects.rs index bbe6cbb0..a2e50b9e 100644 --- a/content/src/effects.rs +++ b/content/src/effects.rs @@ -61,7 +61,7 @@ pub enum CompositeOp { SrcOver, } -/// Blend modes that can be applied to individual paths without creating layers for them. +/// Blend modes that can be applied to individual paths. #[derive(Clone, Copy, PartialEq, Debug)] pub enum BlendMode { Clear, @@ -73,6 +73,10 @@ pub enum BlendMode { Lighter, Lighten, Darken, + Hue, + Saturation, + Color, + Luminosity, } #[derive(Clone, Copy, PartialEq, Debug)] @@ -105,7 +109,11 @@ impl BlendMode { BlendMode::Xor | BlendMode::Lighter | BlendMode::Lighten | - BlendMode::Darken => false, + BlendMode::Darken | + BlendMode::Hue | + BlendMode::Saturation | + BlendMode::Color | + BlendMode::Luminosity => false, } } } diff --git a/examples/canvas_nanovg/src/main.rs b/examples/canvas_nanovg/src/main.rs index 7afa137e..201bd747 100644 --- a/examples/canvas_nanovg/src/main.rs +++ b/examples/canvas_nanovg/src/main.rs @@ -9,7 +9,8 @@ // except according to those terms. use arrayvec::ArrayVec; -use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, LineJoin, Path2D}; +use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D}; +use pathfinder_canvas::{FillStyle, LineJoin, Path2D}; use pathfinder_color::{ColorF, ColorU}; use pathfinder_content::fill::FillRule; use pathfinder_content::gradient::{ColorStop, Gradient}; diff --git a/gl/src/lib.rs b/gl/src/lib.rs index b2742c33..b1ef4178 100644 --- a/gl/src/lib.rs +++ b/gl/src/lib.rs @@ -145,6 +145,9 @@ impl GLDevice { UniformData::Float(value) => { gl::Uniform1f(uniform.location, value); ck(); } + UniformData::IVec3(value) => { + gl::Uniform3i(uniform.location, value[0], value[1], value[2]); ck(); + } UniformData::Int(value) => { gl::Uniform1i(uniform.location, value); ck(); } diff --git a/gpu/src/lib.rs b/gpu/src/lib.rs index d66c067c..25475cab 100644 --- a/gpu/src/lib.rs +++ b/gpu/src/lib.rs @@ -164,6 +164,7 @@ pub enum ShaderKind { #[derive(Clone, Copy)] pub enum UniformData { Float(f32), + IVec3([i32; 3]), Int(i32), Mat2(F32x4), Mat4([F32x4; 4]), diff --git a/metal/src/lib.rs b/metal/src/lib.rs index c7978603..192b2304 100644 --- a/metal/src/lib.rs +++ b/metal/src/lib.rs @@ -873,6 +873,11 @@ impl MetalDevice { UniformData::Float(value) => { uniform_buffer_data.write_f32::(value).unwrap() } + UniformData::IVec3(values) => { + uniform_buffer_data.write_i32::(values[0]).unwrap(); + uniform_buffer_data.write_i32::(values[1]).unwrap(); + uniform_buffer_data.write_i32::(values[2]).unwrap(); + } UniformData::Int(value) => { uniform_buffer_data.write_i32::(value).unwrap() } @@ -1251,6 +1256,9 @@ impl UniformDataExt for UniformData { UniformData::Float(ref data) => { Some(slice::from_raw_parts(data as *const f32 as *const u8, 4 * 1)) } + UniformData::IVec3(ref data) => { + Some(slice::from_raw_parts(data as *const i32 as *const u8, 4 * 3)) + } UniformData::Int(ref data) => { Some(slice::from_raw_parts(data as *const i32 as *const u8, 4 * 1)) } diff --git a/renderer/src/builder.rs b/renderer/src/builder.rs index a71d8bfe..9442c55a 100644 --- a/renderer/src/builder.rs +++ b/renderer/src/builder.rs @@ -11,7 +11,7 @@ //! Packs data onto the GPU. use crate::concurrent::executor::Executor; -use crate::gpu::renderer::MASK_TILES_ACROSS; +use crate::gpu::renderer::{BlendModeProgram, MASK_TILES_ACROSS}; use crate::gpu_data::{AlphaTile, AlphaTileVertex, FillBatchPrimitive, MaskTile, MaskTileVertex}; use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileBatch, TileObjectPrimitive}; use crate::options::{PreparedBuildOptions, RenderCommandListener}; @@ -90,13 +90,22 @@ impl<'a> SceneBuilder<'a> { pub fn build(&mut self, executor: &E) where E: Executor { let start_time = Instant::now(); + // Send the start rendering command. let bounding_quad = self.built_options.bounding_quad(); let clip_path_count = self.scene.clip_paths.len(); let draw_path_count = self.scene.paths.len(); let total_path_count = clip_path_count + draw_path_count; - self.listener.send(RenderCommand::Start { bounding_quad, path_count: total_path_count }); + let needs_readable_framebuffer = self.needs_readable_framebuffer(); + + self.listener.send(RenderCommand::Start { + bounding_quad, + path_count: total_path_count, + needs_readable_framebuffer, + }); + + // Build paint data. let PaintInfo { data: paint_data, metadata: paint_metadata, @@ -249,7 +258,8 @@ impl<'a> SceneBuilder<'a> { culled_tiles.push_mask_tiles(&built_draw_path.path); // Create a new `DrawAlphaTiles` display item if we don't have one or if we have to - // break a batch due to blend mode or paint page. + // break a batch due to blend mode or paint page. Note that every path with a blend + // mode that requires a readable framebuffer needs its own batch. // // TODO(pcwalton): If we really wanted to, we could use tile maps to avoid batch // breaks in some cases… @@ -259,7 +269,9 @@ impl<'a> SceneBuilder<'a> { paint_page, blend_mode }) if paint_page == built_draw_path.paint_page && - blend_mode == built_draw_path.blend_mode => {} + blend_mode == built_draw_path.blend_mode && + !BlendModeProgram::from_blend_mode( + built_draw_path.blend_mode).needs_readable_framebuffer() => {} _ => { culled_tiles.display_list.push(CulledDisplayItem::DrawAlphaTiles { tiles: vec![], @@ -377,6 +389,30 @@ impl<'a> SceneBuilder<'a> { // FIXME(pcwalton): Check for overflow! self.next_mask_tile_index.fetch_add(1, Ordering::Relaxed) as u16 } + + fn needs_readable_framebuffer(&self) -> bool { + let mut framebuffer_nesting = 0; + for display_item in &self.scene.display_list { + match *display_item { + DisplayItem::DrawRenderTarget { .. } => {} + DisplayItem::PushRenderTarget(_) => framebuffer_nesting += 1, + DisplayItem::PopRenderTarget => framebuffer_nesting -= 1, + DisplayItem::DrawPaths { start_index, end_index } => { + if framebuffer_nesting > 0 { + continue; + } + for path_index in start_index..end_index { + let blend_mode = self.scene.paths[path_index as usize].blend_mode(); + let blend_mode_program = BlendModeProgram::from_blend_mode(blend_mode); + if blend_mode_program.needs_readable_framebuffer() { + return true; + } + } + } + } + } + false + } } impl BuiltPath { diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index 0146e207..b24b64e9 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -10,7 +10,8 @@ use crate::gpu::debug::DebugUIPresenter; use crate::gpu::options::{DestFramebuffer, RendererOptions}; -use crate::gpu::shaders::{AlphaTileProgram, AlphaTileVertexArray, FillProgram, FillVertexArray}; +use crate::gpu::shaders::{AlphaTileHSLProgram, AlphaTileProgram, AlphaTileVertexArray}; +use crate::gpu::shaders::{CopyTileProgram, CopyTileVertexArray, FillProgram, FillVertexArray}; use crate::gpu::shaders::{FilterBasicProgram, FilterBasicVertexArray, FilterTextProgram}; use crate::gpu::shaders::{FilterTextVertexArray, MAX_FILLS_PER_BATCH, MaskTileProgram}; use crate::gpu::shaders::{MaskTileVertexArray, ReprojectionProgram, ReprojectionVertexArray}; @@ -18,6 +19,7 @@ use crate::gpu::shaders::{SolidTileProgram, SolidTileVertexArray}; use crate::gpu::shaders::{StencilProgram, StencilVertexArray}; use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents}; use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex}; +use crate::options::BoundingQuad; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use pathfinder_color::{self as color, ColorF}; use pathfinder_content::effects::{BlendMode, CompositeOp, DefringingKernel, Effects, Filter}; @@ -51,6 +53,9 @@ const TEXTURE_CACHE_SIZE: usize = 8; const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; const MASK_FRAMEBUFFER_HEIGHT: i32 = TILE_HEIGHT as i32 * MASK_TILES_DOWN as i32; +const BLEND_TERM_DEST: i32 = 0; +const BLEND_TERM_SRC: i32 = 1; + pub struct Renderer where D: Device, @@ -64,13 +69,18 @@ where fill_program: FillProgram, mask_winding_tile_program: MaskTileProgram, mask_evenodd_tile_program: MaskTileProgram, + copy_tile_program: CopyTileProgram, solid_tile_program: SolidTileProgram, alpha_tile_program: AlphaTileProgram, + alpha_tile_hsl_program: AlphaTileHSLProgram, mask_winding_tile_vertex_array: MaskTileVertexArray, mask_evenodd_tile_vertex_array: MaskTileVertexArray, + copy_tile_vertex_array: CopyTileVertexArray, solid_tile_vertex_array: SolidTileVertexArray, alpha_tile_vertex_array: AlphaTileVertexArray, + alpha_tile_hsl_vertex_array: AlphaTileVertexArray, area_lut_texture: D::Texture, + alpha_tile_vertex_buffer: D::Buffer, quad_vertex_positions_buffer: D::Buffer, quad_vertex_indices_buffer: D::Buffer, quads_vertex_indices_buffer: D::Buffer, @@ -78,6 +88,8 @@ where fill_vertex_array: FillVertexArray, fill_framebuffer: D::Framebuffer, mask_framebuffer: D::Framebuffer, + dest_blend_framebuffer: D::Framebuffer, + intermediate_dest_framebuffer: D::Framebuffer, paint_textures: Vec>, render_targets: Vec>, render_target_stack: Vec, @@ -115,7 +127,7 @@ where pub debug_ui_presenter: DebugUIPresenter, // Extra info - use_depth: bool, + flags: RendererFlags, } impl Renderer @@ -134,8 +146,10 @@ where let mask_evenodd_tile_program = MaskTileProgram::new(FillRule::EvenOdd, &device, resources); + let copy_tile_program = CopyTileProgram::new(&device, resources); let solid_tile_program = SolidTileProgram::new(&device, resources); let alpha_tile_program = AlphaTileProgram::new(&device, resources); + let alpha_tile_hsl_program = AlphaTileHSLProgram::new(&device, resources); let filter_basic_program = FilterBasicProgram::new(&device, resources); let filter_text_program = FilterTextProgram::new(&device, resources); let stencil_program = StencilProgram::new(&device, resources); @@ -144,6 +158,7 @@ where let area_lut_texture = device.create_texture_from_png(resources, "area-lut"); let gamma_lut_texture = device.create_texture_from_png(resources, "gamma-lut"); + let alpha_tile_vertex_buffer = device.create_buffer(); let quad_vertex_positions_buffer = device.create_buffer(); device.allocate_buffer( &quad_vertex_positions_buffer, @@ -176,9 +191,22 @@ where &mask_evenodd_tile_program, &quads_vertex_indices_buffer, ); + let copy_tile_vertex_array = CopyTileVertexArray::new( + &device, + ©_tile_program, + &alpha_tile_vertex_buffer, + &quads_vertex_indices_buffer, + ); let alpha_tile_vertex_array = AlphaTileVertexArray::new( &device, &alpha_tile_program, + &alpha_tile_vertex_buffer, + &quads_vertex_indices_buffer, + ); + let alpha_tile_hsl_vertex_array = AlphaTileVertexArray::new( + &device, + &alpha_tile_hsl_program.alpha_tile_program, + &alpha_tile_vertex_buffer, &quads_vertex_indices_buffer, ); let solid_tile_vertex_array = SolidTileVertexArray::new( @@ -218,12 +246,17 @@ where device.create_texture(TextureFormat::R8, mask_framebuffer_size); let mask_framebuffer = device.create_framebuffer(mask_framebuffer_texture); + let window_size = dest_framebuffer.window_size(&device); + let dest_blend_texture = device.create_texture(TextureFormat::RGBA8, window_size); + let dest_blend_framebuffer = device.create_framebuffer(dest_blend_texture); + let intermediate_dest_texture = device.create_texture(TextureFormat::RGBA8, window_size); + let intermediate_dest_framebuffer = device.create_framebuffer(intermediate_dest_texture); + let clear_paint_texture = device.create_texture_from_data(TextureFormat::RGBA8, Vector2I::splat(1), TextureDataRef::U8(&[0, 0, 0, 255])); - let window_size = dest_framebuffer.window_size(&device); let debug_ui_presenter = DebugUIPresenter::new(&device, resources, window_size); Renderer { @@ -234,13 +267,18 @@ where fill_program, mask_winding_tile_program, mask_evenodd_tile_program, + copy_tile_program, solid_tile_program, alpha_tile_program, + alpha_tile_hsl_program, mask_winding_tile_vertex_array, mask_evenodd_tile_vertex_array, solid_tile_vertex_array, alpha_tile_vertex_array, + copy_tile_vertex_array, + alpha_tile_hsl_vertex_array, area_lut_texture, + alpha_tile_vertex_buffer, quad_vertex_positions_buffer, quad_vertex_indices_buffer, quads_vertex_indices_buffer, @@ -248,6 +286,8 @@ where fill_vertex_array, fill_framebuffer, mask_framebuffer, + dest_blend_framebuffer, + intermediate_dest_framebuffer, paint_textures: vec![], render_targets: vec![], render_target_stack: vec![], @@ -275,7 +315,7 @@ where buffered_fills: vec![], texture_cache: TextureCache::new(), - use_depth: false, + flags: RendererFlags::empty(), } } @@ -287,11 +327,8 @@ where pub fn render_command(&mut self, command: &RenderCommand) { match *command { - RenderCommand::Start { bounding_quad, path_count } => { - if self.use_depth { - self.draw_stencil(&bounding_quad); - } - self.stats.path_count = path_count; + RenderCommand::Start { bounding_quad, path_count, needs_readable_framebuffer } => { + self.start_rendering(bounding_quad, path_count, needs_readable_framebuffer); } RenderCommand::AddPaintData(ref paint_data) => self.upload_paint_data(paint_data), RenderCommand::AddFills(ref fills) => self.add_fills(fills), @@ -328,12 +365,31 @@ where } pub fn end_scene(&mut self) { + self.blit_intermediate_dest_framebuffer_if_necessary(); + self.end_composite_timer_query(); self.pending_timers.push_back(mem::replace(&mut self.current_timers, RenderTimers::new())); self.device.end_commands(); } + fn start_rendering(&mut self, + bounding_quad: BoundingQuad, + path_count: usize, + mut needs_readable_framebuffer: bool) { + if let DestFramebuffer::Other(_) = self.dest_framebuffer { + needs_readable_framebuffer = false; + } + + if self.flags.contains(RendererFlags::USE_DEPTH) { + self.draw_stencil(&bounding_quad); + } + self.stats.path_count = path_count; + + self.flags.set(RendererFlags::INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED, + needs_readable_framebuffer); + } + pub fn draw_debug_ui(&self) { self.debug_ui_presenter.draw(&self.device); } @@ -392,12 +448,12 @@ where #[inline] pub fn disable_depth(&mut self) { - self.use_depth = false; + self.flags.remove(RendererFlags::USE_DEPTH); } #[inline] pub fn enable_depth(&mut self) { - self.use_depth = true; + self.flags.insert(RendererFlags::USE_DEPTH); } #[inline] @@ -481,12 +537,10 @@ where } fn upload_alpha_tiles(&mut self, alpha_tiles: &[AlphaTile]) { - self.device.allocate_buffer( - &self.alpha_tile_vertex_array.vertex_buffer, - BufferData::Memory(&alpha_tiles), - BufferTarget::Vertex, - BufferUploadMode::Dynamic, - ); + self.device.allocate_buffer(&self.alpha_tile_vertex_buffer, + BufferData::Memory(&alpha_tiles), + BufferTarget::Vertex, + BufferUploadMode::Dynamic); self.ensure_index_buffer(alpha_tiles.len()); } @@ -645,18 +699,32 @@ where tile_count: u32, paint_page: PaintPageId, blend_mode: BlendMode) { + let blend_mode_program = BlendModeProgram::from_blend_mode(blend_mode); + if blend_mode_program.needs_readable_framebuffer() { + self.copy_alpha_tiles_to_dest_blend_texture(tile_count); + } + let clear_color = self.clear_color_for_draw_operation(); + let (alpha_tile_program, alpha_tile_vertex_array) = match blend_mode_program { + BlendModeProgram::Regular => (&self.alpha_tile_program, &self.alpha_tile_vertex_array), + BlendModeProgram::HSL => { + (&self.alpha_tile_hsl_program.alpha_tile_program, + &self.alpha_tile_hsl_vertex_array) + } + }; + + let draw_viewport = self.draw_viewport(); + let mut textures = vec![self.device.framebuffer_texture(&self.mask_framebuffer)]; let mut uniforms = vec![ - (&self.alpha_tile_program.transform_uniform, + (&alpha_tile_program.transform_uniform, UniformData::Mat4(self.tile_transform().to_columns())), - (&self.alpha_tile_program.tile_size_uniform, + (&alpha_tile_program.tile_size_uniform, UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))), - (&self.alpha_tile_program.stencil_texture_uniform, UniformData::TextureUnit(0)), - (&self.alpha_tile_program.stencil_texture_size_uniform, - UniformData::Vec2(F32x2::new(MASK_FRAMEBUFFER_WIDTH as f32, - MASK_FRAMEBUFFER_HEIGHT as f32))), + (&alpha_tile_program.stencil_texture_uniform, UniformData::TextureUnit(0)), + (&alpha_tile_program.framebuffer_size_uniform, + UniformData::Vec2(draw_viewport.size().to_f32().0)), ]; let paint_texture = match blend_mode { @@ -670,23 +738,25 @@ where textures.push(paint_texture); uniforms.push((&self.alpha_tile_program.paint_texture_uniform, - UniformData::TextureUnit(1))); - uniforms.push((&self.alpha_tile_program.paint_texture_size_uniform, - UniformData::Vec2(self.device - .texture_size(paint_texture) - .0 - .to_f32x2()))); + UniformData::TextureUnit(1))); + + match blend_mode_program { + BlendModeProgram::Regular => {} + BlendModeProgram::HSL => { + self.set_uniforms_for_hsl_blend_mode(&mut textures, &mut uniforms, blend_mode); + } + } self.device.draw_elements(tile_count * 6, &RenderState { target: &self.draw_render_target(), - program: &self.alpha_tile_program.program, - vertex_array: &self.alpha_tile_vertex_array.vertex_array, + program: &alpha_tile_program.program, + vertex_array: &alpha_tile_vertex_array.vertex_array, primitive: Primitive::Triangles, textures: &textures, uniforms: &uniforms, - viewport: self.draw_viewport(), + viewport: draw_viewport, options: RenderOptions { - blend: Some(blend_mode.to_blend_state()), + blend: blend_mode.to_blend_state(), stencil: self.stencil_state(), clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, ..RenderOptions::default() @@ -696,6 +766,60 @@ where self.preserve_draw_framebuffer(); } + fn set_uniforms_for_hsl_blend_mode<'a>(&'a self, + textures: &mut Vec<&'a D::Texture>, + uniforms: &mut Vec<(&'a D::Uniform, UniformData)>, + blend_mode: BlendMode) { + let hsl_terms = match blend_mode { + BlendMode::Hue => [BLEND_TERM_SRC, BLEND_TERM_DEST, BLEND_TERM_DEST], + BlendMode::Saturation => [BLEND_TERM_DEST, BLEND_TERM_SRC, BLEND_TERM_DEST], + BlendMode::Luminosity => [BLEND_TERM_DEST, BLEND_TERM_DEST, BLEND_TERM_SRC ], + BlendMode::Color => [BLEND_TERM_SRC, BLEND_TERM_SRC, BLEND_TERM_DEST], + _ => unreachable!(), + }; + + textures.push(self.device.framebuffer_texture(&self.dest_blend_framebuffer)); + uniforms.push((&self.alpha_tile_hsl_program.dest_uniform, UniformData::TextureUnit(2))); + uniforms.push((&self.alpha_tile_hsl_program.blend_hsl_uniform, + UniformData::IVec3(hsl_terms))); + } + + fn copy_alpha_tiles_to_dest_blend_texture(&mut self, tile_count: u32) { + let mut textures = vec![]; + let mut uniforms = vec![ + (&self.copy_tile_program.transform_uniform, + UniformData::Mat4(self.tile_transform().to_columns())), + (&self.copy_tile_program.tile_size_uniform, + UniformData::Vec2(F32x2::new(TILE_WIDTH as f32, TILE_HEIGHT as f32))), + ]; + + let draw_framebuffer = match self.draw_render_target() { + RenderTarget::Framebuffer(framebuffer) => framebuffer, + RenderTarget::Default => panic!("Can't copy alpha tiles from default framebuffer!"), + }; + let draw_texture = self.device.framebuffer_texture(&draw_framebuffer); + + textures.push(draw_texture); + uniforms.push((&self.copy_tile_program.src_uniform, UniformData::TextureUnit(0))); + + self.device.draw_elements(tile_count * 6, &RenderState { + target: &RenderTarget::Framebuffer(&self.dest_blend_framebuffer), + program: &self.copy_tile_program.program, + vertex_array: &self.copy_tile_vertex_array.vertex_array, + primitive: Primitive::Triangles, + textures: &textures, + uniforms: &uniforms, + viewport: self.draw_viewport(), + options: RenderOptions { + clear_ops: ClearOps { + color: Some(ColorF::transparent_black()), + ..ClearOps::default() + }, + ..RenderOptions::default() + }, + }); + } + fn draw_solid_tiles(&mut self, tile_count: u32, paint_page: PaintPageId) { let clear_color = self.clear_color_for_draw_operation(); @@ -711,8 +835,6 @@ where textures.push(paint_texture); uniforms.push((&self.solid_tile_program.paint_texture_uniform, UniformData::TextureUnit(0))); - uniforms.push((&self.solid_tile_program.paint_texture_size_uniform, - UniformData::Vec2(self.device.texture_size(paint_texture).0.to_f32x2()))); self.device.draw_elements(6 * tile_count, &RenderState { target: &self.draw_render_target(), @@ -800,7 +922,7 @@ where ], viewport: self.draw_viewport(), options: RenderOptions { - blend: Some(BlendMode::SrcOver.to_blend_state()), + blend: BlendMode::SrcOver.to_blend_state(), depth: Some(DepthState { func: DepthFunc::Less, write: false, }), clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, ..RenderOptions::default() @@ -817,10 +939,14 @@ where RenderTarget::Framebuffer(framebuffer) } None => { - match self.dest_framebuffer { - DestFramebuffer::Default { .. } => RenderTarget::Default, - DestFramebuffer::Other(ref framebuffer) => { - RenderTarget::Framebuffer(framebuffer) + if self.flags.contains(RendererFlags::INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED) { + RenderTarget::Framebuffer(&self.intermediate_dest_framebuffer) + } else { + match self.dest_framebuffer { + DestFramebuffer::Default { .. } => RenderTarget::Default, + DestFramebuffer::Other(ref framebuffer) => { + RenderTarget::Framebuffer(framebuffer) + } } } } @@ -859,15 +985,12 @@ where let clear_color = self.clear_color_for_draw_operation(); let source_framebuffer = &self.render_targets[render_target_id.0 as usize].framebuffer; let source_texture = self.device.framebuffer_texture(source_framebuffer); - let source_texture_size = self.device.texture_size(source_texture); let main_viewport = self.main_viewport(); let uniforms = vec![ (&self.filter_basic_program.framebuffer_size_uniform, UniformData::Vec2(main_viewport.size().to_f32().0)), (&self.filter_basic_program.source_uniform, UniformData::TextureUnit(0)), - (&self.filter_basic_program.source_size_uniform, - UniformData::Vec2(source_texture_size.0.to_f32x2())), ]; let blend_state = match composite_op { @@ -884,7 +1007,7 @@ where viewport: main_viewport, options: RenderOptions { clear_ops: ClearOps { color: clear_color, ..ClearOps::default() }, - blend: Some(blend_state), + blend: blend_state, ..RenderOptions::default() }, }); @@ -941,8 +1064,30 @@ where }); } + fn blit_intermediate_dest_framebuffer_if_necessary(&mut self) { + if !self.flags.contains(RendererFlags::INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED) { + return; + } + + let main_viewport = self.main_viewport(); + + let uniforms = [(&self.filter_basic_program.source_uniform, UniformData::TextureUnit(0))]; + let textures = [(self.device.framebuffer_texture(&self.intermediate_dest_framebuffer))]; + + self.device.draw_elements(6, &RenderState { + target: &RenderTarget::Default, + program: &self.filter_basic_program.program, + vertex_array: &self.filter_basic_vertex_array.vertex_array, + primitive: Primitive::Triangles, + textures: &textures[..], + uniforms: &uniforms[..], + viewport: main_viewport, + options: RenderOptions::default(), + }); + } + fn stencil_state(&self) -> Option { - if !self.use_depth { + if !self.flags.contains(RendererFlags::USE_DEPTH) { return None; } @@ -1163,93 +1308,145 @@ struct RenderTargetInfo where D: Device { } trait BlendModeExt { - fn to_blend_state(self) -> BlendState; + fn to_blend_state(self) -> Option; } impl BlendModeExt for BlendMode { - fn to_blend_state(self) -> BlendState { + fn to_blend_state(self) -> Option { match self { BlendMode::Clear => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::Zero, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::Zero, dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, ..BlendState::default() - } + }) } BlendMode::SrcOver => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::One, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::One, dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, ..BlendState::default() - } + }) } BlendMode::DestOver => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::OneMinusDestAlpha, dest_rgb_factor: BlendFactor::DestAlpha, src_alpha_factor: BlendFactor::OneMinusDestAlpha, dest_alpha_factor: BlendFactor::One, ..BlendState::default() - } + }) } BlendMode::DestOut => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::Zero, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::Zero, dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, ..BlendState::default() - } + }) } BlendMode::SrcAtop => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::DestAlpha, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::DestAlpha, dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, ..BlendState::default() - } + }) } BlendMode::Xor => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::OneMinusDestAlpha, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::OneMinusDestAlpha, dest_alpha_factor: BlendFactor::OneMinusSrcAlpha, ..BlendState::default() - } + }) } BlendMode::Lighter => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::One, dest_rgb_factor: BlendFactor::One, src_alpha_factor: BlendFactor::One, dest_alpha_factor: BlendFactor::One, ..BlendState::default() - } + }) } BlendMode::Lighten => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::One, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::One, dest_alpha_factor: BlendFactor::One, op: BlendOp::Max, - } + }) } BlendMode::Darken => { - BlendState { + Some(BlendState { src_rgb_factor: BlendFactor::One, dest_rgb_factor: BlendFactor::OneMinusSrcAlpha, src_alpha_factor: BlendFactor::One, dest_alpha_factor: BlendFactor::One, op: BlendOp::Min, - } + }) + } + BlendMode::Hue | + BlendMode::Saturation | + BlendMode::Color | + BlendMode::Luminosity => { + // Blending is done manually in the shader. + None } } } } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum BlendModeProgram { + Regular, + HSL, +} + +impl BlendModeProgram { + pub(crate) fn from_blend_mode(blend_mode: BlendMode) -> BlendModeProgram { + match blend_mode { + BlendMode::Clear | + BlendMode::SrcOver | + BlendMode::DestOver | + BlendMode::DestOut | + BlendMode::SrcAtop | + BlendMode::Xor | + BlendMode::Lighter | + BlendMode::Lighten | + BlendMode::Darken => BlendModeProgram::Regular, + BlendMode::Hue | + BlendMode::Saturation | + BlendMode::Color | + BlendMode::Luminosity => BlendModeProgram::HSL, + } + } + + pub(crate) fn needs_readable_framebuffer(self) -> bool { + match self { + BlendModeProgram::Regular => false, + BlendModeProgram::HSL => true, + } + } +} + +bitflags! { + struct RendererFlags: u8 { + // Whether we need a depth buffer. + const USE_DEPTH = 0x01; + // Whether an intermediate destination framebuffer is needed. + // + // This will be true if any exotic blend modes are used at the top level (not inside a + // render target), *and* the output framebuffer is the default framebuffer. + const INTERMEDIATE_DEST_FRAMEBUFFER_NEEDED = 0x02; + } +} diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 3b9222d6..4f42bda1 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -176,16 +176,16 @@ impl MaskTileVertexArray where D: Device { pub struct AlphaTileVertexArray where D: Device { pub vertex_array: D::VertexArray, - pub vertex_buffer: D::Buffer, } impl AlphaTileVertexArray where D: Device { pub fn new( device: &D, alpha_tile_program: &AlphaTileProgram, + alpha_tile_vertex_buffer: &D::Buffer, quads_vertex_indices_buffer: &D::Buffer, ) -> AlphaTileVertexArray { - let (vertex_array, vertex_buffer) = (device.create_vertex_array(), device.create_buffer()); + let vertex_array = device.create_vertex_array(); let tile_position_attr = device.get_vertex_attr(&alpha_tile_program.program, "TilePosition").unwrap(); @@ -194,7 +194,7 @@ impl AlphaTileVertexArray where D: Device { let mask_tex_coord_attr = device.get_vertex_attr(&alpha_tile_program.program, "MaskTexCoord").unwrap(); - device.bind_buffer(&vertex_array, &vertex_buffer, BufferTarget::Vertex); + device.bind_buffer(&vertex_array, alpha_tile_vertex_buffer, BufferTarget::Vertex); device.configure_vertex_attr(&vertex_array, &tile_position_attr, &VertexAttrDescriptor { size: 2, class: VertexAttrClass::Int, @@ -224,7 +224,7 @@ impl AlphaTileVertexArray where D: Device { }); device.bind_buffer(&vertex_array, quads_vertex_indices_buffer, BufferTarget::Index); - AlphaTileVertexArray { vertex_array, vertex_buffer } + AlphaTileVertexArray { vertex_array } } } @@ -281,6 +281,38 @@ where } } +pub struct CopyTileVertexArray where D: Device { + pub vertex_array: D::VertexArray, +} + +impl CopyTileVertexArray where D: Device { + pub fn new( + device: &D, + copy_tile_program: &CopyTileProgram, + copy_tile_vertex_buffer: &D::Buffer, + quads_vertex_indices_buffer: &D::Buffer, + ) -> CopyTileVertexArray { + let vertex_array = device.create_vertex_array(); + + let tile_position_attr = + device.get_vertex_attr(©_tile_program.program, "TilePosition").unwrap(); + + device.bind_buffer(&vertex_array, copy_tile_vertex_buffer, BufferTarget::Vertex); + device.configure_vertex_attr(&vertex_array, &tile_position_attr, &VertexAttrDescriptor { + size: 2, + class: VertexAttrClass::Int, + attr_type: VertexAttrType::I16, + stride: ALPHA_TILE_VERTEX_SIZE, + offset: 0, + divisor: 0, + buffer_index: 0, + }); + device.bind_buffer(&vertex_array, quads_vertex_indices_buffer, BufferTarget::Index); + + CopyTileVertexArray { vertex_array } + } +} + pub struct FillProgram where D: Device, @@ -337,7 +369,6 @@ pub struct SolidTileProgram where D: Device { pub transform_uniform: D::Uniform, pub tile_size_uniform: D::Uniform, pub paint_texture_uniform: D::Uniform, - pub paint_texture_size_uniform: D::Uniform, } impl SolidTileProgram where D: Device { @@ -346,13 +377,11 @@ impl SolidTileProgram where D: Device { let transform_uniform = device.get_uniform(&program, "Transform"); let tile_size_uniform = device.get_uniform(&program, "TileSize"); let paint_texture_uniform = device.get_uniform(&program, "PaintTexture"); - let paint_texture_size_uniform = device.get_uniform(&program, "PaintTextureSize"); SolidTileProgram { program, transform_uniform, tile_size_uniform, paint_texture_uniform, - paint_texture_size_uniform, } } } @@ -361,37 +390,78 @@ pub struct AlphaTileProgram where D: Device { pub program: D::Program, pub transform_uniform: D::Uniform, pub tile_size_uniform: D::Uniform, + pub framebuffer_size_uniform: D::Uniform, pub stencil_texture_uniform: D::Uniform, - pub stencil_texture_size_uniform: D::Uniform, pub paint_texture_uniform: D::Uniform, - pub paint_texture_size_uniform: D::Uniform, } impl AlphaTileProgram where D: Device { + #[inline] pub fn new(device: &D, resources: &dyn ResourceLoader) -> AlphaTileProgram { - let program = device.create_program(resources, "tile_alpha"); + AlphaTileProgram::from_fragment_shader_name(device, resources, "tile_alpha") + } + + fn from_fragment_shader_name(device: &D, + resources: &dyn ResourceLoader, + fragment_shader_name: &str) + -> AlphaTileProgram { + let program = device.create_program_from_shader_names(resources, + fragment_shader_name, + "tile_alpha", + fragment_shader_name); let transform_uniform = device.get_uniform(&program, "Transform"); let tile_size_uniform = device.get_uniform(&program, "TileSize"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); let stencil_texture_uniform = device.get_uniform(&program, "StencilTexture"); - let stencil_texture_size_uniform = device.get_uniform(&program, "StencilTextureSize"); let paint_texture_uniform = device.get_uniform(&program, "PaintTexture"); - let paint_texture_size_uniform = device.get_uniform(&program, "PaintTextureSize"); AlphaTileProgram { program, transform_uniform, tile_size_uniform, + framebuffer_size_uniform, stencil_texture_uniform, - stencil_texture_size_uniform, paint_texture_uniform, - paint_texture_size_uniform, } } } +pub struct CopyTileProgram where D: Device { + pub program: D::Program, + pub transform_uniform: D::Uniform, + pub tile_size_uniform: D::Uniform, + pub src_uniform: D::Uniform, +} + +impl CopyTileProgram where D: Device { + pub fn new(device: &D, resources: &dyn ResourceLoader) -> CopyTileProgram { + let program = device.create_program(resources, "tile_copy"); + let transform_uniform = device.get_uniform(&program, "Transform"); + let tile_size_uniform = device.get_uniform(&program, "TileSize"); + let src_uniform = device.get_uniform(&program, "Src"); + CopyTileProgram { program, transform_uniform, tile_size_uniform, src_uniform } + } +} + +pub struct AlphaTileHSLProgram where D: Device { + pub alpha_tile_program: AlphaTileProgram, + pub dest_uniform: D::Uniform, + pub blend_hsl_uniform: D::Uniform, +} + +impl AlphaTileHSLProgram where D: Device { + pub fn new(device: &D, resources: &dyn ResourceLoader) -> AlphaTileHSLProgram { + let alpha_tile_program = AlphaTileProgram::from_fragment_shader_name(device, + resources, + "tile_alpha_hsl"); + let dest_uniform = device.get_uniform(&alpha_tile_program.program, "Dest"); + let blend_hsl_uniform = device.get_uniform(&alpha_tile_program.program, "BlendHSL"); + AlphaTileHSLProgram { alpha_tile_program, dest_uniform, blend_hsl_uniform } + } +} + pub struct FilterBasicProgram where D: Device { pub program: D::Program, pub source_uniform: D::Uniform, - pub source_size_uniform: D::Uniform, pub framebuffer_size_uniform: D::Uniform, } @@ -402,14 +472,8 @@ impl FilterBasicProgram where D: Device { "filter", "filter_basic"); let source_uniform = device.get_uniform(&program, "Source"); - let source_size_uniform = device.get_uniform(&program, "SourceSize"); let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); - FilterBasicProgram { - program, - source_uniform, - source_size_uniform, - framebuffer_size_uniform, - } + FilterBasicProgram { program, source_uniform, framebuffer_size_uniform } } } diff --git a/renderer/src/gpu_data.rs b/renderer/src/gpu_data.rs index 79490486..54597639 100644 --- a/renderer/src/gpu_data.rs +++ b/renderer/src/gpu_data.rs @@ -22,7 +22,19 @@ use std::time::Duration; pub enum RenderCommand { // Starts rendering a frame. - Start { path_count: usize, bounding_quad: BoundingQuad }, + Start { + /// The number of paths that will be rendered. + path_count: usize, + + /// A bounding quad for the scene. + bounding_quad: BoundingQuad, + + /// Whether the framebuffer we're rendering to must be readable. + /// + /// This is needed if a path that renders directly to the output framebuffer (i.e. not to a + /// render target) uses one of the more exotic blend modes. + needs_readable_framebuffer: bool, + }, // Uploads paint data for use with subsequent rendering commands to the GPU. AddPaintData(PaintData), diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index cf706655..c5e7a172 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -261,11 +261,18 @@ impl Palette { Transform2F::from_translation(texture_origin_uv) * Transform2F::from_scale(gradient_tile_scale / view_box_size.to_f32()) } - Paint::Pattern(_) => { + Paint::Pattern(Pattern { source: PatternSource::Image(_), .. }) => { let texture_origin_uv = rect_to_uv(metadata.tex_rect, texture_scale).origin(); Transform2F::from_translation(texture_origin_uv) * Transform2F::from_scale(texture_scale) } + Paint::Pattern(Pattern { source: PatternSource::RenderTarget(_), .. }) => { + // FIXME(pcwalton): Only do this in GL, not Metal! + let texture_origin_uv = rect_to_uv(metadata.tex_rect, + texture_scale).lower_left(); + Transform2F::from_translation(texture_origin_uv) * + Transform2F::from_scale(texture_scale.scale_xy(Vector2F::new(1.0, -1.0))) + } } } diff --git a/resources/shaders/gl3/filter_basic.fs.glsl b/resources/shaders/gl3/filter_basic.fs.glsl index d9181eb4..eb653f42 100644 --- a/resources/shaders/gl3/filter_basic.fs.glsl +++ b/resources/shaders/gl3/filter_basic.fs.glsl @@ -20,7 +20,6 @@ precision highp float; uniform sampler2D uSource; -uniform vec2 uSourceSize; in vec2 vTexCoord; diff --git a/resources/shaders/gl3/tile_alpha.fs.glsl b/resources/shaders/gl3/tile_alpha.fs.glsl index 20056d8f..8c3253b7 100644 --- a/resources/shaders/gl3/tile_alpha.fs.glsl +++ b/resources/shaders/gl3/tile_alpha.fs.glsl @@ -16,11 +16,10 @@ precision highp float; uniform sampler2D uStencilTexture; uniform sampler2D uPaintTexture; -uniform vec2 uPaintTextureSize; +uniform vec2 uFramebufferSize; in vec2 vColorTexCoord; in vec2 vMaskTexCoord; -in vec4 vColor; out vec4 oFragColor; diff --git a/resources/shaders/gl3/tile_alpha.vs.glsl b/resources/shaders/gl3/tile_alpha.vs.glsl index a5ff0ad0..9db80121 100644 --- a/resources/shaders/gl3/tile_alpha.vs.glsl +++ b/resources/shaders/gl3/tile_alpha.vs.glsl @@ -16,7 +16,6 @@ precision highp float; uniform mat4 uTransform; uniform vec2 uTileSize; -uniform vec2 uStencilTextureSize; in ivec2 aTilePosition; in vec2 aColorTexCoord; diff --git a/resources/shaders/gl3/tile_alpha_hsl.fs.glsl b/resources/shaders/gl3/tile_alpha_hsl.fs.glsl new file mode 100644 index 00000000..b8c8e2f9 --- /dev/null +++ b/resources/shaders/gl3/tile_alpha_hsl.fs.glsl @@ -0,0 +1,90 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + +#extension GL_GOOGLE_include_directive : enable + + + + +precision highp float; + +uniform sampler2D uStencilTexture; +uniform sampler2D uPaintTexture; +uniform sampler2D uDest; +uniform ivec3 uBlendHSL; +uniform vec2 uFramebufferSize; + +in vec2 vColorTexCoord; +in vec2 vMaskTexCoord; + +out vec4 oFragColor; + + + + + + +vec3 convertHSLToRGB(vec3 hsl){ + float a = hsl . y * min(hsl . z, 1.0 - hsl . z); + vec3 ks = mod(vec3(0.0, 8.0, 4.0)+ vec3(hsl . x * 1.9098593171027443), 12.0); + return hsl . zzz - clamp(min(ks - vec3(3.0), vec3(9.0)- ks), - 1.0, 1.0)* a; +} + + +vec3 convertRGBToHSL(vec3 rgb){ + float v = max((rgb . x, rgb . y), rgb . z); + float c = v - min((rgb . x, rgb . y), rgb . z); + float l = v - 0.5 * c; + + vec3 tmp = vec3(0.0); + bvec3 is_v = equal(rgb, vec3(v)); + if(is_v . r) + tmp = vec3(0.0, rgb . gb); + else if(is_v . g) + tmp = vec3(2.0, rgb . br); + else if(is_v . b) + tmp = vec3(4.0, rgb . rg); + float h = 1.0471975511965976 *(tmp . x +(tmp . y - tmp . z)/ c); + + float s = 0.0; + if(l > 0.0 && l < 1.0) + s =(v - l)/ min(l, 1.0 - l); + + return vec3(h, s, l); +} + +void main(){ + float coverage = texture(uStencilTexture, vMaskTexCoord). r; + vec4 srcRGBA = texture(uPaintTexture, vColorTexCoord); + srcRGBA . a *= coverage; + + vec2 destTexCoord = gl_FragCoord . xy / uFramebufferSize; + vec4 destRGBA = texture(uDest, destTexCoord); + + vec3 destHSL = convertRGBToHSL(destRGBA . rgb); + vec3 srcHSL = convertRGBToHSL(srcRGBA . rgb); + bvec3 blendDest = equal(uBlendHSL, ivec3(0)); + vec3 blendedHSL = vec3(blendDest . x ? destHSL . x : srcHSL . x, + blendDest . y ? destHSL . y : srcHSL . y, + blendDest . z ? destHSL . z : srcHSL . z); + vec3 blendedRGB = convertHSLToRGB(blendedHSL); + + + vec4 color = vec4(srcRGBA . a *(1.0 - destRGBA . a)* srcRGBA . rgb + + srcRGBA . a * destRGBA . a * blendedRGB + + (1.0 - srcRGBA . a)* destRGBA . a * destRGBA . rgb, + 1.0); + oFragColor = color; +} + diff --git a/resources/shaders/gl3/tile_copy.fs.glsl b/resources/shaders/gl3/tile_copy.fs.glsl new file mode 100644 index 00000000..4d5e2495 --- /dev/null +++ b/resources/shaders/gl3/tile_copy.fs.glsl @@ -0,0 +1,26 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + +precision highp float; + +uniform vec2 uFramebufferSize; +uniform sampler2D uSrc; + +out vec4 oFragColor; + +void main(){ + vec2 texCoord = gl_FragCoord . xy / uFramebufferSize; + oFragColor = texture(uSrc, texCoord); +} + diff --git a/resources/shaders/gl3/tile_copy.vs.glsl b/resources/shaders/gl3/tile_copy.vs.glsl new file mode 100644 index 00000000..3e22e1f2 --- /dev/null +++ b/resources/shaders/gl3/tile_copy.vs.glsl @@ -0,0 +1,26 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + +precision highp float; + +uniform mat4 uTransform; +uniform vec2 uTileSize; + +in ivec2 aTilePosition; + +void main(){ + vec2 position = aTilePosition * uTileSize; + gl_Position = uTransform * vec4(position, 0.0, 1.0); +} + diff --git a/resources/shaders/gl3/tile_solid.fs.glsl b/resources/shaders/gl3/tile_solid.fs.glsl index 214b026c..c4a548a3 100644 --- a/resources/shaders/gl3/tile_solid.fs.glsl +++ b/resources/shaders/gl3/tile_solid.fs.glsl @@ -15,7 +15,6 @@ precision highp float; uniform sampler2D uPaintTexture; -uniform vec2 uPaintTextureSize; in vec2 vColorTexCoord; diff --git a/resources/shaders/metal/tile_alpha_hsl.fs.metal b/resources/shaders/metal/tile_alpha_hsl.fs.metal new file mode 100644 index 00000000..473c598b --- /dev/null +++ b/resources/shaders/metal/tile_alpha_hsl.fs.metal @@ -0,0 +1,127 @@ +// Automatically generated from files in pathfinder/shaders/. Do not edit! +#pragma clang diagnostic ignored "-Wmissing-prototypes" + +#include +#include + +using namespace metal; + +struct spvDescriptorSetBuffer0 +{ + texture2d uStencilTexture [[id(0)]]; + sampler uStencilTextureSmplr [[id(1)]]; + texture2d uPaintTexture [[id(2)]]; + sampler uPaintTextureSmplr [[id(3)]]; + constant float2* uFramebufferSize [[id(4)]]; + texture2d uDest [[id(5)]]; + sampler uDestSmplr [[id(6)]]; + constant int3* uBlendHSL [[id(7)]]; +}; + +struct main0_out +{ + float4 oFragColor [[color(0)]]; +}; + +struct main0_in +{ + float2 vColorTexCoord [[user(locn0)]]; + float2 vMaskTexCoord [[user(locn1)]]; +}; + +// Implementation of the GLSL mod() function, which is slightly different than Metal fmod() +template +Tx mod(Tx x, Ty y) +{ + return x - y * floor(x / y); +} + +float3 convertRGBToHSL(thread const float3& rgb) +{ + float v = fast::max(rgb.y, rgb.z); + float c = v - fast::min(rgb.y, rgb.z); + float l = v - (0.5 * c); + float3 tmp = float3(0.0); + bool3 is_v = rgb == float3(v); + if (is_v.x) + { + tmp = float3(0.0, rgb.yz); + } + else + { + if (is_v.y) + { + tmp = float3(2.0, rgb.zx); + } + else + { + if (is_v.z) + { + tmp = float3(4.0, rgb.xy); + } + } + } + float h = 1.0471975803375244140625 * (tmp.x + ((tmp.y - tmp.z) / c)); + float s = 0.0; + if ((l > 0.0) && (l < 1.0)) + { + s = (v - l) / fast::min(l, 1.0 - l); + } + return float3(h, s, l); +} + +float3 convertHSLToRGB(thread const float3& hsl) +{ + float a = hsl.y * fast::min(hsl.z, 1.0 - hsl.z); + float3 ks = mod(float3(0.0, 8.0, 4.0) + float3(hsl.x * 1.90985929965972900390625), float3(12.0)); + return hsl.zzz - (fast::clamp(fast::min(ks - float3(3.0), float3(9.0) - ks), float3(-1.0), float3(1.0)) * a); +} + +fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], float4 gl_FragCoord [[position]]) +{ + main0_out out = {}; + float coverage = spvDescriptorSet0.uStencilTexture.sample(spvDescriptorSet0.uStencilTextureSmplr, in.vMaskTexCoord).x; + float4 srcRGBA = spvDescriptorSet0.uPaintTexture.sample(spvDescriptorSet0.uPaintTextureSmplr, in.vColorTexCoord); + srcRGBA.w *= coverage; + float2 destTexCoord = gl_FragCoord.xy / (*spvDescriptorSet0.uFramebufferSize); + float4 destRGBA = spvDescriptorSet0.uDest.sample(spvDescriptorSet0.uDestSmplr, destTexCoord); + float3 param = destRGBA.xyz; + float3 destHSL = convertRGBToHSL(param); + float3 param_1 = srcRGBA.xyz; + float3 srcHSL = convertRGBToHSL(param_1); + bool3 blendDest = (*spvDescriptorSet0.uBlendHSL) == int3(0); + float _225; + if (blendDest.x) + { + _225 = destHSL.x; + } + else + { + _225 = srcHSL.x; + } + float _236; + if (blendDest.y) + { + _236 = destHSL.y; + } + else + { + _236 = srcHSL.y; + } + float _247; + if (blendDest.z) + { + _247 = destHSL.z; + } + else + { + _247 = srcHSL.z; + } + float3 blendedHSL = float3(_225, _236, _247); + float3 param_2 = blendedHSL; + float3 blendedRGB = convertHSLToRGB(param_2); + float4 color = float4(((srcRGBA.xyz * (srcRGBA.w * (1.0 - destRGBA.w))) + (blendedRGB * (srcRGBA.w * destRGBA.w))) + (destRGBA.xyz * ((1.0 - srcRGBA.w) * destRGBA.w)), 1.0); + out.oFragColor = color; + return out; +} + diff --git a/resources/shaders/metal/tile_copy.fs.metal b/resources/shaders/metal/tile_copy.fs.metal new file mode 100644 index 00000000..fac13539 --- /dev/null +++ b/resources/shaders/metal/tile_copy.fs.metal @@ -0,0 +1,26 @@ +// Automatically generated from files in pathfinder/shaders/. Do not edit! +#include +#include + +using namespace metal; + +struct spvDescriptorSetBuffer0 +{ + constant float2* uFramebufferSize [[id(0)]]; + texture2d uSrc [[id(1)]]; + sampler uSrcSmplr [[id(2)]]; +}; + +struct main0_out +{ + float4 oFragColor [[color(0)]]; +}; + +fragment main0_out main0(constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], float4 gl_FragCoord [[position]]) +{ + main0_out out = {}; + float2 texCoord = gl_FragCoord.xy / (*spvDescriptorSet0.uFramebufferSize); + out.oFragColor = spvDescriptorSet0.uSrc.sample(spvDescriptorSet0.uSrcSmplr, texCoord); + return out; +} + diff --git a/resources/shaders/metal/tile_copy.vs.metal b/resources/shaders/metal/tile_copy.vs.metal new file mode 100644 index 00000000..530c42d7 --- /dev/null +++ b/resources/shaders/metal/tile_copy.vs.metal @@ -0,0 +1,30 @@ +// Automatically generated from files in pathfinder/shaders/. Do not edit! +#include +#include + +using namespace metal; + +struct spvDescriptorSetBuffer0 +{ + constant float2* uTileSize [[id(0)]]; + constant float4x4* uTransform [[id(1)]]; +}; + +struct main0_out +{ + float4 gl_Position [[position]]; +}; + +struct main0_in +{ + int2 aTilePosition [[attribute(0)]]; +}; + +vertex main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]]) +{ + main0_out out = {}; + float2 position = float2(in.aTilePosition) * (*spvDescriptorSet0.uTileSize); + out.gl_Position = (*spvDescriptorSet0.uTransform) * float4(position, 0.0, 1.0); + return out; +} + diff --git a/shaders/Makefile b/shaders/Makefile index c03cf84b..68c1b1fe 100644 --- a/shaders/Makefile +++ b/shaders/Makefile @@ -23,6 +23,9 @@ SHADERS=\ stencil.vs.glsl \ tile_alpha.fs.glsl \ tile_alpha.vs.glsl \ + tile_alpha_hsl.fs.glsl \ + tile_copy.fs.glsl \ + tile_copy.vs.glsl \ tile_solid.fs.glsl \ tile_solid.vs.glsl \ $(EMPTY) diff --git a/shaders/filter_basic.fs.glsl b/shaders/filter_basic.fs.glsl index 635e5784..ef776b5d 100644 --- a/shaders/filter_basic.fs.glsl +++ b/shaders/filter_basic.fs.glsl @@ -18,7 +18,6 @@ precision highp float; uniform sampler2D uSource; -uniform vec2 uSourceSize; in vec2 vTexCoord; diff --git a/shaders/tile_alpha.fs.glsl b/shaders/tile_alpha.fs.glsl index f21d53be..5ad53198 100644 --- a/shaders/tile_alpha.fs.glsl +++ b/shaders/tile_alpha.fs.glsl @@ -14,11 +14,10 @@ precision highp float; uniform sampler2D uStencilTexture; uniform sampler2D uPaintTexture; -uniform vec2 uPaintTextureSize; +uniform vec2 uFramebufferSize; in vec2 vColorTexCoord; in vec2 vMaskTexCoord; -in vec4 vColor; out vec4 oFragColor; diff --git a/shaders/tile_alpha.vs.glsl b/shaders/tile_alpha.vs.glsl index 4dc64704..e1b2d31e 100644 --- a/shaders/tile_alpha.vs.glsl +++ b/shaders/tile_alpha.vs.glsl @@ -14,7 +14,6 @@ precision highp float; uniform mat4 uTransform; uniform vec2 uTileSize; -uniform vec2 uStencilTextureSize; in ivec2 aTilePosition; in vec2 aColorTexCoord; diff --git a/shaders/tile_alpha_hsl.fs.glsl b/shaders/tile_alpha_hsl.fs.glsl new file mode 100644 index 00000000..2217e6ff --- /dev/null +++ b/shaders/tile_alpha_hsl.fs.glsl @@ -0,0 +1,87 @@ +#version 330 + +// pathfinder/shaders/tile_alpha_hsl.fs.glsl +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#extension GL_GOOGLE_include_directive : enable + +#define BLEND_TERM_DEST 0 +#define BLEND_TERM_SRC 1 + +precision highp float; + +uniform sampler2D uStencilTexture; +uniform sampler2D uPaintTexture; +uniform sampler2D uDest; +uniform ivec3 uBlendHSL; +uniform vec2 uFramebufferSize; + +in vec2 vColorTexCoord; +in vec2 vMaskTexCoord; + +out vec4 oFragColor; + +#define PI_2 6.283185307179586 +#define DEG_30_INV 1.9098593171027443 +#define DEG_60 1.0471975511965976 + +// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative +vec3 convertHSLToRGB(vec3 hsl) { + float a = hsl.y * min(hsl.z, 1.0 - hsl.z); + vec3 ks = mod(vec3(0.0, 8.0, 4.0) + vec3(hsl.x * DEG_30_INV), 12.0); + return hsl.zzz - clamp(min(ks - vec3(3.0), vec3(9.0) - ks), -1.0, 1.0) * a; +} + +// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB +vec3 convertRGBToHSL(vec3 rgb) { + float v = max((rgb.x, rgb.y), rgb.z); + float c = v - min((rgb.x, rgb.y), rgb.z); + float l = v - 0.5 * c; + + vec3 tmp = vec3(0.0); + bvec3 is_v = equal(rgb, vec3(v)); + if (is_v.r) + tmp = vec3(0.0, rgb.gb); + else if (is_v.g) + tmp = vec3(2.0, rgb.br); + else if (is_v.b) + tmp = vec3(4.0, rgb.rg); + float h = DEG_60 * (tmp.x + (tmp.y - tmp.z) / c); + + float s = 0.0; + if (l > 0.0 && l < 1.0) + s = (v - l) / min(l, 1.0 - l); + + return vec3(h, s, l); +} + +void main() { + float coverage = texture(uStencilTexture, vMaskTexCoord).r; + vec4 srcRGBA = texture(uPaintTexture, vColorTexCoord); + srcRGBA.a *= coverage; + + vec2 destTexCoord = gl_FragCoord.xy / uFramebufferSize; + vec4 destRGBA = texture(uDest, destTexCoord); + + vec3 destHSL = convertRGBToHSL(destRGBA.rgb); + vec3 srcHSL = convertRGBToHSL(srcRGBA.rgb); + bvec3 blendDest = equal(uBlendHSL, ivec3(BLEND_TERM_DEST)); + vec3 blendedHSL = vec3(blendDest.x ? destHSL.x : srcHSL.x, + blendDest.y ? destHSL.y : srcHSL.y, + blendDest.z ? destHSL.z : srcHSL.z); + vec3 blendedRGB = convertHSLToRGB(blendedHSL); + + // FIXME(pcwalton): What should the output alpha be here? + vec4 color = vec4(srcRGBA.a * (1.0 - destRGBA.a) * srcRGBA.rgb + + srcRGBA.a * destRGBA.a * blendedRGB + + (1.0 - srcRGBA.a) * destRGBA.a * destRGBA.rgb, + 1.0); + oFragColor = color; +} diff --git a/shaders/tile_copy.fs.glsl b/shaders/tile_copy.fs.glsl new file mode 100644 index 00000000..5d69ce32 --- /dev/null +++ b/shaders/tile_copy.fs.glsl @@ -0,0 +1,23 @@ +#version 330 + +// pathfinder/shaders/tile_copy.fs.glsl +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +uniform vec2 uFramebufferSize; +uniform sampler2D uSrc; + +out vec4 oFragColor; + +void main() { + vec2 texCoord = gl_FragCoord.xy / uFramebufferSize; + oFragColor = texture(uSrc, texCoord); +} diff --git a/shaders/tile_copy.vs.glsl b/shaders/tile_copy.vs.glsl new file mode 100644 index 00000000..08243b0c --- /dev/null +++ b/shaders/tile_copy.vs.glsl @@ -0,0 +1,23 @@ +#version 330 + +// pathfinder/shaders/tile_copy.vs.glsl +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +precision highp float; + +uniform mat4 uTransform; +uniform vec2 uTileSize; + +in ivec2 aTilePosition; + +void main() { + vec2 position = aTilePosition * uTileSize; + gl_Position = uTransform * vec4(position, 0.0, 1.0); +} diff --git a/shaders/tile_solid.fs.glsl b/shaders/tile_solid.fs.glsl index 1c75b97f..724b68eb 100644 --- a/shaders/tile_solid.fs.glsl +++ b/shaders/tile_solid.fs.glsl @@ -13,7 +13,6 @@ precision highp float; uniform sampler2D uPaintTexture; -uniform vec2 uPaintTextureSize; in vec2 vColorTexCoord;