gfx-rs nuts and bolts

gfx-rs is a project bringing efficient cross-platform graphics to rust. This blog supposedly hosts the major milestones, concepts, and recaps of the project.

Device Abstraction

01 Mar 2015

Up until now, gfx-rs wasn’t ready to support multiple APIs. We had a tight coupling of the device with OpenGL code by using direct typedefs to GLuint types behind GL resources. We couldn’t figure out how to best abstract it before associated types were introduced to Rust. It is a really powerful tool in hands of Rust programmers, and we weren’t shy of exploring the limits of it.

An abstract device has different types associated with it: buffer, texture, sampler, and others. They are used in 2 large groups of methods:

  1. Inside CommandBuffer trait, which uses these types for rendering.
  2. In the resource management routines (create_buffer() and such), which belong to the device.

Since both CommandBuffer and the Device needed access to all the resource types, we decided to enclose them into a separate Resources trait, which became an associated type for our command buffers and devices:

pub trait Resources: {
    type Buffer;
    type ArrayBuffer;
    type Shader;
    type Program;
    type FrameBuffer;
    type Surface;
    type Texture;
    type Sampler;

Here is a part of the Device trait:

pub trait Device {
    type Resources: Resources;
    type Mapper: Clone + RawMapping;
    type CommandBuffer: draw::CommandBuffer<Resources = Self::Resources>;
    fn submit(&mut self, buffer: (&Self::CommandBuffer, &draw::DataBuffer));
    fn create_buffer<T>(&mut self, num: usize, usage: BufferUsage) -> BufferHandle<Self::Resources, T>
    fn create_sampler(&mut self, info: tex::SamplerInfo) -> SamplerHandle<Self::Resources>;

The GL backend implements Resources with a phantom enum:

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum GlResources {}

impl Resources for GlResources {
    type Buffer = Buffer;
    type ArrayBuffer = ArrayBuffer;
    type Shader = Shader;
    type Program = Program;
    type FrameBuffer = FrameBuffer;
    type Surface = Surface;
    type Texture = Texture;
    type Sampler = Sampler;

The migration was started by @bjz in a long series of pull requests (#564, #589, #590, #592, #593, #597) and finished by @kvark (gfx-rs/#598, gfx-rs/#609, gfx_device_gl/#1). The result - gfx_device_gl is a separate crate now that gfx itself doesn’t depend on. Examples are moved into their own home as well. Even the macro-generated implementations for shader parameters and vertex formats are API-independent now.

Needless to say, all of the device abstraction mess is just a burden on our users (sorry!) until we really have more devices. The good news is we are actually ready now to add these, and API fluctuations will be smaller. All the libraries on top of gfx-rs (gfx_texture, gfx_graphics, gfx_debug_draw, etc) as well as large parts of the client code (see hematite) don’t rely on OpenGL.

Current state of our architecture is not solid - we are still in flux, but the important milestone is nevertheless reached. We are now looking to simplify things, both internally and user-exposed:

  • group up resource management methods
  • simplify handle types
  • make examples library device independent, and hook up device-specific examples (gfx_device_gl/examples) to it, while having only minimal code for window/device initialization and event handling

Thanks everyone for supporting us, bearing with our breaking changes, providing fair feedback, and building incredible things with our cross-API technology (snowmew, hematite)!