Abstracting OpenGL into Classes

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what up guys my name is Jenna and welcome back to my OpenGL series so today we're gonna have a nice chill episode in which we just talk about how we're going to abstract some of the code that we've actually written in the past episodes out into some classes so that we can use it more easily as we start to get into more complicated OpenGL stuff which is obviously where this series is headed so I did mention a few times throughout creating this series that I really don't want to make this a game engine series I don't want to make it like hey we're gonna build some kind of OpenGL engine or like a rendering engine or anything like that I don't really want to spend too much time moving code around and setting stuff up and making it absolutely bulletproof and absolutely perfect because that's not the point of this series this series is just me showing you how to use OpenGL however in the real world in order to actually use OpenGL you do need to perform some kind of abstraction of some sort because you're otherwise you're just gonna end up having a pile just a messy pile of code that doesn't make much sense to you it's gonna be extremely verbose and you're just gonna it's it's gonna be difficult to debug there's all sorts of issues and it's gonna be extremely difficult for me to show you for example how to implement shadow mapping if I'm all doing it in one file with just raw organ gel kind of card because it's just it's not gonna make much sense so what I'm going to do over the next few episodes is start moving code out into classes and showing you how we can kind of I guess move common code around so that we can reuse it a lot of times maybe parameterize it as well and all that stuff it's also a really good opportunity to show me a little bit or for me to show you guys a little bit about how like how OpenGL can be abstracted and how like it typically would be abstracted in a game engine even though we're not going to go as deep into this as if I was actually writing again mentioned potentially with multiple rendering at the eyes it's gonna be fairly like brief and high-level here but still it's a really good chance for me to just show you guys how you would typically go about abstracting um gel anyway so out of what we've done so far there are a few things that we need to actually move around there's the vertex buffer setup right the index buffer setup to raise as well and whether or not we even want to use them I've said in the previous episode I think which was about vertex arrays that we will be using them so we need to set up some kind of abstraction for that and then there's the whole shader situation that's probably it like if you really think about what we've done so far we haven't done textures yet or anything like that or like you know frame buffers the right thing or anything like that I said obviously it's not like we have a complete like every feature OpenGL implemented in this main file but it is getting a bit lengthy I think it's like about 200 lines of code and it's getting a little bit hard to tell what's actually going on and it's a little bit hard to read but in terms of what I just listed out their bellies buffer index buffer vertex around shader that's pretty much all we have right now that we can abstract however there is one thing that needs to tie everything together and that is traditionally known as a renderer so we do need to create a renderer at some point as well and the idea where the renderer is fairly simple you basically give it like a command and it will render that thing for you so in other words I want to be able to say hey renderer can you please draw me this and then the draw kind of function will potentially take in everything it needs to draw something which you traditionally would be like a vertex buffer an index buffer potentially any kind of textures any render states such as like you want blending on should we be writing to the depth buffer that kind of stuff and then obviously what does a better go find a spot for like a materials like a shadow maybe texture stuff like that and then all the render stacks right so that that kind of concept of a renderer needs to also exist and that's gonna be extremely important for us to implement so what we're gonna do is today just focus on vertex buffers in in and index buffers which should be very easy especially because we've decided to use vertex arrays so we don't have to worry about the layout of an actual vertex buffer when we write that vertex buffer class we only care about the actual data which is just a bunch of bytes really easy to really easy to deal with at this point and then the index buffer is basically the exact same thing like literally index of a vertical buffer basically the same thing as far as the abstraction and thing like that and then in the next episode we'll probably deal with vertex arrays and then after that probably shaders and then after that maybe the renderer and then once we have all that kind of collection of classes set up we'll be able to actually use them to do things in a relatively easy way that should look very simple in this application like file class thing that we have going on right now so that's the plan let's dive in let's start cracking this code okay so first of all if I just hit f5 so I can show you guys where we're at of course is where we left off in the other episode we basically just created a vertex array and how to do this little animation of our rectangle color here the main things that I'm going to be focusing on today is this vertex buffer which is right over here we'll just called a buffer and that's to take that's just taking in these float positions and that's pretty much it and then of course the index buffer which is this thing here now this kind of stuff this vertex attribute stuff which is at the actual layout of a vertex buffer that's going to be dealt with in the vertex array class and all that and virtus array class will probably end up taking vertex buffer objects into it as well because we typically tie their Tex buffer we typically tie vertex buffers with actual vertex of rays in their layouts and all that kind stuff and then after that of course will go with shaders shadows are gonna be a little bit more complicated because we need to actually deal with the uniforms and all that so that should be a fun episode but anyway today nice and simple vertex buffer end index buffer how do we do that stuff so first of all if we scroll up to the very top you'll see we have some stuff that's fairly common right like this error checking is stuff that's kind of common to OpenGL right now so what I'm going to do is on the source over here going to right-click add new item I'm going to just add a header file and I'm just gonna call it renderer again I don't really want to set this up like a game engine or anything so I'm not going to go and not to try to create too many classes so I'll keep it nice and simple hours put into the renderer class which will eventually contain our renderer as well so I'm making a CPP file as well because we need that so we have a renderer CDP and a renderer dot H okay into the renderer H I'm actually going to put in this a cert which might not seem very intuitive because assertions are typically used throughout our entire program not just the renderer but it's okay in fact I'm just gonna copy and paste all of this kind of stuff cut it and then put it into render H which is pinned accidentally whoops okay cool so now this is what we have I'm going to include some stuff now we are obviously tying this with the shell so it's okay to just include glue over here in our render a header file and then what else we need I re and potentially we don't really need that stuff here I'm not going to include iostream all we need is really just flu okay cool so now this stuff what do we do with this stuff well the first thing I'm going to do is get rid of static and then just make it a an actual declaration rather than definition because we're going to move that definition into the actual CPP file so I'll get rid of static for GL for GL log call as well add a semicolon in fact let's just copy both of this we're going to put it into the CPP file in there I'll include round rotation paste in this stuff then I'm back over here I'm going to just get rid of the bodies of these two functions like that and there we go okay looks pretty simple and then in render recipie P we've all that stuff let's get rid of these semicolons now here we will actually need to include C out so let's say include iostream and that looks pretty good to me okay so we've removed it we've moved out the kind of common stuff and we can just include renderer in all of our classes if we really need to to get access to this kind of jail call macro and everything that it does as well as assert cool okay so back an application let's take a look at what we actually need to move out here so straight away you can see GL call of course is giving us an error because we now need to include renderer because we need the macro and then we'll scroll down and take a look at this buffer stuff so this is the actual code that we need and that really is like all of it I mean this bind buffer will need to kind of be reused in some kind of bind method because we will have to rebind kind of different buffers as we go along but the actual creation is just gem buffers and then setting the data that's it really easy really simple so under source I'm gonna click right click add new item gonna be a header file am I going to pull this vertex buffer dot H I'm going to do the same thing for the CPP file vertex buffer dot CPP vertex buffer CPP is going to include bed X bar for H I'm also going to right-click on a vertex buffer vertex buffer and rename it to vertex buffer because I did miss buildup okay cool now if I go back to my header file notice type in class vertex buffer I'm going to have one private member just for now called unsigned int M renderer ID now I did say just for now but it will have that forever what I meant is we're just gonna add just one member from now now M renderer ID right let's talk about this for a little bit so we know that OpenGL needs some kind of numeric ID in this case it's an unsigned integer that actually kind of keeps track of what it's an ID basically for every type of object we allocate you know we're like create in in OpenGL right so if I create a texture if I create a shader by creative vertex buffer an index buffer a vertex array a frame buffer whatever it gets some kind of ID and that ID is a unique ID that is an integer that identifies that specific Buffalo that specific object that I've created that's how it works now I am calling this a renderer ID because other api's also work on a similar system right so what I'm really doing here is just kind of keeping it fairly generic even though this is an OpenGL series so I am tying things very closely to OpenGL you know instead of just coming kind of calling this OpenGL idea or just ID or like I don't know what I called it before buffer or whatever renderer ID is just the actual internal renderer ID so if you're using OpenGL as your renderer this is the numeric ID that is actually relevant to OpenGL because we may have a high level kind of engine side ID that we also use file projects like that all right so yeah just just trying to explain what I'm doing here because I'm not trying to kind of um this series is going to be kind of tough for me to do it ok now we're breaking like the fourth wall here but anyway this there is gonna be kind of tough for me to do because I'm trying to teach you guys OpenGL but also I'm trying to mix it in with how you'll likely be using OpenGL and those are two different things because if you look at any OpenGL tutorial they just show you how OpenGL works and that's great but that doesn't tell you about how to use it in a game or how to use it in an engine or how to use it in the real world I'm kind of trying to mix the two I hope this makes sense this is obviously a minor case but as the series goes on if you have any thoughts just leave a comment below I am here I'm human I'm here to help you I want to improve this series as much as you probably want to see it improved just leave your thoughts in the comments below anyway let's just get back to this a so we have a render ID I'm going to make a cobbler constructor here I'm going to take in Constanta data and then and also an unsigned int size okay we're going to have a destructor here and we're just gonna have two functions bind and unbind I like to add these two now of course in an actual engine you will have other things like set data maybe maybe have a lock and unlock mechanism so that you can actually kind of stream data to the vertex buffer as you're kind of rendering because you met well not as you're rendering but before you render the next time you might want to modify the vertex buffer and so you might kind of lock it modify it unlock it and do all that kind of stuff there's a bunch of stuff you could do here we're gonna keep it simple for now all we need to run our current program is find an unbind as this series goes on and we start to kind of dive into more complicated topics and we start to require those features we're going to just extend these classes okay so bind and unbind it looks pretty good and we have a constructor and a destructor everything looks pretty good to me so now let's go back to our CPP file I'm actually going to go back to my header file and because I'm using visual assist I can just right click on the class name go to quick actions and create method implementations and it will ask me which ones I want then I can hit OK now visual studio also does this if you kind of hover your mouse over this and I don't know press the little arrow you can also generate stuff generating implementations for your methods as well okay cool so what are we doing here if we go back to that application code all we really need is if I copy these three lines I'm gonna put them back over here alright we're going to include our renderer because we need that for these shell call macros and then instead of buffer it's going to be our end or ID and then instead of positions it's going to be data and instead of this thing going to be sighs and that's really all we need now I'm going to copy this bind buffer and put it into bind and unbind except with unbind I'm going to make it zero okay pretty simple pretty straightforward one more thing we're gonna add here is of course the deleting of this vertex buffer so I'm just going to write she'll call Jill delete the buffers when I delete one buffer and it's going to be our end or our ID okay that's it that is our entire vertex buffer class how simple is that now let's repeat the same thing for an index buffer now because these classes are so similar I'm actually just going to open this containing folder and I'm going to literally copy and paste this vertex buffer these both of these files the header file and the CPP file and it's renamed to index buffer because that's how similar they are okay back in visual studio hit this little button here to refresh this you can see we have two new files select both of them right click include in project and then let's start refactoring them sir this is an index buffer now render ID stays there beyond the other thing we need is an actual count because we need to know how many indices this actually has going to replace this with index buffer of course instead of vertex buffer conformed account for data now for now we're going to support 32-bit indices I don't know if it's even worth adding 16-bit entities at this point especially in this series just know that obviously you can have different integer types different integer sizes for your index buffer you can have 16-bit integers so like shorts unsigned shorts you can have 32-bit as well welcome to use 32-bit I don't think there's a reason for us to support both really of course theoretically there are performance benefits to using 16-bit integers if you don't if you're if you feel 3d model that you're drawing isn't like that complex doesn't have that many triangles that it requires more than 65,000 65535 different not six five five three six different indices but we keep it simple unsigned int so Const unsigned int data now because we've supplied it unsigned int and not a void there's no point for us really taking a bite size what we instead want is the count so how many indices are we supplied now I like to distinguish these two by using sighs and count when I write sighs when I create a variable called size that is almost always in bytes if for some reason it's not they'll probably I'll definitely leave a comment or something but like in the actual source code but size means bites count means element account so in other words if I have if I'm drawing the square and I know that six indices right six indices that means if I write the number six that's the count right I don't write six times four which is 24 that would be this size 24 is a size six is the count okay and that's all we need here let's go back to this implementation file now I'm going to just hit ctrl H over here to bring up this Find and Replace thing and won't replace vertex buffer with index buffer and then hit old age to replace all in this file current make sure it's in current document obviously and there we go so now I'll get rid of this and replace it with what I had there which visual assist can help me out with again and then finally instead of size and data here I'm going to write instead of size primaries when we count times size of unsigned int now there may be some danger here because I'm obviously assuming that the size of an unsigned int is the same as G eluent because I specifically know using that platform differences I've never seen and I'm signed in not before by it's to be honest yeah like never you might be on a platform where it's not keep that in mind I I don't really like using the OpenGL one so I'm still gonna write unsigned int here but just keep that in mind you may want to even be complex really cautious at runtime and potentially check to see if the size of an unsigned int is actually equal to the size of a GL you int make sure you use the type right not the actual like during right chill unsigned in because that's that's not a time make sure you write glu int but you you may want to be like really cautious with that but um anyway I'm just going to use on sign did the other thing we need to replace is a write buffer becomes element array buffer that's really it I mean if I go down here I'm just going to write bind buffer here to be LMS write buffer and that's it the other thing we obviously need to do is still discount somehow I'm going to write an initialize a list here that just sets count to count like that up here and then finally a get up and in line unsigned int get count Const return M account I'm actually also going to my mark bind and unbind as Const because they don't actually modify anything and we'll probably want to call them on Const objects in the future same for the vertex buffer as well so vertex buffer bind and unbind are becoming Const okay there we go so that's it that's really all there is to extracting those two let's go ahead and replace the code that we've got in the application will see the CPP file with this new kind of these new classes that we've created sorry back an application built CPP I'm going to include both of them at the top here so include vertex buffer and include index buffer and then I'm going to scroll down here and then when we actually create this vertex buffer I'm going to leave all this code and write a vertex buffer actually I'm not gonna delete all of it because I'll need to copy parts of it I'll try a vertex buffer VB we're going to write this as the size and then positions as the data so positions and then that size there we go beautiful now we actually bind it automatically here if we look at vertex buffer in the code we actually wrote we never unbind it we just leave it bound so theoretically we don't need to call B B dot bind after we do this because it or it will already be bound but obviously if we have multiple like if we create a few like this we'll have to rebind the ones that we want to use but that'll be handled by the vertex array anyway since the vertex array is associated with a vertex buffer and when we start up track abstracting the vertex array we will see how that works okay so back here we're gonna do the same thing for index buffer so index buff up IB indices is the actual buffer and then instead of six times size of unsigned end we just need to write six because we know we're supplying unsigned okay beautiful get rid of all that as well the other thing we leave here is the actual vertex array stuff right the vertex kind of buffer layout stuff but other than that that's really all we need now finally instead of binding this here we just write I bead or bind and then draw elements is something that you could theoretically put into your index buffer the thing is like the way that this usually works is that you will like if you have a complex 3d model like a spaceship right you will probably have a vertex buffer that just contains every single vertex of that spaceship and that might be like fairly big but then you may have several index buffers that draws parts of that ship because the wings will probably be a different material than the actual glass cockpit right so the the cockpit glass when you draw the cockpit windows they'll probably be like a glass material whereas the wings might be a metal material so you'll have to split them up into two different drawer holes and the way that you'll usually do that is you'll have an index buffer that just draws the wings and an index buffer the just draws the actual glass like cockpit glass and all that stuff and we'll definitely talk more about that when we start loading 3d models and start rendering stuff for reals but that's just a basic example and so because of that you still have the one vertex buffer and then you have several index buffers that are just indexed into that vertex buffer and so what you could do is you could tie your draw your draw elements or actual draw all to your index buffer because theoretically the index buffer is kind of a thing that's like a camera I'm going to draw the like this many indices starting at this offset and all that kind of stuff we're not actually going to do that because we're gonna let the renderer take care of the actual draw call so when we expand our renderer class to actually take in you know but like I want to draw something I'm supplying you with the vertex but for an index but for a material all that stuff it will just look at the index buffer and ask it what the count is and then issue the draw whole itself so that's just like if you're concerned about why I'm not putting draw elements into the index buffer and you've seen that before that's why just gonna let the renderer class take care of that okay cool so if we hit up five just now to verify that our card actually still works because that's important you can see does and everything's great now quick quick little tip here I'm not really a tip of thought if we close this you can see our application doesn't terminate and if we hit pause over here to just pause the debugger and see what's up you can see some GL clear error let's look at the course tab and see what's going on here so we're calling Geo clear error looks like it's an infinite loop and is coming from index buffer destructor which is coming from main that's because it's trying to clean up all of our stack allocated objects in other words this index buffer is trying to clean that up now the thing with this is that you can see that this is a stack allocated object so it's destructor is called when the scope exits which is of course at the end of this main function however we called lfw terminate which destroys our OpenGL context before that so what's actually happening is we now no longer have an open get a valid OpenGL context and GL check error believe it or not will return an OpenGL error if there is no context this is one of the reasons why I think I've been jealous just comedy but yes the check error function will return an error if there's no valid OpenGL context and since OpenGL itself tells you how you should be running this in a loop every time you call it it'll return an error so it's an infinite loop so one thing you could do to fix this is of course heap allocate your actual vertex array which you sorry your vertex buffer and your index buffer and then delete it before you jealous of each other which is what you probably should do anyway it's very rare to see them actually be stack allocated well yeah it's probably rare to see them be stack allocated anyway so what we can do here is actually well technically speaking we could just make these pointers and allocate them using new and then delete them explicitly like over here right so before we gel at I'll be terminate and that would fix our problem however this is kind of a unique case because we're actually making vertex buffers in the main function stop which is very rare another thing we could do obviously just pray and you start sorry here's one way to fix it I'm going to make a skirt around these dispositions thing here and have it whoops just minimize everything I'm gonna create a strip around like just before this positions thing and then have it end like over here like that okay so it's a bit hard to say but if i zoom out maybe it'll be easier to see even though the text is really tiny you can see I've made a scope of here and it's kind of over here alright so theoretically that should contain everything so if I just hit f5 turn on my code now hopefully we haven't corny variables outside I get good at compiles so everything's running now and then if I hit close you can see the application actually terminates so I'll just look quick little thing I wanted to throw in just to fix it back because that was a bit annoying when we closed it and it didn't actually terminate so we had to terminate it through visual studio which is annoying but anyway that's it so we've abstracted the vertex buffer and the index buffer class I think this episode was fairly casual probably talks pretty quickly but that's because this stuff is really straightforward and I hope you understood everything that I did there were some random thoughts here as well let me know what you thought of the video by just dropping a like if you liked it and leaving a comment and just being like hey man this was like terrible you should stop this whole usually the Tanglers probably won't matter anyway next time we're going to abstract the shader class I'll actually no we're gonna do the vertex for a class first and the shader class and then probably the renderer you guys like these videos of mine and you want to support what I do here on YouTube with C++ series and OpenGL series in the soon to come game engine series which is actually coming very soon by the way the then you can head on over to patreon home for search the churner and contribute to helping support this series and everything that I do and you'll get some cool rewards we're about to have the weekly partner sorry the monthly partner hang out I think this week actually where we basically just hang out and we talk like all other partner level tier patreon patrons thing we hang out and we just talk about stuff for like an hour and actually fun and there's some other cool rewards such as access to this source code kind of episode by episode yeah thank you huge thank you again to all the patrons because this series would not be here without them and I will see you next time goodbye [Music]
Info
Channel: The Cherno
Views: 75,954
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, abstracting opengl, opengl, classes, vertex buffer, index buffer, using opengl
Id: bTHqmzjm2UI
Channel Id: undefined
Length: 26min 46sec (1606 seconds)
Published: Wed Dec 13 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.