Fixed Function Pipeline Stages - Vulkan Game Engine Tutorial 04

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome back to another vulcan tutorial continuing right from where we left off last time navigate to your pipeline header in our pipeline config info struct we need to explicitly set the values configuring how the different stages of our pipeline work so let's start with the input assembler by adding a member of type vk pipeline input assembly state create info and we'll call it input assembly info now back in our default pipeline config info function we set the s type to vk structure type pipeline input assembly state create info next we set the topology member to be vk primitive topology triangle list and then the primitive restart enable value to vk false so let's go over what these mean this is the first stage of our pipeline and takes as input a list of vertices and groups them into geometry in a previous video i gave an example of a triangle being represented by six numbers but what the input assembler stage sees is a list of numbers broken up into chunks by vertex note that we're not just limited to passing in positions per vertex we can really specify any data we want and choose how we want to interpret it later within the vertex shader but how does the input assembler know that i wanted these three vertices to be a triangle and not say something like a line instead that's what we specify in the topology in this case by default we will use a triangle list which means that every three vertices are grouped together into a separate triangle so if we added vertices four five and six we'd have two triangles another option is a triangle strip where every additional vertex uses the previous two vertices to form the next triangle this has the benefit of letting us specify more triangles without having to waste memory duplicating vertices but limits the geometry that we can show to being a connected strip of triangles this leads us into the next member primitive restart enable by setting this to true when using a strip variant of topology we can break up a strip by inserting a special index value into an index buffer some other possibilities include point lists line lists and strips and triangle fans but for now we'll stick to just using simple triangle lists if you want to avoid a bit of upcoming typing you can grab the rest of the default pipeline config info implementation from the pastebin link in the description below or from the github repo first i'm going to just copy and paste in my entire pipeline config info struct each of these members needs to be properly initialized which will do so in our default pipeline config info helper function so back in our function let's start with configuring our viewport and our scissor the viewport describes the transformation between our pipeline's output and our target image back in tutorial 2 in our vertex shader we use the reserved variable gl position to output the corners for a triangle i also mentioned back then how these positions required x and y values in the continuous range of negative one to one however images are represented by pixels with the top left corner being at 0 0 and the bottom right at the image's width and height so our viewport tells our pipeline how we want to transform our gl position values to the output image so for example we can render this triangle but if we change this to render to only the top half of the display our entire triangle is still rendered but it is squished into the top half min depth and max depth are the depth range for the viewport and similarly will be used to linearly transform the z component of gl position now the scissor is kind of like the viewport but instead of squishing our triangle it cuts any pixels outside of the scissor rectangle will be discarded so back to our triangle example if we set the scissor to include only the top half of the screen only the top half of our triangle would be drawn we then have to combine our viewport and scissor into a single viewport state create info variable on some graphics cards it is possible to use multiple viewports and scissors by enabling a gpu feature but we'll stick to using just one okay moving on to our rasterization stage this stage breaks up our geometry into fragments for each pixel or triangle overlaps so as always we set the struct type member first next we have a depth clamp enable setting what this setting does is it forces the z component of gl position to be between zero and one values less than zero are clamped to zero and values greater than one are clamped to one usually we don't want this to be true because if a value is less than zero it is kind of like something being behind the camera and when z is greater than one it's like being too far away to c so keep this false for now note that using this requires enabling a gpu feature i'm going to mostly just focus on describing settings that are relevant to us now or in the near future if there's a member variable that you'd like to know about in more detail it is easy to look up a term in the official documentation and i recommend starting to do so as getting used to reading the documentation will be very helpful in the long run so rasterizer discard enable discards all primitives before rasterization so you'd only ever use this in situations where you only want to use the first few stages of the graphics pipeline polygon mode is for when drawing triangles do we want to draw just the corners the edges or the triangle filled in line width is pretty self-explanatory okay now call mode this is something that matters we can optionally discard triangles based on their apparent facing also known as winding order this is determined by the order of the three vertices making up the triangle as well as their apparent order on screen for example to determine which face we are looking at walk around the triangle in the order we specified our vertices if we provided vertex 0 vertex 1 and then vertex 2 then we would be seeing the clockwise face by swapping the order we provide any two of the three vertices we can swap the winding order to the opposite direction additionally the face we're looking at depends on our point of view without changing the order of the vertices if we were to change our point of view to look at the other side it has the opposite winding order therefore we can use this property to uniquely identify which side of a triangle we are seeing we need to specify whether we want to use the clockwise direction or the counterclockwise direction as the front face and then if we want to call the front or back faces for now we will use none as our default so that we don't accidentally call our first triangle backface culling can result in some pretty big performance benefits and is something we'll come back to soon lastly depth bias can be used to alter depth values by adding a constant value or by a factor of the fragment's slope we won't use this for a while so leave this disabled multi-sampling is a topic we'll cover in a future video but relates to how the rasterizer handles the edges of geometry without multi-sampling enabled a fragment is considered either completely in or completely out of a triangle based on where the pixel center is but this can result in some ugly visual artifacts known as aliasing where the edges of objects look jagged msa is a method where multiple samples are taken along the edges of geometry to better approximate how much of the fragment is contained by the triangle and then based on this shading the pixel by a variable amount color blending controls how we combine colors in our frame buffer if we have two triangles overlapping then our fragment shader will return multiple colors for some pixels in our frame buffer here we can optionally enable color blending and set the values determining how we mix the current output with the color value already in the frame buffer if any mixing occurs based on something like this we'll come back to this in more detail once we start working with transparency and finally we have depth testing so a depth buffer is an additional attachment to our frame buffer that stores a value for every pixel just like how our color attachment stores the color previously i've compared depth values to being like the layers in an image editing program but this analogy isn't exactly true a depth buffer doesn't keep track of individual layers but only the depth value to whatever fragment is currently on top for each pixel for example let's say so far we have rendered some mountains and our color buffer looks like this then the matching depth buffer would look something like this where light shades are far away and darker shades correspond to a fragment being closer now if we wanted to draw a cloud what would happen is that for each pixel of the cloud we check its depth against the current values in the depth buffer we only update the color and depth buffers for pixels where the cloud is the closest fragment so in this case if our cloud had a depth of around 0.8 then it is less than the sky's value but greater than the mountains any of the clouds fragments behind the mountains are simply discarded alright that is it for our default pipeline configuration but if you look back into our struct we have two more values that have not yet been set the pipeline layout and the render pass we don't provide any default for these members and we'll set them outside of the function okay so now it's time to complete our create graphics pipeline function let's replace these lines outputting our code size and instead initialize our shader modules call create shader module and pass it in our vertex code and a pointer to our vert shader module then copy and paste to do the same for our fragment shader now we need to set just a couple more create infos for the vertex shader stage and fragment shader stage so create a new local variable of type bk pipeline shader stage create info which will be an array of size 2 and we'll call it shader stages now set the first instances s type to vk structure type pipeline shader stage create info then we need to set the stage member which says that this shader stage is for the vertex shader with vk shader stage vertex bit next set the module member variable to equal vert shader module and then p name to equal main this is the name of our entry function in our vertex shader then set flakes to zero we won't be using any and p next and piece specialization info to be null pointers specialization info is a mechanism to customize shader functionality and will be covered in a future video now copy and paste this doing the same for our fragment shader make sure to change the member stage variable to be a vk shader stage fragment bit instead of vertex and the module member variable to equal frag shader module next create a new local variable of type vk pipeline vertex input state create info called vertex input info this struct is used to describe how we interpret our vertex buffer data that is the initial input into our graphics pipeline we need to set its s-type to vk structure type pipeline vertex input state create info then set the vertex attribute description and vertex binding description counts to zero since for now we've hard coded vertex data directly into the shader and therefore aren't supplying any data as of yet also set p vertex attribute descriptions and p vertex binding descriptions to null pointers and now finally we have our vk graphics pipeline create info which will use all of this configuration we just went through to create our actual graphics pipeline object make a new local variable pipeline info and set its s-type to vk structure type graphics pipeline create info next set the member variable stage count to 2 and p stages to our shader stages array the stage count is to specify how many programmable stages our pipeline will use so in our case for now we just have the vertex and fragment shaders next we need to wire up our pipeline create info to our config info this is a bit tedious but because we've separated our configuration from our pipeline creation we should only have to do this once now and then can use this code whenever we want to create different pipelines in the future so go through and set each member in our pipeline info struct to the corresponding create info in our pipeline config info for your viewport state make sure you use the config info viewport info not config info dot viewport and for the color blend state use config info dot color blend info not the color blend attachment and for p dynamic state just specify a null pointer this is an optional setting used to configure some pipeline functionality such as line width or the viewport dynamically without needing to recreate the pipeline we'll probably use this at some point in the future finally also pass through our pipeline layout render pass and subpass index to our create info we haven't configured these values yet so when we run our program will get an error we also have two more parameters base pipeline handle and base pipeline index which i only mention now for completeness these can be used when trying to optimize performance it can be less expensive for a gpu to create a new graphics pipeline by deriving from an existing one but we're not going to worry about this for now and now in an if statement call vk create graphics pipelines we pass in our device a null handle for our pipeline cache this is another possible performance optimization next for our pipeline count just one and then a pointer to our pipeline info we're not using any allocation callbacks yet so just a null pointer here and for the last parameter a pointer to our graphics pipeline handle check that this is not equal to vk success otherwise throw a runtime error that a graphics pipeline has failed to be created successfully and before we forget let's scroll up to underneath the constructor to implement a destructor to clean up our vulcan objects use vk destroy shader module to clean up both our vertex and fragment shader modules and then vk destroy pipeline on our vulcan graphics pipeline handle lastly in our pipeline header remove the empty body for our destructor so at this point your code should compile but when i try running it it crashes because i haven't yet provided a pipeline layout or a render pass but this is a good example of how the validation layers or device files set up for us can help us debug our code if you look at the first validation layer message it says vk create graphics pipelines required parameter p create infos dot layout specified as vk null handle so we have the function call where the error occurred in this case vk create graphics pipelines and it tells us that we've passed a null value for our layout parameter just below we have a similar message saying we did the same but in this case for our render path but then below that is a message i didn't expect so let's look into what it says if our rasterizer discard enable is false multi-sample state must not be null so i do remember setting up our multi-sample state create info in our pipeline config but let's take a look back in our create graphics pipeline function and yep right here i forgot to connect my multi-sample state info so just below my rasterization state i'll add a line for my multi-sample state now if we try building and running again we can see that the validation layer message has gone now let's make these requirements just a little more explicit by first including c assert at the top of our file and then at the start of our create graphics pipeline function add an assertion to check that our pipeline layout provided in the pipeline config info is not a null handle and similarly let's copy and paste this and do the same for our render pass now if we build and run it's unambiguous that we haven't properly set up our config info yet because we're missing a pipeline layout and a render pass and that's where we'll leave things for today in the very next video we will have drawn our first triangle and from then on our results will be much more visual we will continue to work with pipelines a lot over the course of this video series which will help reinforce the operations of each stage that were introduced today many of the parts we only briefly covered today will get their own dedicated videos diving into their functionality in more detail in order to get new visual results anyway thanks for watching if you're finding these tutorials helpful please like and subscribe
Info
Channel: Brendan Galea
Views: 11,907
Rating: undefined out of 5
Keywords: Vulkan, vulkan api, vulkan tutorial, 3d game engine, coding tutorial, vulkan coding, vulkan graphics, 3d graphics, gpu programming, vulkan programming, vulkan game engine, vulkan game engine tutorial, vulkan engine tutorial, learn vulkan, how to code vulkan, Vulkan beginner, graphics beginner, vulkan noob, vulkan from scratch, vulcan, vulcan api, vulcan tutorial, fixed function stages
Id: ecMcXW6MSYU
Channel Id: undefined
Length: 18min 42sec (1122 seconds)
Published: Fri Dec 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.