diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 52620d3a..b7975f26 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -529,8 +529,12 @@ pub enum CompositeOperation { SourceAtop, Xor, Lighter, - Lighten, + Multiply, + Screen, + Overlay, Darken, + Lighten, + HardLight, Hue, Saturation, Color, @@ -546,8 +550,12 @@ impl CompositeOperation { CompositeOperation::SourceAtop => BlendMode::SrcAtop, CompositeOperation::Xor => BlendMode::Xor, CompositeOperation::Lighter => BlendMode::Lighter, - CompositeOperation::Lighten => BlendMode::Lighten, + CompositeOperation::Multiply => BlendMode::Multiply, + CompositeOperation::Screen => BlendMode::Screen, + CompositeOperation::Overlay => BlendMode::Overlay, CompositeOperation::Darken => BlendMode::Darken, + CompositeOperation::Lighten => BlendMode::Lighten, + CompositeOperation::HardLight => BlendMode::HardLight, CompositeOperation::Hue => BlendMode::Hue, CompositeOperation::Saturation => BlendMode::Saturation, CompositeOperation::Color => BlendMode::Color, diff --git a/content/src/effects.rs b/content/src/effects.rs index a2e50b9e..394a0e86 100644 --- a/content/src/effects.rs +++ b/content/src/effects.rs @@ -64,6 +64,7 @@ pub enum CompositeOp { /// Blend modes that can be applied to individual paths. #[derive(Clone, Copy, PartialEq, Debug)] pub enum BlendMode { + // Supported by GPU blender Clear, SrcOver, DestOver, @@ -73,6 +74,14 @@ pub enum BlendMode { Lighter, Lighten, Darken, + + // Overlay + Multiply, + Screen, + HardLight, + Overlay, + + // HSL Hue, Saturation, Color, @@ -109,6 +118,10 @@ impl BlendMode { BlendMode::Xor | BlendMode::Lighter | BlendMode::Lighten | + BlendMode::Multiply | + BlendMode::Screen | + BlendMode::HardLight | + BlendMode::Overlay | BlendMode::Darken | BlendMode::Hue | BlendMode::Saturation | diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index b24b64e9..b17c7500 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -10,12 +10,12 @@ use crate::gpu::debug::DebugUIPresenter; use crate::gpu::options::{DestFramebuffer, RendererOptions}; -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}; -use crate::gpu::shaders::{SolidTileProgram, SolidTileVertexArray}; +use crate::gpu::shaders::{AlphaTileHSLProgram, AlphaTileOverlayProgram, AlphaTileProgram}; +use crate::gpu::shaders::{AlphaTileVertexArray, CopyTileProgram, CopyTileVertexArray, FillProgram}; +use crate::gpu::shaders::{FillVertexArray, FilterBasicProgram, FilterBasicVertexArray}; +use crate::gpu::shaders::{FilterTextProgram, FilterTextVertexArray, MAX_FILLS_PER_BATCH}; +use crate::gpu::shaders::{MaskTileProgram, MaskTileVertexArray, ReprojectionProgram}; +use crate::gpu::shaders::{ReprojectionVertexArray, SolidTileProgram, SolidTileVertexArray}; use crate::gpu::shaders::{StencilProgram, StencilVertexArray}; use crate::gpu_data::{AlphaTile, FillBatchPrimitive, MaskTile, PaintData, PaintPageContents}; use crate::gpu_data::{PaintPageId, RenderCommand, SolidTileVertex}; @@ -50,11 +50,16 @@ pub(crate) const MASK_TILES_DOWN: u32 = 256; const TEXTURE_CACHE_SIZE: usize = 8; // FIXME(pcwalton): Shrink this again! -const MASK_FRAMEBUFFER_WIDTH: i32 = TILE_WIDTH as i32 * MASK_TILES_ACROSS as i32; +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; +const BLEND_TERM_DEST: i32 = 0; +const BLEND_TERM_SRC: i32 = 1; + +const OVERLAY_BLEND_MODE_MULTIPLY: i32 = 0; +const OVERLAY_BLEND_MODE_SCREEN: i32 = 1; +const OVERLAY_BLEND_MODE_HARD_LIGHT: i32 = 2; +const OVERLAY_BLEND_MODE_OVERLAY: i32 = 3; pub struct Renderer where @@ -72,12 +77,14 @@ where copy_tile_program: CopyTileProgram, solid_tile_program: SolidTileProgram, alpha_tile_program: AlphaTileProgram, + alpha_tile_overlay_program: AlphaTileOverlayProgram, 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_overlay_vertex_array: AlphaTileVertexArray, alpha_tile_hsl_vertex_array: AlphaTileVertexArray, area_lut_texture: D::Texture, alpha_tile_vertex_buffer: D::Buffer, @@ -149,6 +156,7 @@ where 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_overlay_program = AlphaTileOverlayProgram::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); @@ -203,6 +211,12 @@ where &alpha_tile_vertex_buffer, &quads_vertex_indices_buffer, ); + let alpha_tile_overlay_vertex_array = AlphaTileVertexArray::new( + &device, + &alpha_tile_overlay_program.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, @@ -270,12 +284,14 @@ where copy_tile_program, solid_tile_program, alpha_tile_program, + alpha_tile_overlay_program, alpha_tile_hsl_program, mask_winding_tile_vertex_array, mask_evenodd_tile_vertex_array, + copy_tile_vertex_array, solid_tile_vertex_array, alpha_tile_vertex_array, - copy_tile_vertex_array, + alpha_tile_overlay_vertex_array, alpha_tile_hsl_vertex_array, area_lut_texture, alpha_tile_vertex_buffer, @@ -708,6 +724,10 @@ where let (alpha_tile_program, alpha_tile_vertex_array) = match blend_mode_program { BlendModeProgram::Regular => (&self.alpha_tile_program, &self.alpha_tile_vertex_array), + BlendModeProgram::Overlay => { + (&self.alpha_tile_overlay_program.alpha_tile_program, + &self.alpha_tile_overlay_vertex_array) + } BlendModeProgram::HSL => { (&self.alpha_tile_hsl_program.alpha_tile_program, &self.alpha_tile_hsl_vertex_array) @@ -742,6 +762,9 @@ where match blend_mode_program { BlendModeProgram::Regular => {} + BlendModeProgram::Overlay => { + self.set_uniforms_for_overlay_blend_mode(&mut textures, &mut uniforms, blend_mode); + } BlendModeProgram::HSL => { self.set_uniforms_for_hsl_blend_mode(&mut textures, &mut uniforms, blend_mode); } @@ -766,6 +789,26 @@ where self.preserve_draw_framebuffer(); } + fn set_uniforms_for_overlay_blend_mode<'a>(&'a self, + textures: &mut Vec<&'a D::Texture>, + uniforms: &mut Vec<(&'a D::Uniform, UniformData)>, + blend_mode: BlendMode) { + let overlay_blend_mode = match blend_mode { + BlendMode::Multiply => OVERLAY_BLEND_MODE_MULTIPLY, + BlendMode::Screen => OVERLAY_BLEND_MODE_SCREEN, + BlendMode::HardLight => OVERLAY_BLEND_MODE_HARD_LIGHT, + BlendMode::Overlay => OVERLAY_BLEND_MODE_OVERLAY, + _ => unreachable!(), + }; + + uniforms.push((&self.alpha_tile_overlay_program.blend_mode_uniform, + UniformData::Int(overlay_blend_mode))); + + textures.push(self.device.framebuffer_texture(&self.dest_blend_framebuffer)); + uniforms.push((&self.alpha_tile_overlay_program.dest_uniform, + UniformData::TextureUnit(textures.len() as u32 - 1))); + } + fn set_uniforms_for_hsl_blend_mode<'a>(&'a self, textures: &mut Vec<&'a D::Texture>, uniforms: &mut Vec<(&'a D::Uniform, UniformData)>, @@ -778,19 +821,25 @@ where _ => 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))); + + textures.push(self.device.framebuffer_texture(&self.dest_blend_framebuffer)); + uniforms.push((&self.alpha_tile_hsl_program.dest_uniform, + UniformData::TextureUnit(textures.len() as u32 - 1))); } fn copy_alpha_tiles_to_dest_blend_texture(&mut self, tile_count: u32) { + let draw_viewport = self.draw_viewport(); + 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))), + (&self.copy_tile_program.framebuffer_size_uniform, + UniformData::Vec2(draw_viewport.size().to_f32().0)), ]; let draw_framebuffer = match self.draw_render_target() { @@ -809,7 +858,7 @@ where primitive: Primitive::Triangles, textures: &textures, uniforms: &uniforms, - viewport: self.draw_viewport(), + viewport: draw_viewport, options: RenderOptions { clear_ops: ClearOps { color: Some(ColorF::transparent_black()), @@ -1395,6 +1444,10 @@ impl BlendModeExt for BlendMode { op: BlendOp::Min, }) } + BlendMode::Multiply | + BlendMode::Screen | + BlendMode::HardLight | + BlendMode::Overlay | BlendMode::Hue | BlendMode::Saturation | BlendMode::Color | @@ -1409,6 +1462,7 @@ impl BlendModeExt for BlendMode { #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum BlendModeProgram { Regular, + Overlay, HSL, } @@ -1424,6 +1478,10 @@ impl BlendModeProgram { BlendMode::Lighter | BlendMode::Lighten | BlendMode::Darken => BlendModeProgram::Regular, + BlendMode::Multiply | + BlendMode::Screen | + BlendMode::HardLight | + BlendMode::Overlay => BlendModeProgram::Overlay, BlendMode::Hue | BlendMode::Saturation | BlendMode::Color | @@ -1434,7 +1492,7 @@ impl BlendModeProgram { pub(crate) fn needs_readable_framebuffer(self) -> bool { match self { BlendModeProgram::Regular => false, - BlendModeProgram::HSL => true, + BlendModeProgram::Overlay | BlendModeProgram::HSL => true, } } } diff --git a/renderer/src/gpu/shaders.rs b/renderer/src/gpu/shaders.rs index 4f42bda1..838be97b 100644 --- a/renderer/src/gpu/shaders.rs +++ b/renderer/src/gpu/shaders.rs @@ -429,6 +429,7 @@ pub struct CopyTileProgram 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 src_uniform: D::Uniform, } @@ -437,8 +438,15 @@ impl CopyTileProgram where D: Device { 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 framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); let src_uniform = device.get_uniform(&program, "Src"); - CopyTileProgram { program, transform_uniform, tile_size_uniform, src_uniform } + CopyTileProgram { + program, + transform_uniform, + tile_size_uniform, + framebuffer_size_uniform, + src_uniform, + } } } @@ -459,6 +467,23 @@ impl AlphaTileHSLProgram where D: Device { } } +pub struct AlphaTileOverlayProgram where D: Device { + pub alpha_tile_program: AlphaTileProgram, + pub dest_uniform: D::Uniform, + pub blend_mode_uniform: D::Uniform, +} + +impl AlphaTileOverlayProgram where D: Device { + pub fn new(device: &D, resources: &dyn ResourceLoader) -> AlphaTileOverlayProgram { + let alpha_tile_program = AlphaTileProgram::from_fragment_shader_name(device, + resources, + "tile_alpha_overlay"); + let dest_uniform = device.get_uniform(&alpha_tile_program.program, "Dest"); + let blend_mode_uniform = device.get_uniform(&alpha_tile_program.program, "BlendMode"); + AlphaTileOverlayProgram { alpha_tile_program, dest_uniform, blend_mode_uniform } + } +} + pub struct FilterBasicProgram where D: Device { pub program: D::Program, pub source_uniform: D::Uniform, diff --git a/resources/shaders/gl3/tile_alpha_overlay.fs.glsl b/resources/shaders/gl3/tile_alpha_overlay.fs.glsl new file mode 100644 index 00000000..5c49a17d --- /dev/null +++ b/resources/shaders/gl3/tile_alpha_overlay.fs.glsl @@ -0,0 +1,96 @@ +#version {{version}} +// Automatically generated from files in pathfinder/shaders/. Do not edit! + + + + + + + + + + + + + + +#extension GL_GOOGLE_include_directive : enable + + + + + + +precision highp float; + +uniform int uBlendMode; + +out vec4 oFragColor; + + + + + + + + + + + + +uniform sampler2D uStencilTexture; +uniform sampler2D uPaintTexture; +uniform sampler2D uDest; +uniform vec2 uFramebufferSize; + +in vec2 vColorTexCoord; +in vec2 vMaskTexCoord; + + +vec4 sampleSrcColor(){ + float coverage = texture(uStencilTexture, vMaskTexCoord). r; + vec4 srcRGBA = texture(uPaintTexture, vColorTexCoord); + return vec4(srcRGBA . rgb, srcRGBA . a * coverage); +} + +vec4 sampleDestColor(){ + vec2 destTexCoord = gl_FragCoord . xy / uFramebufferSize; + return texture(uDest, destTexCoord); +} + + +vec4 blendColors(vec4 destRGBA, vec4 srcRGBA, vec3 blendedRGB){ + return vec4(srcRGBA . a *(1.0 - destRGBA . a)* srcRGBA . rgb + + srcRGBA . a * destRGBA . a * blendedRGB + + (1.0 - srcRGBA . a)* destRGBA . a * destRGBA . rgb, + 1.0); +} + +vec3 select3(bvec3 cond, vec3 a, vec3 b){ + return vec3(cond . x ? a . x : b . x, cond . y ? a . y : b . y, cond . z ? a . z : b . z); +} + + +void main(){ + vec4 srcRGBA = sampleSrcColor(); + vec4 destRGBA = sampleDestColor(); + + bool reversed = uBlendMode == 3; + vec3 src = reversed ? srcRGBA . rgb : destRGBA . rgb; + vec3 dest = reversed ? destRGBA . rgb : srcRGBA . rgb; + + vec3 multiply = src * dest; + vec3 blended; + if(uBlendMode == 0){ + blended = multiply; + } else { + vec3 screen = dest + src - multiply; + if(uBlendMode == 1) + blended = screen; + else + blended = select3(lessThanEqual(src, vec3(0.5)), multiply, screen * 2.0 - 1.0); + } + + oFragColor = blendColors(destRGBA, srcRGBA, blended); +} + diff --git a/resources/shaders/metal/tile_alpha_overlay.fs.metal b/resources/shaders/metal/tile_alpha_overlay.fs.metal new file mode 100644 index 00000000..689487e0 --- /dev/null +++ b/resources/shaders/metal/tile_alpha_overlay.fs.metal @@ -0,0 +1,135 @@ +// 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 int* uBlendMode [[id(7)]]; +}; + +struct main0_out +{ + float4 oFragColor [[color(0)]]; +}; + +struct main0_in +{ + float2 vColorTexCoord [[user(locn0)]]; + float2 vMaskTexCoord [[user(locn1)]]; +}; + +float4 sampleSrcColor(thread texture2d uStencilTexture, thread const sampler uStencilTextureSmplr, thread float2& vMaskTexCoord, thread texture2d uPaintTexture, thread const sampler uPaintTextureSmplr, thread float2& vColorTexCoord) +{ + float coverage = uStencilTexture.sample(uStencilTextureSmplr, vMaskTexCoord).x; + float4 srcRGBA = uPaintTexture.sample(uPaintTextureSmplr, vColorTexCoord); + return float4(srcRGBA.xyz, srcRGBA.w * coverage); +} + +float4 sampleDestColor(thread float4& gl_FragCoord, thread float2 uFramebufferSize, thread texture2d uDest, thread const sampler uDestSmplr) +{ + float2 destTexCoord = gl_FragCoord.xy / uFramebufferSize; + return uDest.sample(uDestSmplr, destTexCoord); +} + +float3 select3(thread const bool3& cond, thread const float3& a, thread const float3& b) +{ + float _118; + if (cond.x) + { + _118 = a.x; + } + else + { + _118 = b.x; + } + float _130; + if (cond.y) + { + _130 = a.y; + } + else + { + _130 = b.y; + } + float _142; + if (cond.z) + { + _142 = a.z; + } + else + { + _142 = b.z; + } + return float3(_118, _130, _142); +} + +float4 blendColors(thread const float4& destRGBA, thread const float4& srcRGBA, thread const float3& blendedRGB) +{ + return float4(((srcRGBA.xyz * (srcRGBA.w * (1.0 - destRGBA.w))) + (blendedRGB * (srcRGBA.w * destRGBA.w))) + (destRGBA.xyz * ((1.0 - srcRGBA.w) * destRGBA.w)), 1.0); +} + +fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]], float4 gl_FragCoord [[position]]) +{ + main0_out out = {}; + float4 srcRGBA = sampleSrcColor(spvDescriptorSet0.uStencilTexture, spvDescriptorSet0.uStencilTextureSmplr, in.vMaskTexCoord, spvDescriptorSet0.uPaintTexture, spvDescriptorSet0.uPaintTextureSmplr, in.vColorTexCoord); + float4 destRGBA = sampleDestColor(gl_FragCoord, (*spvDescriptorSet0.uFramebufferSize), spvDescriptorSet0.uDest, spvDescriptorSet0.uDestSmplr); + bool reversed = (*spvDescriptorSet0.uBlendMode) == 3; + float3 _167; + if (reversed) + { + _167 = srcRGBA.xyz; + } + else + { + _167 = destRGBA.xyz; + } + float3 src = _167; + float3 _178; + if (reversed) + { + _178 = destRGBA.xyz; + } + else + { + _178 = srcRGBA.xyz; + } + float3 dest = _178; + float3 multiply = src * dest; + float3 blended; + if ((*spvDescriptorSet0.uBlendMode) == 0) + { + blended = multiply; + } + else + { + float3 screen = (dest + src) - multiply; + if ((*spvDescriptorSet0.uBlendMode) == 1) + { + blended = screen; + } + else + { + bool3 param = src <= float3(0.5); + float3 param_1 = multiply; + float3 param_2 = (screen * 2.0) - float3(1.0); + blended = select3(param, param_1, param_2); + } + } + float4 param_3 = destRGBA; + float4 param_4 = srcRGBA; + float3 param_5 = blended; + out.oFragColor = blendColors(param_3, param_4, param_5); + return out; +} + diff --git a/shaders/Makefile b/shaders/Makefile index 4a27276c..a842b02f 100644 --- a/shaders/Makefile +++ b/shaders/Makefile @@ -24,6 +24,7 @@ SHADERS=\ tile_alpha.fs.glsl \ tile_alpha.vs.glsl \ tile_alpha_hsl.fs.glsl \ + tile_alpha_overlay.fs.glsl \ tile_copy.fs.glsl \ tile_copy.vs.glsl \ tile_solid.fs.glsl \ diff --git a/shaders/tile_alpha_overlay.fs.glsl b/shaders/tile_alpha_overlay.fs.glsl new file mode 100644 index 00000000..eb8e6cd3 --- /dev/null +++ b/shaders/tile_alpha_overlay.fs.glsl @@ -0,0 +1,51 @@ +#version 330 + +// pathfinder/shaders/tile_alpha_overlay.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. + +// Multiply, screen, overlay, and hard light filters. + +#extension GL_GOOGLE_include_directive : enable + +#define OVERLAY_BLEND_MODE_MULTIPLY 0 +#define OVERLAY_BLEND_MODE_SCREEN 1 +#define OVERLAY_BLEND_MODE_HARD_LIGHT 2 +#define OVERLAY_BLEND_MODE_OVERLAY 3 + +precision highp float; + +uniform int uBlendMode; + +out vec4 oFragColor; + +#include "tile_alpha_sample.inc.glsl" + +void main() { + vec4 srcRGBA = sampleSrcColor(); + vec4 destRGBA = sampleDestColor(); + + bool reversed = uBlendMode == OVERLAY_BLEND_MODE_OVERLAY; + vec3 src = reversed ? srcRGBA.rgb : destRGBA.rgb; + vec3 dest = reversed ? destRGBA.rgb : srcRGBA.rgb; + + vec3 multiply = src * dest; + vec3 blended; + if (uBlendMode == OVERLAY_BLEND_MODE_MULTIPLY) { + blended = multiply; + } else { + vec3 screen = dest + src - multiply; + if (uBlendMode == OVERLAY_BLEND_MODE_SCREEN) + blended = screen; + else + blended = select3(lessThanEqual(src, vec3(0.5)), multiply, screen * 2.0 - 1.0); + } + + oFragColor = blendColors(destRGBA, srcRGBA, blended); +}