Veracity in GPU Function Terminology

You keep using that word. I do not think it means what you think it means.” – Inigo Montoya

Yo, Shader* People!

Unless you’re only using varyings in GLSL, you need to have a name for the data structure that…

  1. your vertex functions output
  2. your fragment functions consume

I’ve spent a good portion of the past half-decade reading shaders, and I’ve yet to find anyone using a name for that structure that I’ve considered to be accurate and precise. Most of that time, the shaders I’ve written have been in GLSL, so I didn’t pay this subject much mind, until this year, when I started writing in the Metal shading language. Before diving too deep into the befuddling abyss whence springs the problem, let’s look at some example usage.

Apple’s Metal Sample Code

Even in Apple’s own sample code, all released within the span of a few months, there’s a good amount of variety, and hardly any consistency! Aside from one other example I’ll get to later in this post, Apple’s sample shaders represent all the kinds of names that I remember that I’ve seen.

Deferred Lighting

Each shader in this project uses the same structure name.

struct VertexOutput
{
    float4 position [[position]];
};
struct VertexOutput
{
    float4 position [[position]];
    float3 v_texcoord;
};
struct VertexOutput
{
    float4 position [[position]];
    float3 v_view;
};
struct VertexOutput
{
    float4 pos [[position]];
    float3 color;
    float pointSize [[point_size]];
};
struct VertexOutput
{
    float4 position [[position]];
    float4 v_shadowcoord;
    float3 v_normal;
    float3 v_tangent;
    float3 v_bitangent;
    float3 v_texcoord;
    float v_lineardepth;
};

VertexOutput is indeed one thing that the data structure describes, but it’s not the whole story. Why is it not FragmentInput, RasterizerInput, or RasterizerOutput?

I do like that whoever wrote this code understood that they didn’t need to bother coming up with different names for every structure. It serves the same purpose within each file.

Video Capture

Same name, just with prefixes:

struct CubeVertexOutput
{
    float4 position [[position]];
    float3 texCoords;
};
struct EnvMapVertexOutput
{
    float4 position [[position]];
    float4 eye;
    half4 color;
    float3 eye_normal;
    float3 normal;
    float2 uv;
};

The prefixes aren’t necessary, because they’re file-scoped, in files that are named rather well (skybox.metal and environmentMapReflection.metal). The prefixes could be chalked up to educational purposes, but if they’re redundant, wouldn’t that be teaching bad practice?

Image Processing

// Vertex input/output structure for passing results
// from a vertex shader to a fragment shader
struct VertexIO
{
    float4 m_Position [[position]];
    float2 m_TexCoord [[user(texturecoord)]];
};

In theory, this comment would be helpful to some members of the target audience. However, it’s inaccurate, due to not taking the rasterizer into account. I also find “IO” to be confusing. Isn’t VertexOI the correct order, to match up with that comment?

Oi to the World

Textured Quad and Vertex Streaming

…use the same terminology, albeit unabbreviated and without comments:

struct VertexInOut
{
    float4 m_Position [[position]];
    float2 m_TexCoord [[user(texturecoord)]];
};
struct VertexInOut
{
    float4  position [[position]];
    float4  color;
};

The words are related enough to what’s going on, that I can meaningfully use the structure, but I don’t feel like I know the intent of the author. What did they think was going in, and coming out, of what?

Aside from a transformation matrix multiplication in Textured Quad, data associated with vertexes is coming through unaltered, in these vertex functions, so maybe that’s the IO in question? But, neither vertex function takes a VertexInOut as a parameter, and that concept doesn’t match up with the comment from Image Processingsad

Basic 3D

struct ColorInOut {
    float4 position [[position]];
    half4 color;
};

The name isn’t completely accurate, because color isn’t the only datum, but we know that position is always going to be in the structure returned from a vertex shader, so Color does describe the unique data.

As for “InOut” in this case, color isn’t based any vertex data input, unlike what we saw in Textured Quad and Vertex Streaming, so either it’s a mistake, I don’t get it, or, as I’m about to argue, nobody does yet.

Shader Showcase

struct ColorInOut {
    float4 position [[position]];
    float3 normal_cameraspace;
    float3 eye_direction_cameraspace;
    float3 light_direction_cameraspace;
};
struct ColorInOut {
    float4 position [[position]];
    float2 uv;
    float3 light_direction_tangentspace;
    float3 eye_direction_tangentspace;
};
struct ColorInOut {
    float4 position [[position]];
    float point_size [[point_size]];
    float t;
    float lifespan;
};
struct ColorInOut {
    float4 position [[position]];
    float3 normal_cameraspace;
    float3 eye_direction_cameraspace;
    float3 light_direction_cameraspace;
    float3 position_cameraspace;
};
struct ColorInOut {
    float4 position [[position]];
    float3 normal_cameraspace;
    float3 eye_direction_cameraspace;
    float3 light_direction_cameraspace;
    float distance_to_object;
};
struct ColorInOut {
    float4 position [[position]];
    float shine;
    float3 normal_cameraspace;
    float3 eye_direction_cameraspace;
    float3 light_direction_cameraspace;
};
struct ColorInOut {
    float4 position [[position]];
    float3 normal_cameraspace;
    float3 eye_direction_cameraspace;
    float3 light_direction_cameraspace;
    float3 position_modelspace;
    float3 position_cameraspace;
};

I have to hypothesize that the same person who wrote the Basic 3D shaders wrote these right afterwards. The name of the structure is identical, but not a single one of these actually contains data that is considered a “color”. Demonstrably, accurate naming doesn’t matter, for desired graphical results. Why don’t we just standardize a generic default, so nobody has to think about the naming? I believe that naming the structure is useless cognitive overload in every case; I’m interested in evidence to the contrary.

Uniform Streaming

struct VertexOutput
{
    float4 m_Position [[position]];
    float2 m_TexCoord [[user(texturecoord)]];
};
struct FragmentInput
{
    float4 m_Position [[position]];
    float2 m_TexCoord [[user(texturecoord)]];
};

Interesting! This is the first time I’ve seen this. Being able to take advantage of function signature matching is a danial boon, but to use the feature only in order to give a struct two names, instead of one, when a single good name would do? I don’t think that’s a worthwhile use case.

Unity

Unity’s standard is the term v2f, which actually does a good job of telling the structure’s story (it stands for vertex to fragment). Unfortunately, it isn’t actually a name: the efficiency of using three typed characters doesn’t translate to speech. Vertofrag would be a more practical option, in the same spirit.

Also, I think the sensational spelling of to is an especially poor match for graphics code; “2f” has multiple meaning in this environment (e.g. “2, as a floating point value” or “two floating-point values“). I was initially confused about the name, because of that, and although I’ve now understood it for years, I think there will always be some small, but unnecessary, cognitive load for me, with v2f.

Tooth Tooth

Inputs and Outputs

Vertex function

I used to have the idea that a vertex shader operated on vertexes (along with uniform data), and also returned vertexes as output. OpenGL reinforced this idea, via its concept of vertex attributes: I’d only have access to the data from one vertex, in the shader.

Metal, however, enlightened me that my belief was false. Reading “vertex data” actually involves indexing into an array: the drawPrimitives function allows you to specify what indexes you’ll be supplied, and you can use those numbers, with the [[vertex_id]] parameter, to access any data in that array! Whether that’s actually useful or not, I don’t know, but I now understand that vertexes are not inputs to vertex shaders.

As someone who started off in 3D modeling, before moving onto 3D programming, this is a bit counterintuitive. The first piece of 3D asset creation data is generally a model, which is constructed by creating vertexes, moving them around, and assigning them meaningful properties other than position, such as texture coordinates and colors. The vertex function does use the model’s data, but in the form of reconstruction. What comes out is not the same thing that went in. The stuff that you manually model is actually an ingredient to what a vertex is, not a vertex itself.

Accuracy and Precision

The vertex function is a constructor for a vertex.
The data structure I’ve been talking about in this article is the same data that a vertex comprises.

…so, is the name for the structure “Vertex”? If this output were the whole story, I’d say yes! But, it rarely is. There may be tessellation and geometry stages, on other platforms, but for now, with Metal, we at least should plan for the

Fragment function

Aside from the special case of blending, I don’t recall having the notion that a fragment shader took a fragment as input, presumably because I never manually constructed fragments. (Had I done more 2D digital art, this might not have been true.) I also don’t recall thinking that they took vertexes as input, but the conventions shown above don’t make it obvious why that wouldn’t be true.

What’s Missing?

The crucial step performed by the rasterizer is not reflected in code that uses a “VertexOutput” or “VertexInOut” as a parameter for a fragment function. Rasterization is not a programmable step that necessitates a function, in your shader file, but it, not the fragment function, is the next process that your vertexes go through, after construction.

Is the only solution, to avoid prevarication, to copypaste a structure, and give it different names for the different things that comprise it, à la Uniform Streaming? I don’t think so.

Rastexes

I propose that, when generating a structure that will be interpolated by a rasterizer, using data from vertexes, we use the word rastex.

While a vertex function does construct a vertex, and the rasterizer interpolates vertexes’ data, the data structure that we actually need will be rasterized/interpolated vertex data. The output of a vertex shader is only useful to the rasterizer; it’s not an “input” to a fragment function. I’ll call that input a rastex, short for “rasterized vertex data”.

Is it accurate to call the output of a vertex function a rastex, if the vertex hasn’t been rasterized? Perhaps not 100%, but I think it’s a big improvement, and a reasonable compromise, without a language feature that could avoid struct duplication.

When we call a vertex a rastex:

  • It can mean “vertex to be rasterized”.
  • It’s made of the same data as its rasterized version, under the simplest filtering conditions.

As people sometimes shorten “vertex” to “vert”, I believe we can optionally use “rax”, for short. (“Rast” doesn’t work, because it doesn’t include a syllable from “vertex”.)

Raxcoswell Antialiasing

Rax, the Eternal Champion of Cyborganic Hemispherical Rasterization

*What Is this “Shading” We’re Doing?

To go with my old belief about vertex shaders taking vertexes as inputs, I believed that the output of a vertex shader represented a “shaded” version of its input. This didn’t translate to fragment shaders; I always had the idea that the fragment shader was a constructor of a fragment. Now that I’m thinking of vertex and fragment both describing outputs, does that mean that “shade” and “construct” are synonyms?…

Relevant definitions of “shade” from dictionary.com

  • to introduce degrees of darkness into (a drawing or painting)
    in order to render light and shadow or give the effect of color.
  • to render the values of light and dark in (a drawn figure, object,etc.),
    especially in order to create the illusion of three-dimensionality.

…I think that it’s possible that they were synonyms for some piece of hardware, in some era, and for whatever reason, the term “shader” stuck, but it has long-outlived any accuracy it might have had:

  • Fragment Shader
    • Some of the calculations done in forward rendering are not “shading”.
    • Most of the fragment calculations done in deferred rendering are not “shading”.
  • Vertex Shader
    • The A7 processor is nearly two years old. “Shading” will only ever again be performed per-vertex as a last resort.
  • Compute Shader“, “Tessellation Shader“, “Geometry Shader
    • “Shading” very unlikely!

The Swift/Objective-C Metal API doesn’t contain the word “shader”, opting instead for “function”, which might be exactly as general as we need. And yet, Apple hasn’t offered a more accurate name than “Metal shading language”, for the rest of the Metal API. If we do actually need something more than vertex/fragment/geometry constructor, compute/tesselation function, and Metal programming language, then I vote for “doozer” (that can be short for shadoozer, if you want to be sentimental) and “doozing”. And if you’re good with that, and then want to use “fraggle” instead of “rastex”, that will do pigly.

Doozers

Leave a Reply

Your email address will not be published. Required fields are marked *