• 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是什么

     

    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

  • 相关阅读:
    容斥 / dp
    【推荐系统】推荐系统基础算法-基于协同的推荐算法
    万物皆可集成系列:低代码如何不成为数据孤岛
    RationalDMIS 2022位置度评价,轮廓度评价时, 元素理论值变了,如何一劳永逸解决!
    【软考 系统架构设计师】案例分析⑥ Web应用系统架构设计
    (数据科学学习手札145)在Python中利用yarl轻松操作url
    九、数据仓库详细介绍(元数据)
    基于云计算的区域LIS系统系统源码
    python 函数参数 必选参数 默认参数 可变参数 关键字参数
    Qt5开发从入门到精通——第三篇(窗口篇——停靠窗口)
  • 原文地址:https://blog.csdn.net/c553110519/article/details/126278993