#version 300 es
// Copyright 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// ES 3 fragment shader that:
// 1. Samples optical linear BT.2020 RGB from a (non-external) texture with
//    uTexSampler.
// 2. Applies a 4x4 RGB color matrix to change the pixel colors.
// 3. Outputs electrical (HLG or PQ) BT.2020 RGB based on uOutputColorTransfer,
//    via an OETF.
// The output will be red if an error has occurred.

precision mediump float;
uniform sampler2D uTexSampler;
in vec2 vTexSamplingCoord;
out vec4 outColor;
// C.java#ColorTransfer value.
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
uniform int uOutputColorTransfer;
uniform mat3 uColorTransform;
uniform mat4 uRgbMatrix;

// Output colors for an obviously visible error.
const vec3 ERROR_COLOR_RED = vec3(1.0, 0.0, 0.0);
const vec3 ERROR_COLOR_BLUE = vec3(0.0, 0.0, 1.0);

// LINT.IfChange(color_transfer)
const int COLOR_TRANSFER_ST2084 = 6;
const int COLOR_TRANSFER_HLG = 7;
// LINT.ThenChange(../../../../../common/src/main/java/androidx/media3/common/C.java:color_transfer)

// HLG OETF for one channel.
highp float hlgOetfSingleChannel(highp float linearChannel) {
  // Specification:
  // https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_HLG
  // Reference implementation:
  // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=529-543;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
  const highp float a = 0.17883277;
  const highp float b = 0.28466892;
  const highp float c = 0.55991073;

  return linearChannel <= 1.0 / 12.0 ? sqrt(3.0 * linearChannel)
                                     : a * log(12.0 * linearChannel - b) + c;
}

// BT.2100 / BT.2020 HLG OETF.
highp vec3 hlgOetf(highp vec3 linearColor) {
  return vec3(hlgOetfSingleChannel(linearColor.r),
              hlgOetfSingleChannel(linearColor.g),
              hlgOetfSingleChannel(linearColor.b));
}

// BT.2100 / BT.2020, PQ / ST2084 OETF.
highp vec3 pqOetf(highp vec3 linearColor) {
  // Specification:
  // https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.inline.html#TRANSFER_PQ
  // Reference implementation:
  // https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/renderengine/gl/ProgramCache.cpp;l=514-527;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
  const highp float m1 = (2610.0 / 16384.0);
  const highp float m2 = (2523.0 / 4096.0) * 128.0;
  const highp float c1 = (3424.0 / 4096.0);
  const highp float c2 = (2413.0 / 4096.0) * 32.0;
  const highp float c3 = (2392.0 / 4096.0) * 32.0;

  highp vec3 temp = pow(linearColor, vec3(m1));
  temp = (c1 + c2 * temp) / (1.0 + c3 * temp);
  return pow(temp, vec3(m2));
}

// Applies the appropriate OETF to convert linear optical signals to nonlinear
// electrical signals. Input and output are both normalized to [0, 1].
highp vec3 applyOetf(highp vec3 linearColor) {
  if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
    return pqOetf(linearColor);
  } else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
    return hlgOetf(linearColor);
  } else {
    return ERROR_COLOR_RED;
  }
}

highp vec3 normalizeHdrLuminance(highp vec3 inputColor) {
  const float PQ_MAX_LUMINANCE = 10000.0;
  const float HLG_MAX_LUMINANCE = 1000.0;
  if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
    return inputColor * HLG_MAX_LUMINANCE / PQ_MAX_LUMINANCE;
  } else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
    return inputColor;
  } else {
    return ERROR_COLOR_BLUE;
  }
}

void main() {
  vec4 inputColor = texture(uTexSampler, vTexSamplingCoord);
  // transformedColors is an optical color.
  vec4 transformedColors = uRgbMatrix * vec4(inputColor.rgb, 1);
  outColor = vec4(applyOetf(normalizeHdrLuminance(transformedColors.rgb)),
                  inputColor.a);
}
