blob: dc88969a67d8030db20ad697fa48a486105e8757 [file] [log] [blame]
// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/output/shader.h"
#include <stddef.h>
#include <algorithm>
#include <vector>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "cc/output/static_geometry_binding.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "ui/gfx/color_transform.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/size.h"
template <size_t size>
std::string StripLambda(const char(&shader)[size]) {
// Must contain at least "[]() {}" and trailing null (included in size).
static_assert(size >= 8,
"String passed to StripLambda must be at least 8 characters");
DCHECK_EQ(strncmp("[]() {", shader, 6), 0);
DCHECK_EQ(shader[size - 2], '}');
return std::string(shader + 6, shader + size - 2);
}
// Shaders are passed in with lambda syntax, which tricks clang-format into
// handling them correctly. StipLambda removes this.
#define SHADER0(Src) StripLambda(#Src)
#define HDR(x) \
do { \
header += x + std::string("\n"); \
} while (0)
#define SRC(x) \
do { \
source += std::string(" ") + x + std::string("\n"); \
} while (0)
using gpu::gles2::GLES2Interface;
namespace cc {
namespace {
static void GetProgramUniformLocations(GLES2Interface* context,
unsigned program,
size_t count,
const char** uniforms,
int* locations,
int* base_uniform_index) {
for (size_t i = 0; i < count; i++) {
locations[i] = (*base_uniform_index)++;
context->BindUniformLocationCHROMIUM(program, locations[i], uniforms[i]);
}
}
static std::string SetFragmentTexCoordPrecision(
TexCoordPrecision requested_precision,
std::string shader_string) {
std::string prefix;
switch (requested_precision) {
case TEX_COORD_PRECISION_HIGH:
DCHECK_NE(shader_string.find("TexCoordPrecision"), std::string::npos);
prefix =
"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"
" #define TexCoordPrecision highp\n"
"#else\n"
" #define TexCoordPrecision mediump\n"
"#endif\n";
break;
case TEX_COORD_PRECISION_MEDIUM:
DCHECK_NE(shader_string.find("TexCoordPrecision"), std::string::npos);
prefix = "#define TexCoordPrecision mediump\n";
break;
case TEX_COORD_PRECISION_NA:
DCHECK_EQ(shader_string.find("TexCoordPrecision"), std::string::npos);
DCHECK_EQ(shader_string.find("texture2D"), std::string::npos);
DCHECK_EQ(shader_string.find("texture2DRect"), std::string::npos);
break;
default:
NOTREACHED();
break;
}
std::string lut_prefix = "#define LutLookup texture2D\n";
return prefix + lut_prefix + shader_string;
}
TexCoordPrecision TexCoordPrecisionRequired(GLES2Interface* context,
int* highp_threshold_cache,
int highp_threshold_min,
int x,
int y) {
if (*highp_threshold_cache == 0) {
// Initialize range and precision with minimum spec values for when
// GetShaderPrecisionFormat is a test stub.
// TODO(brianderson): Implement better stubs of GetShaderPrecisionFormat
// everywhere.
GLint range[2] = {14, 14};
GLint precision = 10;
context->GetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT,
range, &precision);
*highp_threshold_cache = 1 << precision;
}
int highp_threshold = std::max(*highp_threshold_cache, highp_threshold_min);
if (x > highp_threshold || y > highp_threshold)
return TEX_COORD_PRECISION_HIGH;
return TEX_COORD_PRECISION_MEDIUM;
}
static std::string SetFragmentSamplerType(SamplerType requested_type,
std::string shader_string) {
switch (requested_type) {
case SAMPLER_TYPE_2D:
DCHECK_NE(shader_string.find("SamplerType"), std::string::npos);
DCHECK_NE(shader_string.find("TextureLookup"), std::string::npos);
return "#define SamplerType sampler2D\n"
"#define TextureLookup texture2D\n" +
shader_string;
case SAMPLER_TYPE_2D_RECT:
DCHECK_NE(shader_string.find("SamplerType"), std::string::npos);
DCHECK_NE(shader_string.find("TextureLookup"), std::string::npos);
return "#extension GL_ARB_texture_rectangle : require\n"
"#define SamplerType sampler2DRect\n"
"#define TextureLookup texture2DRect\n" +
shader_string;
case SAMPLER_TYPE_EXTERNAL_OES:
DCHECK_NE(shader_string.find("SamplerType"), std::string::npos);
DCHECK_NE(shader_string.find("TextureLookup"), std::string::npos);
return "#extension GL_OES_EGL_image_external : enable\n"
"#extension GL_NV_EGL_stream_consumer_external : enable\n"
"#define SamplerType samplerExternalOES\n"
"#define TextureLookup texture2D\n" +
shader_string;
case SAMPLER_TYPE_NA:
DCHECK_EQ(shader_string.find("SamplerType"), std::string::npos);
DCHECK_EQ(shader_string.find("TextureLookup"), std::string::npos);
return shader_string;
default:
NOTREACHED();
break;
}
return shader_string;
}
} // namespace
TexCoordPrecision TexCoordPrecisionRequired(GLES2Interface* context,
int* highp_threshold_cache,
int highp_threshold_min,
const gfx::Point& max_coordinate) {
return TexCoordPrecisionRequired(context,
highp_threshold_cache,
highp_threshold_min,
max_coordinate.x(),
max_coordinate.y());
}
TexCoordPrecision TexCoordPrecisionRequired(GLES2Interface* context,
int* highp_threshold_cache,
int highp_threshold_min,
const gfx::Size& max_size) {
return TexCoordPrecisionRequired(context,
highp_threshold_cache,
highp_threshold_min,
max_size.width(),
max_size.height());
}
VertexShader::VertexShader() {}
void VertexShader::Init(GLES2Interface* context,
unsigned program,
int* base_uniform_index) {
std::vector<const char*> uniforms;
std::vector<int> locations;
switch (tex_coord_transform_) {
case TEX_COORD_TRANSFORM_NONE:
break;
case TEX_COORD_TRANSFORM_VEC4:
case TEX_COORD_TRANSFORM_TRANSLATED_VEC4:
uniforms.push_back("vertexTexTransform");
break;
case TEX_COORD_TRANSFORM_MATRIX:
uniforms.push_back("texMatrix");
break;
}
if (is_ya_uv_) {
uniforms.push_back("yaTexScale");
uniforms.push_back("yaTexOffset");
uniforms.push_back("uvTexScale");
uniforms.push_back("uvTexOffset");
}
if (has_matrix_)
uniforms.push_back("matrix");
if (has_vertex_opacity_)
uniforms.push_back("opacity");
if (aa_mode_ == USE_AA) {
uniforms.push_back("viewport");
uniforms.push_back("edge");
}
if (position_source_ == POSITION_SOURCE_ATTRIBUTE_INDEXED_UNIFORM)
uniforms.push_back("quad");
locations.resize(uniforms.size());
GetProgramUniformLocations(context, program, uniforms.size(), uniforms.data(),
locations.data(), base_uniform_index);
size_t index = 0;
switch (tex_coord_transform_) {
case TEX_COORD_TRANSFORM_NONE:
break;
case TEX_COORD_TRANSFORM_VEC4:
case TEX_COORD_TRANSFORM_TRANSLATED_VEC4:
vertex_tex_transform_location_ = locations[index++];
break;
case TEX_COORD_TRANSFORM_MATRIX:
tex_matrix_location_ = locations[index++];
break;
}
if (is_ya_uv_) {
ya_tex_scale_location_ = locations[index++];
ya_tex_offset_location_ = locations[index++];
uv_tex_scale_location_ = locations[index++];
uv_tex_offset_location_ = locations[index++];
}
if (has_matrix_)
matrix_location_ = locations[index++];
if (has_vertex_opacity_)
vertex_opacity_location_ = locations[index++];
if (aa_mode_ == USE_AA) {
viewport_location_ = locations[index++];
edge_location_ = locations[index++];
}
if (position_source_ == POSITION_SOURCE_ATTRIBUTE_INDEXED_UNIFORM)
quad_location_ = locations[index++];
}
std::string VertexShader::GetShaderString() const {
// We unconditionally use highp in the vertex shader since
// we are unlikely to be vertex shader bound when drawing large quads.
// Also, some vertex shaders mutate the texture coordinate in such a
// way that the effective precision might be lower than expected.
std::string header = "#define TexCoordPrecision highp\n";
std::string source = "void main() {\n";
// Define the size of quads for attribute indexed uniform arrays.
if (use_uniform_arrays_) {
header += base::StringPrintf("#define NUM_QUADS %d\n",
StaticGeometryBinding::NUM_QUADS);
}
// Read the index variables.
if (use_uniform_arrays_ ||
position_source_ == POSITION_SOURCE_ATTRIBUTE_INDEXED_UNIFORM) {
HDR("attribute float a_index;");
SRC("// Compute indices for uniform arrays.");
SRC("int vertex_index = int(a_index);");
if (use_uniform_arrays_)
SRC("int quad_index = int(a_index * 0.25);");
SRC("");
}
// Read the position and compute gl_Position.
HDR("attribute TexCoordPrecision vec4 a_position;");
SRC("// Compute the position.");
switch (position_source_) {
case POSITION_SOURCE_ATTRIBUTE:
SRC("vec4 pos = a_position;");
break;
case POSITION_SOURCE_ATTRIBUTE_INDEXED_UNIFORM:
HDR("uniform TexCoordPrecision vec2 quad[4];");
SRC("vec4 pos = vec4(quad[vertex_index], a_position.z, a_position.w);");
break;
}
if (has_matrix_) {
if (use_uniform_arrays_) {
HDR("uniform mat4 matrix[NUM_QUADS];");
SRC("gl_Position = matrix[quad_index] * pos;");
} else {
HDR("uniform mat4 matrix;");
SRC("gl_Position = matrix * pos;");
}
} else {
SRC("gl_Position = pos;");
}
// Compute the anti-aliasing edge distances.
if (aa_mode_ == USE_AA) {
HDR("uniform TexCoordPrecision vec3 edge[8];");
HDR("uniform vec4 viewport;");
HDR("varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.");
SRC("// Compute anti-aliasing properties.\n");
SRC("vec2 ndc_pos = 0.5 * (1.0 + gl_Position.xy / gl_Position.w);");
SRC("vec3 screen_pos = vec3(viewport.xy + viewport.zw * ndc_pos, 1.0);");
SRC("edge_dist[0] = vec4(dot(edge[0], screen_pos),");
SRC(" dot(edge[1], screen_pos),");
SRC(" dot(edge[2], screen_pos),");
SRC(" dot(edge[3], screen_pos)) * gl_Position.w;");
SRC("edge_dist[1] = vec4(dot(edge[4], screen_pos),");
SRC(" dot(edge[5], screen_pos),");
SRC(" dot(edge[6], screen_pos),");
SRC(" dot(edge[7], screen_pos)) * gl_Position.w;");
}
// Read, transform, and write texture coordinates.
if (tex_coord_source_ != TEX_COORD_SOURCE_NONE) {
if (is_ya_uv_) {
HDR("varying TexCoordPrecision vec2 v_uvTexCoord;");
HDR("varying TexCoordPrecision vec2 v_yaTexCoord;");
} else {
HDR("varying TexCoordPrecision vec2 v_texCoord;");
}
SRC("// Compute texture coordinates.");
// Read coordinates.
switch (tex_coord_source_) {
case TEX_COORD_SOURCE_NONE:
break;
case TEX_COORD_SOURCE_POSITION:
SRC("vec2 texCoord = pos.xy;");
break;
case TEX_COORD_SOURCE_ATTRIBUTE:
HDR("attribute TexCoordPrecision vec2 a_texCoord;");
SRC("vec2 texCoord = a_texCoord;");
break;
}
// Transform coordinates (except YUV).
switch (tex_coord_transform_) {
case TEX_COORD_TRANSFORM_NONE:
break;
case TEX_COORD_TRANSFORM_TRANSLATED_VEC4:
SRC("texCoord = texCoord + vec2(0.5);");
// Fall through...
case TEX_COORD_TRANSFORM_VEC4:
if (use_uniform_arrays_) {
HDR("uniform TexCoordPrecision vec4 vertexTexTransform[NUM_QUADS];");
SRC("TexCoordPrecision vec4 texTrans =");
SRC(" vertexTexTransform[quad_index];");
SRC("texCoord = texCoord * texTrans.zw + texTrans.xy;");
} else {
HDR("uniform TexCoordPrecision vec4 vertexTexTransform;");
SRC("texCoord = texCoord * vertexTexTransform.zw +");
SRC(" vertexTexTransform.xy;");
}
break;
case TEX_COORD_TRANSFORM_MATRIX:
HDR("uniform TexCoordPrecision mat4 texMatrix;");
SRC("texCoord = (texMatrix * vec4(texCoord.xy, 0.0, 1.0)).xy;");
break;
}
// Write the output texture coordinates.
if (is_ya_uv_) {
HDR("uniform TexCoordPrecision vec2 uvTexOffset;");
HDR("uniform TexCoordPrecision vec2 uvTexScale;");
HDR("uniform TexCoordPrecision vec2 yaTexOffset;");
HDR("uniform TexCoordPrecision vec2 yaTexScale;");
SRC("v_yaTexCoord = texCoord * yaTexScale + yaTexOffset;");
SRC("v_uvTexCoord = texCoord * uvTexScale + uvTexOffset;");
} else {
SRC("v_texCoord = texCoord;");
}
}
// Write varying vertex opacity.
if (has_vertex_opacity_) {
DCHECK(use_uniform_arrays_);
HDR("uniform float opacity[NUM_QUADS * 4];");
HDR("varying float v_alpha;");
SRC("v_alpha = opacity[vertex_index];");
}
// Add cargo-culted dummy variables for Android.
if (has_dummy_variables_) {
HDR("uniform TexCoordPrecision vec2 dummy_uniform;");
HDR("varying TexCoordPrecision vec2 dummy_varying;");
SRC("dummy_varying = dummy_uniform;");
}
source += "}\n";
return header + source;
}
FragmentShader::FragmentShader() {}
std::string FragmentShader::GetShaderString() const {
TexCoordPrecision precision = tex_coord_precision_;
// The AA shader values will use TexCoordPrecision.
if (aa_mode_ == USE_AA && precision == TEX_COORD_PRECISION_NA)
precision = TEX_COORD_PRECISION_MEDIUM;
return SetFragmentTexCoordPrecision(
precision, SetFragmentSamplerType(
sampler_type_, SetBlendModeFunctions(GetShaderSource())));
}
void FragmentShader::Init(GLES2Interface* context,
unsigned program,
int* base_uniform_index) {
std::vector<const char*> uniforms;
std::vector<int> locations;
if (has_blend_mode()) {
uniforms.push_back("s_backdropTexture");
uniforms.push_back("s_originalBackdropTexture");
uniforms.push_back("backdropRect");
}
if (mask_mode_ != NO_MASK) {
uniforms.push_back("s_mask");
uniforms.push_back("maskTexCoordScale");
uniforms.push_back("maskTexCoordOffset");
}
if (has_color_matrix_) {
uniforms.push_back("colorMatrix");
uniforms.push_back("colorOffset");
}
if (has_uniform_alpha_)
uniforms.push_back("alpha");
if (has_background_color_)
uniforms.push_back("background_color");
switch (input_color_type_) {
case INPUT_COLOR_SOURCE_RGBA_TEXTURE:
uniforms.push_back("s_texture");
if (has_rgba_fragment_tex_transform_)
uniforms.push_back("fragmentTexTransform");
break;
case INPUT_COLOR_SOURCE_YUV_TEXTURES:
uniforms.push_back("y_texture");
if (uv_texture_mode_ == UV_TEXTURE_MODE_UV)
uniforms.push_back("uv_texture");
if (uv_texture_mode_ == UV_TEXTURE_MODE_U_V) {
uniforms.push_back("u_texture");
uniforms.push_back("v_texture");
}
if (yuv_alpha_texture_mode_ == YUV_HAS_ALPHA_TEXTURE)
uniforms.push_back("a_texture");
uniforms.push_back("ya_clamp_rect");
uniforms.push_back("uv_clamp_rect");
uniforms.push_back("resource_multiplier");
uniforms.push_back("resource_offset");
break;
case INPUT_COLOR_SOURCE_UNIFORM:
uniforms.push_back("color");
break;
}
if (color_conversion_mode_ == COLOR_CONVERSION_MODE_LUT) {
uniforms.push_back("lut_texture");
uniforms.push_back("lut_size");
}
locations.resize(uniforms.size());
GetProgramUniformLocations(context, program, uniforms.size(), uniforms.data(),
locations.data(), base_uniform_index);
size_t index = 0;
if (has_blend_mode()) {
backdrop_location_ = locations[index++];
original_backdrop_location_ = locations[index++];
backdrop_rect_location_ = locations[index++];
}
if (mask_mode_ != NO_MASK) {
mask_sampler_location_ = locations[index++];
mask_tex_coord_scale_location_ = locations[index++];
mask_tex_coord_offset_location_ = locations[index++];
}
if (has_color_matrix_) {
color_matrix_location_ = locations[index++];
color_offset_location_ = locations[index++];
}
if (has_uniform_alpha_)
alpha_location_ = locations[index++];
if (has_background_color_)
background_color_location_ = locations[index++];
switch (input_color_type_) {
case INPUT_COLOR_SOURCE_RGBA_TEXTURE:
sampler_location_ = locations[index++];
if (has_rgba_fragment_tex_transform_)
fragment_tex_transform_location_ = locations[index++];
break;
case INPUT_COLOR_SOURCE_YUV_TEXTURES:
y_texture_location_ = locations[index++];
if (uv_texture_mode_ == UV_TEXTURE_MODE_UV)
uv_texture_location_ = locations[index++];
if (uv_texture_mode_ == UV_TEXTURE_MODE_U_V) {
u_texture_location_ = locations[index++];
v_texture_location_ = locations[index++];
}
if (yuv_alpha_texture_mode_ == YUV_HAS_ALPHA_TEXTURE)
a_texture_location_ = locations[index++];
ya_clamp_rect_location_ = locations[index++];
uv_clamp_rect_location_ = locations[index++];
resource_multiplier_location_ = locations[index++];
resource_offset_location_ = locations[index++];
break;
case INPUT_COLOR_SOURCE_UNIFORM:
color_location_ = locations[index++];
break;
}
if (color_conversion_mode_ == COLOR_CONVERSION_MODE_LUT) {
lut_texture_location_ = locations[index++];
lut_size_location_ = locations[index++];
}
DCHECK_EQ(index, locations.size());
}
std::string FragmentShader::SetBlendModeFunctions(
const std::string& shader_string) const {
if (shader_string.find("ApplyBlendMode") == std::string::npos)
return shader_string;
if (!has_blend_mode()) {
return "#define ApplyBlendMode(X, Y) (X)\n" + shader_string;
}
static const std::string kUniforms = SHADER0([]() {
uniform sampler2D s_backdropTexture;
uniform sampler2D s_originalBackdropTexture;
uniform TexCoordPrecision vec4 backdropRect;
});
std::string mixFunction;
if (mask_for_background_) {
mixFunction = SHADER0([]() {
vec4 MixBackdrop(TexCoordPrecision vec2 bgTexCoord, float mask) {
vec4 backdrop = texture2D(s_backdropTexture, bgTexCoord);
vec4 original_backdrop =
texture2D(s_originalBackdropTexture, bgTexCoord);
return mix(original_backdrop, backdrop, mask);
}
});
} else {
mixFunction = SHADER0([]() {
vec4 MixBackdrop(TexCoordPrecision vec2 bgTexCoord, float mask) {
return texture2D(s_backdropTexture, bgTexCoord);
}
});
}
static const std::string kFunctionApplyBlendMode = SHADER0([]() {
vec4 GetBackdropColor(float mask) {
TexCoordPrecision vec2 bgTexCoord = gl_FragCoord.xy - backdropRect.xy;
bgTexCoord.x /= backdropRect.z;
bgTexCoord.y /= backdropRect.w;
return MixBackdrop(bgTexCoord, mask);
}
vec4 ApplyBlendMode(vec4 src, float mask) {
vec4 dst = GetBackdropColor(mask);
return Blend(src, dst);
}
});
return "precision mediump float;" + GetHelperFunctions() +
GetBlendFunction() + kUniforms + mixFunction +
kFunctionApplyBlendMode + shader_string;
}
std::string FragmentShader::GetHelperFunctions() const {
static const std::string kFunctionHardLight = SHADER0([]() {
vec3 hardLight(vec4 src, vec4 dst) {
vec3 result;
result.r =
(2.0 * src.r <= src.a)
? (2.0 * src.r * dst.r)
: (src.a * dst.a - 2.0 * (dst.a - dst.r) * (src.a - src.r));
result.g =
(2.0 * src.g <= src.a)
? (2.0 * src.g * dst.g)
: (src.a * dst.a - 2.0 * (dst.a - dst.g) * (src.a - src.g));
result.b =
(2.0 * src.b <= src.a)
? (2.0 * src.b * dst.b)
: (src.a * dst.a - 2.0 * (dst.a - dst.b) * (src.a - src.b));
result.rgb += src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a);
return result;
}
});
static const std::string kFunctionColorDodgeComponent = SHADER0([]() {
float getColorDodgeComponent(float srcc, float srca, float dstc,
float dsta) {
if (0.0 == dstc)
return srcc * (1.0 - dsta);
float d = srca - srcc;
if (0.0 == d)
return srca * dsta + srcc * (1.0 - dsta) + dstc * (1.0 - srca);
d = min(dsta, dstc * srca / d);
return d * srca + srcc * (1.0 - dsta) + dstc * (1.0 - srca);
}
});
static const std::string kFunctionColorBurnComponent = SHADER0([]() {
float getColorBurnComponent(float srcc, float srca, float dstc,
float dsta) {
if (dsta == dstc)
return srca * dsta + srcc * (1.0 - dsta) + dstc * (1.0 - srca);
if (0.0 == srcc)
return dstc * (1.0 - srca);
float d = max(0.0, dsta - (dsta - dstc) * srca / srcc);
return srca * d + srcc * (1.0 - dsta) + dstc * (1.0 - srca);
}
});
static const std::string kFunctionSoftLightComponentPosDstAlpha =
SHADER0([]() {
float getSoftLightComponent(float srcc, float srca, float dstc,
float dsta) {
if (2.0 * srcc <= srca) {
return (dstc * dstc * (srca - 2.0 * srcc)) / dsta +
(1.0 - dsta) * srcc + dstc * (-srca + 2.0 * srcc + 1.0);
} else if (4.0 * dstc <= dsta) {
float DSqd = dstc * dstc;
float DCub = DSqd * dstc;
float DaSqd = dsta * dsta;
float DaCub = DaSqd * dsta;
return (-DaCub * srcc +
DaSqd * (srcc - dstc * (3.0 * srca - 6.0 * srcc - 1.0)) +
12.0 * dsta * DSqd * (srca - 2.0 * srcc) -
16.0 * DCub * (srca - 2.0 * srcc)) /
DaSqd;
} else {
return -sqrt(dsta * dstc) * (srca - 2.0 * srcc) - dsta * srcc +
dstc * (srca - 2.0 * srcc + 1.0) + srcc;
}
}
});
static const std::string kFunctionLum = SHADER0([]() {
float luminance(vec3 color) { return dot(vec3(0.3, 0.59, 0.11), color); }
vec3 set_luminance(vec3 hueSat, float alpha, vec3 lumColor) {
float diff = luminance(lumColor - hueSat);
vec3 outColor = hueSat + diff;
float outLum = luminance(outColor);
float minComp = min(min(outColor.r, outColor.g), outColor.b);
float maxComp = max(max(outColor.r, outColor.g), outColor.b);
if (minComp < 0.0 && outLum != minComp) {
outColor = outLum +
((outColor - vec3(outLum, outLum, outLum)) * outLum) /
(outLum - minComp);
}
if (maxComp > alpha && maxComp != outLum) {
outColor =
outLum +
((outColor - vec3(outLum, outLum, outLum)) * (alpha - outLum)) /
(maxComp - outLum);
}
return outColor;
}
});
static const std::string kFunctionSat = SHADER0([]() {
float saturation(vec3 color) {
return max(max(color.r, color.g), color.b) -
min(min(color.r, color.g), color.b);
}
vec3 set_saturation_helper(float minComp, float midComp, float maxComp,
float sat) {
if (minComp < maxComp) {
vec3 result;
result.r = 0.0;
result.g = sat * (midComp - minComp) / (maxComp - minComp);
result.b = sat;
return result;
} else {
return vec3(0, 0, 0);
}
}
vec3 set_saturation(vec3 hueLumColor, vec3 satColor) {
float sat = saturation(satColor);
if (hueLumColor.r <= hueLumColor.g) {
if (hueLumColor.g <= hueLumColor.b) {
hueLumColor.rgb = set_saturation_helper(hueLumColor.r, hueLumColor.g,
hueLumColor.b, sat);
} else if (hueLumColor.r <= hueLumColor.b) {
hueLumColor.rbg = set_saturation_helper(hueLumColor.r, hueLumColor.b,
hueLumColor.g, sat);
} else {
hueLumColor.brg = set_saturation_helper(hueLumColor.b, hueLumColor.r,
hueLumColor.g, sat);
}
} else if (hueLumColor.r <= hueLumColor.b) {
hueLumColor.grb = set_saturation_helper(hueLumColor.g, hueLumColor.r,
hueLumColor.b, sat);
} else if (hueLumColor.g <= hueLumColor.b) {
hueLumColor.gbr = set_saturation_helper(hueLumColor.g, hueLumColor.b,
hueLumColor.r, sat);
} else {
hueLumColor.bgr = set_saturation_helper(hueLumColor.b, hueLumColor.g,
hueLumColor.r, sat);
}
return hueLumColor;
}
});
switch (blend_mode_) {
case BLEND_MODE_OVERLAY:
case BLEND_MODE_HARD_LIGHT:
return kFunctionHardLight;
case BLEND_MODE_COLOR_DODGE:
return kFunctionColorDodgeComponent;
case BLEND_MODE_COLOR_BURN:
return kFunctionColorBurnComponent;
case BLEND_MODE_SOFT_LIGHT:
return kFunctionSoftLightComponentPosDstAlpha;
case BLEND_MODE_HUE:
case BLEND_MODE_SATURATION:
return kFunctionLum + kFunctionSat;
case BLEND_MODE_COLOR:
case BLEND_MODE_LUMINOSITY:
return kFunctionLum;
default:
return std::string();
}
}
std::string FragmentShader::GetBlendFunction() const {
return "vec4 Blend(vec4 src, vec4 dst) {"
" vec4 result;" +
GetBlendFunctionBodyForAlpha() + GetBlendFunctionBodyForRGB() +
" return result;"
"}";
}
std::string FragmentShader::GetBlendFunctionBodyForAlpha() const {
if (blend_mode_ == BLEND_MODE_DESTINATION_IN)
return "result.a = src.a * dst.a;";
else
return "result.a = src.a + (1.0 - src.a) * dst.a;";
}
std::string FragmentShader::GetBlendFunctionBodyForRGB() const {
switch (blend_mode_) {
case BLEND_MODE_NORMAL:
return "result.rgb = src.rgb + dst.rgb * (1.0 - src.a);";
case BLEND_MODE_DESTINATION_IN:
return "result.rgb = dst.rgb * src.a;";
case BLEND_MODE_SCREEN:
return "result.rgb = src.rgb + (1.0 - src.rgb) * dst.rgb;";
case BLEND_MODE_LIGHTEN:
return "result.rgb = max((1.0 - src.a) * dst.rgb + src.rgb,"
" (1.0 - dst.a) * src.rgb + dst.rgb);";
case BLEND_MODE_OVERLAY:
return "result.rgb = hardLight(dst, src);";
case BLEND_MODE_DARKEN:
return "result.rgb = min((1.0 - src.a) * dst.rgb + src.rgb,"
" (1.0 - dst.a) * src.rgb + dst.rgb);";
case BLEND_MODE_COLOR_DODGE:
return "result.r = getColorDodgeComponent(src.r, src.a, dst.r, dst.a);"
"result.g = getColorDodgeComponent(src.g, src.a, dst.g, dst.a);"
"result.b = getColorDodgeComponent(src.b, src.a, dst.b, dst.a);";
case BLEND_MODE_COLOR_BURN:
return "result.r = getColorBurnComponent(src.r, src.a, dst.r, dst.a);"
"result.g = getColorBurnComponent(src.g, src.a, dst.g, dst.a);"
"result.b = getColorBurnComponent(src.b, src.a, dst.b, dst.a);";
case BLEND_MODE_HARD_LIGHT:
return "result.rgb = hardLight(src, dst);";
case BLEND_MODE_SOFT_LIGHT:
return "if (0.0 == dst.a) {"
" result.rgb = src.rgb;"
"} else {"
" result.r = getSoftLightComponent(src.r, src.a, dst.r, dst.a);"
" result.g = getSoftLightComponent(src.g, src.a, dst.g, dst.a);"
" result.b = getSoftLightComponent(src.b, src.a, dst.b, dst.a);"
"}";
case BLEND_MODE_DIFFERENCE:
return "result.rgb = src.rgb + dst.rgb -"
" 2.0 * min(src.rgb * dst.a, dst.rgb * src.a);";
case BLEND_MODE_EXCLUSION:
return "result.rgb = dst.rgb + src.rgb - 2.0 * dst.rgb * src.rgb;";
case BLEND_MODE_MULTIPLY:
return "result.rgb = (1.0 - src.a) * dst.rgb +"
" (1.0 - dst.a) * src.rgb + src.rgb * dst.rgb;";
case BLEND_MODE_HUE:
return "vec4 dstSrcAlpha = dst * src.a;"
"result.rgb ="
" set_luminance(set_saturation(src.rgb * dst.a,"
" dstSrcAlpha.rgb),"
" dstSrcAlpha.a,"
" dstSrcAlpha.rgb);"
"result.rgb += (1.0 - src.a) * dst.rgb + (1.0 - dst.a) * src.rgb;";
case BLEND_MODE_SATURATION:
return "vec4 dstSrcAlpha = dst * src.a;"
"result.rgb = set_luminance(set_saturation(dstSrcAlpha.rgb,"
" src.rgb * dst.a),"
" dstSrcAlpha.a,"
" dstSrcAlpha.rgb);"
"result.rgb += (1.0 - src.a) * dst.rgb + (1.0 - dst.a) * src.rgb;";
case BLEND_MODE_COLOR:
return "vec4 srcDstAlpha = src * dst.a;"
"result.rgb = set_luminance(srcDstAlpha.rgb,"
" srcDstAlpha.a,"
" dst.rgb * src.a);"
"result.rgb += (1.0 - src.a) * dst.rgb + (1.0 - dst.a) * src.rgb;";
case BLEND_MODE_LUMINOSITY:
return "vec4 srcDstAlpha = src * dst.a;"
"result.rgb = set_luminance(dst.rgb * src.a,"
" srcDstAlpha.a,"
" srcDstAlpha.rgb);"
"result.rgb += (1.0 - src.a) * dst.rgb + (1.0 - dst.a) * src.rgb;";
case BLEND_MODE_NONE:
NOTREACHED();
}
return "result = vec4(1.0, 0.0, 0.0, 1.0);";
}
std::string FragmentShader::GetShaderSource() const {
std::string header = "precision mediump float;\n";
std::string source = "void main() {\n";
// Read the input into vec4 texColor.
switch (input_color_type_) {
case INPUT_COLOR_SOURCE_RGBA_TEXTURE:
if (ignore_sampler_type_)
HDR("uniform sampler2D s_texture;");
else
HDR("uniform SamplerType s_texture;");
HDR("varying TexCoordPrecision vec2 v_texCoord;");
if (has_rgba_fragment_tex_transform_) {
HDR("uniform TexCoordPrecision vec4 fragmentTexTransform;");
SRC("// Transformed texture lookup");
SRC("TexCoordPrecision vec2 texCoord =");
SRC(" clamp(v_texCoord, 0.0, 1.0) * fragmentTexTransform.zw +");
SRC(" fragmentTexTransform.xy;");
SRC("vec4 texColor = TextureLookup(s_texture, texCoord);");
DCHECK(!ignore_sampler_type_);
} else {
SRC("// Texture lookup");
if (ignore_sampler_type_)
SRC("vec4 texColor = texture2D(s_texture, v_texCoord);");
else
SRC("vec4 texColor = TextureLookup(s_texture, v_texCoord);");
}
break;
case INPUT_COLOR_SOURCE_YUV_TEXTURES:
// Compute the clamped texture coordinates for the YA and UV textures.
HDR("uniform SamplerType y_texture;");
SRC("// YUV texture lookup and conversion to RGB.");
SRC("vec2 ya_clamped =");
SRC(" max(ya_clamp_rect.xy, min(ya_clamp_rect.zw, v_yaTexCoord));");
SRC("vec2 uv_clamped =");
SRC(" max(uv_clamp_rect.xy, min(uv_clamp_rect.zw, v_uvTexCoord));");
// Read the Y and UV or U and V textures into |yuv|.
SRC("vec4 texColor;");
SRC("texColor.w = 1.0;");
SRC("texColor.x = TextureLookup(y_texture, ya_clamped).x;");
if (uv_texture_mode_ == UV_TEXTURE_MODE_UV) {
HDR("uniform SamplerType uv_texture;");
SRC("texColor.yz = TextureLookup(uv_texture, uv_clamped).xy;");
}
if (uv_texture_mode_ == UV_TEXTURE_MODE_U_V) {
HDR("uniform SamplerType u_texture;");
HDR("uniform SamplerType v_texture;");
SRC("texColor.y = TextureLookup(u_texture, uv_clamped).x;");
SRC("texColor.z = TextureLookup(v_texture, uv_clamped).x;");
}
if (yuv_alpha_texture_mode_ == YUV_HAS_ALPHA_TEXTURE)
HDR("uniform SamplerType a_texture;");
HDR("uniform vec4 ya_clamp_rect;");
HDR("uniform vec4 uv_clamp_rect;");
HDR("uniform float resource_multiplier;");
HDR("uniform float resource_offset;");
HDR("varying TexCoordPrecision vec2 v_yaTexCoord;");
HDR("varying TexCoordPrecision vec2 v_uvTexCoord;");
SRC("texColor.xyz -= vec3(resource_offset);");
SRC("texColor.xyz *= resource_multiplier;");
break;
case INPUT_COLOR_SOURCE_UNIFORM:
DCHECK(!ignore_sampler_type_);
DCHECK(!has_rgba_fragment_tex_transform_);
HDR("uniform vec4 color;");
SRC("// Uniform color");
SRC("vec4 texColor = color;");
break;
}
// Apply LUT based color conversion.
switch (color_conversion_mode_) {
case COLOR_CONVERSION_MODE_LUT:
HDR("uniform sampler2D lut_texture;");
HDR("uniform float lut_size;");
HDR("vec4 LUT(sampler2D sampler, vec3 pos, float size) {");
HDR(" pos *= size - 1.0;");
HDR(" // Select layer");
HDR(" float layer = min(floor(pos.z), size - 2.0);");
HDR(" // Compress the xy coordinates so they stay within");
HDR(" // [0.5 .. 31.5] / N (assuming a LUT size of 17^3)");
HDR(" pos.xy = (pos.xy + vec2(0.5)) / size;");
HDR(" pos.y = (pos.y + layer) / size;");
HDR(" return mix(LutLookup(sampler, pos.xy),");
HDR(" LutLookup(sampler, pos.xy + vec2(0, 1.0 / size)),");
HDR(" pos.z - layer);");
HDR("}");
SRC("texColor.rgb = LUT(lut_texture, texColor.xyz, lut_size).xyz;");
break;
case COLOR_CONVERSION_MODE_SHADER:
header += color_transform_->GetShaderSource();
SRC("texColor.rgb = DoColorConversion(texColor.xyz);");
break;
case COLOR_CONVERSION_MODE_NONE:
break;
}
// Apply the color matrix to texColor.
if (has_color_matrix_) {
HDR("uniform mat4 colorMatrix;");
HDR("uniform vec4 colorOffset;");
SRC("// Apply color matrix");
SRC("float nonZeroAlpha = max(texColor.a, 0.00001);");
SRC("texColor = vec4(texColor.rgb / nonZeroAlpha, nonZeroAlpha);");
SRC("texColor = colorMatrix * texColor + colorOffset;");
SRC("texColor.rgb *= texColor.a;");
SRC("texColor = clamp(texColor, 0.0, 1.0);");
}
// Read the mask texture.
if (mask_mode_ != NO_MASK) {
HDR("uniform SamplerType s_mask;");
HDR("uniform vec2 maskTexCoordScale;");
HDR("uniform vec2 maskTexCoordOffset;");
SRC("// Read the mask");
SRC("TexCoordPrecision vec2 maskTexCoord =");
SRC(" vec2(maskTexCoordOffset.x + v_texCoord.x * maskTexCoordScale.x,");
SRC(" maskTexCoordOffset.y + v_texCoord.y * maskTexCoordScale.y);");
SRC("vec4 maskColor = TextureLookup(s_mask, maskTexCoord);");
}
// Compute AA.
if (aa_mode_ == USE_AA) {
HDR("varying TexCoordPrecision vec4 edge_dist[2]; // 8 edge distances.");
SRC("// Compute AA");
SRC("vec4 d4 = min(edge_dist[0], edge_dist[1]);");
SRC("vec2 d2 = min(d4.xz, d4.yw);");
SRC("float aa = clamp(gl_FragCoord.w * min(d2.x, d2.y), 0.0, 1.0);");
}
// Premultiply by alpha.
if (premultiply_alpha_mode_ == NON_PREMULTIPLIED_ALPHA) {
SRC("// Premultiply alpha");
SRC("texColor.rgb *= texColor.a;");
}
// Apply background texture.
if (has_background_color_) {
HDR("uniform vec4 background_color;");
SRC("// Apply uniform background color blending");
SRC("texColor += background_color * (1.0 - texColor.a);");
}
// Apply swizzle.
if (swizzle_mode_ == DO_SWIZZLE) {
SRC("// Apply swizzle");
SRC("texColor = texColor.bgra;\n");
}
// Include header text for alpha.
if (has_uniform_alpha_) {
HDR("uniform float alpha;");
}
if (has_varying_alpha_) {
HDR("varying float v_alpha;");
}
// Apply uniform alpha, aa, varying alpha, and the mask.
if (has_varying_alpha_ || aa_mode_ == USE_AA || has_uniform_alpha_ ||
mask_mode_ != NO_MASK) {
SRC("// Apply alpha from uniform, varying, aa, and mask.");
std::string line = " texColor = texColor";
if (has_varying_alpha_)
line += " * v_alpha";
if (has_uniform_alpha_)
line += " * alpha";
if (aa_mode_ == USE_AA)
line += " * aa";
if (mask_mode_ != NO_MASK)
line += " * maskColor.a";
if (yuv_alpha_texture_mode_ == YUV_HAS_ALPHA_TEXTURE)
line += " * TextureLookup(a_texture, ya_clamped).x";
line += ";\n";
source += line;
}
// Write the fragment color.
SRC("// Write the fragment color");
switch (frag_color_mode_) {
case FRAG_COLOR_MODE_DEFAULT:
DCHECK_EQ(blend_mode_, BLEND_MODE_NONE);
SRC("gl_FragColor = texColor;");
break;
case FRAG_COLOR_MODE_OPAQUE:
DCHECK_EQ(blend_mode_, BLEND_MODE_NONE);
SRC("gl_FragColor = vec4(texColor.rgb, 1.0);");
break;
case FRAG_COLOR_MODE_APPLY_BLEND_MODE:
if (mask_mode_ != NO_MASK)
SRC("gl_FragColor = ApplyBlendMode(texColor, maskColor.w);");
else
SRC("gl_FragColor = ApplyBlendMode(texColor, 0.0);");
break;
}
source += "}\n";
return header + source;
}
} // namespace cc