基于OpenGl与XDMA进行Xilinx FPGA内部DDR图像的显示

如题,本质上就是将存储在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的刷新率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值