基于OpenGL编写一个简易的2D渲染框架-12 重构渲染器-BlockAllocator
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 = ;
for ( int i = ; 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 == ) return ;
assert(size > ); /* 使用四个字节记录 block 的类型索引,free 是使用 */
size += sizeof(int); /* 申请的空间大于规定的最大值,直接申请,不放到块的链表中去 */
if ( size > MAX_BLOCK_SIZE ) {
int* data = ( int* ) malloc(size);
/* -1 表示这是直接分配的内存 */
data[] = UNKNOWN_MEMORY;
return (data + );
} int index = blockTypeSizeLookup[size];
assert( <= index && index < BLOCK_TYPE_COUNT); /* 存在同类型的未被使用的内存块?返回内存块 */
if ( pFreeLists[index] ) {
/* 使块头指针指向新的未被使用的 block */
Block* block = pFreeLists[index];
pFreeLists[index] = block->next;
return (( int* ) block + );
}
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, , 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 = ; i < blockCount - ; i++ ) {
Block* block = ( Block* ) (( char* ) chunk->blocks + blockSize * i);
Block* next = ( Block* ) (( char* ) chunk->blocks + blockSize * (i + )); block->sizeIndex = index;
block->next = next;
}
/* 将最后一个 block 的 next 指向空结点,表示这是最后一个 block */
Block* lastBlock = ( Block* ) (( char* ) chunk->blocks + blockSize * (blockCount - ));
lastBlock->sizeIndex = index;
lastBlock->next = nullptr; /* 将刚申请的 block 链表的第二块 block 保存到 pFreeLists 对应类型的数组中 */
pFreeLists[index] = chunk->blocks->next;
nChunkCount++; /* 返回刚申请的 block 链表的第一块 block */
return (( int* ) chunk->blocks + );
}
}
根据申请内存的大小获取 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 - ;
int index = data[]; if ( index == UNKNOWN_MEMORY ) {
::free(data);
return;
} /* 根据内存大小获取 block 类型的索引值,并判断是否有效 */
assert( <= 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渲染框架-12 重构渲染器-BlockAllocator的更多相关文章
- 基于OpenGL编写一个简易的2D渲染框架-05 渲染文本
阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤: 获 ...
- 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统
在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...
- 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口
最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...
- 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构
事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...
- 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片
阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...
- 基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader
Shader 只是进行一些简单的封装,主要功能: 1.编译着色程序 2.绑定 Uniform 数据 3.根据着色程序的顶点属性传递顶点数据到 GPU 着色程序的编译 GLuint Shader::cr ...
- 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形
阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...
- 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境
由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...
- 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer
假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...
- 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass
Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...
随机推荐
- 通过HTTP协议发送远程消息
通过HTTP协议发送远程消息 MSMQ一般情况是通过tcp协议进行通讯,但如果遇到端口被禁用或防火墙,则通过HTTP协议发送消息是一个有效的解决办法. 通过HTTP协议发送消息到远程服务器 publi ...
- 【转】每天一个linux命令(25):linux文件属性详解
原文网址:http://www.cnblogs.com/peida/archive/2012/11/23/2783762.html Linux 文件或目录的属性主要包括:文件或目录的节点.种类.权限模 ...
- 洛谷4030(Codeplus11月月赛)可做题1
题目:https://www.luogu.org/problemnew/show/P4030 原来一个方阵巧妙的充要条件是该方阵的每个2*2子方阵都是巧妙的!!! 可以把每一行选的列视为一个排列,需要 ...
- Angular 4 路由守卫
路由守卫 只有当用户已经登录并拥有某些权限时才能进入某些路由 一个有多个表单组成的向导,如注册流程,用户只有在当前组件的组件中填写了满足要求的信息才可以导航到下一个路由 当用户未执行保存操作而试图离开 ...
- Linux系统Centos安装Python3.7
Linux下默认系统自带python2.7的版本,这个版本被系统很多程序所依赖,所以不建议删除,如果使用最新的Python3那么我们知道编译安装源码包和系统默认包之间是没有任何影响的,所以可以安装py ...
- PHP接口开发加密技术实例原理与例子
下面例子简单讲解PHP接口开发加密技术:如app要请求用户列表,api是“index.php?module=user&action=list”app生成token = md5sum (‘use ...
- TroubleShoot: Enable Developer Mode in Windows 10 Insider Preview Build 10074
There is a known issue in Windows 10 Insider Preview build 10074 (see here). Developers cannot enabl ...
- 豆瓣源安装python包
例如安装scrapy: pip install -i https://pypi.douban.com/simple/ scrapy
- linux 信号处理 四 (sigaction参数说明)
sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作). 他是POSIX的信号接口,而signal()是标准C的信号接口(如果程序必须在非POSIX系统上运行,那么就应该 ...
- ios之block笔记
目测和函数指针基本类似用法,贴个hello world,备用 typedef int (^TestBlock)(int val1,int val2); __block ;//这里加__block是为了 ...