Day 6 (Dec 19, 2020) ~~~ Creating Shaders for the Vulkan Graphics Pipeline - kwilson33/learning-vulkan GitHub Wiki

Progress Today

I made it through these pages today of the official Vulkan tutorial. I started the Graphics pipeline basics part of Drawing a triangle after a 1 day break.

Here are some major takeaways

1. The graphics pipeline in Vulkan is almost completely immutable, unlike older APIs like OpenGL and Direct3D,

  • This means that you will have to create multiple pipelines to perform different combinations of states you want to use. For example, one pipeline may include the Fragment shader while another won't.
  • This takes time, but because all the steps are known in advance the driver can optimize it much better/faster (ahead of time compilation vs just in time compilation)

2. The graphics pipeline in Vulkan and fixed-function stages vs programmable stages,

Graphics pipeline Image from the Vulkan tutorial

  • The yellow stages, like the Fragment shader are programmable which means you can use your own code to do the operations exactly as you want.
  • On the other hand, green stages, like Rasterization are fixed-function stages meaning you can tweak their operations a little using parameters, but the way they work is predefined by the Vulkan API.

3. The geometry shader (a programmable stage) is not used much in today's applications.

  • The geometry shader is run on every primitive (like a triangle) and can discard it or output more prims than came in.
  • This is similar to the tessellation shader, except it's more flexible. The performance is not very good, and so it's not used often.

4. Shader code in Vulkan has to be specified in a bytecode format called SPIR-V.

  • This is different than human-readable syntax like GLSL (OpenGL shading language) and HLSL (High Level Shading Language used for DirectX)
  • SPIR-V is designed to be used with both Vulkan and OpenCL. It is used for both graphics and compute shaders.
  • The benefit is that compilers written by GPU vendors to turn shader code into company specific code are way less complex.
  • Also, with a more straighforward format, Vulkan uses SPIR-V in hopes that shader code is more shareable and not rejected based on different companies.
  • SPIR-V does NOT need to be written by hand. Khronos (the company that makes Vulkan) has their own vendor-independent compiler than compiles GLSL into SPIR-V. The compiler verifies that the GLSL code is compliant with the SPIR-V standards. There are other compilers that do this, for example glslc.exe made by Google. The benefit of using Google's compiler is that it uses the same parameter format as well-known compilers like GCC and Clang, and has some extra functionality like includes. Both these compilers are included in the Vulkan SDK.

5. How the vertex shader (VS) takes in position and normalizes it.

  • The VS processes each incoming vertex. It takes in attributes like world pos, color, normal, and texture coords.

  • The output of the VS is the final pos in clip coords and the attributes that need to be passed onto the fragment shader.

  • A clip coord is a 4D vector that is then turned into a normalized device coordinate by dividing the whole vector by its last component.

  • These normalized device coords are homogeneous coordinates, meaning they map the framebuffer to a [-1,1] by [-1, 1] coord system, like below

Coord system Image from the Vulkan tutorial.

  • For example, a clip coord in the VS may look like vec4(1.0, 2.0, 3.0, 1.0) where the first three params are X,Y,Z coords, and the last parameter is the value to divide by to get the normalized device coord. You can make this value 1.0 to not change anything.

6. With use of different entrypoints, like main, it's possible to combine multiple fragment shaders into one.

  • You specify the entrypoint when you are specifying the pipeline stage for a shader through VkPipelineShaderStageCreateInfo structures.

7. One optimization you can make is using the optional pSpecializationInfo field when creating shaders.

  • When specifying the pipelne stage for a shader through VkPipelineShaderStageCreateInfo structures, you can use this field to specify values for shader constants.
  • This means you can use a single shader module where its behavior can be configured at pipeline creation, kind of like Verilog parameters.
  • This is more efficient than configuring the shader using variables at render time, because the compiler can do optimizations like removing if statements that depend on these values.

See ya!

Now that the tutorial is getting into the graphics pipeline things are starting to make more sense. Today was probably the most fun I had with the tutorial, because it was stuff I had a little experience with from my intro to Computer Graphics class. I wrote a little GLSL in that class for shaders and today I learned how to do that (again) and in-depth what everything did (more so than my class). I also learned how to compile the shader code (GLSL) into shader bytecode (SPIR-V). I feel like I'm really learning a lot by going through each step painstakingly slow and it feels really good to make progress. Until tomorrow!