如题,本质上就是将存储在FPGA内部DDR的图像,通过PCIe接口读取到上位机进行显示,使用了DMA进行数据搬运,并通过OpenGl显示在屏幕上,自适应分辨率,先看下显示的效果:
上图显示的是一幅分辨率为4096x512的彩条图像,在title上,并没有截取完整,底下是在终端打印的调试信息,也会打印图像分辨率,这里使用xdma h2c 0通道进行数据搬运。接下来对设计部分进行说明,主要分为以下几个部分
1、通过dma进行图像数据读取;
2、通过OpenGl进行图像数据显示;
3、将数据通过dma写入FPGA内部的DDR。
1、通过dma进行图像数据读取
这部分借鉴了Xilinx官方的xdma linux驱动,主要调用linux下的read函数进行数据读取。
rc = read(fd, buf, bytes);
其中,fd是xdma_h2c_0文件描述符,在之前我们使用open函数已经打开
fpga_fd = open(devname, O_RDWR);
定义一个read_from_dma函数,对输入的图像行列大小进行解析,调用上述函数读取数据。
int read_from_dma(char *devname, uint64_t addr, uint64_t aperture,
uint64_t size, uint64_t offset, uint64_t count,
char *ofname, char *shared_buffer)
其中shared_buffer即为后续OpenGl图像的数据缓存区,这里起了一个单独的线程更新图像分辨率,并且读取图像数据。
void* read_data_thread(void* arg) {
while(shared_flag){
int rc = read_from_dma(device_c2h, address, aperture, 10, offset, 1, ofname, shared_buffer);
if (rc != 0) {
fprintf(stderr, "Error in read_from_dma\n");
break;
}
imageHeight = ((uint16_t)shared_buffer[5] << 8) | shared_buffer[4];
imageWidth = ((uint16_t)shared_buffer[7] << 8) | shared_buffer[6];
printf("imageWidth = %u\n", imageWidth);
printf("imageHeight = %u\n", imageHeight);
rc = read_from_dma(device_c2h, address, 0, imageWidth * 4, 0, imageHeight + 1, ofname, shared_buffer);
}
return NULL;
}
2、通过OpenGl进行图像数据显示
这部分的内容较多,主要是OpenGl显示需要初始化,并且在图像分辨率变化的时候更新图像的title以及视口,保证图像正常显示,在这里贴部分代码:
// 设置OpenGL版本为1.2
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
snprintf(title, sizeof(title), "Resolution - %dx%d", imageWidth, imageHeight);
// 创建窗口
GLFWwindow* window = glfwCreateWindow(imageWidth, imageHeight, title, NULL, NULL);
if (!window) {
fprintf(stderr, "Failed to open GLFW window\n");
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// 初始化GLEW
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
return -1;
}
// 设置视口
glViewport(0, 0, imageWidth, imageHeight);
// 创建顶点数组对象(VAO),顶点缓冲对象(VBO)和元素缓冲对象(EBO)
GLuint VAO, VBO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
float vertices[] = {
// positions // texture coords
1.0f, 1.0f, 0.0f, 1.0f, 0.0f, // top right
1.0f, -1.0f, 0.0f, 1.0f, 1.0f, // bottom right
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f, // bottom left
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f // top left
};
unsigned int indices[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 创建并绑定纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 设置纹理环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// 设置纹理过滤选项
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, imageWidth);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
//创建着色器程序
GLuint shader_program = create_program(vertex_shader_source, fragment_shader_source);
最后在一个while循环中进行图像数据的更新
while (!glfwWindowShouldClose(window)) {
if(value_frameheader != shared_buffer[0]){
value_frameheader = shared_buffer[0];
glfwSetWindowSize(window, imageWidth, imageHeight);
snprintf(title, sizeof(title), "Resolution - %dx%d", imageWidth, imageHeight);
glfwSetWindowTitle(window, title);
// 设置视口
glViewport(0, 0, imageWidth, imageHeight);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, imageWidth);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, shared_buffer + BYTENUM_IMAGEHEADER);
3、将数据通过dma写入FPGA内部的DDR
这部分主要是为了将数据写回FPGA,因为图像数据从FPGA到上位机后进行了一系列的算法操作,处理完成后再通过dma写入FPGA内部的DDR。
以25Hz的刷新率写入,这里设置了一个25ms的定时器,并加入线程锁,写入的操作同样借鉴xdma的官方驱动。
// 独立线程函数
void* write_data_thread(void* arg) {
while (!exit_flag) {
pthread_mutex_lock(&mutex);
// 等待定时器信号
while (!timer_expired && !exit_flag) {
pthread_cond_wait(&cond, &mutex);
}
// 处理任务
//sleep(1);
if(exit_flag){
pthread_mutex_unlock(&mutex);
break;
}
int rc = write_to_dma(device_h2c, address + OFFSET_WRITE, imageWidth * 4, offset, imageHeight + 1, shared_buffer);
// 重置标志位
timer_expired = 0;
pthread_mutex_unlock(&mutex);
}
return NULL;
}
// 定时器处理函数
void timer_handler(int signum) {
pthread_mutex_lock(&mutex);
timer_expired = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
至此这个图像显示以及回传的demo就完成了,最终单dma通道传输的速率在PCIe 3.0下可以达到1GByte/s,1080P下可以达到60Hz的刷新率。