1/*
2 * Copyright 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <GLES2/gl2.h>
18#include <GLES2/gl2ext.h>
19
20#include <utils/String8.h>
21
22#include "ProgramCache.h"
23#include "Program.h"
24#include "Description.h"
25
26namespace android {
27// -----------------------------------------------------------------------------------------------
28
29
30/*
31 * A simple formatter class to automatically add the endl and
32 * manage the indentation.
33 */
34
35class Formatter;
36static Formatter& indent(Formatter& f);
37static Formatter& dedent(Formatter& f);
38
39class Formatter {
40    String8 mString;
41    int mIndent;
42    typedef Formatter& (*FormaterManipFunc)(Formatter&);
43    friend Formatter& indent(Formatter& f);
44    friend Formatter& dedent(Formatter& f);
45public:
46    Formatter() : mIndent(0) {}
47
48    String8 getString() const {
49        return mString;
50    }
51
52    friend Formatter& operator << (Formatter& out, const char* in) {
53        for (int i=0 ; i<out.mIndent ; i++) {
54            out.mString.append("    ");
55        }
56        out.mString.append(in);
57        out.mString.append("\n");
58        return out;
59    }
60    friend inline Formatter& operator << (Formatter& out, const String8& in) {
61        return operator << (out, in.string());
62    }
63    friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
64        return (*func)(to);
65    }
66};
67Formatter& indent(Formatter& f) {
68    f.mIndent++;
69    return f;
70}
71Formatter& dedent(Formatter& f) {
72    f.mIndent--;
73    return f;
74}
75
76// -----------------------------------------------------------------------------------------------
77
78ANDROID_SINGLETON_STATIC_INSTANCE(ProgramCache)
79
80ProgramCache::ProgramCache() {
81    // Until surfaceflinger has a dependable blob cache on the filesystem,
82    // generate shaders on initialization so as to avoid jank.
83    primeCache();
84}
85
86ProgramCache::~ProgramCache() {
87}
88
89void ProgramCache::primeCache() {
90    uint32_t shaderCount = 0;
91    uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK |
92                       Key::PLANE_ALPHA_MASK | Key::TEXTURE_MASK;
93    // Prime the cache for all combinations of the above masks,
94    // leaving off the experimental color matrix mask options.
95
96    nsecs_t timeBefore = systemTime();
97    for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
98        Key shaderKey;
99        shaderKey.set(keyMask, keyVal);
100        uint32_t tex = shaderKey.getTextureTarget();
101        if (tex != Key::TEXTURE_OFF &&
102            tex != Key::TEXTURE_EXT &&
103            tex != Key::TEXTURE_2D) {
104            continue;
105        }
106        Program* program = mCache.valueFor(shaderKey);
107        if (program == NULL) {
108            program = generateProgram(shaderKey);
109            mCache.add(shaderKey, program);
110            shaderCount++;
111        }
112    }
113    nsecs_t timeAfter = systemTime();
114    float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
115    ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
116}
117
118ProgramCache::Key ProgramCache::computeKey(const Description& description) {
119    Key needs;
120    needs.set(Key::TEXTURE_MASK,
121            !description.mTextureEnabled ? Key::TEXTURE_OFF :
122            description.mTexture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES ? Key::TEXTURE_EXT :
123            description.mTexture.getTextureTarget() == GL_TEXTURE_2D           ? Key::TEXTURE_2D :
124            Key::TEXTURE_OFF)
125    .set(Key::PLANE_ALPHA_MASK,
126            (description.mPlaneAlpha < 1) ? Key::PLANE_ALPHA_LT_ONE : Key::PLANE_ALPHA_EQ_ONE)
127    .set(Key::BLEND_MASK,
128            description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
129    .set(Key::OPACITY_MASK,
130            description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
131    .set(Key::COLOR_MATRIX_MASK,
132            description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON :  Key::COLOR_MATRIX_OFF);
133    return needs;
134}
135
136String8 ProgramCache::generateVertexShader(const Key& needs) {
137    Formatter vs;
138    if (needs.isTexturing()) {
139        vs  << "attribute vec4 texCoords;"
140            << "varying vec2 outTexCoords;";
141    }
142    vs << "attribute vec4 position;"
143       << "uniform mat4 projection;"
144       << "uniform mat4 texture;"
145       << "void main(void) {" << indent
146       << "gl_Position = projection * position;";
147    if (needs.isTexturing()) {
148        vs << "outTexCoords = (texture * texCoords).st;";
149    }
150    vs << dedent << "}";
151    return vs.getString();
152}
153
154String8 ProgramCache::generateFragmentShader(const Key& needs) {
155    Formatter fs;
156    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
157        fs << "#extension GL_OES_EGL_image_external : require";
158    }
159
160    // default precision is required-ish in fragment shaders
161    fs << "precision mediump float;";
162
163    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
164        fs << "uniform samplerExternalOES sampler;"
165           << "varying vec2 outTexCoords;";
166    } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
167        fs << "uniform sampler2D sampler;"
168           << "varying vec2 outTexCoords;";
169    } else if (needs.getTextureTarget() == Key::TEXTURE_OFF) {
170        fs << "uniform vec4 color;";
171    }
172    if (needs.hasPlaneAlpha()) {
173        fs << "uniform float alphaPlane;";
174    }
175    if (needs.hasColorMatrix()) {
176        fs << "uniform mat4 colorMatrix;";
177    }
178    fs << "void main(void) {" << indent;
179    if (needs.isTexturing()) {
180        fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
181    } else {
182        fs << "gl_FragColor = color;";
183    }
184    if (needs.isOpaque()) {
185        fs << "gl_FragColor.a = 1.0;";
186    }
187    if (needs.hasPlaneAlpha()) {
188        // modulate the alpha value with planeAlpha
189        if (needs.isPremultiplied()) {
190            // ... and the color too if we're premultiplied
191            fs << "gl_FragColor *= alphaPlane;";
192        } else {
193            fs << "gl_FragColor.a *= alphaPlane;";
194        }
195    }
196
197    if (needs.hasColorMatrix()) {
198        if (!needs.isOpaque() && needs.isPremultiplied()) {
199            // un-premultiply if needed before linearization
200            fs << "gl_FragColor.rgb = gl_FragColor.rgb/gl_FragColor.a;";
201        }
202        fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(2.2));";
203        fs << "vec4 transformed = colorMatrix * vec4(gl_FragColor.rgb, 1);";
204        fs << "gl_FragColor.rgb = transformed.rgb/transformed.a;";
205        fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(1.0 / 2.2));";
206        if (!needs.isOpaque() && needs.isPremultiplied()) {
207            // and re-premultiply if needed after gamma correction
208            fs << "gl_FragColor.rgb = gl_FragColor.rgb*gl_FragColor.a;";
209        }
210    }
211
212    fs << dedent << "}";
213    return fs.getString();
214}
215
216Program* ProgramCache::generateProgram(const Key& needs) {
217    // vertex shader
218    String8 vs = generateVertexShader(needs);
219
220    // fragment shader
221    String8 fs = generateFragmentShader(needs);
222
223    Program* program = new Program(needs, vs.string(), fs.string());
224    return program;
225}
226
227void ProgramCache::useProgram(const Description& description) {
228
229    // generate the key for the shader based on the description
230    Key needs(computeKey(description));
231
232     // look-up the program in the cache
233    Program* program = mCache.valueFor(needs);
234    if (program == NULL) {
235        // we didn't find our program, so generate one...
236        nsecs_t time = -systemTime();
237        program = generateProgram(needs);
238        mCache.add(needs, program);
239        time += systemTime();
240
241        //ALOGD(">>> generated new program: needs=%08X, time=%u ms (%d programs)",
242        //        needs.mNeeds, uint32_t(ns2ms(time)), mCache.size());
243    }
244
245    // here we have a suitable program for this description
246    if (program->isValid()) {
247        program->use();
248        program->setUniforms(description);
249    }
250}
251
252
253} /* namespace android */
254