615 lines
24 KiB
GLSL
615 lines
24 KiB
GLSL
// pathfinder/shaders/tile_fragment.inc.glsl
|
||
//
|
||
// Copyright © 2020 The Pathfinder Project Developers.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||
// option. This file may not be copied, modified, or distributed
|
||
// except according to those terms.
|
||
|
||
// Mask UV 0 Mask UV 1
|
||
// + +
|
||
// | |
|
||
// +-----v-----+ +-----v-----+
|
||
// | | MIN | |
|
||
// | Mask 0 +-----> Mask 1 +------+
|
||
// | | | | |
|
||
// +-----------+ +-----------+ v +-------------+
|
||
// Apply | | GPU
|
||
// Mask +----> Composite +---->Blender
|
||
// ^ | |
|
||
// +-----------+ +-----------+ | +-------------+
|
||
// | | | | |
|
||
// | Color 0 +-----> Color 1 +------+
|
||
// | Filter | × | |
|
||
// | | | |
|
||
// +-----^-----+ +-----^-----+
|
||
// | |
|
||
// + +
|
||
// Color UV 0 Color UV 1
|
||
|
||
#define FRAC_6_PI 1.9098593171027443
|
||
#define FRAC_PI_3 1.0471975511965976
|
||
|
||
#define TILE_CTRL_MASK_MASK 0x3
|
||
#define TILE_CTRL_MASK_WINDING 0x1
|
||
#define TILE_CTRL_MASK_EVEN_ODD 0x2
|
||
|
||
#define TILE_CTRL_MASK_0_SHIFT 0
|
||
|
||
#define COMBINER_CTRL_COLOR_COMBINE_MASK 0x3
|
||
#define COMBINER_CTRL_COLOR_COMBINE_SRC_IN 0x1
|
||
#define COMBINER_CTRL_COLOR_COMBINE_DEST_IN 0x2
|
||
|
||
#define COMBINER_CTRL_FILTER_MASK 0xf
|
||
#define COMBINER_CTRL_FILTER_RADIAL_GRADIENT 0x1
|
||
#define COMBINER_CTRL_FILTER_TEXT 0x2
|
||
#define COMBINER_CTRL_FILTER_BLUR 0x3
|
||
#define COMBINER_CTRL_FILTER_COLOR_MATRIX 0x4
|
||
|
||
#define COMBINER_CTRL_COMPOSITE_MASK 0xf
|
||
#define COMBINER_CTRL_COMPOSITE_NORMAL 0x0
|
||
#define COMBINER_CTRL_COMPOSITE_MULTIPLY 0x1
|
||
#define COMBINER_CTRL_COMPOSITE_SCREEN 0x2
|
||
#define COMBINER_CTRL_COMPOSITE_OVERLAY 0x3
|
||
#define COMBINER_CTRL_COMPOSITE_DARKEN 0x4
|
||
#define COMBINER_CTRL_COMPOSITE_LIGHTEN 0x5
|
||
#define COMBINER_CTRL_COMPOSITE_COLOR_DODGE 0x6
|
||
#define COMBINER_CTRL_COMPOSITE_COLOR_BURN 0x7
|
||
#define COMBINER_CTRL_COMPOSITE_HARD_LIGHT 0x8
|
||
#define COMBINER_CTRL_COMPOSITE_SOFT_LIGHT 0x9
|
||
#define COMBINER_CTRL_COMPOSITE_DIFFERENCE 0xa
|
||
#define COMBINER_CTRL_COMPOSITE_EXCLUSION 0xb
|
||
#define COMBINER_CTRL_COMPOSITE_HUE 0xc
|
||
#define COMBINER_CTRL_COMPOSITE_SATURATION 0xd
|
||
#define COMBINER_CTRL_COMPOSITE_COLOR 0xe
|
||
#define COMBINER_CTRL_COMPOSITE_LUMINOSITY 0xf
|
||
|
||
#define COMBINER_CTRL_COLOR_FILTER_SHIFT 4
|
||
#define COMBINER_CTRL_COLOR_COMBINE_SHIFT 8
|
||
#define COMBINER_CTRL_COMPOSITE_SHIFT 10
|
||
|
||
// Color sampling
|
||
|
||
vec4 sampleColor(sampler2D colorTexture, vec2 colorTexCoord) {
|
||
return texture(colorTexture, colorTexCoord);
|
||
}
|
||
|
||
// Color combining
|
||
|
||
vec4 combineColor0(vec4 destColor, vec4 srcColor, int op) {
|
||
switch (op) {
|
||
case COMBINER_CTRL_COLOR_COMBINE_SRC_IN:
|
||
return vec4(srcColor.rgb, srcColor.a * destColor.a);
|
||
case COMBINER_CTRL_COLOR_COMBINE_DEST_IN:
|
||
return vec4(destColor.rgb, srcColor.a * destColor.a);
|
||
}
|
||
return destColor;
|
||
}
|
||
|
||
// Text filter
|
||
|
||
float filterTextSample1Tap(float offset, sampler2D colorTexture, vec2 colorTexCoord) {
|
||
return texture(colorTexture, colorTexCoord + vec2(offset, 0.0)).r;
|
||
}
|
||
|
||
// Samples 9 taps around the current pixel.
|
||
void filterTextSample9Tap(out vec4 outAlphaLeft,
|
||
out float outAlphaCenter,
|
||
out vec4 outAlphaRight,
|
||
sampler2D colorTexture,
|
||
vec2 colorTexCoord,
|
||
vec4 kernel,
|
||
float onePixel) {
|
||
bool wide = kernel.x > 0.0;
|
||
outAlphaLeft =
|
||
vec4(wide ? filterTextSample1Tap(-4.0 * onePixel, colorTexture, colorTexCoord) : 0.0,
|
||
filterTextSample1Tap(-3.0 * onePixel, colorTexture, colorTexCoord),
|
||
filterTextSample1Tap(-2.0 * onePixel, colorTexture, colorTexCoord),
|
||
filterTextSample1Tap(-1.0 * onePixel, colorTexture, colorTexCoord));
|
||
outAlphaCenter = filterTextSample1Tap(0.0, colorTexture, colorTexCoord);
|
||
outAlphaRight =
|
||
vec4(filterTextSample1Tap(1.0 * onePixel, colorTexture, colorTexCoord),
|
||
filterTextSample1Tap(2.0 * onePixel, colorTexture, colorTexCoord),
|
||
filterTextSample1Tap(3.0 * onePixel, colorTexture, colorTexCoord),
|
||
wide ? filterTextSample1Tap(4.0 * onePixel, colorTexture, colorTexCoord) : 0.0);
|
||
}
|
||
|
||
float filterTextConvolve7Tap(vec4 alpha0, vec3 alpha1, vec4 kernel) {
|
||
return dot(alpha0, kernel) + dot(alpha1, kernel.zyx);
|
||
}
|
||
|
||
float filterTextGammaCorrectChannel(float bgColor, float fgColor, sampler2D gammaLUT) {
|
||
return texture(gammaLUT, vec2(fgColor, 1.0 - bgColor)).r;
|
||
}
|
||
|
||
// `fgColor` is in linear space.
|
||
vec3 filterTextGammaCorrect(vec3 bgColor, vec3 fgColor, sampler2D gammaLUT) {
|
||
return vec3(filterTextGammaCorrectChannel(bgColor.r, fgColor.r, gammaLUT),
|
||
filterTextGammaCorrectChannel(bgColor.g, fgColor.g, gammaLUT),
|
||
filterTextGammaCorrectChannel(bgColor.b, fgColor.b, gammaLUT));
|
||
}
|
||
|
||
// | x y z w
|
||
// --------------+--------------------------------------------------------
|
||
// filterParams0 | kernel[0] kernel[1] kernel[2] kernel[3]
|
||
// filterParams1 | bgColor.r bgColor.g bgColor.b -
|
||
// filterParams2 | fgColor.r fgColor.g fgColor.b gammaCorrectionEnabled
|
||
vec4 filterText(vec2 colorTexCoord,
|
||
sampler2D colorTexture,
|
||
sampler2D gammaLUT,
|
||
vec2 colorTextureSize,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1,
|
||
vec4 filterParams2) {
|
||
// Unpack.
|
||
vec4 kernel = filterParams0;
|
||
vec3 bgColor = filterParams1.rgb;
|
||
vec3 fgColor = filterParams2.rgb;
|
||
bool gammaCorrectionEnabled = filterParams2.a != 0.0;
|
||
|
||
// Apply defringing if necessary.
|
||
vec3 alpha;
|
||
if (kernel.w == 0.0) {
|
||
alpha = texture(colorTexture, colorTexCoord).rrr;
|
||
} else {
|
||
vec4 alphaLeft, alphaRight;
|
||
float alphaCenter;
|
||
filterTextSample9Tap(alphaLeft,
|
||
alphaCenter,
|
||
alphaRight,
|
||
colorTexture,
|
||
colorTexCoord,
|
||
kernel,
|
||
1.0 / colorTextureSize.x);
|
||
|
||
float r = filterTextConvolve7Tap(alphaLeft, vec3(alphaCenter, alphaRight.xy), kernel);
|
||
float g = filterTextConvolve7Tap(vec4(alphaLeft.yzw, alphaCenter), alphaRight.xyz, kernel);
|
||
float b = filterTextConvolve7Tap(vec4(alphaLeft.zw, alphaCenter, alphaRight.x),
|
||
alphaRight.yzw,
|
||
kernel);
|
||
|
||
alpha = vec3(r, g, b);
|
||
}
|
||
|
||
// Apply gamma correction if necessary.
|
||
if (gammaCorrectionEnabled)
|
||
alpha = filterTextGammaCorrect(bgColor, alpha, gammaLUT);
|
||
|
||
// Finish.
|
||
return vec4(mix(bgColor, fgColor, alpha), 1.0);
|
||
}
|
||
|
||
// Other filters
|
||
|
||
// This is based on Pixman (MIT license). Copy and pasting the excellent comment
|
||
// from there:
|
||
|
||
// Implementation of radial gradients following the PDF specification.
|
||
// See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference
|
||
// Manual (PDF 32000-1:2008 at the time of this writing).
|
||
//
|
||
// In the radial gradient problem we are given two circles (c₁,r₁) and
|
||
// (c₂,r₂) that define the gradient itself.
|
||
//
|
||
// Mathematically the gradient can be defined as the family of circles
|
||
//
|
||
// ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂)
|
||
//
|
||
// excluding those circles whose radius would be < 0. When a point
|
||
// belongs to more than one circle, the one with a bigger t is the only
|
||
// one that contributes to its color. When a point does not belong
|
||
// to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0).
|
||
// Further limitations on the range of values for t are imposed when
|
||
// the gradient is not repeated, namely t must belong to [0,1].
|
||
//
|
||
// The graphical result is the same as drawing the valid (radius > 0)
|
||
// circles with increasing t in [-∞, +∞] (or in [0,1] if the gradient
|
||
// is not repeated) using SOURCE operator composition.
|
||
//
|
||
// It looks like a cone pointing towards the viewer if the ending circle
|
||
// is smaller than the starting one, a cone pointing inside the page if
|
||
// the starting circle is the smaller one and like a cylinder if they
|
||
// have the same radius.
|
||
//
|
||
// What we actually do is, given the point whose color we are interested
|
||
// in, compute the t values for that point, solving for t in:
|
||
//
|
||
// length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂
|
||
//
|
||
// Let's rewrite it in a simpler way, by defining some auxiliary
|
||
// variables:
|
||
//
|
||
// cd = c₂ - c₁
|
||
// pd = p - c₁
|
||
// dr = r₂ - r₁
|
||
// length(t·cd - pd) = r₁ + t·dr
|
||
//
|
||
// which actually means
|
||
//
|
||
// hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr
|
||
//
|
||
// or
|
||
//
|
||
// ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr.
|
||
//
|
||
// If we impose (as stated earlier) that r₁ + t·dr ≥ 0, it becomes:
|
||
//
|
||
// (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)²
|
||
//
|
||
// where we can actually expand the squares and solve for t:
|
||
//
|
||
// t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² =
|
||
// = r₁² + 2·r₁·t·dr + t²·dr²
|
||
//
|
||
// (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t +
|
||
// (pdx² + pdy² - r₁²) = 0
|
||
//
|
||
// A = cdx² + cdy² - dr²
|
||
// B = pdx·cdx + pdy·cdy + r₁·dr
|
||
// C = pdx² + pdy² - r₁²
|
||
// At² - 2Bt + C = 0
|
||
//
|
||
// The solutions (unless the equation degenerates because of A = 0) are:
|
||
//
|
||
// t = (B ± ⎷(B² - A·C)) / A
|
||
//
|
||
// The solution we are going to prefer is the bigger one, unless the
|
||
// radius associated to it is negative (or it falls outside the valid t
|
||
// range).
|
||
//
|
||
// Additional observations (useful for optimizations):
|
||
// A does not depend on p
|
||
//
|
||
// A < 0 ⟺ one of the two circles completely contains the other one
|
||
// ⟺ for every p, the radii associated with the two t solutions have
|
||
// opposite sign
|
||
//
|
||
// | x y z w
|
||
// --------------+-----------------------------------------------------
|
||
// filterParams0 | lineFrom.x lineFrom.y lineVector.x lineVector.y
|
||
// filterParams1 | radii.x radii.y uvOrigin.x uvOrigin.y
|
||
// filterParams2 | - - - -
|
||
vec4 filterRadialGradient(vec2 colorTexCoord,
|
||
sampler2D colorTexture,
|
||
vec2 colorTextureSize,
|
||
vec2 fragCoord,
|
||
vec2 framebufferSize,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1) {
|
||
vec2 lineFrom = filterParams0.xy, lineVector = filterParams0.zw;
|
||
vec2 radii = filterParams1.xy, uvOrigin = filterParams1.zw;
|
||
|
||
vec2 dP = colorTexCoord - lineFrom, dC = lineVector;
|
||
float dR = radii.y - radii.x;
|
||
|
||
float a = dot(dC, dC) - dR * dR;
|
||
float b = dot(dP, dC) + radii.x * dR;
|
||
float c = dot(dP, dP) - radii.x * radii.x;
|
||
float discrim = b * b - a * c;
|
||
|
||
vec4 color = vec4(0.0);
|
||
if (discrim != 0.0) {
|
||
vec2 ts = vec2(sqrt(discrim) * vec2(1.0, -1.0) + vec2(b)) / vec2(a);
|
||
if (ts.x > ts.y)
|
||
ts = ts.yx;
|
||
float t = ts.x >= 0.0 ? ts.x : ts.y;
|
||
color = texture(colorTexture, uvOrigin + vec2(t, 0.0));
|
||
}
|
||
|
||
return color;
|
||
}
|
||
|
||
// | x y z w
|
||
// --------------+----------------------------------------------------
|
||
// filterParams0 | srcOffset.x srcOffset.y support -
|
||
// filterParams1 | gaussCoeff.x gaussCoeff.y gaussCoeff.z -
|
||
// filterParams2 | - - - -
|
||
vec4 filterBlur(vec2 colorTexCoord,
|
||
sampler2D colorTexture,
|
||
vec2 colorTextureSize,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1) {
|
||
// Unpack.
|
||
vec2 srcOffsetScale = filterParams0.xy / colorTextureSize;
|
||
int support = int(filterParams0.z);
|
||
vec3 gaussCoeff = filterParams1.xyz;
|
||
|
||
// Set up our incremental calculation.
|
||
float gaussSum = gaussCoeff.x;
|
||
vec4 color = texture(colorTexture, colorTexCoord) * gaussCoeff.x;
|
||
gaussCoeff.xy *= gaussCoeff.yz;
|
||
|
||
// This is a common trick that lets us use the texture filtering hardware to evaluate two
|
||
// texels at a time. The basic principle is that, if c0 and c1 are colors of adjacent texels
|
||
// and k0 and k1 are arbitrary factors, the formula `k0 * c0 + k1 * c1` is equivalent to
|
||
// `(k0 + k1) * lerp(c0, c1, k1 / (k0 + k1))`. Linear interpolation, as performed by the
|
||
// texturing hardware when sampling adjacent pixels in one direction, evaluates
|
||
// `lerp(c0, c1, t)` where t is the offset from the texel with color `c0`. To evaluate the
|
||
// formula `k0 * c0 + k1 * c1`, therefore, we can use the texture hardware to perform linear
|
||
// interpolation with `t = k1 / (k0 + k1)`.
|
||
for (int i = 1; i <= support; i += 2) {
|
||
float gaussPartialSum = gaussCoeff.x;
|
||
gaussCoeff.xy *= gaussCoeff.yz;
|
||
gaussPartialSum += gaussCoeff.x;
|
||
|
||
vec2 srcOffset = srcOffsetScale * (float(i) + gaussCoeff.x / gaussPartialSum);
|
||
color += (texture(colorTexture, colorTexCoord - srcOffset) +
|
||
texture(colorTexture, colorTexCoord + srcOffset)) * gaussPartialSum;
|
||
|
||
gaussSum += 2.0 * gaussPartialSum;
|
||
gaussCoeff.xy *= gaussCoeff.yz;
|
||
}
|
||
|
||
// Finish.
|
||
return color / gaussSum;
|
||
}
|
||
|
||
vec4 filterColorMatrix(vec2 colorTexCoord,
|
||
sampler2D colorTexture,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1,
|
||
vec4 filterParams2,
|
||
vec4 filterParams3,
|
||
vec4 filterParams4) {
|
||
vec4 srcColor = texture(colorTexture, colorTexCoord);
|
||
mat4 colorMatrix = mat4(filterParams0, filterParams1, filterParams2, filterParams3);
|
||
return colorMatrix * srcColor + filterParams4;
|
||
}
|
||
|
||
vec4 filterNone(vec2 colorTexCoord, sampler2D colorTexture) {
|
||
return sampleColor(colorTexture, colorTexCoord);
|
||
}
|
||
|
||
vec4 filterColor(vec2 colorTexCoord,
|
||
sampler2D colorTexture,
|
||
sampler2D gammaLUT,
|
||
vec2 colorTextureSize,
|
||
vec2 fragCoord,
|
||
vec2 framebufferSize,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1,
|
||
vec4 filterParams2,
|
||
vec4 filterParams3,
|
||
vec4 filterParams4,
|
||
int colorFilter) {
|
||
switch (colorFilter) {
|
||
case COMBINER_CTRL_FILTER_RADIAL_GRADIENT:
|
||
return filterRadialGradient(colorTexCoord,
|
||
colorTexture,
|
||
colorTextureSize,
|
||
fragCoord,
|
||
framebufferSize,
|
||
filterParams0,
|
||
filterParams1);
|
||
case COMBINER_CTRL_FILTER_BLUR:
|
||
return filterBlur(colorTexCoord,
|
||
colorTexture,
|
||
colorTextureSize,
|
||
filterParams0,
|
||
filterParams1);
|
||
case COMBINER_CTRL_FILTER_TEXT:
|
||
return filterText(colorTexCoord,
|
||
colorTexture,
|
||
gammaLUT,
|
||
colorTextureSize,
|
||
filterParams0,
|
||
filterParams1,
|
||
filterParams2);
|
||
case COMBINER_CTRL_FILTER_COLOR_MATRIX:
|
||
return filterColorMatrix(colorTexCoord,
|
||
colorTexture,
|
||
filterParams0,
|
||
filterParams1,
|
||
filterParams2,
|
||
filterParams3,
|
||
filterParams4);
|
||
}
|
||
return filterNone(colorTexCoord, colorTexture);
|
||
}
|
||
|
||
// Compositing
|
||
|
||
vec3 compositeSelect(bvec3 cond, vec3 ifTrue, vec3 ifFalse) {
|
||
return vec3(cond.x ? ifTrue.x : ifFalse.x,
|
||
cond.y ? ifTrue.y : ifFalse.y,
|
||
cond.z ? ifTrue.z : ifFalse.z);
|
||
}
|
||
|
||
float compositeDivide(float num, float denom) {
|
||
return denom != 0.0 ? num / denom : 0.0;
|
||
}
|
||
|
||
vec3 compositeColorDodge(vec3 destColor, vec3 srcColor) {
|
||
bvec3 destZero = equal(destColor, vec3(0.0)), srcOne = equal(srcColor, vec3(1.0));
|
||
return compositeSelect(destZero,
|
||
vec3(0.0),
|
||
compositeSelect(srcOne, vec3(1.0), destColor / (vec3(1.0) - srcColor)));
|
||
}
|
||
|
||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative
|
||
vec3 compositeHSLToRGB(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 * FRAC_6_PI), 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 compositeRGBToHSL(vec3 rgb) {
|
||
float v = max(max(rgb.r, rgb.g), rgb.b), xMin = min(min(rgb.r, rgb.g), rgb.b);
|
||
float c = v - xMin, l = mix(xMin, v, 0.5);
|
||
vec3 terms = rgb.r == v ? vec3(0.0, rgb.gb) :
|
||
rgb.g == v ? vec3(2.0, rgb.br) :
|
||
vec3(4.0, rgb.rg);
|
||
float h = FRAC_PI_3 * compositeDivide(terms.x * c + terms.y - terms.z, c);
|
||
float s = compositeDivide(c, v);
|
||
return vec3(h, s, l);
|
||
}
|
||
|
||
vec3 compositeScreen(vec3 destColor, vec3 srcColor) {
|
||
return destColor + srcColor - destColor * srcColor;
|
||
}
|
||
|
||
vec3 compositeHardLight(vec3 destColor, vec3 srcColor) {
|
||
return compositeSelect(lessThanEqual(srcColor, vec3(0.5)),
|
||
destColor * vec3(2.0) * srcColor,
|
||
compositeScreen(destColor, vec3(2.0) * srcColor - vec3(1.0)));
|
||
}
|
||
|
||
vec3 compositeSoftLight(vec3 destColor, vec3 srcColor) {
|
||
vec3 darkenedDestColor =
|
||
compositeSelect(lessThanEqual(destColor, vec3(0.25)),
|
||
((vec3(16.0) * destColor - 12.0) * destColor + 4.0) * destColor,
|
||
sqrt(destColor));
|
||
vec3 factor = compositeSelect(lessThanEqual(srcColor, vec3(0.5)),
|
||
destColor * (vec3(1.0) - destColor),
|
||
darkenedDestColor - destColor);
|
||
return destColor + (srcColor * 2.0 - 1.0) * factor;
|
||
}
|
||
|
||
vec3 compositeHSL(vec3 destColor, vec3 srcColor, int op) {
|
||
switch (op) {
|
||
case COMBINER_CTRL_COMPOSITE_HUE:
|
||
return vec3(srcColor.x, destColor.y, destColor.z);
|
||
case COMBINER_CTRL_COMPOSITE_SATURATION:
|
||
return vec3(destColor.x, srcColor.y, destColor.z);
|
||
case COMBINER_CTRL_COMPOSITE_COLOR:
|
||
return vec3(srcColor.x, srcColor.y, destColor.z);
|
||
default:
|
||
return vec3(destColor.x, destColor.y, srcColor.z);
|
||
}
|
||
}
|
||
|
||
vec3 compositeRGB(vec3 destColor, vec3 srcColor, int op) {
|
||
switch (op) {
|
||
case COMBINER_CTRL_COMPOSITE_MULTIPLY:
|
||
return destColor * srcColor;
|
||
case COMBINER_CTRL_COMPOSITE_SCREEN:
|
||
return compositeScreen(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_OVERLAY:
|
||
return compositeHardLight(srcColor, destColor);
|
||
case COMBINER_CTRL_COMPOSITE_DARKEN:
|
||
return min(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_LIGHTEN:
|
||
return max(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_COLOR_DODGE:
|
||
return compositeColorDodge(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_COLOR_BURN:
|
||
return vec3(1.0) - compositeColorDodge(vec3(1.0) - destColor, vec3(1.0) - srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_HARD_LIGHT:
|
||
return compositeHardLight(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_SOFT_LIGHT:
|
||
return compositeSoftLight(destColor, srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_DIFFERENCE:
|
||
return abs(destColor - srcColor);
|
||
case COMBINER_CTRL_COMPOSITE_EXCLUSION:
|
||
return destColor + srcColor - vec3(2.0) * destColor * srcColor;
|
||
case COMBINER_CTRL_COMPOSITE_HUE:
|
||
case COMBINER_CTRL_COMPOSITE_SATURATION:
|
||
case COMBINER_CTRL_COMPOSITE_COLOR:
|
||
case COMBINER_CTRL_COMPOSITE_LUMINOSITY:
|
||
return compositeHSLToRGB(compositeHSL(compositeRGBToHSL(destColor),
|
||
compositeRGBToHSL(srcColor),
|
||
op));
|
||
}
|
||
return srcColor;
|
||
}
|
||
|
||
vec4 composite(vec4 srcColor,
|
||
sampler2D destTexture,
|
||
vec2 destTextureSize,
|
||
vec2 fragCoord,
|
||
int op) {
|
||
if (op == COMBINER_CTRL_COMPOSITE_NORMAL)
|
||
return srcColor;
|
||
|
||
// FIXME(pcwalton): What should the output alpha be here?
|
||
vec2 destTexCoord = fragCoord / destTextureSize;
|
||
vec4 destColor = texture(destTexture, destTexCoord);
|
||
vec3 blendedRGB = compositeRGB(destColor.rgb, srcColor.rgb, op);
|
||
return vec4(srcColor.a * (1.0 - destColor.a) * srcColor.rgb +
|
||
srcColor.a * destColor.a * blendedRGB +
|
||
(1.0 - srcColor.a) * destColor.rgb,
|
||
1.0);
|
||
}
|
||
|
||
// Masks
|
||
|
||
float sampleMask(float maskAlpha,
|
||
sampler2D maskTexture,
|
||
vec2 maskTextureSize,
|
||
vec3 maskTexCoord,
|
||
int maskCtrl) {
|
||
if (maskCtrl == 0)
|
||
return maskAlpha;
|
||
|
||
ivec2 maskTexCoordI = ivec2(floor(maskTexCoord.xy));
|
||
vec4 texel = texture(maskTexture, (vec2(maskTexCoordI / ivec2(1, 4)) + 0.5) / maskTextureSize);
|
||
float coverage = texel[maskTexCoordI.y % 4] + maskTexCoord.z;
|
||
|
||
if ((maskCtrl & TILE_CTRL_MASK_WINDING) != 0)
|
||
coverage = abs(coverage);
|
||
else
|
||
coverage = 1.0 - abs(1.0 - mod(coverage, 2.0));
|
||
return min(maskAlpha, coverage);
|
||
}
|
||
|
||
// Main function
|
||
|
||
vec4 calculateColor(vec2 fragCoord,
|
||
sampler2D colorTexture0,
|
||
sampler2D maskTexture0,
|
||
sampler2D destTexture,
|
||
sampler2D gammaLUT,
|
||
vec2 colorTextureSize0,
|
||
vec2 maskTextureSize0,
|
||
vec4 filterParams0,
|
||
vec4 filterParams1,
|
||
vec4 filterParams2,
|
||
vec4 filterParams3,
|
||
vec4 filterParams4,
|
||
vec2 framebufferSize,
|
||
int ctrl,
|
||
vec3 maskTexCoord0,
|
||
vec2 colorTexCoord0,
|
||
vec4 baseColor,
|
||
int tileCtrl) {
|
||
// Sample mask.
|
||
int maskCtrl0 = (tileCtrl >> TILE_CTRL_MASK_0_SHIFT) & TILE_CTRL_MASK_MASK;
|
||
float maskAlpha = 1.0;
|
||
maskAlpha = sampleMask(maskAlpha, maskTexture0, maskTextureSize0, maskTexCoord0, maskCtrl0);
|
||
|
||
// Sample color.
|
||
vec4 color = baseColor;
|
||
int color0Combine = (ctrl >> COMBINER_CTRL_COLOR_COMBINE_SHIFT) &
|
||
COMBINER_CTRL_COLOR_COMBINE_MASK;
|
||
if (color0Combine != 0) {
|
||
int color0Filter = (ctrl >> COMBINER_CTRL_COLOR_FILTER_SHIFT) & COMBINER_CTRL_FILTER_MASK;
|
||
vec4 color0 = filterColor(colorTexCoord0,
|
||
colorTexture0,
|
||
gammaLUT,
|
||
colorTextureSize0,
|
||
fragCoord,
|
||
framebufferSize,
|
||
filterParams0,
|
||
filterParams1,
|
||
filterParams2,
|
||
filterParams3,
|
||
filterParams4,
|
||
color0Filter);
|
||
color = combineColor0(color, color0, color0Combine);
|
||
}
|
||
|
||
// Apply mask.
|
||
color.a *= maskAlpha;
|
||
|
||
// Apply composite.
|
||
int compositeOp = (ctrl >> COMBINER_CTRL_COMPOSITE_SHIFT) & COMBINER_CTRL_COMPOSITE_MASK;
|
||
color = composite(color, destTexture, framebufferSize, fragCoord, compositeOp);
|
||
|
||
// Premultiply alpha.
|
||
color.rgb *= color.a;
|
||
return color;
|
||
}
|