diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index 3039acb..671fc5b 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -531,6 +531,13 @@
- "SHADER_spinblurGPU.radius" "Safe Radius"
- "SHADER_spinblurGPU.blur" "Blur"
+ - "SHADER_HSLBlendGPU" "GPU HSL Blend"
+ - "SHADER_HSLBlendGPU.bhue" "Hue"
+ - "SHADER_HSLBlendGPU.bsat" "Saturation"
+ - "SHADER_HSLBlendGPU.blum" "Luminosity"
+ - "SHADER_HSLBlendGPU.balpha" "Opacity"
+ - "SHADER_HSLBlendGPU.bmask" "Clipping Mask"
+
diff --git a/stuff/library/shaders/HSLBlendGPU.xml b/stuff/library/shaders/HSLBlendGPU.xml
new file mode 100644
index 0000000..9c86f78
--- /dev/null
+++ b/stuff/library/shaders/HSLBlendGPU.xml
@@ -0,0 +1,69 @@
+
+
+ SHADER_HSLBlendGPU
+
+
+ "programs/HSLBlendGPU.frag"
+
+
+
+
+
+ "BlendSrc"
+
+
+ "BaseSrc"
+
+
+
+
+ SHADER_HSLBlendGPU_ports
+
+
+ "programs/HSLBlendGPU_ports.vert"
+
+
+
+
+
+ isotropic
+
+
+
+
+ bool bhue
+
+ 1
+
+
+
+ bool bsat
+
+ 1
+
+
+
+ bool blum
+
+ 0
+
+
+
+ float balpha
+
+ percent
+
+
+ 1.0
+
+
+ 0 1
+
+
+
+ bool bmask
+
+ 0
+
+
+
diff --git a/stuff/library/shaders/programs/HSLBlendGPU.frag b/stuff/library/shaders/programs/HSLBlendGPU.frag
new file mode 100644
index 0000000..070ad7e
--- /dev/null
+++ b/stuff/library/shaders/programs/HSLBlendGPU.frag
@@ -0,0 +1,126 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+
+uniform mat3 worldToOutput;
+
+uniform sampler2D inputImage[2];
+uniform mat3 outputToInput[2];
+
+uniform bool bhue; // Blend HUE?
+uniform bool bsat; // Blend Saturation?
+uniform bool blum; // Blend Luminosity?
+uniform float balpha; // Blending Alpha
+uniform bool bmask; // Base mask?
+
+// ---------------------------
+// Blending calculations from:
+// https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt
+
+float minv3(vec3 c)
+{
+ return min(min(c.r, c.g), c.b);
+}
+
+float maxv3(vec3 c)
+{
+ return max(max(c.r, c.g), c.b);
+}
+
+float lumv3(vec3 c)
+{
+ return dot(c, vec3(0.30, 0.59, 0.11));
+}
+
+float satv3(vec3 c)
+{
+ return maxv3(c) - minv3(c);
+}
+
+// If any color components are outside [0,1], adjust the color to get the components in range.
+vec3 ClipColor(vec3 color)
+{
+ float lum = lumv3(color);
+ float mincol = minv3(color);
+ float maxcol = maxv3(color);
+ if (mincol < 0.0) {
+ color = lum + ((color-lum) * lum) / (lum-mincol);
+ }
+ if (maxcol > 1.0) {
+ color = lum + ((color-lum) * (1.0 -lum)) / (maxcol-lum);
+ }
+ return color;
+}
+
+// Take the base RGB color and override its luminosity
+// with that of the RGB color .
+vec3 SetLum(vec3 cbase, vec3 clum)
+{
+ float lbase = lumv3(cbase);
+ float llum = lumv3(clum);
+ float ldiff = llum - lbase;
+ vec3 color = cbase + vec3(ldiff);
+ return ClipColor(color);
+}
+
+// Take the base RGB color and override its saturation with
+// that of the RGB color . The override the luminosity of the
+// result with that of the RGB color .
+vec3 SetLumSat(vec3 cbase, vec3 csat, vec3 clum)
+{
+ float minbase = minv3(cbase);
+ float sbase = satv3(cbase);
+ float ssat = satv3(csat);
+ vec3 color;
+ if (sbase > 0.0) {
+ // Equivalent (modulo rounding errors) to setting the
+ // smallest (R,G,B) component to 0, the largest to ,
+ // and interpolating the "middle" component based on its
+ // original value relative to the smallest/largest.
+ color = (cbase - minbase) * ssat / sbase;
+ } else {
+ color = vec3(0.0);
+ }
+ return SetLum(color, clum);
+}
+
+// ---------------------------
+
+void main( void )
+{
+ // Read sources
+ vec2 fg_texPos = (outputToInput[0] * vec3(gl_FragCoord.xy, 1.0)).xy;
+ vec2 bg_texPos = (outputToInput[1] * vec3(gl_FragCoord.xy, 1.0)).xy;
+ vec4 fg_frag = texture2D(inputImage[0], fg_texPos);
+ vec4 bg_frag = texture2D(inputImage[1], bg_texPos);
+
+ // De-premultiplication
+ vec3 fg_pix = vec3(0.0);
+ if (fg_frag.a > 0.0) fg_pix = fg_frag.rgb / fg_frag.a;
+ vec3 bg_pix = vec3(0.0);
+ if (bg_frag.a > 0.0) bg_pix = bg_frag.rgb / bg_frag.a;
+
+ // Figure out output alpha
+ float fg_alpha = fg_frag.a * balpha;
+ float bg_alpha = bg_frag.a;
+ if (bmask) {
+ gl_FragColor.a = bg_alpha;
+ } else {
+ gl_FragColor.a = fg_alpha + bg_alpha * (1.0 - fg_alpha);
+ }
+ if (gl_FragColor.a <= 0.0) discard;
+
+ // Perform blending
+ if (fg_alpha > 0.0 && bg_alpha > 0.0) {
+ vec3 o_pix = SetLumSat(bhue ? fg_pix : bg_pix, bsat ? fg_pix : bg_pix, blum ? fg_pix : bg_pix);
+ gl_FragColor.rgb = mix(bg_pix, o_pix, balpha);
+ } else if (fg_alpha > 0.0) {
+ gl_FragColor.rgb = fg_pix;
+ } else {
+ gl_FragColor.rgb = bg_pix;
+ }
+
+ // Premultiplication
+ gl_FragColor.rgb *= gl_FragColor.a;
+}
diff --git a/stuff/library/shaders/programs/HSLBlendGPU_ports.vert b/stuff/library/shaders/programs/HSLBlendGPU_ports.vert
new file mode 100644
index 0000000..924d542
--- /dev/null
+++ b/stuff/library/shaders/programs/HSLBlendGPU_ports.vert
@@ -0,0 +1,21 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform mat3 worldToOutput;
+uniform vec4 outputRect;
+
+varying vec4 inputRect[2];
+varying mat3 worldToInput[2];
+
+
+void main( void )
+{
+ // Let the input and output references be the same
+ worldToInput[0] = worldToOutput;
+ worldToInput[1] = worldToOutput;
+ inputRect[0] = outputRect;
+ inputRect[1] = outputRect;
+
+ gl_Position = vec4(0.0); // Does not link without
+}
diff --git a/stuff/profiles/layouts/fxs/SHADER_HSLBlendGPU.xml b/stuff/profiles/layouts/fxs/SHADER_HSLBlendGPU.xml
new file mode 100644
index 0000000..e970951
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/SHADER_HSLBlendGPU.xml
@@ -0,0 +1,9 @@
+
+
+ bhue
+ bsat
+ blum
+ balpha
+ bmask
+
+
diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst
index b374719..ff93317 100644
--- a/stuff/profiles/layouts/fxs/fxs.lst
+++ b/stuff/profiles/layouts/fxs/fxs.lst
@@ -184,6 +184,7 @@
SHADER_wavy
SHADER_radialblurGPU
SHADER_spinblurGPU
+ SHADER_HSLBlendGPU
STD_nothingFx