在图形编程中,尤其是在使用现代图形 API(如 Vulkan、OpenGL ES、Metal)时,理解 CPU 侧的 API 编程部分和 GPU 侧的 Shader 编程部分是非常重要的。以下是详细解释和扩展:
1. 数据类型
1.1 简单数据结构
在 CPU 侧,API 使用的基本数据结构可以分为几类,主要包括简单数据结构、设备资源和提交相关的数据。
-
简单数据结构:
- 由于这些 API 通常基于 C++,因此可以使用 C++ 中的所有基本数据类型。不同的图形 API 可能会定义一些额外的数据类型,以便于跨平台的兼容性。
数据类型 Vulkan OpenGL ES 布尔值 VkBool32
/VK_TRUE
/VK_FALSE
GLboolean
地址 VkDeviceAddress
- 浮点数 float
/VkHalf
/VkFixed16
GLfloat
/GLclampf
整数 int
/uint32_t
GLint
/GLuint
1.2 设备(GPU 上的)资源
在现代图形 API 中,GPU 资源通常被视为句柄(handle),这使得资源管理更加灵活和高效。
-
OpenGL:
- 在 OpenGL 中,所有 GPU 资源(如纹理、缓冲区等)都使用
GLuint
类型的句柄来表示。
- 在 OpenGL 中,所有 GPU 资源(如纹理、缓冲区等)都使用
-
Vulkan:
- Vulkan 进一步将句柄封装为特定的类型,例如
VkBuffer
、VkImage
等。这种类型安全性使得 API 更加清晰和易于使用。
- Vulkan 进一步将句柄封装为特定的类型,例如
-
Metal:
- 在 Metal 中,资源被封装为具体的对象,例如
MtlBuffer
、MtlImage
。这种面向对象的设计使得资源管理更加直观。
- 在 Metal 中,资源被封装为具体的对象,例如
1.3 提交相关的数据
在现代图形 API 中,提交相关的数据结构是必不可少的。这些结构用于管理命令的提交和执行。
-
图形设备:
- 代表图形硬件的接口,例如
VkDevice
(Vulkan)和MTLDevice
(Metal)。
- 代表图形硬件的接口,例如
-
提交队列:
- 用于管理命令的执行顺序,例如
VkQueue
(Vulkan)和MTLCommandQueue
(Metal)。
- 用于管理命令的执行顺序,例如
-
提交指令:
- 用于存储和管理待执行的命令,例如
VkCommandBuffer
(Vulkan)和MTLCommandBuffer
(Metal)。
- 用于存储和管理待执行的命令,例如
2. 线程安全性
大多数现代图形 API 的数据结构和资源管理并不是线程安全的。这意味着在多线程环境中,开发者需要小心管理对这些资源的访问,以避免数据竞争和不一致性的问题。通常,开发者需要使用锁或其他同步机制来确保在多线程环境中对 GPU 资源的安全访问。
总结
在现代图形编程中,理解 CPU 侧的 API 编程部分是至关重要的。不同的图形 API 提供了不同的数据类型和资源管理方式,开发者需要根据具体的 API 特性来设计和实现图形应用程序。同时,注意线程安全性也是确保应用程序稳定性和性能的重要因素。
1.2对象创建
1. OpenGL ES 风格的创建 API
在 OpenGL ES 中,资源的创建和管理相对简单,API 设计上隐藏了许多底层细节。以下是创建对象的基本流程:
创建对象的步骤
-
生成句柄:
- 使用
glGenBuffers
等函数生成一个或多个对象的句柄(handle)。
GLuint id; glGenBuffers(1, &id); // 生成一个 buffer 的句柄
- 使用
-
绑定对象:
- 使用
glBindBuffer
等函数将生成的句柄绑定到实际的对象上。这一过程会创建对象的实体,并将其与句柄关联。
glBindBuffer(GL_ARRAY_BUFFER, id); // 创建 buffer 对象并绑定到句柄 id
- 使用
-
操作对象:
- 之后可以使用该句柄来操作对象,例如填充数据、设置属性等。
-
删除对象:
- 使用
glDeleteBuffers
等函数释放对象,回收句柄。
glDeleteBuffers(1, &id); // 释放 buffer 对象
- 使用
2. 高级 API(如 Vulkan 和 Metal)
在现代图形 API 中,资源的创建通常更加复杂,提供了更多的灵活性和性能优化选项。
直接创建
-
Vulkan:
- 使用
vkCreateXXX
函数直接创建对象,通常需要提供一个结构体(如VkBufferCreateInfo
)来描述对象的属性。
VkBuffer buffer; VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = bufferSize; bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; vkCreateBuffer(device, &bufferInfo, nullptr, &buffer); // 创建 buffer
- 使用
-
Metal:
- 使用类似的方式,通过设备对象直接创建资源。
id<MTLBuffer> buffer = [device newBufferWithLength:bufferSize options:MTLResourceStorageModeShared]; // 创建 buffer
从池创建
-
Vulkan:
- 先创建资源池(如
VkDescriptorPool
),然后从池中分配资源。这种方式通常性能更高,适合频繁创建和销毁的资源。
VkDescriptorPool pool; // 创建池的代码... VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = pool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &setLayout; vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet); // 从池中分配资源
- 先创建资源池(如
-
Metal:
- Metal 中的资源池称为 heap,首先需要创建一个 heap,然后从 heap 中分配资源。
id<MTLHeap> heap = [device newHeapWithDescriptor:heapDescriptor]; // 创建 heap id<MTLBuffer> buffer = [heap newBufferWithLength:bufferSize options:MTLResourceStorageModePrivate]; // 从 heap 创建 buffer
总结
在 OpenGL ES 中,资源的创建过程相对简单,主要通过生成句柄和绑定对象来实现。而在 Vulkan 和 Metal 等现代图形 API 中,资源的创建提供了更多的灵活性和性能优化选项,包括直接创建和从池创建的方式。开发者需要根据具体的应用需求和性能考虑来选择合适的资源创建方式。
在图形编程中,数据查询是一个重要的功能,允许开发者获取当前上下文或特定对象的状态信息。不同的图形 API 提供了不同的查询机制。以下是对 OpenGL ES、Metal 和 Vulkan 中数据查询的详细说明。
1. 数据查询
1.1 Metal
在 Metal 中,所有对象都有明确的数据结构,因此可以直接从这些对象中获取所需的信息。这种设计使得查询操作更加直观和高效。
-
示例:
- 获取缓冲区的大小:
NSUInteger bufferSize = [buffer length]; // 直接从 MTLBuffer 对象获取大小
-
获取其他属性:
- Metal 提供了丰富的 API 来查询对象的状态,例如获取纹理的宽度和高度、获取渲染管线的状态等。
1.2 OpenGL ES
在 OpenGL ES 中,查询操作通常依赖于全局 API。开发者可以使用一系列的 glGet*
函数来获取当前上下文的状态或特定对象的属性。
-
全局上下文查询:
- 使用
glGetBooleanv
、glGetIntegerv
、glGetFloatv
等函数查询当前上下文的状态。
GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); // 查询当前视口大小
- 使用
-
对象属性查询:
- OpenGL ES 提供了多种
GetXXX
类型的 API 来查询特定对象的属性,这些查询可能会显得有些杂乱。 - 示例:
- 查询顶点属性:
GLint vertexAttributeSize; glGetVertexAttribiv(attributeIndex, GL_VERTEX_ATTRIB_ARRAY_SIZE, &vertexAttributeSize);
- 查询缓冲区参数:
GLint bufferSize; glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &bufferSize);
- 查询着色器状态:
GLint shaderCompileStatus; glGetShaderiv(shader, GL_COMPILE_STATUS, &shaderCompileStatus);
- OpenGL ES 提供了多种
1.3 Vulkan
Vulkan 的设计哲学与 OpenGL ES 和 Metal 有所不同。Vulkan 不提供类似的查询接口,因为它的设计目标是让开发者对状态和数据的管理有更高的控制权。所有的数据和状态都是由应用程序自己管理的,Vulkan 认为开发者有能力记住这些数据。
-
状态管理:
- 在 Vulkan 中,开发者需要在创建资源时明确设置所有的状态信息,并在应用程序中维护这些状态。
- 例如,创建缓冲区时,开发者需要手动记录缓冲区的大小和其他属性。
-
示例:
- 在 Vulkan 中,开发者通常会在创建资源时将相关信息存储在自己的数据结构中,以便后续使用。
总结
数据查询在不同的图形 API 中有着不同的实现方式。Metal 提供了直接从对象中获取信息的方式,OpenGL ES 则依赖于全局 API 来查询状态,而 Vulkan 则强调开发者对状态的管理,避免了多余的查询接口。开发者需要根据所使用的 API 选择合适的查询方式,并在设计应用程序时考虑如何有效地管理和维护状态信息。