hello everyone and welcome back to the
kohi game engine series today we are
actually going to be creating our
graphics pipeline
but before we do i would like to take a
quick second and thank the supporters of
the channel beginning with our partners
ar slia wen shang kaden and patrick
they are the partners of the channel
which is the highest tier of support for
the channel so thank you very much for
that and i would also like to thank all
the other supporters listed here on the
screen your support is very much
appreciated so thank you for that if
you're interested in supporting the
channel you can find links in the
description to
youtube memberships as well as my
patreon page which is
patreon.com forward slash travis roman
before we jump into that though there is
one
minor thing that i would like to enhance
with our logger so
in the last video we actually added our
file system which was a
component that we were missing to be
able to actually write out our logging
files so writing out your logs to a file
is a great way to be able to debug
crashes so if you're running the
application and for some reason it just
crashes on you
it's one way to be able to
debug the application so
particularly if you're not running in
debug mode you might want to actually be
able to examine your log file so
we're going to go ahead and put that in
real quick it's actually pretty
straightforward so let's go ahead and
knock it out real quick
first thing we're going to do is we're
going to include platform filesystem.h
and next in the log system state we're
going to get rid of this rubbish
initialized property because we don't
actually need it or use it
so
that is going to go bye-bye
and we are going to instead
create a
new file or wipe an existing file if it
exists
our
log file okay so we're just going to say
file system open
and we're going to call this console.log
we're going to use the dot log extension
so that vs code can actually pick up on
some highlighting for that
it is not going to be in binary mode it
is only going to be in write mode
and we are just going to store this in
the log file handle
and if for some reason we cannot do this
that indicates some problem that we
actually need to investigate and fix at
the engine level
or the file system level and so we are
actually going to abort uh
the logging
initialization if we cannot write that
file okay
all right
so
one other thing that we are going to
need the ability to do
is
we are going to need the ability to
append to a log file so i'm just going
to put a
private method in here
where we have a message that is passed
in
and so if we have a state pointer and
the handle to the log file is valid
then we are going to basically take the
length of the string and we are going to
write to that log file
and uh we are going to um
report if for some reason we can't write
to the file we're going to report to the
console that there's been error writing
to that but basically this is just a a
way for us to be able to quickly and
easily write to the log file so now that
we have that in place
we just need to hook up this method
[Music]
and all that entails is just going to
the end of log
output
and tacking that at the bottom
so all we do is just append to the log
file the out message that we've already
built here
and that should be it
one other thing that i would like to
take a moment and simplify also is
this logic in here because we actually
have
added some things to our string library
that should actually allow us to
simplify this so i'm going to go ahead
and
include
k string
and i'm actually going to get rid of
string.h and stdio
[Music]
and i'm basically going to replace this
chunk of code with something a little
bit simpler and
honestly better commented
so one note i'm putting in here is a to
do so uh string operations are generally
pretty slow
so one thing that we need to do
eventually is split this out to a
different thread once we have threading
available
but i want to take
advantage of our string format
capabilities and go ahead and utilize
those instead so
this is just a little bit cleaner
in terms of
our actual ability to log okay great so
now that that is done we can go ahead
and move on so let's just uh
really quickly build
and it looks like we have
ah
so it looks like i did not include
memory i need to include k memory as
well
[Music]
so we'll go ahead and get that in there
let's build again
and now we're good okay we'll go ahead
and run
just as a sanity check
perfect so
we're good as far as that goes
okay so obviously you'll see that we got
some
some validation errors here and that's
because we did not
actually destroy the shader module
before we killed everything else so
first and foremost we should probably go
back into our
object shader
[Music]
and we'll actually need to fix this in
two places so our object shader should
actually properly destroy that
so in our object shader destroy we'll go
ahead and put
destroy shader modules
and this should actually be using the
context
device
[Music]
logical device
[Music]
and that will allow us to
not get these validation errors anymore
of course that means that this vulcan
object shader destroy needs to also be
called so we'll go into vulcan back end
and in the shutdown
we will put this
since we're doing this last we will put
it first here
so here we will actually destroy our
object shader and render a back end
shutdown
passing of course the context and
a the address of the shader itself so
let's go ahead and build this
run it
and shut it down
and now that validation error is gone
okay
all right so the next thing that we need
to do is we're actually going to
skip the descriptors for now and we're
actually going to set up the
pipeline
and we will come back to descriptors and
go over what those are but for right now
um i think it makes more sense to sort
of create the pipeline
first
so the pipeline as i mentioned before is
basically a structure in vulcan that
connects a lot of the various pieces of
a
render pipeline together
pieces such as descriptions of vertex
information
and lay up the layout of that as well as
our shader modules that we've created
here
our render pass that we created in
previous videos
things like that so basically this is
the glue that holds all that together
right and then um we can then use those
pipelines so to speak
uh as our actual shader so
these are generally speaking going to be
unique per shader
at least for now until we get into more
configurable
pipelines and things of that nature so
for now i'm going to create the pipeline
inside of the vulcan
object shader
creation
however i am going to do the creation of
this in a separate file it's just going
to be sort of um
there's going to be one created
specifically for this right and it'll be
stored as part of the shader
[Music]
i hope that makes sense uh under vulcan
i'm going to create a new file
i'm going to call it vulcanpipeline.h
obviously we will want uh pragma once
and vulcan types inl
[Music]
and we are going to have a
create for our graphics pipeline
and this is going to take a whole bunch
of parameters which i'm not going to
explain what all of these are right now
but some of them should at least look
familiar to you so obviously we have the
context
the render pass that's going to be used
with the pipeline
and i should mention that this render
pass is compatible so it doesn't
necessarily have to use this render pass
it just has to use a render pass that
has the same setup as this render pass
so i just want to point that out
uh our attribute count as well as our
attributes i'll get into what attributes
are in a bit our descriptor set layout
count as well as our descriptor set
layouts again i'll get into that in a
minute
the number of stages we have the stage
create infos that are associated with
that
the viewport the scissor which we have
created before
whether or not this is actually going to
be a wireframe pipeline because that is
the kind of thing configuration that we
need to actually declare here
as well as a pointer to the vulcan
pipeline structure that will actually
contain the created pipeline when we're
done
so
this may wind up actually changing into
a structure instead of passing so many
parameters i may wind up creating a
structure
for this um
to simplify this interface a little bit
but for right now um
it's a little bit simpler in code
to just have
this many
parameters just because it's probably
going to be changing
[Music]
pipeline destroy is much simpler just
takes the context and the pipeline in
fact i'm going to keep that on one line
and then
the only other thing that we have is
just a
simple call to
pipeline bind because we actually need
to bind a pipeline in order to be able
to use it
it's
those terms can basically be uh
interchanged
at this point uh basically whenever you
bind a pipeline you're saying any any
commands that occur in the command
buffer
while this pipeline is bound will be
executed against that pipeline okay so
obviously that's going to take a command
buffer
uh the pipeline bind point which i'll
get to in a bit
and then uh obviously a pointer to the
pipeline itself okay
and that is our interface it's actually
pretty pretty straightforward and it's
not too big okay so now i'm going to
copy
these signatures and i'm going to go to
here and create a new file
called vulcanpipeline.c
and i'm going to paste these guys in
convert them to a function definition
[Music]
and then
i'll go ahead and include vulcan
pipeline dot h
okay
we're also going to need
vulcanutils.h
okay and we're going to need the core
k memory and the logger for this
okay
so our
vulcan graphics pipeline creates is
basically going to return true if
successful otherwise false
and thankfully there's not a whole heck
of a lot to it so
the first thing that we have to create
is something called the viewport state
and this is basically going to take our
past in viewport and scissor again we're
making the assumption here that there's
only one of each this may be something
that we have to change in the future
but for right now this is basically
saying this is the initial viewport and
scissor that we want to use with this
pipeline
and that could be changed by something
called dynamic state which i'll get to
in a moment
next we have the
something called the rasterization state
that we need a create info for
so here we have a vk pipeline
rasterization state crate info
that's a mouthful
and basically we have a whole bunch of
properties in here
and i'm only going to take time to
actually point out the most common ones
because or the most important ones for
right now because
most of these won't matter right away
so
first off this polygon mode right here
is either going to be
line
or fill depending on whether or not we
say wireframe
so that is the reason that we
take this boolean right here to say if
we should create this as a wireframe
pipeline uh then we're going to use
we're going to render the polygons as
lines otherwise we're going to fill them
in
and the reason that you might want to do
this is because
you are going to have situations where
you're going to want a wireframe view of
your geometry and this is how you would
do that and
i should note that
since
pipelines are immutable once they're
created
it's not like opengl where you can just
flip back and forth in terms of what
this polygon mode would be once it's set
it's set so if you want a wireframe
pipeline you actually have to create a
separate pipeline for that and use it
instead
so that is the reason for this
the line width i'm just setting to 1.0
the co mode is just saying we want to
call back faces
the front face is basically
indicating our winding order of our
geometry
so
front faces versus back faces are
determined by the order of the vertices
themselves and whether they go any
clockwise or counterclockwise
direction
most
applications tend to use
counterclockwise to to
indicate the front face which is what
we're going to be doing here
so if i were to flip this to
clockwise then it would actually cull
the front faces or what appears to be
the front faces because
the winding order is reversed so
if you guys ever run into issues with
your geometry sort of rendering inside
out or backwards it may have to do with
this right
also
we could if we wanted to disable
backface culling we could set this to
disable that but for right now we
actually want to
disable back faces which means we don't
want to see the back side of of geometry
we don't want to render that as far as
the rest of this stuff it's not really
important for the time being i just set
it explicitly here
just so that we can come in and change
it later on so
i am going to move on the next thing
that we need to configure is
multi-sampling multi-sampling is not
something that we're going to be using
right off the bat so i've set some
defaults here to say we're just going to
use a single sample
and that is basically it
multi-sampling is something we'll
probably come back to but for now it's
not something i'm going to implement
depth and stencil testing is another
thing so we are actually going to
enable depth testing
and enable we're also going to enable
writing to the depth buffer
and then uh this is our depth compare up
so basically this is saying
when we compare two different depths um
what is the comparison that we want to
use do we want to use
less than
or greater than or equal there's a bunch
of different operations on this it's
basically determining what gets clipped
when you're doing depth testing
okay and uh we are not using uh stencil
testing right now so i've gone ahead and
set that to false
so the next thing is something called
color blend attachment state and this
has to do with
the way that colors are actually written
to
written to the image right
and so
these are the various
blending modes
to handle alpha and we'll come back to
these when we actually start handling
alpha transparency
but these this setup will actually allow
us to use alpha transparency when we get
to that stage so
i'll double back at that point and
describe what all these things are
and also our color right mask indicates
what channels we actually want to write
and so in this case we want r g b and a
okay
so the next thing is our color blend
state create info so this basically just
takes the attachments that we define
here
again we only have one attachment we're
making that assumption right now
um
we may need to come back and revisit
this later but for right now it's okay
to make that assumption
uh all right so
the next thing i want to talk about
really quickly is dynamic state so as i
mentioned before
pipelines are immutable
however there are a few things
within something called dynamic state
that we can change on the fly
and viewport and scissor
as well as actually line width are a few
of those things so we can actually
change these properties
by calling various vulcan commands
to set those things without having to
recreate the pipeline if we wanted to
change something that is not part of the
dynamic state we would actually have to
recreate that pipeline in order to
actually do that but since
we have this dynamic state
this is how we're able to actually
resize the window
and not have to recreate this whole
pipeline every time we do that so
dynamic state is actually a pretty
useful thing there are certain things
that you can actually use for that
and viewport scissor and line with are
going to be
the three things that we're going to use
right off the bat for that there are
more but those are the main ones we're
going to use for right now
dynamic state create info does pretty
much what you think it might where it
takes basically this information here
and jams it into a structure
that tells the pipeline how that stuff
is going to be used
now
the
next thing that we're going to create is
something called our vertex input and we
haven't really touched on vertexes too
much
and as we can see we have a type here we
have not defined
so
let me stop for a second and describe
what we're talking about when we say
vertex so a vertex
is a individual point of geometry
that holds various information and that
information tends to include not only
positional information so xyz to tell
you where that that point in space is
but it also holds things like your
texture mapping coordinates
it can hold things such as color it can
hold
lots of different information and you
can generally put whatever you want in
there but
keep in mind that the larger you make
your vertex
the larger
or the more information has to be pushed
to the gpu every single time you upload
geometry so when you upload geometry
it's in the form of this vertex
structure that you create
and in this case we're going to create
something called vertex 3d
and so what this is is this is basically
saying this is what that vertex kind of
looks like and how to use it
and so uh this stride right here is
basically telling us how many bytes
the vertex each vertex is and then the
input rate is basically saying the
input rate is one per vertex right
so uh we move to the next entry for each
individual vertex
so before we move on
we should probably go ahead and define
vertex 3d right
because it's definitely something that
we're going to need to know
so to define this we actually are going
to go into math types
so that is going to be under math
math types
and we're going to go here to the bottom
and we'll go ahead and put
our definition right there so we're
going to type def structure called
vertex 3d
to be called vertex 3d so we don't have
to type this
and then for right now all we're
actually going to store in here is
position right and like i said there are
going to be other things that we're
going to be adding to this
but for right now i want to keep things
simple until we actually get stuff up
and running and then i'll come back and
add properties to this as we go so for
right now we're just storing position
okay
so now that we have our vertex 3d we can
go back
and we need to
include
the
math types
in this file
[Music]
so
here i'll say include
math
mathtypes.h
and now if we go ahead and save that we
see that vertex 3d is defined
so the reason we take
the size of this is because this is
going to be changing we don't have to
have to come back in here every single
time and change this
although there will be parts that we
will have to come back here and change i
don't want this to be one of them so
we'll take the size of that in bytes and
use that instead
okay
next we have something called attributes
and this is basically how
each individual vertex is split up
so if we recall in our
our vertex shader here actually let me
open the one
here
we have this layout location zero in
vec3 imposition
this is actually an attribute so what we
are saying here is uh the location zero
equals zero
is basically saying this is the first
attribute and it is of type vector3 so
the shader actually knows how to
interpret that so every time we add an
attribute
we are going to be adding one of these
things and an attribute is loaded as
part of a vertex so
this is just a way for us to be able to
split these things up programmatically
so that the shader actually knows what
it's dealing with right and so we're
actually going to be including an
attribute count as well as attributes
listing to say how this guy is actually
split up okay we should probably make
this guy configurable too but for right
now i'm not going to worry about it
okay so that is what attributes are on a
high level
and how they're used
okay
so the next thing is we have to define
input assembly and that is basically
what takes this information and
assembles it into a form that the vertex
shader can then use
so in this case what we're going to do
is we're just going to say that the
topology type that we want to use is
primitive topology triangle list which
is basically saying all of our geometry
is expected to be triangles now we could
use quads if we wanted to we might do
that
in the future but at least for now
everything we're going to do is with
triangles
okay and then
our pipeline layout
is basically going to start off by
just setting the vk structure type and
zeroing out the rest
the next thing that we're going to do is
we are going to utilize something called
descriptor sets or at least the layouts
of that
and i'll come back to this but this
basically has to do with something
called uniforms
which are various parameters that we can
pass to our
program
that are not necessarily vertex specific
but that can be
that can be passed in to give us
additional information that we need
for the render
okay and so
what we wind up with is all of this
information that we take here
winds up being part of
a pipeline layout so this here
determines what the format
of our pipeline will be
so the next logical step is to call
vk create pipeline layout
and we basically just pass
all of the stuff that we just wound up
actually creating
so
our pipeline layout create info here um
is basically the glue that holds all
that stuff together okay
and so we create that
and then we actually create our pipeline
itself
below here
starting off with a create info just as
you do for a lot of other things in
vulcan
and so here is where we pass things like
what the stage count is what those
individual stages are
the vertex input the
input assembly state
so all these other things that we
created above right
we are going to
pass a bunch of other properties such as
our viewport state our rasterization
multi-sample so this is where all those
pieces above are actually used
and then tessellation state we're
actually not using so i'm just hard
coding that to zero just to be explicit
about it
and then
the pipeline layout we're going to use
is the layout that we just defined here
right so we create this
there use it here
and then we are going to add a couple
more things to this so we have the
render pass which is passed in um
just passed in to us
any subpasses that we use we're not
using subpasses just yet
[Music]
and then we don't have base pipelines or
base pipeline
indices so we're just sort of nulling
that out okay
and so at this point we're finally ready
to create our pipeline
and uh this just is a call to vk create
graphics pipelines you'll note that this
is plural because you can create more
than one
so if you were to create uh
if you wanted to you could create two
here and just have one
be
wireframe and the other not be wireframe
for example
and you could certainly use this to do
that we're just going to be creating one
passing the info and then
obviously just setting
our pipeline handle
to that
so we're going to take the result of
that
and we are going to double check it
against success
and if it is a success then we're going
to go ahead
and return true
if for some reason we have an error we
are actually going to
bleat about that here
saying um we failed and then with what
error and get the vulcan error string
here okay
and that is all there is in terms of
actual pipeline creation it's it's more
just filling out structs than anything
else there's not really
much
if any logic in here
so uh
the only other thing that we really need
to fill out is the destroy which i'm
actually just going to kind of plop in
here all at once because it's pretty
straightforward stuff
so basically we just check to make sure
the pipeline exists if there's a handle
to it destroy it and then set it to zero
and then also make sure to destroy the
pipeline layout that we
stored as well and that's that's really
it right
[Music]
that's all there is to the destruction
of a pipeline
and then last but not least we have the
pipeline bind which is just a simple one
liner to vk command by bind pipeline
right so this is called from within the
context of a command buffer the bind
point uh which we will get to when we
actually set this up is passed through
and then obviously the pipeline handle
okay
and so uh there's a lot of information
here uh and unfortunately if i were to
cover all of it
we would be here for
probably another hour
so
i would say this is a good opportunity
to have a read through the spec on the
vulcan pipeline and see what some of
these properties in here
do and become familiar with them because
we're actually going to be coming back
in here and changing some of this stuff
and so be good a good idea for you guys
to sort of you know get used to reading
the documentation and and fully
understanding
everything that is is being said here
okay
all right so that is the pipeline
so now that we
have
a interface here to actually create the
pipeline
let's go back to our shader
and we'll actually start setting this
stuff up
okay so the first thing i'm going to do
is put a to-do here for descriptors
because we're not actually immediately
going to use those although we are going
to be using them very shortly uh we
should not actually have to use those to
get things up and running so i'm going
to put it to do there
and
and skip those okay
so now we're going to go ahead and
proceed with pipeline generation so
the first thing we're going to want to
define is the viewport so the viewport
is basically going to be
x
and y
[Music]
so x is going to be zero based y is
going to be the height of the frame
buffer
the width is going to be as you would
expect and then the height is actually
going to be negative again because we're
using
the opengl equivalent way of doing this
that's why we're starting at the bottom
and going towards the top
and then our depth we're clamping to min
and max of zero and one perspective
respectively
our scissors basically going to be the
size of the screen right it's going to
clip anything that's outside of that
now our attributes so our attributes are
required we can't skip those so what i'm
going to do is
set up a
structure that we can sort of
expand as we go
and so
what we're going to do is we're going to
track an offset that's going to be an
offset size
and we'll see where that comes into play
here but basically as we add
attributes we're going to be offsetting
this and bytes
and we'll go ahead and create
an array here
size of attribute descriptions and this
is not using vlas because we are using a
constant value here so just bear that in
mind
and then
what we're going to do is we are going
to
define our actual
layout of our
attributes okay
and so for right now
the way that this works is we have a
variable that we're going to call
formats and this is going to be of
attribute count size and this is going
to give us the
format of each attribute
and our first first and right now only
attribute if you recall
is one of position which is a vector
three
so we are actually going to use the vk
format r32 g32 b32 s float
and so even though
this is colors you can also use this as
positions because it winds up being the
same data
basically what it's saying is we're
using a
32 bit for r or x 32 bit for g or y
32 bits for
b or z
and then that they're going to be
floating point right so this just tells
um the pipeline that that's what we're
going to be using
okay and so every time we add an
attribute we'll increment this and add
its format here
and then obviously we will need to track
sizes as well
so at least initially we only have one
size
because it's the you know obviously the
size of attribute count and it's the
size of vector 3 because position is a
vector 3.
and i believe that we actually do not
have vector 3 here so i'll
i'll need to include math types up here
[Music]
math types
okay
and so we have an array of formats an
array of sizes those things line up
and so what we'll wind up doing
is looping through both of those arrays
and setting our attribute descriptions
which is here
in this array
and we'll set the
oops i need a semicolon there there we
go and so we'll set the the binding
index which is always going to be
0 in our case because we're not going to
be using multiple bindings for this
the location of it is going to be i so
at least for the first one it'll be zero
for the second one it'll be one et
cetera so this location maps to
[Music]
this location here okay so the first
will always be position right
and then uh we'll use the format at the
given index and then we'll also use the
offset which is going to be
uh incremented by the size that we
defined here right so the offset for the
first one obviously will be zero and
that'll be
offset by the size of that index which
the first one will be size of vector 3
which will wind up being 12. okay
and so this
is just a nice easy way for us to make
this so that all we have to change
is this
and this as well as the overall count
and we don't have to change any of our
logic to support it going forward okay
so it's kind of configurable kind of not
we'll probably wind up making something
a little bit more flexible than this in
the future but for right now
this will simplify things for us for the
time being
okay
so the next thing that we need to do
is we need to set up our stages
and our stages
are just going to be pipeline shader
stage create info right and in this case
it's going to be 2 because our object
shader stage count is 2.
so to be safe we're just going to zero
out that entire thing and then we're
going to loop through each stage and
copy over the s-type
as well as the shader create info right
and so that will fill this guy out to be
used
i also do want to point out that uh
we're not doing descriptors yet
but this is where that's going to wind
up being so i'm going to put it to do
here
so that we come back to that
when we actually start using descriptors
[Music]
and then we're going to go ahead and
create the pipeline
okay and obviously that's going to call
we're going to call vulcan graphics
pipeline create which means that we need
to actually include that
so right under
shader details
i'm going to include vulcan pipeline.h
[Music]
and that should be defined there we are
we're good to go okay so
we're passing the context uh the render
pass the main render pass from the
context
the attribute counts which is one the
our array that we've created
our next thing that we're gonna that
this is looking for is the descriptor
set layout uh or descriptor set layout
count and descriptor set layouts array
we don't have those yet so we're just
passing zeros for that
how many stages we have which is two our
stage grade infos our viewport or
scissor
we're passing in false
for is wireframe for right now because
we don't want wireframe
and then a pointer to hold the created
pipeline okay
[Music]
all right
so all of this
to create our pipeline so what i'm going
to do is i'm going to set a breakpoint
here
and i'm gonna go ahead and build
and looks like we had a warning let me
just
verify this
looks like we forgot to include k memory
in our object shader i thought i already
got that
nope i didn't okay
so i'll include core
okay memory
build again
and no errors okay
so let's go ahead and run
and we'll go ahead and check our
pipeline creation so i'm just going to
actually skip over all this assignment
here
and come down here
to our pipeline layout right
so i will go ahead and run this okay and
we didn't assert there so we're good
and i'll skip over these things
[Music]
and here our
result
for our create pipelines
we'll go ahead and
skip over that
our result is vk success so we have
wired up everything as needed and we do
not have any validation errors here so
that's also a good sign
and
that means that our graphics pipeline is
created and we're good to go so
that means that we can go ahead and let
this run
and again we're not going to see
sort of anything actually appearing
just yet on the screen but
we are
getting there
uh however it looks like we have
forgotten to destroy something so
destroy device
ah we did not call our
pipeline destroy so let me go ahead and
wire that up
[Music]
so that is going to be an object shader
and then
before we destroy the shader modules we
are going to destroy this so we'll say
context
and
context
[Music]
say context and then shader
pipeline
[Music]
and
we'll need the address of that
okay let's go ahead and build again
and we get a we got a warning
[Music]
oh i guess we don't need the address of
that because that's already pointing
okay there we go okay so here is our
pipeline destroy
so now we'll go ahead and run again
let's get rid of that break point
okay
and
go ahead and exit this and we can see
that the validation errors have gone
away so
our pipeline is
good to go uh we are
ready to move on to the next step which
is going to be very soon getting stuff
on the actual screen so we're getting
there folks
so this is uh going to wrap it up for
this video thank you guys so much for
watching
if you liked the video go ahead and toss
it a thumbs up i greatly appreciate it
if you haven't already consider
subscribing click the little bell icon
there to get notifications as to when
the next video and this or other series
drops and i will see you guys next time
[Music]
you