Kohi #007: Memory Subsystem (Vulkan Game Engine Series)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone and welcome back to the kohi game engine series today we're going to go ahead and get started setting up our memory subsystem and we're going to get started on that right now really quick though i would like to take a quick second and thank the partners of the channel ar slayer and wen chang the partner is the highest tier of supporter on the channel and so i just wanted to say thank you to our partners as well as our other supporters that are listed here on the screen so if you're interested in supporting the channel there are a couple ways to do that now first off i have channel memberships available you can access that by clicking the join button below this video i'll also provide a link to a video that i have describing the memberships and i also have a newly launched patreon page which is patreon.com forward slash travis roman and i've set that up because i've had a few requests for that versus the youtube memberships so if it's easier for you to use that instead that is now available for you so thank you all very much for your support it is greatly appreciated and i will be rolling a lot of that back into this channel so with all that out of the way let's go ahead and jump into our discussion as i mentioned before we are going to be creating at least the start of a memory subsystem now you might be asking what is a memory subsystem and why do we need it basically we want a way to track our memory allocations throughout the system as well as the ability to use custom memory allocators for certain types of allocations throughout the engine one of the things that we want to avoid is doing a lot of dynamic allocation on the fly and so having a memory subsystem sort of allows us to to control how those allocations actually occur and the other thing that would be nice to have is a way to track how much memory is being used by what types of allocations we have so how much memory we're using to allocate strings or textures for example we'll say arrays uh dictionaries all these things that we're eventually going to be creating and so we need some way to sort of track that so that if we do wind up with perhaps a memory leak this is one way that we could use to detect it we could also use this as some very basic level instrumentation and so having that control over memory is really the reason why we want to go ahead and set this up and so the system is actually going to be pretty simple on the surface and all the complexities will be abstracted away and as we expand the system we'll make it more robust and add stuff to it as we go but for right now we're actually going to start off pretty simple so to get started the very first thing i'm going to do is within core i'm going to create a new file and i'm going to call that k memory.h and this is going to be the file that we perform all of our allocations in the system with going forward so obviously as with most of our other header files we will do a pragma once and include defines h and then as i mentioned before we're going to have a way to tag our memory allocation so we will need a enumeration of what those memory tags should be and i've gone ahead and pre-populated some tags here that i think we're going to need in the future and obviously we'll be adding to this list as we can as we continue but the important things to note here is that we have an unknown type which is sort of the default right so if we if we don't pass anything to our allocation routines that we're going to be writing here in a minute it will be sort of filed away under the unknown category if you will and we're actually going to have logic to detect that to say hey if you're using this you shouldn't be you should really be using a category for it uh we also have uh all these other sort of categories in here and then we have a just a max tags here so that we can use that to iterate through all of these when we go to actually report on them so this guy here will always be the last entry in this list as with most subsystems we will actually need an an initialization routine which will actually perform some basic operations and set up some stuff that we're going to need for this tagging stuff here and so we'll have a initialize memory and of course a shutdown memory and of course these will not be exported because we're only going to be calling these internally within the engine all right so as you may have guessed we are going to need a few interface functions to actually handle our allocations and these are the ones we're going to need right now we're obviously going to be adding to these as as time continues but we have a k allocate k free k 0 memory k copy memory k set memory so these may look familiar to you from the platform layer and i did mention before that we do not want to be calling the platform layer outside the engine however these will all be exported so these can actually be called from user code if we need to actually perform an allocation but it allows us to control at the engine level how those allocations happen so for example we have our k allocate here which takes in a size which is this is basically like calling malik almost so it takes a size and returns a void pointer but it also requires a memory tag so you'd be passing it one of these things this is how we're going to categorize our memory allocations so for free we actually pass the the block and we also pass the size here which is not something that you pass to the normal free function but we do it here because we're actually going to be tagging our allocations and keeping track of how big those things are and so [Music] generally speaking whenever we do an allocation we'll know at some point how large that actually is and we'll be tracking that somewhere so when we go to free it makes sense that we also have that information and so what this basically does is it allows us to report on when we perform an allocation against the specific tag and when we free against a specific tag which is the last argument here we also have some convenience methods here just one for zero memory which just takes the block and the size of memory to be zeroed out then we also have a copy memory which works just the same as a standard mem copy and then we also have a set memory which is the equivalent of mem set and again this is so that we don't have to include those headers throughout our file and we have a little bit more fine green control about how these things work under the hood which we're not going to utilize right now but i want to get these in place so that later we don't have to change all of our code base we can just change the things behind this interface and go from there so there is one last thing that i want to add and it is also an exported function which is to get the memory usage string and this is sort of a debug function that we're going to have in place um i'm not going to mark it as debug for now i may decide to do that later but this is basically going to print out some usage statistics to the console so this is basically what our our k memory interface is going to look like in the system so it again it's pretty straightforward and simple and it might seem you know like just another layer but i promise you guys you will see the benefit of this later on down the road once we actually start controlling how our allocations work this will allow us to have very fine-tuned control over the how we allocate for specific categories okay so that is k memory.h we will also need kmemory.c and of course that includes k memory.h it is also going to include the logger as well as platform h because it is going to be calling our platform memory functions speaking of which before i go any further on this i'm actually going to go to the platform h file and if you recall we actually had this platform allocate and platform free exported we are going to not export those anymore so i'll make that quick change jump back to k memory dot c so the first thing that we're going to want is a very basic way to track our memory allocations and since we are using an enumeration here technically what we also have here is a set of array indices right because it starts at at zero and then works its way up right so we can actually technically use this in this enumeration to hold all of our allocations at least the sizes of our allocations within this particular category so what i mean by that is back in memory.c we're going to create a memory stats struct that's just private and in that we're going to hold our total allocated memory as well as our tagged allocations and you'll see here that i have the size of that array set to memory tag max tags that way whenever we add a new category here we don't have to remember to come here and add this particular we don't have to come and update the size of this okay so now that we have our struct we're actually going to create a static instance of that just private to this c file we're just going to call that stats and then after that we're going to have our initialize memory which simply zeros that out right so it zeros all of these counts out for us instead of having to do it manually so the next thing that we need to set up is our k allocate method and you'll note here that we we take in a size and a memory tag and return a void pointer so the very first thing that we want to do is we want to check this tag and say if that tag is unknown we want to throw a warning about it saying hey you're using memory tag unknown which is technically valid but it really should be reclassed so anytime we actually use this we will get a warning about it and we have that set up so that if we're just trying some experimental code or something where we don't want to create a class for it we can put this in there and it makes it very easy to find so first and foremost we will define that and then the next thing that we're actually gonna do is we are going to update this stats right so the first thing we'll do in here is we'll say the total allocated uh we're gonna add the size that's passed into that so that's the size and bytes right and then the next thing we're going to do is we are going to update our tagged allocations using the tag as the index into the array we're going to increase the size of that by the size that's passed in so this is how we're going to actually track how much memory we're using per category so the next thing we're going to do is we are going to call platform allocate we are going to pass the size and we are going to pass false right now for memory alignment i know that i've said i've got i'm going to come back to that and i promise we will touch on memory alignment at some point but for now i'm just going to hard code that to false and that's going to create our block of memory for us and then the next thing that we're going to do is something that a typical malek does not do for us and we are actually going to zero out that memory so we're going to zero out the block of memory for the size that it is that way any memory that we actually get from this will automatically be zeroed out we don't have to worry about rubbish data we will always get zeroed out memory and then from there we will simply return the block so here obviously we want to do some detection on memory alignment so i'm actually going to put a to do to come back and revisit memory alignment okay all right so the next thing we have to fill out is our k free which is going to work almost like the opposite of this particular function right so um the very first thing that we're going to do is since we have this check here on allocation we're going to have this check here on free as well so for freeing with an unknown tag we're actually going to warn about it as well okay the next thing uh as we have done up here i'm actually just going to copy this and instead of adding to the total size and tag allocation we are going to subtract from the total size and the tag allocation for that particular tag finally we're going to call platform free and again we're just going to pass the block and false for alignment so i'm actually going to put a to-do in here to handle memory alignment at a later time so basically what we're going to be able to do is based on the tag we will be able to align or not align memory based on that is what we eventually want to go for but we don't have memory alignment set up so i'm not going to use it right now again memory alignment is something i'll touch on later down the road once we actually need that optimization but this will allow us to basically flip that switch from from this point and then anywhere in the system that actually should be using that will automatically get it when we make that update okay so the next ones are pretty simple i'm not going to go through these line by line because they're actually pretty straightforward so we have k0 memory which calls platform zero memory pretty straightforward stuff copy memory calls platform copy memory case that memory calls platform set memory so again we may need more fine grain control over the way that these things work at some point so we want that flexibility but for now we're just going to treat them as a pass through okay one more thing that i want to touch on really quickly is we have our initialized memory we also need our shutdown memory which for now is actually not going to do anything because we don't have any allocations here to clean up but at some point we may actually need to perform some cleanup so i want to put the entry point here so that we don't have to change any of the outlying code afterwards okay so we have everything here implemented except for our get memory usage string so i'm going to go ahead and implement that now and what this is going to do for us is it's going to print out a series of sizes for each particular category basically telling us how much memory each category is actually occupying but we don't necessarily want to write out some huge number in bytes like something that looks like that obviously that's a crazy amount of bytes but that's not really readable it's not really usable so we want some way to sort of translate to kilobytes if we're in kilobytes if we have kilobytes worth of memory allocated or megabytes or even gigabytes right and so what i'm going to do is some quick multipliers here one for gigabytes which is technically uh the measurement that we're going to be using since we're using 1024 not a thousand megabytes and then kibabytes and uh the difference between these if you're not aware of that is kilobytes and megabytes and gigabytes are measured by multiples of a thousand and what a computer actually uses is multiples of 1024 so technically this is the more accurate measurement to use it's just um gigabytes and and megabytes and kilobytes are a little bit easier for people to understand on the surface which is why those are the ones that are commonly used but in this case uh we're actually going to use the more technically correct measurement so the next thing we want to do is we want a simple character buffer that we are going to sort of format some strings to right so i'm going to create a buffer with 8 000 characters that should be more than enough and i'm going to simply start that off with system memory use and then tagged and snip in a new line here we're going to essentially iterate through each one of the categories and print out on a new line how much memory that category is actually taking so we have this first line here and then we're basically going to append to the string as we go so the very first thing that we're going to want is we're going to want to get the offset and we're going to have to maintain this as we go and the offset is uh basically the size of this string as it is now so we're just saying the string length of buffer which i don't think yeah we haven't imported that yet so let's go ahead and include string.h and just a hint as to something else that's coming down the road we are eventually going to be replacing this with our own custom string library so this is temporary we will be writing our own with useful library functions and whatnot that will be helpful in processing strings so i'll put a to do there for that for now all right so the first thing we have is our offset and what we're going to do is as i said we're going to loop through each one of these categories or the tags right and we're going to print those out one by one each on its own line so the first thing we're going to do is create a couple quick variables we're going to create a small character array here for the unit and we're going to start this off with xib which actually means nothing the x is just a a character that i've chosen and we're actually going to switch out that x to something else depending on which one of these categories we fit so that's sort of our template string and this obviously includes an old terminator that's why it's four and then we have our amount here which we're defaulting to 1.0 f but our amount is actually going to be the calculated the calculated gibba bits megabits or gibba bits so that needs to be floating point because it's going to be fractional numbers so the next thing is some quick detection code here to see which category you fit in so basically we check to see if this particular tag is greater than or equal to the size of a giveabyte then we go ahead and replace that with a g so we replace the x with a g to make it giveabytes and then we take our amount here and divide it by gibba bytes to actually give us our our final amount we then do the same check if it doesn't meet this criteria we then check for megabyte megabytes and then kibabytes and if it doesn't mean any of those then it means that we are we just have an amount of bytes in which case we actually just set the first character to b and then the second character to a null terminator and that is what we put in instead okay so the next step of this is actually going to be appending to our string however we have the size and the unit right to print out to the string but we don't actually have a string representation of these tags so what we're going to do for right now and we will be making something that's a little bit more robust in the near future but what i'm going to do right now is i'm going to create a array of character arrays basically and i'm going to call this memory tag strings right and this is going to be the same size as our tagged allocations right and we're going to have one of these string representations per category and i've aligned these strings here so that they're all the same length this is probably not the most ideal way to do it but it is the simplest way so for now we're going to stick with this and we'll come up with something a little bit more robust down the road but what this is going to allow us to do is basically say by index get me the string for the particular category one other thing we're going to need to do is at the top we are going to also need to include stdio.h because we are going to be using sn printf so back down here in our method we need a line that looks something like this so our we're going to call sm printf and we're going to say the buffer plus the current offset so basically this is some pointer arithmetic here to say we want to start at the buffer plus whatever the current string offset is our buffer size is 8000 so we won't go over that and then here's our format string so what this basically taking is i've spaced this over a little bit just so that it's sort of two spaces in from where this string begins it makes it a little clearer uh in the logs and then we're going to print out a string which is going to be our memory tags at index i which is going to be our current memory tag and then a semicolon and then we're going to do some formatting here for the size so this percent point 2 f is basically saying that we want two decimal places for our fractional representation of how big of one of these units it is except for b so we're basically saying we want to format that down to two decimal places of our float and then we're going to print another string after that which is the unit right so memory tag string amount unit from that we will be returned a length so sm printf actually returns the number of characters written to the particular string area that we've designated here and so that is actually exactly what we need to add to our offset so we extract that length which is basically the number of characters written and then we go ahead and we add that to our offset i could have done this in one line but i feel like this is a little bit clearer as to what's going on so this is actually expecting to return a character array and what we have here is basically a sort of a character array in the stack right this is not dynamically allocated and so we actually need to return a dynamically allocated array which is not ideal but this is again something that's a debug function it's not something that should be called constantly so i'm not really worried about performance here so in this case i think it's fine to go ahead and return a character array so what i'm going to do is i'm going to call the underscore string duplicate method which takes the buffer string that we have here and performs a string copy to a new newly allocated pointer so this actually performs the allocation of the hud and then returns that for us and so we're going to assign that to uh something called outstring and then we're going to go ahead and return out string so one thing we'll have to be careful with is anytime we call this we'll also have to free that not ideal but again this is just a sort of debug thing for visualization so this will never actually be used in a release build so i'm not really worried about it from a performance perspective okay so that pretty much takes care of memory for now i think what i'll go ahead and do is go ahead and tie this into the application so i'm going to actually put this this is probably going to be the one subsystem that i'm actually going to include in entry.h because this is low level enough that i i feel like it's actually needed at this level so before we do anything else in our main function we're actually going to include k memory [Music] and before we do anything else we are going to call initialize memory here and by proxy right before we return we're going to call shutdown memory here so like i said this is probably the only subsystem that's actually going to be created at this low level because it's so necessary to to actually have this in place because we're going to be doing lots of allocations probably at this level before we actually even get into the engine so with that i do want to point out something that somebody brought up in the comments where the application calls initialize logging here in application create and somebody pointed out that there are actual logs that are being written before this is call before this is called which means that it will not be written out properly to a file when we actually start writing out to a file which is fine because the only things that are actually called at that point are things which we don't necessarily need written to a file so if that ever changes i could just move this to entry.h as well and call it here for now i'm not going to do that i'm going to leave it in application but if for some reason we want to do that we would do that probably right after initialized memory so nothing should be called before or after these memory calls here but if we wanted to address that we could certainly do it that way okay so one more thing [Music] what i'm going to do is just to test and make sure that this works [Music] i am going to do a quick search in our code for platform allocate and you can see here we're using it in a few different places so the platform uh using it the platform itself using it uh makes sense platform h platform linux okay so these are fine the only one that really doesn't make sense is this entry dot c in our game so let's go ahead and change this to instead of using platform we'll say core kmemory.h and we'll change this to k allocate and this instead of wanting uh instead of wanting to know whether or not we're going to be aligning this or not it wants to know what the memory tag is so i'm going to say memory tag and we'll use game for this right because it is the game level it makes sense to tag that allocation as the game so if we go ahead and go back to our application right before we begin our game loop let's go ahead and call our method to actually print that out so i'm going to include at the top here [Music] core k memory okay so right here we'll go ahead and just say k info and then get memory usage string and we'll just log that directly and i'm not going to worry about the fact that this is technically leaking memory i'm only calling it once i'm not going to worry about it this is just the test to make sure this works anyways okay so it looks like we have a couple of unresolved external symbols initialized memory and function main okay so uh because i'm getting undefined or unresolved external symbols i'm gonna go ahead and export these for now it's not ideal but i can go ahead and move those later so just so that it builds for us and that we can run it and test this out i'm going to go ahead and do that okay so i'm going to go ahead and run and we now see that our our log actually works right so we have an output for each one of these individual each one of these individual categories right and we're not actually allocating anything yet but we do see here that our game does in fact occupy 4 bytes so we know at least that this system is working as intended again this is probably something that if we're really wanting to trace something down we'd probably want to write this a few times more than once and at that point we probably want to manage the memory a little bit better but for now i think this is proof of concept that our system is working just fine okay so one last thing i want to do is i want to just do a quick search for any maliks that we're using to try and get rid of those things and it doesn't look like we're actually using any that i'm too concerned about replacing so i know that one of you had pointed out um in a previous video that uh the platform layers are actually calling malik at this level that is actually fine um i could change this to use the platform allocation but this standard malloc at this level is i'm actually fine with because at this point the only thing that we're actually allocating memory for is something that's part of the startup routine in which case dynamic allocation is just fine so i'm not going to worry about replacing these let me do a quick check also on free and it does look like the only thing that is using free is again the platform layer which i'm fine with so i think at this point um that pretty much covers our our memory system at least for now now i realize this doesn't look like we're actually doing a whole heck of a lot but we're going to be tying in a lot of instrumentation to this as well as custom allocation routines for some of these specifically um that use custom allocators that we're going to be writing down down the uh down the road the other reason i wanted to put this into place as i said is because we don't want to have to go changing the rest of the application to use this down the road we want to get this in before the application actually gets too large that way we can just use this going forward so there are a couple more small systems like this that we're going to be putting into place before we actually move on to the renderer just because we don't want to have to go back and do tons of refactoring later so that is going to do it for this particular video thank you guys so much for watching please give the video a thumbs up if you like the video if you haven't already consider subscribing hit the little bell notification there so you get a notification as to when the next video in this or other series drops and if there's anything that you guys have seen along the way leave a comment in the comment section below and i'd be more than happy to answer it thank you guys so much i hope you learned and i will see you next time [Music]
Info
Channel: Travis Vroman
Views: 2,211
Rating: undefined out of 5
Keywords: write a game engine, write your own game engine, make a game engine, how to write a game engine, learn to write a game engine, writing a game engine, make your own game engine, game development, game dev, game engine, game developer, how to, tutorial, programming, gamedev, vulkan game engine, vulkan, game engine series, how to make a game engine, vulkan api, 3d game engine, vulkan engine, vulkan tutorial, 3d graphics, learn vulkan, 3d vulkan, how to vulkan, vulkan renderer
Id: _GBUCo2FbUk
Channel Id: undefined
Length: 31min 57sec (1917 seconds)
Published: Fri Apr 23 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.