ft,
                                   const float4 colorUnpremul) {
      float3 rgb = colorUnpremul.rgb;

      float y = dot(rgb, float3(0.299,  0.587,  0.114));
      float i = dot(rgb, float3(0.596, -0.275, -0.321));
      float q = dot(rgb, float3(0.212, -0.523,  0.311));

      // When colorUnpremul.rgb is pure black, (y, i, q) == (0, 0, 0), and
      // atan(0, 0) is undefined. To avoid that, we set hueRadians to 0 in that
      // case. Generally we aim to avoid branching in the shader for
      // performance, but in this case the branching is on a uniform value,
      // which is not an issue for performance like branching on a vertex
      // attribute value is.
      float hueRadians = colorUnpremul.rgb == float3(0, 0, 0) ? 0 : atan(q, i);

      float chroma = sqrt(i * i + q * q);

      hueRadians -= hslShift.x * radians(360);
      chroma *= (hslShift.y + 1);
      y += hslShift.z;
      i = chroma * cos(hueRadians);
      q = chroma * sin(hueRadians);

      float3 yiq = float3(y, i, q);
      rgb = float3(dot(yiq, float3(1,  0.956,  0.621)),
                   dot(yiq, float3(1, -0.272, -0.647)),
                   dot(yiq, float3(1, -1.107,  1.704)));
      return float4(rgb, applyOpacityShift(opacityShift, colorUnpremul.a));
    }
    float2 decodeMargins(const float2 labels) {
      return (4.0 / 126.0) * max(abs(labels) - float2(1.0), float2(0.0));
    }
    float2 calculateAntialiasingAndPositionOutset(
        const float3 sideDerivativeAndLabel,
        const float3 forwardDerivativeAndLabel,
        const mat2 objectToCanvasLinearComponent,
        out float2 pixelsPerDimension,
        out float4 normalizedToEdgeLRFB,
        out float4 outsetPixelsLRFB) {
      float2 sideDerivative = sideDerivativeAndLabel.xy;
      float2 forwardDerivative = forwardDerivativeAndLabel.xy;

      pixelsPerDimension =
          abs(matrixDeterminant(objectToCanvasLinearComponent)) *
          float2(dot(sideDerivative, sideDerivative),
                 dot(forwardDerivative, forwardDerivative)) /
          max(float2(0.000001), float2(length(objectToCanvasLinearComponent *
                                              orthogonal(sideDerivative)),
                                       length(objectToCanvasLinearComponent *
                                              orthogonal(forwardDerivative))));

      float2 labels = float2(sideDerivativeAndLabel.z,
                             forwardDerivativeAndLabel.z);

      normalizedToEdgeLRFB = float4(labels.x > -0.005,   // distance to left
                                    labels.x <  0.005,   //             right
                                    labels.y > -0.005,   //             front
                                    labels.y <  0.005);  //             back

      float pixelOutsetTarget =
          targetAntialiasingPixelOutset(pixelsPerDimension.x);
      float2 outsetTargets = float2(pixelOutsetTarget) / pixelsPerDimension;
      float2 outsets = min(outsetTargets, decodeMargins(labels));
      outsets.x = mix(outsetTargets.x, outsets.x,
                      saturate(4.0 * pixelsPerDimension.x - 1.0));
      float4 fromEdge = float4(1.0) - normalizedToEdgeLRFB;
      outsetPixelsLRFB =
          pixelOutsetTarget * fromEdge * (outsets / outsetTargets).xxyy;

      float2 sideOutset = sign(labels.x) * outsets.x * sideDerivative;
      float2 forwardOutset = sign(labels.y) * outsets.y * forwardDerivative;
      float commonForwardMagnitude =
          saturate(dot(sideOutset, forwardOutset) /
                   max(0.000001, dot(forwardOutset, forwardOutset)));

      return sideOutset + (1.0 - commonForwardMagnitude) * forwardOutset;
    }
    float2 calculateStampingTextureUv(
        const float2 surfaceUv,
        const float particleAnimationOffset,
        const float textureAnimationProgress,
        const int numTextureAnimationFrames,
        const int numTextureAnimationRows,
        const int numTextureAnimationColumns) {
      // Progress overshooting 1 is handled by wrapping around to 0. Effectively
      // progress is mod 1.
      float progress =
          fract(textureAnimationProgress + particleAnimationOffset);

      float numFrames = float(numTextureAnimationFrames);
      float numRows = float(numTextureAnimationRows);
      float numColumns = float(numTextureAnimationColumns);
      float frameIndex = floor(progress * numFrames);
      float frameRowFractional = frameIndex / numColumns;
      float frameRow = floor(frameRowFractional);
      float frameColumn = floor(fract(frameRowFractional) * numColumns);
      return vec2((surfaceUv.x + frameColumn) / numColumns,
                  (surfaceUv.y + frameRow) / numRows);
    }
    float2 unpackFloat2PackedIntoFloat(const float4 unpackingTransform,
                                       const float packedValue) {
      float2 unpacked = float2(floor(packedValue / 4096.0),
                               fract(packedValue / 4096.0) * 4096.0);
      return unpackingTransform.yw * unpacked + unpackingTransform.xz;
    }
    float2 unpackFloat2PackedIntoUByte3(const float4 unpackingTransform,
                                        const half3 packedValue) {
      float mixedXY = 15.9375 * float(packedValue.y);
      float2 unpacked =
          float2(4080.0 * float(packedValue.x) + floor(mixedXY),
                 4096.0 * fract(mixedXY) + 255.0 * float(packedValue.z));
      return unpackingTransform.yw * unpacked + unpackingTransform.xz;
    }
    float3 unpackPositionAndOpacityShift(const float4 unpackingTransform,
                                         const float2 packedValue) {
      return float3(
          unpackFloat2PackedIntoFloat(unpackingTransform, packedValue.x),
          packedValue.y);
    }
    float3 unpackPositionAndOpacityShift(const float4 unpackingTransform,
                                         const half4 packedValue) {
      return float3(
          unpackFloat2PackedIntoUByte3(unpackingTransform, packedValue.xyz),
          (255.0 / 127.0) * packedValue.w - 1.0);
    }
    float3 unpackDerivativeAndLabel(const float4 unusedUnpackingTransform,
                                    const float3 unpackedValue) {
      return unpackedValue;
    }
    float3 unpackDerivativeAndLabel(const float4 unpackingTransform,
                                    const half4 packedValue) {
      return float3(
          unpackFloat2PackedIntoUByte3(unpackingTransform, packedValue.xyz),
          255.0 * packedValue.w - 128.0);
    }
    float3 unpackHSLColorShift(const float3 unpackedValue) {
      return unpackedValue;
    }
    float3 unpackHSLColorShift(const half4 packedValue) {
      float mixedXY = 3.984375 * float(packedValue.y);
      float mixedYZ = 15.9375 * float(packedValue.z);
      return float3(1020.0 * float(packedValue.x) + floor(mixedXY),
                    1024.0 * fract(mixedXY) + floor(mixedYZ),
                    1024.0 * fract(mixedYZ) + 63.75 * float(packedValue.w)) /
                 511.0 -
             float3(1.0);
    }
    float2 unpackSurfaceUv(const float2 unpackedValue) {
      return unpackedValue;
    }
    float2 unpackSurfaceUv(const half3 packedValue) {
      float mixedUV = 15.9375 * float(packedValue.y);
      return float2(
          (4080.0 * float(packedValue.x) + floor(mixedUV)) / 4095.0,
          (4096.0 * fract(mixedUV) + 255.0 * float(packedValue.z)) / 4095.0);
    }
    float2 unpackSurfaceUv(const half4 packedValue) {
      float mixedXY = 15.9375 * float(packedValue.y);
      return float2((4080.0 * float(packedValue.x) + floor(mixedXY)) / 4095.0,
                    (1048576.0 * fract(mixedXY) +
                     65280.0 * float(packedValue.z) +
                     255.0 * float(packedValue.w)) / 1048575.0);
    }
    float unpackAnimationOffset(const float unpackedValue) {
      return unpackedValue;
    }
    float unpackAnimationOffset(const half packedValue) {
      return float(packedValue);
    }