🐛 Fix source switching crash & enhance stability (v3.0.4)
### 🐛 Bug Fixes - Fix random crashes when switching video sources in settings management - Enhanced VodConfig.setHome() null pointer exception handling - Improved Fragment lifecycle checks to prevent crashes - Optimized HistoryDialog source switching safety - Enhanced thread safety for concurrent loading ### ⚡ Performance Improvements - Added automatic cache cleaning functionality - Improved memory usage optimization - Enhanced network request stability ### 🆕 New Features - Added comprehensive error handling mechanisms - Enhanced crash protection functionality - Improved Fragment state validation ### 📱 Build Improvements - Updated README with professional documentation - Enhanced build configuration for ARM64-V8A and ARM V7A - Improved APK packaging and signing process
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
#version 100
|
||||
// Copyright 2023 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 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, and multiplies its alpha value by uAlphaScale.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform float uAlphaScale;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
vec4 src = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
gl_FragColor = vec4(src.rgb, src.a * uAlphaScale);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#version 100
|
||||
// Copyright 2023 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 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler and copies this to the output.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#version 100
|
||||
// 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 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler. It then converts the RGB color input into HSL and adjusts
|
||||
// the Hue, Saturation, and Lightness and converts it then back to RGB.
|
||||
|
||||
// We use the algorithm based on the work by Sam Hocevar, which optimizes
|
||||
// for an efficient branchless RGB <-> HSL conversion. A blog post is
|
||||
// at https://www.chilliant.com/rgb2hsv.html and it is further explained at
|
||||
// http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
|
||||
|
||||
precision highp float;
|
||||
uniform sampler2D uTexSampler;
|
||||
// uHueAdjustmentDegrees, uSaturationAdjustment, and uLightnessAdjustment
|
||||
// are normalized to the unit interval [0, 1].
|
||||
uniform float uHueAdjustmentDegrees;
|
||||
uniform float uSaturationAdjustment;
|
||||
uniform float uLightnessAdjustment;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
const float epsilon = 1e-10;
|
||||
|
||||
vec3 rgbToHcv(vec3 rgb) {
|
||||
vec4 p = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0 / 3.0)
|
||||
: vec4(rgb.gb, 0.0, -1.0 / 3.0);
|
||||
vec4 q = (rgb.r < p.x) ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
|
||||
float c = q.x - min(q.w, q.y);
|
||||
float h = abs((q.w - q.y) / (6.0 * c + epsilon) + q.z);
|
||||
return vec3(h, c, q.x);
|
||||
}
|
||||
|
||||
vec3 rgbToHsl(vec3 rgb) {
|
||||
vec3 hcv = rgbToHcv(rgb);
|
||||
float l = hcv.z - hcv.y * 0.5;
|
||||
float s = hcv.y / (1.0 - abs(l * 2.0 - 1.0) + epsilon);
|
||||
return vec3(hcv.x, s, l);
|
||||
}
|
||||
|
||||
vec3 hueToRgb(float hue) {
|
||||
float r = abs(hue * 6.0 - 3.0) - 1.0;
|
||||
float g = 2.0 - abs(hue * 6.0 - 2.0);
|
||||
float b = 2.0 - abs(hue * 6.0 - 4.0);
|
||||
return clamp(vec3(r, g, b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 hslToRgb(vec3 hsl) {
|
||||
vec3 rgb = hueToRgb(hsl.x);
|
||||
float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
|
||||
return (rgb - 0.5) * c + hsl.z;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec3 hslColor = rgbToHsl(inputColor.rgb);
|
||||
|
||||
hslColor.x = mod(hslColor.x + uHueAdjustmentDegrees, 1.0);
|
||||
hslColor.y = clamp(hslColor.y + uSaturationAdjustment, 0.0, 1.0);
|
||||
hslColor.z = clamp(hslColor.z + uLightnessAdjustment, 0.0, 1.0);
|
||||
|
||||
gl_FragColor = vec4(hslToRgb(hslColor), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#version 100
|
||||
// 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.
|
||||
|
||||
// ES2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, copying from this texture to the current output while
|
||||
// applying a 3D color lookup table to change the pixel colors.
|
||||
|
||||
precision highp float;
|
||||
uniform sampler2D uTexSampler;
|
||||
// The uColorLut texture is a N x N^2 2D texture where each z-plane of the 3D
|
||||
// LUT is vertically stacked on top of each other. The red channel of the input
|
||||
// color (z-axis in LUT[R][G][B] = LUT[z][y][x]) points to the plane to sample
|
||||
// from. For more information check the
|
||||
// androidx/media3/effect/SingleColorLut.java class, especially the function
|
||||
// #transformCubeIntoBitmap with a provided example.
|
||||
uniform sampler2D uColorLut;
|
||||
uniform float uColorLutLength;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
// Applies the color lookup using uLut based on the input colors.
|
||||
vec3 applyLookup(vec3 color) {
|
||||
// Reminder: Inside OpenGL vector.xyz is the same as vector.rgb.
|
||||
// Here we use mentions of x and y coordinates to references to
|
||||
// the position to sample from inside the 2D LUT plane and
|
||||
// rgb to create the 3D coordinates based on the input colors.
|
||||
|
||||
// To sample from the 3D LUT we interpolate bilinearly twice in the 2D LUT
|
||||
// to replicate the trilinear interpolation in a 3D LUT. Thus we sample
|
||||
// from the plane of position redCoordLow and on the plane above.
|
||||
// redCoordLow points to the lower plane to sample from.
|
||||
float redCoord = color.r * (uColorLutLength - 1.0);
|
||||
// Clamping to uColorLutLength - 2 is only needed if redCoord points to the
|
||||
// most upper plane. In this case there would not be any plane above
|
||||
// available to sample from.
|
||||
float redCoordLow = clamp(floor(redCoord), 0.0, uColorLutLength - 2.0);
|
||||
|
||||
// lowerY is indexed in two steps. First redCoordLow defines the plane to
|
||||
// sample from. Next the green color component is added to index the row in
|
||||
// the found plane. As described in the NVIDIA blog article about LUTs
|
||||
// https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-24-using-lookup-tables-accelerate-color
|
||||
// (Section 24.2), we sample from color * scale + offset, where offset is
|
||||
// defined by 1 / (2 * uColorLutLength) and the scale is defined by
|
||||
// (uColorLutLength - 1.0) / uColorLutLength.
|
||||
|
||||
// The following derives the equation of lowerY. For this let
|
||||
// N = uColorLutLenght. The general formula to sample at row y
|
||||
// is defined as y = N * r + g.
|
||||
// Using the offset and scale as described in NVIDIA's blog article we get:
|
||||
// y = offset + (N * r + g) * scale
|
||||
// y = 1 / (2 * N) + (N * r + g) * (N - 1) / N
|
||||
// y = 1 / (2 * N) + N * r * (N - 1) / N + g * (N - 1) / N
|
||||
// We have defined redCoord as r * (N - 1) if we excluded the clamping for
|
||||
// now, giving us:
|
||||
// y = 1 / (2 * N) + N * redCoord / N + g * (N - 1) / N
|
||||
// This simplifies to:
|
||||
// y = 0.5 / N + (N * redCoord + g * (N - 1)) / N
|
||||
// y = (0.5 + N * redCoord + g * (N - 1)) / N
|
||||
// This formula now assumes a coordinate system in the range of [0, N] but
|
||||
// OpenGL uses a [0, 1] unit coordinate system internally. Thus dividing
|
||||
// by N gives us the final formula for y:
|
||||
// y = ((0.5 + N * redCoord + g * (N - 1)) / N) / N
|
||||
// y = (0.5 + redCoord * N + g * (N - 1)) / (N * N)
|
||||
float lowerY = (0.5 + redCoordLow * uColorLutLength +
|
||||
color.g * (uColorLutLength - 1.0)) /
|
||||
(uColorLutLength * uColorLutLength);
|
||||
// The upperY is the same position moved up by one LUT plane.
|
||||
float upperY = lowerY + 1.0 / uColorLutLength;
|
||||
|
||||
// The x position is the blue color channel (x-axis in LUT[R][G][B]).
|
||||
float x = (0.5 + color.b * (uColorLutLength - 1.0)) / uColorLutLength;
|
||||
|
||||
vec3 lowerRgb = texture2D(uColorLut, vec2(x, lowerY)).rgb;
|
||||
vec3 upperRgb = texture2D(uColorLut, vec2(x, upperY)).rgb;
|
||||
|
||||
// Linearly interpolate between lowerRgb and upperRgb based on the
|
||||
// distance of the actual in the plane and the lower sampling position.
|
||||
return mix(lowerRgb, upperRgb, redCoord - redCoordLow);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
|
||||
gl_FragColor.rgb = applyLookup(inputColor.rgb);
|
||||
gl_FragColor.a = inputColor.a;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#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;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization.
|
||||
|
||||
// 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) {
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture(uTexSampler, vTexSamplingCoord);
|
||||
// transformedColors is an optical color.
|
||||
vec4 transformedColors = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#version 100
|
||||
// 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 2 fragment shader that samples from a (non-external) texture with
|
||||
// uTexSampler, copying from this texture to the current output while
|
||||
// applying a 4x4 RGB color matrix to change the pixel colors.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
gl_FragColor = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
gl_FragColor.a = inputColor.a;
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
#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 electrical (HLG or PQ) BT.2020 YUV from an external texture with
|
||||
// uTexSampler, where the sampler uses the EXT_YUV_target extension specified
|
||||
// at
|
||||
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt,
|
||||
// 2. Applies a YUV to RGB conversion using the specified color transform
|
||||
// uYuvToRgbColorTransform, yielding electrical (HLG or PQ) BT.2020 RGB,
|
||||
// 3. Applies an EOTF based on uInputColorTransfer, yielding optical linear
|
||||
// BT.2020 RGB.
|
||||
// 4. Optionally applies a BT2020 to BT709 OOTF, if OpenGL tone-mapping is
|
||||
// requested via uApplyHdrToSdrToneMapping.
|
||||
// 5. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 6. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_ST2084 /
|
||||
// COLOR_TRANSFER_HLG to output electrical colors via an OETF (e.g. to an
|
||||
// encoder).
|
||||
// The output will be red or blue if an error has occurred.
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
#extension GL_EXT_YUV_target : require
|
||||
precision mediump float;
|
||||
uniform __samplerExternal2DY2YEXT uTexSampler;
|
||||
uniform mat3 uYuvToRgbColorTransform;
|
||||
uniform mat4 uRgbMatrix;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
uniform int uApplyHdrToSdrToneMapping;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR, COLOR_TRANSFER_GAMMA_2_2, COLOR_TRANSFER_ST2084,
|
||||
// and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
in vec2 vTexSamplingCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization errors.
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF for one channel.
|
||||
highp float hlgEotfSingleChannel(highp float hlgChannel) {
|
||||
// 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=265-279;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
return hlgChannel <= 0.5 ? hlgChannel * hlgChannel / 3.0
|
||||
: (b + exp((hlgChannel - c) / a)) / 12.0;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF.
|
||||
highp vec3 hlgEotf(highp vec3 hlgColor) {
|
||||
return vec3(hlgEotfSingleChannel(hlgColor.r),
|
||||
hlgEotfSingleChannel(hlgColor.g),
|
||||
hlgEotfSingleChannel(hlgColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 PQ EOTF.
|
||||
highp vec3 pqEotf(highp vec3 pqColor) {
|
||||
// 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=250-263;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(clamp(pqColor, 0.0, 1.0), 1.0 / vec3(m2));
|
||||
temp = max(temp - c1, 0.0) / (c2 - c3 * temp);
|
||||
return pow(temp, 1.0 / vec3(m1));
|
||||
}
|
||||
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical values to linear
|
||||
// optical values. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyEotf(highp vec3 electricalColor) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgEotf(electricalColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the HLG BT2020 to BT709 OOTF.
|
||||
highp vec3 applyHlgBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
// Reference ("HLG Reference OOTF" section):
|
||||
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf
|
||||
// Matrix values based on computeXYZMatrix(BT2020Primaries, BT2020WhitePoint)
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/utils/HostColorSpace.cpp;l=200-232;drc=86bd214059cd6150304888a285941bf74af5b687
|
||||
const mat3 RGB_TO_XYZ_BT2020 =
|
||||
mat3(0.63695805f, 0.26270021f, 0.00000000f, 0.14461690f, 0.67799807f,
|
||||
0.02807269f, 0.16888098f, 0.05930172f, 1.06098506f);
|
||||
// Matrix values based on computeXYZMatrix(BT709Primaries, BT709WhitePoint)
|
||||
const mat3 XYZ_TO_RGB_BT709 =
|
||||
mat3(3.24096994f, -0.96924364f, 0.05563008f, -1.53738318f, 1.87596750f,
|
||||
-0.20397696f, -0.49861076f, 0.04155506f, 1.05697151f);
|
||||
// hlgGamma is 1.2 + 0.42 * log10(nominalPeakLuminance/1000);
|
||||
// nominalPeakLuminance was selected to use a 500 as a typical value, used
|
||||
// in
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/tonemap/tonemap.cpp;drc=7a577450e536aa1e99f229a0cb3d3531c82e8a8d;l=62,
|
||||
// b/199162498#comment35, and
|
||||
// https://www.microsoft.com/applied-sciences/uploads/projects/investigation-of-hdr-vs-tone-mapped-sdr/investigation-of-hdr-vs-tone-mapped-sdr.pdf.
|
||||
const float hlgGamma = 1.0735674018211279;
|
||||
|
||||
vec3 linearXyzBt2020 = RGB_TO_XYZ_BT2020 * linearRgbBt2020;
|
||||
vec3 linearXyzBt709 =
|
||||
linearXyzBt2020 * pow(linearXyzBt2020[1], hlgGamma - 1.0);
|
||||
vec3 linearRgbBt709 = clamp((XYZ_TO_RGB_BT709 * linearXyzBt709), 0.0, 1.0);
|
||||
return linearRgbBt709;
|
||||
}
|
||||
|
||||
// Apply the PQ BT2020 to BT709 OOTF.
|
||||
highp vec3 applyPqBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
float pqPeakLuminance = 10000.0;
|
||||
float sdrPeakLuminance = 500.0;
|
||||
|
||||
return linearRgbBt2020 * pqPeakLuminance / sdrPeakLuminance;
|
||||
}
|
||||
|
||||
highp vec3 applyBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return applyPqBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return applyHlgBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else {
|
||||
// Output green as an obviously visible error.
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 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));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_GAMMA2_2
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// 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 if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
|
||||
return linearColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 yuvToRgb(vec3 yuv) {
|
||||
const vec3 yuvOffset = vec3(0.0625, 0.5, 0.5);
|
||||
return clamp(uYuvToRgbColorTransform * (yuv - yuvOffset), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 srcYuv = texture(uTexSampler, vTexSamplingCoord).xyz;
|
||||
vec3 opticalColorBt2020 = applyEotf(yuvToRgb(srcYuv));
|
||||
vec4 opticalColor =
|
||||
(uApplyHdrToSdrToneMapping == 1)
|
||||
? vec4(applyBt2020ToBt709Ootf(opticalColorBt2020), 1.0)
|
||||
: vec4(opticalColorBt2020, 1.0);
|
||||
vec4 transformedColors = uRgbMatrix * opticalColor;
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
#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 electrical (HLG or PQ) BT.2020 RGB from an internal texture.
|
||||
// 2. Applies an EOTF based on uInputColorTransfer, yielding optical linear
|
||||
// BT.2020 RGB.
|
||||
// 3. Optionally applies a BT2020 to BT709 OOTF, if OpenGL tone-mapping is
|
||||
// requested via uApplyHdrToSdrToneMapping.
|
||||
// 4. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 5. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_ST2084 /
|
||||
// COLOR_TRANSFER_HLG to output electrical colors via an OETF (e.g. to an
|
||||
// encoder).
|
||||
// The output will be red or blue if an error has occurred.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_ST2084 and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
uniform int uApplyHdrToSdrToneMapping;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR, COLOR_TRANSFER_GAMMA_2_2, COLOR_TRANSFER_ST2084,
|
||||
// and COLOR_TRANSFER_HLG are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
in vec2 vTexSamplingCoord;
|
||||
out vec4 outColor;
|
||||
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
const int COLOR_TRANSFER_ST2084 = 6;
|
||||
const int COLOR_TRANSFER_HLG = 7;
|
||||
|
||||
// TODO(b/227624622): Consider using mediump to save precision, if it won't lead
|
||||
// to noticeable quantization errors.
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF for one channel.
|
||||
highp float hlgEotfSingleChannel(highp float hlgChannel) {
|
||||
// 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=265-279;drc=de09f10aa504fd8066370591a00c9ff1cafbb7fa
|
||||
const highp float a = 0.17883277;
|
||||
const highp float b = 0.28466892;
|
||||
const highp float c = 0.55991073;
|
||||
return hlgChannel <= 0.5 ? hlgChannel * hlgChannel / 3.0
|
||||
: (b + exp((hlgChannel - c) / a)) / 12.0;
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 HLG EOTF.
|
||||
highp vec3 hlgEotf(highp vec3 hlgColor) {
|
||||
return vec3(hlgEotfSingleChannel(hlgColor.r),
|
||||
hlgEotfSingleChannel(hlgColor.g),
|
||||
hlgEotfSingleChannel(hlgColor.b));
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 PQ EOTF.
|
||||
highp vec3 pqEotf(highp vec3 pqColor) {
|
||||
// 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=250-263;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(clamp(pqColor, 0.0, 1.0), 1.0 / vec3(m2));
|
||||
temp = max(temp - c1, 0.0) / (c2 - c3 * temp);
|
||||
return pow(temp, 1.0 / vec3(m1));
|
||||
}
|
||||
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical values to linear
|
||||
// optical values. Input and output are both normalized to [0, 1].
|
||||
highp vec3 applyEotf(highp vec3 electricalColor) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return pqEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return hlgEotf(electricalColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the HLG BT2020 to BT709 OOTF.
|
||||
highp vec3 applyHlgBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
// Reference ("HLG Reference OOTF" section):
|
||||
// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf
|
||||
// Matrix values based on computeXYZMatrix(BT2020Primaries, BT2020WhitePoint)
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/utils/HostColorSpace.cpp;l=200-232;drc=86bd214059cd6150304888a285941bf74af5b687
|
||||
const mat3 RGB_TO_XYZ_BT2020 =
|
||||
mat3(0.63695805f, 0.26270021f, 0.00000000f, 0.14461690f, 0.67799807f,
|
||||
0.02807269f, 0.16888098f, 0.05930172f, 1.06098506f);
|
||||
// Matrix values based on computeXYZMatrix(BT709Primaries, BT709WhitePoint)
|
||||
const mat3 XYZ_TO_RGB_BT709 =
|
||||
mat3(3.24096994f, -0.96924364f, 0.05563008f, -1.53738318f, 1.87596750f,
|
||||
-0.20397696f, -0.49861076f, 0.04155506f, 1.05697151f);
|
||||
// hlgGamma is 1.2 + 0.42 * log10(nominalPeakLuminance/1000);
|
||||
// nominalPeakLuminance was selected to use a 500 as a typical value, used
|
||||
// in
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/tonemap/tonemap.cpp;drc=7a577450e536aa1e99f229a0cb3d3531c82e8a8d;l=62,
|
||||
// b/199162498#comment35, and
|
||||
// https://www.microsoft.com/applied-sciences/uploads/projects/investigation-of-hdr-vs-tone-mapped-sdr/investigation-of-hdr-vs-tone-mapped-sdr.pdf.
|
||||
const float hlgGamma = 1.0735674018211279;
|
||||
|
||||
vec3 linearXyzBt2020 = RGB_TO_XYZ_BT2020 * linearRgbBt2020;
|
||||
vec3 linearXyzBt709 =
|
||||
linearXyzBt2020 * pow(linearXyzBt2020[1], hlgGamma - 1.0);
|
||||
vec3 linearRgbBt709 = clamp((XYZ_TO_RGB_BT709 * linearXyzBt709), 0.0, 1.0);
|
||||
return linearRgbBt709;
|
||||
}
|
||||
|
||||
// Apply the PQ BT2020 to BT709 OOTF.
|
||||
highp vec3 applyPqBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
float pqPeakLuminance = 10000.0;
|
||||
float sdrPeakLuminance = 500.0;
|
||||
|
||||
return linearRgbBt2020 * pqPeakLuminance / sdrPeakLuminance;
|
||||
}
|
||||
|
||||
highp vec3 applyBt2020ToBt709Ootf(highp vec3 linearRgbBt2020) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_ST2084) {
|
||||
return applyPqBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_HLG) {
|
||||
return applyHlgBt2020ToBt709Ootf(linearRgbBt2020);
|
||||
} else {
|
||||
// Output green as an obviously visible error.
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
// BT.2100 / BT.2020 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));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_GAMMA2_2
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// 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 if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR) {
|
||||
return linearColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 opticalColorBt2020 =
|
||||
applyEotf(texture(uTexSampler, vTexSamplingCoord).xyz);
|
||||
vec4 opticalColor =
|
||||
(uApplyHdrToSdrToneMapping == 1)
|
||||
? vec4(applyBt2020ToBt709Ootf(opticalColorBt2020), 1.0)
|
||||
: vec4(opticalColorBt2020, 1.0);
|
||||
vec4 transformedColors = uRgbMatrix * opticalColor;
|
||||
outColor = vec4(applyOetf(transformedColors.rgb), 1.0);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#version 100
|
||||
// Copyright 2021 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 2 fragment shader that:
|
||||
// 1. Samples from an external texture with uTexSampler copying from this
|
||||
// texture to the current output.
|
||||
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M
|
||||
// EOTF.
|
||||
// 3. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
|
||||
// output electrical colors via an OETF (e.g. to an encoder).
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
uniform samplerExternalOES uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
uniform int uEnableColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
const float gamma = 1.0 / inverseGamma;
|
||||
const int GL_FALSE = 0;
|
||||
const int GL_TRUE = 1;
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return electricalChannel < 0.0812
|
||||
? electricalChannel / 4.500
|
||||
: pow((electricalChannel + 0.099) / 1.099, gamma);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the SMPTE 170M EOTF.
|
||||
vec3 smpte170mEotf(vec3 electricalColor) {
|
||||
return vec3(smpte170mEotfSingleChannel(electricalColor.r),
|
||||
smpte170mEotfSingleChannel(electricalColor.g),
|
||||
smpte170mEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_LINEAR ||
|
||||
uEnableColorTransfer == GL_FALSE) {
|
||||
return linearColor;
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 applyEotf(vec3 electricalColor) {
|
||||
if (uEnableColorTransfer == GL_TRUE) {
|
||||
return smpte170mEotf(electricalColor);
|
||||
} else if (uEnableColorTransfer == GL_FALSE) {
|
||||
return electricalColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec3 linearInputColor = applyEotf(inputColor.rgb);
|
||||
|
||||
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#version 100
|
||||
// Copyright 2023 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 2 fragment shader that:
|
||||
// 1. Samples from an input texture created from an internal texture (e.g. a
|
||||
// texture created from a bitmap), with uTexSampler copying from this texture
|
||||
// to the current output.
|
||||
// 2. Transforms the electrical colors to optical colors using the SMPTE 170M
|
||||
// EOTF or the sRGB EOTF, as requested by uInputColorTransfer.
|
||||
// 3. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 4. Outputs as requested by uOutputColorTransfer. Use COLOR_TRANSFER_LINEAR
|
||||
// for outputting to intermediate shaders, or COLOR_TRANSFER_SDR_VIDEO to
|
||||
// output electrical colors via an OETF (e.g. to an encoder).
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_SRGB and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uInputColorTransfer;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_LINEAR and COLOR_TRANSFER_SDR_VIDEO are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
uniform int uEnableColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
const float gamma = 1.0 / inverseGamma;
|
||||
const int GL_FALSE = 0;
|
||||
const int GL_TRUE = 1;
|
||||
// LINT.IfChange(color_transfer)
|
||||
const int COLOR_TRANSFER_LINEAR = 1;
|
||||
const int COLOR_TRANSFER_SRGB = 2;
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the sRGB
|
||||
// EOTF.
|
||||
float srgbEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://developer.android.com/ndk/reference/group/a-data-space#group___a_data_space_1gga2759ad19cae46646cc5f7002758c4a1cac1bef6aa3a72abbf4a651a0bfb117f96
|
||||
return electricalChannel <= 0.04045
|
||||
? electricalChannel / 12.92
|
||||
: pow((electricalChannel + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the sRGB EOTF.
|
||||
vec3 srgbEotf(const vec3 electricalColor) {
|
||||
return vec3(srgbEotfSingleChannel(electricalColor.r),
|
||||
srgbEotfSingleChannel(electricalColor.g),
|
||||
srgbEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from electrical to optical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mEotfSingleChannel(float electricalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return electricalChannel < 0.0812
|
||||
? electricalChannel / 4.500
|
||||
: pow((electricalChannel + 0.099) / 1.099, gamma);
|
||||
}
|
||||
|
||||
// Transforms electrical to optical SDR using the SMPTE 170M EOTF.
|
||||
vec3 smpte170mEotf(vec3 electricalColor) {
|
||||
return vec3(smpte170mEotfSingleChannel(electricalColor.r),
|
||||
smpte170mEotfSingleChannel(electricalColor.g),
|
||||
smpte170mEotfSingleChannel(electricalColor.b));
|
||||
}
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
// Applies the appropriate EOTF to convert nonlinear electrical signals to
|
||||
// linear optical signals. Input and output are both normalized to [0, 1].
|
||||
vec3 applyEotf(vec3 electricalColor) {
|
||||
if (uEnableColorTransfer == GL_TRUE) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
|
||||
return srgbEotf(electricalColor);
|
||||
} else if (uInputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mEotf(electricalColor);
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
} else if (uEnableColorTransfer == GL_FALSE) {
|
||||
return electricalColor;
|
||||
} else {
|
||||
// Output blue as an obviously visible error.
|
||||
return vec3(0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// 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_LINEAR ||
|
||||
uEnableColorTransfer == GL_FALSE) {
|
||||
return linearColor;
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 getAdjustedTexSamplingCoord(vec2 originalTexSamplingCoord) {
|
||||
if (uInputColorTransfer == COLOR_TRANSFER_SRGB) {
|
||||
// Whereas the Android system uses the top-left corner as (0,0) of the
|
||||
// coordinate system, OpenGL uses the bottom-left corner as (0,0), so the
|
||||
// texture gets flipped. We flip the texture vertically to ensure the
|
||||
// orientation of the output is correct.
|
||||
return vec2(originalTexSamplingCoord.x, 1.0 - originalTexSamplingCoord.y);
|
||||
} else {
|
||||
return originalTexSamplingCoord;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor =
|
||||
texture2D(uTexSampler, getAdjustedTexSamplingCoord(vTexSamplingCoord));
|
||||
vec3 linearInputColor = applyEotf(inputColor.rgb);
|
||||
vec4 transformedColors = uRgbMatrix * vec4(linearInputColor, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#version 100
|
||||
// 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 2 fragment shader that:
|
||||
// 1. Samples from uTexSampler, copying from this texture to the current
|
||||
// output.
|
||||
// 2. Applies a 4x4 RGB color matrix to change the pixel colors.
|
||||
// 3. Transforms the optical colors to electrical colors using the SMPTE
|
||||
// 170M OETF.
|
||||
|
||||
precision mediump float;
|
||||
uniform sampler2D uTexSampler;
|
||||
uniform mat4 uRgbMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
// C.java#ColorTransfer value.
|
||||
// Only COLOR_TRANSFER_SDR and COLOR_TRANSFER_GAMMA_2_2 are allowed.
|
||||
uniform int uOutputColorTransfer;
|
||||
|
||||
const float inverseGamma = 0.4500;
|
||||
|
||||
// Transforms a single channel from optical to electrical SDR using the SMPTE
|
||||
// 170M OETF.
|
||||
float smpte170mOetfSingleChannel(float opticalChannel) {
|
||||
// Specification:
|
||||
// https://www.itu.int/rec/R-REC-BT.1700-0-200502-I/en
|
||||
return opticalChannel < 0.018
|
||||
? opticalChannel * 4.500
|
||||
: 1.099 * pow(opticalChannel, inverseGamma) - 0.099;
|
||||
}
|
||||
|
||||
// Transforms optical SDR colors to electrical SDR using the SMPTE 170M OETF.
|
||||
vec3 smpte170mOetf(vec3 opticalColor) {
|
||||
return vec3(smpte170mOetfSingleChannel(opticalColor.r),
|
||||
smpte170mOetfSingleChannel(opticalColor.g),
|
||||
smpte170mOetfSingleChannel(opticalColor.b));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF for one channel.
|
||||
float gamma22OetfSingleChannel(highp float linearChannel) {
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/hardware/DataSpace#TRANSFER_gamma22
|
||||
return pow(linearChannel, (1.0 / 2.2));
|
||||
}
|
||||
|
||||
// BT.709 gamma 2.2 OETF.
|
||||
vec3 gamma22Oetf(highp vec3 linearColor) {
|
||||
return vec3(gamma22OetfSingleChannel(linearColor.r),
|
||||
gamma22OetfSingleChannel(linearColor.g),
|
||||
gamma22OetfSingleChannel(linearColor.b));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// LINT.IfChange(color_transfer_oetf)
|
||||
const int COLOR_TRANSFER_SDR_VIDEO = 3;
|
||||
const int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||
if (uOutputColorTransfer == COLOR_TRANSFER_SDR_VIDEO) {
|
||||
return smpte170mOetf(linearColor);
|
||||
} else if (uOutputColorTransfer == COLOR_TRANSFER_GAMMA_2_2) {
|
||||
return gamma22Oetf(linearColor);
|
||||
} else {
|
||||
// Output red as an obviously visible error.
|
||||
return vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
|
||||
vec4 transformedColors = uRgbMatrix * vec4(inputColor.rgb, 1);
|
||||
|
||||
gl_FragColor = vec4(applyOetf(transformedColors.rgb), inputColor.a);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#version 100
|
||||
// Copyright 2023 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.
|
||||
|
||||
// ES2 vertex shader that tiles frames horizontally.
|
||||
|
||||
attribute vec4 aFramePosition;
|
||||
uniform int uIndex;
|
||||
uniform int uCount;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
|
||||
void main() {
|
||||
// Translate the coordinates from -1,+1 to 0,+2.
|
||||
float x = aFramePosition.x + 1.0;
|
||||
// Offset the frame by its index times its width (2).
|
||||
x += float(uIndex) * 2.0;
|
||||
// Shrink the frame to fit the thumbnail strip.
|
||||
x /= float(uCount);
|
||||
// Translate the coordinates back to -1,+1.
|
||||
x -= 1.0;
|
||||
|
||||
gl_Position = vec4(x, aFramePosition.yzw);
|
||||
vTexSamplingCoord = aFramePosition.xy * 0.5 + 0.5;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#version 100
|
||||
// Copyright 2021 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 2 vertex shader that applies the 4 * 4 transformation matrices
|
||||
// uTransformationMatrix and the uTexTransformationMatrix.
|
||||
|
||||
attribute vec4 aFramePosition;
|
||||
uniform mat4 uTransformationMatrix;
|
||||
uniform mat4 uTexTransformationMatrix;
|
||||
varying vec2 vTexSamplingCoord;
|
||||
void main() {
|
||||
gl_Position = uTransformationMatrix * aFramePosition;
|
||||
vec4 texturePosition = vec4(aFramePosition.x * 0.5 + 0.5,
|
||||
aFramePosition.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
vTexSamplingCoord = (uTexTransformationMatrix * texturePosition).xy;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#version 300 es
|
||||
// Copyright 2021 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 vertex shader that applies the 4 * 4 transformation matrices
|
||||
// uTransformationMatrix and the uTexTransformationMatrix.
|
||||
|
||||
in vec4 aFramePosition;
|
||||
uniform mat4 uTransformationMatrix;
|
||||
uniform mat4 uTexTransformationMatrix;
|
||||
out vec2 vTexSamplingCoord;
|
||||
void main() {
|
||||
gl_Position = uTransformationMatrix * aFramePosition;
|
||||
vec4 texturePosition = vec4(aFramePosition.x * 0.5 + 0.5,
|
||||
aFramePosition.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
vTexSamplingCoord = (uTexTransformationMatrix * texturePosition).xy;
|
||||
}
|
||||
Reference in New Issue
Block a user