blob: 4f138dc14c0d665df32d8e35b316679f06cb3bdf [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
Mathias Agopian3f844832013-08-07 21:24:32 -070022#include "Description.h"
Chia-I Wub027f802017-11-29 14:00:52 -080023#include "Program.h"
24#include "ProgramCache.h"
Mathias Agopian3f844832013-08-07 21:24:32 -070025
26namespace android {
27// -----------------------------------------------------------------------------------------------
28
Mathias Agopian3f844832013-08-07 21:24:32 -070029/*
30 * A simple formatter class to automatically add the endl and
31 * manage the indentation.
32 */
33
34class Formatter;
35static Formatter& indent(Formatter& f);
36static Formatter& dedent(Formatter& f);
37
38class Formatter {
39 String8 mString;
40 int mIndent;
41 typedef Formatter& (*FormaterManipFunc)(Formatter&);
42 friend Formatter& indent(Formatter& f);
43 friend Formatter& dedent(Formatter& f);
Chia-I Wub027f802017-11-29 14:00:52 -080044
Mathias Agopian3f844832013-08-07 21:24:32 -070045public:
Andy McFadden892f22d2013-08-15 10:05:01 -070046 Formatter() : mIndent(0) {}
47
Chia-I Wub027f802017-11-29 14:00:52 -080048 String8 getString() const { return mString; }
Mathias Agopian3f844832013-08-07 21:24:32 -070049
Chia-I Wub027f802017-11-29 14:00:52 -080050 friend Formatter& operator<<(Formatter& out, const char* in) {
51 for (int i = 0; i < out.mIndent; i++) {
Mathias Agopian3f844832013-08-07 21:24:32 -070052 out.mString.append(" ");
53 }
54 out.mString.append(in);
55 out.mString.append("\n");
56 return out;
57 }
Chia-I Wub027f802017-11-29 14:00:52 -080058 friend inline Formatter& operator<<(Formatter& out, const String8& in) {
59 return operator<<(out, in.string());
Mathias Agopian3f844832013-08-07 21:24:32 -070060 }
61 friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
62 return (*func)(to);
63 }
64};
65Formatter& indent(Formatter& f) {
66 f.mIndent++;
67 return f;
68}
69Formatter& dedent(Formatter& f) {
70 f.mIndent--;
71 return f;
72}
73
74// -----------------------------------------------------------------------------------------------
75
76ANDROID_SINGLETON_STATIC_INSTANCE(ProgramCache)
77
Mathias Agopian3f844832013-08-07 21:24:32 -070078ProgramCache::ProgramCache() {
Riley Andrewsa51fafc2014-09-29 13:29:40 -070079 // Until surfaceflinger has a dependable blob cache on the filesystem,
80 // generate shaders on initialization so as to avoid jank.
81 primeCache();
Mathias Agopian3f844832013-08-07 21:24:32 -070082}
83
Chia-I Wub027f802017-11-29 14:00:52 -080084ProgramCache::~ProgramCache() {}
Mathias Agopian3f844832013-08-07 21:24:32 -070085
Riley Andrewsa51fafc2014-09-29 13:29:40 -070086void ProgramCache::primeCache() {
87 uint32_t shaderCount = 0;
Chia-I Wub027f802017-11-29 14:00:52 -080088 uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK | Key::ALPHA_MASK | Key::TEXTURE_MASK;
Riley Andrewsa51fafc2014-09-29 13:29:40 -070089 // Prime the cache for all combinations of the above masks,
90 // leaving off the experimental color matrix mask options.
91
92 nsecs_t timeBefore = systemTime();
93 for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
94 Key shaderKey;
95 shaderKey.set(keyMask, keyVal);
96 uint32_t tex = shaderKey.getTextureTarget();
Chia-I Wub027f802017-11-29 14:00:52 -080097 if (tex != Key::TEXTURE_OFF && tex != Key::TEXTURE_EXT && tex != Key::TEXTURE_2D) {
Riley Andrewsa51fafc2014-09-29 13:29:40 -070098 continue;
99 }
100 Program* program = mCache.valueFor(shaderKey);
101 if (program == NULL) {
102 program = generateProgram(shaderKey);
103 mCache.add(shaderKey, program);
104 shaderCount++;
105 }
106 }
107 nsecs_t timeAfter = systemTime();
108 float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
109 ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
110}
111
Mathias Agopian3f844832013-08-07 21:24:32 -0700112ProgramCache::Key ProgramCache::computeKey(const Description& description) {
113 Key needs;
114 needs.set(Key::TEXTURE_MASK,
Chia-I Wub027f802017-11-29 14:00:52 -0800115 !description.mTextureEnabled
116 ? Key::TEXTURE_OFF
117 : description.mTexture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES
118 ? Key::TEXTURE_EXT
119 : description.mTexture.getTextureTarget() == GL_TEXTURE_2D
120 ? Key::TEXTURE_2D
121 : Key::TEXTURE_OFF)
122 .set(Key::ALPHA_MASK,
123 (description.mColor.a < 1) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE)
124 .set(Key::BLEND_MASK,
125 description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
126 .set(Key::OPACITY_MASK,
127 description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
128 .set(Key::COLOR_MATRIX_MASK,
129 description.mColorMatrixEnabled ? Key::COLOR_MATRIX_ON : Key::COLOR_MATRIX_OFF)
130 .set(Key::WIDE_GAMUT_MASK,
131 description.mIsWideGamut ? Key::WIDE_GAMUT_ON : Key::WIDE_GAMUT_OFF);
Mathias Agopian3f844832013-08-07 21:24:32 -0700132 return needs;
133}
134
135String8 ProgramCache::generateVertexShader(const Key& needs) {
136 Formatter vs;
137 if (needs.isTexturing()) {
Chia-I Wub027f802017-11-29 14:00:52 -0800138 vs << "attribute vec4 texCoords;"
139 << "varying vec2 outTexCoords;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700140 }
141 vs << "attribute vec4 position;"
142 << "uniform mat4 projection;"
143 << "uniform mat4 texture;"
Chia-I Wub027f802017-11-29 14:00:52 -0800144 << "void main(void) {" << indent << "gl_Position = projection * position;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700145 if (needs.isTexturing()) {
146 vs << "outTexCoords = (texture * texCoords).st;";
147 }
148 vs << dedent << "}";
149 return vs.getString();
150}
151
152String8 ProgramCache::generateFragmentShader(const Key& needs) {
153 Formatter fs;
154 if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
155 fs << "#extension GL_OES_EGL_image_external : require";
156 }
Mathias Agopian458197d2013-08-15 14:56:51 -0700157
158 // default precision is required-ish in fragment shaders
159 fs << "precision mediump float;";
160
Mathias Agopian3f844832013-08-07 21:24:32 -0700161 if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
162 fs << "uniform samplerExternalOES sampler;"
163 << "varying vec2 outTexCoords;";
164 } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
165 fs << "uniform sampler2D sampler;"
166 << "varying vec2 outTexCoords;";
chaviw13fdc492017-06-27 12:40:18 -0700167 }
168
169 if (needs.getTextureTarget() == Key::TEXTURE_OFF || needs.hasAlpha()) {
Mathias Agopian3f844832013-08-07 21:24:32 -0700170 fs << "uniform vec4 color;";
171 }
chaviw13fdc492017-06-27 12:40:18 -0700172
Mathias Agopianff2ed702013-09-01 21:36:12 -0700173 if (needs.hasColorMatrix()) {
174 fs << "uniform mat4 colorMatrix;";
175 }
Romain Guy88d37dd2017-05-26 17:57:05 -0700176 if (needs.hasColorMatrix()) {
177 // When in wide gamut mode, the color matrix will contain a color space
178 // conversion matrix that needs to be applied in linear space
179 // When not in wide gamut, we can simply no-op the transfer functions
180 // and let the shader compiler get rid of them
181 if (needs.isWideGamut()) {
182 fs << R"__SHADER__(
183 float OETF_sRGB(const float linear) {
184 return linear <= 0.0031308 ?
185 linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
186 }
187
188 vec3 OETF_sRGB(const vec3 linear) {
189 return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
190 }
191
192 vec3 OETF_scRGB(const vec3 linear) {
193 return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
194 }
195
196 float EOTF_sRGB(float srgb) {
197 return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
198 }
199
200 vec3 EOTF_sRGB(const vec3 srgb) {
201 return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
202 }
203
204 vec3 EOTF_scRGB(const vec3 srgb) {
205 return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
206 }
207 )__SHADER__";
208 } else {
209 fs << R"__SHADER__(
210 vec3 OETF_scRGB(const vec3 linear) {
211 return linear;
212 }
213
214 vec3 EOTF_scRGB(const vec3 srgb) {
215 return srgb;
216 }
217 )__SHADER__";
218 }
219 }
Mathias Agopian3f844832013-08-07 21:24:32 -0700220 fs << "void main(void) {" << indent;
221 if (needs.isTexturing()) {
222 fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
223 } else {
chaviw13fdc492017-06-27 12:40:18 -0700224 fs << "gl_FragColor.rgb = color.rgb;";
225 fs << "gl_FragColor.a = 1.0;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700226 }
Mathias Agopian2eaefe12013-08-14 16:33:27 -0700227 if (needs.isOpaque()) {
228 fs << "gl_FragColor.a = 1.0;";
229 }
chaviw13fdc492017-06-27 12:40:18 -0700230 if (needs.hasAlpha()) {
231 // modulate the current alpha value with alpha set
Mathias Agopian3f844832013-08-07 21:24:32 -0700232 if (needs.isPremultiplied()) {
233 // ... and the color too if we're premultiplied
chaviw13fdc492017-06-27 12:40:18 -0700234 fs << "gl_FragColor *= color.a;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700235 } else {
chaviw13fdc492017-06-27 12:40:18 -0700236 fs << "gl_FragColor.a *= color.a;";
Mathias Agopian3f844832013-08-07 21:24:32 -0700237 }
238 }
Mathias Agopianff2ed702013-09-01 21:36:12 -0700239
240 if (needs.hasColorMatrix()) {
241 if (!needs.isOpaque() && needs.isPremultiplied()) {
242 // un-premultiply if needed before linearization
Romain Guy88d37dd2017-05-26 17:57:05 -0700243 // avoid divide by 0 by adding 0.5/256 to the alpha channel
244 fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700245 }
Romain Guy88d37dd2017-05-26 17:57:05 -0700246 fs << "vec4 transformed = colorMatrix * vec4(EOTF_scRGB(gl_FragColor.rgb), 1);";
247 // We assume the last row is always {0,0,0,1} and we skip the division by w
248 fs << "gl_FragColor.rgb = OETF_scRGB(transformed.rgb);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700249 if (!needs.isOpaque() && needs.isPremultiplied()) {
250 // and re-premultiply if needed after gamma correction
Romain Guy88d37dd2017-05-26 17:57:05 -0700251 fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
Mathias Agopianff2ed702013-09-01 21:36:12 -0700252 }
253 }
254
Mathias Agopian3f844832013-08-07 21:24:32 -0700255 fs << dedent << "}";
256 return fs.getString();
257}
258
259Program* ProgramCache::generateProgram(const Key& needs) {
260 // vertex shader
261 String8 vs = generateVertexShader(needs);
262
263 // fragment shader
264 String8 fs = generateFragmentShader(needs);
265
266 Program* program = new Program(needs, vs.string(), fs.string());
267 return program;
268}
269
270void ProgramCache::useProgram(const Description& description) {
Mathias Agopian3f844832013-08-07 21:24:32 -0700271 // generate the key for the shader based on the description
272 Key needs(computeKey(description));
273
Chia-I Wub027f802017-11-29 14:00:52 -0800274 // look-up the program in the cache
Mathias Agopian3f844832013-08-07 21:24:32 -0700275 Program* program = mCache.valueFor(needs);
276 if (program == NULL) {
277 // we didn't find our program, so generate one...
278 nsecs_t time = -systemTime();
279 program = generateProgram(needs);
280 mCache.add(needs, program);
281 time += systemTime();
282
Chia-I Wub027f802017-11-29 14:00:52 -0800283 // ALOGD(">>> generated new program: needs=%08X, time=%u ms (%d programs)",
Mathias Agopian3f844832013-08-07 21:24:32 -0700284 // needs.mNeeds, uint32_t(ns2ms(time)), mCache.size());
285 }
286
287 // here we have a suitable program for this description
288 if (program->isValid()) {
289 program->use();
290 program->setUniforms(description);
291 }
292}
293
Mathias Agopian3f844832013-08-07 21:24:32 -0700294} /* namespace android */