SPIR-V and the New Shader System // Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is channel welcome back to the game engine series we are back and we are going to be doing a little bit of a different format so let me know what your thoughts are below as we get into this because i've just slowly been realizing that um like i you know the way that the game engine series is going at the moment it's very kind of detailed it's very like me writing code in front of you guys which is just not going to scale well as we get into more complex features in the engine and the way that i see things at least for myself you know i have to make decisions this isn't like some company this isn't like a team of people behind us it's just me making this series and i need to kind of um you know make sure that i've got time for other things such as you know the actual development of hazel like hazel dev and all of that stuff actually making a proper game engine being able to make a game with it hopefully and i feel like the way that we're moving along with the game engine series is just a little bit too slow um so i've decided to change things up a bit i did put up a video there'll be a link up there regarding like what's going on with the game engine series in general and we are basically going to be following that course so what i've done is i've actually already worked on like um you know some features which we'll talk about now they're already integrated i'm about to push them to github and i'm going to just walk you through the work that i've already done instead of you watching me do all of the work now i'm going to try my best to make this as detailed as possible we're basically moving moving from like you know line by line tutorial series style videos to like you know devlogs post-mortems but very in-depth ones not like the hazel dev ones where i just show you guys the features talk about my story my journey all of that i'll do that as well but this will be like you know we will walk through the code i'll show you some things that i learned along the way you know that kind of stuff and also hopefully um give you some insights into into decisions that were made however if you want to actually see every line of code being written every decision being made you can do that because i've streamed everything that i've done here on twitch.tv slash the channel there'll be a link in the description to that live stream i'm making the video on them like the the videos the vods um public for everyone right they used to be only for twitch subscribers as of right now um well i need to actually flick the switch but before this video goes up i'm going to change it to public so if it's been within 60 days because twitch only keeps them for 60 days um you can actually watch all of the vods if you want that um you'll have to like i've labeled them mostly game engine series if they're about the game engine series and not about other stuff that i stream like just hazel dev kind of development you know that kind of stuff so it should it should be like fairly easy to find hopefully past 60 days though i do upload them to patreon um like to youtube and like give access to patreons so there'll be a link in the description as well patreon.com um best way to help support you know my series and everything that i'm doing here and you'll also get access to those kind of past live streams forever they're not going to expire because they're uploaded to youtube so um that's basically the scope of things but anyway let's stop talking let's dive into the code i'll show you guys what's new and what's changed so first of all actually um let's talk a little bit about where we left off last time i think we were doing like mouse picking stuff and that had pretty much finished and the next stage was really working on the material system now i just want to say that like a material system is kind of something that applies a lot more to a 3d engine this like hazel is a 3d engine the way i see it is it is a 3d engine like at the moment we're kind of doing 2d rendering i mean it's still kind of in 3d space but it's we're trying to keep it two-dimensional even though our rotations and everything are very much 3d i think at the moment and we've got a 3d camera but the point is we're focusing on 2d rendering and like a full 2d workflow that's kind of our goal but we still need the system the underlying system to be 3d right like as in it needs to support a 3d workflow and the way that we typically render things is by using materials um and if you're not sure what a material is and how it needs to exist and all that there's a video up there i did talk about this and it was kind of like a game engine series episode although it was a live stream um you can you can take a look at that video and see how like what materials are and all of that but basically in order to support a material system we need a better shader system because materials are basically shaders and and a bunch of data as well to go along with that so um what i've done is i've basically changed the whole way that we deal with shaders so let's take a look at that so um basically if i launch this just to be um you know just to show that this actually works and of course it's recompiling stuff i don't know why anyway if i open up like you know pink cube.hazel um you know you can see that everything is basically exactly the same as it used to be nothing has changed since the last episode we can still click on stuff obviously and pick stuff but the underlying shader system has changed entirely and that's what we're going to walk through now i do have diffs here like if we look through all of this this is what's changed as i mentioned i haven't committed anything this is going to go right into the master branch but i haven't committed anything um so we can actually walk through these diffs in future episodes where maybe i do actually commit from time to time we can go through like you know the commit log basically and see everything um so that'll be okay but i'll just more or less share the code if you really want to see the diff you know you can go to a particular commit and view it there's actually a question i get pretty often is like how do i view you know this particular episode or whatever um if you actually go to github um you know there's this kind of commits over here right you can click on this and then it will actually take you to all of these things so for any of these you can just click on this hash here and actually view the diff of everything that's changed but you can also browse the repository at a particular point in history so i can go back to like literally like you know the first episode or whatever i don't know if i'll go back that far um because that's like that's a while but if i go back to like i don't know somewhere like here dispatching events from jfw i can click this browse repository button and you can see this is this is the repository from that point in time so like you know sandbox probably looks very bare bones um yeah that's it right there is no hazelnut application so you can actually you know you can check that out if you uh if you have it checked out locally just like in fork right click checkout commit um you know or you can do it through the command line or if you're just browsing it through github you can also do that so there's always a way for you to recall a particular episode or like whatever changes happened so i i don't think um i'll go through the diffs exactly i might skim through them but basically i'll just talk about what's changed um and uh you know what the i guess decisions why they were made and what the journey was like all of that kind of stuff but as i mentioned because this is kind of a new format let me know where your feedback is but i'm just going to tell you guys straight right now there is no way we can continue writing code line by line like that's just not going to happen we're going to make an engine that way um i think that i've you know the videos that are helpful to people who are very much beginners and want to write code line by line i feel like we've done all that you know i feel like we've moved past that you should now be able to watch a video like this and understand how a system works and implement it yourself that is my goal that is what i think should happen and i'm going to try my best to make that happen for you guys i promise so um uh yeah so so what's happened then so the way that shaders used to work is we simply used to load them like from a file and they got immediately sent into like uh i don't know gl like share into opengl sharer compiler basically the way that they work now is very much the same if we open up renderer2d and we take a look at um the way that we load like this we really only have one shader at the moment right texture dot glsl so if we look at assets um you know shaders texture.jls so this is the texture sorry this is the shader so what i've done first and foremost is i've actually rewritten it um i've rewritten it very slightly so and actually this is where the diff might be useful so if we look at the diff um what has changed so the biggest thing that's changed is we no longer will ever use plain kind of uniforms the reason being one of the big changes that we want going forward is we want to be able to support vulkan as a rendering api now at the moment and for the you know unforeseeable future we're going to continue just doing opengl hazeldev at this point in time supports vulcan i'll leave a video up there that you can check out if you want to see where hazeldev is at and what that's like and my kind of vulcan journey i recommend all of you guys actually watch those hazel devlogs because um they can be extremely useful to see the bigger picture of engines and like how we actually you know do maybe more advanced stuff because i know some people are kind of you know wanting to move past the stuff that we're doing here that's kind of shown very um very verbosely i guess very in a very detailed fashion but basically um because we want to support vulcan going forward um you know we need to be able to support uh opengl now because that's a decision i've made and so we need to basically kind of support both but i don't want to do things in a strictly open gl only fashion because uh if i do that then it will kind of lock us into opengl for a lot longer than we need to be and then it'll be harder to rewrite things i'm kind of looking ahead a little bit more and so because of that because we need vulcan vulcan also uses glsl however a common misconception is that it's the same as opengl jlsl it is not it is different if there are things in vulcan that don't exist in opengl and there are things in opengl that don't exist in vulkan and i mean in specifically the shading languages right a good example is uniforms uniforms do not work in vulcan it's not a thing you cannot just say uniform you know matte for transform or whatever i don't know why you have that in your fragment shader um i mean i guess can be useful but anyway unif you know you don't have this in vulcan it doesn't exist doesn't matter if you have a layout location for it whatever it does not exist right in vulcan things need to be either as a push constant which looks like this layout push constant and then it's basically a uniform buffer right so you have like uniform transform and then you have mat for um you know transform or whatever uh inside this kind of buffer um and you probably you know would give it like a layout and all that kind of stuff or you have it as an actual uniform buffer which looks like this which looks very similar as well um let's not compile that uh which just basically instead of push constant it just has like a particular binding right that's how uniforms work in in falcon there are no uniforms there are uniform buffers there are dynamic uniform buffers and there are push constants that is how and there are like storage buffer that we won't talk about some other more advanced things because they're not really like i'm talking about the simple case of how do i get a matrix how do i get a vector from my cpu to my gpu and then access it in the shader and this is the answer right so because of that um we need to get rid of uniforms now in the case of this i mean what do we have here before we had a view projection now the thing is this you have to realize the context of this of this shader this is not a this is not a 3d model kind of shader this is not our like pbr shade that we render our scene with we're dealing with a 2d renderer so this shader technically speaking will more or less run once right um what do i mean by run once i don't mean i don't actually mean run once i mean like this is more or less going to be like one draw call maybe two draw calls right if we have like ten 000 i don't know what the limit is but basically we can render like a lot of sprites at once right in a single draw call that's that's the purpose of this batch renderer 20 000 quads so in our pink cube scene where we're rendering three quads that's a single draw call right so it's not a huge impact here but when we have a 3d scene every single every single object every single sub mesh most likely unless it's batched together will be a separate draw call we could easily have over a thousand draw calls now something like the view projection matrix that's the camera that does not change over any of those 1000 draw calls in a single render pass let's just pretend that we're just rendering our render pass from the point of view of the camera you know here are all of the objects in our scene that does not change right and so because that doesn't change you know why would we need to and and this is the big thing though out of those 1000 objects it's very very common for us to have different materials right different materials have potentially different shaders right so instead of us having to have a view projection matrix that we set per shader as a uniform right because usually shaders have their own kind of uniform story again this is an existing vulcan but we have our own kind of uniform storage per shader right instead of us having to update all of the shaders in our entire engine what we can do instead is we can use something called the uniform buffer which is basically just a way for us to be like hey gpu allocate a block of memory for me that is accessible by shaders right that's what a uniform buffer is it's a buffer of memory which we can then let shaders access so i can say that hey i want to create a uniform buffer at index zero right it's going to be my camera buffer and then if shaders want they can be like hey hook me up man i want that binding zero i want that i want that camera buffer i need it because i need to render this from the point of view of the camera right that's how uniform buffers work the good the good thing about them is that they're global across our it's a gpu thing it's not a shader thing really i mean it's intended to be used by shaders but it's not like a per shader situation no it's just a buffer of memory on the gpu that we can then use inside any shader we want and again because something at binding zero it's it's not like a per shader thing it's global it's across all about all of our shaders any shader in our whole engine can be like binding zero uniform buffer hook me up i want the camera view projection matrix and it will get it right so what that means is that now suddenly this is entirely separate from shaders right this camera stuff um is not something that we need to worry about on a shader level it's something that we worry about on a renderer level so the renderer at the beginning of the frame can be like hey man set up my camera and as i mentioned we switched to using uniform buffers so what does that look like in code we have a uniform buffer class which we will explore in a minute i think i'll save that for another episode um because i noticed that i'm already going pretty in-depth as i mentioned this is it's a devlog but it's super in-depth so let me know what you guys think of this um because i do still i do still want to actually you know teach this stuff i don't want to just be like hey this is what i've done let's move on that's boring right so um we might actually explicitly look at how uniform buffers work in a separate uh episode but today we'll focus on like the shader stuff so um we create the uniform buffer and then you can see that in like begin it begins in um that's our job like we know the view projection matrix the camera and the inverse the transform or in this case is actually just the camera's view projection matrix then we can just set that data onto the gpu and that's it right we do this once per frame when we start a scene why because starting a scene is basically analog to setting a camera right there's no we don't have like environments so we don't have anything like you know special like lighting setups we just have a camera and we render our 2d geometry from the point of view of the camera so setting it starting a scene is providing a camera setting all that stuff up that's what we're doing and then we start the batch and then now our shaders can just be like you know uniform camera it's a binding zero i want access to that view projection matrix you can give this a name like you know camera buffer or whatever and then view projection that might even be better um i don't know why i've just written it like this for now um which basically if you don't give it a name you just access it as if it's a normal uniform but as you can see that's what we do so it works very similarly to what we had before before we literally just had this right but it means that this is something we need to set this uniform for every shader that uses it if we had a slightly different shader that did something else um you know we'd all would we'd also have to give it this and we'd also have to set it right um and obviously the way that we can set uniforms is by either binding the shader by doing glues program and then doing gl you know program sorry gl uh you know uniform matte matrix for fv you know for the matrix and setting it or we can actually just cut out the binding and do gl program uniform matrix for v provide the um the shader id so the program id that opengl gives us when we create the shader and then actually upload that matrix per shader we don't have to do that anymore we just we just say that our shader is hooked up to that uniform buffer done so it that's all that's really changed and again the reason this change exists if we were just doing opengl i'd still make this change that's what uniform buffers are for it's for data that you share across shaders such as like camera environment you know maybe shadow information you know whatever stuff that basically is not particular to a particular shader particular to a particular shader right so a camera is a good example because we want to render a whole scene from the point of view of the camera it doesn't matter what material what shade is being used we still need to know where the camera is so that's perfect um now the other the other the other stuff that's really changed here mostly has just been like you know this stuff so for example with this sampler 2d we've been explicit without binding right now bindings for textures um there are 32 of these here but bindings for textures work uh and again i'm not gonna i don't think i'll spend too long talking about this today but basically if you have a sampler 2d let's just ignore the array for now i have like some kind of texture that i want to set right um in opengl you know it's possible for us to just do uniform sampler 2d and then we have to set that texture to be like a value so we do gl uniform 1i we set the sampler to be like with zero you know and by doing that it means that we can do gl you know gl binds texture units or gl active texture if we're really old school and set it to like be you know zeros the active texture and then i'm binding my texture in in more modern versions of opengl and in vulkan especially involving is required in but in more modern versions of opengl and jlsl it's it's optional you can actually just write code like this so what that means is that i'm setting the text unit to be zero no more gl uniform one eye none of that right this is texture zero right now i think you can override it by setting uniform one eye if you really want to i'm not 100 sure if that even works but my point is you can actually just in your shader just define you know any texture unit for this right here in the glsl code which is great um and in vulcan i think like in vulcan it's mandatory that you do that there's no such thing as setting integers in the shader to be like texture units or whatever and vulcan it doesn't exist right so we have to do it this way um also everything else has to be uh hooked up with certain locations so for example we have an out vertex output i've put this into a struct um we've set it at location zero we can't just say out and then match it by name it gets matched by location so what that means by the way is that um you'll notice here that we jump from zero to four right and the reason we do that is because um i think it's every like every 16 bytes occupies a slot in this location right so vec 4 16 bytes and this is another or maybe it's every 8 bytes because that's 16 bytes and then this is 16 bytes maybe it's just because of the padding i'm not 100 sure this might be 16 bytes that might be 8 bytes with 8 bytes of padding that might be four bytes with 12 bytes of padding and then another one um but the point is this needs to be at location four right because we have apparently um uh for you know either i don't know again as i said per 8 buys per 16 bucks i'd have to double check the documentation but that's um basically what we do so this isn't like per struct or whatever it's dependent on the size um and then obviously for this flat int or whatever because we want it flat we can't really put it into the struct we have it outside of the struct i'm not sure if it's necessary to have it as flat um we are yeah it's probably not someone just i think someone may have done that in a pull request i'm not sure i've left it as flat for now um because it shouldn't be it shouldn't change across um across vertex shade sorry across vertices because it's going to be the same object it'll always have the same entity id so i don't think it's going to interpolate over it i mean it might try and maybe that's what flat does prevents it from trying in this case because even if it did interpolate it would wind up with the exact same value because the entity id is going to be constant across all these vertices but anyway so the point is we have to include these locations um yeah i think everything else we basically had the same right so again referring to this nothing else really changed it's just we put this stuff into a structure now we gave it a location because everything needs to have like a binding or a location um and that looks pretty good to me so that's basically we can just mark that as done so that's that is basically the shader changes that have been made um to the actual shader so as i mentioned we're now compiling vulcan shaders so what on earth does that look like in the pipeline how does it work so instead of grabbing this text and sending it to opengl into like gl i don't even know what it is jill shader source or whatever whatever the function is right we don't do that anymore instead we use something called spur v now spur v is a shader compiler it compiles into uh this kind of intermediate uh byte code right so um what i want to do is just show this right um you might want to look at this you will you will want to look at this if you've never heard of spurve before but it's basically um it's like this whole like shader ecosystem i guess um that's a nice way of putting it um so it's it's okay standard portable intermediate representation it's basically like a shader it's it's like some intermediate shader presentation right and then what you can do with this kind of shader code is you can do a lot using the tools that spurvey provides you with or you can just flat out be like the spurvy byte code i'm going to give it to opengl right so um basically the pipeline looks like this you you grab this text you compile it with spur v right we're using shader c in this case which is like a google compile google shared compiler i think um that seems to be what everyone's using so we're using this library shader c to actually compile it into spur v and then we're using a few of the chronos provided spur v libraries to actually uh reflect on that shader code meaning we can actually once we compile this to that byte code which is originally just binary just a bunch of bytes right um it's almost like when you compile uh c plus into like a translation unit and it becomes like a dot object file obj file or whatever kind of like that right um it's a little bit more high level though i think than than like machine instructions but um uh basically once you have it in that format you can reflect on it meaning i can pull out information about like you know what uniforms does it have what uniform buffers like what uniform buffers do we have do we have any push constants um i haven't talked about push constants but i probably won't for now because we're not using them um oh no i did talk about push constants loosely uh you know what textures do we have what resources do we have available to us inside the shader we can reflect on it which is fantastic from a validation point of view because we can make sure that we have um everything we need and for certain things like you know push constants as an example because that's usually where where we would want to have our material kind of uniforms right our material properties material you know variables whatever we can actually ask it what do you have available how many biases take up what are they called stuff like that and then we can create a material system around that which is what hazeldev does so um that's fantastic but then also we have the ability once it's in that spurvy format to cross compile it to anything else i can i can take this code compile it with spervy into the spurvy binary right then i can cross compile it into metal the metal shading language or into hlsl directx shading language right or into you know in this case we're actually using that to convert it into an opengl compatible glsl so as i mentioned this is vulcan glsl right i mean at the moment this should work in opengl but as soon as i start introducing introducing things like push constants so if i have a little push constant here called transform maybe and it actually does contain my transform right what will happen here well this is not opengl jlsl what's a push constant right doesn't work but what we can do is we can still compile this into vulcan glsl to vulcan spur v then take that vulcan spervy binary and open it up in like spur v cross which is the cross compiler and tell it compile it for opengl and what it will actually do is generate glsl text that we can read it'll look like this that will now be compatible with opengl right and then we can send that into v again if we wanted to guess get a spurvy binary and then just do gl shader binary to send that binary to open gel or we could take the text directly and put it into opengl right just the same way we were doing before which is just reading text and putting it into opengl right telling you that this is my shader source code please compile it for me but it's better to do it with spur v because um you'll actually apart from getting more metadata and more information if you need it you can easily cache that onto disk and then you can read that cache right um at any point instead of having to go through this process again so basically once you've decided this is my shader i'll i'll compile it you can write that binary result the binary shader to disk and then never look at the source code again and you can ship that if you want because the shaders might not change it might change if you have like dynamic shader compilation maybe stuff depends on you know whatever but if you have those binaries that's it and it'll be way faster because it's not going to have to look through this code and compile it do anything you're not going to have to open or read text files right it's just it's going to be in that format you can give it to you know gel shader binary and then you're good so basically that's how this works that's how the hazel pipeline works we take vulcan glsl code we compile it using spur v into a binary file we then take that binary file that vulcan compatible binary file we fit it into the spervy cross compiler that gives us an opengl compatible glsl text file or just a string we take that string we compile it using spervy but this time for opengl and then we uh you know cache both both of those binaries to disk um and we can now give that to opengl using gel shader binary one step that i missed there was the reflection step because spur v is kind of made for vulcan um we actually can't you know one okay so in this scenario let's talk about this so what does this actually become an opengl if push constants are not supported well what it will actually do is first of all we can give our push constant our name right so i could be like you transform or you render a uniforms or whatever transform right um let's pretend it's that and we can access it you know as you would here maybe if you were rendering a 3d model you'd multiply it with like you know this as an example um we can actually uh take this right um or what spervy cross rather will do is it will take this and it'll be like okay so what i'm going to do is i'm going to make a struct for you i'm going to call the struct transform right and then i'm going to add your little transform into the struct right and then i'm going to make a uniform transform urandra uniforms so basically this becomes that it just becomes a plain old uniform but it's a struct which is obviously fine and valid jsl right so that's what the spervy cross compiler will do with push constants now can we then ask the shader via reflection tell me what your uniform is opengl actually has its own reflection functions but when we're trying not to use them because again trying not to stay in the opengl ecosystem too much but um can we do that with spervy reflect the answer is no we can't look at these uniforms why well they didn't really exist in vulcan not really worth i guess i don't know why they didn't support them but they didn't what we can do though is we can inquire about push constants so the way this actually works is even though we're using opengl we're compiling using vulkan um you know vulcan oh sorry we're compiling vulcan gelson to eva into a vulcan spervy binary and then we're taking that spurvy binary and reflecting on it so even though we're then later cross compiling it into glsl into opengl jlsell and then using that in opengl so we still need that vulcan binary we still have to have it so that we can reflect on it and find out information about like oh you have a push constant which for opengl we know becomes this right um but you can see the naming is the same you know you render a dot transform you render uniforms.transform you know it works obviously for both shading languages um okay so does that also mean then that if we have vulcan there's another stage to this i i don't think i will get into this too much but basically um another thing that we use and another thing that we do in hazel dev um is uh and see this is why it's so useful you know i i love the hazel dev project because apart from that being kind of me making my own engine without like you know having to like teach it every step of the way and thus not getting very far um apart from that it's also really useful for this series because obviously now i have a much more detailed idea of the bigger picture so because um this is a good example there are some cases in opengl sorry there are some cases in your glsl code where the actual physical glsl code might have to be different between opengl and vulkan right so i'm talking about like calculations so it might be desirable for for us to do something like if instead of running two different shaders which we never want to do that's why we do this whole thing by the way we want to support opengl and vulkan and maybe directx in the future maybe metal in the future who knows but we don't want to write our shaders multiple times so what we do instead is we use we write it in one format such as vulcan glsl which i'll admit is not i would probably choose hlsl if i had to choose a format but since we don't have any plans at all for supporting directx like ever um you know because the only reason we'd need to is if we wanted to ship on like xbox which i don't think we're gonna do um anytime soon at least and if we do in the fin i don't know we can add we can always add it but anyway um and so it doesn't make much too much sense for me to use hlself whenever supporting direct acts um so uh basically what i was saying was that um with this vulcan jealous uh with with vulcan um it might be uh with the whole vulcan such opengl thing it might i don't know where like why i started this conversation but basically it might be desirable for us to actually um you know instead of writing our shaders twice once for opengl once for once uh for vulcan if the um actual content like the calculations have to be different we can use preprocessor like arguments you know directives instead so we can actually write a little thing such as like into you know value equals zero float value equals zero and then if opengl maybe value maybe we normalize it or like we put into a different range you know so we say something like this value equals value times 0.5 plus 0.5 you know just for opengl um or something i don't know i'm making stuff up but the point is um we can easily do this using preprocessor directives but that means that when we compile our vulcan code we actually need to compile it with this flag because obviously you know when we cross compile it since it's using that spurvy binary this stuff will have already been evaluated so basically what i'm saying is that we can't simply take the vulcan spur v and then feed it into the cross compiler from the beginning of the pipeline from the text we need to be like this is an opengl vulcan spur v binary so that actually does have a bit of an effect on the way that we like cache these binary files and all that stuff um a real world example of this by the way is um vulcan actually has a different depth range opengl has negative one to one uh for its like depth uh vulcan has zero to one what that means is that for some calculations you know when for example we do like shadows and we have to um you know calculate the right um you know once we look up the shadow map and we want to actually calculate the correct depth from the point of view of the camera versus the point of view of the light we have to remember in vulcan to put it into a different range or rather in opengl to put it to a different range than when we're dealing with vulcan so that's a that's an example of a real world kind of use of why we might want to actually have different calculations um you can obviously have if you wanted to like some kind of uniform and that would be like the modifier you know i i like doing it more in the glsl code um because it makes a little bit more sense i think and you don't need to like use up a uniform slot or anything um okay anyway so let's take a look at this is again becoming a huge video but we haven't had a gaming series episode in a while so i hope you guys are enjoying this the next step is going to be um probably to look at what actually happens in the shader so as i mentioned the shader um this is a bit annoying but whatever um all these increases and line and endings the shader um actually uh has like had the most changes i think like if you look at this diff you know this is all the stuff that's changed basically everything the whole file has pretty much changed i basically rewritten it um it also used to be like 187 lines now it's like 374. so it's been expanded has been rewritten what happens now well we again as you saw the api stays the same this part did not change this whole idea of like um you know we're creating a texture shader with this gel cell code that doesn't change um we don't really need to be doing this by the way i haven't actually even tested this with textures this doesn't need to be done anymore in fact i can just get rid of this um this isn't like 100 done let's just see if this works because we are actually using the white texture as texture slot zero um so there you go see i slam writing code in front of you guys that will never change i guess or rather deleting code um pink cube okay that does still work so if it wasn't a white texture we probably wouldn't see anything if the texture wasn't working because it multiplies the color with the texture and if it was missing most likely we'd have black and we just would not show up or it would be black anyway so this doesn't change right so what actually happens um is we still obviously support that we take in a file path um and then the first thing we actually do is we create the cache directory if needed so as i mentioned it's very slow for us to recompile all of these text shaders every time we run hazel that's just that's not needed right every time the shader changes we do need to do that and at the moment we're not detecting changes we're basically just saying hey does the cache exist if yes use the cached binary file um so this is us create creating that cache directory right just to make sure this doesn't need to be here there used to be two no there's one um so the cage directory is going to be assets k shader opengl and again for future apis we might have like you know vulcan here um whatever at the moment it's just opengl make sure assets directory is valid apparently it's yeah to make sure that the assets directory is actually assets because it might not be in the future um then we just see if that exists and then if not um we can use the brilliant c417 file system api to create recursively all directories along the way to make this path happen which is amazing i love that um so that's what this does just to make sure it's there because if we try and write a file and the directory doesn't exist it won't be able to then we read the uh source and we pre-process it which means we split it up into the different um source codes because as you know we still have this type vertex type fragment of the whole cherno shading language situation um so that's what that does that has not changed now i actually hook this up with a timer to see how long it takes but basically these are the three steps compile or get vulcan binaries that takes in the shader sources which is the actual string source code for each opengl type right so again you know we have uh you know like a vertex header fragment shader that's what the type is um we create a shader c compiler we look at the uh you know we set the options um in this case what we're doing is we're just setting the options to basically be vulkan 1.2 so we're compiling for vulcan 1.2 as i mentioned right um in these options it would be a fantastic place to actually add a macro definition like i mentioned of opengl right so you can just be like you know what um you know we're defining opengl and you would have to do that for the vulcan version here because as i mentioned the preprocessor is going to run you're going to lose all your text so you have to do it there optimization you can set to false i don't know why we would ever not optimize in this case um but whatever um and then we set it to performance just a little flag here uh could be based on some input parameters whatever um then we just get the cache directory and we basically compile all of the different shader sources ignoring the cache um we uh basically create a module here and we just do compiler.compile glsl to spur v we just pass in our arguments basically i don't think anything here is useful we have our stage we just convert that into shader c so vertex shader becomes shader c or cell vertex shader fragment shader becomes that pretty simple stuff um if the compilation doesn't succeed we print the error message as usual and then we actually have the shader data inside a vector of words it likes to use the un32ts instead of bytes here um and then we write it out using the output stream someone convinced me to use the simples api instead of file i think in hazel dev it's just file and fopen because that's how i roll but um i guess we'll be c level 17 c plus plus e here um and we'll do this i think it mostly just wraps um i think this mostly just wraps the api anyway and i've lost it maybe not here but i'm pretty sure it does probably not worth me doing this now but you might have to take my word for it basic of stream basic oh stream the hardest part is just trying to find all of this stuff to be honest especially when you're rushing no okay whatever um it's definitely got some more like validation and uh safety stuff around it okay so um then we've got uh yeah so once we have created that we cache it to disk right and that's what this brought this branch over here does if the cache exists we read it we open it and we now have our data if not we compile it and we well we still have our data so it gets written and then once we have all the shaders all the shader stages done and compiled which is what this code does we can reflect on it so what does that do i keep trying to build um so what reflection does at the moment is mostly just printing stuff we don't actually use the reflection yet we will in the future for materials just to see what's available and potentially for validation as well but what this does at the moment is it's just a demonstration of um how this whole reflection works so you can see open javascript reflect and we actually uh print you know the file path the shader stage to string that just basically you know converts that to a string unifor how many uniform buffers do we have and you can see we can use this perfect cross api here once we've reflected over it compiler gets shaded resources to actually see how many uniform buffers it has how many sampled images it has so like textures right what are the uniform buffers what are the names of the uniform buffers how big are they what we can actually even iterate through the members if we want you know what's the binding set to right is it zero is it one you know we can see all that stuff here and this is just a little example to show you guys how the reflection works so the way i write this code as well is is i try and um almost explain it in the code itself so again you should now be able to hopefully look at this reflect function and be like ah this is how i can find out potentially the information that i need and then write that code in my own engine um so i'm trying to be extra kind of a burst descriptive with my code i'm not trying to be all you know four into x equals y w is less than z you know cryptic stuff this is supposed to be very um easy to read hopefully that's my goal with this code anyway and of course these explanations hopefully if you can be bothered sitting through 40 minutes it better not be running out of disk space because that would be the worst no i'm so good um okay so where were we yeah that's this stage then we do the compiler get opengl binaries so what does that do well the same thing but um so we have the vulcan binaries now we have them we've either loaded them from disk and done reflection on them i should really move reflect out of the compile function don't know why it's here we'll do that later um we we already have the welcome ones we we've we've either actually compiled them and then written them to disk or we've read them from disk so now what do we do um to do read from cache i think we already do that it's probably done compile compile well what i don't know i'm getting rid of that so um we have uh this is just i think the um where it goes but basically what does this do same thing so except in this case right um oh where is where does the cross compilation happen i think we've done that or does it happen here yeah it probably happens yeah it happens here okay so this time though we create a new compiler and we set it to be opengl and opengl 4.5 instead of vulkan vulcan 1.2 um and i think uh 4.5 is the latest by the way so there's no like 4.6 currently no difference between opengl 4.5 and 4.6 so there you go and just lang doesn't accept an original client of 460. okay that's why our shaders as well uh obviously 450 core stop telling me about this i should have okay whatever um so we go through the vulcan spervy we either read the binary if it's already on the disk right um so this is by the way uh as we're going through the velcro spur v this is us reading um the uh gl shader stage k shape and gel file extension this is us reading not the vulcan stuff we've already got that that's what the vulcan stage was for this is us trying to read the opengl cached binary if we have one if we don't we go over here so if we don't we cross compile we're taking the vulcan spur v which comes from here we're going through the vulcan spur v even though we're trying to retrieve the opengl spervy we're going through the vulcan spur v um and then we're cross compiling it into um opengl and how we do that we do a compiler glsl and then i think by default this will just be set to be like an opengl compatible one i believe um i think yeah so version 450 um and vulcan semantics is false by default right so it's not going to use vulcan jealously features it's going to use gl compatible features because it's false and there's a few other things you can do here actually which is really useful i haven't looked at this too much because we have a pretty simple example i think some of these might be used in hazel dev specifically but we basically compile that source code it gets saved into opengl source code so we have these two things we have opengl source code we save that just so we can for debug reasons view what the cross compiler generated what text it generated but otherwise you can see we have our vulcan and opengl spervy we also at the moment retain all this in memory you don't need this in memory once you've reflected and once you've uh compiled and sent it to the you know to opengl but we still keep it for now just for debugging purposes um yeah we compile that um yeah so we've cross compiled it we now have the text and then we send the text into jlsell just like we did originally with our original textbook for vulcan right so hopefully all this is making sense uh read through the code um and you can run it with a breakpoint and with debugger that might help as well um and then we cache it to disk so what does that mean that means that as you saw me run that first of all let's take a look at this the assets directory has a cache directory right and inside the cache directory there's a shader directory um opengl like you saw and then we have four files we have the opengl.vert.frag and we have the vulkan.vert.frag right if i delete these or in fact we can test it by just deleting the whole case directory right it will have to recompile all of them from text and at the moment because we're not detecting changes by like hashing the string or whatever hashing the binary um it will that's the only way if you change your shader you have to delete the cached one or or maybe i'll set it like in hazel dev there's a force compiled flag that you can give anyway as you saw that took wow 6.8 seconds usually takes about five seconds i don't know why it's suddenly slower but that's how long it takes to compile everything um now uh and you can see that we've got uniform buffers and the reflection has worked here as well we have one uniform buffer it doesn't have a name i didn't actually give it a name as you know i was just um using um you know i didn't give it a name the name would go here right so it's generated its own name but we know that um look it's got a matrix called view projection in it that's what we see here we have size 64 binding zero one member right so we can get access to that we could print the names as well it is trying to print the name of the uniform buffer it's had to make one because we haven't given it one but anyway 6.8 seconds that's how long it took to launch and to compile all this now watch how long it takes if i run it again instant right 19 milliseconds right huge difference 6.8 seconds 19 milliseconds right so that's why it's so important to cash your shaders um we don't want to you know spend a lot of time compiling shaders um and then you can open up your pink cube um and everything is great that's basically the major system um again i'm not i haven't talked about uniform buffers but that's what we do now with our shaders we compiled them using that um now the final stage which i didn't touch on was the creating the program and i apologize for this hugely long video um maybe i'll break them up in the future a bit more i just didn't think i would be able to talk so much about this um stuff anyway i hope it's i hope this level of depth though is good because i know that a lot of people did still want it to be very in-depth um we create the program the same way and then we just go through all the stages right and we get inside the opengl spur v and then what do we do gel shader binary right we just say binary format spur v this is obviously the opengl spurvy not the vulcan spervy we do gel specialized shader that just points it to the main function there's a few other things here like specialization constants and stuff like that but the entry point is the important one that we need to give we're using actual specializations um then we attach it to the program we link the program as before this part is identical to before and then we just link it and do all of that stuff delete the shader ids to detach the shader oh sorry delete them if they fail i think we also should delete the shaders um here let's just try that let's detach and then delete um we can obviously group those two uh four loops together but i'm just curious if this if this works um i mean i assume so i think this is what we were doing before the program can't be deleted but these individual shader objects should be i think so let's uh do detach and delete like that uh and we've saved ourselves a comment we can get rid of the comment brilliant so um yeah and then this part as i mentioned that remains the same so not much has like not not like everything has changed it's still kind of the input and output of this class is identical right it's just the actual this the contents of it has changed we still have all of these uniforms you know we can keep them like these functions but again um they're going to be very open gel specific so we will probably end up getting rid of them eventually but that is basically currently how the shader system works um i don't want to go through uh there's a few different things i want to go through i will commit all this code so you guys can take a look at it i have a lot of other stuff to talk about i just didn't not realize as i mentioned this would be so long but um uh yeah i think there's also like so another huge thing that i want to talk about which i'll talk about in the next video actually is our whole dependency on the vulcan sdk so this uses the vulcan sdk vulcan actually ships with all these tools to be able to compile this stuff and you know which is great um the vulcan sdk uh has two types of libraries we could compile ourselves well some of these libraries ourselves um i don't really want to um i think that it might be something that we might have to do in the future for like different platforms um but for now for windows there are pre-compiled binaries now these pre-compiled binaries are very big and obviously we need debug binaries and release binaries the debug binaries are two and a half gigabytes i don't want to commit them into the project that will massively inflate our get repository size so instead we are you have to download them basically from kronos but i've written some scripts to do that and that's what we'll talk about next time so anyway hope you guys enjoyed this video if you did please don't forget to hit the like button patreon.com the channel best way to help support everything everything that i'm doing here 50 minutes i've been recording for i don't know what the edited video will be like probably very close to that let me know what you thought of this kind of new format um you can kind of see i hope that the fact that this has taken 50 minutes to explain um this took me like i don't know how many hours of actual work maybe four or five hours of actual work i have done this stuff before and hazeldev does very similar stuff as well uh but it was live stream so you can catch the live streams or you can go through the backlog on patreon as well and take a look at that i've labeled them like game engine series live stream or whatever so you should be able to find them um but uh you can see how hopefully how if i had actually done this for the first time or whatever or written all the code in front of you um it would have taken a little longer and i don't think it would have been worth it because i think i i personally think this kind of reflection way of doing things is um hopefully going to be more useful to more people but anyway leave your thoughts in the comment section below please thank you all for your support um and i'll see you next time have a good day [Music] you
Info
Channel: The Cherno
Views: 31,731
Rating: 4.9416909 out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, game engine, how to make a game engine, game engine series, spir-v, shaders, shader system, shader reflection, graphics shaders, vulkan, opengl, uniform buffers
Id: SXDlZRDjtXg
Channel Id: undefined
Length: 51min 32sec (3092 seconds)
Published: Fri Apr 30 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.