shader stage states
continuous-integration/drone/push Build is passing Details

main
cosmonaut 2021-01-28 12:52:39 -08:00
parent 9f38535a3d
commit d18233a906
5 changed files with 83 additions and 4 deletions

View File

@ -1,7 +1,7 @@
---
title: "Graphics Pipeline Layout Info"
date: 2021-01-27T15:04:37-08:00
weight: 3
weight: 4
---
You finally get a bit of a break for now, because this one is easy.

View File

@ -1,7 +1,7 @@
---
title: "Multisample State"
date: 2021-01-27T15:10:55-08:00
weight: 4
weight: 5
---
This is another basic one. `MultisampleCount` tells the pipeline how many samples should be used in rasterization, and `SampleMask` is a coverage mask used by the multisampling process. You can read about how that process works [here](https://www.khronos.org/registry/vulkan/specs/1.2/html/chap26.html#primsrast-multisampling).

View File

@ -1,7 +1,7 @@
---
title: "PrimitiveType"
date: 2021-01-27T15:32:11-08:00
weight: 5
weight: 6
---
`PrimitiveType` determines how a stream of vertex information will be interpreted as shapes by the pipeline. There are 5 types of primitives that MoonWorks supports.

View File

@ -1,7 +1,7 @@
---
title: "Rasterizer State"
date: 2021-01-27T15:30:01-08:00
weight: 6
weight: 7
---
Rasterization is, essentially, the process of converting 3D vertex information into pixels on a surface. There are many different ways that we may wish to control rasterization.

View File

@ -0,0 +1,79 @@
---
title: "ShaderStageState"
date: 2021-01-28T12:15:13-08:00
weight: 3
---
We described shader modules earlier, now we get to put them into practice. A shader stage state has three fields: a shader module, an "entry point name", and a "uniform buffer" size.
Let's have a look at a simple shader example:
```glsl
#version 450
layout(set = 3, binding = 0) uniform UniformBlock
{
float time;
} Uniforms;
layout(location = 0) in vec2 fragCoord;
layout(location = 0) out vec4 FragColor;
void main()
{
// Time varying pixel color
vec3 col = 0.5 + 0.5 * cos(Uniforms.time + fragCoord.xyx + vec3(0, 2, 4));
FragColor = vec4(col, 1.0);
}
```
This shader outputs for each pixel a color value dependent on a time value and the pixel's position on the screen.
Notice the "main" function here. This will be our "entry point". You can name your entry point function anything you want and pass it to the EntryPointName field. You can even have multiple entry points compiled in the same shader module! This is why it is called a "shader module" and not just a shader.
Finally, notice the "UniformBlock" above. This block describes "shader uniforms". Shader uniforms are used to pass in state to the shader that it needs to to do its job when that info doesn't come from the vertex shader.
To elaborate a bit: the graphics pipeline looks like this so far.
Vertex Input -> Vertex Shader -> Rasterizer -> Fragment Shader -> Framebuffer
Notice that our input into this pipeline comes from vertex input. But something like "elapsed time" applies to every pixel we need to shade and doesn't make sense to store with vertex data. So we pass that in as a uniform.
To define our "uniform buffer", we create a C# struct.
```cs
using MoonWorks.Math;
using System.Runtime.InteropServices;
namespace MyProject
{
[StructLayout(LayoutKind.Sequential)]
public struct ColorPhaseUniforms
{
public float Time;
public Vector3 Padding;
}
}
```
Why is that padding vector in there? SPIR-V shaders expect uniform data in 16-byte blocks. So if we just pass in a single float value, which is 4 bytes, SPIR-V will get confused. Also, for this reason, if you ever have a uniform value which is, for example, a Vector3, you will need to add an extra float value as padding. It's a little annoying, I know, but you'll get used to it.
Finally, `StructLayout` is a C# attribute that tells the runtime how it should lay out the data contained in the struct. Note that, at the end of the day, all data that lives in the computer is just a list of 0s and 1s, and how those 0s and 1s are interpreted depends on context. The layout you provide in the C# struct *has* to *exactly* match the order of fields you provide in the shader uniform block or the data will be interpreted incorrectly. It does *not* use the field names or anything like that. `LayoutKind.Sequential` means that the fields will appear in the exact order you list them in the struct definition. If you really want to, you can also specify exactly where the fields should go in byte order. by using the `FieldOffset` attribute above fields. If that makes no sense to you, don't worry about it and just use Sequential.
Back to our shader stage state. The final thing you need to provide is the size of the uniform block. For this I recommend using C#'s `Marshal.SizeOf` method, which will return the size of the provided struct type in bytes. This means that if you ever change your shader uniform struct in some way, you won't have to remember to update the numbers you provided to your shader stage states.
```cs
var myColorPhaseFragmentShaderModule = new ShaderModule(
GraphicsDevice,
"ColorPhaseFrag.spv"
);
var myFragmentShaderState = new ShaderStageState
{
ShaderModule = myColorPhaseFragmentShaderModule,
EntryPoint = "main",
UniformBufferSize = Marshal.SizeOf<ColorPhaseUniforms>()
};
```