OpenGLE_VAO局部影响vs全局影响

全局顶点属性数组使能状态

glEnableVertexAttribArrayglDisableVertexAttribArray 确实是全局状态。这意味着在不使用 VAO 的情况下,启用或禁用特定的顶点属性数组会影响所有后续的绘制调用,直到该状态被改变。例如:

1
2
3
4
5
6
7
8
9
10
11
// 启用顶点属性数组索引 0
glEnableVertexAttribArray(0);

// 进行绘制调用,使用索引 0 的顶点属性数组
glDrawArrays(GL_TRIANGLES, 0, 3);

// 禁用顶点属性数组索引 0
glDisableVertexAttribArray(0);

// 再次进行绘制调用,此时索引 0 的顶点属性数组将不会被使用
glDrawArrays(GL_TRIANGLES, 0, 3);

顶点数组对象(VAO)

当使用 VAO 时,glEnableVertexAttribArrayglVertexAttribPointer 等顶点属性相关的状态是存储在 VAO 中的。这意味着当你绑定一个 VAO 时,它会恢复之前存储的所有顶点属性状态,包括哪些属性数组是启用的。这样,每个 VAO 可以拥有自己独立的顶点属性配置。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建并绑定第一个 VAO
GLuint vao1;
glGenVertexArrays(1, &vao1);
glBindVertexArray(vao1);

// 设置顶点属性指针和启用顶点属性数组
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(0);

// 解绑 VAO
glBindVertexArray(0);

// 创建并绑定第二个 VAO
GLuint vao2;
glGenVertexArrays(1, &vao2);
glBindVertexArray(vao2);

// 设置不同的顶点属性指针和启用顶点属性数组
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void*)0);
glEnableVertexAttribArray(1);

// 解绑 VAO
glBindVertexArray(0);

// 使用第一个 VAO 进行绘制
glBindVertexArray(vao1);
glDrawArrays(GL_TRIANGLES, 0, 3);

// 使用第二个 VAO 进行绘制
glBindVertexArray(vao2);
glDrawArrays(GL_TRIANGLES, 0, 3);

在这个例子中:

  • vao1 存储了顶点属性索引 0 的配置和使能状态。
  • vao2 存储了顶点属性索引 1 的配置和使能状态。

绑定 vao1 会恢复其配置和状态,绑定 vao2 则会恢复其自身的配置和状态。

总结

  • 全局顶点属性数组使能状态:在不使用 VAO 时,glEnableVertexAttribArrayglDisableVertexAttribArray 对所有后续绘制调用生效,直到状态被改变。
  • 顶点数组对象(VAO):存储了顶点属性相关的所有状态,包括使能状态。当绑定 VAO 时,会恢复之前存储的顶点属性配置和使能状态,使得每个 VAO 拥有独立的顶点属性配置。

使用 VAO 是管理复杂场景和多个对象的最佳实践,因为它简化了状态管理,并且可以提高渲染性能。

OpenGLES设置顶点属性的默认值

在 OpenGL ES 中,设置顶点属性的默认值和通过顶点缓冲对象(VBO)上传顶点属性是两种不同的处理顶点属性的方法。以下是详细的解释:

设置顶点属性默认值

在 OpenGL ES 中,可以使用 glVertexAttrib4f(或其他类似的函数)来设置顶点属性的默认值。这些函数允许您为指定的顶点属性索引设置一个常量值,而不必为每个顶点提供一个值。例如:

1
2
// 设置索引为 0 的顶点属性的默认值为 (1.0, 0.0, 0.0, 1.0)
glVertexAttrib4f(0, 1.0f, 0.0f, 0.0f, 1.0f);

请注意,这种方法直接在顶点着色器中使用常量值,因此适用于需要相同属性值的所有顶点的情况。

通过 VBO 上传顶点属性

VBO 是一种在图形硬件上存储顶点数据的高效方式。使用 VBO 可以提高渲染性能,因为数据存储在 GPU 内存中,而不是通过每次绘制调用从 CPU 内存传输数据。以下是使用 VBO 上传顶点属性的步骤:

  1. 生成和绑定 VBO

    1
    2
    3
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
  2. 上传顶点数据

    1
    2
    3
    4
    5
    6
    7
    GLfloat vertices[] = {
    // 顶点数据,假设每个顶点包含 3 个浮点数 (x, y, z)
    0.0f, 0.0f, 0.0f,
    1.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  3. 设置顶点属性指针

    1
    2
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0);
    glEnableVertexAttribArray(0);
  4. 绘制

    1
    glDrawArrays(GL_TRIANGLES, 0, 3);

性能差别

  • 设置顶点属性默认值:这种方法适用于所有顶点共享相同属性值的情况。其优点是简单直接,不需要额外的内存传输。然而,它缺乏灵活性,只能应用于特定的简单场景。

  • 通过 VBO 上传顶点属性:这种方法更为灵活,可以为每个顶点设置不同的属性值。因为数据存储在 GPU 内存中,所以渲染性能通常更高,特别是在处理大量顶点数据时。这种方法适用于大多数复杂的渲染任务。

总结来说,如果您的应用场景中所有顶点都共享相同的属性值,可以使用设置默认值的方法。如果不同顶点有不同的属性值,并且您希望获得更好的性能,应使用 VBO。

AddressSanitizer实现原理

AddressSanitizer(简称ASan)是一种内存错误检测工具,用于捕捉诸如缓冲区溢出、使用已释放内存、堆栈溢出等内存错误。ASan在编译时和运行时对程序进行处理,以检测和报告内存相关的错误。以下是AddressSanitizer的实现原理的详细解释:

1. 编译时插桩

ASan在编译时对代码进行插桩,插入额外的检查代码。这些检查代码会在每次内存访问时执行,以验证访问是否合法。具体过程包括:

  • 插入检查代码:在每次内存分配、释放和访问操作前后插入检查代码。
  • 标记内存区域:在内存分配和释放时,更新内存区域的元数据,以记录该区域的状态(已分配、已释放等)。

2. Shadow Memory

ASan使用影子内存(Shadow Memory)来记录主内存的状态。影子内存是主内存的一个映射区域,每个字节的影子内存对应主内存的几个字节(通常是8字节)。影子内存的值用于指示主内存的哪些部分是合法访问的,哪些部分是非法的。

  • 影子内存布局:影子内存占用主内存的1/8,因为每个影子字节代表8个主内存字节。
  • 影子内存值:影子内存中的值表示主内存的状态。例如,0表示所有8个字节都是合法的,负值表示不合法的访问,正值表示部分合法访问。

3. 内存分配和释放

ASan对内存分配和释放函数(如mallocfree)进行重载,以维护内存区域的元数据。这些元数据包括:

  • 红色区域(Redzones):在每个分配的内存块周围添加红色区域,用于检测缓冲区溢出。红色区域被标记为非法访问区域。
  • 元数据:记录每个内存块的大小、分配堆栈等信息,以便在检测到错误时提供详细报告。

4. 运行时检查

在运行时,ASan插入的检查代码会在每次内存访问时执行,检查访问的地址是否在合法范围内。如果访问非法区域,则报告错误并终止程序。

  • 内存访问检查:每次内存读取或写入时,检查影子内存中的值,以确定访问是否合法。
  • 错误报告:在检测到非法访问时,ASan会生成详细的错误报告,包含访问地址、内存块信息、调用堆栈等。

5. 错误类型检测

ASan能够检测多种类型的内存错误,包括:

  • 缓冲区溢出:访问数组或内存块的边界外的区域。
  • 使用未初始化内存:访问未初始化的内存。
  • 使用已释放内存:访问已经释放的内存。
  • 双重释放:重复释放同一块内存。
  • 堆栈溢出:访问栈帧之外的内存。

6. 性能与开销

ASan在检测内存错误时会引入一定的性能开销和内存开销:

  • 性能开销:由于插入了额外的检查代码,程序的执行速度会有所降低。通常,ASan引入的性能开销在2-3倍左右。
  • 内存开销:影子内存和红色区域会增加内存使用量,通常内存开销在2倍左右。

总结

AddressSanitizer通过编译时插桩、使用影子内存和运行时检查等机制,能够有效地检测和报告内存相关的错误。尽管引入了一定的性能和内存开销,但它在提高程序安全性和稳定性方面具有重要意义,是开发和调试C/C++程序的强大工具。

pthread_create设置栈size

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

void* threadFunction(void* arg) {
// 线程的执行代码
printf("Thread is running\n");
return NULL;
}

int main() {
pthread_t thread;
pthread_attr_t attr;
size_t stackSize = 2 * 1024 * 1024; // 设置栈大小为2 MiB

// 初始化线程属性
if (pthread_attr_init(&attr) != 0) {
perror("pthread_attr_init");
return EXIT_FAILURE;
}

// 设置线程栈大小
if (pthread_attr_setstacksize(&attr, stackSize) != 0) {
perror("pthread_attr_setstacksize");
return EXIT_FAILURE;
}

// 创建线程
if (pthread_create(&thread, &attr, threadFunction, NULL) != 0) {
perror("pthread_create");
return EXIT_FAILURE;
}

// 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("pthread_join");
return EXIT_FAILURE;
}

// 销毁线程属性
if (pthread_attr_destroy(&attr) != 0) {
perror("pthread_attr_destroy");
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}

使用__asan_unpoison_memory_region屏蔽某处内存检查

__asan_unpoison_memory_region 是 AddressSanitizer (ASan) 库中的一个函数。ASan 是一个用于检测内存错误的工具,主要用于 C 和 C++ 程序开发者。__asan_unpoison_memory_region 的作用是标记一段内存区域为“未污染”状态,这意味着这段内存可以被访问且不会触发 ASan 的错误报告。

具体来说,这个函数通常用于以下场景:

  1. 在内存被分配后但未初始化之前,标记该区域为未污染,以便在初始化期间可以安全访问。
  2. 当程序知道某段内存区域将被合法访问时,预先标记该区域为未污染,以避免误报。

函数原型通常如下:

1
void __asan_unpoison_memory_region(void *addr, size_t size);

参数解释:

  • addr:内存区域的起始地址。
  • size:内存区域的大小(以字节为单位)。

通过调用这个函数,开发者可以更精细地控制 ASan 的内存监控行为,减少误报,提高调试效率。

UTF编码内存角度比较.md

UTF-8、UTF-16 和 UTF-32 是三种不同的 Unicode 编码方式,它们在表示字符时占用的字节数各不相同。具体如下:

  1. UTF-8

    • UTF-8 是一种可变长度的编码方式,每个字符占用 1 到 4 个字节。
    • 具体字节数取决于字符的 Unicode 码点:
      • U+0000 至 U+007F(基本拉丁字母)占 1 个字节。
      • U+0080 至 U+07FF 占 2 个字节。
      • U+0800 至 U+FFFF 占 3 个字节。
      • U+10000 至 U+10FFFF 占 4 个字节。
  2. UTF-16

    • UTF-16 也是一种可变长度的编码方式,每个字符占用 2 或 4 个字节。
    • 具体字节数取决于字符的 Unicode 码点:
      • U+0000 至 U+FFFF(基本多语言平面,BMP)占 2 个字节。
      • U+10000 至 U+10FFFF(辅助平面)占 4 个字节(使用一对代理项,即高位代理项和低位代理项,每个占 2 个字节)。
  3. UTF-32

    • UTF-32 是一种固定长度的编码方式,每个字符占用 4 个字节。
    • 无论字符的 Unicode 码点是多少,每个字符始终占用 4 个字节。

总结:

  • UTF-8:1 到 4 个字节,具体取决于字符。
  • UTF-16:2 或 4 个字节,具体取决于字符。
  • UTF-32:固定 4 个字节。

这三种编码方式各有优缺点。UTF-8 是最常用的编码方式,因其对 ASCII 字符的高效编码(仅占 1 个字节),节省空间且向后兼容 ASCII。UTF-16 在处理基本多语言平面字符时相对高效,但对于包括大量辅助平面字符的文本,可能会占用更多空间。UTF-32 最简单,但由于每个字符固定占用 4 个字节,通常会占用更多的存储空间。

tag dispatch

对于short类型来说,会优先匹配通用引用版本的重载,导致无法构造string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace TagDispatch {

template <typename T>
void fun(T&& params) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "called fun(T&& params)" << std::endl;
}

void fun(int a) {
std::cout << "called fun(int a)" << std::endl;
}

template <typename T>
void fwd(T&& params) {
fun(std::forward<T>(params));
}

void test()
{
fwd("abc"); //suc
int a = 0;
fwd(a); //suc

short b = 10;
//fwd(b); //failed, 匹配到了通用引用函数

}

int main(int argc, const char * argv[]) {
TagDispatch::test();

return 0;
}

}

使用TagDispath,来解决匹配的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace TagDispatch {

template <typename T>
void fun2(T&& params, std::false_type) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "called fun(T&& params)" << std::endl;
}

void fun2(int a, std::true_type) {
std::cout << "called fun(int a)" << std::endl;
}

template <typename T>
void fwd(T&& params) {
fun2(std::forward<T>(params), std::is_integral<typename std::remove_reference<T>::type>());
}

void test()
{
fwd("abc"); //suc
int a = 0;
fwd(a); //suc

short b = 10;
fwd(b); //succeed, 匹配到了通用引用函数
}

}

针对函数的构造函数,使用通用引用重载,主要解决思路是通过enable_if来限制模板的匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Person
{
public:
template <
typename T,
typename = std::enable_if_t<
!std::is_base_of<Person, std::decay_t<T>>::value // 防止调用拷贝和移动构造函数,并且考虑了子类
&& // decay,移除指针引用和cv修饰符
!std::is_integral<std::remove_reference_t<T>>::value
>
>
explicit Person(T&& params) {
std::vector<std::string> temp;
temp.emplace_back(std::forward<T>(params));
std::cout << "Person(T&& params)" << std::endl;
}

explicit Person(int a) {
std::cout << "Person(int a)" << std::endl;
}
};

void testCtrOverload() {

short b = 10;
Person person(b);

}

}