基于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 的混合模式,并使 ...
随机推荐
- streamsets 集成 cratedb 测试
我们可以集成crate 到streamsets 中可以实现强大的数据导入,数据分析能力. 演示的是进行csv 文件的解析并输出到cratedb 环境使用docker && docker ...
- BZOJ 1845三角形面积并
题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1845 给定100个三角形,求三角形面积并. 戴神模板太可怕.直接调用函数秒掉.思路 ...
- Oracle 10g RAC OCR 和 VotingDisk 的备份与恢复
Oracle RAC 中OCR 和Voting Disk 备份在我的blog: Oracle RAC 常用维护工具和命令 中已经有说明,现在再次把它单独拿出做一个说明, 因为OCR 和Voting D ...
- bzoj 3622 已经没有什么好害怕的了——二项式反演
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3622 令 f[i] 表示钦定 i 对 a[ ]>b[ ] 的关系的方案数:g[i] 表 ...
- 栈的一个实例——Dijkstra的双栈算术表达式求值法
Dijkstra的双栈算术表达式求值法,即是计算算术表达式的值,如表达式(1 + ( (2+3) * (4*5) ) ). 该方法是 使用两个栈分别存储算术表达式的运算符与操作数 忽略左括号 遇到右括 ...
- led的驱动及测试程序
一.驱动源码 #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> # ...
- BASIC-5_蓝桥杯_查找整数
示例代码: #include <stdio.h>#include <stdlib.h> int main(void){ int n = 0 , key = 0 , count ...
- 通过shell进行数学计算
对于基本运算,可以使用let, $(())和$[] 对于高级运算,使用expr和bc这两个工具 [hupeng@hupeng-vm shell]$n1= [hupeng@hupeng-vm shell ...
- poj3190 Stall Reservations (贪心+优先队列)
Cleaning Shifts Time Limit : 2000/1000ms (Java/Other) Memory Limit : 131072/65536K (Java/Other) To ...
- Bootstrap:百科
ylbtech-Bootstrap:百科 Bootstrap (Web框架) Bootstrap,来自 Twitter,是目前很受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.Java ...