diff options
author | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-05-02 15:08:18 +0000 |
---|---|---|
committer | commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2014-05-02 15:08:18 +0000 |
commit | f9deb8a15de8061863b38839850207f63a8e47e4 (patch) | |
tree | 289a1643ad96fbc701fffdeec1566f35e4400c57 /src/gpu | |
parent | daf48e02ad2edbc5c96cbee0acecdf97d4684b92 (diff) |
Add support for glMapBufferRange. Use glMapBufferRange and glMapBufferSubData.
BUG=skia:2402
R=robertphillips@google.com
Author: bsalomon@google.com
Review URL: https://codereview.chromium.org/243413002
git-svn-id: http://skia.googlecode.com/svn/trunk@14533 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/gpu')
-rw-r--r-- | src/gpu/GrBufferAllocPool.cpp | 4 | ||||
-rw-r--r-- | src/gpu/GrDrawTarget.cpp | 29 | ||||
-rw-r--r-- | src/gpu/GrDrawTargetCaps.h | 18 | ||||
-rw-r--r-- | src/gpu/gl/GrGLAssembleInterface.cpp | 5 | ||||
-rw-r--r-- | src/gpu/gl/GrGLBufferImpl.cpp | 97 | ||||
-rw-r--r-- | src/gpu/gl/GrGLBufferImpl.h | 2 | ||||
-rw-r--r-- | src/gpu/gl/GrGLCaps.cpp | 42 | ||||
-rw-r--r-- | src/gpu/gl/GrGLCaps.h | 23 | ||||
-rw-r--r-- | src/gpu/gl/GrGLCreateNullInterface.cpp | 34 | ||||
-rw-r--r-- | src/gpu/gl/GrGLDefines.h | 8 | ||||
-rw-r--r-- | src/gpu/gl/GrGLInterface.cpp | 16 | ||||
-rw-r--r-- | src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp | 14 | ||||
-rw-r--r-- | src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp | 8 | ||||
-rw-r--r-- | src/gpu/gl/debug/GrBufferObj.h | 12 | ||||
-rw-r--r-- | src/gpu/gl/debug/GrGLCreateDebugInterface.cpp | 82 | ||||
-rw-r--r-- | src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp | 5 |
16 files changed, 336 insertions, 63 deletions
diff --git a/src/gpu/GrBufferAllocPool.cpp b/src/gpu/GrBufferAllocPool.cpp index 2dbf3eb283..30d02d953e 100644 --- a/src/gpu/GrBufferAllocPool.cpp +++ b/src/gpu/GrBufferAllocPool.cpp @@ -303,7 +303,7 @@ bool GrBufferAllocPool::createBlock(size_t requestSize) { // threshold (since we don't expect it is likely that we will see more vertex data) // b) If the hint is not set we lock if the buffer size is greater than the threshold. bool attemptLock = block.fBuffer->isCPUBacked(); - if (!attemptLock && fGpu->caps()->bufferLockSupport()) { + if (!attemptLock && GrDrawTargetCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags()) { if (fFrequentResetHint) { attemptLock = requestSize > GR_GEOM_BUFFER_LOCK_THRESHOLD; } else { @@ -351,7 +351,7 @@ void GrBufferAllocPool::flushCpuData(GrGeometryBuffer* buffer, SkASSERT(flushSize <= buffer->sizeInBytes()); VALIDATE(true); - if (fGpu->caps()->bufferLockSupport() && + if (GrDrawTargetCaps::kNone_MapFlags != fGpu->caps()->mapBufferFlags() && flushSize > GR_GEOM_BUFFER_LOCK_THRESHOLD) { void* data = buffer->lock(); if (NULL != data) { diff --git a/src/gpu/GrDrawTarget.cpp b/src/gpu/GrDrawTarget.cpp index 5512f174d7..8b84edd79a 100644 --- a/src/gpu/GrDrawTarget.cpp +++ b/src/gpu/GrDrawTarget.cpp @@ -1016,13 +1016,14 @@ void GrDrawTargetCaps::reset() { fShaderDerivativeSupport = false; fGeometryShaderSupport = false; fDualSourceBlendingSupport = false; - fBufferLockSupport = false; fPathRenderingSupport = false; fDstReadInShaderSupport = false; fDiscardRenderTargetSupport = false; fReuseScratchTextures = true; fGpuTracingSupport = false; + fMapBufferFlags = kNone_MapFlags; + fMaxRenderTargetSize = 0; fMaxTextureSize = 0; fMaxSampleCount = 0; @@ -1040,13 +1041,14 @@ GrDrawTargetCaps& GrDrawTargetCaps::operator=(const GrDrawTargetCaps& other) { fShaderDerivativeSupport = other.fShaderDerivativeSupport; fGeometryShaderSupport = other.fGeometryShaderSupport; fDualSourceBlendingSupport = other.fDualSourceBlendingSupport; - fBufferLockSupport = other.fBufferLockSupport; fPathRenderingSupport = other.fPathRenderingSupport; fDstReadInShaderSupport = other.fDstReadInShaderSupport; fDiscardRenderTargetSupport = other.fDiscardRenderTargetSupport; fReuseScratchTextures = other.fReuseScratchTextures; fGpuTracingSupport = other.fGpuTracingSupport; + fMapBufferFlags = other.fMapBufferFlags; + fMaxRenderTargetSize = other.fMaxRenderTargetSize; fMaxTextureSize = other.fMaxTextureSize; fMaxSampleCount = other.fMaxSampleCount; @@ -1056,6 +1058,26 @@ GrDrawTargetCaps& GrDrawTargetCaps::operator=(const GrDrawTargetCaps& other) { return *this; } +static SkString map_flags_to_string(uint32_t flags) { + SkString str; + if (GrDrawTargetCaps::kNone_MapFlags == flags) { + str = "none"; + } else { + SkASSERT(GrDrawTargetCaps::kCanMap_MapFlag & flags); + SkDEBUGCODE(flags &= ~GrDrawTargetCaps::kCanMap_MapFlag); + str = "can_map"; + + if (GrDrawTargetCaps::kSubset_MapFlag & flags) { + str.append(" partial"); + } else { + str.append(" full"); + } + SkDEBUGCODE(flags &= ~GrDrawTargetCaps::kSubset_MapFlag); + } + SkASSERT(0 == flags); // Make sure we handled all the flags. + return str; +} + SkString GrDrawTargetCaps::dump() const { SkString r; static const char* gNY[] = {"NO", "YES"}; @@ -1068,7 +1090,6 @@ SkString GrDrawTargetCaps::dump() const { r.appendf("Shader Derivative Support : %s\n", gNY[fShaderDerivativeSupport]); r.appendf("Geometry Shader Support : %s\n", gNY[fGeometryShaderSupport]); r.appendf("Dual Source Blending Support : %s\n", gNY[fDualSourceBlendingSupport]); - r.appendf("Buffer Lock Support : %s\n", gNY[fBufferLockSupport]); r.appendf("Path Rendering Support : %s\n", gNY[fPathRenderingSupport]); r.appendf("Dst Read In Shader Support : %s\n", gNY[fDstReadInShaderSupport]); r.appendf("Discard Render Target Support: %s\n", gNY[fDiscardRenderTargetSupport]); @@ -1078,6 +1099,8 @@ SkString GrDrawTargetCaps::dump() const { r.appendf("Max Render Target Size : %d\n", fMaxRenderTargetSize); r.appendf("Max Sample Count : %d\n", fMaxSampleCount); + r.appendf("Map Buffer Support : %s\n", map_flags_to_string(fMapBufferFlags).c_str()); + static const char* kConfigNames[] = { "Unknown", // kUnknown_GrPixelConfig "Alpha8", // kAlpha_8_GrPixelConfig, diff --git a/src/gpu/GrDrawTargetCaps.h b/src/gpu/GrDrawTargetCaps.h index a77bce4430..648b5c36be 100644 --- a/src/gpu/GrDrawTargetCaps.h +++ b/src/gpu/GrDrawTargetCaps.h @@ -37,12 +37,25 @@ public: bool shaderDerivativeSupport() const { return fShaderDerivativeSupport; } bool geometryShaderSupport() const { return fGeometryShaderSupport; } bool dualSourceBlendingSupport() const { return fDualSourceBlendingSupport; } - bool bufferLockSupport() const { return fBufferLockSupport; } bool pathRenderingSupport() const { return fPathRenderingSupport; } bool dstReadInShaderSupport() const { return fDstReadInShaderSupport; } bool discardRenderTargetSupport() const { return fDiscardRenderTargetSupport; } bool gpuTracingSupport() const { return fGpuTracingSupport; } + /** + * Indicates whether GPU->CPU memory mapping for GPU resources such as vertex buffers and + * textures allows partial mappings or full mappings. + */ + enum MapFlags { + kNone_MapFlags = 0x0, //<! Cannot map the resource. + + kCanMap_MapFlag = 0x1, //<! The resource can be mapped. Must be set for any of + // the other flags to have meaning.k + kSubset_MapFlag = 0x2, //<! The resource can be partially mapped. + }; + + uint32_t mapBufferFlags() const { return fMapBufferFlags; } + // Scratch textures not being reused means that those scratch textures // that we upload to (i.e., don't have a render target) will not be // recycled in the texture cache. This is to prevent ghosting by drivers @@ -69,13 +82,14 @@ protected: bool fShaderDerivativeSupport : 1; bool fGeometryShaderSupport : 1; bool fDualSourceBlendingSupport : 1; - bool fBufferLockSupport : 1; bool fPathRenderingSupport : 1; bool fDstReadInShaderSupport : 1; bool fDiscardRenderTargetSupport: 1; bool fReuseScratchTextures : 1; bool fGpuTracingSupport : 1; + uint32_t fMapBufferFlags; + int fMaxRenderTargetSize; int fMaxTextureSize; int fMaxSampleCount; diff --git a/src/gpu/gl/GrGLAssembleInterface.cpp b/src/gpu/gl/GrGLAssembleInterface.cpp index aed11e539f..e4337259a2 100644 --- a/src/gpu/gl/GrGLAssembleInterface.cpp +++ b/src/gpu/gl/GrGLAssembleInterface.cpp @@ -173,6 +173,11 @@ const GrGLInterface* GrGLAssembleGLInterface(void* ctx, GrGLGetProc get) { GET_PROC(DeleteVertexArrays); } + if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_map_buffer_range")) { + GET_PROC(MapBufferRange); + GET_PROC(FlushMappedBufferRange); + } + // First look for GL3.0 FBO or GL_ARB_framebuffer_object (same since // GL_ARB_framebuffer_object doesn't use ARB suffix.) if (glVer >= GR_GL_VER(3,0) || extensions.has("GL_ARB_framebuffer_object")) { diff --git a/src/gpu/gl/GrGLBufferImpl.cpp b/src/gpu/gl/GrGLBufferImpl.cpp index 3c75b9fe6a..3dea4f5ac7 100644 --- a/src/gpu/gl/GrGLBufferImpl.cpp +++ b/src/gpu/gl/GrGLBufferImpl.cpp @@ -26,20 +26,22 @@ GrGLBufferImpl::GrGLBufferImpl(GrGpuGL* gpu, const Desc& desc, GrGLenum bufferTy , fLockPtr(NULL) { if (0 == desc.fID) { fCPUData = sk_malloc_flags(desc.fSizeInBytes, SK_MALLOC_THROW); + fGLSizeInBytes = 0; } else { fCPUData = NULL; + // We assume that the GL buffer was created at the desc's size initially. + fGLSizeInBytes = fDesc.fSizeInBytes; } VALIDATE(); } void GrGLBufferImpl::release(GrGpuGL* gpu) { + VALIDATE(); // make sure we've not been abandoned or already released if (NULL != fCPUData) { - VALIDATE(); sk_free(fCPUData); fCPUData = NULL; } else if (fDesc.fID && !fDesc.fIsWrapped) { - VALIDATE(); GL_CALL(gpu, DeleteBuffers(1, &fDesc.fID)); if (GR_GL_ARRAY_BUFFER == fBufferType) { gpu->notifyVertexBufferDelete(fDesc.fID); @@ -48,15 +50,19 @@ void GrGLBufferImpl::release(GrGpuGL* gpu) { gpu->notifyIndexBufferDelete(fDesc.fID); } fDesc.fID = 0; + fGLSizeInBytes = 0; } fLockPtr = NULL; + VALIDATE(); } void GrGLBufferImpl::abandon() { fDesc.fID = 0; + fGLSizeInBytes = 0; fLockPtr = NULL; sk_free(fCPUData); fCPUData = NULL; + VALIDATE(); } void GrGLBufferImpl::bind(GrGpuGL* gpu) const { @@ -67,6 +73,7 @@ void GrGLBufferImpl::bind(GrGpuGL* gpu) const { SkASSERT(GR_GL_ELEMENT_ARRAY_BUFFER == fBufferType); gpu->bindIndexBufferAndDefaultVertexArray(fDesc.fID); } + VALIDATE(); } void* GrGLBufferImpl::lock(GrGpuGL* gpu) { @@ -74,17 +81,55 @@ void* GrGLBufferImpl::lock(GrGpuGL* gpu) { SkASSERT(!this->isLocked()); if (0 == fDesc.fID) { fLockPtr = fCPUData; - } else if (gpu->caps()->bufferLockSupport()) { - this->bind(gpu); - // Let driver know it can discard the old data - GL_CALL(gpu, BufferData(fBufferType, - (GrGLsizeiptr) fDesc.fSizeInBytes, - NULL, - fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW)); - GR_GL_CALL_RET(gpu->glInterface(), - fLockPtr, - MapBuffer(fBufferType, GR_GL_WRITE_ONLY)); + } else { + switch (gpu->glCaps().mapBufferType()) { + case GrGLCaps::kNone_MapBufferType: + VALIDATE(); + return NULL; + case GrGLCaps::kMapBuffer_MapBufferType: + this->bind(gpu); + // Let driver know it can discard the old data + if (GR_GL_USE_BUFFER_DATA_NULL_HINT || fDesc.fSizeInBytes != fGLSizeInBytes) { + fGLSizeInBytes = fDesc.fSizeInBytes; + GL_CALL(gpu, + BufferData(fBufferType, fGLSizeInBytes, NULL, + fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW)); + } + GR_GL_CALL_RET(gpu->glInterface(), fLockPtr, + MapBuffer(fBufferType, GR_GL_WRITE_ONLY)); + break; + case GrGLCaps::kMapBufferRange_MapBufferType: { + this->bind(gpu); + // Make sure the GL buffer size agrees with fDesc before mapping. + if (fDesc.fSizeInBytes != fGLSizeInBytes) { + fGLSizeInBytes = fDesc.fSizeInBytes; + GL_CALL(gpu, + BufferData(fBufferType, fGLSizeInBytes, NULL, + fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW)); + } + static const GrGLbitfield kAccess = GR_GL_MAP_INVALIDATE_BUFFER_BIT | + GR_GL_MAP_WRITE_BIT; + GR_GL_CALL_RET(gpu->glInterface(), + fLockPtr, + MapBufferRange(fBufferType, 0, fGLSizeInBytes, kAccess)); + break; + } + case GrGLCaps::kChromium_MapBufferType: + this->bind(gpu); + // Make sure the GL buffer size agrees with fDesc before mapping. + if (fDesc.fSizeInBytes != fGLSizeInBytes) { + fGLSizeInBytes = fDesc.fSizeInBytes; + GL_CALL(gpu, + BufferData(fBufferType, fGLSizeInBytes, NULL, + fDesc.fDynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW)); + } + GR_GL_CALL_RET(gpu->glInterface(), + fLockPtr, + MapBufferSubData(fBufferType, 0, fGLSizeInBytes, GR_GL_WRITE_ONLY)); + break; + } } + VALIDATE(); return fLockPtr; } @@ -92,9 +137,20 @@ void GrGLBufferImpl::unlock(GrGpuGL* gpu) { VALIDATE(); SkASSERT(this->isLocked()); if (0 != fDesc.fID) { - SkASSERT(gpu->caps()->bufferLockSupport()); - this->bind(gpu); - GL_CALL(gpu, UnmapBuffer(fBufferType)); + switch (gpu->glCaps().mapBufferType()) { + case GrGLCaps::kNone_MapBufferType: + SkDEBUGFAIL("Shouldn't get here."); + return; + case GrGLCaps::kMapBuffer_MapBufferType: // fall through + case GrGLCaps::kMapBufferRange_MapBufferType: + this->bind(gpu); + GL_CALL(gpu, UnmapBuffer(fBufferType)); + break; + case GrGLCaps::kChromium_MapBufferType: + this->bind(gpu); + GR_GL_CALL(gpu->glInterface(), UnmapBufferSubData(fLockPtr)); + break; + } } fLockPtr = NULL; } @@ -127,7 +183,8 @@ bool GrGLBufferImpl::updateData(GrGpuGL* gpu, const void* src, size_t srcSizeInB // draws that reference the old contents. With this hint it can // assign a different allocation for the new contents to avoid // flushing the gpu past draws consuming the old contents. - GL_CALL(gpu, BufferData(fBufferType, (GrGLsizeiptr) fDesc.fSizeInBytes, NULL, usage)); + fGLSizeInBytes = fDesc.fSizeInBytes; + GL_CALL(gpu, BufferData(fBufferType, fGLSizeInBytes, NULL, usage)); GL_CALL(gpu, BufferSubData(fBufferType, 0, (GrGLsizeiptr) srcSizeInBytes, src)); } #else @@ -147,10 +204,12 @@ bool GrGLBufferImpl::updateData(GrGpuGL* gpu, const void* src, size_t srcSizeInB // Chromium's command buffer may turn a glBufferSubData where the size // exactly matches the buffer size into a glBufferData. So we tack 1 // extra byte onto the glBufferData. - GL_CALL(gpu, BufferData(fBufferType, srcSizeInBytes + 1, NULL, usage)); + fGLSizeInBytes = srcSizeInBytes + 1; + GL_CALL(gpu, BufferData(fBufferType, fGLSizeInBytes, NULL, usage)); GL_CALL(gpu, BufferSubData(fBufferType, 0, srcSizeInBytes, src)); } else { - GL_CALL(gpu, BufferData(fBufferType, srcSizeInBytes, src, usage)); + fGLSizeInBytes = srcSizeInBytes; + GL_CALL(gpu, BufferData(fBufferType, fGLSizeInBytes, src, usage)); } #endif return true; @@ -161,5 +220,7 @@ void GrGLBufferImpl::validate() const { // The following assert isn't valid when the buffer has been abandoned: // SkASSERT((0 == fDesc.fID) == (NULL != fCPUData)); SkASSERT(0 != fDesc.fID || !fDesc.fIsWrapped); + SkASSERT(NULL == fCPUData || 0 == fGLSizeInBytes); + SkASSERT(NULL == fLockPtr || fGLSizeInBytes == fDesc.fSizeInBytes); SkASSERT(NULL == fCPUData || NULL == fLockPtr || fCPUData == fLockPtr); } diff --git a/src/gpu/gl/GrGLBufferImpl.h b/src/gpu/gl/GrGLBufferImpl.h index 148ca1b2ee..19d23e0dac 100644 --- a/src/gpu/gl/GrGLBufferImpl.h +++ b/src/gpu/gl/GrGLBufferImpl.h @@ -53,6 +53,8 @@ private: GrGLenum fBufferType; // GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER void* fCPUData; void* fLockPtr; + size_t fGLSizeInBytes; // In certain cases we make the size of the GL buffer object + // smaller or larger than the size in fDesc. typedef SkNoncopyable INHERITED; }; diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp index 890b816752..f577e9d740 100644 --- a/src/gpu/gl/GrGLCaps.cpp +++ b/src/gpu/gl/GrGLCaps.cpp @@ -24,6 +24,7 @@ void GrGLCaps::reset() { fMSFBOType = kNone_MSFBOType; fFBFetchType = kNone_FBFetchType; fInvalidateFBType = kNone_InvalidateFBType; + fMapBufferType = kNone_MapBufferType; fMaxFragmentUniformVectors = 0; fMaxVertexAttributes = 0; fMaxFragmentTextureUnits = 0; @@ -47,7 +48,6 @@ void GrGLCaps::reset() { fIsCoreProfile = false; fFullClearIsFree = false; fDropsTileOnZeroDivide = false; - fMapSubSupport = false; } GrGLCaps::GrGLCaps(const GrGLCaps& caps) : GrDrawTargetCaps() { @@ -66,6 +66,7 @@ GrGLCaps& GrGLCaps::operator= (const GrGLCaps& caps) { fMSFBOType = caps.fMSFBOType; fFBFetchType = caps.fFBFetchType; fInvalidateFBType = caps.fInvalidateFBType; + fMapBufferType = caps.fMapBufferType; fRGBA8RenderbufferSupport = caps.fRGBA8RenderbufferSupport; fBGRAFormatSupport = caps.fBGRAFormatSupport; fBGRAIsInternalFormat = caps.fBGRAIsInternalFormat; @@ -85,7 +86,6 @@ GrGLCaps& GrGLCaps::operator= (const GrGLCaps& caps) { fIsCoreProfile = caps.fIsCoreProfile; fFullClearIsFree = caps.fFullClearIsFree; fDropsTileOnZeroDivide = caps.fDropsTileOnZeroDivide; - fMapSubSupport = caps.fMapSubSupport; return *this; } @@ -290,12 +290,27 @@ bool GrGLCaps::init(const GrGLContextInfo& ctxInfo, const GrGLInterface* gli) { } if (kGL_GrGLStandard == standard) { - fBufferLockSupport = true; // we require VBO support and the desktop VBO extension includes - // glMapBuffer. - fMapSubSupport = false; + fMapBufferFlags = kCanMap_MapFlag; // we require VBO support and the desktop VBO + // extension includes glMapBuffer. + if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_ARB_map_buffer_range")) { + fMapBufferFlags |= kSubset_MapFlag; + fMapBufferType = kMapBufferRange_MapBufferType; + } else { + fMapBufferType = kMapBuffer_MapBufferType; + } } else { - fBufferLockSupport = ctxInfo.hasExtension("GL_OES_mapbuffer"); - fMapSubSupport = ctxInfo.hasExtension("GL_CHROMIUM_map_sub"); + // Unextended GLES2 doesn't have any buffer mapping. + fMapBufferFlags = kNone_MapBufferType; + if (ctxInfo.hasExtension("GL_CHROMIUM_map_sub")) { + fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag; + fMapBufferType = kChromium_MapBufferType; + } else if (version >= GR_GL_VER(3, 0) || ctxInfo.hasExtension("GL_EXT_map_buffer_range")) { + fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag; + fMapBufferType = kMapBufferRange_MapBufferType; + } else if (ctxInfo.hasExtension("GL_OES_mapbuffer")) { + fMapBufferFlags = kCanMap_MapFlag; + fMapBufferType = kMapBuffer_MapBufferType; + } } if (kGL_GrGLStandard == standard) { @@ -655,10 +670,23 @@ SkString GrGLCaps::dump() const { GR_STATIC_ASSERT(2 == kInvalidate_InvalidateFBType); GR_STATIC_ASSERT(SK_ARRAY_COUNT(kInvalidateFBTypeStr) == kLast_InvalidateFBType + 1); + static const char* kMapBufferTypeStr[] = { + "None", + "MapBuffer", + "MapBufferRange", + "Chromium", + }; + GR_STATIC_ASSERT(0 == kNone_MapBufferType); + GR_STATIC_ASSERT(1 == kMapBuffer_MapBufferType); + GR_STATIC_ASSERT(2 == kMapBufferRange_MapBufferType); + GR_STATIC_ASSERT(3 == kChromium_MapBufferType); + GR_STATIC_ASSERT(SK_ARRAY_COUNT(kMapBufferTypeStr) == kLast_MapBufferType + 1); + r.appendf("Core Profile: %s\n", (fIsCoreProfile ? "YES" : "NO")); r.appendf("MSAA Type: %s\n", kMSFBOExtStr[fMSFBOType]); r.appendf("FB Fetch Type: %s\n", kFBFetchTypeStr[fFBFetchType]); r.appendf("Invalidate FB Type: %s\n", kInvalidateFBTypeStr[fInvalidateFBType]); + r.appendf("Map Buffer Type: %s\n", kMapBufferTypeStr[fMapBufferType]); r.appendf("Max FS Uniform Vectors: %d\n", fMaxFragmentUniformVectors); r.appendf("Max FS Texture Units: %d\n", fMaxFragmentTextureUnits); if (!fIsCoreProfile) { diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h index 48925d48b4..ea0f41245d 100644 --- a/src/gpu/gl/GrGLCaps.h +++ b/src/gpu/gl/GrGLCaps.h @@ -86,6 +86,15 @@ public: kLast_InvalidateFBType = kInvalidate_InvalidateFBType }; + enum MapBufferType { + kNone_MapBufferType, + kMapBuffer_MapBufferType, // glMapBuffer() + kMapBufferRange_MapBufferType, // glMapBufferRange() + kChromium_MapBufferType, // GL_CHROMIUM_map_sub + + kLast_MapBufferType = kChromium_MapBufferType, + }; + /** * Creates a GrGLCaps that advertises no support for any extensions, * formats, etc. Call init to initialize from a GrGLContextInfo. @@ -169,10 +178,8 @@ public: InvalidateFBType invalidateFBType() const { return fInvalidateFBType; } - /** - * Returs a string containeng the caps info. - */ - virtual SkString dump() const SK_OVERRIDE; + /// What type of buffer mapping is supported? + MapBufferType mapBufferType() const { return fMapBufferType; } /** * Gets an array of legal stencil formats. These formats are not guaranteed @@ -258,8 +265,10 @@ public: bool dropsTileOnZeroDivide() const { return fDropsTileOnZeroDivide; } - /// Is GL_CHROMIUM_map_sub supported? - bool mapSubSupport() const { return fMapSubSupport; } + /** + * Returns a string containing the caps info. + */ + virtual SkString dump() const SK_OVERRIDE; private: /** @@ -322,6 +331,7 @@ private: MSFBOType fMSFBOType; FBFetchType fFBFetchType; InvalidateFBType fInvalidateFBType; + MapBufferType fMapBufferType; bool fRGBA8RenderbufferSupport : 1; bool fBGRAFormatSupport : 1; @@ -342,7 +352,6 @@ private: bool fIsCoreProfile : 1; bool fFullClearIsFree : 1; bool fDropsTileOnZeroDivide : 1; - bool fMapSubSupport : 1; typedef GrDrawTargetCaps INHERITED; }; diff --git a/src/gpu/gl/GrGLCreateNullInterface.cpp b/src/gpu/gl/GrGLCreateNullInterface.cpp index 2ef7659db7..6cfa8c29d0 100644 --- a/src/gpu/gl/GrGLCreateNullInterface.cpp +++ b/src/gpu/gl/GrGLCreateNullInterface.cpp @@ -186,8 +186,29 @@ GrGLvoid GR_GL_FUNCTION_TYPE nullGLDeleteBuffers(GrGLsizei n, const GrGLuint* id } } -GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) { +GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBufferRange(GrGLenum target, GrGLintptr offset, + GrGLsizeiptr length, GrGLbitfield access) { + GrGLuint id = 0; + switch (target) { + case GR_GL_ARRAY_BUFFER: + id = gCurrArrayBuffer; + break; + case GR_GL_ELEMENT_ARRAY_BUFFER: + id = gCurrElementArrayBuffer; + break; + } + if (id > 0) { + // We just ignore the offset and length here. + GrBufferObj* buffer = look_up(id); + SkASSERT(!buffer->mapped()); + buffer->setMapped(true); + return buffer->dataPtr(); + } + return NULL; +} + +GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) { GrGLuint id = 0; switch (target) { case GR_GL_ARRAY_BUFFER: @@ -209,6 +230,11 @@ GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) return NULL; // no buffer bound to target } +GrGLvoid GR_GL_FUNCTION_TYPE nullGLFlushMappedBufferRange(GrGLenum target, + GrGLintptr offset, + GrGLsizeiptr length) {} + + GrGLboolean GR_GL_FUNCTION_TYPE nullGLUnmapBuffer(GrGLenum target) { GrGLuint id = 0; switch (target) { @@ -304,6 +330,7 @@ const GrGLInterface* GrGLCreateNullInterface() { functions->fEndQuery = noOpGLEndQuery; functions->fFinish = noOpGLFinish; functions->fFlush = noOpGLFlush; + functions->fFlushMappedBufferRange = nullGLFlushMappedBufferRange; functions->fFrontFace = noOpGLFrontFace; functions->fGenBuffers = nullGLGenBuffers; functions->fGenerateMipmap = nullGLGenerateMipmap; @@ -329,6 +356,8 @@ const GrGLInterface* GrGLCreateNullInterface() { functions->fInsertEventMarker = noOpGLInsertEventMarker; functions->fLineWidth = noOpGLLineWidth; functions->fLinkProgram = noOpGLLinkProgram; + functions->fMapBuffer = nullGLMapBuffer; + functions->fMapBufferRange = nullGLMapBufferRange; functions->fPixelStorei = nullGLPixelStorei; functions->fPopGroupMarker = noOpGLPopGroupMarker; functions->fPushGroupMarker = noOpGLPushGroupMarker; @@ -368,6 +397,7 @@ const GrGLInterface* GrGLCreateNullInterface() { functions->fUniformMatrix2fv = noOpGLUniformMatrix2fv; functions->fUniformMatrix3fv = noOpGLUniformMatrix3fv; functions->fUniformMatrix4fv = noOpGLUniformMatrix4fv; + functions->fUnmapBuffer = nullGLUnmapBuffer; functions->fUseProgram = nullGLUseProgram; functions->fVertexAttrib4fv = noOpGLVertexAttrib4fv; functions->fVertexAttribPointer = noOpGLVertexAttribPointer; @@ -387,10 +417,8 @@ const GrGLInterface* GrGLCreateNullInterface() { functions->fRenderbufferStorageMultisample = noOpGLRenderbufferStorageMultisample; functions->fBlitFramebuffer = noOpGLBlitFramebuffer; functions->fResolveMultisampleFramebuffer = noOpGLResolveMultisampleFramebuffer; - functions->fMapBuffer = nullGLMapBuffer; functions->fMatrixLoadf = noOpGLMatrixLoadf; functions->fMatrixLoadIdentity = noOpGLMatrixLoadIdentity; - functions->fUnmapBuffer = nullGLUnmapBuffer; functions->fBindFragDataLocationIndexed = noOpGLBindFragDataLocationIndexed; interface->fExtensions.init(kGL_GrGLStandard, functions->fGetString, functions->fGetStringi, diff --git a/src/gpu/gl/GrGLDefines.h b/src/gpu/gl/GrGLDefines.h index a4dc2f782b..73f3d2e146 100644 --- a/src/gpu/gl/GrGLDefines.h +++ b/src/gpu/gl/GrGLDefines.h @@ -601,6 +601,14 @@ /* Vertex Buffer Object */ #define GR_GL_WRITE_ONLY 0x88B9 #define GR_GL_BUFFER_MAPPED 0x88BC + +#define GR_GL_MAP_READ_BIT 0x0001 +#define GR_GL_MAP_WRITE_BIT 0x0002 +#define GR_GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#define GR_GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 +#define GR_GL_MAP_FLUSH_EXPLICIT_BIT 0x0010 +#define GR_GL_MAP_UNSYNCHRONIZED_BIT 0x0020 + /* Read Format */ #define GR_GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A #define GR_GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B diff --git a/src/gpu/gl/GrGLInterface.cpp b/src/gpu/gl/GrGLInterface.cpp index 056a601874..ee184d0a56 100644 --- a/src/gpu/gl/GrGLInterface.cpp +++ b/src/gpu/gl/GrGLInterface.cpp @@ -486,8 +486,8 @@ bool GrGLInterface::validate() const { } } -#if 0 // This can be enabled once Chromium is updated to set these functions pointers. - if ((kGL_GrGLStandard == fStandard) || fExtensions.has("GL_ARB_invalidate_subdata")) { + if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,3)) || + fExtensions.has("GL_ARB_invalidate_subdata")) { if (NULL == fFunctions.fInvalidateBufferData || NULL == fFunctions.fInvalidateBufferSubData || NULL == fFunctions.fInvalidateFramebuffer || @@ -496,7 +496,7 @@ bool GrGLInterface::validate() const { NULL == fFunctions.fInvalidateTexSubImage) { RETURN_FALSE_INTERFACE; } - } else if (glVer >= GR_GL_VER(3,0)) { + } else if (kGLES_GrGLStandard == fStandard && glVer >= GR_GL_VER(3,0)) { // ES 3.0 adds the framebuffer functions but not the others. if (NULL == fFunctions.fInvalidateFramebuffer || NULL == fFunctions.fInvalidateSubFramebuffer) { @@ -512,7 +512,15 @@ bool GrGLInterface::validate() const { RETURN_FALSE_INTERFACE; } } -#endif + // These functions are added to the 3.0 version of both GLES and GL. + if (glVer >= GR_GL_VER(3,0) || + (kGLES_GrGLStandard == fStandard && fExtensions.has("GL_EXT_map_buffer_range")) || + (kGL_GrGLStandard == fStandard && fExtensions.has("GL_ARB_map_buffer_range"))) { + if (NULL == fFunctions.fMapBufferRange || + NULL == fFunctions.fFlushMappedBufferRange) { + RETURN_FALSE_INTERFACE; + } + } return true; } diff --git a/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp b/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp index b50063fb1a..312299adae 100644 --- a/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp +++ b/src/gpu/gl/android/GrGLCreateNativeInterface_android.cpp @@ -75,7 +75,7 @@ static GrGLInterface* create_es_interface(GrGLVersion version, functions->fGetShaderInfoLog = glGetShaderInfoLog; functions->fGetShaderiv = glGetShaderiv; functions->fGetString = glGetString; -#if GL_ES_VERSION_30 +#if GL_ES_VERSION_3_0 functions->fGetStringi = glGetStringi; #else functions->fGetStringi = (GrGLGetStringiProc) eglGetProcAddress("glGetStringi"); @@ -183,12 +183,24 @@ static GrGLInterface* create_es_interface(GrGLVersion version, functions->fGetFramebufferAttachmentParameteriv = glGetFramebufferAttachmentParameteriv; functions->fGetRenderbufferParameteriv = glGetRenderbufferParameteriv; functions->fRenderbufferStorage = glRenderbufferStorage; + #if GL_OES_mapbuffer functions->fMapBuffer = glMapBufferOES; functions->fUnmapBuffer = glUnmapBufferOES; #else functions->fMapBuffer = (GrGLMapBufferProc) eglGetProcAddress("glMapBufferOES"); functions->fUnmapBuffer = (GrGLUnmapBufferProc) eglGetProcAddress("glUnmapBufferOES"); + +#endif + +#if GL_ES_VERSION_3_0 || GL_EXT_map_buffer_range + functions->fMapBufferRange = glMapBufferRange; + functions->fFlushMappedBufferRange = glFlushMappedBufferRange; +#else + if (version >= GR_GL_VER(3,0) || extensions->has("GL_EXT_map_buffer_range")) { + functions->fMapBufferRange = (GrGLMapBufferRangeProc) eglGetProcAddress("glMapBufferRange"); + functions->fFlushMappedBufferRange = (GrGLFlushMappedBufferRangeProc) eglGetProcAddress("glFlushMappedBufferRange"); + } #endif if (extensions->has("GL_EXT_debug_marker")) { diff --git a/src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp b/src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp index a316ff1c11..cb2fc95336 100644 --- a/src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp +++ b/src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp @@ -154,6 +154,14 @@ const GrGLInterface* GrGLCreateANGLEInterface() { functions->fMapBuffer = (GrGLMapBufferProc) eglGetProcAddress("glMapBufferOES"); functions->fUnmapBuffer = (GrGLUnmapBufferProc) eglGetProcAddress("glUnmapBufferOES"); +#if GL_ES_VERSION_3_0 + functions->fMapBufferRange = GET_PROC(glMapBufferRange); + functions->fFlushMappedBufferRange = GET_PROC(glFlushMappedBufferRange); +#else + functions->fMapBufferRange = (GrGLMapBufferRangeProc) eglGetProcAddress("glMapBufferRange"); + functions->fFlushMappedBufferRange = (GrGLFlushMappedBufferRangeProc) eglGetProcAddress("glFlushMappedBufferRange"); +#endif + functions->fInsertEventMarker = (GrGLInsertEventMarkerProc) eglGetProcAddress("glInsertEventMarkerEXT"); functions->fPushGroupMarker = (GrGLInsertEventMarkerProc) eglGetProcAddress("glPushGroupMarkerEXT"); functions->fPopGroupMarker = (GrGLPopGroupMarkerProc) eglGetProcAddress("glPopGroupMarkerEXT"); diff --git a/src/gpu/gl/debug/GrBufferObj.h b/src/gpu/gl/debug/GrBufferObj.h index fecfeb5e3c..05d3cfddde 100644 --- a/src/gpu/gl/debug/GrBufferObj.h +++ b/src/gpu/gl/debug/GrBufferObj.h @@ -34,9 +34,15 @@ public: GrAlwaysAssert(!fMapped); } - void setMapped() { fMapped = true; } + void setMapped(GrGLintptr offset, GrGLsizeiptr length) { + fMapped = true; + fMappedOffset = offset; + fMappedLength = length; + } void resetMapped() { fMapped = false; } bool getMapped() const { return fMapped; } + GrGLsizei getMappedOffset() const { return fMappedOffset; } + GrGLsizei getMappedLength() const { return fMappedLength; } void setBound() { fBound = true; } void resetBound() { fBound = false; } @@ -55,7 +61,9 @@ protected: private: GrGLchar* fDataPtr; - bool fMapped; // is the buffer object mapped via "glMapBuffer"? + bool fMapped; // is the buffer object mapped via "glMapBuffer[Range]"? + GrGLintptr fMappedOffset; // the offset of the buffer range that is mapped + GrGLsizeiptr fMappedLength; // the size of the buffer range that is mapped bool fBound; // is the buffer object bound via "glBindBuffer"? GrGLsizeiptr fSize; // size in bytes GrGLint fUsage; // one of: GL_STREAM_DRAW, diff --git a/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp b/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp index 087bd4723e..7c430b4b73 100644 --- a/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp +++ b/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp @@ -622,12 +622,14 @@ GrGLvoid GR_GL_FUNCTION_TYPE debugGLDeleteBuffers(GrGLsizei n, const GrGLuint* i } // map a buffer to the caller's address space -GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBuffer(GrGLenum target, GrGLenum access) { - +GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBufferRange(GrGLenum target, GrGLintptr offset, + GrGLsizeiptr length, GrGLbitfield access) { GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target || GR_GL_ELEMENT_ARRAY_BUFFER == target); - // GR_GL_READ_ONLY == access || || GR_GL_READ_WRIT == access); - GrAlwaysAssert(GR_GL_WRITE_ONLY == access); + + // We only expect read access and we expect that the buffer or range is always invalidated. + GrAlwaysAssert(!SkToBool(GR_GL_MAP_READ_BIT & access)); + GrAlwaysAssert((GR_GL_MAP_INVALIDATE_BUFFER_BIT | GR_GL_MAP_INVALIDATE_RANGE_BIT) & access); GrBufferObj *buffer = NULL; switch (target) { @@ -638,20 +640,41 @@ GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBuffer(GrGLenum target, GrGLenum access) buffer = GrDebugGL::getInstance()->getElementArrayBuffer(); break; default: - SkFAIL("Unexpected target to glMapBuffer"); + SkFAIL("Unexpected target to glMapBufferRange"); break; } - if (buffer) { + if (NULL != buffer) { + GrAlwaysAssert(offset >= 0 && offset + length <= buffer->getSize()); GrAlwaysAssert(!buffer->getMapped()); - buffer->setMapped(); - return buffer->getDataPtr(); + buffer->setMapped(offset, length); + return buffer->getDataPtr() + offset; } GrAlwaysAssert(false); return NULL; // no buffer bound to the target } +GrGLvoid* GR_GL_FUNCTION_TYPE debugGLMapBuffer(GrGLenum target, GrGLenum access) { + GrAlwaysAssert(GR_GL_WRITE_ONLY == access); + + GrBufferObj *buffer = NULL; + switch (target) { + case GR_GL_ARRAY_BUFFER: + buffer = GrDebugGL::getInstance()->getArrayBuffer(); + break; + case GR_GL_ELEMENT_ARRAY_BUFFER: + buffer = GrDebugGL::getInstance()->getElementArrayBuffer(); + break; + default: + SkFAIL("Unexpected target to glMapBuffer"); + break; + } + + return debugGLMapBufferRange(target, 0, buffer->getSize(), + GR_GL_MAP_WRITE_BIT | GR_GL_MAP_INVALIDATE_BUFFER_BIT); +} + // remove a buffer from the caller's address space // TODO: check if the "access" method from "glMapBuffer" was honored GrGLboolean GR_GL_FUNCTION_TYPE debugGLUnmapBuffer(GrGLenum target) { @@ -672,7 +695,7 @@ GrGLboolean GR_GL_FUNCTION_TYPE debugGLUnmapBuffer(GrGLenum target) { break; } - if (buffer) { + if (NULL != buffer) { GrAlwaysAssert(buffer->getMapped()); buffer->resetMapped(); return GR_GL_TRUE; @@ -682,6 +705,34 @@ GrGLboolean GR_GL_FUNCTION_TYPE debugGLUnmapBuffer(GrGLenum target) { return GR_GL_FALSE; // GR_GL_INVALID_OPERATION; } +GrGLvoid GR_GL_FUNCTION_TYPE debugGLFlushMappedBufferRange(GrGLenum target, + GrGLintptr offset, + GrGLsizeiptr length) { + GrAlwaysAssert(GR_GL_ARRAY_BUFFER == target || + GR_GL_ELEMENT_ARRAY_BUFFER == target); + + GrBufferObj *buffer = NULL; + switch (target) { + case GR_GL_ARRAY_BUFFER: + buffer = GrDebugGL::getInstance()->getArrayBuffer(); + break; + case GR_GL_ELEMENT_ARRAY_BUFFER: + buffer = GrDebugGL::getInstance()->getElementArrayBuffer(); + break; + default: + SkFAIL("Unexpected target to glUnmapBuffer"); + break; + } + + if (NULL != buffer) { + GrAlwaysAssert(buffer->getMapped()); + GrAlwaysAssert(offset >= 0 && (offset + length) <= buffer->getMappedLength()); + } else { + GrAlwaysAssert(false); + } +} + + GrGLvoid GR_GL_FUNCTION_TYPE debugGLGetBufferParameteriv(GrGLenum target, GrGLenum value, GrGLint* params) { @@ -706,17 +757,17 @@ GrGLvoid GR_GL_FUNCTION_TYPE debugGLGetBufferParameteriv(GrGLenum target, switch (value) { case GR_GL_BUFFER_MAPPED: *params = GR_GL_FALSE; - if (buffer) + if (NULL != buffer) *params = buffer->getMapped() ? GR_GL_TRUE : GR_GL_FALSE; break; case GR_GL_BUFFER_SIZE: *params = 0; - if (buffer) + if (NULL != buffer) *params = SkToInt(buffer->getSize()); break; case GR_GL_BUFFER_USAGE: *params = GR_GL_STATIC_DRAW; - if (buffer) + if (NULL != buffer) *params = buffer->getUsage(); break; default: @@ -826,6 +877,7 @@ const GrGLInterface* GrGLCreateDebugInterface() { functions->fEndQuery = noOpGLEndQuery; functions->fFinish = noOpGLFinish; functions->fFlush = noOpGLFlush; + functions->fFlushMappedBufferRange = debugGLFlushMappedBufferRange; functions->fFrontFace = noOpGLFrontFace; functions->fGenerateMipmap = debugGLGenerateMipmap; functions->fGenBuffers = debugGLGenBuffers; @@ -850,6 +902,8 @@ const GrGLInterface* GrGLCreateDebugInterface() { functions->fGenVertexArrays = debugGLGenVertexArrays; functions->fLineWidth = noOpGLLineWidth; functions->fLinkProgram = noOpGLLinkProgram; + functions->fMapBuffer = debugGLMapBuffer; + functions->fMapBufferRange = debugGLMapBufferRange; functions->fPixelStorei = debugGLPixelStorei; functions->fQueryCounter = noOpGLQueryCounter; functions->fReadBuffer = noOpGLReadBuffer; @@ -887,6 +941,7 @@ const GrGLInterface* GrGLCreateDebugInterface() { functions->fUniformMatrix2fv = noOpGLUniformMatrix2fv; functions->fUniformMatrix3fv = noOpGLUniformMatrix3fv; functions->fUniformMatrix4fv = noOpGLUniformMatrix4fv; + functions->fUnmapBuffer = debugGLUnmapBuffer; functions->fUseProgram = debugGLUseProgram; functions->fVertexAttrib4fv = noOpGLVertexAttrib4fv; functions->fVertexAttribPointer = noOpGLVertexAttribPointer; @@ -909,10 +964,9 @@ const GrGLInterface* GrGLCreateDebugInterface() { functions->fBlitFramebuffer = noOpGLBlitFramebuffer; functions->fResolveMultisampleFramebuffer = noOpGLResolveMultisampleFramebuffer; - functions->fMapBuffer = debugGLMapBuffer; functions->fMatrixLoadf = noOpGLMatrixLoadf; functions->fMatrixLoadIdentity = noOpGLMatrixLoadIdentity; - functions->fUnmapBuffer = debugGLUnmapBuffer; + functions->fBindFragDataLocationIndexed = noOpGLBindFragDataLocationIndexed; diff --git a/src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp b/src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp index 6af047159a..d61afbd675 100644 --- a/src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp +++ b/src/gpu/gl/iOS/GrGLCreateNativeInterface_iOS.cpp @@ -132,6 +132,11 @@ const GrGLInterface* GrGLCreateNativeInterface() { functions->fUnmapBuffer = glUnmapBufferOES; #endif +#if GL_EXT_map_buffer_range || GL_ES_VERSION_3_0 + functions->fMapBufferRange = glMapBufferRange; + functions->fFlushMappedBufferRange = glFlushMappedBufferRange; +#endif + #if GL_APPLE_framebuffer_multisample functions->fRenderbufferStorageMultisample = glRenderbufferStorageMultisampleAPPLE; functions->fResolveMultisampleFramebuffer = glResolveMultisampleFramebufferAPPLE; |