{
    "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 {
            VideoShaderUniformsMetal;
            float blockSize;
            float maxColors;
            float aberration;
        } Uniforms;

        fragment fragmentOut fragmentShader(
            fragmentIn in [[stage_in]],
            constant Uniforms & uniforms [[buffer(1)]],
            DefaultInputs defaultInputs
        ) {
            fragmentOut out = {};

            float2 uv = in.sourceTextureCoordinate;

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

            float2 st = (uv - 0.5) * uniforms.output_size;
            float2 pixelatedST = floor(st / bs) * bs + bs * 0.5;
            float2 pixelatedUV = pixelatedST / uniforms.output_size + 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);

            // 8-bit color
            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);

            out._gl_FragColor = toOutputFormat(color);
            return out;
        }
      ",
        "uniforms": {
          "blockSize": {
            "value": 5
          },
          "maxColors": {
            "value": 256
          },
          "aberration": {
            "value": 0.004
          }
        }
      }
    ]
  }
