6
votes

I want to be able to input a bunch of vertices to my graphics program and then I want to be able to do the following on them:

  • Use them in the graphics part of OpenGL, especially in the Vertex Shader.
  • Do physics calculations on them in a Compute Shader.

By these requirements I figured that I need some structure in which I store my vertices and can access them correctly, I thought of the following:

  • ArrayBuffers
  • Textures (as in storing the information, not for texturing itself)

However I've thought and came up with drawbacks of both variants:

ArrayBuffers:

  • I'm unsure how my Compute Shader can read, let alone modify, the vertices. Yet I do know how to draw them.

Textures:

  • I know how to modify them in Compute Shaders, however I am unsure how to draw from a texture. More specifically, the number of elements needed to be drawn depends on the number of written (data not zero) elements in the texture.

I might have overlooked some important other features that suffice my need, so as the real question:
How do I create Vertices that reside on the GPU and which I can both access in the Vertex and in the Compute Shader?

1

1 Answers

14
votes

Hopefully this will clear up a few misconceptions, and give you a little bit better understanding of how general purpose shader storage is setup.

What you have to understand is how buffer objects really work in GL. You often hear people distinguish between things like "Vertex Buffer Objects" and "Uniform Buffer Objects". In reality, there is no fundamental distinction – a buffer object is treated the same way no matter what it stores. It is just a generic data store, and it only takes on special meaning while it is bound to a specific point (e.g. GL_ARRAY_BUFFER or GL_UNIFORM_BUFFER).

Do not think of special purpose vertex buffers residing on the GPU, think more generally – it is actually unformatted memory that you can read/write if you know the structure. Calls like glVertexAttribPointer (...) describe the data structure of the buffer object sufficiently for glDrawArrays (...) to meaningfully pull vertex attributes from the buffer object's memory for each vertex shader invocation.

You need to do the same thing yourself for compute shaders, as demonstrated below. You need to familiarize yourself with the rules discussed in 7.6.2.2 - Standard Uniform Block Layout to fully understand the following data structure.

Description of a vertex data structure using Shader Storage Blocks can be done like so:

// Compute Shader SSB Data Structure and Buffer Definition

struct VtxData {
   vec4  vtx_pos;       // 4N [GOOD] -- Largest base alignment
   vec3  vtx_normal;    // 3N [BAD]
   float vtx_padding7;  //  N (such that vtx_st begins on a 2N boundary)
   vec2  vtx_st;        // 2N [BAD]
   vec2  vtx_padding10; // 2N (in order to align the entire thing to 4N)
};                      // ^^ 12 * sizeof (GLfloat) per-vtx

// std140 is pretty important here, it is the only way to guarantee the data
//   structure is aligned as described above and that the stride between
//     elements in verts[] is 0.
layout (std140, binding = 1) buffer VertexBuffer {
   VtxData verts [];
};

This allows you to use an interleaved vertex buffer in a compute shader, with the data structure defined above. You have to be careful with data alignment when you do this... you could haphazardly use any alignment/stride you wanted for an interleaved vertex array ordinarily, but here you want to conform to the std140 layout rules. This means using 3-component vectors is not always a wise use of memory; you need things to be aligned on N (float), 2N (vec2) or 4N (vec3/vec4) boundaries and this often necessitates the insertion of padding and/or clever packing of data. In the example above, you could fit an entire 3-component vector worth of data in all the space wasted by alignment padding.

Pseudo-code showing how the buffer would be created and bound for dual-use:

struct Vertex {
   GLfloat pos       [4];
   GLfloat normal    [3];
   GLfloat padding7;
   GLfloat st        [2];
   GLfloat padding10 [2];
} *verts;

[... code to allocate and fill verts ...]

GLuint vbo;
glGenBuffers (1, &vbo);

glBindBuffer (GL_ARRAY_BUFFER, vbo);
glBufferData (GL_ARRAY_BUFFER, sizeof (Vertex) * num_verts, verts, GL_STATIC_DRAW);

glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 48,  0); // Vertex Attrib. 0
glVertexAttribPointer (1, 3, GL_FLOAT, GL_FALSE, 48, 16); // Vertex Attrib. 1
glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, 48, 32); // Vertex Attrib. 2

glBindBufferBase      (GL_SHADER_STORAGE_BUFFER, 1, vbo); // Buffer Binding 1