From d18233a9065d1e7527ea6f0e7e6bdd157dff97c6 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 28 Jan 2021 12:52:39 -0800 Subject: [PATCH] shader stage states --- .../GraphicsPipelineLayoutInfo.md | 2 +- .../GraphicsPipeline/MultisampleState.md | 2 +- .../GraphicsPipeline/PrimitiveType.md | 2 +- .../GraphicsPipeline/RasterizerState.md | 2 +- .../GraphicsPipeline/ShaderStageState.md | 79 +++++++++++++++++++ 5 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 content/Graphics/Resources/GraphicsPipeline/ShaderStageState.md diff --git a/content/Graphics/Resources/GraphicsPipeline/GraphicsPipelineLayoutInfo.md b/content/Graphics/Resources/GraphicsPipeline/GraphicsPipelineLayoutInfo.md index 4268f59..9dcc4e3 100644 --- a/content/Graphics/Resources/GraphicsPipeline/GraphicsPipelineLayoutInfo.md +++ b/content/Graphics/Resources/GraphicsPipeline/GraphicsPipelineLayoutInfo.md @@ -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. diff --git a/content/Graphics/Resources/GraphicsPipeline/MultisampleState.md b/content/Graphics/Resources/GraphicsPipeline/MultisampleState.md index ac3aed3..ada508b 100644 --- a/content/Graphics/Resources/GraphicsPipeline/MultisampleState.md +++ b/content/Graphics/Resources/GraphicsPipeline/MultisampleState.md @@ -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). diff --git a/content/Graphics/Resources/GraphicsPipeline/PrimitiveType.md b/content/Graphics/Resources/GraphicsPipeline/PrimitiveType.md index b9f94aa..5daaa73 100644 --- a/content/Graphics/Resources/GraphicsPipeline/PrimitiveType.md +++ b/content/Graphics/Resources/GraphicsPipeline/PrimitiveType.md @@ -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. diff --git a/content/Graphics/Resources/GraphicsPipeline/RasterizerState.md b/content/Graphics/Resources/GraphicsPipeline/RasterizerState.md index cb2a1ed..439febe 100644 --- a/content/Graphics/Resources/GraphicsPipeline/RasterizerState.md +++ b/content/Graphics/Resources/GraphicsPipeline/RasterizerState.md @@ -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. diff --git a/content/Graphics/Resources/GraphicsPipeline/ShaderStageState.md b/content/Graphics/Resources/GraphicsPipeline/ShaderStageState.md new file mode 100644 index 0000000..2c7d0a1 --- /dev/null +++ b/content/Graphics/Resources/GraphicsPipeline/ShaderStageState.md @@ -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() +}; +```