[][src]Module luminance::pipeline

Graphics pipelines.

Graphics pipelines are the means used to describe — and hence perform — renders. They provide a way to describe how resources should be shared and used to produce a single pixel frame.

Pipelines and AST

luminance has a very particular way of doing graphics. It represents a typical graphics pipeline via a typed AST that is embedded into your code. As you might already know, when you write code, you’re actually creating an AST: expressions, assignments, bindings, conditions, function calls, etc. They all represent a typed tree that represents your program.

luminance uses that property to create a dependency between resources your GPU needs to have in order to perform a render. It might be weird at first but you’ll see how simple and easy it is. If you want to perform a simple draw call of a triangle, you need several resources:

  • A Tess that represents the triangle. It holds three vertices.
  • A shader Program, for shading the triangle with a constant color, for short and simple.
  • A Framebuffer, to accept and hold the actual render.
  • A RenderState, to state how the render should be performed.
  • And finally, a PipelineState, which allows even more customization on how the pipeline behaves

There is a dependency graph to represent how the resources must behave regarding each other:

(AST1)

PipelineState ─── Framebuffer ─── Shader ─── RenderState ─── Tess

The framebuffer must be active, bound, used — or whatever verb you want to picture it with — before the shader can start doing things. The shader must also be in use before we can actually render the tessellation.

That triple dependency relationship is already a small flat AST. Imagine we want to render a second triangle with the same render state and a third triangle with a different render state:

(AST2)

PipelineState ─── Framebuffer ─── Shader ─┬─ RenderState ─┬─ Tess
                                          │               │
                                          │               └─ Tess
                                          │
                                          └─ RenderState ─── Tess

That AST looks more complex. Imagine now that we want to shade one other triangle with another shader!

(AST3)

PipelineState ─── Framebuffer ─┬─ Shader ─┬─ RenderState ─┬─ Tess
                               │          │               │
                               │          │               └─ Tess
                               │          │
                               │          └─ RenderState ─── Tess
                               │
                               └─ Shader ─── RenderState ─── Tess

You can now clearly see the ASTs and the relationships between objects. Those are encoded in luminance within your code directly: lambdas / closures.

If you have followed thoroughly, you might have noticed that you cannot, with such ASTs, shade a triangle with another shader but using the same render state as another node. That was a decision that was needed to be made: how should we allow the AST to be shared? In terms of graphics pipeline, luminance tries to do the best thing to minimize the number of GPU context switches and CPU <=> GPU bandwidth congestion.

The lambda & closure design

A function is a perfect candidate to modelize a dependency: the arguments of the function modelize the dependency — they will be provided at some point in time, but it doesn’t matter when while writing the function. We can then write code depending on something without even knowing where it’s from.

Using pseudo-code, here’s what the ASTs from above look like: (this is not a real luminance, excerpt, just a simplification).

This example is not tested
// AST1
pipeline(framebuffer, pipeline_state, || {
  // here, we are passing a closure that will get called whenever the framebuffer is ready to
  // receive renders
  use_shader(shader, || {
    // same thing but for shader
    use_render_state(render_state, || {
      // ditto for render state
      triangle.render(); // render the tessellation
    });
  );
);

See how simple it is to represent AST1 with just closures? Rust’s lifetimes and existential quantification allow us to ensure that no resource will leak from the scope of each closures, hence enforcing memory and coherency safety.

Now let’s try to tackle AST2.

This example is not tested
// AST2
pipeline(framebuffer, pipeline_state, || {
  use_shader(shader, || {
    use_render_state(render_state, || {
      first_triangle.render();
      second_triangle.render(); // simple and straight-forward
    });

    // we can just branch a new render state here!
    use_render_state(other_render_state, || {
      third.render()
    });
  );
);

And AST3:

This example is not tested
// AST3
pipeline(framebuffer, pipeline_state, || {
  use_shader(shader, || {
    use_render_state(render_state, || {
      first_triangle.render();
      second_triangle.render(); // simple and straight-forward
    });

    // we can just branch a new render state here!
    use_render_state(other_render_state, || {
      third.render()
    });
  );

  use_shader(other_shader, || {
    use_render_state(yet_another_render_state, || {
      other_triangle.render();
    });
  });
);

The luminance equivalent is a bit more complex because it implies some objects that need to be introduced first.

PipelineGate and Pipeline

A PipelineGate represents a whole AST as seen as just above. It is created by a GraphicsContext when you ask to create a pipeline gate. A PipelineGate is typically destroyed at the end of the current frame, but that’s not a general rule.

Such an object gives you access, via the PipelineGate::pipeline, to two other objects :

A Pipeline is a special object you can use to use some specific scarce resources, such as textures and buffers. Those are treated a bit specifically on the GPU, so you have to use the Pipeline interface to deal with them.

Creating a PipelineGate requires two resources: a Framebuffer to render to, and a PipelineState, allowing to customize how the pipeline will perform renders at runtime.

ShadingGate

When you create a pipeline, you’re also handed a ShadingGate. A ShadingGate is an object that allows you to create shader nodes in the AST you’re building. You have no other way to go deeper in the AST.

That node will typically borrow a shader Program and will move you one level lower in the graph (AST). A shader Program is typically an object you create at initialization or at specific moment in time (i.e. you don’t create them each frame) that tells the GPU how vertices should be transformed; how primitives should be moved and generated, how tessellation occurs and how fragment (i.e. pixels) are computed / shaded — hence the name.

At that level (i.e. in that closure), you are given two objects:

The ProgramInterface is the only way for you to access your uniform interface. More on this in the dedicated section. It also provides you with the ProgramInterface::query method, that allows you to perform dynamic uniform lookup.

RenderGate

A RenderGate is the second to last gate you will be handling. It allows you to create render state nodes in your AST, creating a new level for you to render tessellations with an obvious, final gate: the TessGate.

The kind of object that node manipulates is RenderState. A RenderState — a bit like for PipelineGate with PipelineState — enables to customize how a render of a specific set of objects (i.e. tessellations) will occur. It’s a bit more specific to renders than pipelines.

TessGate

The TessGate is the final gate you use in an AST. It’s used to create tessellation nodes. Those are used to render actual Tess. You cannot go any deeper in the AST at that stage.

TessGates don’t immediately use Tess as inputs. They use TessView. That type is a simple GPU view into a GPU tessellation (Tess). It can be obtained from a Tess via the View trait or built explicitly.

Structs

BoundBuffer

A bound Buffer.

BoundTexture

A bound Texture.

BufferBinding

Opaque buffer binding.

Pipeline

A GPU pipeline handle.

PipelineGate

Top-most node in a graphics pipeline.

PipelineState

Various customization options for pipelines.

Render

Output of a PipelineGate.

TextureBinding

Opaque texture binding.

Enums

PipelineError

Possible errors that might occur in a graphics Pipeline.

Viewport

The viewport being part of the PipelineState.