基于opengl编写一个简易的2d渲染框架-12重构渲染器-blockallocator

为了邮箱5 为了邮箱5     2022-09-06     311

关键词:

 BlockAllocator 的内存管理情况可以用下图表示

 

  整体思路是,先分配一大块内存 Chunk,然后将 Chunk 分割成小块 Block。由于 Block 是链表的一个结点,所以可以通过链表的形式把未使用的 Block 连接起来,并保存到 pFreeLists 中。当我们向 BlockAllocator 申请一块内存时,BlockAllocator 会通过 pFreeLists 表索引出一块空闲的 Block,并返回其地址。当我们不断申请内存的时候,BlockAllocator 会不断返回未使用的 Block 块。当 Chunk 内存空间用尽时,BlockAllocator 会再开辟一块新的 Chunk。

  但是,分割 Chunk 时,Block 的大小是确定的。假如 Block 的大小为 148 字节,当只申请 64 字节大小的空间时,会造成浪费;当申请 256 字节时,Block 的大小不足;

  所以需要把 Block 根据大小分成不同的类型,如 64、148、256 ...,当申请内存的时候,BlockAllocator 先确定要申请内存的大小,再查找表,返回适当大小类型的 block。但是如何快速返回适当大小类型的 block?可以使用一个数组:

static int blockTypeSizeTable[BLOCK_TYPE_COUNT];

 

先通过下面的方式初始化 blockTypeSizeTable:

        /* 设置索引数组 blockTypeSizeLookup 的 block 类型索引值 */
        if ( blockTypeSizeLookipInitialized == false ) {
            blockTypeSizeLookipInitialized = true;

            int blockTypeSizeIndex = 0;
            for ( int i = 0; i <= MAX_BLOCK_SIZE; i++ ) {
                if ( i <= blockTypeSizeTable[blockTypeSizeIndex] ) {
                    blockTypeSizeLookup[i] = blockTypeSizeIndex;
                }
                else {
                    blockTypeSizeIndex++;
                    blockTypeSizeLookup[i] = blockTypeSizeIndex;
                }
            }
        }

假设有三种类型大小的 Block,16 字节、32 字节、48字节,则数组 blockTypeSizeTable 的大小为 49, 初始化后的 blockTypeSizeTable 储存的内容是:

    0-16 储存索引 0

  17-32 储存索引 1

  33-48 储存索引 2

 

当我们申请大小为 36 字节的内存时,由于 36 落在区间 33-48 内,所以 blockTypeSizeTable[36] 会得到索引 2。然后通过 2 查找表 pFreeLists 即可获取大小为 48 字节的 Block。pFreeLists 是一个数组,储存所有类型 Block 链表的地址。pFreeLists[0] 指向的是大小为 16 字节的空闲 Block 链表,pFreeLists[2] 指向的是大小为 32 字节的空闲 Block 链表。

 

具体的分配操作在函数 allocate 中:

    void* BlockAllocator::allocate(int size)
    {
        if ( size == 0 ) return 0;
        assert(size > 0);

        /* 使用四个字节记录 block 的类型索引,free 是使用 */
        size += sizeof(int);

        /* 申请的空间大于规定的最大值,直接申请,不放到块的链表中去 */
        if ( size > MAX_BLOCK_SIZE ) {
            int* data = ( int* ) malloc(size);
            /* -1 表示这是直接分配的内存 */
            data[0] = UNKNOWN_MEMORY;        
            return (data + 1);
        }

        int index = blockTypeSizeLookup[size];
        assert(0 <= index && index < BLOCK_TYPE_COUNT);

        /* 存在同类型的未被使用的内存块?返回内存块 */
        if ( pFreeLists[index] ) {
            /* 使块头指针指向新的未被使用的 block */
            Block* block = pFreeLists[index];
            pFreeLists[index] = block->next;
            return (( int* ) block + 1);
        }
        else {
            /* 扩展 chunk 数组 */
            if ( nChunkCount == nChunkSpace ) {
                Chunk* oldChunks = pChunks;
                nChunkSpace += CHUNK_ARRAY_INCREMENT;
                pChunks = ( Chunk* ) malloc(nChunkSpace * sizeof(Chunk));
                memcpy(pChunks, oldChunks, nChunkCount * sizeof(Chunk));
                memset(pChunks + nChunkCount, 0, CHUNK_ARRAY_INCREMENT * sizeof(Chunk));
                ::free(oldChunks);
            }
            int chunkSize = chunkSizeTable[index];
            /* 获取一个未被使用的可以用来分配内存的 chunk */
            Chunk* chunk = pChunks + nChunkCount;
            chunk->blocks = ( Block* ) malloc(chunkSize);

            /* 获取当前申请的 block 类型大小 */
            int blockSize = blockTypeSizeTable[index];

            /* 计算一块 chunk 内存能够分割成 block 的数量 */
            int blockCount = chunkSize / blockSize;
            assert(blockCount * blockSize <= chunkSize);

            /* 将 chunk 分割出许多 block,再将 block 以链表的形式串起来 */
            for ( int i = 0; i < blockCount - 1; i++ ) {
                Block* block = ( Block* ) (( char* ) chunk->blocks + blockSize * i);
                Block* next  = ( Block* ) (( char* ) chunk->blocks + blockSize * (i + 1));

                block->sizeIndex = index;
                block->next  = next;
            }
            /* 将最后一个 block 的 next 指向空结点,表示这是最后一个 block */
            Block* lastBlock = ( Block* ) (( char* ) chunk->blocks + blockSize * (blockCount - 1));
            lastBlock->sizeIndex = index;
            lastBlock->next = nullptr;

            /* 将刚申请的 block 链表的第二块 block 保存到 pFreeLists 对应类型的数组中 */
            pFreeLists[index] = chunk->blocks->next;
            nChunkCount++;

            /* 返回刚申请的 block 链表的第一块 block */
            return (( int* ) chunk->blocks + 1);
        }
    }

根据申请内存的大小获取 Block 类型的索引 index,然后通过 index 查找表 pFreeLists:

  1、存在未使用 Block,返回 Block,并使 pFreeLists 指向下一个未使用的 Block。

  2、不存在未使用 Block,申请一块 Chunk,分割 Chunk 为 Blocks,返回首 Block,并使 pFreeLists 指向下一个未使用的 Block。

 

值得注意的是:Block 内存的前四个字节是用来储存 Block 类型的信息 sizeIndex,所以在返回 Block 内存地址的时候,向后偏移了 4 个字节。通过申请内存大小索引 Block 类型时已经将 size 多添加了 sizeof(int) 个字节的大小。

 

当使用完 Block 后并返还给 BlockAllocator 时,需要知道当前 Block 的类型才能正确添加 Block 到对应类型的未使用链表中,所以前面要用四个字节的大小储存器类型信息 sizeIndex:

    void BlockAllocator::free(void* ptr)
    {
        int* data = ( int* ) ptr - 1;
        int index = data[0];

        if ( index == UNKNOWN_MEMORY ) {
            ::free(data);
            return;
        }

        /* 根据内存大小获取 block 类型的索引值,并判断是否有效 */
        assert(0 <= index && index < BLOCK_TYPE_COUNT);
        int size = blockTypeSizeTable[index];

        /* 用头插法将 block 插到 pFreeLists[index] 指向的 block 链表中去 */
        Block* block = ( Block* ) data;
        block->next = pFreeLists[index];
        pFreeLists[index] = block;
    }

通过返回的地址向前偏移四个字节,获取 Block 类型信息,然后插入到未使用 Block 链表中。

当申请的内存过大时,BlockAllocator 会直接使用 malloc 函数分配内存(没有合适大小的 Block),并标记为 UNKNOWN_MEMERY。所以在释放时会调用 free 函数释放。

通过 BlockAllocator 可以实现顶点数据的内存管理。

 

基于opengl编写一个简易的2d渲染框架-04绘制图片

阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/   过程简述:利用FreeImage库加载图像数据,再创建OpenGL纹理,通过Canvas2D画布绘制,最后又Renderer渲染器渲染   本来想用soil库... 查看详情

基于opengl编写一个简易的2d渲染框架-09重构渲染器-shader

  Shader只是进行一些简单的封装,主要功能:    1、编译着色程序    2、绑定Uniform数据    3、根据着色程序的顶点属性传递顶点数据到GPU   着色程序的编译GLuintShader::createShaderProgram(constchar*vsname,constchar*p... 查看详情

基于opengl编写一个简易的2d渲染框架-05渲染文本

阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤:获取要绘制的字符的Unicode码,使用FreeType库获取对应的位图数据,添加到字符表中(后面同样的字符可以再表中... 查看详情

基于opengl编写一个简易的2d渲染框架02——搭建opengl环境

由于没有使用GLFW库,接下来得费一番功夫。阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20Creating%20a%20window/ 以下,我摘取了一点片段 Windows上的OpenGL库  如果你是Windows平台,opengl32.l... 查看详情

基于opengl编写一个简易的2d渲染框架-13使用例子

 这是重构渲染器的最后一部分了,将会给出一个demo,测试模板测试、裁剪测试、半透明排序等等: 上图是本次demo的效果图,中间的绿色图形展现的是模板测试。 模板测试voidinit(Pass*&p1,Pass*&p2){p1=newPass;p2=newPass;... 查看详情

基于opengl编写一个简易的2d渲染框架-08重构渲染器-整体架构

  事实上,前面编写的渲染器Renderer非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求。  当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式。所以同时渲染粒子系统和... 查看详情

基于opengl编写一个简易的2d渲染框架-11重构渲染器-renderer

 假如要渲染一个纯色矩形在窗口上,应该怎么做?先确定顶点的格式,一个顶点应该包含位置信息vec3以及颜色信息vec4,所以顶点的结构体定义可以这样:structVertex{Vec3position;Vec4color;};然后填充矩形四个顶点是数据信息:Verte... 查看详情

基于opengl编写一个简易的2d渲染框架-07鼠标事件和键盘事件

这次为程序添加鼠标事件和键盘事件   当检测到鼠标事件和键盘事件的信息时,捕获其信息并将信息传送到需要信息的对象处理。为此,需要一个可以分派信息的对象,这个对象能够正确的把信息交到正确的对象。 实... 查看详情

OpenGL 2d矩形没有被渲染

】OpenGL2d矩形没有被渲染【英文标题】:OpenGL2drectanglenotbeingrendered【发布时间】:2016-12-2200:07:43【问题描述】:我正在尝试在屏幕上渲染一个矩形。程序运行时,只显示清晰的颜色,没有矩形显示。代码如下:glClearColor(0.0,0.0,0.0,... 查看详情

OpenGL 2D像素完美渲染

】OpenGL2D像素完美渲染【英文标题】:OpenGL2Dpixelperfectrendering【发布时间】:2013-12-2115:04:10【问题描述】:我正在尝试渲染2D图像,以便它完全覆盖整个窗口。对于我的测试,我设置了一个窗口,使客户区正好是320x240,纹理也是... 查看详情

基于opengles的深度学习框架编写

基于OpenGLES的深度学习框架编写背景与工程定位背景项目组基于深度学习实现了视频风格化和人像抠图的功能,但这是在PC/服务端上跑的,现在需要移植到移动端,因此需要一个移动端的深度学习的计算框架。同类型的库caffe-andr... 查看详情

OpenGL 3.3 2D 渲染:VAO 配置不正确?

】OpenGL3.32D渲染:VAO配置不正确?【英文标题】:OpenGL3.32DRendering:VAOnotproperlyconfigured?【发布时间】:2014-12-1105:12:26【问题描述】:添加VAO后,我似乎无法让这个OpenGL程序渲染四边形。假设程序正确初始化没有错误,下面是填充VAO... 查看详情

opengl绘制三角形(代码片段)

...对象:ElementBufferObject,EBO或IndexBufferObject,IBO渲染管线在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。3D坐标转为2D坐标的处理过程是由Ope... 查看详情

OpenGL:如何在 3d 模式下优化多层相互重叠的 2d 渲染?

】OpenGL:如何在3d模式下优化多层相互重叠的2d渲染?【英文标题】:OpenGL:Howtooptimize2drenderingwithmultiplelayersoverlappingeachotherin3dmode?【发布时间】:2011-06-0612:36:31【问题描述】:我知道如何通过简单地首先渲染最近的平面来加速3d渲... 查看详情

导出对象渲染opengl

】导出对象渲染opengl【英文标题】:exportobjectrenderopengl【发布时间】:2012-12-1710:22:51【问题描述】:我有一个格式为.obj的CAD文件,我想使用旋转平移进行渲染,并为我的不同对象获取2D位置。我使用C/C++和OPENGL。使用库assimp,我... 查看详情

opengl工作流程

  在OpenGL中,一切事物都在3D空间中,但我们的屏幕坐标确实2D像素数组,OpenGL大部分工作就是把3D坐标转换成适应屏幕的2D像素。3D坐标转换成2D屏幕坐标的过程是有OpenGL的图形渲染管线管理的。图形渲染管线的工作可以被划分... 查看详情

opengl-渲染流程

参考技术A在OpenGL中,任何事物都处于3D空间中,而屏幕和窗口却都是2D像素数组,这就导致了OpenGL大部分工作都是关于把3D坐标转变为适配你屏幕的2D像素,3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(指的是一堆原始图... 查看详情

使用 opencl 将软件渲染到 opengl 2d 视图 [关闭]

】使用opencl将软件渲染到opengl2d视图[关闭]【英文标题】:softwarerenderingwithopenclontoopengl2dview[closed]【发布时间】:2011-07-0606:59:44【问题描述】:我想构建一个小的软件渲染库,因为我喜欢体素的想法,以及其他可能的替代渲染方... 查看详情