opengl 源码分析常见问题

Opengl 一些问题解答

为什么opengl 不能跨线程

  大家有没有想过这个问题,网上给出的答案其实看得不太明白,接下来我们看源码让你知道

C
EGLContext Display::createContext(EGLConfig configHandle, const gl::Context *shareContext, bool notifyResets, bool robustAccess)
{
    const egl::Config *config = mConfigSet.get(configHandle);
    gl::Context *context = glCreateContext(config, shareContext, notifyResets, robustAccess);
    return context;
}
gl::Context *glCreateContext(const egl::Config *config, const gl::Context *shareContext, bool notifyResets, bool robustAccess)
{
    return new gl::Context(config, shareContext, notifyResets, robustAccess);
}

EGLBoolean __stdcall eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx)
{
    ...
    glMakeCurrent(context, display, static_cast(draw));
}
void glMakeCurrent(gl::Context *context, egl::Display *display, egl::Surface *surface)
{
    gl::makeCurrent(context, display, surface);
}
void makeCurrent(Context *context, egl::Display *display, egl::Surface *surface)
{
    Current *current = (Current*)TlsGetValue(currentTLS);

    current->context = context;
    current->display = display;
}

通过上边源码可以看到, 它是把context 放入到thread local中去,这就是不能夸线程根本原因,在做opengl 一些操作的时候,首先去线程中取context, 如果夸了线程取的context就不是之前使用的context导致了crash,这个希望大家要明确知道

纹理是什么

C++
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, screenWidth, screenHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

 

 glGenTextures(GLsizei n, GLuint* textures)

   这个textures返回的 是int值,具体是什么?

   要想知道这个答案要看下opengles的源码:

C
void __stdcall glGenTextures(GLsizei n, GLuint* textures)
{
         gl::Context *context = gl::getNonLostContext();
         for (int i = 0; i < n; i++)
         {
             textures[i] = context->createTexture();
         }
}
    GLuint Context::createTexture()
    {
        return mResourceManager->createTexture();
    }
        GLuint ResourceManager::createTexture()
        {
            GLuint handle = mTextureHandleAllocator.allocate();
            mTextureMap[handle] = NULL;
            return handle;
        }
       
            GLuint HandleAllocator::allocate()
            {
                /*
                 typedef std::vector HandleList;
                HandleList mFreeValues;
                */
                if (mFreeValues.size())
                {
                    GLuint handle = mFreeValues.back();
                    mFreeValues.pop_back();
                    return handle;
                }
                return mNextValue++;
            }
     

通过上边源码也可以看出来, 首先创建纹理的时候会从当前线程的threadlocal取出context,再从context取出来mResourceManager,在从mResourcManager取出一个空隙的ID,也就说这个gentexutre只是拿到一个id,其他什么都没干

纹理绑定到底干了些啥

C
void __stdcall glBindTexture(GLenum target, GLuint texture)
{
    EVENT("(GLenum target = 0x%X, GLuint texture = %d)", target, texture);
    gl::Context *context = gl::getNonLostContext();
    gl::Texture *textureObject = context->getTexture(texture);   
    switch (target)
    {
         case GL_TEXTURE_2D:
              context->bindTexture2D(texture);
             return;
    }
}
    Texture *Context::getTexture(GLuint handle)
    {
        return mResourceManager->getTexture(handle);
    }
        Texture *ResourceManager::getTexture(unsigned int handle)
        {
            if (handle == 0) return NULL;
       
            TextureMap::iterator texture = mTextureMap.find(handle);
            if (texture == mTextureMap.end())
            {
                return NULL;
            }
            else
            {
                return texture->second;
            }
        }
    void Context::bindTexture2D(GLuint texture)
    {
        mResourceManager->checkTextureAllocation(texture, TEXTURE_2D);
        //    BindingPointer samplerTexture[TEXTURE_TYPE_COUNT][MAX_COMBINED_TEXTURE_IMAGE_UNITS_VTF];
        mState.samplerTexture[TEXTURE_2D][mState.activeSampler].set(getTexture(texture));
    }
//
        void ResourceManager::checkTextureAllocation(GLuint texture, TextureType type)
        {
            if (!getTexture(texture) && texture != 0)
            {
                Texture *textureObject;
       
                if (type == TEXTURE_2D)
                {
                    textureObject = new Texture2D(texture);
                }
                mTextureMap[texture] = textureObject;
                textureObject->addRef();
            }
        }
 

也就是说从上边可以看的出来纹理绑定是了创建2D的纹理对象,指定了当前 mState.samplerTexture[TEXTURE_2D][mState.activeSampler]活动的纹理对象,这个时候还没有分配真正的分配显存

glActiveTexutre到底干了啥

glTexImage2D 并没有参数是纹理ID,那它是怎么知道上传到哪个纹理的

也就是说这两个问题很类似,这一点我之前也很困惑,只有源码才能解决我们的困惑

C
void __stdcall glActiveTexture(GLenum texture)
{
        gl::Context *context = gl::getNonLostContext();
        context->setActiveSampler(texture - GL_TEXTURE0);
}
    void Context::setActiveSampler(unsigned int active)
    {
        mState.activeSampler = active;
    }

从上边可以看出来activetexutre是设置当前活动的activeSampler,然后纹理绑定的时候会使用这个信息,在上传纹理,或者其他使用纹理的时候,就去mStat去取对应的纹理

纹理是如何分配显存的

想必大家都困惑 显存是在什么时候分配的,我们c,c++分配一般会调用new,malloc,但是opengl从头到尾接口也没有看到,gl什么alloc,gl什么new之类的函数,这个我从学习opengl以来,十分困惑,接下来我就重点讲述下纹理是如何分配显存的

   可能你会觉得glTexImage2D(......, nullptr);最后这个nullptr是告诉opengl 要分配显存,到底是不是呢,接下来我们就重点分析这个glTexImage2D

C
void __stdcall glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height,
                            GLint border, GLenum format, GLenum type, const GLvoid* pixels)
{
        //...
        gl::Context *context = gl::getNonLostContext();

        //...
        if (target == GL_TEXTURE_2D)
        {
                gl::Texture2D *texture = context->getTexture2D();
                texture->setImage(level, width, height, format, type, context->getUnpackAlignment(), pixels);
        }
}
    Texture2D *Context::getTexture2D()
    {
        return static_cast(getSamplerTexture(mState.activeSampler, TEXTURE_2D));
    }
        Texture *Context::getSamplerTexture(unsigned int sampler, TextureType type)
        {
            GLuint texid = mState.samplerTexture[type][sampler].id();
            return mState.samplerTexture[type][sampler].get();
        }
       

void Texture2D::setImage(GLint level, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels)
{
    redefineImage(level, format, width, height, type);
    Texture::setImage(unpackAlignment, pixels, &mImageArray[level]);
}
    bool redefineImage(GLenum format, GLsizei width, GLsizei height, GLenum type, bool forceRelease)
    {
       mWidth = width;
       mHeight = height;
       mFormat = format;
       mType = type;
    // compute the d3d format that will be used
        mD3DFormat = ConvertTextureFormatType(mFormat, mType);
        return true;
    }
    //也就是说当传递是nullptr时候并不会分配显存,只是记录了纹理的宽高format等信息
    void Texture::setImage(GLint unpackAlignment, const void *pixels, Image *image)
    {
        if (pixels != NULL)
        {
            image->loadData(0, 0, image->getWidth(), image->getHeight(), image->getType(), unpackAlignment, pixels);
            mDirtyImages = true;
        }
    }
    //当非空的时候就会Lock一张surface,也就是这个时候才会创建 显存,绑定到surface上
        void Image::loadData(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum type,
                             GLint unpackAlignment, const void *input)
        {
            RECT lockRect = transformPixelRect(xoffset, yoffset, width, height, mHeight);
            D3DLOCKED_RECT locked;
            HRESULT result = lock(&locked, &lockRect);
                switch (mFormat)
                {
                  //........
                  case GL_RGBA:
                        loadRGBAUByteData(width, height, inputPitch, input, locked.Pitch, locked.pBits);
                        break;
                }
       
            unlock();
        }

            HRESULT Image::lock(D3DLOCKED_RECT *lockedRect, const RECT *rect)
            {
                createSurface();
                if (mSurface)
                {
                    result = mSurface->LockRect(lockedRect, rect, 0);
                }
                return result;
            }
                void Image::createSurface()
                {
                    IDirect3DTexture9 *newTexture = NULL;
                    IDirect3DSurface9 *newSurface = NULL;
                    const D3DPOOL poolToUse = D3DPOOL_SYSTEMMEM;
               
                    if (mWidth != 0 && mHeight != 0)
                    {
                        int levelToFetch = 0;
                        GLsizei requestWidth = mWidth;
                        GLsizei requestHeight = mHeight;
                        HRESULT result = getDevice()->CreateTexture(requestWidth, requestHeight, levelToFetch + 1, NULL, getD3DFormat(),
                                                                    poolToUse, &newTexture, NULL);       
                        newTexture->GetSurfaceLevel(levelToFetch, &newSurface);
                    }
                    mSurface = newSurface;//IDirect3DSurface9 *mSurface;
                }
   

从上边分析可以看出来,首先它会从当前绑定的纹理上找到要上传的纹理,当真正只有裸数传进来的,才会创建显存

FBO是什么

opengl 源码分析常见问题_第1张图片

 

C++
class Framebuffer
{
  protected:
    GLenum mColorbufferType;
    BindingPointer mColorbufferPointer;

    GLenum mDepthbufferType;
    BindingPointer mDepthbufferPointer;

    GLenum mStencilbufferType;
    BindingPointer mStencilbufferPointer;
};

相信这张图很多人都看到过,接下来我们就着重分析帧缓冲区到底是什么东西

glGenFramebuffers 做了什么

C
void __stdcall glGenFramebuffers(GLsizei n, GLuint* framebuffers)
{
        gl::Context *context = gl::getNonLostContext();
        for (int i = 0; i < n; i++)
        {
                framebuffers[i] = context->createFramebuffer();
        }
}
    GLuint Context::createFramebuffer()
    {
        GLuint handle = mFramebufferHandleAllocator.allocate();
   
        mFramebufferMap[handle] = NULL;
   
        return handle;
    }
        GLuint HandleAllocator::allocate()
        {
            if (mFreeValues.size())
            {
                GLuint handle = mFreeValues.back();
                mFreeValues.pop_back();
                return handle;
            }
            return mNextValue++;
        }

跟纹理类似,帧缓冲区创建也仅仅是分配了id而已

glBindFramebuffer 干了什么

C
void __stdcall glBindFramebuffer(GLenum target, GLuint framebuffer)
{
        gl::Context *context = gl::getNonLostContext();
        context->bindReadFramebuffer(framebuffer); 
        context->bindDrawFramebuffer(framebuffer);
}
    void Context::bindReadFramebuffer(GLuint framebuffer)
    {
        if (!getFramebuffer(framebuffer))
        {
            mFramebufferMap[framebuffer] = new Framebuffer();
        }
   
        mState.readFramebuffer = framebuffer;
    }
   
    void Context::bindDrawFramebuffer(GLuint framebuffer)
    {
        if (!getFramebuffer(framebuffer))
        {
            mFramebufferMap[framebuffer] = new Framebuffer();
        }
   
        mState.drawFramebuffer = framebuffer;
   
        mBoundDrawFramebuffer = getFramebuffer(framebuffer);
    }
   
    Framebuffer *Context::getFramebuffer(unsigned int handle)
    {
        FramebufferMap::iterator framebuffer = mFramebufferMap.find(handle);
   
        if (framebuffer == mFramebufferMap.end())
        {
            return NULL;
        }
        else
        {
            return framebuffer->second;
        }
    }

也就是说glBindFramebuffer 填充了mState.readFramebuffer = mState.drawFramebuffer 这个read跟draw在后边绘制的时候还是会用的上的

glFramebufferTexture2D 跟纹理是什么关系

C++
void __stdcall glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
{
        gl::Framebuffer *framebuffer = NULL;
        GLuint framebufferHandle = 0;
  
        framebuffer = context->getDrawFramebuffer();
        framebufferHandle = context->getDrawFramebufferHandle();
       
        if (framebufferHandle == 0 || !framebuffer)
        {
                return error(GL_INVALID_OPERATION);
        }

        switch (attachment)
        {
          case GL_COLOR_ATTACHMENT0:  framebuffer->setColorbuffer(textarget, texture);   break;
          case GL_DEPTH_ATTACHMENT:   framebuffer->setDepthbuffer(textarget, texture);   break;
          case GL_STENCIL_ATTACHMENT: framebuffer->setStencilbuffer(textarget, texture); break;
        }
}
    void Framebuffer::setColorbuffer(GLenum type, GLuint colorbuffer)
    {
        mColorbufferType = (colorbuffer != 0) ? type : GL_NONE;
        mColorbufferPointer.set(lookupRenderbuffer(type, colorbuffer));
    }
   
    void Framebuffer::setDepthbuffer(GLenum type, GLuint depthbuffer)
    {
        mDepthbufferType = (depthbuffer != 0) ? type : GL_NONE;
        mDepthbufferPointer.set(lookupRenderbuffer(type, depthbuffer));
    }
   
    void Framebuffer::setStencilbuffer(GLenum type, GLuint stencilbuffer)
    {
        mStencilbufferType = (stencilbuffer != 0) ? type : GL_NONE;
        mStencilbufferPointer.set(lookupRenderbuffer(type, stencilbuffer));
    }
    //从纹理中取出renderbuffer,最终渲染的时候会去取renderbuffer的suface进行绘制
        Renderbuffer *Framebuffer::lookupRenderbuffer(GLenum type, GLuint handle) const
        {
            gl::Context *context = gl::getContext();
            Renderbuffer *buffer = NULL;
            buffer = context->getTexture(handle)->getRenderbuffer(type);
          
            return buffer;
        }

也就是说glFramebufferTexture2D 是纹理的显存单元附加到framebuffer颜色附件上去了,当然纹理也可以作为深度或者模板附件,添加framebuffer

什么只有绑定了FBO纹理的才能下载

大家肯定或多或少的都碰到过android 硬件解码出来的纹理,直接glreadpixel 读出来是一张黑图,当我们调用了copy texture接口后,读下来的纹理就是正常的,我们就看下源码中是这怎么做到的

C
void __stdcall glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                            GLenum format, GLenum type, GLvoid* pixels)
{
        gl::Context *context = gl::getNonLostContext();
        if (context)
        {
                context->readPixels(x, y, width, height, format, type, NULL, pixels);
        }
}
    void Context::readPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                             GLenum format, GLenum type, GLsizei *bufSize, void* pixels)
    {
        Framebuffer *framebuffer = getReadFramebuffer();
   
        IDirect3DSurface9 *renderTarget = framebuffer->getRenderTarget();
        if (!renderTarget)
        {
            return;   // Context must be lost, return silently
        }
   
        result = mDevice->GetRenderTargetData(renderTarget, systemSurface);
   
        result = systemSurface->LockRect(&lock, &rect, D3DLOCK_READONLY);
   
        unsigned char *dest = (unsigned char*)pixels;
  
        unsigned char *source;

        source = ((unsigned char*)lock.pBits) + lock.Pitch * (rect.bottom - rect.top - 1);

        for (int j = 0; j < height; j++)
        {
   
            for (int i = 0; i < width; i++)
            {
                float r;
                float g;
                float b;
                float a;
   
                switch (desc.Format)
                {
           
                  case D3DFMT_A8R8G8B8:
                    {
                        unsigned int argb = *(unsigned int*)(source + 4 * i + j * inputPitch);
       
                            a = (argb & 0xFF000000) * (1.0f / 0xFF000000);
                            b = (argb & 0x000000FF) * (1.0f / 0x000000FF);
                            g = (argb & 0x0000FF00) * (1.0f / 0x0000FF00);
                            r = (argb & 0x00FF0000) * (1.0f / 0x00FF0000);
                        }
                        break;
                    }
                    switch (format)
                    {
                      case GL_RGBA:
                        switch (type)
                        {
                          case GL_UNSIGNED_BYTE:
                            dest[4 * i + j * outputPitch + 0] = (unsigned char)(255 * r + 0.5f);
                            dest[4 * i + j * outputPitch + 1] = (unsigned char)(255 * g + 0.5f);
                            dest[4 * i + j * outputPitch + 2] = (unsigned char)(255 * b + 0.5f);
                            dest[4 * i + j * outputPitch + 3] = (unsigned char)(255 * a + 0.5f);
                            break;
                          default: UNREACHABLE();
                        }
                        break;
                    }
                }
            }
           
            systemSurface->UnlockRect();
       
            systemSurface->Release();
    }

也就说glreadpixel 是 通过getReadFramebuffer 来获取的FBO,从FBO中绑定的rendertarget,最终去取像素值,所以 如果你想读取的纹理没有绑定fbo,是读取不出来的,原因就是它压根不是直接读取纹理的

FBO 屏幕的关系

glDrawArrays 干了啥

 大家有没有想过为什么纹理绑定在fbo上,就是离屏渲染,没有绑定就是渲染到屏幕呢,这一点也困惑我很久,因为调用glDrawArrays 并没有指定绘制在哪里,很奇怪,所以接下来我们就要探究下,底层是怎么做的

首先要分析下glDrawArrays 的源码

C++
void __stdcall glDrawArrays(GLenum mode, GLint first, GLsizei count)
{
    gl::Context *context = gl::getNonLostContext();
    if (context)
    {
        context->drawArrays(mode, first, count, 0);
    }
}
    void Context::drawArrays(GLenum mode, GLint first, GLsizei count, GLsizei instances)
    {
        if (!applyRenderTarget(false))
        {
            return;
        }
    }
   
    bool Context::applyRenderTarget(bool ignoreViewport)
    {
        Framebuffer *framebufferObject = getDrawFramebuffer();
            IDirect3DSurface9 *renderTarget = framebufferObject->getRenderTarget();
            if (!renderTarget)
            {
                    return false;   // Context must be lost
            }
            mDevice->SetRenderTarget(0, renderTarget);
        return true;
    }
   
        Framebuffer *Context::getDrawFramebuffer()
        {
            return mBoundDrawFramebuffer;
        }
        //如果绑定了纹理,它会去纹surface送device
        IDirect3DSurface9 *Texture2D::getRenderTarget(GLenum target)
        {  
            return mTexStorage->getSurfaceLevel(0);
        }
       
       
        //那么如果没有绑定纹理 是怎么做到画到屏幕上的
        // 也就是make current 会把取屏幕surface做挂载FBO 0 号对应的上边去
        //也就是说,其实屏幕也是最终挂载FBO上,很是巧妙
        void Context::makeCurrent(egl::Display *display, egl::Surface *surface)
        {
            mDisplay = display;
            mDevice = mDisplay->getDevice();
            // Wrap the existing Direct3D 9 resources into GL objects and assign them to the '0' names
            IDirect3DSurface9 *defaultRenderTarget = surface->getRenderTarget();
            IDirect3DSurface9 *depthStencil = surface->getDepthStencil();
       
            Colorbuffer *colorbufferZero = new Colorbuffer(defaultRenderTarget);
            DepthStencilbuffer *depthStencilbufferZero = new DepthStencilbuffer(depthStencil);
            Framebuffer *framebufferZero = new DefaultFramebuffer(colorbufferZero, depthStencilbufferZero);
            setFramebufferZero(framebufferZero);
        }
            void Context::setFramebufferZero(Framebuffer *buffer)
            {
                mFramebufferMap[0] = buffer;
                if (mState.drawFramebuffer == 0)
                {
                    mBoundDrawFramebuffer = buffer;
                }
            }
   

通过上边代码分析,我们可以明确的得出结论,就是无论画屏幕,还是画纹理,本质都是对其绑定surface 做处理,也就是说FBO id是0号,就是画屏幕,非0号,就是绑定了其他的纹理或者renderbuffer。总而言之,大家应该清晰的明白这到底是怎么回事

为什么FBO 不能共享context

为什么纹理可以共享context

有哪些资源可以共享context

C++
Context::Context(const egl::Config *config, const gl::Context *shareContext, bool notifyResets, bool robustAccess) : mConfig(config)
{
   
    if (shareContext != NULL)
    {
        mResourceManager = shareContext->mResourceManager;
        mResourceManager->addRef();
    }
    else
    {
        mResourceManager = new ResourceManager();
    }
}
class ResourceManager
{
    typedef stdext::hash_map BufferMap;
    BufferMap mBufferMap;
    HandleAllocator mBufferHandleAllocator;

    typedef stdext::hash_map ShaderMap;
    ShaderMap mShaderMap;

    typedef stdext::hash_map ProgramMap;
    ProgramMap mProgramMap;
    HandleAllocator mProgramShaderHandleAllocator;

    typedef stdext::hash_map TextureMap;
    TextureMap mTextureMap;
    HandleAllocator mTextureHandleAllocator;

    typedef stdext::hash_map RenderbufferMap;
    RenderbufferMap mRenderbufferMap;
    HandleAllocator mRenderbufferHandleAllocator;
};
 

共享context后可以同时创建纹理吗

Sourcecode:

https://github.com/adobe/angle

你可能感兴趣的:(opengl,android,图形渲染)