/* Copyright 2010 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #include "GrGpuGL.h" #include "GrMemory.h" #include static const GLuint GR_MAX_GLUINT = ~0; static const GLint GR_INVAL_GLINT = ~0; #define SKIP_CACHE_CHECK true static const GLenum gXfermodeCoeff2Blend[] = { GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, }; bool has_gl_extension(const char* ext) { const char* glstr = (const char*) glGetString(GL_EXTENSIONS); int extLength = strlen(ext); while (true) { int n = strcspn(glstr, " "); if (n == extLength && 0 == strncmp(ext, glstr, n)) { return true; } if (0 == glstr[n]) { return false; } glstr += n+1; } } void gl_version(int* major, int* minor) { const char* v = (const char*) glGetString(GL_VERSION); if (NULL == v) { GrAssert(0); *major = 0; *minor = 0; return; } #if GR_GL_DESKTOP int n = sscanf(v, "%d.%d", major, minor); if (n != 2) { GrAssert(0); *major = 0; *minor = 0; return; } #else char profile[2]; int n = sscanf(v, "OpenGL ES-%c%c %d.%d", profile, profile+1, major, minor); bool ok = 4 == n; if (!ok) { int n = sscanf(v, "OpenGL ES %d.%d", major, minor); ok = 2 == n; } if (!ok) { GrAssert(0); *major = 0; *minor = 0; return; } #endif } /////////////////////////////////////////////////////////////////////////////// bool fbo_test(GrGLExts exts, int w, int h) { GLuint testFBO; GR_GLEXT(exts, GenFramebuffers(1, &testFBO)); GR_GLEXT(exts, BindFramebuffer(GR_FRAMEBUFFER, testFBO)); GLuint testRTTex; GR_GL(GenTextures(1, &testRTTex)); GR_GL(BindTexture(GL_TEXTURE_2D, testRTTex)); GR_GL(TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL)); GR_GL(BindTexture(GL_TEXTURE_2D, 0)); GR_GLEXT(exts, FramebufferTexture2D(GR_FRAMEBUFFER, GR_COLOR_ATTACHMENT0, GL_TEXTURE_2D, testRTTex, 0)); GLenum status = GR_GLEXT(exts, CheckFramebufferStatus(GR_FRAMEBUFFER)); GR_GLEXT(exts, DeleteFramebuffers(1, &testFBO)); GR_GL(DeleteTextures(1, &testRTTex)); return status == GR_FRAMEBUFFER_COMPLETE; } /////////////////////////////////////////////////////////////////////////////// GrGpuGL::GrGpuGL() { GrPrintf("------------------------- create GrGpuGL %p --------------\n", this); GrPrintf("------ VENDOR %s\n", glGetString(GL_VENDOR)); GrPrintf("------ RENDERER %s\n", glGetString(GL_RENDERER)); GrPrintf("------ VERSION %s\n", glGetString(GL_VERSION)); GrPrintf("------ EXTENSIONS\n %s \n", glGetString(GL_EXTENSIONS)); GrGLClearErr(); GrGLInitExtensions(&fExts); resetContextHelper(); GrGLRenderTarget::GLRenderTargetIDs defaultRTIDs; GR_GL(GetIntegerv(GR_FRAMEBUFFER_BINDING, (GLint*)&defaultRTIDs.fRTFBOID)); defaultRTIDs.fTexFBOID = defaultRTIDs.fRTFBOID; defaultRTIDs.fMSColorRenderbufferID = 0; defaultRTIDs.fStencilRenderbufferID = 0; GLint vp[4]; GR_GL(GetIntegerv(GL_VIEWPORT, vp)); fHWBounds.fViewportRect.setLTRB(vp[0], vp[1] + vp[3], vp[0] + vp[2], vp[1]); defaultRTIDs.fOwnIDs = false; fDefaultRenderTarget = new GrGLRenderTarget(defaultRTIDs, fHWBounds.fViewportRect, NULL, this); fHWDrawState.fRenderTarget = fDefaultRenderTarget; fRenderTargetChanged = true; fCurrDrawState = fHWDrawState; //////////////////////////////////////////////////////////////////////////// // Check for supported features. int major, minor; gl_version(&major, &minor); GLint numFormats; GR_GL(GetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numFormats)); GrAutoSTMalloc<10, GLint> formats(numFormats); GR_GL(GetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, formats)); for (int i = 0; i < numFormats; ++i) { if (formats[i] == GR_PALETTE8_RGBA8) { f8bitPaletteSupport = true; break; } } GrPrintf("Palette8 support: %s\n", (f8bitPaletteSupport ? "YES" : "NO")); GR_STATIC_ASSERT(0 == kNone_AALevel); GR_STATIC_ASSERT(1 == kLow_AALevel); GR_STATIC_ASSERT(2 == kMed_AALevel); GR_STATIC_ASSERT(3 == kHigh_AALevel); memset(fAASamples, 0, sizeof(fAASamples)); fMSFBOType = kNone_MSFBO; if (has_gl_extension("GL_IMG_multisampled_render_to_texture")) { fMSFBOType = kIMG_MSFBO; GrPrintf("MSAA Support: IMG ES EXT.\n"); } else if (has_gl_extension("GL_APPLE_framebuffer_multisample")) { fMSFBOType = kApple_MSFBO; GrPrintf("MSAA Support: APPLE ES EXT.\n"); } #if GR_GL_DESKTOP else if ((major >= 3) || has_gl_extension("GL_ARB_framebuffer_object") || (has_gl_extension("GL_EXT_framebuffer_multisample") && has_gl_extension("GL_EXT_framebuffer_blit"))) { fMSFBOType = kDesktop_MSFBO; GrPrintf("MSAA Support: DESKTOP\n"); } #endif else { GrPrintf("MSAA Support: NONE\n"); } if (kNone_MSFBO != fMSFBOType) { GLint maxSamples; GLenum maxSampleGetter = (kIMG_MSFBO == fMSFBOType) ? GR_MAX_SAMPLES_IMG : GR_MAX_SAMPLES; GR_GL(GetIntegerv(maxSampleGetter, &maxSamples)); if (maxSamples > 1 ) { fAASamples[kNone_AALevel] = 0; fAASamples[kLow_AALevel] = GrMax(2, GrFixedFloorToInt((GR_FixedHalf) * maxSamples)); fAASamples[kMed_AALevel] = GrMax(2, GrFixedFloorToInt(((GR_Fixed1*3)/4) * maxSamples)); fAASamples[kHigh_AALevel] = maxSamples; } GrPrintf("\tMax Samples: %d\n", maxSamples); } #if GR_GL_DESKTOP fHasStencilWrap = (major >= 2 || (major == 1 && minor >= 4)) || has_gl_extension("GL_EXT_stencil_wrap"); #else fHasStencilWrap = (major >= 2) || has_gl_extension("GL_OES_stencil_wrap"); #endif GrPrintf("Stencil Wrap: %s\n", (fHasStencilWrap ? "YES" : "NO")); #if GR_GL_DESKTOP // we could also look for GL_ATI_separate_stencil extension or // GL_EXT_stencil_two_side but they use different function signatures // than GL2.0+ (and than each other). fSingleStencilPassForWinding = (major >= 2); #else // ES 2 has two sided stencil but 1.1 doesn't. There doesn't seem to be // an ES1 extension. fSingleStencilPassForWinding = (major >= 2); #endif GrPrintf("Single Stencil Pass For Winding: %s\n", (fSingleStencilPassForWinding ? "YES" : "NO")); #if GR_GL_DESKTOP fRGBA8Renderbuffer = true; #else fRGBA8Renderbuffer = has_gl_extension("GL_OES_rgb8_rgba8"); #endif GrPrintf("RGBA Renderbuffer: %s\n", (fRGBA8Renderbuffer ? "YES" : "NO")); #if GR_GL_DESKTOP fBufferLockSupport = true; // we require VBO support and the desktop VBO // extension includes glMapBuffer. #else fBufferLockSupport = has_gl_extension("GL_OES_mapbuffer"); #endif GrPrintf("Map Buffer: %s\n", (fBufferLockSupport ? "YES" : "NO")); #if GR_GL_DESKTOP fNPOTTextureSupport = (major >= 2 || has_gl_extension("GL_ARB_texture_non_power_of_two")) ? kFull_NPOTTextureType : kNone_NPOTTextureType; #else if (has_gl_extension("GL_OES_texture_npot")) { fNPOTTextureSupport = kFull_NPOTTextureType; } else if (major >= 2 || has_gl_extension("GL_APPLE_texture_2D_limited_npot")) { fNPOTTextureSupport = kNoRepeat_NPOTTextureType; } else { fNPOTTextureSupport = kNone_NPOTTextureType; } #endif //////////////////////////////////////////////////////////////////////////// // Experiments to determine limitations that can't be queried. TODO: Make // these a preprocess that generate some compile time constants. /* Experimentation has found that some GLs that support NPOT textures do not support FBOs with a NPOT texture. They report "unsupported" FBO status. I don't know how to explicitly query for this. Do an experiment. Note they may support NPOT with a renderbuffer but not a texture. Presumably, the implementation bloats the renderbuffer internally to the next POT. */ if (fNPOTTextureSupport == kFull_NPOTTextureType) { bool npotFBOSuccess = fbo_test(fExts, 200, 200); if (!npotFBOSuccess) { fNPOTTextureSupport = kNonRendertarget_NPOTTextureType; GrPrintf("NPOT Renderbuffer Test: FAILED\n"); } else { GrPrintf("NPOT Renderbuffer Test: PASSED\n"); } } switch (fNPOTTextureSupport) { case kNone_NPOTTextureType: GrPrintf("NPOT Support: NONE\n"); break; case kNoRepeat_NPOTTextureType: GrPrintf("NPOT Support: NO REPEAT\n"); break; case kNonRendertarget_NPOTTextureType: GrPrintf("NPOT Support: NO FBOTEX\n"); break; case kFull_NPOTTextureType: GrPrintf("NPOT Support: FULL\n"); break; } // sanity check to make sure we can at least create an FBO from a POT texture if (fNPOTTextureSupport < kFull_NPOTTextureType) { bool npotFBOSuccess = fbo_test(fExts, 128, 128); if (!npotFBOSuccess) { GrPrintf("FBO Sanity Test: FAILED\n"); } else { GrPrintf("FBO Sanity Test: PASSED\n"); } } /* The iPhone 4 has a restriction that for an FBO with texture color attachment with height <= 8 then the width must be <= height. Here we look for such a limitation. */ fMinRenderTargetHeight = GR_INVAL_GLINT; GLint maxRenderSize; glGetIntegerv(GR_MAX_RENDERBUFFER_SIZE, &maxRenderSize); GrPrintf("Small height FBO texture experiments\n"); for (GLuint i = 1; i <= 256; (kFull_NPOTTextureType != fNPOTTextureSupport) ? i *= 2 : ++i) { GLuint w = maxRenderSize; GLuint h = i; if (fbo_test(fExts, w, h)) { GrPrintf("\t[%d, %d]: PASSED\n", w, h); fMinRenderTargetHeight = i; break; } else { GrPrintf("\t[%d, %d]: FAILED\n", w, h); } } GrAssert(GR_INVAL_GLINT != fMinRenderTargetHeight); GrPrintf("Small width FBO texture experiments\n"); fMinRenderTargetWidth = GR_MAX_GLUINT; for (GLuint i = 1; i <= 256; (kFull_NPOTTextureType != fNPOTTextureSupport) ? i *= 2 : ++i) { GLuint w = i; GLuint h = maxRenderSize; if (fbo_test(fExts, w, h)) { GrPrintf("\t[%d, %d]: PASSED\n", w, h); fMinRenderTargetWidth = i; break; } else { GrPrintf("\t[%d, %d]: FAILED\n", w, h); } } GrAssert(GR_INVAL_GLINT != fMinRenderTargetWidth); #if GR_IOS_BUILD /* The iPad seems to fail, at least sometimes, if the height is < 16, so we pin the values here for now. A better fix might be to conditionalize this based on known that its an iPad (or some other check). */ fMinRenderTargetWidth = GrMax(fMinRenderTargetWidth, 16); fMinRenderTargetHeight = GrMax(fMinRenderTargetHeight, 16); #endif // bind back to original FBO GR_GLEXT(fExts, BindFramebuffer(GR_FRAMEBUFFER, defaultRTIDs.fRTFBOID)); #if GR_COLLECT_STATS ++fStats.fRenderTargetChngCnt; #endif eraseStencil(0, ~0); } GrGpuGL::~GrGpuGL() { fDefaultRenderTarget->abandon(); fDefaultRenderTarget->unref(); } void GrGpuGL::resetContextHelper() { // We detect cases when blending is effectively off fHWBlendDisabled = false; GR_GL(Enable(GL_BLEND)); // this is always disabled GR_GL(Disable(GL_CULL_FACE)); GR_GL(Disable(GL_DITHER)); #if GR_GL_DESKTOP GR_GL(Disable(GL_LINE_SMOOTH)); GR_GL(Disable(GL_POINT_SMOOTH)); GR_GL(Disable(GL_MULTISAMPLE)); #endif // we only ever use lines in hairline mode GR_GL(LineWidth(1)); GR_GL(ActiveTexture(GL_TEXTURE0)); fHWDrawState.fFlagBits = 0; // illegal values fHWDrawState.fSrcBlend = (BlendCoeff)-1; fHWDrawState.fDstBlend = (BlendCoeff)-1; fHWDrawState.fColor = GrColor_ILLEGAL; fHWDrawState.fPointSize = -1; fHWDrawState.fTexture = NULL; GR_GL(Scissor(0,0,0,0)); fHWBounds.fScissorRect.setLTRB(0,0,0,0); fHWBounds.fScissorEnabled = false; GR_GL(Disable(GL_SCISSOR_TEST)); fHWDrawState.fSamplerState.setRadial2Params(-GR_ScalarMax, -GR_ScalarMax, true); for (int i = 0; i < kMatrixModeCount; i++) { fHWDrawState.fMatrixModeCache[i].setScale(GR_ScalarMax, GR_ScalarMax); // illegal } // disabling the stencil test also disables // stencil buffer writes GR_GL(Disable(GL_STENCIL_TEST)); GR_GL(StencilMask(0xffffffff)); GR_GL(ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); fHWDrawState.fReverseFill = false; fHWDrawState.fStencilPass = kNone_StencilPass; fHWStencilClip = false; fHWGeometryState.fIndexBuffer = NULL; fHWGeometryState.fVertexBuffer = NULL; GR_GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GR_GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); fHWDrawState.fRenderTarget = NULL; } void GrGpuGL::resetContext() { INHERITED::resetContext(); resetContextHelper(); } // defines stencil formats from more to less preferred #if GR_GL_ES GLenum GR_GL_STENCIL_FORMAT_ARRAY[] = { GR_STENCIL_INDEX8, }; #else GLenum GR_GL_STENCIL_FORMAT_ARRAY[] = { GR_STENCIL_INDEX8, GR_STENCIL_INDEX16, GR_UNSIGNED_INT_24_8, GR_DEPTH_STENCIL, }; #endif // good to set a break-point here to know when createTexture fails static GrTexture* return_null_texture() { // GrAssert(!"null texture"); return NULL; } #if GR_DEBUG static size_t as_size_t(int x) { return x; } #endif GrRenderTarget* GrGpuGL::createPlatformRenderTarget( intptr_t platformRenderTarget, int width, int height) { GrGLRenderTarget::GLRenderTargetIDs rtIDs; rtIDs.fStencilRenderbufferID = 0; rtIDs.fMSColorRenderbufferID = 0; rtIDs.fTexFBOID = 0; rtIDs.fOwnIDs = false; GrIRect viewport; // viewport is in GL coords (top >= bottom) viewport.setLTRB(0, height, width, 0); rtIDs.fRTFBOID = (GLuint)platformRenderTarget; rtIDs.fTexFBOID = (GLuint)platformRenderTarget; GrGLRenderTarget* rt = new GrGLRenderTarget(rtIDs, viewport, NULL, this); return rt; } GrTexture* GrGpuGL::createTexture(const TextureDesc& desc, const void* srcData, size_t rowBytes) { #if GR_COLLECT_STATS ++fStats.fTextureCreateCnt; #endif GrGLTexture::GLTextureDesc glDesc; GLenum internalFormat; glDesc.fContentWidth = desc.fWidth; glDesc.fContentHeight = desc.fHeight; glDesc.fAllocWidth = desc.fWidth; glDesc.fAllocHeight = desc.fHeight; glDesc.fFormat = desc.fFormat; bool renderTarget = 0 != (desc.fFlags & kRenderTarget_TextureFlag); if (!canBeTexture(desc.fFormat, &internalFormat, &glDesc.fUploadFormat, &glDesc.fUploadType)) { return return_null_texture(); } GrAssert(as_size_t(desc.fAALevel) < GR_ARRAY_COUNT(fAASamples)); GLint samples = fAASamples[desc.fAALevel]; if (kNone_MSFBO == fMSFBOType && desc.fAALevel != kNone_AALevel) { GrPrintf("AA RT requested but not supported on this platform."); } GR_GL(GenTextures(1, &glDesc.fTextureID)); if (!glDesc.fTextureID) { return return_null_texture(); } glDesc.fUploadByteCount = GrTexture::BytesPerPixel(desc.fFormat); /* * check if our srcData has extra bytes past each row. If so, we need * to trim those off here, since GL doesn't let us pass the rowBytes as * a parameter to glTexImage2D */ #if GR_GL_DESKTOP if (srcData) { GR_GL(PixelStorei(GL_UNPACK_ROW_LENGTH, rowBytes / glDesc.fUploadByteCount)); } #else GrAutoSMalloc<128 * 128> trimStorage; size_t trimRowBytes = desc.fWidth * glDesc.fUploadByteCount; if (srcData && (trimRowBytes < rowBytes)) { size_t trimSize = desc.fHeight * trimRowBytes; trimStorage.realloc(trimSize); // now copy the data into our new storage, skipping the trailing bytes const char* src = (const char*)srcData; char* dst = (char*)trimStorage.get(); for (uint32_t y = 0; y < desc.fHeight; y++) { memcpy(dst, src, trimRowBytes); src += rowBytes; dst += trimRowBytes; } // now point srcData to our trimmed version srcData = trimStorage.get(); } #endif if (fNPOTTextureSupport < kNonRendertarget_NPOTTextureType || (fNPOTTextureSupport == kNonRendertarget_NPOTTextureType && renderTarget)) { glDesc.fAllocWidth = GrNextPow2(desc.fWidth); glDesc.fAllocHeight = GrNextPow2(desc.fHeight); } if (renderTarget) { glDesc.fAllocWidth = GrMax(fMinRenderTargetWidth, glDesc.fAllocWidth); glDesc.fAllocHeight = GrMax(fMinRenderTargetHeight, glDesc.fAllocHeight); } GR_GL(BindTexture(GL_TEXTURE_2D, glDesc.fTextureID)); #if GR_COLLECT_STATS ++fStats.fTextureChngCnt; #endif GR_GL(PixelStorei(GL_UNPACK_ALIGNMENT, glDesc.fUploadByteCount)); if (GrTexture::kIndex_8_PixelConfig == desc.fFormat && supports8BitPalette()) { // ES only supports CompressedTexImage2D, not CompressedTexSubimage2D GrAssert(desc.fWidth == glDesc.fAllocWidth); GrAssert(desc.fHeight == glDesc.fAllocHeight); GLsizei imageSize = glDesc.fAllocWidth * glDesc.fAllocHeight + kColorTableSize; GR_GL(CompressedTexImage2D(GL_TEXTURE_2D, 0, glDesc.fUploadFormat, glDesc.fAllocWidth, glDesc.fAllocHeight, 0, imageSize, srcData)); GrGL_RestoreResetRowLength(); } else { if (NULL != srcData && (glDesc.fAllocWidth != desc.fWidth || glDesc.fAllocHeight != desc.fHeight)) { GR_GL(TexImage2D(GL_TEXTURE_2D, 0, internalFormat, glDesc.fAllocWidth, glDesc.fAllocHeight, 0, glDesc.fUploadFormat, glDesc.fUploadType, NULL)); GR_GL(TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, desc.fWidth, desc.fHeight, glDesc.fUploadFormat, glDesc.fUploadType, srcData)); GrGL_RestoreResetRowLength(); uint32_t extraW = glDesc.fAllocWidth - desc.fWidth; uint32_t extraH = glDesc.fAllocHeight - desc.fHeight; uint32_t maxTexels = extraW * extraH; maxTexels = GrMax(extraW * desc.fHeight, maxTexels); maxTexels = GrMax(desc.fWidth * extraH, maxTexels); GrAutoSMalloc<128*128> texels(glDesc.fUploadByteCount * maxTexels); uint32_t rowSize = desc.fWidth * glDesc.fUploadByteCount; if (extraH) { uint8_t* lastRowStart = (uint8_t*) srcData + (desc.fHeight - 1) * rowSize; uint8_t* extraRowStart = (uint8_t*)texels.get(); for (uint32_t i = 0; i < extraH; ++i) { memcpy(extraRowStart, lastRowStart, rowSize); extraRowStart += rowSize; } GR_GL(TexSubImage2D(GL_TEXTURE_2D, 0, 0, desc.fHeight, desc.fWidth, extraH, glDesc.fUploadFormat, glDesc.fUploadType, texels.get())); } if (extraW) { uint8_t* edgeTexel = (uint8_t*)srcData + rowSize - glDesc.fUploadByteCount; uint8_t* extraTexel = (uint8_t*)texels.get(); for (uint32_t j = 0; j < desc.fHeight; ++j) { for (uint32_t i = 0; i < extraW; ++i) { memcpy(extraTexel, edgeTexel, glDesc.fUploadByteCount); extraTexel += glDesc.fUploadByteCount; } edgeTexel += rowSize; } GR_GL(TexSubImage2D(GL_TEXTURE_2D, 0, desc.fWidth, 0, extraW, desc.fHeight, glDesc.fUploadFormat, glDesc.fUploadType, texels.get())); } if (extraW && extraH) { uint8_t* cornerTexel = (uint8_t*)srcData + desc.fHeight * rowSize - glDesc.fUploadByteCount; uint8_t* extraTexel = (uint8_t*)texels.get(); for (uint32_t i = 0; i < extraW*extraH; ++i) { memcpy(extraTexel, cornerTexel, glDesc.fUploadByteCount); extraTexel += glDesc.fUploadByteCount; } GR_GL(TexSubImage2D(GL_TEXTURE_2D, 0, desc.fWidth, desc.fHeight, extraW, extraH, glDesc.fUploadFormat, glDesc.fUploadType, texels.get())); } } else { GR_GL(TexImage2D(GL_TEXTURE_2D, 0, internalFormat, glDesc.fAllocWidth, glDesc.fAllocHeight, 0, glDesc.fUploadFormat, glDesc.fUploadType, srcData)); GrGL_RestoreResetRowLength(); } } glDesc.fOrientation = GrGLTexture::kTopDown_Orientation; GrGLRenderTarget::GLRenderTargetIDs rtIDs; rtIDs.fStencilRenderbufferID = 0; rtIDs.fMSColorRenderbufferID = 0; rtIDs.fRTFBOID = 0; rtIDs.fTexFBOID = 0; rtIDs.fOwnIDs = true; GLenum msColorRenderbufferFormat = -1; if (renderTarget) { #if GR_COLLECT_STATS ++fStats.fRenderTargetCreateCnt; #endif bool failed = true; GLenum status; GLint err; // If need have both RT flag and srcData we have // to invert the data before uploading because FBO // will be rendered bottom up GrAssert(NULL == srcData); glDesc.fOrientation = GrGLTexture::kBottomUp_Orientation; GR_GLEXT(fExts, GenFramebuffers(1, &rtIDs.fTexFBOID)); GrAssert(rtIDs.fTexFBOID); // If we are using multisampling and any extension other than the IMG // one we will create two FBOs. We render to one and then resolve to // the texture bound to the other. The IMG extension does an implicit // resolve. if (samples > 1 && kIMG_MSFBO != fMSFBOType && kNone_MSFBO != fMSFBOType) { GR_GLEXT(fExts, GenFramebuffers(1, &rtIDs.fRTFBOID)); GrAssert(0 != rtIDs.fRTFBOID); GR_GLEXT(fExts, GenRenderbuffers(1, &rtIDs.fMSColorRenderbufferID)); GrAssert(0 != rtIDs.fMSColorRenderbufferID); if (!fboInternalFormat(desc.fFormat, &msColorRenderbufferFormat)) { GR_GLEXT(fExts, DeleteRenderbuffers(1, &rtIDs.fMSColorRenderbufferID)); GR_GL(DeleteTextures(1, &glDesc.fTextureID)); GR_GLEXT(fExts, DeleteFramebuffers(1, &rtIDs.fTexFBOID)); GR_GLEXT(fExts, DeleteFramebuffers(1, &rtIDs.fRTFBOID)); fHWDrawState.fTexture = NULL; return return_null_texture(); } } else { rtIDs.fRTFBOID = rtIDs.fTexFBOID; } int attempts = 1; if (!(kNoPathRendering_TextureFlag & desc.fFlags)) { GR_GLEXT(fExts, GenRenderbuffers(1, &rtIDs.fStencilRenderbufferID)); GrAssert(0 != rtIDs.fStencilRenderbufferID); attempts = GR_ARRAY_COUNT(GR_GL_STENCIL_FORMAT_ARRAY); } // need to unbind the texture before we call FramebufferTexture2D GR_GL(BindTexture(GL_TEXTURE_2D, 0)); #if GR_COLLECT_STATS ++fStats.fTextureChngCnt; #endif fHWDrawState.fTexture = NULL; err = ~GL_NO_ERROR; for (int i = 0; i < attempts; ++i) { if (rtIDs.fStencilRenderbufferID) { GR_GLEXT(fExts, BindRenderbuffer(GR_RENDERBUFFER, rtIDs.fStencilRenderbufferID)); if (samples > 1) { GR_GLEXT_NO_ERR(fExts, RenderbufferStorageMultisample( GR_RENDERBUFFER, samples, GR_GL_STENCIL_FORMAT_ARRAY[i], glDesc.fAllocWidth, glDesc.fAllocHeight)); } else { GR_GLEXT_NO_ERR(fExts, RenderbufferStorage( GR_RENDERBUFFER, GR_GL_STENCIL_FORMAT_ARRAY[i], glDesc.fAllocWidth, glDesc.fAllocHeight)); } err = glGetError(); if (err != GL_NO_ERROR) { continue; } } if (rtIDs.fRTFBOID != rtIDs.fTexFBOID) { GrAssert(samples > 1); GR_GLEXT(fExts, BindRenderbuffer(GR_RENDERBUFFER, rtIDs.fMSColorRenderbufferID)); GR_GLEXT_NO_ERR(fExts, RenderbufferStorageMultisample( GR_RENDERBUFFER, samples, msColorRenderbufferFormat, glDesc.fAllocWidth, glDesc.fAllocHeight)); err = glGetError(); if (err != GL_NO_ERROR) { continue; } } GR_GLEXT(fExts, BindFramebuffer(GR_FRAMEBUFFER, rtIDs.fTexFBOID)); #if GR_COLLECT_STATS ++fStats.fRenderTargetChngCnt; #endif if (kIMG_MSFBO == fMSFBOType && samples > 1) { GR_GLEXT(fExts, FramebufferTexture2DMultisample( GR_FRAMEBUFFER, GR_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glDesc.fTextureID, 0, samples)); } else { GR_GLEXT(fExts, FramebufferTexture2D(GR_FRAMEBUFFER, GR_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glDesc.fTextureID, 0)); } if (rtIDs.fRTFBOID != rtIDs.fTexFBOID) { GLenum status = GR_GLEXT(fExts, CheckFramebufferStatus(GR_FRAMEBUFFER)); if (status != GR_FRAMEBUFFER_COMPLETE) { GrPrintf("-- glCheckFramebufferStatus %x %d %d\n", status, desc.fWidth, desc.fHeight); continue; } GR_GLEXT(fExts, BindFramebuffer(GR_FRAMEBUFFER, rtIDs.fRTFBOID)); #if GR_COLLECT_STATS ++fStats.fRenderTargetChngCnt; #endif GR_GLEXT(fExts, FramebufferRenderbuffer(GR_FRAMEBUFFER, GR_COLOR_ATTACHMENT0, GR_RENDERBUFFER, rtIDs.fMSColorRenderbufferID)); } if (rtIDs.fStencilRenderbufferID) { // bind the stencil to rt fbo if present, othewise the tex fbo GR_GLEXT(fExts, FramebufferRenderbuffer(GR_FRAMEBUFFER, GR_STENCIL_ATTACHMENT, GR_RENDERBUFFER, rtIDs.fStencilRenderbufferID)); } status = GR_GLEXT(fExts, CheckFramebufferStatus(GR_FRAMEBUFFER)); #if GR_GL_DESKTOP // On some implementations you have to be bound as DEPTH_STENCIL. // (Even binding to DEPTH and STENCIL separately with the same // buffer doesn't work.) if (rtIDs.fStencilRenderbufferID && status != GR_FRAMEBUFFER_COMPLETE) { GR_GLEXT(fExts, FramebufferRenderbuffer(GR_FRAMEBUFFER, GR_STENCIL_ATTACHMENT, GR_RENDERBUFFER, 0)); GR_GLEXT(fExts, FramebufferRenderbuffer(GR_FRAMEBUFFER, GR_DEPTH_STENCIL_ATTACHMENT, GR_RENDERBUFFER, rtIDs.fStencilRenderbufferID)); status = GR_GLEXT(fExts, CheckFramebufferStatus(GR_FRAMEBUFFER)); } #endif if (status != GR_FRAMEBUFFER_COMPLETE) { GrPrintf("-- glCheckFramebufferStatus %x %d %d\n", status, desc.fWidth, desc.fHeight); #if GR_GL_DESKTOP if (rtIDs.fStencilRenderbufferID) { GR_GLEXT(fExts, FramebufferRenderbuffer(GR_FRAMEBUFFER, GR_DEPTH_STENCIL_ATTACHMENT, GR_RENDERBUFFER, 0)); } #endif continue; } // we're successful! failed = false; break; } if (failed) { if (rtIDs.fStencilRenderbufferID) { GR_GLEXT(fExts, DeleteRenderbuffers(1, &rtIDs.fStencilRenderbufferID)); } if (rtIDs.fMSColorRenderbufferID) { GR_GLEXT(fExts, DeleteRenderbuffers(1, &rtIDs.fMSColorRenderbufferID)); } if (rtIDs.fRTFBOID != rtIDs.fTexFBOID) { GR_GLEXT(fExts, DeleteFramebuffers(1, &rtIDs.fRTFBOID)); } if (rtIDs.fTexFBOID) { GR_GLEXT(fExts, DeleteFramebuffers(1, &rtIDs.fTexFBOID)); } GR_GL(DeleteTextures(1, &glDesc.fTextureID)); return return_null_texture(); } } #ifdef TRACE_TEXTURE_CREATION GrPrintf("--- new texture [%d] size=(%d %d) bpp=%d\n", tex->fTextureID, width, height, tex->fUploadByteCount); #endif GrGLTexture* tex = new GrGLTexture(glDesc, rtIDs, this); if (0 != rtIDs.fTexFBOID) { GrRenderTarget* rt = tex->asRenderTarget(); // We've messed with FBO state but may not have set the correct viewport // so just dirty the rendertarget state to force a resend. fHWDrawState.fRenderTarget = NULL; // clear the new stencil buffer if we have one if (!(desc.fFlags & kNoPathRendering_TextureFlag)) { GrRenderTarget* rtSave = fCurrDrawState.fRenderTarget; fCurrDrawState.fRenderTarget = rt; eraseStencil(0, ~0); fCurrDrawState.fRenderTarget = rtSave; } } return tex; } GrRenderTarget* GrGpuGL::defaultRenderTarget() { return fDefaultRenderTarget; } GrVertexBuffer* GrGpuGL::createVertexBuffer(uint32_t size, bool dynamic) { GLuint id; GR_GL(GenBuffers(1, &id)); if (id) { GR_GL(BindBuffer(GL_ARRAY_BUFFER, id)); GrGLClearErr(); // make sure driver can allocate memory for this buffer GR_GL_NO_ERR(BufferData(GL_ARRAY_BUFFER, size, NULL, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW)); if (glGetError() != GL_NO_ERROR) { GR_GL(DeleteBuffers(1, &id)); // deleting bound buffer does implicit bind to 0 fHWGeometryState.fVertexBuffer = NULL; return NULL; } GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(id, this, size, dynamic); fHWGeometryState.fVertexBuffer = vertexBuffer; return vertexBuffer; } return NULL; } GrIndexBuffer* GrGpuGL::createIndexBuffer(uint32_t size, bool dynamic) { GLuint id; GR_GL(GenBuffers(1, &id)); if (id) { GR_GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, id)); GrGLClearErr(); // make sure driver can allocate memory for this buffer GR_GL_NO_ERR(BufferData(GL_ELEMENT_ARRAY_BUFFER, size, NULL, dynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW)); if (glGetError() != GL_NO_ERROR) { GR_GL(DeleteBuffers(1, &id)); // deleting bound buffer does implicit bind to 0 fHWGeometryState.fIndexBuffer = NULL; return NULL; } GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(id, this, size, dynamic); fHWGeometryState.fIndexBuffer = indexBuffer; return indexBuffer; } return NULL; } void GrGpuGL::setDefaultRenderTargetSize(uint32_t width, uint32_t height) { GrIRect viewport(0, height, width, 0); if (viewport != fDefaultRenderTarget->viewport()) { fDefaultRenderTarget->setViewport(viewport); if (fHWDrawState.fRenderTarget == fDefaultRenderTarget) { fHWDrawState.fRenderTarget = NULL; } } } void GrGpuGL::flushScissor(const GrIRect* rect) { GrAssert(NULL != fCurrDrawState.fRenderTarget); const GrIRect& vp = ((GrGLRenderTarget*)fCurrDrawState.fRenderTarget)->viewport(); if (NULL != rect && rect->contains(vp)) { rect = NULL; } if (NULL != rect) { GrIRect scissor; // viewport is already in GL coords // create a scissor in GL coords (top > bottom) scissor.setLTRB(vp.fLeft + rect->fLeft, vp.fTop - rect->fTop, vp.fLeft + rect->fRight, vp.fTop - rect->fBottom); if (fHWBounds.fScissorRect != scissor) { GR_GL(Scissor(scissor.fLeft, scissor.fBottom, scissor.width(), -scissor.height())); fHWBounds.fScissorRect = scissor; } if (!fHWBounds.fScissorEnabled) { GR_GL(Enable(GL_SCISSOR_TEST)); fHWBounds.fScissorEnabled = true; } } else { if (fHWBounds.fScissorEnabled) { GR_GL(Disable(GL_SCISSOR_TEST)); fHWBounds.fScissorEnabled = false; } } } void GrGpuGL::setSamplerStateImm(const GrSamplerState& state) { GLenum filter = state.isFilter() ? GL_LINEAR : GL_NEAREST; GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)); GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)); GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GrGLTexture::gWrapMode2GLWrap[state.getWrapX()])); GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GrGLTexture::gWrapMode2GLWrap[state.getWrapY()])); } void GrGpuGL::eraseColor(GrColor color) { flushRenderTarget(); if (fHWBounds.fScissorEnabled) { GR_GL(Disable(GL_SCISSOR_TEST)); } GR_GL(ColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE)); GR_GL(ClearColor(GrColorUnpackR(color)/255.f, GrColorUnpackG(color)/255.f, GrColorUnpackB(color)/255.f, GrColorUnpackA(color)/255.f)); GR_GL(Clear(GL_COLOR_BUFFER_BIT)); fHWBounds.fScissorEnabled = false; fWriteMaskChanged = true; } void GrGpuGL::eraseStencil(uint32_t value, uint32_t mask) { flushRenderTarget(); if (fHWBounds.fScissorEnabled) { GR_GL(Disable(GL_SCISSOR_TEST)); } GR_GL(StencilMask(mask)); GR_GL(ClearStencil(value)); GR_GL(Clear(GL_STENCIL_BUFFER_BIT)); fHWBounds.fScissorEnabled = false; fWriteMaskChanged = true; } void GrGpuGL::eraseStencilClip() { GLint stencilBitCount; GR_GL(GetIntegerv(GL_STENCIL_BITS, &stencilBitCount)); GrAssert(stencilBitCount > 0); GLint clipStencilMask = (1 << (stencilBitCount - 1)); eraseStencil(0, clipStencilMask); } void GrGpuGL::forceRenderTargetFlush() { flushRenderTarget(); } bool GrGpuGL::readPixels(int left, int top, int width, int height, GrTexture::PixelConfig config, void* buffer) { GLenum internalFormat; // we don't use this for glReadPixels GLenum format; GLenum type; if (!this->canBeTexture(config, &internalFormat, &format, &type)) { return false; } GrAssert(NULL != fCurrDrawState.fRenderTarget); const GrIRect& vp = ((GrGLRenderTarget*)fCurrDrawState.fRenderTarget)->viewport(); // Brian says that viewport rects are already upside down (grrrrr) glReadPixels(left, -vp.height() - top - height, width, height, format, type, buffer); // now reverse the order of the rows, since GL's are bottom-to-top, but our // API presents top-to-bottom { size_t stride = width * GrTexture::BytesPerPixel(config); GrAutoMalloc rowStorage(stride); void* tmp = rowStorage.get(); const int halfY = height >> 1; char* top = reinterpret_cast(buffer); char* bottom = top + (height - 1) * stride; for (int y = 0; y < halfY; y++) { memcpy(tmp, top, stride); memcpy(top, bottom, stride); memcpy(bottom, tmp, stride); top += stride; bottom -= stride; } } return true; } void GrGpuGL::flushRenderTarget() { if (fHWDrawState.fRenderTarget != fCurrDrawState.fRenderTarget) { GrGLRenderTarget* rt = (GrGLRenderTarget*)fCurrDrawState.fRenderTarget; GR_GLEXT(fExts, BindFramebuffer(GR_FRAMEBUFFER, rt->renderFBOID())); #if GR_COLLECT_STATS ++fStats.fRenderTargetChngCnt; #endif rt->setDirty(true); #if GR_DEBUG GLenum status = GR_GLEXT(fExts, CheckFramebufferStatus(GR_FRAMEBUFFER)); if (status != GR_FRAMEBUFFER_COMPLETE) { GrPrintf("-- glCheckFramebufferStatus %x\n", status); } #endif fHWDrawState.fRenderTarget = fCurrDrawState.fRenderTarget; const GrIRect& vp = rt->viewport(); fRenderTargetChanged = true; if (fHWBounds.fViewportRect != vp) { GR_GL(Viewport(vp.fLeft, vp.fBottom, vp.width(), -vp.height())); fHWBounds.fViewportRect = vp; } } } GLenum gPrimitiveType2GLMode[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_POINTS, GL_LINES, GL_LINE_STRIP }; void GrGpuGL::drawIndexedHelper(PrimitiveType type, uint32_t startVertex, uint32_t startIndex, uint32_t vertexCount, uint32_t indexCount) { GrAssert((size_t)type < GR_ARRAY_COUNT(gPrimitiveType2GLMode)); GLvoid* indices = (GLvoid*)(sizeof(uint16_t) * startIndex); if (kReserved_GeometrySrcType == fGeometrySrc.fIndexSrc) { indices = (GLvoid*)((intptr_t)indices + (intptr_t)fIndices.get()); } else if (kArray_GeometrySrcType == fGeometrySrc.fIndexSrc) { indices = (GLvoid*)((intptr_t)indices + (intptr_t)fGeometrySrc.fIndexArray); } GR_GL(DrawElements(gPrimitiveType2GLMode[type], indexCount, GL_UNSIGNED_SHORT, indices)); } void GrGpuGL::drawNonIndexedHelper(PrimitiveType type, uint32_t startVertex, uint32_t vertexCount) { GrAssert((size_t)type < GR_ARRAY_COUNT(gPrimitiveType2GLMode)); GR_GL(DrawArrays(gPrimitiveType2GLMode[type], 0, vertexCount)); } #if !defined(SK_GL_HAS_COLOR4UB) static inline GrFixed byte2fixed(unsigned value) { return (value + (value >> 7)) << 8; } #endif void GrGpuGL::resolveTextureRenderTarget(GrGLTexture* texture) { GrGLRenderTarget* rt = (GrGLRenderTarget*) texture->asRenderTarget(); if (NULL != rt && rt->needsResolve()) { GrAssert(kNone_MSFBO != fMSFBOType); GrAssert(rt->textureFBOID() != rt->renderFBOID()); GR_GLEXT(fExts, BindFramebuffer(GR_READ_FRAMEBUFFER, rt->renderFBOID())); GR_GLEXT(fExts, BindFramebuffer(GR_DRAW_FRAMEBUFFER, rt->textureFBOID())); #if GR_COLLECT_STATS ++fStats.fRenderTargetChngCnt; #endif // make sure we go through set render target fHWDrawState.fRenderTarget = NULL; GLint left = 0; GLint right = texture->contentWidth(); // we will have rendered to the top of the FBO. GLint top = texture->allocHeight(); GLint bottom = texture->allocHeight() - texture->contentHeight(); if (kApple_MSFBO == fMSFBOType) { GR_GL(Enable(GL_SCISSOR_TEST)); GR_GL(Scissor(left, bottom, right-left, top-bottom)); GR_GLEXT(fExts, ResolveMultisampleFramebuffer()); fHWBounds.fScissorRect.setEmpty(); fHWBounds.fScissorEnabled = true; } else { GR_GLEXT(fExts, BlitFramebuffer(left, bottom, right, top, left, bottom, right, top, GL_COLOR_BUFFER_BIT, GL_NEAREST)); } rt->setDirty(false); } } void GrGpuGL::flushStencil() { // use stencil for clipping if clipping is enabled and the clip // has been written into the stencil. bool stencilClip = fClipState.fClipInStencil && (kClip_StateBit & fCurrDrawState.fFlagBits); bool stencilChange = fWriteMaskChanged || fHWStencilClip != stencilClip || fHWDrawState.fStencilPass != fCurrDrawState.fStencilPass || (kNone_StencilPass != fCurrDrawState.fStencilPass && (StencilPass)kSetClip_StencilPass != fCurrDrawState.fStencilPass && fHWDrawState.fReverseFill != fCurrDrawState.fReverseFill); if (stencilChange) { GLint stencilBitCount; GLint clipStencilMask; GLint pathStencilMask; GR_GL(GetIntegerv(GL_STENCIL_BITS, &stencilBitCount)); GrAssert(stencilBitCount > 0 || kNone_StencilPass == fCurrDrawState.fStencilPass); clipStencilMask = (1 << (stencilBitCount - 1)); pathStencilMask = clipStencilMask - 1; switch (fCurrDrawState.fStencilPass) { case kNone_StencilPass: if (stencilClip) { GR_GL(Enable(GL_STENCIL_TEST)); GR_GL(StencilFunc(GL_EQUAL, clipStencilMask, clipStencilMask)); GR_GL(StencilOp(GL_KEEP, GL_KEEP, GL_KEEP)); } else { GR_GL(Disable(GL_STENCIL_TEST)); } GR_GL(ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); if (!fSingleStencilPassForWinding) { GR_GL(Disable(GL_CULL_FACE)); } break; case kEvenOddStencil_StencilPass: GR_GL(Enable(GL_STENCIL_TEST)); if (stencilClip) { GR_GL(StencilFunc(GL_EQUAL, clipStencilMask, clipStencilMask)); } else { GR_GL(StencilFunc(GL_ALWAYS, 0x0, 0x0)); } GR_GL(StencilMask(pathStencilMask)); GR_GL(ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); GR_GL(StencilOp(GL_KEEP, GL_INVERT, GL_INVERT)); GR_GL(ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); if (!fSingleStencilPassForWinding) { GR_GL(Disable(GL_CULL_FACE)); } break; case kEvenOddColor_StencilPass: { GR_GL(Enable(GL_STENCIL_TEST)); GLint funcRef = 0; GLuint funcMask = pathStencilMask; if (stencilClip) { funcRef |= clipStencilMask; funcMask |= clipStencilMask; } if (!fCurrDrawState.fReverseFill) { funcRef |= pathStencilMask; } glStencilFunc(GL_EQUAL, funcRef, funcMask); glStencilMask(pathStencilMask); GR_GL(StencilOp(GL_ZERO, GL_ZERO, GL_ZERO)); GR_GL(ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); if (!fSingleStencilPassForWinding) { GR_GL(Disable(GL_CULL_FACE)); } } break; case kWindingStencil1_StencilPass: GR_GL(Enable(GL_STENCIL_TEST)); if (fHasStencilWrap) { if (stencilClip) { GR_GL(StencilFunc(GL_EQUAL, clipStencilMask, clipStencilMask)); } else { GR_GL(StencilFunc(GL_ALWAYS, 0x0, 0x0)); } if (fSingleStencilPassForWinding) { GR_GL(StencilOpSeparate(GL_FRONT, GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP)); GR_GL(StencilOpSeparate(GL_BACK, GL_KEEP, GL_DECR_WRAP, GL_DECR_WRAP)); } else { GR_GL(StencilOp(GL_KEEP, GL_INCR_WRAP, GL_INCR_WRAP)); GR_GL(Enable(GL_CULL_FACE)); GR_GL(CullFace(GL_BACK)); } } else { // If we don't have wrap then we use the Func to detect // values that would wrap (0 on decr and mask on incr). We // make the func fail on these values and use the sfail op // to effectively wrap by inverting. // This applies whether we are doing a two-pass (front faces // followed by back faces) or a single pass (separate func/op) // Note that in the case where we are also using stencil to // clip this means we will write into the path bits in clipped // out pixels. We still apply the clip bit in the color pass // stencil func so we don't draw color outside the clip. // We also will clear the stencil bits in clipped pixels by // using zero in the sfail op with write mask set to the // path mask. GR_GL(Enable(GL_STENCIL_TEST)); if (fSingleStencilPassForWinding) { GR_GL(StencilFuncSeparate(GL_FRONT, GL_NOTEQUAL, pathStencilMask, pathStencilMask)); GR_GL(StencilFuncSeparate(GL_BACK, GL_NOTEQUAL, 0x0, pathStencilMask)); GR_GL(StencilOpSeparate(GL_FRONT, GL_INVERT, GL_INCR, GL_INCR)); GR_GL(StencilOpSeparate(GL_BACK, GL_INVERT, GL_DECR, GL_DECR)); } else { GR_GL(StencilFunc(GL_NOTEQUAL, pathStencilMask, pathStencilMask)); GR_GL(StencilOp(GL_INVERT, GL_INCR, GL_INCR)); GR_GL(Enable(GL_CULL_FACE)); GR_GL(CullFace(GL_BACK)); } } GR_GL(StencilMask(pathStencilMask)); GR_GL(ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); break; case kWindingStencil2_StencilPass: GrAssert(!fSingleStencilPassForWinding); GR_GL(Enable(GL_STENCIL_TEST)); if (fHasStencilWrap) { if (stencilClip) { GR_GL(StencilFunc(GL_EQUAL, clipStencilMask, clipStencilMask)); } else { GR_GL(StencilFunc(GL_ALWAYS, 0x0, 0x0)); } GR_GL(StencilOp(GL_DECR_WRAP, GL_DECR_WRAP, GL_DECR_WRAP)); } else { GR_GL(StencilFunc(GL_NOTEQUAL, 0x0, pathStencilMask)); GR_GL(StencilOp(GL_INVERT, GL_DECR, GL_DECR)); } GR_GL(StencilMask(pathStencilMask)); GR_GL(Enable(GL_CULL_FACE)); GR_GL(CullFace(GL_FRONT)); GR_GL(ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); break; case kWindingColor_StencilPass: { GR_GL(Enable(GL_STENCIL_TEST)); GLint funcRef = 0; GLuint funcMask = pathStencilMask; GLenum funcFunc; if (stencilClip) { funcRef |= clipStencilMask; funcMask |= clipStencilMask; } if (fCurrDrawState.fReverseFill) { funcFunc = GL_EQUAL; } else { funcFunc = GL_LESS; } GR_GL(StencilFunc(funcFunc, funcRef, funcMask)); GR_GL(StencilMask(pathStencilMask)); // must zero in sfail because winding w/o wrap will write // path stencil bits in clipped out pixels GR_GL(StencilOp(GL_ZERO, GL_ZERO, GL_ZERO)); GR_GL(ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); if (!fSingleStencilPassForWinding) { GR_GL(Disable(GL_CULL_FACE)); } } break; case kSetClip_StencilPass: GR_GL(Enable(GL_STENCIL_TEST)); GR_GL(StencilFunc(GL_ALWAYS, clipStencilMask, clipStencilMask)); GR_GL(StencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE)); GR_GL(StencilMask(clipStencilMask)); GR_GL(ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE)); if (!fSingleStencilPassForWinding) { GR_GL(Disable(GL_CULL_FACE)); } break; default: GrAssert(!"Unexpected stencil pass."); break; } fHWDrawState.fStencilPass = fCurrDrawState.fStencilPass; fHWDrawState.fReverseFill = fCurrDrawState.fReverseFill; fWriteMaskChanged = false; fHWStencilClip = stencilClip; } } void GrGpuGL::flushGLStateCommon(PrimitiveType type) { bool usingTexture = VertexHasTexCoords(fGeometrySrc.fVertexLayout); // bind texture and set sampler state if (usingTexture) { GrGLTexture* nextTexture = (GrGLTexture*)fCurrDrawState.fTexture; if (NULL != nextTexture) { // if we created a rt/tex and rendered to it without using a texture // and now we're texuring from the rt it will still be the last bound // texture, but it needs resolving. So keep this out of the last // != next check. resolveTextureRenderTarget(nextTexture); if (fHWDrawState.fTexture != nextTexture) { GR_GL(BindTexture(GL_TEXTURE_2D, nextTexture->textureID())); #if GR_COLLECT_STATS ++fStats.fTextureChngCnt; #endif //GrPrintf("---- bindtexture %d\n", nextTexture->textureID()); fHWDrawState.fTexture = nextTexture; } const GrSamplerState& lastSampler = nextTexture->samplerState(); if (lastSampler.isFilter() != fCurrDrawState.fSamplerState.isFilter()) { GLenum filter = fCurrDrawState.fSamplerState.isFilter() ? GL_LINEAR : GL_NEAREST; GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)); GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)); } if (lastSampler.getWrapX() != fCurrDrawState.fSamplerState.getWrapX()) { GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GrGLTexture::gWrapMode2GLWrap[ fCurrDrawState.fSamplerState.getWrapX()])); } if (lastSampler.getWrapY() != fCurrDrawState.fSamplerState.getWrapY()) { GR_GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GrGLTexture::gWrapMode2GLWrap[ fCurrDrawState.fSamplerState.getWrapY()])); } nextTexture->setSamplerState(fCurrDrawState.fSamplerState); } else { GrAssert(!"Rendering with texture vert flag set but no texture"); if (NULL != fHWDrawState.fTexture) { GR_GL(BindTexture(GL_TEXTURE_2D, 0)); // GrPrintf("---- bindtexture 0\n"); #if GR_COLLECT_STATS ++fStats.fTextureChngCnt; #endif fHWDrawState.fTexture = NULL; } } } flushRenderTarget(); if ((fCurrDrawState.fFlagBits & kDither_StateBit) != (fHWDrawState.fFlagBits & kDither_StateBit)) { if (fCurrDrawState.fFlagBits & kDither_StateBit) { GR_GL(Enable(GL_DITHER)); } else { GR_GL(Disable(GL_DITHER)); } } #if GR_GL_DESKTOP // ES doesn't support toggling GL_MULTISAMPLE and doesn't have // smooth lines. if (fRenderTargetChanged || (fCurrDrawState.fFlagBits & kAntialias_StateBit) != (fHWDrawState.fFlagBits & kAntialias_StateBit)) { GLint msaa = 0; // only perform query if we know MSAA is supported. // calling on non-MSAA target caused a crash in one environment, // though I don't think it should. if (!fAASamples[kHigh_AALevel]) { GR_GL(GetIntegerv(GL_SAMPLE_BUFFERS, &msaa)); } if (fCurrDrawState.fFlagBits & kAntialias_StateBit) { if (msaa) { GR_GL(Enable(GL_MULTISAMPLE)); } else { GR_GL(Enable(GL_LINE_SMOOTH)); } } else { if (msaa) { GR_GL(Disable(GL_MULTISAMPLE)); } GR_GL(Disable(GL_LINE_SMOOTH)); } } #endif bool blendOff = canDisableBlend(); if (fHWBlendDisabled != blendOff) { if (blendOff) { GR_GL(Disable(GL_BLEND)); } else { GR_GL(Enable(GL_BLEND)); } fHWBlendDisabled = blendOff; } if (!blendOff) { if (fHWDrawState.fSrcBlend != fCurrDrawState.fSrcBlend || fHWDrawState.fDstBlend != fCurrDrawState.fDstBlend) { GR_GL(BlendFunc(gXfermodeCoeff2Blend[fCurrDrawState.fSrcBlend], gXfermodeCoeff2Blend[fCurrDrawState.fDstBlend])); fHWDrawState.fSrcBlend = fCurrDrawState.fSrcBlend; fHWDrawState.fDstBlend = fCurrDrawState.fDstBlend; } } // check for circular rendering GrAssert(!usingTexture || NULL == fCurrDrawState.fRenderTarget || NULL == fCurrDrawState.fTexture || fCurrDrawState.fTexture->asRenderTarget() != fCurrDrawState.fRenderTarget); flushStencil(); fHWDrawState.fFlagBits = fCurrDrawState.fFlagBits; } void GrGpuGL::notifyVertexBufferBind(const GrGLVertexBuffer* buffer) { fHWGeometryState.fVertexBuffer = buffer; } void GrGpuGL::notifyVertexBufferDelete(const GrGLVertexBuffer* buffer) { GrAssert(!(kBuffer_GeometrySrcType == fGeometrySrc.fVertexSrc && buffer == fGeometrySrc.fVertexBuffer)); if (fHWGeometryState.fVertexBuffer == buffer) { // deleting bound buffer does implied bind to 0 fHWGeometryState.fVertexBuffer = NULL; } } void GrGpuGL::notifyIndexBufferBind(const GrGLIndexBuffer* buffer) { fGeometrySrc.fIndexBuffer = buffer; } void GrGpuGL::notifyIndexBufferDelete(const GrGLIndexBuffer* buffer) { GrAssert(!(kBuffer_GeometrySrcType == fGeometrySrc.fIndexSrc && buffer == fGeometrySrc.fIndexBuffer)); if (fHWGeometryState.fIndexBuffer == buffer) { // deleting bound buffer does implied bind to 0 fHWGeometryState.fIndexBuffer = NULL; } } void GrGpuGL::notifyTextureBind(GrGLTexture* texture) { fHWDrawState.fTexture = texture; #if GR_COLLECT_STATS ++fStats.fTextureChngCnt; #endif } void GrGpuGL::notifyRenderTargetDelete(GrRenderTarget* renderTarget) { GrAssert(NULL != renderTarget); // if the bound FBO is destroyed we can't rely on the implicit bind to 0 // a) we want the default RT which may not be FBO 0 // b) we set more state than just FBO based on the RT // So trash the HW state to force an RT flush next time if (fCurrDrawState.fRenderTarget == renderTarget) { fCurrDrawState.fRenderTarget = (GrRenderTarget*)&fDefaultRenderTarget; } if (fHWDrawState.fRenderTarget == renderTarget) { fHWDrawState.fRenderTarget = NULL; } if (fClipState.fStencilClipTarget == renderTarget) { fClipState.fStencilClipTarget = NULL; } } void GrGpuGL::notifyTextureDelete(GrGLTexture* texture) { if (fCurrDrawState.fTexture == texture) { fCurrDrawState.fTexture = NULL; } if (fHWDrawState.fTexture == texture) { // deleting bound texture does implied bind to 0 fHWDrawState.fTexture = NULL; } } void GrGpuGL::notifyTextureRemoveRenderTarget(GrGLTexture* texture) { GrAssert(NULL != texture->asRenderTarget()); // if there is a pending resolve, perform it. resolveTextureRenderTarget(texture); } bool GrGpuGL::canBeTexture(GrTexture::PixelConfig config, GLenum* internalFormat, GLenum* format, GLenum* type) { switch (config) { case GrTexture::kRGBA_8888_PixelConfig: case GrTexture::kRGBX_8888_PixelConfig: // todo: can we tell it our X? *format = SK_GL_32BPP_COLOR_FORMAT; *internalFormat = GL_RGBA; *type = GL_UNSIGNED_BYTE; break; case GrTexture::kRGB_565_PixelConfig: *format = GL_RGB; *internalFormat = GL_RGB; *type = GL_UNSIGNED_SHORT_5_6_5; break; case GrTexture::kRGBA_4444_PixelConfig: *format = GL_RGBA; *internalFormat = GL_RGBA; *type = GL_UNSIGNED_SHORT_4_4_4_4; break; case GrTexture::kIndex_8_PixelConfig: if (this->supports8BitPalette()) { *format = GR_PALETTE8_RGBA8; *internalFormat = GR_PALETTE8_RGBA8; *type = GL_UNSIGNED_BYTE; // unused I think } else { return false; } break; case GrTexture::kAlpha_8_PixelConfig: *format = GL_ALPHA; *internalFormat = GL_ALPHA; *type = GL_UNSIGNED_BYTE; break; default: return false; } return true; } /* On ES the internalFormat and format must match for TexImage and we use GL_RGB, GL_RGBA for color formats. We also generally like having the driver decide the internalFormat. However, on ES internalFormat for RenderBufferStorage* has to be a specific format (not a base format like GL_RGBA). */ bool GrGpuGL::fboInternalFormat(GrTexture::PixelConfig config, GLenum* format) { switch (config) { case GrTexture::kRGBA_8888_PixelConfig: case GrTexture::kRGBX_8888_PixelConfig: if (fRGBA8Renderbuffer) { *format = GR_RGBA8; return true; } else { return false; } #if GR_GL_ES // ES2 supports 565. ES1 supports it with FBO extension // desktop GL has no such internal format case GrTexture::kRGB_565_PixelConfig: *format = GR_RGB565; return true; #endif case GrTexture::kRGBA_4444_PixelConfig: *format = GL_RGBA4; return true; default: return false; } } /////////////////////////////////////////////////////////////////////////////// void GrGLCheckErr(const char* location, const char* call) { uint32_t err = glGetError(); if (GL_NO_ERROR != err) { GrPrintf("---- glGetError %x", err); if (NULL != location) { GrPrintf(" at\n\t%s", location); } if (NULL != call) { GrPrintf("\n\t\t%s", call); } GrPrintf("\n"); } } /////////////////////////////////////////////////////////////////////////////// typedef void (*glProc)(void); void get_gl_proc(const char procName[], glProc *address) { #if GR_WIN32_BUILD *address = wglGetProcAddress(procName); GrAssert(NULL != *address); #elif GR_MAC_BUILD || GR_IOS_BUILD GrAssert(!"Extensions don't need to be initialized!"); #elif GR_ANDROID_BUILD *address = eglGetProcAddress(procName); GrAssert(NULL != *address); #elif GR_LINUX_BUILD GR_STATIC_ASSERT(!"Add environment-dependent implementation here"); //*address = glXGetProcAddressARB(procName); //*address = eglGetProcAddress(procName); #elif GR_QNX_BUILD *address = eglGetProcAddress(procName); GrAssert(NULL != *address); #else // hopefully we're on a system with EGL *address = eglGetProcAddress(procName); GrAssert(NULL != *address); #endif } #define GET_PROC(EXT_STRUCT, PROC_NAME, EXT_TAG) \ get_gl_proc("gl" #PROC_NAME #EXT_TAG, (glProc*)&EXT_STRUCT-> PROC_NAME); extern void GrGLInitExtensions(GrGLExts* exts) { exts->GenFramebuffers = NULL; exts->BindFramebuffer = NULL; exts->FramebufferTexture2D = NULL; exts->CheckFramebufferStatus = NULL; exts->DeleteFramebuffers = NULL; exts->RenderbufferStorage = NULL; exts->GenRenderbuffers = NULL; exts->DeleteRenderbuffers = NULL; exts->FramebufferRenderbuffer = NULL; exts->BindRenderbuffer = NULL; exts->RenderbufferStorageMultisample = NULL; exts->BlitFramebuffer = NULL; exts->ResolveMultisampleFramebuffer = NULL; exts->FramebufferTexture2DMultisample = NULL; exts->MapBuffer = NULL; exts->UnmapBuffer = NULL; #if GR_MAC_BUILD exts->GenFramebuffers = glGenFramebuffers; exts->BindFramebuffer = glBindFramebuffer; exts->FramebufferTexture2D = glFramebufferTexture2D; exts->CheckFramebufferStatus = glCheckFramebufferStatus; exts->DeleteFramebuffers = glDeleteFramebuffers; exts->RenderbufferStorage = glRenderbufferStorage; exts->GenRenderbuffers = glGenRenderbuffers; exts->DeleteRenderbuffers = glDeleteRenderbuffers; exts->FramebufferRenderbuffer = glFramebufferRenderbuffer; exts->BindRenderbuffer = glBindRenderbuffer; exts->RenderbufferStorageMultisample = glRenderbufferStorageMultisample; exts->BlitFramebuffer = glBlitFramebuffer; exts->MapBuffer = glMapBuffer; exts->UnmapBuffer = glUnmapBuffer; #elif GR_IOS_BUILD exts->GenFramebuffers = glGenFramebuffers; exts->BindFramebuffer = glBindFramebuffer; exts->FramebufferTexture2D = glFramebufferTexture2D; exts->CheckFramebufferStatus = glCheckFramebufferStatus; exts->DeleteFramebuffers = glDeleteFramebuffers; exts->RenderbufferStorage = glRenderbufferStorage; exts->GenRenderbuffers = glGenRenderbuffers; exts->DeleteRenderbuffers = glDeleteRenderbuffers; exts->FramebufferRenderbuffer = glFramebufferRenderbuffer; exts->BindRenderbuffer = glBindRenderbuffer; exts->RenderbufferStorageMultisample = glRenderbufferStorageMultisampleAPPLE; exts->ResolveMultisampleFramebuffer = glResolveMultisampleFramebufferAPPLE; exts->MapBuffer = glMapBufferOES; exts->UnmapBuffer = glUnmapBufferOES; #else GLint major, minor; gl_version(&major, &minor); #if GR_GL_DESKTOP if (major >= 3) {// FBO, FBOMS, and FBOBLIT part of 3.0 exts->GenFramebuffers = glGenFramebuffers; exts->BindFramebuffer = glBindFramebuffer; exts->FramebufferTexture2D = glFramebufferTexture2D; exts->CheckFramebufferStatus = glCheckFramebufferStatus; exts->DeleteFramebuffers = glDeleteFramebuffers; exts->RenderbufferStorage = glRenderbufferStorage; exts->GenRenderbuffers = glGenRenderbuffers; exts->DeleteRenderbuffers = glDeleteRenderbuffers; exts->FramebufferRenderbuffer = glFramebufferRenderbuffer; exts->BindRenderbuffer = glBindRenderbuffer; exts->RenderbufferStorageMultisample = glRenderbufferStorageMultisample; exts->BlitFramebuffer = glBlitFramebuffer; } else if (has_gl_extension("GL_ARB_framebuffer_object")) { GET_PROC(exts, GenFramebuffers, ARB); GET_PROC(exts, BindFramebuffer, ARB); GET_PROC(exts, FramebufferTexture2D, ARB); GET_PROC(exts, CheckFramebufferStatus, ARB); GET_PROC(exts, DeleteFramebuffers, ARB); GET_PROC(exts, RenderbufferStorage, ARB); GET_PROC(exts, GenRenderbuffers, ARB); GET_PROC(exts, DeleteRenderbuffers, ARB); GET_PROC(exts, FramebufferRenderbuffer, ARB); GET_PROC(exts, BindRenderbuffer, ARB); GET_PROC(exts, RenderbufferStorageMultisample, ARB); GET_PROC(exts, BlitFramebuffer, ARB); } else { // we require some form of FBO GrAssert(has_gl_extension("GL_EXT_framebuffer_object")); GET_PROC(exts, GenFramebuffers, EXT); GET_PROC(exts, BindFramebuffer, EXT); GET_PROC(exts, FramebufferTexture2D, EXT); GET_PROC(exts, CheckFramebufferStatus, EXT); GET_PROC(exts, DeleteFramebuffers, EXT); GET_PROC(exts, RenderbufferStorage, EXT); GET_PROC(exts, GenRenderbuffers, EXT); GET_PROC(exts, DeleteRenderbuffers, EXT); GET_PROC(exts, FramebufferRenderbuffer, EXT); GET_PROC(exts, BindRenderbuffer, EXT); if (has_gl_extension("GL_EXT_framebuffer_multisample")) { GET_PROC(exts, RenderbufferStorageMultisample, EXT); } if (has_gl_extension("GL_EXT_framebuffer_blit")) { GET_PROC(exts, BlitFramebuffer, EXT); } } // we assume we have at least GL 1.5 or higher (VBOs introduced in 1.5) exts->MapBuffer = glMapBuffer; exts->UnmapBuffer = glUnmapBuffer; #else // !GR_GL_DESKTOP if (major >= 2) {// ES 2.0 supports FBO exts->GenFramebuffers = glGenFramebuffers; exts->BindFramebuffer = glBindFramebuffer; exts->FramebufferTexture2D = glFramebufferTexture2D; exts->CheckFramebufferStatus = glCheckFramebufferStatus; exts->DeleteFramebuffers = glDeleteFramebuffers; exts->RenderbufferStorage = glRenderbufferStorage; exts->GenRenderbuffers = glGenRenderbuffers; exts->DeleteRenderbuffers = glDeleteRenderbuffers; exts->FramebufferRenderbuffer = glFramebufferRenderbuffer; exts->BindRenderbuffer = glBindRenderbuffer; } else { // we require some form of FBO GrAssert(has_gl_extension("GL_OES_framebuffer_object")); GET_PROC(exts, GenFramebuffers, OES); GET_PROC(exts, BindFramebuffer, OES); GET_PROC(exts, FramebufferTexture2D, OES); GET_PROC(exts, CheckFramebufferStatus, OES); GET_PROC(exts, DeleteFramebuffers, OES); GET_PROC(exts, RenderbufferStorage, OES); GET_PROC(exts, GenRenderbuffers, OES); GET_PROC(exts, DeleteRenderbuffers, OES); GET_PROC(exts, FramebufferRenderbuffer, OES); GET_PROC(exts, BindRenderbuffer, OES); } if (has_gl_extension("GL_APPLE_framebuffer_multisample")) { GET_PROC(exts, ResolveMultisampleFramebuffer, APPLE); } if (has_gl_extension("GL_IMG_multisampled_render_to_texture")) { GET_PROC(exts, FramebufferTexture2DMultisample, IMG); } if (has_gl_extension("GL_OES_mapbuffer")) { GET_PROC(exts, MapBuffer, OES); GET_PROC(exts, UnmapBuffer, OES); } #endif // !GR_GL_DESKTOP #endif // BUILD } bool gPrintGL = true;