{
    "name": "Low Res",
    "passes": [
      {
        "name": "lowRes",
        "inputs": [
          "default"
        ],
       "glsl": "
      varying vec2 sourceTextureCoordinate;
      uniform float blockSize;
      uniform float maxColors;
      uniform float aberration;
      uniform vec2 outputSize;

      void main() {

        vec2 uv = sourceTextureCoordinate;

        // ensure pixel size looks the same in all resolutions (1080, 2K)
        float bs = blockSize * outputSize.y / 1920.0;

        vec2 st = (uv - 0.5) * outputSize;
        vec2 pixelatedST = floor(st / bs) * bs + bs * 0.5;
        vec2 pixelatedUV = pixelatedST / outputSize + 0.5;

        vec4 baseR = sampleInput(pixelatedUV + aberration);
        vec4 baseG = sampleInput(pixelatedUV);
        vec4 baseB = sampleInput(pixelatedUV - aberration);
        vec4 base = vec4(baseR.r, baseG.g, baseB.b, baseR.a);

        // 8-bit color
        vec3 color_resolution = vec3(8.0, 8.0, 4.0) * maxColors / 256.0;
        vec3 color_bands = floor(base.rgb * color_resolution) / (color_resolution - 1.0);
        vec4 color = vec4(min(color_bands, 1.0), base.a);

        gl_FragColor = toOutputFormat(color);
      }
      ",
        "metal": "using namespace metal;
        struct fragmentOut {float4 _gl_FragColor [[color(0)]];};
        struct fragmentIn {float2 sourceTextureCoordinate [[user(locn1)]];};

        typedef struct {
          float4x4 content_transform;
          float4x4 texture_transform;
          float strength;
          float progress;
          float time_sec;
          float duration_sec;
          float clip_duration_sec;
          float2 inputSize;
          float2 outputSize;
          float maxColors;
          float blockSize;
          float aberration;} Uniforms;

          fragment fragmentOut fragmentShader(
            fragmentIn in [[stage_in]],
            constant Uniforms & uniforms [[buffer(1)]],
            DefaultInputs defaultInputs) {
          fragmentOut out = {};
          float2 uv = in.sourceTextureCoordinate;
          float bs = uniforms.blockSize * uniforms.outputSize.y / 1920.0;
          float2 st = (uv - 0.5) * uniforms.outputSize;
          float2 pixelatedST = floor(st / bs) * bs + bs * 0.5;
          float2 pixelatedUV = pixelatedST / uniforms.outputSize + 0.5;
          float4 baseR = sampleInput(defaultInputs, pixelatedUV + uniforms.aberration);
          float4 baseG = sampleInput(defaultInputs, pixelatedUV);
          float4 baseB = sampleInput(defaultInputs, pixelatedUV - uniforms.aberration);
          float4 base = float4(baseR.r, baseG.g, baseB.b, baseR.a);
          float3 color_resolution = float3(8.0, 8.0, 4.0) * uniforms.maxColors / 256.0;
          float3 color_bands = floor(base.rgb * color_resolution) / (color_resolution - 1.0);
          float4 color = float4(min(color_bands, 1.0), base.a);
          color = mix(base, color, uniforms.strength);
          out._gl_FragColor = toOutputFormat(color);
          return out;}
        ",
        "uniforms": {
          "blockSize": {
            "value": 5.0
          },
          "maxColors": {
            "value": 256.0
          },
          "aberration": {
            "value": 0.004
          },
          "strength": {
            "min": 0,
            "max": 1,
            "value": 1.0
          }
        }
      }
    ]
}
