Loading libs/hwui/Extensions.h +19 −0 Original line number Diff line number Diff line Loading @@ -26,17 +26,33 @@ namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Defines /////////////////////////////////////////////////////////////////////////////// // Debug #define DEBUG_EXTENSIONS 0 // Debug #if DEBUG_EXTENSIONS #define EXT_LOGD(...) LOGD(__VA_ARGS__) #else #define EXT_LOGD(...) #endif class Extensions { public: Extensions() { const char* buffer = (const char*) glGetString(GL_EXTENSIONS); const char* current = buffer; const char* head = current; EXT_LOGD("Available GL extensions:"); do { head = strchr(current, ' '); String8 s(current, head ? head - current : strlen(current)); if (s.length()) { mExtensionList.add(s); EXT_LOGD(" %s", s.string()); } current = head + 1; } while (head); Loading @@ -44,6 +60,7 @@ public: mHasNPot = hasExtension("GL_OES_texture_npot"); mHasDrawPath = hasExtension("GL_NV_draw_path"); mHasCoverageSample = hasExtension("GL_NV_coverage_sample"); mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch"); mExtensions = buffer; } Loading @@ -51,6 +68,7 @@ public: inline bool hasNPot() const { return mHasNPot; } inline bool hasDrawPath() const { return mHasDrawPath; } inline bool hasCoverageSample() const { return mHasCoverageSample; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } bool hasExtension(const char* extension) const { const String8 s(extension); Loading @@ -69,6 +87,7 @@ private: bool mHasNPot; bool mHasDrawPath; bool mHasCoverageSample; bool mHasFramebufferFetch; }; // class Extensions }; // namespace uirenderer Loading libs/hwui/OpenGLRenderer.cpp +64 −33 Original line number Diff line number Diff line Loading @@ -240,11 +240,15 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, if (p) { alpha = p->getAlpha(); if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode); if (!isMode) { // Assume SRC_OVER mode = SkXfermode::kSrcOver_Mode; } } else { mode = getXfermode(p->getXfermode()); } } else { mode = SkXfermode::kSrcOver_Mode; } Loading Loading @@ -519,12 +523,15 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } SkXfermode::Mode mode; if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode); if (!isMode) { // Assume SRC_OVER mode = SkXfermode::kSrcOver_Mode; } } else { mode = getXfermode(p->getXfermode()); } // Skia draws using the color's alpha channel if < 255 // Otherwise, it uses the paint's alpha Loading Loading @@ -731,11 +738,12 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t } } // Setup the blending mode chooseBlending(true, mode, description); // Build and use the appropriate shader useProgram(mCaches.programCache.get(description)); // Setup the blending mode chooseBlending(true, mode); bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit); glUniform1i(mCaches.currentProgram->getUniform("sampler"), textureUnit); Loading Loading @@ -837,9 +845,6 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot GLuint textureUnit = 0; // Setup the blending mode chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode); // Describe the required shaders ProgramDescription description; if (mShader) { Loading @@ -849,6 +854,9 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot mColorFilter->describe(description, mExtensions); } // Setup the blending mode chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description); // Build and use the appropriate shader useProgram(mCaches.programCache.get(description)); Loading Loading @@ -907,11 +915,11 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b mModelView.loadTranslate(left, top, 0.0f); mModelView.scale(right - left, bottom - top, 1.0f); chooseBlending(blend || alpha < 1.0f, mode, description); useProgram(mCaches.programCache.get(description)); mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform); chooseBlending(blend || alpha < 1.0f, mode); // Texture bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0); glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0); Loading Loading @@ -939,24 +947,36 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b glDisableVertexAttribArray(texCoordsSlot); } void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) { void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description) { blend = blend || mode != SkXfermode::kSrcOver_Mode; if (blend) { if (mode < SkXfermode::kPlus_Mode) { if (!mCaches.blend) { glEnable(GL_BLEND); } GLenum sourceMode = gBlends[mode].src; GLenum destMode = gBlends[mode].dst; if (!isPremultiplied && sourceMode == GL_ONE) { sourceMode = GL_SRC_ALPHA; } if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) { glBlendFunc(sourceMode, destMode); mCaches.lastSrcMode = sourceMode; mCaches.lastDstMode = destMode; } } else { // These blend modes are not supported by OpenGL directly and have // to be implemented using shaders. Since the shader will perform // the blending, turn blending off here if (mExtensions.hasFramebufferFetch()) { description.framebufferMode = mode; } if (mCaches.blend) { glDisable(GL_BLEND); } blend = false; } } else if (mCaches.blend) { glDisable(GL_BLEND); } Loading @@ -983,11 +1003,15 @@ void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, flo void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) { if (paint) { if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode); if (!isMode) { // Assume SRC_OVER *mode = SkXfermode::kSrcOver_Mode; } } else { *mode = getXfermode(paint->getXfermode()); } // Skia draws using the color's alpha channel if < 255 // Otherwise, it uses the paint's alpha Loading @@ -1002,6 +1026,13 @@ void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermod } } SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) { if (mode == NULL) { return SkXfermode::kSrcOver_Mode; } return mode->fMode; } void OpenGLRenderer::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) { glActiveTexture(gTextureUnits[textureUnit]); glBindTexture(GL_TEXTURE_2D, texture); Loading libs/hwui/OpenGLRenderer.h +3 −1 Original line number Diff line number Diff line Loading @@ -322,7 +322,9 @@ private: * Enable or disable blending as necessary. This function sets the appropriate * blend function based on the specified xfermode. */ inline void chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied = true); inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description); inline SkXfermode::Mode getXfermode(SkXfermode* mode); /** * Use the specified program with the current GL context. If the program is already Loading libs/hwui/ProgramCache.cpp +20 −2 Original line number Diff line number Diff line Loading @@ -66,6 +66,8 @@ const char* gVS_Footer = // Fragment shaders snippets /////////////////////////////////////////////////////////////////////////////// const char* gFS_Header_Extension_FramebufferFetch = "#extension GL_NV_shader_framebuffer_fetch : enable\n\n"; const char* gFS_Header = "precision mediump float;\n\n"; const char* gFS_Uniforms_Color = Loading Loading @@ -115,6 +117,8 @@ const char* gFS_Main_BitmapShader_Modulate = " fragColor = bitmapColor * fragColor.a;\n"; const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; const char* gFS_Main_FragColor_Blend = " gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n"; const char* gFS_Main_ApplyColorOp[4] = { // None "", Loading Loading @@ -281,7 +285,14 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) { // Set the default precision String8 shader(gFS_Header); String8 shader; bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode; if (blendFramebuffer) { shader.append(gFS_Header_Extension_FramebufferFetch); } shader.append(gFS_Header); // Varyings if (description.hasTexture) { Loading Loading @@ -315,6 +326,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.colorOp == ProgramDescription::kColorBlend) { generateBlend(shader, "blendColors", description.colorMode); } if (blendFramebuffer) { generateBlend(shader, "blendFramebuffer", description.framebufferMode); } if (description.isBitmapNpot) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); } Loading Loading @@ -359,7 +373,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Apply the color op if needed shader.append(gFS_Main_ApplyColorOp[description.colorOp]); // Output the fragment if (!blendFramebuffer) { shader.append(gFS_Main_FragColor); } else { shader.append(gFS_Main_FragColor_Blend); } } // End the shader shader.append(gFS_Footer); Loading libs/hwui/ProgramCache.h +9 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Debug #define DEBUG_PROGRAM_CACHE 0 #define DEBUG_PROGRAM_CACHE 1 // Debug #if DEBUG_PROGRAM_CACHE Loading @@ -61,6 +61,7 @@ namespace uirenderer { #define PROGRAM_MAX_XFERMODE 0x1f #define PROGRAM_XFERMODE_SHADER_SHIFT 26 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20 #define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14 #define PROGRAM_BITMAP_WRAPS_SHIFT 9 #define PROGRAM_BITMAP_WRAPT_SHIFT 11 Loading Loading @@ -93,7 +94,8 @@ struct ProgramDescription { hasBitmap(false), isBitmapNpot(false), hasGradient(false), shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false), bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE), colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) { colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode), framebufferMode(SkXfermode::kClear_Mode) { } // Texturing Loading @@ -113,6 +115,10 @@ struct ProgramDescription { int colorOp; SkXfermode::Mode colorMode; // Framebuffer blending (requires Extensions.hasFramebufferFetch()) // Ignored for all values < SkXfermode::kPlus_Mode SkXfermode::Mode framebufferMode; inline uint32_t getEnumForWrap(GLenum wrap) const { switch (wrap) { case GL_CLAMP_TO_EDGE: Loading Loading @@ -156,6 +162,7 @@ struct ProgramDescription { case kColorNone: break; } key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; return key; } }; // struct ProgramDescription Loading Loading
libs/hwui/Extensions.h +19 −0 Original line number Diff line number Diff line Loading @@ -26,17 +26,33 @@ namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Defines /////////////////////////////////////////////////////////////////////////////// // Debug #define DEBUG_EXTENSIONS 0 // Debug #if DEBUG_EXTENSIONS #define EXT_LOGD(...) LOGD(__VA_ARGS__) #else #define EXT_LOGD(...) #endif class Extensions { public: Extensions() { const char* buffer = (const char*) glGetString(GL_EXTENSIONS); const char* current = buffer; const char* head = current; EXT_LOGD("Available GL extensions:"); do { head = strchr(current, ' '); String8 s(current, head ? head - current : strlen(current)); if (s.length()) { mExtensionList.add(s); EXT_LOGD(" %s", s.string()); } current = head + 1; } while (head); Loading @@ -44,6 +60,7 @@ public: mHasNPot = hasExtension("GL_OES_texture_npot"); mHasDrawPath = hasExtension("GL_NV_draw_path"); mHasCoverageSample = hasExtension("GL_NV_coverage_sample"); mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch"); mExtensions = buffer; } Loading @@ -51,6 +68,7 @@ public: inline bool hasNPot() const { return mHasNPot; } inline bool hasDrawPath() const { return mHasDrawPath; } inline bool hasCoverageSample() const { return mHasCoverageSample; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } bool hasExtension(const char* extension) const { const String8 s(extension); Loading @@ -69,6 +87,7 @@ private: bool mHasNPot; bool mHasDrawPath; bool mHasCoverageSample; bool mHasFramebufferFetch; }; // class Extensions }; // namespace uirenderer Loading
libs/hwui/OpenGLRenderer.cpp +64 −33 Original line number Diff line number Diff line Loading @@ -240,11 +240,15 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, if (p) { alpha = p->getAlpha(); if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode); if (!isMode) { // Assume SRC_OVER mode = SkXfermode::kSrcOver_Mode; } } else { mode = getXfermode(p->getXfermode()); } } else { mode = SkXfermode::kSrcOver_Mode; } Loading Loading @@ -519,12 +523,15 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } SkXfermode::Mode mode; if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode); if (!isMode) { // Assume SRC_OVER mode = SkXfermode::kSrcOver_Mode; } } else { mode = getXfermode(p->getXfermode()); } // Skia draws using the color's alpha channel if < 255 // Otherwise, it uses the paint's alpha Loading Loading @@ -731,11 +738,12 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t } } // Setup the blending mode chooseBlending(true, mode, description); // Build and use the appropriate shader useProgram(mCaches.programCache.get(description)); // Setup the blending mode chooseBlending(true, mode); bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit); glUniform1i(mCaches.currentProgram->getUniform("sampler"), textureUnit); Loading Loading @@ -837,9 +845,6 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot GLuint textureUnit = 0; // Setup the blending mode chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode); // Describe the required shaders ProgramDescription description; if (mShader) { Loading @@ -849,6 +854,9 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot mColorFilter->describe(description, mExtensions); } // Setup the blending mode chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description); // Build and use the appropriate shader useProgram(mCaches.programCache.get(description)); Loading Loading @@ -907,11 +915,11 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b mModelView.loadTranslate(left, top, 0.0f); mModelView.scale(right - left, bottom - top, 1.0f); chooseBlending(blend || alpha < 1.0f, mode, description); useProgram(mCaches.programCache.get(description)); mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform); chooseBlending(blend || alpha < 1.0f, mode); // Texture bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0); glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0); Loading Loading @@ -939,24 +947,36 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b glDisableVertexAttribArray(texCoordsSlot); } void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) { void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description) { blend = blend || mode != SkXfermode::kSrcOver_Mode; if (blend) { if (mode < SkXfermode::kPlus_Mode) { if (!mCaches.blend) { glEnable(GL_BLEND); } GLenum sourceMode = gBlends[mode].src; GLenum destMode = gBlends[mode].dst; if (!isPremultiplied && sourceMode == GL_ONE) { sourceMode = GL_SRC_ALPHA; } if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) { glBlendFunc(sourceMode, destMode); mCaches.lastSrcMode = sourceMode; mCaches.lastDstMode = destMode; } } else { // These blend modes are not supported by OpenGL directly and have // to be implemented using shaders. Since the shader will perform // the blending, turn blending off here if (mExtensions.hasFramebufferFetch()) { description.framebufferMode = mode; } if (mCaches.blend) { glDisable(GL_BLEND); } blend = false; } } else if (mCaches.blend) { glDisable(GL_BLEND); } Loading @@ -983,11 +1003,15 @@ void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, flo void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) { if (paint) { if (!mExtensions.hasFramebufferFetch()) { const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode); if (!isMode) { // Assume SRC_OVER *mode = SkXfermode::kSrcOver_Mode; } } else { *mode = getXfermode(paint->getXfermode()); } // Skia draws using the color's alpha channel if < 255 // Otherwise, it uses the paint's alpha Loading @@ -1002,6 +1026,13 @@ void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermod } } SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) { if (mode == NULL) { return SkXfermode::kSrcOver_Mode; } return mode->fMode; } void OpenGLRenderer::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) { glActiveTexture(gTextureUnits[textureUnit]); glBindTexture(GL_TEXTURE_2D, texture); Loading
libs/hwui/OpenGLRenderer.h +3 −1 Original line number Diff line number Diff line Loading @@ -322,7 +322,9 @@ private: * Enable or disable blending as necessary. This function sets the appropriate * blend function based on the specified xfermode. */ inline void chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied = true); inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description); inline SkXfermode::Mode getXfermode(SkXfermode* mode); /** * Use the specified program with the current GL context. If the program is already Loading
libs/hwui/ProgramCache.cpp +20 −2 Original line number Diff line number Diff line Loading @@ -66,6 +66,8 @@ const char* gVS_Footer = // Fragment shaders snippets /////////////////////////////////////////////////////////////////////////////// const char* gFS_Header_Extension_FramebufferFetch = "#extension GL_NV_shader_framebuffer_fetch : enable\n\n"; const char* gFS_Header = "precision mediump float;\n\n"; const char* gFS_Uniforms_Color = Loading Loading @@ -115,6 +117,8 @@ const char* gFS_Main_BitmapShader_Modulate = " fragColor = bitmapColor * fragColor.a;\n"; const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; const char* gFS_Main_FragColor_Blend = " gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n"; const char* gFS_Main_ApplyColorOp[4] = { // None "", Loading Loading @@ -281,7 +285,14 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) { // Set the default precision String8 shader(gFS_Header); String8 shader; bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode; if (blendFramebuffer) { shader.append(gFS_Header_Extension_FramebufferFetch); } shader.append(gFS_Header); // Varyings if (description.hasTexture) { Loading Loading @@ -315,6 +326,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.colorOp == ProgramDescription::kColorBlend) { generateBlend(shader, "blendColors", description.colorMode); } if (blendFramebuffer) { generateBlend(shader, "blendFramebuffer", description.framebufferMode); } if (description.isBitmapNpot) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); } Loading Loading @@ -359,7 +373,11 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Apply the color op if needed shader.append(gFS_Main_ApplyColorOp[description.colorOp]); // Output the fragment if (!blendFramebuffer) { shader.append(gFS_Main_FragColor); } else { shader.append(gFS_Main_FragColor_Blend); } } // End the shader shader.append(gFS_Footer); Loading
libs/hwui/ProgramCache.h +9 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Debug #define DEBUG_PROGRAM_CACHE 0 #define DEBUG_PROGRAM_CACHE 1 // Debug #if DEBUG_PROGRAM_CACHE Loading @@ -61,6 +61,7 @@ namespace uirenderer { #define PROGRAM_MAX_XFERMODE 0x1f #define PROGRAM_XFERMODE_SHADER_SHIFT 26 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20 #define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14 #define PROGRAM_BITMAP_WRAPS_SHIFT 9 #define PROGRAM_BITMAP_WRAPT_SHIFT 11 Loading Loading @@ -93,7 +94,8 @@ struct ProgramDescription { hasBitmap(false), isBitmapNpot(false), hasGradient(false), shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false), bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE), colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) { colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode), framebufferMode(SkXfermode::kClear_Mode) { } // Texturing Loading @@ -113,6 +115,10 @@ struct ProgramDescription { int colorOp; SkXfermode::Mode colorMode; // Framebuffer blending (requires Extensions.hasFramebufferFetch()) // Ignored for all values < SkXfermode::kPlus_Mode SkXfermode::Mode framebufferMode; inline uint32_t getEnumForWrap(GLenum wrap) const { switch (wrap) { case GL_CLAMP_TO_EDGE: Loading Loading @@ -156,6 +162,7 @@ struct ProgramDescription { case kColorNone: break; } key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; return key; } }; // struct ProgramDescription Loading