Skip to main content

Crate mew

Crate mew 

Source
Expand description

§WIP (but definitely working)

Maybe Easier wgpu, a thin (?) abstraction to hopefully help organize your 3 buffers, 20 bind groups, 13 vertex formats, and 7 pipelines. No other combination is allowed.

wgpu is a 1 0.98:1 API for the WebGPU standard. While that makes it very portable, versatile, and well-documented, since I’m not a hardcore graphics chud, I don’t find it very intuitive to use.

My main problem with it is how many bloody times you need to define the same thing, and quadruple-check that all the layouts line up with each other: first the BindGroupLayoutDescriptor, which turns into a BindGroupLayout, which is then used in a PipelineLayoutDescriptor by putting it into a BindGroupLayoutEntry
Wait, crap, I think I forgot a struct somewhere along the line.

§The point of this

This library aims to, at least a little bit, make it easier to define all the related information in one place (in wrapper structs). It primarily uses like 10 macro_rules! to write boilerplate that generates buffer, bind group, and pipeline layouts. It also stores them in a convenient “render context” struct.

My original goal wasn’t to simplify the wgpu API, rather just to follow a programming pattern I used, but with the amount of boilerplate this let me cut down on, it did kind of end up as a “simplification wrapper”. However, it still should be possible to lobotomize my code and mix-and-match your own custom handlers, so you can do the same funky lower-level stuff as you could if you use wgpu directly.

It eventually mutated and grew a couple helper structs though, see below.

§Not the point of this

As I mentioned, this isn’t supposed to dumb down the wgpu API, but rather make it easier to define everything. This library is for people who have already used wgpu at least once, so if you’re brand-new to graphics programming, this likely isn’t the super-simple graphics toolkit you’re looking for.

I should also point out, this doesn’t handle making windows. You can use winit for that, its API is actually quite usable directly, especially compared to wgpu. The example highlights integrating this and winit in practice, because, well, what else would an example do if not drawing to the screen?

Update: Not that long after, I made a smol helper struct that bundled a winit window with a wgpu surface and managed construction, which I found really helpful to cut down on boilerplate.
Not long after that, I tried dealing with WebGL and realized I had to interleave context creation with window creation… WHAT the FU-
Anyway, I made a helper struct to deal with that anti-pattern. It took a bit of mental gymnastics to figure out what needed to happen, but I hope my API is idiot-proof enough to keep myself from breaking everything, at least.
(Laughs in refactoring an already half-completed project)

§Final note

Because I’m not a graphics programming chud (my only “graphical” programming experience prior to wgpu is Processing, ShaderToy, a 3D wireframe demo in the terminal, raycasting in a 2D game engine, and Bevy), I don’t know the normal way to do proper graphics. All my knowledge of wgpu came from the docs, experimentation, and this tutorial, which is really handy though ever-so-slightly (very) outdated.

You’ll also need to excuse my weird code, using both really verbose traits in combination with macros where traits would be too painful (trust me, I tried). I use lots of type wrappers and generics to (maybe) make it easier to integrate your own types if you need any functionality that I missed.

§public static final note

§Usage

TL;DR all those pesky layout descriptors you need to keep track of are generated & stored in a central “context” object, and then fetched when needed. The context also holds the adapter & device, handles creating buffers & pipelines for you, etc.
Originally, I wrote a hacky “static map” tuple that mapped pipeline/bindgroup types to their descriptor objects using generic functions. It was cool and worked, but the biggest downside of it was that you need to declare all the things you’re going to use beforehand in the root context object, which was annoying if you had lots of graphics sub-modules. Now, it’s just one RenderContext struct defined in the crate, mapping type IDs dynamically once you use them the first time.

To get started, you need to define all the shader structs/types, buffers, samplers, textures, bind groups, and pipelines - put simply, anything that needs a ...Descriptor struct to create (excluding render passes, though that might come in a future update if I think I came up with a good abstraction).

// Vertex type, with an optional step of Vertex or Instance.
vertex_struct! { ExampleVertexStruct step Vertex [
    0 ints => Uint16x2,
    1 norms => Snorm8x4,
    2 floats => Float32x3,
] }

// Vertex buffer, sized by its "type" parameter <ExampleVertexStruct>.
buffer! { VertexBuffer <ExampleVertexStruct> as STORAGE | VERTEX | COPY_DST }

// Regular shader struct. Note how you need to manually number fields.
shader_struct! { ExampleShaderStruct [
    0 int => I32,
    1 mat => Mat4x4f,
    2 float => F32,
] }

// Shader struct buffer, same deal as VertexBuffer.
buffer! { ShaderBuffer <ExampleShaderStruct> as STORAGE | COPY_DST }

// Sampler with a mode, optionally setting other properties beyond the default.
sampler! { Sampler as Filtering {
    mag_filter: Nearest,
} }

// Texture with various properties
texture! { Texture {
    usage: TEXTURE_BINDING | COPY_DST,
    dimension: D2,
    sample_type: FLOAT,
} }

// Putting it all together in a bindgroup. Buffers are taken ownership of by
// bind group objects when they are created, but they are reference-counted by
// wgpu internally, so go nuts cloning them.
bind_group! { ExampleBindGroup [
    0 sampler @ FRAGMENT => Sampler,
    1 texture @ FRAGMENT => Texture,
    2 buffer @ FRAGMENT | VERTEX => ShaderBuffer,
] }

// Putting everything you've put together, together.
pipeline! { ExamplePipeline {
    bind_groups: [
        0 => ExampleBindGroup,
    ],
    vertex_types: [
        0 => ExampleVertexStruct,
    ],
    fragment_targets: [
        0 => ALPHA_BLENDING ALL as ANY,
    ],
    immediate_size: 4,
    depth: Some(Depth32Float),
    cull: Some(Front),
} }

Next, we initialize a RenderContext, either with the builder model or with a straight new(), and we’re basically ready to go.
To create a buffer, call RenderContext::new_buffer, with the desired length of the buffer. The only reason we need to define the “type parameter” in the buffer definition is because normally, buffer creations are measured by bytes.
To create a bind group, call RenderContext::new_bind_group, passing in a tuple of the inner bind groups. The bind group will take ownership but you can access them later.
Same goes for creating a pipeline, except RenderContext::new_pipeline takes a few more arguments about the target surface and shader.

And that’s the biggest pain point of wgpu done for you, no more wrangling with trying to keep 30 struct definitions in line with each other. The rest of the job is left to you, writing to the buffers and using the bind groups is basically the same as vanilla wgpu (do keep an eye out on derefs though, they’re a bit special - see the examples).

§Caveats

  • Uniform buffers are weird. Put all data in a matrix and hope for the best. I managed to correctly pad regular storage buffers though.
  • You can’t change some default values, like mipmap size. Probably coming in a future version.

§Problem?

It’s open source, fix it yourself fatass.

Shoot me an email at 64_Tesseract@protonmail.com, I’ll maybe respond. I can’t stand github & I’m definitely not uploading my code to microslop, so my issue tracker is email.

§Features

§android

Proxies feature android-native-activity in winit. Does nothing in the crate itself.

§winit

Default
Enables winit as a public dependency and provides some handy utilities under winitutils.

§webgl

Not compatible with poll
Proxies feature webgl in winit. Permits deferring RenderContext construction in a JS thread until a window is created, by way of winitutils::DeferredContext.

§poll

Not compatible with webgl
Enables pollster as a public dependency. Permits deferring RenderContext construction on native targets until a window is created, by way of winitutils::DeferredContext.

Re-exports§

pub use wgpu;
pub use winit;winit

Modules§

bindgroup
buffer
doc_example
Example module to show available methods on generated structs. Check the source code to see how they’re defined.
pipeline
prelude
sampler
shaderprimitive
Yes, these differ from crate::vertexformats.
shaderstruct
texture
vertexformat
Yes, these differ from crate::shaderprimitives.
vertexstruct
winitutilswinit
Utilities for working with windows & surfaces.

Macros§

bind_group
Define a bind group, with internal buffer types (and their field names), and their shader visibility (see wgpu::ShaderStages). All members are taken ownership of in the struct - good to note that they are reference-counted (Arc’d) by wgpu internally.
buffer
Define a storage buffer, with an optional internal type/struct to align its length with.
pipeline
Define a render pipeline, with internal bind group and vertex types.
sampler
Define a sampler.
shader_struct
Define a shader struct (to use in a storage or uniform buffer).
texture
Define a type of texture - not its size or format, but a set of properties.
vertex_struct
Define a vertex struct (to use in a vertex buffer).

Structs§

RenderContext
The heart of mew. Bundles all necessary wgpu handles and takes care of constructing buffers, bind groups, & pipelines with a single function call.
RenderContextBuilder
A builder abstraction for making a RenderContext.

Enums§

BuildContextError
Error returned when trying to build a context.