写点什么

Rust 元宇宙 5 —— SDL2.0

作者:Miracle
  • 2021 年 11 月 29 日
  • 本文字数:2957 字

    阅读完需:约 10 分钟

Rust 元宇宙 5 —— SDL2.0

SDL2.0 是一个跨平台的底层图形库,支持 Windows,Mac,Android,IOS 等多个平台,考虑到我们需要观察元宇宙是如何运作的,即使不考虑客户端,仅仅是元宇宙服务器,增加一个 SDL 的观察方法,也是有助于开发和调试的。

Rust 的 SDL2.0 绑定比较成熟,但是如果要显示文字的话,就比较坑了,SDL2.0 自己带了一个 SDL_TTF 的库,支持 FreeType + SDL2.0 显示文字,但是 依赖非常多,特别是在国内,Mac brew 下载 freetype 的库,简直就是噩梦。

所以我们选择 纯 rust 的 字体解决方案

支持  TrueType 和 OpenType 的 ttf-parser 以及渲染字体的 fontdue

使用 fontdue 基本获取 字体的代码 如下


let font = File::open(font_name).map(|mut file| {            let mut buf = Vec::<u8>::new();             file.read_to_end(&mut buf).unwrap();            buf        }).ok().and_then(|data| fontdue::Font::from_bytes(data, fontdue::FontSettings::default()).ok()).unwrap();let mut size = (0, 0);let metrics_bitmaps = text.chars().fold(Vec::new(), |mut v, ch| {    let mb = self.font.rasterize(ch, height);    if mb.0.advance_height as u32 > size.1 { size.1 = mb.0.advance_height as u32; }    size.0 += mb.0.advance_width as u32;    v.push(mb);    v});
复制代码


其中 font_name 是字体文件的名字

text 是需要显示的字符串 String 或者 &str

获取的 (metrics, bitmap) 的 列表放在 一个 Vec 里面,size 是一个元组,存放了这行文字的 width 和 height,我们这里没有考虑换行的问题。


SDL2.0 基本的 窗口显示和消息处理流程如下


let sdl_context = sdl2::init().unwrap();let video_subsystem = sdl_context.video().unwrap();let window = video_subsystem.window("元宇宙 - 观察者", 1280,1024).position_centered().build().unwrap();let mut canvas = window.into_canvas().build().unwrap();let mut event_pump = sdl_context.event_pump().unwrap();'running: loop {    for event in event_pump.poll_iter() {        match event {            Event::Quit {..} |            Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {                break 'running            },            _ => {}         }     }     canvas.present();     std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));}
复制代码


这里 我们要为语句标号 Label 和 goto 平反,类似这样 break 到 循环的开始,逻辑上是很清晰的,比完全不使用 goto + label 的方案要容易理解多了


SDL2.0 有多种绘制的方法,一种是直接在 Canvas 上面绘制,但是在多线程的环境下,就会出现 canvas.present 和 对 canvas 的绘制不在同一个线程,这样会造成程序运行很不流畅,另外,大部分需要显示的内容是跟具体的对象相关的,比如说 角色的 ID Position 或者名字,头像等等,这些内容显示的范围较小,而且更新不那么频繁,所以我们可以使用 一张很大的 Texture 里面的一部分,来存放显示的内容。

需要显示的时候,一个 copy 就能迅速地送到屏幕缓冲区中。


可划分的 Texture 作为缓存

我们使用 Block 表示 Texture 中的一个块(占用的区域),使用 Container 来处理 Texture 作为缓冲区的分配和释放,使用一个 vector 处理 一个 texture 不够的情况(绝大多数情况用不到)


#[derive(Debug, Clone)]struct Block { x: u32, y: u32, w: u32, h: u32 }
impl Container {    fn new(w: u32, h: u32)-> Container {        Container{            w: w,            h: h,            free_blocks: vec![Block{x: 0, y: 0, w: w, h: h}],            used_blocks: Vec::new()        }    }    fn get(&self, id: u32)-> Block {        return self.used_blocks[id as usize].clone();    }    fn malloc(&mut self, w: u32, h: u32)-> i32 {         //malloc     }    fn free(&mut self, id: i32) {                //free a block     }}

复制代码


Container 有两个基本方法,malloc 一个块,free 一个块


textures: Vec<(Container, Texture)>,
复制代码


我们使用 textures 数组,来存放所有的 Texture 和 它们的容器

下面的代码 在贴图中分配一个矩形块,ID 作为唯一标识,以后可以用来绘制和显示


fn get_id(&mut self, w: u32, h: u32)-> u32 {        for index in 0..self.textures.len() {            let id = self.textures[index].0.malloc(w, h);            if id >= 0 {                return (id as u32) | (index as u32) << 16;            }        }        let mut container = Container::new(TEXTURE_SIZE, TEXTURE_SIZE);        let id = container.malloc(w, h);        let index = self.textures.len() as u32;        let mut tex = self.creator.create_texture_target(PixelFormatEnum::RGBA32, TEXTURE_SIZE, TEXTURE_SIZE).unwrap();        tex.set_blend_mode(BlendMode::Blend);        self.textures.push((container, tex));        if id >= 0 {  (id as u32) | index << 16 }        else { 0xffffffffu32 }    }
复制代码


下面的代码 在这个缓冲区里面进行绘制

fn draw<F: FnOnce(&mut Canvas<sdl2::video::Window>)>(&mut self, id: u32, f: F) {        let index = (id >> 16) as usize;        if index < self.textures.len() {            let rect: Rect = Rect::from(self.textures[index].0.get(id & 0xffff));            self.canvas.with_texture_canvas(&mut self.textures[index].1, |canvas| {                canvas.set_viewport(rect);                f(canvas);            }).unwrap();        }    }
复制代码

比如说:使用指定颜色和 alpha 清除一个缓冲区


self.draw(self.nodes[label].id, |canvas| {    canvas.set_draw_color(Color::RGBA(0x8f, 0x8f, 0x80, 0x40));    canvas.clear();});
复制代码


使用前述获取的文字 metrics 和 bitmap,绘制指定颜色文字的代码如下

self.draw(id, |canvas| {    let mut pos = 0;    for mb in metrics_bitmaps {        let _top = y + mb.0.advance_height as i32 - mb.0.height as i32;        for top in 0..mb.0.height {            for left in 0..(mb.0.width) {                let gray = mb.1[top * mb.0.width + left];                if gray != 0 {                    canvas.set_draw_color(Color::RGBA((color >> 16) as u8, ((color >> 8) & 0xff) as u8, (color & 0xff) as u8, gray));                    canvas.draw_point((x + pos + left as i32, _top + top as i32)).unwrap();                }            }        }        pos += mb.0.advance_width as i32;    }});
复制代码


用户头像

Miracle

关注

三十年资深码农 2019.10.25 加入

还未添加个人简介

评论

发布
暂无评论
Rust 元宇宙 5 —— SDL2.0