Rust文本渲染库glyph_brush的使用,高效处理2D文本和字形布局的Rust插件库

Rust文本渲染库glyph_brush的使用,高效处理2D文本和字形布局的Rust插件库

glyph_brush是一个使用ab_glyph的快速缓存文本渲染库。它提供了与渲染API无关的栅格化和绘制缓存逻辑。

该库广泛使用缓存来优化帧性能:

  • GPU纹理缓存逻辑,动态维护已渲染字形的GPU纹理
  • 缓存字形布局输出,避免在连续帧上重复渲染相同文本的成本
  • 重用布局以优化变更后相似布局的计算
  • 每个部分的顶点生成都被缓存,并在变更时重新组装成总顶点数组
  • 当相同文本在连续帧上渲染时,避免任何布局或顶点计算

该库设计为可以轻松包装,以创建特定渲染API的便捷版本,例如gfx-glyph。

基本用法示例

use glyph_brush::{ab_glyph::FontArc, BrushAction, BrushError, GlyphBrushBuilder, Section, Text};

let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?;
let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build();

glyph_brush.queue(Section::default().add_text(Text::new("Hello glyph_brush")));
glyph_brush.queue(some_other_section);

match glyph_brush.process_queued(
    |rect, tex_data| update_texture(rect, tex_data),
    |vertex_data| into_vertex(vertex_data),
) {
    Ok(BrushAction::Draw(vertices)) => {
        // 绘制新顶点
    }
    Ok(BrushAction::ReDraw) => {
        // 重新绘制上一帧未修改的顶点
    }
    Err(BrushError::TextureTooSmall { suggested }) => {
        // 扩大纹理+glyph_brush纹理缓存并重试
    }
}

完整示例DEMO

以下是一个更完整的示例,展示了如何使用glyph_brush库与wgpu渲染器结合使用:

use glyph_brush::{
    ab_glyph::{FontArc, PxScale}, 
    GlyphBrushBuilder, Section, Text
};
use wgpu::{util::DeviceExt, *};

struct TextRenderer {
    glyph_brush: GlyphBrush<()>,
    texture: Texture,
    vertex_buffer: Option<Buffer>,
    texture_size: u32,
    device: Device,
    queue: Queue,
}

impl TextRenderer {
    pub async fn new() -> Self {
        // 1. 加载字体
        let font_data = include_bytes!("DejaVuSans.ttf");
        let font = FontArc::try_from_slice(font_data).unwrap();
        
        // 2. 创建WGPU实例
        let instance = Instance::new(Backends::PRIMARY);
        let adapter = instance.request_adapter(&RequestAdapterOptions::default())
            .await
            .unwrap();
        
        let (device, queue) = adapter.request_device(&DeviceDescriptor::default(), None)
            .await
            .unwrap();

        // 3. 初始化glyph_brush
        let glyph_brush = GlyphBrushBuilder::using_font(font).build();
        
        // 4. 创建初始纹理
        let texture_size = 1024;
        let texture = device.create_texture(&TextureDescriptor {
            size: Extent3d {
                width: texture_size,
                height: texture_size,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: TextureDimension::D2,
            format: TextureFormat::R8Unorm,
            usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
            label: Some("glyph_brush_texture"),
        });

        Self {
            glyph_brush,
            texture,
            vertex_buffer: None,
            texture_size,
            device,
            queue,
        }
    }

    pub fn draw_text(&mut self, text: &str, position: (f32, f32), color: [f32; 4], scale: f32) {
        self.glyph_brush.queue(Section {
            screen_position: position,
            bounds: (f32::INFINITY, f32::INFINITY),
            text: vec![
                Text::new(text)
                    .with_scale(PxScale::from(scale))
                    .with_color(color),
            ],
            ..Section::default()
        });
    }

    pub fn render(&mut self) {
        let result = self.glyph_brush.process_queued(
            |rect, data| {
                // 更新纹理数据
                self.queue.write_texture(
                    ImageCopyTexture {
                        texture: &self.texture,
                        mip_level: 0,
                        origin: Origin3d {
                            x: rect.min[0],
                            y: rect.min[1],
                            z: 0,
                        },
                        aspect: TextureAspect::All,
                    },
                    data,
                    ImageDataLayout {
                        offset: 0,
                        bytes_per_row: Some(rect.width()),
                        rows_per_image: Some(rect.height()),
                    },
                    Extent3d {
                        width: rect.width(),
                        height: rect.height(),
                        depth_or_array_layers: 1,
                    },
                );
            },
            |vertices| {
                // 创建顶点缓冲区
                self.vertex_buffer = Some(
                    self.device.create_buffer_init(&util::BufferInitDescriptor {
                        label: Some("Text Vertex Buffer"),
                        contents: bytemuck::cast_slice(vertices),
                        usage: BufferUsages::VERTEX,
                    })
                );
            },
        );

        if let Err(BrushError::TextureTooSmall { suggested }) = result {
            // 纹理太小,需要调整大小
            self.texture_size = suggested.width.max(suggested.height);
            self.texture = self.device.create_texture(&TextureDescriptor {
                size: Extent3d {
                    width: self.texture_size,
                    height: self.texture_size,
                    depth_or_array_layers: 1,
                },
                ..TextureDescriptor::default()
            });
            // 重试渲染
            self.render();
        }
    }
}

示例运行

可以通过运行以下命令查看opengl示例:

cargo run --example opengl --release

这个库提供了高效的2D文本渲染和字形布局功能,特别适合需要频繁更新文本内容的图形应用程序。


1 回复

Rust文本渲染库glyph_brush的使用指南

简介

glyph_brush是一个高效的2D文本和字形布局处理库,专为Rust设计。它建立在glyph_brush_layout和底层图形API(如gfx、wgpu等)之上,提供了高性能的文本渲染解决方案。

主要特点:

  • 高效的文本缓存和批处理
  • 自动布局和重排
  • 支持多行文本
  • 自定义字体支持
  • 与多种图形后端兼容

安装

在Cargo.toml中添加依赖:

[dependencies]
glyph_brush = "0.7"
glyph_brush_layout = "0.2"

基本使用方法

1. 初始化

use glyph_brush::{GlyphBrush, GlyphBrushBuilder};
use glyph_brush_layout::{FontId, Section, Text};

// 加载字体
let font_data = include_bytes!("path/to/font.ttf");
let font = glyph_brush::ab_glyph::FontRef::try_from_slice(font_data).unwrap();

// 创建GlyphBrush
let mut glyph_brush = GlyphBrushBuilder::using_font(font).build();

2. 渲染文本

// 定义要渲染的文本部分
let section = Section {
    screen_position: (30.0, 30.0),
    bounds: (300.0, 200.0),
    text: vec![
        Text::new("Hello, glyph_brush!")
            .with_color([1.0, 1.0, 1.0, 1.0])
            .with_scale(40.0),
    ],
    ..Section::default()
};

// 队列文本
glyph_brush.queue(section);

// 绘制(需要提供图形后端的上下文)
// glyph_brush.draw_queued(...);

3. 多字体支持

// 加载多个字体
let font1 = ...;
let font2 = ...;

let mut glyph_brush = GlyphBrushBuilder::using_fonts(vec![font1, font2]).build();

// 使用不同字体
let section = Section {
    text: vec![
        Text::new("Font 1").with_font_id(FontId(0)),
        Text::new(" and Font 2").with_font_id(FontId(1)),
    ],
    ..Section::default()
};

高级用法

自定义布局

use glyph_brush_layout::{HorizontalAlign, VerticalAlign, Layout};

let section = Section {
    layout: Layout::Wrap {
        line_breaker: Default::default(),
        h_align: HorizontalAlign::Center,
        v_align: VerticalAlign::Center,
    },
    ..section
};

性能优化

对于频繁更新的文本,可以使用GlyphBrush::keep_cached来保留缓存:

glyph_brush.keep_cached(|_| true);  // 保留所有缓存

与wgpu集成示例

// 创建wgpu渲染器后
let mut glyph_brush = GlyphBrushBuilder::using_font(font)
    .initial_cache_size((2048, 2048))
    .build(wgpu_device, wgpu_swap_chain_format);

// 渲染循环中
glyph_brush.queue(section);
glyph_brush
    .draw_queued(
        wgpu_device,
        &mut staging_belt,
        &mut encoder,
        wgpu_texture_view,
        wgpu_screen_width,
        wgpu_screen_height,
    )
    .unwrap();

完整示例代码

下面是一个完整的wgpu集成示例,展示如何使用glyph_brush渲染文本:

use wgpu::{Device, Queue, SurfaceConfiguration, CommandEncoder, TextureView};
use wgpu::util::DeviceExt;
use glyph_brush::{GlyphBrush, GlyphBrushBuilder};
use glyph_brush_layout::{FontId, Section, Text};

async fn run() {
    // 初始化wgpu
    let instance = wgpu::Instance::new(wgpu::Backends::all());
    let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
        power_preference: wgpu::PowerPreference::default(),
        compatible_surface: None,
    }).await.unwrap();
    
    let (device, queue) = adapter.request_device(
        &wgpu::DeviceDescriptor {
            features: wgpu::Features::empty(),
            limits: wgpu::Limits::default(),
            label: None,
        },
        None
    ).await.unwrap();

    // 加载字体
    let font_data = include_bytes!("path/to/font.ttf");
    let font = glyph_brush::ab_glyph::FontRef::try_from_slice(font_data).unwrap();

    // 创建glyph_brush实例
    let mut glyph_brush = GlyphBrushBuilder::using_font(font)
        .initial_cache_size((2048, 2048))
        .build(&device, wgpu::TextureFormat::Bgra8UnormSrgb);

    // 渲染循环
    loop {
        // 创建命令编码器
        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
            label: Some("Render Encoder"),
        });

        // 定义要渲染的文本
        let section = Section {
            screen_position: (100.0, 100.0),
            bounds: (500.0, 300.0),
            text: vec![
                Text::new("Hello wgpu + glyph_brush!")
                    .with_color([1.0, 1.0, 1.0, 1.0])
                    .with_scale(40.0),
                Text::new("\n高性能文本渲染")
                    .with_color([0.5, 0.8, 1.0, 1.0])
                    .with_scale(30.0),
            ],
            ..Section::default()
        };

        // 队列并绘制文本
        glyph_brush.queue(section);
        
        // 假设我们已经获取了texture_view
        let texture_view = ...; // 实际应用中需要从surface获取
        
        glyph_brush.draw_queued(
            &device,
            &mut staging_belt,
            &mut encoder,
            &texture_view,
            800,  // 屏幕宽度
            600,  // 屏幕高度
        ).unwrap();

        // 提交命令缓冲区
        queue.submit(std::iter::once(encoder.finish()));
    }
}

注意事项

  1. glyph_brush需要与图形API配合使用,单独使用时只能处理布局
  2. 频繁修改文本内容会影响性能,尽量重用Section对象
  3. 对于静态文本,考虑缓存渲染结果
  4. 大文本量时注意调整初始缓存大小

通过合理使用glyph_brush,可以在Rust应用中实现高性能的2D文本渲染效果。

回到顶部