shader 概述和EGL

本文深入解析GPU渲染管线的工作原理,包括顶点着色器、光栅化过程、像素着色器的作用及流程,并介绍了EGL相关API的使用方法。

着色器:在gpu上运行的一组指令
(GPU即图形处理器,是一个对CPU的补充,它的目的就是尽可能的加速图形处理速度,光栅化,主要负责跟显示相关的数据处理)

顶点数据打包给顶点着色器,顶点输出
(平面旗子的顶点位置,通过sin处理,输出为飘扬的旗子顶点位置)
顶点输出给 光栅化器 (光珊化会根据这些关键点生成很多点用来组成图形的形状) 找到顶点几何区域中的像素, 划分片段 内插顶点着色器的数据 给 像素着色器
根据顶点数据 (计算区域内各像素着色器 数据)
(顶点法线与面法线的区别:若第一个顶点光照强度是10,第二个顶点光照强度是1,那么这两个顶点正中间的光照强度是(10-1)*0.5 = 4.5,由此可见顶点光照强度在整个面上是均匀过度的。面法线整个面的光照强度一样)

渲染管道:顶点数据→顶点着色器 →光栅化 几何着色器(划分区域,计算)→像素/片段着色器(进行最终的颜色计算,输出像素颜色alpha)→绘制像素

读取顶点数据→执行顶点着色器→组装图元→光栅化图元→执行片段着色器→写入帧缓冲区→显示在屏幕上。

计算着色器在渲染管道之外,用来运行gpu并行处理功能,处理通过任务

在这里插入图片描述
shader中尽量不用if,因为GPU会两种判断都计算

在这里插入图片描述
blend原像素颜色与着色器输出颜色混合模式

在这里插入图片描述

8bit为一个字节,所以假如图像时单通道的黑白图像,那么每一个像素包含一个字节的信息。假如图像是R、G、B三通道的彩色图像,那么每一个像素包含3个字节的信息。

在这里插入图片描述

EGLint eglGetError(void)

用于返回当前thread如果EGL的API出错的话,最近一个错误所对应的错误代码(错误代码写入寄存器中,通过该函数读取)
输入:空
输出:错误代码 (1种成功,15种错误情况)

EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);

从EGL运行的操作系统中获取一个Display
输入:从操作系统中得知的Display的ID
输出:用于显示图片绘制的Display

EGLBoolean eglInitialize(EGLDisplay dpy, EGLint * major, EGLint *minot);

针对某display初始化一个某版本的EGL
输入:使用Display的handle特指某个Display,major和minor共同指定EGL的版本(若为空,则不初始化)
输出:EGL初始化成功或失败

EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config);

获取Display支持的配置信息
输入:DIsplay的handle,一个用于保存配置信息的指针,指针中存放的配置信息的数量,某display支持的配置信息数量(第四个参数若未传入变量地址,而是null,直接返回false),2,4为输出参数(通过函数获取的信息传与输入的参数)
输出:配置信息获取成功或者失败

EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);

获取与需求匹配,且某display支持的配置信息
输入:Display的handle,一个指向所匹配的需求信息的指针,一个指向保存匹配需求的配置信息的指针,指针中存放的配置信息的数量,display匹配需求的配置信息的数量,3,5为输出参数
输出:匹配的配置信息获取成功或者失败

EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);

创建一个可以显示在屏幕上的rendering surface
输入:DIsplay的handle,用于创建surface的配置信息(通常传入eglChooseConfig获取的配置信息,已默认排好序,使用第一个),窗口信息的handle,额外的需求信息(EGLRender Buffersr的属性, 可null按默认处理)
输出:创建的rendering surface的handle

EGLBoolean EGLBindAPI(EGLenum api);

设置当前thread的绘制API(当前的API要用来创建context,context要与surface唯一对应匹配使用)
输入:支持的绘制API(OpenGL是pc端,OpenGL Es是移动端)
输出:API设置成功或者失败

EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);

针对当前的绘制API创建一个rendering context(context中保存OpenGL ES状态机信息, 若一对surface与context兼容,context可使用保存的信息往rendering surface上进行绘制)
输入:DIsplay的handle,用于创建context的配置信息(一般般传入eglChooseConfig得到的信息),指定一个context使得创建的context与其share(通过该参数多个context之间都可以进行共享),额外的需求信息
输出:创建的rendering context的handle

一个native windows handle只能创建一个 rendering surface,而display与EGL对应可创建多个rendering surface,context与native windows无关,则display可创建多个context,每个context对应一种绘制api,只要surface与context的格式匹配,两则可进行关联,同一时间,一个surface只能与一个context关联,一种绘制api也只能有一个context

EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);

将指定的context绑定到当前thread以及读和写surface上
输入:Display的handle,用于写入的surface,用于读取的surface,(第二个和第三个一般会设置成一个参数)指定的context
(如果当前thread已经有相同绘制api的context,则会先将之前的context进行flash操作,把未执行的命令全部执行完毕,然后设置disabled状态,再把新传入的context设置成enabled)
输出:成功或失败
(如果想要释放当前context,即设为disabled,则第二第三参数传入EGLNoSurface,第四个设置成EGLNoContext,只有这种情况第一个参数可传入未初始化的display)

EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);

将surface中的color buffer显示到屏幕上
输入:Display的handle,将会被展示的surface
输出:显示成功或失败

EGLBoolean eglTeminate(EGLDisplay dpy);

将某display对应的EGL相关的资源释放(例如与display关联的surface,context等,若surface,context在释放时仍被使用,不会导致程序瘫痪,但会使绘制命令出错,绘制结果不确定)
在这里插入图片描述

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/time.h> #include <sys/types.h> #include <sys/shm.h> #include <gbm.h> #include <png.h> #include <EGL/egl.h> #include <GLES2/gl2.h> #include <EGL/egl.h> #include <GLES3/gl3.h> #include <stdio.h> #include <stdlib.h> #include <EGL/egl.h> #include <GLES3/gl3.h> #include <stdio.h> #include <stdlib.h> #include <queue> #include <thread> #include <mutex> #include <condition_variable> // 模拟 DMA 缓冲区(实际应使用 Rockchip 的 DRM/DMA-BUF 接口) typedef struct { int width; int height; uint8_t *nv12_data; } DMABuffer; // EGL 配置 EGLDisplay eglDisplay; EGLContext eglContext; EGLSurface eglSurface; // 初始化 EGL int init_egl(int width, int height) { eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL_NO_DISPLAY) return -1; EGLint major, minor; if (!eglInitialize(eglDisplay, &major, &minor)) return -1; EGLint configAttrs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; EGLConfig eglConfig; EGLint numConfigs; if (!eglChooseConfig(eglDisplay, configAttrs, &eglConfig, 1, &numConfigs)) return -1; EGLint surfaceAttrs[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE }; eglSurface = eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttrs); if (eglSurface == EGL_NO_SURFACE) return -1; EGLint contextAttrs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttrs); if (eglContext == EGL_NO_CONTEXT) return -1; if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) return -1; return 0; } GLuint y_texture, uv_texture; void load_nv12_texture(const DMABuffer &buf) { // Y 平面 glBindTexture(GL_TEXTURE_2D, y_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, buf.width, buf.height, 0, GL_RED, GL_UNSIGNED_BYTE, buf.nv12_data); // UV 平面 const uint8_t *uv_data = buf.nv12_data + buf.width * buf.height; glBindTexture(GL_TEXTURE_2D, uv_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, buf.width / 2, buf.height / 2, 0, GL_RG, GL_UNSIGNED_BYTE, uv_data); } GLuint compile_shader(GLenum type, const char *source) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, NULL); glCompileShader(shader); GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (!status) { char log[1024]; glGetShaderInfoLog(shader, 1024, NULL, log); printf("Shader compile error: %s\n", log); glDeleteShader(shader); return 0; } return shader; } GLuint create_program(const char *vs_source, const char *fs_source) { GLuint vs = compile_shader(GL_VERTEX_SHADER, vs_source); GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fs_source); GLuint program = glCreateProgram(); glAttachShader(program, vs); glAttachShader(program, fs); glLinkProgram(program); GLint status; glGetProgramiv(program, GL_LINK_STATUS, &status); if (!status) { char log[1024]; glGetProgramInfoLog(program, 1024, NULL, log); printf("Program link error: %s\n", log); glDeleteProgram(program); return 0; } glDeleteShader(vs); glDeleteShader(fs); return program; } const char *vertex_shader_source = R"( #version 300 es layout(location = 0) in vec2 position; layout(location = 1) in vec2 texCoord; out vec2 v_texCoord; void main() { gl_Position = vec4(position, 0.0, 1.0); v_texCoord = texCoord; } )"; const char *fragment_shader_source = R"( #version 300 es precision mediump float; in vec2 v_texCoord; out vec4 fragColor; uniform sampler2D y_texture; uniform sampler2D uv_texture; uniform vec2 texScale; // (target_width / src_width, target_height / src_height) void main() { // 缩放后的纹理坐标 vec2 scaledCoord = v_texCoord * texScale; // 采样 Y 平面 float y = texture(y_texture, scaledCoord).r; // 采样 UV 平面(NV12 的 UV 是 2x2 下采样的) vec2 uvCoord = scaledCoord * 0.5; vec2 uv = texture(uv_texture, uvCoord).rg; // 输出 NV12(实际需要分开处理,此处仅作示例) // 实际应用中,Y UV 需要分别渲染到两个 FBO fragColor = vec4(y, uv, 0.0, 1.0); // 伪代码,实际需分开输出 } )"; // 模拟 DMA 输入队列(实际应使用 Rockchip 的 V4L2 或 DRM 接口) std::queue<DMABuffer> dma_input_queue; std::mutex dma_mutex; std::condition_variable dma_cv; // 模拟 DMA 数据生成线程(实际应替换为摄像头采集) void dma_producer_thread() { while (true) { DMABuffer buf; buf.width = 1920; buf.height = 1080; buf.nv12_data = (uint8_t *)malloc(buf.width * buf.height * 3 / 2); // ... 填充测试数据(实际应从摄像头读取) ... { std::lock_guard<std::mutex> lock(dma_mutex); dma_input_queue.push(buf); dma_cv.notify_one(); } std::this_thread::sleep_for(std::chrono::milliseconds(33)); // ~30FPS } } typedef struct { int width; int height; uint8_t *y_data; // Y 平面 uint8_t *uv_data; // UV 平面 } ScaledFrame; std::queue<ScaledFrame> output_queue; std::mutex output_mutex; std::condition_variable output_cv; GLuint fbo_y, fbo_uv; GLuint output_y, output_uv; void setup_fbo(int width, int height) { // Y 平面 FBO glGenFramebuffers(1, &fbo_y); glBindFramebuffer(GL_FRAMEBUFFER, fbo_y); glGenTextures(1, &output_y); glBindTexture(GL_TEXTURE_2D, output_y); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, output_y, 0); // UV 平面 FBO glGenFramebuffers(1, &fbo_uv); glBindFramebuffer(GL_FRAMEBUFFER, fbo_uv); glGenTextures(1, &output_uv); glBindTexture(GL_TEXTURE_2D, output_uv); glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width / 2, height / 2, 0, GL_RG, GL_UNSIGNED_BYTE, NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, output_uv, 0); } void render_nv12(GLuint program, int src_width, int src_height, int target_width, int target_height) { glViewport(0, 0, target_width, target_height); // 渲染 Y 平面 glBindFramebuffer(GL_FRAMEBUFFER, fbo_y); glUseProgram(program); glUniform2f(glGetUniformLocation(program, "texScale"), (float)target_width / src_width, (float)target_height / src_height); glUniform1i(glGetUniformLocation(program, "y_texture"), 0); glUniform1i(glGetUniformLocation(program, "uv_texture"), 1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, y_texture); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, uv_texture); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 渲染 UV 平面 glBindFramebuffer(GL_FRAMEBUFFER, fbo_uv); glViewport(0, 0, target_width / 2, target_height / 2); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } void read_nv12_data(int width, int height, ScaledFrame &frame) { frame.y_data = (uint8_t *)malloc(width * height); frame.uv_data = (uint8_t *)malloc(width * height / 2); frame.width = width; frame.height = height; // 读取 Y 平面 glBindFramebuffer(GL_FRAMEBUFFER, fbo_y); glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, frame.y_data); // 读取 UV 平面 glBindFramebuffer(GL_FRAMEBUFFER, fbo_uv); glReadPixels(0, 0, width / 2, height / 2, GL_RG, GL_UNSIGNED_BYTE, frame.uv_data); } void processing_loop(int target_width, int target_height) { GLuint program = create_program(vertex_shader_source, fragment_shader_source); setup_fbo(target_width, target_height); while (true) { DMABuffer input_buf; { std::unique_lock<std::mutex> lock(dma_mutex); dma_cv.wait(lock, [] { return !dma_input_queue.empty(); }); input_buf = dma_input_queue.front(); dma_input_queue.pop(); } // 加载纹理 load_nv12_texture(input_buf); // 渲染缩放 render_nv12(program, input_buf.width, input_buf.height, target_width, target_height); // 读取缩放后的数据 ScaledFrame output_frame; read_nv12_data(target_width, target_height, output_frame); // 存入输出队列 { std::lock_guard<std::mutex> lock(output_mutex); output_queue.push(output_frame); output_cv.notify_one(); } // 释放输入缓冲区 free(input_buf.nv12_data); } } void consumer_thread() { while (true) { ScaledFrame frame; { std::unique_lock<std::mutex> lock(output_mutex); output_cv.wait(lock, [] { return !output_queue.empty(); }); frame = output_queue.front(); output_queue.pop(); } // 合并为 NV12(可选) uint8_t *nv12_out = (uint8_t *)malloc(frame.width * frame.height * 3 / 2); memcpy(nv12_out, frame.y_data, frame.width * frame.height); memcpy(nv12_out + frame.width * frame.height, frame.uv_data, frame.width * frame.height / 2); // ... 处理 nv12_out(如编码、显示或写入文件) ... // 释放内存 free(frame.y_data); free(frame.uv_data); free(nv12_out); } } int main() { int target_width = 1280, target_height = 720; // 初始化 EGL + GLES3 if (init_egl(target_width, target_height) < 0) { printf("EGL init failed!\n"); return -1; } // 初始化纹理 glGenTextures(1, &y_texture); glGenTextures(1, &uv_texture); // 启动线程 std::thread producer(dma_producer_thread); std::thread processor(processing_loop, target_width, target_height); std::thread consumer(consumer_thread); producer.join(); processor.join(); consumer.join(); eglTerminate(eglDisplay); return 0; } 完善上述代码,并解释
07-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值