1 个不稳定版本

0.1.0 2023 年 8 月 4 日

#726图形 API

LGPL-3.0 或更高版 OR MPL-2.0

125KB
2K SLoC

theo

为所有窗口和图形后端提供通用的 piet 渲染上下文。

窗口框架,如 winit,默认不提供绘制到其中的方式。这个决定是有意为之的;它允许用户选择他们想要的图形后端,同时也使得维护窗口代码变得更加简单。对于游戏(winit最初是为这类游戏设计的),通常使用像 wgpuglow 这样的 3D 渲染上下文。然而,GUI 应用程序将需要一个 2D 向量图形上下文。

piet 是一种 2D 图形抽象,可以与许多不同的图形后端一起使用。然而,piet 的默认实现 piet-common 难以与其他窗口系统集成,除了不支持许多其他窗口系统支持的 druid-shell。因此,theo 通过提供一个通用的、易于与窗口系统集成的 piet 渲染上下文,旨在弥合这一差距。

与通过类似 cairo 和 DirectX 的绘图 API 相比,theo 直接使用 GPU API 来渲染到窗口,这可以提供更好的性能和更大的灵活性,同时也确保了渲染逻辑的大部分安全性。这还减少了最终程序需要依赖的动态依赖项数量。

theo优先考虑通用性和性能。默认情况下,theo使用优化的GPU后端进行渲染。如果GPU不可用,theo将回退到软件渲染。

使用示例

首先,用户必须创建一个Display,它代表系统的根显示。从这里,用户应该创建Surface,它代表绘图区域。最后,可以使用Surface创建RenderContext类型,用于绘图。

use piet::{RenderContext as _, kurbo::Circle};
use theo::{Display, Surface, RenderContext};

// Create a display using a display handle from your windowing framework.
// `my_display` is used as a stand-in for the root of your display system.
// It must implement `raw_window_handle::HasRawDisplayHandle`.
let mut display = unsafe {
    Display::builder()
        .build(&my_display)
        .expect("failed to create display")
};

// Create a surface using a window handle from your windowing framework.
// `window` is used as a stand-in for a window in your display system.
// It must implement `raw_window_handle::HasRawWindowHandle`.
let surface_future = unsafe {
    display.make_surface(
        &window,
        window.width(),
        window.height()
    )
};

// make_surface returns a future that needs to be polled.
let mut surface = surface_future.await.expect("failed to create surface");

// Set up drawing logic.
surface.on_draw(move || async move {
    // Create the render context.
    let mut ctx = RenderContext::new(
        &mut display,
        &mut surface,
        window.width(),
        window.height()
    ).expect("failed to create render context");

    // Clear the screen and draw a circle.
    ctx.clear(None, piet::Color::WHITE);
    ctx.fill(
        &Circle::new((200.0, 200.0), 50.0),
        &piet::Color::RED
    );

    // Finish drawing.
    ctx.finish().expect("failed to finish drawing");

    // If you don't have any other windows to draw, make sure the windows are
    // presented.
    display.present().await;
});

有关如何使用绘图API的更多信息,请参阅piet crate的文档。

后端

截至编写时,theo支持以下后端

  • wgpu后端(通过wgpu功能启用),使用piet-wgpu crate将渲染到窗口。此后端支持wgpu支持的所有图形API,包括Vulkan、Metal和DirectX 11/12。
  • glow后端(通过gl功能启用),使用piet-glow crate将渲染到窗口。在桌面平台上使用glutin创建OpenGL上下文,使用glow与OpenGL API交互。此后端支持OpenGL 3.2及以上版本。
  • 软件光栅化后端。使用tiny-skia将渲染到位图,然后使用softbuffer将位图复制到窗口。默认情况下启用此后端,并在没有其他后端可用时使用。

性能

由于theo实现了大部分自己的渲染逻辑,如果使用不当,可能会导致性能严重下降,尤其是在软件光栅化后端。在某些情况下,在调试模式下而不是发布模式下编译theo可能会将应用程序的帧率减半。如果您在使用theo时遇到帧率低的问题,请确保您正在发布模式下编译它。

此外,渐变画刷经过优化,仅需要计算一次实际渐变。但是,这意味着如果您每次都重新实例化画刷,则每次都会重新计算渐变。即使在硬件加速后端,这也可能导致性能严重下降。解决方案是缓存您使用的画刷。例如,而不是这样做

let gradient = /* ... */;
surface.on_draw(|| {
    let mut ctx = /* ... */;
    ctx.fill(&Circle::new((200.0, 200.0), 50.0), &gradient);
})

这样做,确保缓存渐变画刷

let gradient = /* ... */;
let mut gradient_brush = None;
surface.on_draw(|| {
    let mut ctx = /* ... */;
    let gradient_brush = gradient_brush.get_or_insert_with(|| {
        ctx.gradient_brush(gradient.clone()).unwrap()
    });
    ctx.fill(&Circle::new((200.0, 200.0), 50.0), gradient_brush);
})

theo明确选择了线程不安全模型。不仅线程不安全代码的效率更高,而且这些API类型通常都是线程不安全的。

许可

theo是自由软件:您可以根据以下任一条款重新分发和/或修改它

  • 自由软件基金会发布的GNU Lesser General Public License的第3版,或(根据您的选择)任何更高版本。
  • Mozilla Foundation发布的Mozilla Public License的第2版。

theo的发布是为了希望它会有所帮助,但没有任何保证;甚至没有对适销性或特定目的适用性的暗示保证。有关详细信息,请参阅GNU Lesser General Public License或Mozilla Public License。

您应该已经收到了GNU Lesser General Public License和Mozilla Public License的副本,以及theo。如果没有,请参阅https://gnu.ac.cn/licenses/

依赖项

~15–51MB
~816K SLoC