Refs, Scopes and Smart Pointers | Game Engine series
The Cherno
·
Intermediate
·6y ago
Key Takeaways
The video discusses the use of references, scopes, and smart pointers in a game engine, specifically in the context of memory management and ownership, using tools such as std::shared_ptr and Hazel ref.
Full Transcript
hey what's up guys my name is China welcome back to my game engine series so today we've got a very very exciting episode but maybe not maybe to leave starting to some people it's actually pretty exciting to me even though it might be considered objectively tame but today we're gonna be talking about references and smart pointers and all of that fun stuff that we just need to consider for kind of our core engine because this kind of stuff is stuff that you kind of just brush under the rug it's stuff that everyone loves to just ignore and then eventually all of your technology will come crashing down because you just didn't consider this back in the day of you kind of beginning this stuff so last time we talked about something else that I can't even remember what did we talk about last time shader abstraction and uniform so check out that video if you haven't already had to check that out for me because I'm so focused on today's episode which is references and small pointers and unique pointers and how all that system works okay so enough kind of fluff enough of that crap we're gonna actually get into it now so what is going on right now with our code pretty much everywhere we're using this kind of shared pointer thing we're you know just we need to create a shader so it needs to be created on the heap so what do we do we encapsulate it inside a shared pointer object for two purposes first of all because we want to control the lifetime of this object we want to somewhat tie the ownership to sandbox app but also make it so that if this app kind of gets destroyed or any scope that we're dealing with gets destroyed this object too will get destroyed because it's a shared pointer provided there are no other references to it now we could have used the unique point if you had no reason kind of not to rewrite you would think you know just type in unique pointer scoped it I think some people actually did suggest that in the comments though why is this not a unique pointer because it needs to be you know clearly the lifetime is tied to this it can't exist outside of sandbox app or anything like that should we be using unique pointer now smart pointers are interesting because they kind of provide us with a system that helps us write they're there to help us they're there to say that you're not gonna actually call delete are you will handle that for you based the kind of traits of the object that you're kind of shoving into that smart pointer now I have a video on smart pointers I even have a video in the C++ series specifically about what I think of smart pointers now I think that has changed in that video I think isn't necessarily about what I think of them but it's just how I think they should be taught because I think there's a lot of emphasis on smart pointers these days and people getting really angry in comments in my comments being like why why did you know just write new did you know just write delete why that's wrong the point is smart pointers are fantastic I wouldn't leave without smart pointers because if smart pointers didn't exist I would just write a smart pointer class and just we would just use that but the problem is more it's kind of deeper than that the problem is the fact that it's not just about new and delete it's not just about abstracting that away and especially automating that delete part it's 50% of it if not more it's about ownership it becomes a question of ownership right this is now owned by something if we make this a unique point I guess what that means that this sandbox class literally owns that shadow right because if it doesn't own that change some of the sandbox classes the example a class but the point is that example a class literally owns that shader why it's nothing you can do you can't copy you can't hand over ownership that's it when that example layer gets popped off the layer stack because the application is closed because we want to transition to a new level because we want to do anything like that right as soon as that dies that's it our shadows wiped off the face of the earth it the memory is released Kabir instantly reclaimed it's gone right I want to suggest that that is actually an issue that's a problem we can't have that we literally can't have that because right now if that were to happen I think we'd probably be ok but you have to consider the bigger picture let's take a look at a renderer let's have a think about what our renderer would be like if it was actually you know a solid renderer and not just like a bunch of functions and you know I mean we're on our way we're on our way we're gonna get there but right now this renderer is you know very very bare-bones if we had a real renderer let's have a think of what that would look like okay so we have a renderer submission right we call hazel renderer submit what does that do well at the moment it takes in a shader in the future as I said it would take in a material potentially but right now it takes in a shader what have we done we've submitted a shader for rendering we've submitted a vertex array for rendering of vertex array that's also an asset right any kind of asset is going to be a pointer essentially it's gonna be a heap allocated object and eventually probably not allocated in the heap but allocated in kind of our memory arena or something like that vertex array is a it's it's it's a resource it's an asset that we need to actually use to render that and we have a transform now I transform that's a map for I mean argue belief you're just not really a real asset you could if you wanted to 64 bytes of data you probably copy that and you take that and that's it you can just deal with that but shaders write that shader that vertex array their actual assets right what I mean by that is we can't just like decide that if I want to retain an asset I'll copy it that should never be done because it's an asset you know this might be huge right I mean I sure it might just be like a opengl ID or something but for all intents and purposes it's an asset so what are we done we've submitted it for rendering rendering what did I what I said about rendering all this time it's deferred right you will not be rendering stuff immediately if we look at submit right now it calls render command at draw indexed what is it doing its immediately drawing not gonna happen in the future right because as I talked about we need to be able to have a context of an entire scene that we want to actually render before we start rendering right so before we actually start even rendering one single object on the screen I want to have practically the full scene of everything because I need to do sorting I need to do actual like calling potentially right I need to bash things together and improve the performance so many things I might want to do right I have to have the whole scene in order to do that so that I don't do be inefficient rendering because that would be bad especially for us because we're trying to be all cool and hip and trend so the point is I need to take this shader object that has just been given to me and I need to kind of hold a reference to it if I don't hold a reference to it that's gonna be problematic the vertex are a same thing I need that vertex array later right how later well I mean I don't know in the future I'm gonna render this in the future so first of all set one's render at rendering is deferred now step two most engines are multi-threaded right you would not run an engine on a single thread I don't think that's ever happened but well it's definitely happened before but I don't think recently any engine really that has shipped a game as well we shipped on a single thread why because your computer your device that you're on has multiple cores multiple actual like virtual threads it has a system for dealing with stuff at the same time so if you're literally not doing that you're just wasting potential performance stuff that you could get for free you're just wasting it right it's kind of like having a gas stove you have like five different things you need to cook here to fry up this you need four out that all in separate pans and you use one of your gas burners right if you've got an electric stove for some reason then you know adapt that metaphor to that but my point is what you wouldn't do that you'd use all four burners I came up with this on the spot by the way I before is like perfect because a lot of things have like four cores and anyway the point is you're we're not gonna use a single burner at a time that's silly right all your food's gonna get cold you're gonna use all four burners at once you could create this masterpiece when is cooking with China coming very soon but my point is that you're not gonna do that you're gonna actually use each stove same same thing here you wouldn't just right imagine that uses a single thread because that's somewhat silly so because we use multiple threads in our engine that read the command cue that we've kind of deferred that all the stuff that our renderer has to do that's going to run on a separate thread what that means is that while our current kind of thread our game thread our updated our application thread whatever while bad is just you know submitting data to the renderer the renderer is actually at the same time processing rendering that previous frame worth of data okay so what we end up with effectively is something that is one friend behind now what happens is once that once that application has finished submitting everything it needs to do for this current frame it will move on to the next frame while the renderer renders that frame consider this we've changed our level right well we've don't know we've released an object from memory because the application thread is done with it right now that up that let's just say that's example layer we've released example layer from memory right it's gone however next time we roll around to the next frame that our renderer is going to want to render right that shader object that vertex array has been deleted so explosion nothing gets rented we crash tears everywhere probably from just everyone everyone all of you crying at the same time ridiculous right so we can't have that what that means is that in some form or another that render thread has to actually have a strong reference to that object strong reference meaning that it has a reference that is somewhat counts as like ownership meaning that until that reference is released if the object is not freed from memory this sounds exactly like a reference count system right that's a ref count system okay shed pointer is a ref count system that's why we're using shed pointers at the moment now here's another little curveball and by the way that little explanation there just keep in mind that that that is why I personally think that pretty much every object in your engine should probably be a shed pointer people may make people may say that that's inefficient you have to use unique pointer in a lot of cases and I would probably maybe agree but nor because the performance overhead is so negligible from using a set point or a very unique pointer in certain situations one hundred percent you might want to use a unique pointer but to be honest I think from experience and I'm just kind of thinking back in my head now most of the times where that has been the case I have just used a roll pointer because just because right I don't think if I'm dealing with that kind of stuff where performance is such an issue I'll mostly usually use roll pointers not just but not because unique point is a slow of course they're not they have zero overhead it's because that kind of code base that requires that is most likely going to benefit from a roll pointer you could still enclose it in you could still encapsulate in slight a unique pointer if you wanted to but from experience probably not something that I've actually done the point is that unique pointers are great and you might want to use them but in pretty much all cases especially right now when you're not when you're not just bottlenecked by the fact that you're sharing 90% of your thread time is because SharePoint is incrementing and decrementing references that's not the case so every single asset in the engine that gets submitted to the render thread especially should be a shared pointer and that's kind of what we're dealing with now before we get into the code we take a look at this because what I want to do is change the way that we actually present share pointers like just change the way they look and you'll see what I mean in a minute before we get into that I just want to quickly mention shared pointers do have overhead right now I've just mentioned that it's negligible and I'm probably right but in at some certain situations maybe we decide that we don't want a shared pointer necessarily right because shed pointers do a few things I mean first of all they're not an intrusive rep count object meaning the reference count is not stored as part of the actual object that you've allocated it's stored in a separate control block which in most cases is going to be in line with your object so that's somewhat negates that issue the other issue that we could potentially face if we're nitpicking is thread safety so shared pointers are thread safe meaning that the way that they achieve that thread safety specifically is they use an atomic increment and an atomic decrement detriment decrement I don't never say that word anyway they decrement decrement decrement is gonna be decrement they decrement an increment the pointers that the reference count atomically which is fantastic for thread safety means we'll never get eras really but that obviously has to lock everything up and do that atomic increment decrement that stuff takes time could potentially add a little bit of overhead now look I'm not gonna argue with you I'm not one of those just crazy people who will absolutely fight over such a small like in reality such a small kind of hitch in performance right especially because there's so many other things we could optimize but since it could be a problem and since we might want to say ah we don't need thread safety we want to just run fast although we weren't run fast with this but you know just bear with me we might want to change it into our own kind of account object right which would fall intensive purposes probably be a shared pointer class like it'd probably be exactly the same except it would do the normal increment decrement is at an atomic one because of that we might not want to change in the future for whatever reason shared pointers might not be something we stick with forever so because of that it has gonna be very difficult change because we have to find replace all Chad pointers and replacing with something else maybe in some cases we actually do want to use SharePoint as though all right so what we need right now is well as two issues there's that which I've just mentioned but there's also the fact that STD shed pointer everywhere kind of looks a bit ugly no it's not the worst thing in the world but it kind of looks ugly so what we're gonna do is we're gonna create our little type that is essentially a ship it's gonna be a shared pointer right now in the future it could blossom into its own type where it's like you know actually a class and everything but for right now we're just gonna change it so that it's gonna be our own thing and that's gonna be it's gonna be it's gonna be called a ref so it's gonna be called a hazel ref okay and what we'll do is for the unique pointers we'll also create a hazel obscure right so we have a scope and we have a ref so the scope kind of pointer is gonna be our hazel scope thing and then our ref is gonna be a shared pointer so unique pointer scope because that's what it is it's just a scope and our reference we're gonna call ref so let's take a look at what that's gonna look like and see if it makes our codebase look nice up all right so the first thing that we're going to do is go to our core H file which contains pretty much all of the core stuff and I'm going to add a namespace called hazel right nothing too special there and then what we're going to do is create essentially a type def so we're going to use using for our two different types so scope being a scope pointer right so this will be a unique pointer and then we're also going to create wrap for our ref countered object which is a shared pointer so because of course we want this to work with templates that's why we're actually using using apart from the fact that using is the thing to use these days it also enables us to actually define us as a template so we have a template I've named tape and then this just goes here and now what we can actually do is use scope with a you know a type when we actually make it whenever we end up using it so this is obviously vital because otherwise this wouldn't really work we'd have to like and and I mean that's the other thing like you could definitely do stuff like you know let's just say we had a shader class right instead of writing RAF shader which you currently have to do and this is as opposed to you know STD shared pointer so what's what is basically done is enabled us to change this card into you're just simply writing ref shader and if we're outside of the hazal namespace we'll have to do that kind of shows you very easily that it says it's a Hazeltine unique pointer we would do subscribe right but apart from doing that you could also create your own using we're basically shader ref is a new type that is that right and if you do that a lot maybe that might make sense that's not something that we particularly care about that's another common way to do this where basically you just define you type you know at the end of your shader class officially in shade up up H I mean we could potentially just add like they're using shader reft people's you know either yesterday she had points our shader or whatever or you could just simply in this case just write ref shader but I think ref shader is like is not too difficult to write and I don't I don't like creating a billion different types so because of that you can basically just stick with this now this is all the code that we need right it's really simple what it means in the future is we can simply just change this scope thing you know to be an actual template so type name what am i doing template type name team and then you know class score or whatever and then we can just make a class out of it if we want to and we don't need to change anything in our code base as long as we're we've used scope and ref as a lot of those api's that consistent with shared pointer and you need to point out anyway that's done super-exciting what we'll do is we'll include memory up here just in case we include this header file we don't want to suddenly have issues if this header files like included before another one and suddenly doesn't know what STD you need pointer or share pointer ah so we'll just include memory up here and then we can start changing everything okay those are the fun part so what we'll do is for all of this stuff again if we wanted it to be a unique pointer we will just do hazel scope because we're in the sandbox class right now and we haven't we're not inside the hazel namespace but of course we want everything to be a ref that is a resource so we'll put this in and of course one other thing that I wanna quickly mention is this does not in any way interfere with the with the possibility of having like an asset manager system this like honestly in a very rough way is kind of like an asset manager system since we're reference counting everything we know that if I give an asset if anyone is holding a reference so in other words if anyone requires any given asset for any reason that asset will start that asset will stay alive but as soon as like you know we've basically unloaded a level right the level classes like gone and we don't have we don't need this asset anymore it can it will get released from memory well don't you want that I mean it in a real engine you probably wouldn't want that right because assets can be shared between levels and you yeah you kind of have this whole system as well so in other words in the future we will have an asset manager welcome probably control the ownership in more kind of a finer grained fashion than just by having it as a shared pointer but this is a fantastic start and if you just need to like have a quick little asset system in your game engine like honestly just use SharePoint is that great so that's pretty much done I think for this class really all I'm gonna do is just search for STD I don't think we've got too many of these but STD shared pointer across my entire solution maybe the entire solution was a was I'm not necessary we actually do have a quite a lot of them of course but we use them everywhere right for things like this so this just becomes a ref and you can see that because we're inside hazel we don't need to do anything we can just change this to be ref and I guess we'll just work through all of these basically and change them all to be ref there might be quite a lot of them okay so we have like you know we have them up here as well now nor note this right I'm not changing them here this I'm leaving as STV share pointer the point of this is not to just rename the standard template library and just create our own version of it no that's silly right I mean we're not gonna we could type def string STD string to just be like string with upper case with an uppercase s and that would be like our string class right no I don't want to try to do that what we're trying to do is share some line as to what is like a Hazel reference and what is like you know and I see you share pointer this is like this is a log thing right we're still gonna use shared pointers in our code if we want to but for things that are explicitly hazel only write things that maybe in the future would be handled by an asset manager so in other words assets right that's the kind of stuff we're trying to rename into reps and like you know scope pointers as well so that's why we're gonna just leave these alone okay let's see vertex array for example see how this is returning like an index buffer clearly that index buffer is simply a it's like it's an asset so we just return a reference and this is returning a vector and this stuff is well vertex buffers and index buffers those are also assets that looks pretty good now let's look at render API we have this yeah so I'll just rename this you guys can I get the point of this I probably just cut the video here in just myself but I'll still drive it it's just in case we run into anything fun or and or unusual okay and then render CVP we renamed this stuff really does make our code a lot cleaner as well that's one of the things that I like about doing this I gave what a bunch of these things and this needs to be a hazel wrap a bunch of these all of our data that we create inside our example layout yeah that looks pretty good I'll just do a another search again for SharePoint err okay so we're looking a lot better we've got stuff in log but that's again that's totally fine we're gonna leave that alone and then there's a bunch of stuff in OpenGL that text array I could again I could do a finer place but you'll notice I'm doing this manually just because sometimes like we want the hazel namespace sometimes we don't want the hazel namespace and so because of that's just easy you know if it is though I can easily just find a place that it's just a ref in this current document because that will just work out nicely okay cool that should be it again I'll do another search just to be like a hundred percent sure okay so now the only reference as you can see in a search I inside log and call which is fantastic so we're not using smart pointers basically anywhere eyes and we're not using SharePoint or anywhere except that a log class we're just simply using hazel ref which is pretty cool all right obviously we're gonna try and compile this code make sure that we make any mistakes and I mean it should compile and run as usual all right so we get no compile errors and now if I try and run this code we should get the same result as last time where we have our kind of weld here and everything seems to be working correctly now I hope you guys enjoyed this video if you did if you hit the like button you can help support this series and everything I do here on YouTube by going patreon account for size to show no huge thank you as always to all the lovely patrons that make this series possible started doing new exclusive videos as I mentioned last time where basically if you support even for as little as three dollars a month you get access to a whole bunch of exclusive videos in the last one I actually showed a bunch of like the hazel development branch and I think that I actually am gonna put that video off his be my already just kind of share that exclusive video with kind of all of you in general but there's just tons of stuff to kind of explore there as well as access to the hazel development branch source code in which this stuff and much much more including like physically based rendering and like animation and a whole bunch of stuff has already been done and obviously it helps support this series and just ensures that I can keep making these videos full-time because that's my this is my full-time job right now which is really really cool and that's all thanks to you guys and I'm super excited for it for the future I'll see you guys next time goodbye [Music]
Original Description
Patreon ► https://patreon.com/thecherno
GitHub repository ► https://github.com/TheCherno/Hazel
Instagram ► https://instagram.com/thecherno
Twitter ► https://twitter.com/thecherno
Discord ► https://thecherno.com/discord
Series Playlist ► https://thecherno.com/engine
Gear I use:
-----------------
BEST laptop for programming! ► http://geni.us/pakTES
My FAVOURITE keyboard for programming! ► http://geni.us/zNhB
FAVOURITE monitors for programming! ► http://geni.us/Ig6KBq
MAIN Camera ► http://geni.us/t6xyDRO
MAIN Lens ► http://geni.us/xGoDWT
Second Camera ► http://geni.us/CYUQ
Microphone ► http://geni.us/wqO6g7K
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from The Cherno · The Cherno · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
3D Game Programming - Episode 1 - Window
The Cherno
3D Game Programming - Episode 2 - Game Loop
The Cherno
3D Game Programming - Episode 3 - Arrays
The Cherno
3D Game Programming - Episode 4 - Drawing Pixels!
The Cherno
3D Game Programming - Episode 4.5 - How Rendering Works
The Cherno
3D Game Programming - Episode 5 - Playing with Pixels!
The Cherno
3D Game Programming - Episode 6 - Performance Boosting
The Cherno
3D Game Programming - Episode 7 - FPS Counter
The Cherno
3D Game Programming - Episode 8 - Alpha Support and More
The Cherno
3D Game Programming - Episode 9 - Beginning 3D
The Cherno
3D Game Programming - Episode 10 - Floors and Animation
The Cherno
3D Game Programming - Episode 11 - Rotation
The Cherno
3D Game Programming - Episode 12 - User Input
The Cherno
3D Game Programming - Episode 13 - Render Distance Limiter!
The Cherno
3D Game Programming - Episode 14 - Basic Mouse Movement
The Cherno
3D Game Programming - Episode 15 - Textures + More!
The Cherno
3D Game Programming - Episode 16 - Walking, Crouching, Sprinting + More
The Cherno
3D Game Programming - Episode 16.5 - Exporting Runnable Jars
The Cherno
3D Game Programming - Episode 17 - Small Adjustments + Birthday!
The Cherno
3D Game Programming - Episode 17.5 - Creating an Applet
The Cherno
3D Game Programming - Episode 18 - The Beginning of Walls
The Cherno
3D Game Programming - Episode 18.1 - A Few More Things
The Cherno
Episode 18.5 - Creating an EXE File in Java
The Cherno
3D Game Programming - Episode 19 - Rendering Walls
The Cherno
3D Game Programming - Episode 20 - Continuing Walls, Fixing Bugs, and Managing Crashes
The Cherno
3D Game Programming - Episode 21 - Texturing Walls, Fixing Clipping, and Fixing the Mouse
The Cherno
3D Game Programming - Episode 22 - Random Level Generator + Properly Fixing Clipping
The Cherno
3D Game Programming - Episode 23 - Graphical User Interface (GUI) Launcher
The Cherno
3D Game Programming - Episode 24 - Making Our Launcher Work
The Cherno
3D Game Programming - Episode 25 - Writing and Reading Files
The Cherno
3D Game Programming - Episode 26 - Custom Resolutions
The Cherno
3D Game Programming - Episode 27 - Decorating the Launcher
The Cherno
3D Game Programming - Episode 28 - Continuing our Custom Launcher!
The Cherno
3D Game Programming - Episode 29 - Launching The Game
The Cherno
3D Game Programming - Episode 30 - Colour Processing In-Depth
The Cherno
3D Game Programming - Episode 31 - Sprites!
The Cherno
3D Game Programming - Episode 32 - Sprite Mapping
The Cherno
3D Game Programming - Episode 33 - High Resolution Rendering
The Cherno
3D Game Programming - Episode 34 - Entities
The Cherno
Genesis - My Game for Ludum Dare 24
The Cherno
Vlog + Ludum Dare Results
The Cherno
Game Programming - Episode 1 - Resolution
The Cherno
Game Programming - Episode 2 - Threads
The Cherno
Game Programming - Episode 3 - Game Loop
The Cherno
Game Programming - Episode 4 - Window
The Cherno
Episode 5 - Buffer Strategy
The Cherno
Game Programming - Episode 6 - Graphics Initialized
The Cherno
Game Programming - Episode 7 - Buffered Image and Rasters
The Cherno
Game Programming - Episode 8 - The Screen Class
The Cherno
Game Programming - Episode 9 - Rendering Pixels
The Cherno
Game Programming - Episode 10 - Clearing the Screen
The Cherno
Game Programming - Episode 11 - "Out of Bounds, Baby!"
The Cherno
Game Programming - Episode 12 - Negative Bounds
The Cherno
Game Programming - Episode 13 - Timer
The Cherno
Game Programming - Episode 14 - FPS Counter
The Cherno
Episode 15 - Tiles
The Cherno
Game Programming - Episode 16 - The Map
The Cherno
The Walls 2 - Minecraft PvP Survival Map
The Cherno
Game Programming - Episode 17 - Key Input
The Cherno
Game Programming - Episode 18 - Controlling The Map
The Cherno
More on: Systems Design Basics
View skill →
🎓
Tutor Explanation
DeepCamp AI