blob: 06b225299c0b0b95c550a2b5045375112cfa2186 [file] [log] [blame]
/*
* Copyright 2013 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.
*/
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <utils/String8.h>
#include "ProgramCache.h"
#include "Program.h"
#include "Description.h"
namespace android {
// -----------------------------------------------------------------------------------------------
/*
* A simple formatter class to automatically add the endl and
* manage the indentation.
*/
class Formatter;
static Formatter& indent(Formatter& f);
static Formatter& dedent(Formatter& f);
class Formatter {
String8 mString;
int mIndent;
typedef Formatter& (*FormaterManipFunc)(Formatter&);
friend Formatter& indent(Formatter& f);
friend Formatter& dedent(Formatter& f);
public:
Formatter() : mIndent(0) {}
String8 getString() const {
return mString;
}
friend Formatter& operator << (Formatter& out, const char* in) {
for (int i=0 ; i<out.mIndent ; i++) {
out.mString.append(" ");
}
out.mString.append(in);
out.mString.append("\n");
return out;
}
friend inline Formatter& operator << (Formatter& out, const String8& in) {
return operator << (out, in.string());
}
friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
return (*func)(to);
}
};
Formatter& indent(Formatter& f) {
f.mIndent++;
return f;
}
Formatter& dedent(Formatter& f) {
f.mIndent--;
return f;
}
// -----------------------------------------------------------------------------------------------
ANDROID_SINGLETON_STATIC_INSTANCE(ProgramCache)
ProgramCache::ProgramCache() {
// Until surfaceflinger has a dependable blob cache on the filesystem,
// generate shaders on initialization so as to avoid jank.
primeCache();
}
ProgramCache::~ProgramCache() {
}
void ProgramCache::primeCache() {
uint32_t shaderCount = 0;
uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK |
Key::PLANE_ALPHA_MASK | Key::TEXTURE_MASK;
// Prime the cache for all combinations of the above masks,
// leaving off the experimental color matrix mask options.
nsecs_t timeBefore = systemTime();
for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
Key shaderKey;
shaderKey.set(keyMask, keyVal);
uint32_t tex = shaderKey.getTextureTarget();
if (tex != Key::TEXTURE_OFF &&
tex != Key::TEXTURE_EXT &&
tex != Key::TEXTURE_2D) {
continue;
}
Program* program = mCache.valueFor(shaderKey);
if (program == NULL) {
program = generateProgram(shaderKey);
mCache.add(shaderKey, program);
shaderCount++;
}
}
nsecs_t timeAfter = systemTime();
float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
}
ProgramCache::Key ProgramCache::computeKey(const Description& description) {
Key needs;
needs.set(Key::TEXTURE_MASK,
!description.mTextureEnabled ? Key::TEXTURE_OFF :
description.mTexture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES ? Key::TEXTURE_EXT :
description.mTexture.getTextureTarget() == GL_TEXTURE_2D ? Key::TEXTURE_2D :
Key::TEXTURE_OFF)
.set(Key::PLANE_ALPHA_MASK,
(description.mPlaneAlpha < 1) ? Key::PLANE_ALPHA_LT_ONE : Key::PLANE_ALPHA_EQ_ONE)
.set(Key::BLEND_MASK,
description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
.set(Key::OPACITY_MASK,
description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
.set(Key::COLOR_MATRIX_MASK,
description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON : Key::COLOR_MATRIX_OFF)
.set(Key::WIDE_GAMUT_MASK,
description.mIsWideGamut ? Key::WIDE_GAMUT_ON : Key::WIDE_GAMUT_OFF);
return needs;
}
String8 ProgramCache::generateVertexShader(const Key& needs) {
Formatter vs;
if (needs.isTexturing()) {
vs << "attribute vec4 texCoords;"
<< "varying vec2 outTexCoords;";
}
vs << "attribute vec4 position;"
<< "uniform mat4 projection;"
<< "uniform mat4 texture;"
<< "void main(void) {" << indent
<< "gl_Position = projection * position;";
if (needs.isTexturing()) {
vs << "outTexCoords = (texture * texCoords).st;";
}
vs << dedent << "}";
return vs.getString();
}
String8 ProgramCache::generateFragmentShader(const Key& needs) {
Formatter fs;
if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
fs << "#extension GL_OES_EGL_image_external : require";
}
// default precision is required-ish in fragment shaders
fs << "precision mediump float;";
if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
fs << "uniform samplerExternalOES sampler;"
<< "varying vec2 outTexCoords;";
} else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
fs << "uniform sampler2D sampler;"
<< "varying vec2 outTexCoords;";
} else if (needs.getTextureTarget() == Key::TEXTURE_OFF) {
fs << "uniform vec4 color;";
}
if (needs.hasPlaneAlpha()) {
fs << "uniform float alphaPlane;";
}
if (needs.hasColorMatrix()) {
fs << "uniform mat4 colorMatrix;";
}
if (needs.hasColorMatrix()) {
// When in wide gamut mode, the color matrix will contain a color space
// conversion matrix that needs to be applied in linear space
// When not in wide gamut, we can simply no-op the transfer functions
// and let the shader compiler get rid of them
if (needs.isWideGamut()) {
fs << R"__SHADER__(
float OETF_sRGB(const float linear) {
return linear <= 0.0031308 ?
linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
}
vec3 OETF_sRGB(const vec3 linear) {
return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
}
vec3 OETF_scRGB(const vec3 linear) {
return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
}
float EOTF_sRGB(float srgb) {
return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
}
vec3 EOTF_sRGB(const vec3 srgb) {
return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
}
vec3 EOTF_scRGB(const vec3 srgb) {
return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
}
)__SHADER__";
} else {
fs << R"__SHADER__(
vec3 OETF_scRGB(const vec3 linear) {
return linear;
}
vec3 EOTF_scRGB(const vec3 srgb) {
return srgb;
}
)__SHADER__";
}
}
fs << "void main(void) {" << indent;
if (needs.isTexturing()) {
fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
} else {
fs << "gl_FragColor = color;";
}
if (needs.isOpaque()) {
fs << "gl_FragColor.a = 1.0;";
}
if (needs.hasPlaneAlpha()) {
// modulate the alpha value with planeAlpha
if (needs.isPremultiplied()) {
// ... and the color too if we're premultiplied
fs << "gl_FragColor *= alphaPlane;";
} else {
fs << "gl_FragColor.a *= alphaPlane;";
}
}
if (needs.hasColorMatrix()) {
if (!needs.isOpaque() && needs.isPremultiplied()) {
// un-premultiply if needed before linearization
// avoid divide by 0 by adding 0.5/256 to the alpha channel
fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
}
fs << "vec4 transformed = colorMatrix * vec4(EOTF_scRGB(gl_FragColor.rgb), 1);";
// We assume the last row is always {0,0,0,1} and we skip the division by w
fs << "gl_FragColor.rgb = OETF_scRGB(transformed.rgb);";
if (!needs.isOpaque() && needs.isPremultiplied()) {
// and re-premultiply if needed after gamma correction
fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
}
}
fs << dedent << "}";
return fs.getString();
}
Program* ProgramCache::generateProgram(const Key& needs) {
// vertex shader
String8 vs = generateVertexShader(needs);
// fragment shader
String8 fs = generateFragmentShader(needs);
Program* program = new Program(needs, vs.string(), fs.string());
return program;
}
void ProgramCache::useProgram(const Description& description) {
// generate the key for the shader based on the description
Key needs(computeKey(description));
// look-up the program in the cache
Program* program = mCache.valueFor(needs);
if (program == NULL) {
// we didn't find our program, so generate one...
nsecs_t time = -systemTime();
program = generateProgram(needs);
mCache.add(needs, program);
time += systemTime();
//ALOGD(">>> generated new program: needs=%08X, time=%u ms (%d programs)",
// needs.mNeeds, uint32_t(ns2ms(time)), mCache.size());
}
// here we have a suitable program for this description
if (program->isValid()) {
program->use();
program->setUniforms(description);
}
}
} /* namespace android */