38.1 概述
OpenCL 与 OpenGL ES 的互操作(cl_khr_gl_event 扩展)允许 OpenCL 在移动和嵌入式平台上与 OpenGL ES 共享图形资源。OpenGL ES 是 OpenGL 的子集,专为资源受限的设备设计,广泛应用于移动设备、游戏机和嵌入式系统。
38.1.1 与 OpenGL 互操作的区别
| 特性 |
OpenGL |
OpenGL ES |
| 目标平台 |
桌面系统 |
移动/嵌入式设备 |
| API 复杂度 |
完整 |
精简子集 |
| 扩展名称 |
cl_khr_gl_sharing |
cl_khr_gl_sharing |
| 上下文创建 |
GLX/WGL/CGL |
EGL |
| 支持的对象 |
更全面 |
核心对象 |
38.1.2 支持的 GLES 版本
- OpenGL ES 2.0:基本的着色器支持
- OpenGL ES 3.0:纹理数组、3D 纹理、变换反馈
- OpenGL ES 3.1:计算着色器、原子计数器
- OpenGL ES 3.2:几何着色器、细分着色器
38.2 EGL 上下文创建
38.2.1 EGL 初始化
c
复制代码
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <CL/cl.h>
#include <CL/cl_gl.h>
// EGL 上下文结构
typedef struct {
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
} EGLContext_t;
// 初始化 EGL 并创建 GLES 上下文
EGLContext_t* create_egl_context(int width, int height) {
EGLContext_t *egl_ctx = (EGLContext_t*)malloc(sizeof(EGLContext_t));
// 获取默认显示
egl_ctx->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (egl_ctx->display == EGL_NO_DISPLAY) {
printf("Failed to get EGL display\n");
return NULL;
}
// 初始化 EGL
EGLint major, minor;
if (!eglInitialize(egl_ctx->display, &major, &minor)) {
printf("Failed to initialize EGL\n");
return NULL;
}
printf("EGL version: %d.%d\n", major, minor);
// 选择配置
EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};
EGLint num_configs;
if (!eglChooseConfig(egl_ctx->display, config_attribs,
&egl_ctx->config, 1, &num_configs)) {
printf("Failed to choose EGL config\n");
return NULL;
}
// 创建 Pbuffer 表面
EGLint pbuffer_attribs[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE
};
egl_ctx->surface = eglCreatePbufferSurface(egl_ctx->display,
egl_ctx->config,
pbuffer_attribs);
if (egl_ctx->surface == EGL_NO_SURFACE) {
printf("Failed to create EGL surface\n");
return NULL;
}
// 创建 GLES 3.0 上下文
EGLint context_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
egl_ctx->context = eglCreateContext(egl_ctx->display,
egl_ctx->config,
EGL_NO_CONTEXT,
context_attribs);
if (egl_ctx->context == EGL_NO_CONTEXT) {
printf("Failed to create EGL context\n");
return NULL;
}
// 激活上下文
eglMakeCurrent(egl_ctx->display, egl_ctx->surface,
egl_ctx->surface, egl_ctx->context);
return egl_ctx;
}
38.2.2 从 EGL 上下文创建 CL 上下文
c
复制代码
// 从 EGL 上下文创建 CL 上下文
cl_context create_cl_context_from_egl(cl_platform_id platform,
cl_device_id device,
EGLContext_t *egl_ctx) {
cl_int err;
// 设置上下文属性
cl_context_properties properties[] = {
CL_GL_CONTEXT_KHR, (cl_context_properties)egl_ctx->context,
CL_EGL_DISPLAY_KHR, (cl_context_properties)egl_ctx->display,
CL_CONTEXT_PLATFORM, (cl_context_properties)platform,
0
};
// 创建 CL 上下文
cl_context context = clCreateContext(properties, 1, &device,
NULL, NULL, &err);
if (err != CL_SUCCESS) {
printf("Failed to create CL context: %d\n", err);
return NULL;
}
return context;
}
// 从 EGL 获取合适的 CL 设备
cl_device_id get_device_from_egl(cl_platform_id platform,
EGLContext_t *egl_ctx) {
cl_device_id device;
cl_int err;
cl_context_properties properties[] = {
CL_GL_CONTEXT_KHR, (cl_context_properties)egl_ctx->context,
CL_EGL_DISPLAY_KHR, (cl_context_properties)egl_ctx->display,
CL_CONTEXT_PLATFORM, (cl_context_properties)platform,
0
};
// 获取 clGetGLContextInfoKHR 函数指针
clGetGLContextInfoKHR_fn clGetGLContextInfoKHR =
(clGetGLContextInfoKHR_fn)clGetExtensionFunctionAddressForPlatform(
platform, "clGetGLContextInfoKHR");
if (!clGetGLContextInfoKHR) {
printf("clGetGLContextInfoKHR not available\n");
return NULL;
}
// 获取当前 GL 上下文的设备
err = clGetGLContextInfoKHR(properties,
CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR,
sizeof(cl_device_id), &device, NULL);
if (err != CL_SUCCESS) {
printf("Failed to get device from GL context: %d\n", err);
return NULL;
}
return device;
}
38.3 GLES 缓冲区互操作
38.3.1 创建和共享 GLES 缓冲区
c
复制代码
// 创建 GLES 缓冲区
GLuint create_gles_buffer(size_t size, void *data) {
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 检查 GL 错误
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("GL error in buffer creation: 0x%x\n", err);
return 0;
}
return buffer;
}
// 测试 GLES 缓冲区互操作
int test_gles_buffer(cl_context context, cl_command_queue queue,
size_t buffer_size) {
cl_int err;
GLuint gl_buffer;
cl_mem cl_buffer;
// 创建测试数据
float *data = (float*)malloc(buffer_size);
for (size_t i = 0; i < buffer_size / sizeof(float); i++) {
data[i] = (float)i;
}
// 创建 GLES 缓冲区
gl_buffer = create_gles_buffer(buffer_size, data);
if (gl_buffer == 0) {
free(data);
return TEST_FAIL;
}
// 从 GL 缓冲区创建 CL 对象
cl_buffer = clCreateFromGLBuffer(context, CL_MEM_READ_WRITE,
gl_buffer, &err);
if (err != CL_SUCCESS) {
printf("clCreateFromGLBuffer failed: %d\n", err);
glDeleteBuffers(1, &gl_buffer);
free(data);
return TEST_FAIL;
}
// 创建处理内核
const char *kernel_source =
"__kernel void scale_buffer(__global float *buf, float factor) {\n"
" int gid = get_global_id(0);\n"
" buf[gid] *= factor;\n"
"}\n";
cl_program program = clCreateProgramWithSource(context, 1,
&kernel_source, NULL, &err);
clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "scale_buffer", &err);
// 获取 GL 对象
err = clEnqueueAcquireGLObjects(queue, 1, &cl_buffer, 0, NULL, NULL);
if (err != CL_SUCCESS) {
printf("clEnqueueAcquireGLObjects failed: %d\n", err);
return TEST_FAIL;
}
// 执行内核
float factor = 2.0f;
clSetKernelArg(kernel, 0, sizeof(cl_mem), &cl_buffer);
clSetKernelArg(kernel, 1, sizeof(float), &factor);
size_t global_size = buffer_size / sizeof(float);
err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size,
NULL, 0, NULL, NULL);
// 释放 GL 对象
err = clEnqueueReleaseGLObjects(queue, 1, &cl_buffer, 0, NULL, NULL);
clFinish(queue);
// 验证结果
float *result = (float*)malloc(buffer_size);
glBindBuffer(GL_ARRAY_BUFFER, gl_buffer);
glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, result);
glBindBuffer(GL_ARRAY_BUFFER, 0);
int pass = 1;
for (size_t i = 0; i < buffer_size / sizeof(float); i++) {
if (fabs(result[i] - data[i] * factor) > 0.001f) {
printf("Mismatch at index %zu: expected %f, got %f\n",
i, data[i] * factor, result[i]);
pass = 0;
break;
}
}
// 清理
free(data);
free(result);
clReleaseMemObject(cl_buffer);
clReleaseKernel(kernel);
clReleaseProgram(program);
glDeleteBuffers(1, &gl_buffer);
return pass ? TEST_PASS : TEST_FAIL;
}
38.3.2 顶点缓冲区对象(VBO)
c
复制代码
// 测试 VBO 互操作
int test_gles_vbo(cl_context context, cl_command_queue queue,
int num_vertices) {
cl_int err;
GLuint vbo;
cl_mem cl_vbo;
// 创建顶点数据
typedef struct {
float x, y, z;
float nx, ny, nz;
} Vertex;
size_t buffer_size = num_vertices * sizeof(Vertex);
Vertex *vertices = (Vertex*)malloc(buffer_size);
// 初始化顶点
for (int i = 0; i < num_vertices; i++) {
vertices[i].x = (float)i;
vertices[i].y = (float)i * 2.0f;
vertices[i].z = 0.0f;
vertices[i].nx = 0.0f;
vertices[i].ny = 0.0f;
vertices[i].nz = 1.0f;
}
// 创建 VBO
vbo = create_gles_buffer(buffer_size, vertices);
// 从 VBO 创建 CL 对象
cl_vbo = clCreateFromGLBuffer(context, CL_MEM_READ_WRITE, vbo, &err);
test_error(err, "clCreateFromGLBuffer failed");
// CL 处理顶点(例如:物理模拟)
const char *kernel_source =
"typedef struct {\n"
" float x, y, z;\n"
" float nx, ny, nz;\n"
"} Vertex;\n"
"\n"
"__kernel void update_vertices(__global Vertex *vertices, float time) {\n"
" int gid = get_global_id(0);\n"
" vertices[gid].y += sin(time + vertices[gid].x * 0.1f) * 0.1f;\n"
"}\n";
cl_program program = clCreateProgramWithSource(context, 1,
&kernel_source, NULL, &err);
clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "update_vertices", &err);
// 模拟循环
for (int frame = 0; frame < 10; frame++) {
float time = frame * 0.1f;
// 获取 GL 对象
clEnqueueAcquireGLObjects(queue, 1, &cl_vbo, 0, NULL, NULL);
// 更新顶点
clSetKernelArg(kernel, 0, sizeof(cl_mem), &cl_vbo);
clSetKernelArg(kernel, 1, sizeof(float), &time);
size_t global_size = num_vertices;
clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size,
NULL, 0, NULL, NULL);
// 释放 GL 对象
clEnqueueReleaseGLObjects(queue, 1, &cl_vbo, 0, NULL, NULL);
clFinish(queue);
// GL 渲染顶点
// ... 渲染代码 ...
}
// 清理
free(vertices);
clReleaseMemObject(cl_vbo);
clReleaseKernel(kernel);
clReleaseProgram(program);
glDeleteBuffers(1, &vbo);
return TEST_PASS;
}
38.4 GLES 纹理互操作
38.4.1 2D 纹理测试
c
复制代码
// 创建 GLES 2D 纹理
GLuint create_gles_texture_2d(int width, int height, void *data) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("GL error in texture creation: 0x%x\n", err);
return 0;
}
return texture;
}
// 测试 2D 纹理互操作
int test_gles_images_2D(cl_context context, cl_command_queue queue,
int width, int height) {
cl_int err;
GLuint gl_texture;
cl_mem cl_image;
// 创建测试纹理
size_t image_size = width * height * 4;
unsigned char *data = (unsigned char*)malloc(image_size);
for (size_t i = 0; i < image_size; i++) {
data[i] = i % 256;
}
gl_texture = create_gles_texture_2d(width, height, data);
if (gl_texture == 0) {
free(data);
return TEST_FAIL;
}
// 从 GL 纹理创建 CL 图像
cl_image = clCreateFromGLTexture(context, CL_MEM_READ_WRITE,
GL_TEXTURE_2D, 0, gl_texture, &err);
if (err != CL_SUCCESS) {
printf("clCreateFromGLTexture failed: %d\n", err);
glDeleteTextures(1, &gl_texture);
free(data);
return TEST_FAIL;
}
// 图像处理内核
const char *kernel_source =
"__kernel void invert_image(\n"
" __read_only image2d_t input,\n"
" __write_only image2d_t output)\n"
"{\n"
" int x = get_global_id(0);\n"
" int y = get_global_id(1);\n"
" \n"
" sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE |\n"
" CLK_ADDRESS_CLAMP_TO_EDGE |\n"
" CLK_FILTER_NEAREST;\n"
" \n"
" float4 pixel = read_imagef(input, sampler, (int2)(x, y));\n"
" pixel = (float4)(1.0f) - pixel;\n"
" pixel.w = 1.0f; // 保持 alpha 通道\n"
" write_imagef(output, (int2)(x, y), pixel);\n"
"}\n";
cl_program program = clCreateProgramWithSource(context, 1,
&kernel_source, NULL, &err);
clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(program, "invert_image", &err);
// 获取 GL 对象
clEnqueueAcquireGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
// 执行图像处理
clSetKernelArg(kernel, 0, sizeof(cl_mem), &cl_image);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &cl_image); // 就地处理
size_t global_size[] = { width, height };
clEnqueueNDRangeKernel(queue, kernel, 2, NULL, global_size,
NULL, 0, NULL, NULL);
// 释放 GL 对象
clEnqueueReleaseGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
clFinish(queue);
// 清理
free(data);
clReleaseMemObject(cl_image);
clReleaseKernel(kernel);
clReleaseProgram(program);
glDeleteTextures(1, &gl_texture);
return TEST_PASS;
}
38.4.2 3D 纹理测试(GLES 3.0+)
c
复制代码
// 测试 3D 纹理互操作(需要 GLES 3.0+)
int test_gles_images_3D(cl_context context, cl_command_queue queue,
int width, int height, int depth) {
cl_int err;
GLuint gl_texture;
cl_mem cl_image;
// 创建 3D 纹理
glGenTextures(1, &gl_texture);
glBindTexture(GL_TEXTURE_3D, gl_texture);
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, width, height, depth, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_3D, 0);
// 从 GL 纹理创建 CL 图像
cl_image = clCreateFromGLTexture(context, CL_MEM_READ_WRITE,
GL_TEXTURE_3D, 0, gl_texture, &err);
if (err != CL_SUCCESS) {
printf("clCreateFromGLTexture (3D) failed: %d\n", err);
glDeleteTextures(1, &gl_texture);
return TEST_FAIL;
}
// 验证图像维度
size_t image_width, image_height, image_depth;
clGetImageInfo(cl_image, CL_IMAGE_WIDTH, sizeof(size_t), &image_width, NULL);
clGetImageInfo(cl_image, CL_IMAGE_HEIGHT, sizeof(size_t), &image_height, NULL);
clGetImageInfo(cl_image, CL_IMAGE_DEPTH, sizeof(size_t), &image_depth, NULL);
if (image_width != width || image_height != height || image_depth != depth) {
printf("3D image dimensions mismatch\n");
clReleaseMemObject(cl_image);
glDeleteTextures(1, &gl_texture);
return TEST_FAIL;
}
// 获取并释放 GL 对象
clEnqueueAcquireGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
// ... 处理 3D 图像 ...
clEnqueueReleaseGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
clFinish(queue);
// 清理
clReleaseMemObject(cl_image);
glDeleteTextures(1, &gl_texture);
return TEST_PASS;
}
38.5 GLES 渲染缓冲区互操作
38.5.1 渲染缓冲区测试
c
复制代码
// 创建 GLES 渲染缓冲区
GLuint create_gles_renderbuffer(int width, int height, GLenum format) {
GLuint renderbuffer;
glGenRenderbuffers(1, &renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, format, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
printf("GL error in renderbuffer creation: 0x%x\n", err);
return 0;
}
return renderbuffer;
}
// 测试渲染缓冲区互操作
int test_gles_renderbuffer(cl_context context, cl_command_queue queue,
int width, int height) {
cl_int err;
GLuint gl_rb;
cl_mem cl_image;
// 创建渲染缓冲区
gl_rb = create_gles_renderbuffer(width, height, GL_RGBA8);
if (gl_rb == 0) {
return TEST_FAIL;
}
// 从渲染缓冲区创建 CL 图像
cl_image = clCreateFromGLRenderbuffer(context, CL_MEM_READ_WRITE,
gl_rb, &err);
if (err != CL_SUCCESS) {
printf("clCreateFromGLRenderbuffer failed: %d\n", err);
glDeleteRenderbuffers(1, &gl_rb);
return TEST_FAIL;
}
// 查询 GL 对象信息
cl_GLuint gl_name;
cl_gl_object_type obj_type;
err = clGetGLObjectInfo(cl_image, &obj_type, &gl_name);
test_error(err, "clGetGLObjectInfo failed");
if (obj_type != CL_GL_OBJECT_RENDERBUFFER) {
printf("Object type mismatch\n");
clReleaseMemObject(cl_image);
glDeleteRenderbuffers(1, &gl_rb);
return TEST_FAIL;
}
if (gl_name != gl_rb) {
printf("GL name mismatch\n");
clReleaseMemObject(cl_image);
glDeleteRenderbuffers(1, &gl_rb);
return TEST_FAIL;
}
// 获取并释放 GL 对象
clEnqueueAcquireGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
// ... 处理渲染缓冲区 ...
clEnqueueReleaseGLObjects(queue, 1, &cl_image, 0, NULL, NULL);
clFinish(queue);
// 清理
clReleaseMemObject(cl_image);
glDeleteRenderbuffers(1, &gl_rb);
return TEST_PASS;
}
38.5.2 帧缓冲区对象(FBO)
c
复制代码
// 使用 FBO 和 CL 进行离屏渲染
int test_gles_fbo_cl_pipeline(cl_context context, cl_command_queue queue,
int width, int height) {
cl_int err;
GLuint fbo, color_rb, depth_rb;
cl_mem cl_color;
// 创建 FBO
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// 创建颜色渲染缓冲区
color_rb = create_gles_renderbuffer(width, height, GL_RGBA8);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, color_rb);
// 创建深度渲染缓冲区
depth_rb = create_gles_renderbuffer(width, height, GL_DEPTH_COMPONENT24);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, depth_rb);
// 检查 FBO 完整性
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
printf("FBO is not complete\n");
return TEST_FAIL;
}
// 从颜色缓冲区创建 CL 图像
cl_color = clCreateFromGLRenderbuffer(context, CL_MEM_READ_WRITE,
color_rb, &err);
test_error(err, "clCreateFromGLRenderbuffer failed");
// GL 渲染到 FBO
glViewport(0, 0, width, height);
glClearColor(0.2f, 0.3f, 0.4f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// ... 渲染场景 ...
// 确保 GL 渲染完成
glFinish();
// CL 后处理
clEnqueueAcquireGLObjects(queue, 1, &cl_color, 0, NULL, NULL);
// ... CL 图像处理 ...
clEnqueueReleaseGLObjects(queue, 1, &cl_color, 0, NULL, NULL);
clFinish(queue);
// 读取最终结果
// ...
// 清理
glBindFramebuffer(GL_FRAMEBUFFER, 0);
clReleaseMemObject(cl_color);
glDeleteFramebuffers(1, &fbo);
glDeleteRenderbuffers(1, &color_rb);
glDeleteRenderbuffers(1, &depth_rb);
return TEST_PASS;
}
38.6 EGL Sync 同步
38.6.1 EGL Sync 对象
c
复制代码
// 使用 EGL Sync 进行 GL-CL 同步
int test_gles_egl_sync(cl_context context, cl_command_queue queue,
EGLDisplay display) {
cl_int err;
// 在 GL 端插入 sync 对象
EGLSyncKHR egl_sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL);
if (egl_sync == EGL_NO_SYNC_KHR) {
printf("Failed to create EGL sync\n");
return TEST_FAIL;
}
// 刷新 GL 命令
glFlush();
// 从 EGL sync 创建 CL 事件(如果支持)
clCreateEventFromEGLSyncKHR_fn clCreateEventFromEGLSyncKHR =
(clCreateEventFromEGLSyncKHR_fn)clGetExtensionFunctionAddress(
"clCreateEventFromEGLSyncKHR");
if (clCreateEventFromEGLSyncKHR) {
cl_event cl_event = clCreateEventFromEGLSyncKHR(context, egl_sync,
display, &err);
if (err == CL_SUCCESS) {
// CL 命令等待 GL 完成
clEnqueueMarkerWithWaitList(queue, 1, &cl_event, NULL);
clReleaseEvent(cl_event);
}
} else {
// 降级到客户端同步
EGLint result = eglClientWaitSyncKHR(display, egl_sync,
EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
if (result == EGL_FALSE) {
printf("eglClientWaitSyncKHR failed\n");
return TEST_FAIL;
}
}
// 清理
eglDestroySyncKHR(display, egl_sync);
return TEST_PASS;
}
38.7 移动端优化
38.7.1 内存带宽优化
c
复制代码
// 针对移动 GPU 优化的图像处理
const char *mobile_optimized_kernel =
"__kernel void mobile_blur(\n"
" __read_only image2d_t input,\n"
" __write_only image2d_t output)\n"
"{\n"
" int x = get_global_id(0);\n"
" int y = get_global_id(1);\n"
" \n"
" sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE |\n"
" CLK_ADDRESS_CLAMP_TO_EDGE |\n"
" CLK_FILTER_LINEAR; // 利用硬件线性插值\n"
" \n"
" // 使用较小的滤波核以减少内存访问\n"
" float4 sum = (float4)(0.0f);\n"
" sum += read_imagef(input, sampler, (float2)(x-1, y-1)) * 0.0625f;\n"
" sum += read_imagef(input, sampler, (float2)(x, y-1)) * 0.125f;\n"
" sum += read_imagef(input, sampler, (float2)(x+1, y-1)) * 0.0625f;\n"
" sum += read_imagef(input, sampler, (float2)(x-1, y )) * 0.125f;\n"
" sum += read_imagef(input, sampler, (float2)(x, y )) * 0.25f;\n"
" sum += read_imagef(input, sampler, (float2)(x+1, y )) * 0.125f;\n"
" sum += read_imagef(input, sampler, (float2)(x-1, y+1)) * 0.0625f;\n"
" sum += read_imagef(input, sampler, (float2)(x, y+1)) * 0.125f;\n"
" sum += read_imagef(input, sampler, (float2)(x+1, y+1)) * 0.0625f;\n"
" \n"
" write_imagef(output, (int2)(x, y), sum);\n"
"}\n";
38.7.2 功耗优化
c
复制代码
// 功耗优化策略
void optimize_for_mobile(cl_command_queue queue) {
// 1. 使用异步操作减少CPU等待
clFlush(queue); // 而不是 clFinish()
// 2. 批量处理减少命令提交开销
// 积累多个操作后再flush
// 3. 选择合适的工作组大小
// 移动GPU通常较小的工作组更高效
size_t local_size[] = { 8, 8 }; // 而不是 16x16
}
38.8 CTS 测试用例清单
38.8.1 缓冲区测试
| 测试名称 |
测试内容 |
test_buffers |
GLES 缓冲区基本读写 |
test_buffers_getinfo |
GLES 缓冲区信息查询 |
38.8.2 纹理测试
| 测试名称 |
测试内容 |
test_images_2D |
2D 纹理互操作 |
test_images_2D_info |
2D 纹理信息查询 |
test_images_3D |
3D 纹理互操作(GLES 3.0+) |
test_images_3D_info |
3D 纹理信息查询(GLES 3.0+) |
38.8.3 渲染缓冲区测试
| 测试名称 |
测试内容 |
test_renderbuffer |
渲染缓冲区读写 |
test_renderbuffer_info |
渲染缓冲区信息查询 |
38.8.4 同步测试
| 测试名称 |
测试内容 |
test_fence_sync |
EGL fence sync 同步 |
38.9 实际应用场景
38.9.1 移动游戏后处理
c
复制代码
// 移动游戏实时后处理流水线
void mobile_game_postprocess(GLuint scene_texture, GLuint output_texture,
cl_context context, cl_command_queue queue,
int width, int height) {
cl_mem cl_input, cl_output;
// 创建 CL 图像
cl_input = clCreateFromGLTexture(context, CL_MEM_READ_ONLY,
GL_TEXTURE_2D, 0, scene_texture, NULL);
cl_output = clCreateFromGLTexture(context, CL_MEM_WRITE_ONLY,
GL_TEXTURE_2D, 0, output_texture, NULL);
// 每帧处理
while (game_running) {
// GLES 渲染场景
render_game_scene();
// CL 后处理
cl_mem objects[] = { cl_input, cl_output };
clEnqueueAcquireGLObjects(queue, 2, objects, 0, NULL, NULL);
// 应用多种效果:HDR、Bloom、Color Grading
apply_hdr_tonemapping(queue, cl_input, cl_output, width, height);
apply_bloom(queue, cl_output, width, height);
apply_color_grading(queue, cl_output, width, height);
clEnqueueReleaseGLObjects(queue, 2, objects, 0, NULL, NULL);
clFlush(queue); // 不使用 Finish 以提高性能
// GLES 显示结果
display_texture(output_texture);
swap_buffers();
}
clReleaseMemObject(cl_input);
clReleaseMemObject(cl_output);
}
38.9.2 AR 应用
c
复制代码
// AR 应用中的实时图像分析
void ar_image_processing(GLuint camera_texture,
cl_context context, cl_command_queue queue,
int width, int height) {
cl_mem cl_camera;
// 从相机纹理创建 CL 图像
cl_camera = clCreateFromGLTexture(context, CL_MEM_READ_ONLY,
GL_TEXTURE_2D, 0, camera_texture, NULL);
while (ar_active) {
// 相机帧更新到纹理
update_camera_texture(camera_texture);
// CL 图像分析
clEnqueueAcquireGLObjects(queue, 1, &cl_camera, 0, NULL, NULL);
// 特征检测、跟踪
detect_markers(queue, cl_camera, width, height);
track_objects(queue, cl_camera, width, height);
clEnqueueReleaseGLObjects(queue, 1, &cl_camera, 0, NULL, NULL);
clFlush(queue);
// GLES 渲染 AR 内容
render_ar_overlays();
swap_buffers();
}
clReleaseMemObject(cl_camera);
}
38.10 最佳实践
38.10.1 移动平台特定优化
- 优先使用图像对象:移动 GPU 对图像对象优化更好
- 减少内存分配:重用 GL/CL 对象
- 控制精度:适当使用半精度浮点(half)
- 批量操作:减少 API 调用开销
38.10.2 功耗管理
c
复制代码
// 动态调整处理质量以平衡性能和功耗
typedef enum {
QUALITY_LOW, // 低功耗模式
QUALITY_MEDIUM, // 平衡模式
QUALITY_HIGH // 高质量模式
} QualityLevel;
void adjust_processing_quality(QualityLevel level, size_t *global_size) {
switch (level) {
case QUALITY_LOW:
// 降低分辨率
global_size[0] /= 2;
global_size[1] /= 2;
break;
case QUALITY_MEDIUM:
// 标准分辨率
break;
case QUALITY_HIGH:
// 全分辨率,可能增加采样
break;
}
}
38.11 总结
38.11.1 关键要点
- EGL 是移动平台的标准接口用于创建 GL-CL 共享上下文
- 功能子集:GLES 相比 OpenGL 功能较少,但核心互操作相同
- 性能优化至关重要:移动设备资源受限
- 功耗考虑:需要平衡性能和电池寿命
38.11.2 与桌面 GL 的差异
- 使用 EGL 而非 GLX/WGL/CGL
- 某些高级 GL 特性不可用
- 更严格的性能和功耗约束
- 更小的工作组大小通常更优
参考资源:
cl_khr_gl_sharing 扩展规范
- EGL 规范文档
test_conformance/gles/ 目录中的测试实现
相关测试目录 :test_conformance/gles/
依赖章节:
- 第37章:GL - OpenGL 互操作测试
- 第6章:Images - 图像对象测试