Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 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 | |
Mathias Agopian | 5bbe0ab | 2013-08-14 18:22:02 -0700 | [diff] [blame] | 17 | #include <GLES2/gl2.h> |
| 18 | #include <GLES2/gl2ext.h> |
| 19 | |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 20 | #include <ui/DisplayInfo.h> |
| 21 | #include <gui/SurfaceComposerClient.h> |
| 22 | |
| 23 | #include "GLHelper.h" |
| 24 | |
| 25 | namespace android { |
| 26 | |
| 27 | GLHelper::GLHelper() : |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 28 | mDisplay(EGL_NO_DISPLAY), |
| 29 | mContext(EGL_NO_CONTEXT), |
| 30 | mDummySurface(EGL_NO_SURFACE), |
| 31 | mConfig(0), |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 32 | mShaderPrograms(nullptr), |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 33 | mDitherTexture(0) { |
| 34 | } |
| 35 | |
| 36 | GLHelper::~GLHelper() { |
| 37 | } |
| 38 | |
| 39 | bool GLHelper::setUp(const ShaderDesc* shaderDescs, size_t numShaders) { |
| 40 | bool result; |
| 41 | |
| 42 | mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); |
| 43 | if (mDisplay == EGL_NO_DISPLAY) { |
| 44 | fprintf(stderr, "eglGetDisplay error: %#x\n", eglGetError()); |
| 45 | return false; |
| 46 | } |
| 47 | |
| 48 | EGLint majorVersion; |
| 49 | EGLint minorVersion; |
| 50 | result = eglInitialize(mDisplay, &majorVersion, &minorVersion); |
| 51 | if (result != EGL_TRUE) { |
| 52 | fprintf(stderr, "eglInitialize error: %#x\n", eglGetError()); |
| 53 | return false; |
| 54 | } |
| 55 | |
| 56 | EGLint numConfigs = 0; |
| 57 | EGLint configAttribs[] = { |
| 58 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, |
| 59 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, |
| 60 | EGL_RED_SIZE, 8, |
| 61 | EGL_GREEN_SIZE, 8, |
| 62 | EGL_BLUE_SIZE, 8, |
| 63 | EGL_ALPHA_SIZE, 8, |
| 64 | EGL_NONE |
| 65 | }; |
| 66 | result = eglChooseConfig(mDisplay, configAttribs, &mConfig, 1, |
| 67 | &numConfigs); |
| 68 | if (result != EGL_TRUE) { |
| 69 | fprintf(stderr, "eglChooseConfig error: %#x\n", eglGetError()); |
| 70 | return false; |
| 71 | } |
| 72 | |
| 73 | EGLint contextAttribs[] = { |
| 74 | EGL_CONTEXT_CLIENT_VERSION, 2, |
| 75 | EGL_NONE |
| 76 | }; |
| 77 | mContext = eglCreateContext(mDisplay, mConfig, EGL_NO_CONTEXT, |
| 78 | contextAttribs); |
| 79 | if (mContext == EGL_NO_CONTEXT) { |
| 80 | fprintf(stderr, "eglCreateContext error: %#x\n", eglGetError()); |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | bool resultb = createNamedSurfaceTexture(0, 1, 1, &mDummyGLConsumer, |
| 85 | &mDummySurface); |
| 86 | if (!resultb) { |
| 87 | return false; |
| 88 | } |
| 89 | |
| 90 | resultb = makeCurrent(mDummySurface); |
| 91 | if (!resultb) { |
| 92 | return false; |
| 93 | } |
| 94 | |
| 95 | resultb = setUpShaders(shaderDescs, numShaders); |
| 96 | if (!resultb) { |
| 97 | return false; |
| 98 | } |
| 99 | |
| 100 | return true; |
| 101 | } |
| 102 | |
| 103 | void GLHelper::tearDown() { |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 104 | if (mShaderPrograms != nullptr) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 105 | delete[] mShaderPrograms; |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 106 | mShaderPrograms = nullptr; |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 107 | } |
| 108 | |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 109 | if (mSurfaceComposerClient != nullptr) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 110 | mSurfaceComposerClient->dispose(); |
| 111 | mSurfaceComposerClient.clear(); |
| 112 | } |
| 113 | |
| 114 | if (mDisplay != EGL_NO_DISPLAY) { |
| 115 | eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| 116 | EGL_NO_CONTEXT); |
| 117 | } |
| 118 | |
| 119 | if (mContext != EGL_NO_CONTEXT) { |
| 120 | eglDestroyContext(mDisplay, mContext); |
| 121 | } |
| 122 | |
| 123 | if (mDummySurface != EGL_NO_SURFACE) { |
| 124 | eglDestroySurface(mDisplay, mDummySurface); |
| 125 | } |
| 126 | |
| 127 | mDisplay = EGL_NO_DISPLAY; |
| 128 | mContext = EGL_NO_CONTEXT; |
| 129 | mDummySurface = EGL_NO_SURFACE; |
| 130 | mDummyGLConsumer.clear(); |
| 131 | mConfig = 0; |
| 132 | } |
| 133 | |
| 134 | bool GLHelper::makeCurrent(EGLSurface surface) { |
| 135 | EGLint result; |
| 136 | |
| 137 | result = eglMakeCurrent(mDisplay, surface, surface, mContext); |
| 138 | if (result != EGL_TRUE) { |
| 139 | fprintf(stderr, "eglMakeCurrent error: %#x\n", eglGetError()); |
| 140 | return false; |
| 141 | } |
| 142 | |
| 143 | EGLint w, h; |
| 144 | eglQuerySurface(mDisplay, surface, EGL_WIDTH, &w); |
| 145 | eglQuerySurface(mDisplay, surface, EGL_HEIGHT, &h); |
| 146 | glViewport(0, 0, w, h); |
| 147 | |
| 148 | return true; |
| 149 | } |
| 150 | |
| 151 | bool GLHelper::createSurfaceTexture(uint32_t w, uint32_t h, |
| 152 | sp<GLConsumer>* glConsumer, EGLSurface* surface, |
| 153 | GLuint* name) { |
| 154 | if (!makeCurrent(mDummySurface)) { |
| 155 | return false; |
| 156 | } |
| 157 | |
| 158 | *name = 0; |
| 159 | glGenTextures(1, name); |
| 160 | if (*name == 0) { |
| 161 | fprintf(stderr, "glGenTextures error: %#x\n", glGetError()); |
| 162 | return false; |
| 163 | } |
| 164 | |
| 165 | return createNamedSurfaceTexture(*name, w, h, glConsumer, surface); |
| 166 | } |
| 167 | |
| 168 | void GLHelper::destroySurface(EGLSurface* surface) { |
| 169 | if (eglGetCurrentSurface(EGL_READ) == *surface || |
| 170 | eglGetCurrentSurface(EGL_DRAW) == *surface) { |
| 171 | eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, |
| 172 | EGL_NO_CONTEXT); |
| 173 | } |
| 174 | eglDestroySurface(mDisplay, *surface); |
| 175 | *surface = EGL_NO_SURFACE; |
| 176 | } |
| 177 | |
| 178 | bool GLHelper::swapBuffers(EGLSurface surface) { |
| 179 | EGLint result; |
| 180 | result = eglSwapBuffers(mDisplay, surface); |
| 181 | if (result != EGL_TRUE) { |
| 182 | fprintf(stderr, "eglSwapBuffers error: %#x\n", eglGetError()); |
| 183 | return false; |
| 184 | } |
| 185 | return true; |
| 186 | } |
| 187 | |
| 188 | bool GLHelper::getShaderProgram(const char* name, GLuint* outPgm) { |
| 189 | for (size_t i = 0; i < mNumShaders; i++) { |
| 190 | if (strcmp(mShaderDescs[i].name, name) == 0) { |
| 191 | *outPgm = mShaderPrograms[i]; |
| 192 | return true; |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | fprintf(stderr, "unknown shader name: \"%s\"\n", name); |
| 197 | |
| 198 | return false; |
| 199 | } |
| 200 | |
| 201 | bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h, |
| 202 | sp<GLConsumer>* glConsumer, EGLSurface* surface) { |
Dan Stoza | 6780a2d | 2014-03-13 11:31:43 -0700 | [diff] [blame] | 203 | sp<IGraphicBufferProducer> producer; |
| 204 | sp<IGraphicBufferConsumer> consumer; |
Mathias Agopian | 0556d79 | 2017-03-22 15:49:32 -0700 | [diff] [blame] | 205 | BufferQueue::createBufferQueue(&producer, &consumer); |
Dan Stoza | 6780a2d | 2014-03-13 11:31:43 -0700 | [diff] [blame] | 206 | sp<GLConsumer> glc = new GLConsumer(consumer, name, |
Dan Stoza | e49ba8e | 2014-06-24 13:09:19 -0700 | [diff] [blame] | 207 | GL_TEXTURE_EXTERNAL_OES, false, true); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 208 | glc->setDefaultBufferSize(w, h); |
Pablo Ceballos | 19e3e06 | 2015-08-19 16:16:06 -0700 | [diff] [blame] | 209 | producer->setMaxDequeuedBufferCount(2); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 210 | glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER); |
| 211 | |
Dan Stoza | 6780a2d | 2014-03-13 11:31:43 -0700 | [diff] [blame] | 212 | sp<ANativeWindow> anw = new Surface(producer); |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 213 | EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), nullptr); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 214 | if (s == EGL_NO_SURFACE) { |
| 215 | fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError()); |
| 216 | return false; |
| 217 | } |
| 218 | |
| 219 | *glConsumer = glc; |
| 220 | *surface = s; |
| 221 | return true; |
| 222 | } |
| 223 | |
| 224 | bool GLHelper::computeWindowScale(uint32_t w, uint32_t h, float* scale) { |
| 225 | sp<IBinder> dpy = mSurfaceComposerClient->getBuiltInDisplay(0); |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 226 | if (dpy == nullptr) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 227 | fprintf(stderr, "SurfaceComposer::getBuiltInDisplay failed.\n"); |
| 228 | return false; |
| 229 | } |
| 230 | |
| 231 | DisplayInfo info; |
| 232 | status_t err = mSurfaceComposerClient->getDisplayInfo(dpy, &info); |
| 233 | if (err != NO_ERROR) { |
| 234 | fprintf(stderr, "SurfaceComposer::getDisplayInfo failed: %#x\n", err); |
| 235 | return false; |
| 236 | } |
| 237 | |
| 238 | float scaleX = float(info.w) / float(w); |
| 239 | float scaleY = float(info.h) / float(h); |
| 240 | *scale = scaleX < scaleY ? scaleX : scaleY; |
| 241 | |
| 242 | return true; |
| 243 | } |
| 244 | |
| 245 | bool GLHelper::createWindowSurface(uint32_t w, uint32_t h, |
| 246 | sp<SurfaceControl>* surfaceControl, EGLSurface* surface) { |
| 247 | bool result; |
| 248 | status_t err; |
| 249 | |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 250 | if (mSurfaceComposerClient == nullptr) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 251 | mSurfaceComposerClient = new SurfaceComposerClient; |
| 252 | } |
| 253 | err = mSurfaceComposerClient->initCheck(); |
| 254 | if (err != NO_ERROR) { |
| 255 | fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err); |
| 256 | return false; |
| 257 | } |
| 258 | |
| 259 | sp<SurfaceControl> sc = mSurfaceComposerClient->createSurface( |
| 260 | String8("Benchmark"), w, h, PIXEL_FORMAT_RGBA_8888, 0); |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 261 | if (sc == nullptr || !sc->isValid()) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 262 | fprintf(stderr, "Failed to create SurfaceControl.\n"); |
| 263 | return false; |
| 264 | } |
| 265 | |
| 266 | float scale; |
| 267 | result = computeWindowScale(w, h, &scale); |
| 268 | if (!result) { |
| 269 | return false; |
| 270 | } |
| 271 | |
Robert Carr | 4cdc58f | 2017-08-23 14:22:20 -0700 | [diff] [blame] | 272 | SurfaceComposerClient::Transaction{}.setLayer(sc, 0x7FFFFFFF) |
| 273 | .setMatrix(sc, scale, 0.0f, 0.0f, scale) |
| 274 | .show(sc) |
| 275 | .apply(); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 276 | |
| 277 | sp<ANativeWindow> anw = sc->getSurface(); |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 278 | EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), nullptr); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 279 | if (s == EGL_NO_SURFACE) { |
| 280 | fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError()); |
| 281 | return false; |
| 282 | } |
| 283 | |
| 284 | *surfaceControl = sc; |
| 285 | *surface = s; |
| 286 | return true; |
| 287 | } |
| 288 | |
| 289 | static bool compileShader(GLenum shaderType, const char* src, |
| 290 | GLuint* outShader) { |
| 291 | GLuint shader = glCreateShader(shaderType); |
| 292 | if (shader == 0) { |
| 293 | fprintf(stderr, "glCreateShader error: %#x\n", glGetError()); |
| 294 | return false; |
| 295 | } |
| 296 | |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 297 | glShaderSource(shader, 1, &src, nullptr); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 298 | glCompileShader(shader); |
| 299 | |
| 300 | GLint compiled = 0; |
| 301 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); |
| 302 | if (!compiled) { |
| 303 | GLint infoLen = 0; |
| 304 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); |
| 305 | if (infoLen) { |
| 306 | char* buf = new char[infoLen]; |
| 307 | if (buf) { |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 308 | glGetShaderInfoLog(shader, infoLen, nullptr, buf); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 309 | fprintf(stderr, "Shader compile log:\n%s\n", buf); |
| 310 | delete[] buf; |
| 311 | } |
| 312 | } |
| 313 | glDeleteShader(shader); |
| 314 | return false; |
| 315 | } |
| 316 | *outShader = shader; |
| 317 | return true; |
| 318 | } |
| 319 | |
| 320 | static void printShaderSource(const char* const* src) { |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 321 | for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != nullptr; i++) { |
Mark Salyzyn | 92dc3fc | 2014-03-12 13:12:44 -0700 | [diff] [blame] | 322 | fprintf(stderr, "%3zu: %s\n", i+1, src[i]); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 323 | } |
| 324 | } |
| 325 | |
| 326 | static const char* makeShaderString(const char* const* src) { |
| 327 | size_t len = 0; |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 328 | for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != nullptr; i++) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 329 | // The +1 is for the '\n' that will be added. |
| 330 | len += strlen(src[i]) + 1; |
| 331 | } |
| 332 | |
| 333 | char* result = new char[len+1]; |
| 334 | char* end = result; |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 335 | for (size_t i = 0; i < MAX_SHADER_LINES && src[i] != nullptr; i++) { |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 336 | strcpy(end, src[i]); |
| 337 | end += strlen(src[i]); |
| 338 | *end = '\n'; |
| 339 | end++; |
| 340 | } |
| 341 | *end = '\0'; |
| 342 | |
| 343 | return result; |
| 344 | } |
| 345 | |
| 346 | static bool compileShaderLines(GLenum shaderType, const char* const* lines, |
| 347 | GLuint* outShader) { |
| 348 | const char* src = makeShaderString(lines); |
| 349 | bool result = compileShader(shaderType, src, outShader); |
| 350 | if (!result) { |
| 351 | fprintf(stderr, "Shader source:\n"); |
| 352 | printShaderSource(lines); |
Manoj Gupta | 4122118 | 2016-11-01 17:30:24 -0700 | [diff] [blame] | 353 | delete[] src; |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 354 | return false; |
| 355 | } |
| 356 | delete[] src; |
| 357 | |
| 358 | return true; |
| 359 | } |
| 360 | |
| 361 | static bool linkShaderProgram(GLuint vs, GLuint fs, GLuint* outPgm) { |
| 362 | GLuint program = glCreateProgram(); |
| 363 | if (program == 0) { |
| 364 | fprintf(stderr, "glCreateProgram error: %#x\n", glGetError()); |
| 365 | return false; |
| 366 | } |
| 367 | |
| 368 | glAttachShader(program, vs); |
| 369 | glAttachShader(program, fs); |
| 370 | glLinkProgram(program); |
| 371 | GLint linkStatus = GL_FALSE; |
| 372 | glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); |
| 373 | if (linkStatus != GL_TRUE) { |
| 374 | GLint bufLength = 0; |
| 375 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); |
| 376 | if (bufLength) { |
| 377 | char* buf = new char[bufLength]; |
| 378 | if (buf) { |
Yi Kong | 19d5c00 | 2018-07-20 13:39:55 -0700 | [diff] [blame] | 379 | glGetProgramInfoLog(program, bufLength, nullptr, buf); |
Jamie Gennis | 9c183f2 | 2012-12-03 16:44:16 -0800 | [diff] [blame] | 380 | fprintf(stderr, "Program link log:\n%s\n", buf); |
| 381 | delete[] buf; |
| 382 | } |
| 383 | } |
| 384 | glDeleteProgram(program); |
| 385 | program = 0; |
| 386 | } |
| 387 | |
| 388 | *outPgm = program; |
| 389 | return program != 0; |
| 390 | } |
| 391 | |
| 392 | bool GLHelper::setUpShaders(const ShaderDesc* shaderDescs, size_t numShaders) { |
| 393 | mShaderPrograms = new GLuint[numShaders]; |
| 394 | bool result = true; |
| 395 | |
| 396 | for (size_t i = 0; i < numShaders && result; i++) { |
| 397 | GLuint vs, fs; |
| 398 | |
| 399 | result = compileShaderLines(GL_VERTEX_SHADER, |
| 400 | shaderDescs[i].vertexShader, &vs); |
| 401 | if (!result) { |
| 402 | return false; |
| 403 | } |
| 404 | |
| 405 | result = compileShaderLines(GL_FRAGMENT_SHADER, |
| 406 | shaderDescs[i].fragmentShader, &fs); |
| 407 | if (!result) { |
| 408 | glDeleteShader(vs); |
| 409 | return false; |
| 410 | } |
| 411 | |
| 412 | result = linkShaderProgram(vs, fs, &mShaderPrograms[i]); |
| 413 | glDeleteShader(vs); |
| 414 | glDeleteShader(fs); |
| 415 | } |
| 416 | |
| 417 | mNumShaders = numShaders; |
| 418 | mShaderDescs = shaderDescs; |
| 419 | |
| 420 | return result; |
| 421 | } |
| 422 | |
| 423 | bool GLHelper::getDitherTexture(GLuint* outTexName) { |
| 424 | if (mDitherTexture == 0) { |
| 425 | const uint8_t pattern[] = { |
| 426 | 0, 8, 2, 10, |
| 427 | 12, 4, 14, 6, |
| 428 | 3, 11, 1, 9, |
| 429 | 15, 7, 13, 5 |
| 430 | }; |
| 431 | |
| 432 | glGenTextures(1, &mDitherTexture); |
| 433 | glBindTexture(GL_TEXTURE_2D, mDitherTexture); |
| 434 | |
| 435 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 436 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 437 | |
| 438 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
| 439 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
| 440 | |
| 441 | glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, |
| 442 | DITHER_KERNEL_SIZE, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); |
| 443 | } |
| 444 | |
| 445 | *outTexName = mDitherTexture; |
| 446 | |
| 447 | return true; |
| 448 | } |
| 449 | |
| 450 | } |