blob: 06b225299c0b0b95c550a2b5045375112cfa2186 [file] [log] [blame]
Mathias Agopian3f844832013-08-07 21:24:32 -07001/*
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:
Andy McFadden892f22d2013-08-15 10:05:01 -070046 Formatter() : mIndent(0) {}
47
Mathias Agopian3f844832013-08-07 21:24:32 -070048 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
Mathias Agopian3f844832013-08-07 21:24:32 -070080ProgramCache::ProgramCache() {
Riley Andrewsa51fafc2014-09-29 13:29:40 -070081 // Until surfaceflinger has a dependable blob cache on the filesystem,
82 // generate shaders on initialization so as to avoid jank.
83 primeCache();
Mathias Agopian3f844832013-08-07 21:24:32 -070084}
85
86ProgramCache::~ProgramCache() {
87}
88
Riley Andrewsa51fafc2014-09-29 13:29:40 -070089void 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
Mathias Agopian3f844832013-08-07 21:24:32 -0700118ProgramCache::Key ProgramCache::computeKey(const Description& description) {
119 Key needs;
120 needs.set(Key::TEXTURE_MASK,
Mathias Agopian49457ac2013-08-14 18:20:17 -0700121 !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 :
Mathias Agopian3f844832013-08-07 21:24:32 -0700124 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,
Mathias Agopianff2ed702013-09-01 21:36:12 -0700130 description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
131 .set(Key::COLOR_MATRIX_MASK,
Romain Guy88d37dd2017-05-26 17:57:05 -0700132 description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON : Key::COLOR_MATRIX_OFF)
133 .set(Key::WIDE_GAMUT_MASK,
134 description.mIsWideGamut ? Key::WIDE_GAMUT_ON : Key::WIDE_GAMUT_OFF);
Mathias Agopian3f844832013-08-07 21:24:32 -0700135 return needs;
136}
137
138String8 ProgramCache::generateVertexShader(const Key& needs) {
139 Formatter vs;
140 if (needs.isTexturing()) {
141 vs << "attribute vec4 texCoords;"
142 << "varying vec2 outTexCoords;";
143 }
144 vs << "attribute vec4 position;"
145 << "uniform mat4 projection;"
146 << "uniform mat4 texture;"
147 << "void main(void) {" << indent
148 << "gl_Position = projection * position;";
149 if (needs.isTexturing()) {
150 vs << "outTexCoords = (texture * texCoords).st;";
151 }
152 vs << dedent << "}";
153 return vs.getString();
154}
155
156String8 ProgramCache::generateFragmentShader(const Key& needs) {
157 Formatter fs;
158 if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
159 fs << "#extension GL_OES_EGL_image_external : require";
160 }
Mathias Agopian458197d2013-08-15 14:56:51 -0700161
162 // default precision is required-ish in fragment shaders
163 fs << "precision mediump float;";
164
Mathias Agopian3f844832013-08-07 21:24:32 -0700165 if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
166 fs << "uniform samplerExternalOES sampler;"
167 << "varying vec2 outTexCoords;";
168 } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
169 fs << "uniform sampler2D sampler;"
170 << "varying vec2 outTexCoords;";
171 } else if (needs.getTextureTarget() == Key::TEXTURE_OFF) {
172 fs << "uniform vec4 color;";
173 }
174 if (needs.hasPlaneAlpha()) {
175 fs << "uniform float alphaPlane;";
176 }
Mathias Agopianff2ed702013-09-01 21:36:12 -0700177 if (needs.hasColorMatrix()) {
178 fs << "uniform mat4 colorMatrix;";
179 }
Romain Guy88d37dd2017-05-26 17:57:05 -0700180 if (needs.hasColorMatrix()) {
181 // When in wide gamut mode, the color matrix will contain a color space
182 // conversion matrix that needs to be applied in linear space
183 // When not in wide gamut, we can simply no-op the transfer functions
184 // and let the shader compiler get rid of them
185 if (needs.isWideGamut()) {
186 fs << R"__SHADER__(
187 float OETF_sRGB(const float linear) {
188 return linear <= 0.0031308 ?
189 linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
190 }
191
192 vec3 OETF_sRGB(const vec3 linear) {
193 return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
194 }
195
196 vec3 OETF_scRGB(const vec3 linear) {
197 return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
198 }
199
200 float EOTF_sRGB(float srgb) {
201 return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
202 }
203
204 vec3 EOTF_sRGB(const vec3 srgb) {
205 return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
206 }
207
208 vec3 EOTF_scRGB(const vec3 srgb) {
209 return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
210 }
211 )__SHADER__";
212 } else {
213 fs << R"__SHADER__(
214 vec3 OETF_scRGB(const vec3 linear) {
215 return linear;
216 }
217
218 vec3 EOTF_scRGB(const vec3 srgb) {
219 return srgb;
220 }
221 )__SHADER__";
222 }
223 }
Mathias Agopian3f844832013-08-07 21:24:32 -0700224 fs << "void main(void) {" << indent;
225 if (needs.isTexturing()) {
226 fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
227 } else {
228 fs << "gl_FragColor = color;";
229 }
Mathias Agopian2eaefe12013-08-14 16:33:27 -0700230 if (needs.isOpaque()) {
231 fs << "gl_FragColor.a = 1.0;";
232 }
Mathias Agopian3f844832013-08-07 21:24:32 -0700233 if (needs.hasPlaneAlpha()) {
234 // modulate the alpha value with planeAlpha
235 if (needs.isPremultiplied()) {
236 // ... and the color too if we're premultiplied
Mathias Agopian2eaefe12013-08-14 16:33:27 -0700237 fs << "gl_FragColor *= alphaPlane;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700238 } else {
Mathias Agopian2eaefe12013-08-14 16:33:27 -0700239 fs << "gl_FragColor.a *= alphaPlane;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700240 }
241 }
Mathias Agopianff2ed702013-09-01 21:36:12 -0700242
243 if (needs.hasColorMatrix()) {
244 if (!needs.isOpaque() && needs.isPremultiplied()) {
245 // un-premultiply if needed before linearization
Romain Guy88d37dd2017-05-26 17:57:05 -0700246 // avoid divide by 0 by adding 0.5/256 to the alpha channel
247 fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700248 }
Romain Guy88d37dd2017-05-26 17:57:05 -0700249 fs << "vec4 transformed = colorMatrix * vec4(EOTF_scRGB(gl_FragColor.rgb), 1);";
250 // We assume the last row is always {0,0,0,1} and we skip the division by w
251 fs << "gl_FragColor.rgb = OETF_scRGB(transformed.rgb);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700252 if (!needs.isOpaque() && needs.isPremultiplied()) {
253 // and re-premultiply if needed after gamma correction
Romain Guy88d37dd2017-05-26 17:57:05 -0700254 fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700255 }
256 }
257
Mathias Agopian3f844832013-08-07 21:24:32 -0700258 fs << dedent << "}";
259 return fs.getString();
260}
261
262Program* ProgramCache::generateProgram(const Key& needs) {
263 // vertex shader
264 String8 vs = generateVertexShader(needs);
265
266 // fragment shader
267 String8 fs = generateFragmentShader(needs);
268
269 Program* program = new Program(needs, vs.string(), fs.string());
270 return program;
271}
272
273void ProgramCache::useProgram(const Description& description) {
274
275 // generate the key for the shader based on the description
276 Key needs(computeKey(description));
277
278 // look-up the program in the cache
279 Program* program = mCache.valueFor(needs);
280 if (program == NULL) {
281 // we didn't find our program, so generate one...
282 nsecs_t time = -systemTime();
283 program = generateProgram(needs);
284 mCache.add(needs, program);
285 time += systemTime();
286
287 //ALOGD(">>> generated new program: needs=%08X, time=%u ms (%d programs)",
288 // needs.mNeeds, uint32_t(ns2ms(time)), mCache.size());
289 }
290
291 // here we have a suitable program for this description
292 if (program->isValid()) {
293 program->use();
294 program->setUniforms(description);
295 }
296}
297
298
299} /* namespace android */