Linux framebuffer双缓冲防止闪烁

昨天写了一篇文章:
使用Linux Framebuffer绘制32位真彩图形: https://blog.csdn.net/dog250/article/details/90113737
并发了朋友圈表示这件事结束了,玩了一天,玩恶心了。

但是我依然是想做出一个可以拖拽的不规则GUI界面(用皮鞋或者小小的照片做界面轮廓)来的。所以半夜就爬起来继续折腾。

无奈,没有找到获取鼠标焦点的好方法,都太复杂,要知道,我是希望在framebuffer上玩啊,不希望依赖那些已经集成在GUI里面的东西。

我不就想模拟个拖拽嘛,简单,用线程控制图片在屏幕上漂移,即:

// setPoint方法已经抽象独立了出来,成为一个static方法,以免main函数太长。
while(true) {
	setPoint(width, height, xoffset%200, yoffset%50);
	try {
		Thread.sleep(100);
	} catch (InterruptedException e) { }
	xoffset += 2;
	yoffset += 2;
}

这个代码测试下来, 闪烁太厉害了! 根本就没法看:

在这里插入图片描述

怎么办?如果简单的图片漂移都这么闪烁,那如果鼠标拖拽移动图片,结局注定令人遗憾。怎么办?

找根源!根源就是 画图的时间太久了! 我可是一个像素一个像素画的啊!

当然了,我知道,如果Java通过JNI将一个像素数组传递到本地代码,然后本地代码直接 memcpy,那将是令人赛里布瑞特的。可是我并不知道如何从Java往本地代码传递大数组…另外,我注意是想把事情做纯粹些。 我不想把事情交给库去解决,我要自己解决! (可能赚钱的经理们又要笑我了,但我就是这样,鄙视业务逻辑。)

利用双缓冲来解决问题。

意思就是说, 逐像素点画图这件耗时的操作,不要直接操作显存,而是操作一块预先分配好的和显存一样大小的缓冲区,等逐点画图完成之后,一次性将该缓冲区的内容memcpy到事先mmap好的显存地址空间。

关于 双缓冲 技术我就不多说了,这技术的解释已经烂大街了,诸如什么流水线相关的形而上解释,看着都烦了,不过确实是那么回事。

直接上代码吧,先看Java代码 Drawimage.java

import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;

public class Drawimage {
	static File src = null;
	static BufferedImage img = null;
	native static void setFB(int x, int y, int rgb);
	native static void show(int x);
	static {
		System.loadLibrary("setFB");
	}

	static void setPoint(int width, int height, int xoffset, int yoffset) {
		int i, j, rgb, a1;
		for (i = 1; i < width - 1; i++) {
			for (j = 1; j < height-1; j++) {
				rgb = img.getRGB(i, j);
				a1 = (rgb>>24)&0xFF;
				if (a1 != 0) {
					Drawimage.setFB(i + xoffset, j + yoffset, rgb);
				}
			}
		}
	}

	public static void main(String[] args) throws IOException {
		int i, j, width, height, xoffset = 0, yoffset = 0;
		src = new File(args[0]);
		img = ImageIO.read(src);
		width = img.getWidth();
		height = img.getHeight();

		while(true) {
			setPoint(width, height, xoffset%200, yoffset%50);
			Drawimage.show(0);	// 清空之前的图形
			Drawimage.show(1);	// 显示当下的图形 
    		try {
				Thread.sleep(100); // 这个时间频率最好和你的显示器刷新频率切合。
			} catch (InterruptedException e) { }
			xoffset += 2; // x漂移
			yoffset += 2; // y漂移
		}
	}
}

再看本地代码 setFB.c

#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <stdio.h>
#include <jni.h>

unsigned int *mem = NULL;

// 定义第二缓冲区
unsigned int *back_buffer = NULL;
static struct fb_var_screeninfo info;

void setPixel(int x, int y, int c)
{
	int idx;

	if (x < 0 || x >= info.xres || y < 0 || y >= info.yres) {
		return;
	}

	idx = y*info.xres + x;
	// 操作第二缓冲区,而不是直接操作显存
	back_buffer[idx] = c;
}

JNIEXPORT void JNICALL Java_Drawimage_show (JNIEnv *env, jclass class, int a)
{
	// show指令下达,说明画图操作已经完成,这里一次性替换显存的内容
	// 注意,替换的时机最好是显示器刷新的时机,完美契合!!
	if (a) {
		memcpy(mem, back_buffer, info.xres*info.yres*info.bits_per_pixel/8);
		memset(back_buffer, 0, info.xres*info.yres*info.bits_per_pixel/8);
	} else {
		memset(mem, 0, info.xres*info.yres*info.bits_per_pixel/8);
	}
}

JNIEXPORT void JNICALL Java_Drawimage_setFB (JNIEnv *env, jclass class, jint x, jint y, int rgb)
{
	static int fd = -1;

	if (fd == -1) {
		fd = open("/dev/fb0", O_RDWR);
		if (ioctl(fd, FBIOGET_VSCREENINFO, &info)) {
			exit(1);
		}
		info.bits_per_pixel = 8;
		info.xres = 800;
		info.yres = 600;
		if (ioctl(fd, FBIOPUT_VSCREENINFO, &info)) {
			exit(1);
		}
		mem = (unsigned int *)mmap(NULL, info.xres*info.yres*info.bits_per_pixel/8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
		if (mem == NULL) {
			printf("exit\n");
			exit(1);
		}
		back_buffer = (unsigned int *)mmap(NULL, info.xres*info.yres*info.bits_per_pixel/8, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
		if (back_buffer == NULL) {
			printf("back_buffer exit\n");
			exit(1);
		}
	}
	setPixel(x, y, rgb);
}

动图不闪哦:
在这里插入图片描述
在这里插入图片描述

双缓冲的原理就是这么简单。10年前看的的那些冗长的Java代码,都是假的,及其虚假。


皮鞋?下雨进水,一定会湿,问题的关键不是会不会湿,而是皮鞋进水湿了之后, 会不会胖!

浙江温州皮鞋湿,下雨进水不会胖。

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
下面是一个简单的 C++ 实现 framebuffer 双缓冲: ```cpp #include <iostream> #include <vector> class Framebuffer { public: Framebuffer(int width, int height) : width_(width), height_(height) { // 初始化两个缓冲区 back_buffer_.resize(width_ * height_); front_buffer_.resize(width_ * height_); } // 渲染函数,将 front_buffer_ 渲染到屏幕上 void render() { for (int i = 0; i < height_; ++i) { for (int j = 0; j < width_; ++j) { std::cout << front_buffer_[i * width_ + j] << " "; } std::cout << std::endl; } } // 将数据写入 back_buffer_ void write(int x, int y, int value) { back_buffer_[y * width_ + x] = value; } // 切换缓冲区,front_buffer_ 变为 back_buffer_,back_buffer_ 变为 front_buffer_ void swap() { std::swap(back_buffer_, front_buffer_); } private: int width_; int height_; std::vector<int> back_buffer_; // 后缓冲区 std::vector<int> front_buffer_; // 前缓冲区 }; int main() { Framebuffer framebuffer(10, 10); framebuffer.write(1, 1, 1); framebuffer.write(2, 2, 2); framebuffer.write(3, 3, 3); framebuffer.swap(); // 切换缓冲区 framebuffer.render(); // 将 front_buffer_ 渲染到屏幕上 return 0; } ``` 在这个实现中,我们使用了两个 `std::vector` 来存储两个缓冲区,`back_buffer_` 表示后缓冲区,`front_buffer_` 表示前缓冲区。在每次渲染前,我们将 `front_buffer_` 渲染到屏幕上,然后在渲染过程中将数据写入到 `back_buffer_` 中。在需要切换缓冲区时,我们只需要交换 `back_buffer_` 和 `front_buffer_` 即可。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值