I need advice for a game that might be using too many sprites.

Discussion in 'Programming' started by ddddd54543, Sep 5, 2019.

  1. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Alright, so here's the deal. I've been working on a game in my spare time for the past year or so and building it up over a very long time, and I think I've gotten to the point where it has too many sprites to run on my computer.

    I'd made it up to programming the final levels when suddenly it threw me "Unexpected error occurred when running the game" anytime I tried to load it. After looking into it for a while and playing with some sprite assets, I came to the conclusion that I've been using too many sprites and it was shorting out the memory. There are admittedly a TON of sprites, the final boss in particular has three different parts all of which are heavily animated and have very large sprites, like 500x500 pixels on rough average and over two hundred of these subimages total for each part. The game runs fine if I take all of the final boss stuff out right now, but even without that giant chunk, it still uses enough sprites that just adding a few more will cause the same problem. Maybe I was overambitious on this one? I don't know. But it's kind of preventing me from progressing more with programming the game and becoming a problem.

    Maybe keeping the sprites external would help? This could theoretically help according to some posts I've read, though I tried doing some of this according to some old tutorials I found on YouTube and just ended up at the same dead end I hit before with the same "Unexpected error" message showing up, just this time happening after the loading bar finishes filling up. I'm guessing the root cause of the issue is the same as before.

    The computer I work on isn't particularly up-to-date, I use an Acer laptop running Windows 8.1, with 8 gigs of RAM on it and a terabyte hard drive (that's nearing full since I do a lot of video work, but I've had no problems with old games I made that take up significantly more hard drive space than the one I'm working on). Perhaps I may be due for a RAM upgrade or a different hardware upgrade or even a whole new laptop or something like that? (well maybe a whole new laptop is a bit much but you get what I mean) While I do prefer the idea of running the game on lower-level machines like mine, this could be a sacrifice I'd be willing to take, this game is more of a personal passion project than something I'd planned on rolling out with a big release or the kind of thing that would get a lot of attention so limiting it to people who have more RAM wouldn't bug me if that happens to be the problem. (Starting to get the feeling I should be upgrading my RAM soon anyway.)

    As an aside, I also admittedly have been programming this game on Game Maker 8.0 Pro. Yes, don't laugh, I'm aware that it's *extremely* outdated, I've just been wary about upgrading because I'm so used to the workflow of that version, and to be honest I don't really think upgrading to the latest version of Game Maker Studio is going to actually help my problem. 8.0 has been working perfectly fine for me up until this problem popped up, and all signs I've read around have pointed towards this being more of a problem on the side of Windows. But I could be wrong, if upgrading the version of Game Maker I use will help, of course let me know in that regard.

    If possible I do still want to carry out what I initially had in mind for the game with the overabundance of sprites and all without curtailing the stuff I already had or completely starting over from scratch, but it is entirely possible I've been going about something wrong this whole time that I never thought about. I get the feeling some giant overhaul will be needed at this point, just need advice on the best way to move forward from here.
     
  2. SupernaturalCow

    SupernaturalCow Member

    Joined:
    Sep 11, 2016
    Posts:
    142
    I will preface this with: I have very limited experience with the specific goings-on with GameMaker under the hood, and have done very little in the way of empirical testing for how the engine operates outside of my own niche scope. My own projects rarely use more than 10% of the GM functions, and are always encapsulated in 1 object and 1 room. Having said that, we could talk all day about the conceptual issues with resource management.

    The primary frame of mind I think you should be aware of, is "how many different sprites are required at any given, singular point in time?" If the answer to that is "less than a 2048x2048 texture page", I wouldn't worry about changing your sprites. In 2019, computers can handle a lot of processing power, and usually what happens (in my experience) when a project gets too slow, is that you're putting too much stress on one particular task, and not divying it out to other tasks. In your case, I suspect that task to be the texture swaps in your graphics pipeline.

    This might be obvious to you, but GameMaker (and your computer) has no concept of what a sprite is. All it knows is that when given a point in 2D space (called a UV coordinate, because XY was taken), its job is to find a colour value in a lookup table (the texture page), that corresponds with the position of the sprite on that page. E.g. if your main character is a rectangle with a width and height of 10, and it's stored in the top left of the texture page, then when it's your main character's turn to be drawn to the screen, your computer will go over each of the 100 pixels in the rectangle's area, finding the colour of the pixel at 0,0, then 0,1, then 0,2, etc. until all the pixels of that sprite have been found. You may have heard of the fragment shader - this is what it does.

    As you might imagine, doing this for every instance in the game is quite a laborious process. You've got: 1. For each instance; 2. For each pixel in width; 3. For each pixel in height: 4. Find the corresponding pixel's RGBA value on the sprite sheet. And then we do stuff with changing the colour with blend modes and whatnot. One of the biggest ways in which games have gotten faster, is when we decided to give this task to a 'graphics card', which is something we built that is optimized to be very very good at this kind of singular instruction on massive data (not verbatim, but it is called 'SIMD' processing). The thing about this, is they can be so fast because they're doing the same thing over and over again. Take UV coordinates, check texture page, return with pixel. Take UV coordinates, check texture page, return with pixel. Etc. This is great and all when you only have 1 texture page... but what happens when they don't all fit on there anymore? The instruction isn't singular anymore, and you do what's called a batch break. Again, this is just my own understanding of it all, but the general purpose for this is to tell the graphics card to stop what it's doing, and accept a different argument for its process. This is why you shouldn't change the blend mode or colour too often - because it breaks the 'SIMD' batch. And this is why you can't keep asking it to swap out texture pages. It's very very good at doing 1 thing very fast, but not so good at doing maybe 7 different things, even moderately quick.

    I'd say this is where most projects suffer their first real performance deficit. The project has been building and building, sprites were added in over time and there was never a problem, but now you're asking for too many texture swaps, and the game runs at an absolute crawl. All because you put too much stress on one particular task.

    So, the solution is to divide those tasks up. Gamemaker offers functions for loading and unloading textures from memory. You can experiment with these and the debugger tool (under the graphics tab) to see how many textures you're using. This is of course using GMS:2, I don't know what the equivalent is with your version, and I don't know many of the differences between the versions. I do know, however, that there were some pretty fundamental changes to the graphics pipeline between Studio 1 and 2, that may severely impact performance on the older engines. Besides, dude, the workflow in GMS:2 is way better - get onto it.

    Anyway, once you're kosher with managing textures in memory, the next step is to use texture groups to divide your sprites into groups based on what will be used and when. If you were to have all the sprites in one area on several different pages, for example, you're having to go backwards and forwards between pages every frame, all because you're taking up precious space with sprites you're never going to look at in this area. If that's the case, you've got an easy optimization fix. In this way, if you group all of your sprites by region, and even group your bosses separately, then you can load only the textures you need for each room. I've barely used rooms before so I can't really tell you the best way to go about this, but like I said, we're talking conceptual. If you can manage your game so that you only ever have texture pages loaded into memory that have sprites actively in your scene, then you've hit your peak performance for this issue. You've not made the load on performance any less, but you've spread it out over various tasks - e.g. your graphics card only has to do its one thing, and your CPU gets the task of sorting through sprites by region, and probably also deactivating instances out of view.

    Hopefully that covered a few of the broad strokes you needed to connect the dots. Let me know if you have a question about anything I mentioned, but otherwise there is such a wealth of knowledge on this forum that I'm sure someone else will be better suited to your technical questions.
     
    Last edited: Sep 5, 2019
    DaMuffin likes this.
  3. spoonsinbunnies

    spoonsinbunnies Member

    Joined:
    Aug 24, 2018
    Posts:
    111
    not really the most technical answer, but if your already at programming the final boss and literally the last boss is where your experiencing memory issues, why not simply release in two parts? I mean it seems a bit janky in this day and age but if your alternative excludes potential players idk to me it seems better to just have two parts. Sorry I cant give you a technical answer your looking for, I don't know any way to load or more importantly unload a texture when its not needed.
     
  4. CloseRange

    CloseRange Member

    Joined:
    Jul 2, 2016
    Posts:
    760
    This may or may not help
     
  5. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Well, I'll be honest, I never actually had problems with the game running slowly, just one day I loaded a sprite into the editable .gmk file and it decided to throw me an error message instead of running properly. I don't know how much difference that makes, it's really just more of an inference from what I've read around. (I did already try exporting the executable and running that by itself, that didn't help, same result came of it. Well, it didn't give me the dialog box, just froze the loading bar at the beginning and Windows made the default error sound, but I'm guessing that means the same thing.)
    I mean, I'm hardly a super-experienced or even very technical-minded programmer myself (the game is programmed in a mix of drag & drop and GML coding and I've just been going by what I know, probably not always using the most efficient methods possible) and also don't even know where to start when it comes to loading and unloading textures like that, I only just found out what texture pages were while looking into this problem. All the sprites in my game are loaded internally in the editable right now, some of which I'm not even using in the game yet because I hadn't programmed those parts yet and aren't actually called up (again I get the feeling whether they're called up by objects doesn't really matter). The final boss sprites are an example of such, I hadn't even started programming that part of the game even if I am pretty close to it.

    Now I have thought about the potential of releasing the game in two parts as well, that could probably work if no better answers are brought up. Janky, yes, but I at least know how to do that and could probably work that into what I already have.
     
    Last edited: Sep 5, 2019
  6. RefresherTowel

    RefresherTowel Member

    Joined:
    Jul 13, 2016
    Posts:
    1,211
    The better answers brought up are indeed the texture solutions. The problem is very probably the amount of memory your texture pages are taking up. The way that GMS works is that when you add a new sprite to the game, GM picks a texture page (the default size is 2048x2048 I believe) with a valid amount of pixel space and places the sprite on that page. If none of the current pages have enough pixel space it creates a new page. When you run your game, GMS loads in whatever texture pages contain sprites that are going to be used for the current room (at least, that's what I think happens, but I haven't looked into this thoroughly). If you have a lot of large sprites or the sprites are randomly scattered across a lot of texture pages, it will have to load in a bunch of pages, each which take up a fairly large chunk of memory. So what probably happened is you added one extra sprite, there was no room for it on any of the texture pages so a new one was created and that new texture page bumped the memory load over what your computer can reliably handle. The solution is to organise your texture pages manually so that all the sprites for similar areas/functions are the same texture page/as few texture pages as possible and load and unload texture pages manually when you need them (not -exactly- when you need them, this is often done with a loading screen and is why you often have loading screens between rooms or stuff in older games, as they are loading in new textures/sounds/whatever that need to be shown in the new room and unloading that stuff from the previous room.
     
  7. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    1,923
    Another think to look into would be your animation methods, if streamlining would be applicable there. If your boss has a twenty-frame animation of it rolling its eyes when it gets hit, you don't save it as twenty 500x500 sprites. You save it as twenty 20x10 sprites covering the eyes region. This means custom variables for positioning and counting frames, but in the end you've saved nearly five million pixels on your texture pages. Or, since you could fit 16 of those frames on one, about 1.25 texture pages.
     
    Ido-f, DaMuffin and RefresherTowel like this.
  8. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Alright then, so how should I go about loading / unloading sprites or working with texture pages? I don't really know where to start in that department, never properly worked with anything like that before.
    My only previous attempt at that kind of thing was using the sprite_add_sprite() function to load sprites externally (and again after the problem showed up as mentioned above), though I'm told this is not the best method either since each line of this function uses its own texture page, though I could be wrong about that too.
    (and that streamlining mentioned in the last post wouldn't help as much in the case of the final boss since there's a lot of movement involved)
     
  9. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Are you running the entire game in one room? Or do you have multiple rooms going on at the same time? Are you destroying the sprites that you are adding in when they are no longer needed and then adding them back in when they are needed again? I don't know if gm8 prefetched all sprites, but Studio 1 only loads texture pages it needs, which you can flush out at as needed.

    as nightfrost said, the first thing that popped into my mind when I read how many sprites you have just for the boss alone is that you don't understand proper Sprite design. it is usually better to draw 10 small sprites in 50 locations than it is to draw 500 large sprites in one location. I'm not talking about skeletal animations either (I hate how they look, lol). Then again, if your boss is too complex, you might be better off using skeletal animation just for the boss. You should probably show somebody all the frames of one particular animation of your boss to see if they can figure out how to streamline your boss. Because that is a crap ton of sprites even by modern standards.

    Have you upgraded to studio 1 or studio 2 yet? Or are you still using gm8? because if you are still on GMA 8, then all these talks about extra pages won't do you any good.

    GM8 also had sprite_replace(), but I never tested it for memory leaks.
     
  10. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Well, thing is, the game I've been working is almost entirely made up of graphics I got from The Spriters Resource (like I said it was more a personal passion project that wouldn't get presented as anything more than a free fangame once it was done, I am not good at drawing), the final boss was taken from a fighting game where all the sprites are pretty big and just came in zips of loads of pngs that I had to line up and crop myself in Photoshop. Not to mention I hadn't even started on working on programming it or even loaded it in the editable at all yet when this happened, it's just been something that I knew was on the way and pretty clearly wasn't going to fit at this point. And yes, I'm still using GM8. The game uses multiple rooms, but certainly not at the same time, I don't even know how that would work.
    I could be wrong, but I'm relatively sure that GM8 does in fact prefetch all the sprites beforehand. The default way GM8 games load in my experience is that there's a big load bar at the very beginning where it gets all the assets loaded into the editable ready (not just sprites but also backgrounds, music, sound effects, everything) and then starts up the game, and at the part where the loading bar usually loads graphics is where the errors/freezing happen. I suppose what I should probably be doing is loading just the sprites I need for a given room and unloading them when I leave the room, I hadn't actually been doing that previously and this discussion is giving me the idea that this could be helping me a lot if I do it right. The final boss is ridiculously complex, yes, but maybe this method would free up enough memory to get it to work? It's been able to run fine with 9 different previously programmed bosses that while not as complex as the final ones are still pretty complex, as well as loading several different areas that have tons of other sprites involved, so who knows. It'd take a lot of work but it's gotta be at least worth a shot.
     
  11. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    3,556
    You can load assets during runtime in GM8 if you want to as well, using functions like sprite_add.

    I would've suggested that you ran the game using the profiler to see where it crashed (it also displays things like total memory usage by source), but that's a GMS feature.

    Also worth noting if you delete a lot of assets is that there was a bug where you would leak metadata, so you would benefit from exporting the game as an asset file and then importing it into an empty project every now and then. Not sure what version that applied to, though.
     
  12. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    I don't think exporting the game as an asset file is a feature in GM8 unfortunately, just exporting it as an executable. I think I'll try making the transition towards loading the sprites externally with sprite_add_sprite and just load them room-by-room & deleting them afterwards, guess we'll see where that takes me in time.
     
  13. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    No, you can export it. You need to goto File --> Export Resources... And select all the resources in your project. Give it some arbitrary name, it doesn't matter what you call it. Then close the program and start a new project. Inside the new project, goto File --> Import Resources... And select the file you created above. GM will then recompile your resources anew. Note: This will not change resource names, but it will change resource IDs. For 99% of the people that used GM8, this isn't a big deal. For the 1% that is me, it's a handy feature in GM8 (that's obsolete in GMS).
     
  14. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Oh, I see, that's what you meant by asset file. Got confused there, thanks. I'll keep that in mind too.
     
  15. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Whether or not it will even help you in this case, you'll just have to cross your fingers and see. lol

    GM8's resource handling was atrocious. If you created a resource, then deleted it, then created a new one, GM8 would just keep increasing the resource data structure. So if you created 3000 resources, it wouldn't matter if you only had 1 resource left by the end of the project, you'd still have a 3000-entry data structure. When you export all the resources and then import them into a clean project, GM8 streamlines the resources, ignoring their IDs as it unpacks them.

    GMS fixed this up. You still need to clear the project cache routinely, but the resource data structure doesn't get written until the game is compiled.
     
  16. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    3,556
    The drawback of GMS's new system is that resource IDs are re-assigned each time you compile, so resource IDs aren't guaranteed to have the same values for a given object for the lifetime of a project... this broke a bunch of my projects (since object IDs saved from a level editor would change as I created new objects, so the levels would load the wrong objects since the files had the old IDs... and a bunch of more atrocious examples where other bugs were caused from me using hardcoded numbers instead of the name constants) but ultimately is better than the alternative.
     
  17. curato

    curato Member

    Joined:
    Jun 30, 2016
    Posts:
    384
    You probably already though of this, but do you have redundant sprites that you can get rid of like having a right and left sprite where you could flip it with code or something like that?
     
  18. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    Not really, I've been doing a lot of flipping/rotating/scaling sprites with code to try not to use up as much space. I'd already been trying that kind of stuff to make sure the game didn't take up a lot of disk space because I knew what was coming at the end, but unfortunately as I've learned, disk space and memory are two different things.
    The game isn't exactly small either, the editable was like 70 MB by the time it stopped working (and a little less than 80 MB if I put in all the final boss sprites), but like I said I've worked with stuff that takes up a lot more disk space and still ran perfectly fine.
     
  19. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Disk space should be unimportant, the memory an uncompressed image takes up is most important. A 50kb image can take up 30mb of memory uncompressed quite easily.
     
  20. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    3,556
    Does GM8 still store backgrounds as BMP or did it go over to PNG entirely? I don't remember. If it's the former, this definitely could be a memory issue if you have a lot of big backgrounds.
     
  21. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    It didn't store anything as separate files as far as I've ever seen. The only thing that ever gets dumped is included files, which get dumped when the code tells them to get dumped (either via game settings or code). The entire project was inside the game file. I mean, they could have still compressed the image data, but the image data would still need to be decompressed at some point. If it decompresses at game start, that's a lot of graphics data getting dumped into memory all at once.

    But I don't really know. All my GM8 stuff is 8bit graphics and I just import GIFs using sprite_add() since that's the most flexible palette swapping method for GM8.

    ..... Speaking of which, is there palette swapping in this game? Do you have multiple copies of the same sprites with different colors between them?
    [​IMG]
    Like in this example. Is your game set up so that (in this case) Kyo has two (or more) sets of sprites where the only difference between the sets is the colors? If that's the case, then you should remove all duplicate sprite, convert all your sprites to GIFs, then use a combination of file_bin_write_byte() and sprite_replace() to load in only the necessary sprites as needed with the proper palettes. Any sprites that cannot be palette swapped can stay in the executable, but anything that will need a palette swap should be a GIF.
     
  22. ddddd54543

    ddddd54543 Member

    Joined:
    Sep 5, 2019
    Posts:
    8
    There's no big backgrounds in this game, it's mainly built out of a couple of tilesets. I don't think they have nearly as big an impact on the game's memory problems as the sprites do, there's just a lot more of those.
    There are a couple of palette swaps for some of the special effect sprites though (not that many, but some).
    I'm looking into storing more of the game's sprites externally rather than internally and only loading the necessary ones for one given area in the game, I'm hoping that will help solve my problems.
     
  23. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Just the effects? None of the enemy, boss, or player sprites? How many frames of duplicate data are we talking, here? lol

    For external data in GM8, as I said above, I'm a personal fan of using GIFs. They take up more disk space than PNGs, but far less than BMPs. They are (or should be) quicker to load when using sprite_replace and can have their palettes changed on the fly very easily. That's another thing, too, if you store graphics externally. Use sprite_replace() and background_replace(). Do NOT use sprite_add() or background_add(). I mean, if you're using Studio, then you need to use sprite_add() and background_add() to create "dynamic" assets, since the Studio is coded so that any resource you create in the IDE cannot be modified during runtime. But in GM8, there's no need. Just create a couple placeholder sprites and backgrounds in the IDE, then in your project use sprite_replace() or background_replace() to swap out those placeholders with whatever assets you need at the time.

    This does require a little forethought, though. How many placeholders do you actually need? In my Castlevania project, I had 46 sprite placeholders. The max number of sprited instances would be 23, and then I doubled that for any instances that would have a flashing palette swap, which I would want to speed up by performing the actual palette swap at the start of the room and then just toggle between sprites when needed. If I wanted to, I could probably narrow this down quite a bit. With my current setup, multiple instances of the same object (e.g., obj_zombie) would each be using their own sprite asset, but each of those assets would essentially be the same sprite. That's kinda wasteful -- why have 3 duplicate zombie sprites when I could use just 1? Well at the time, I had different sprites for different actions (e.g., spr_zombie_walk and spr_zombie_attack), so I needed duplicates for each instance so that if one zombie was attacking but not the other, I could change the sprite of just the attacking one. Now, if I had combined all the sprites into one sprite and used a custom animation script, then I could narrow down the number of assets to be the number of possible enemies and projectiles in the room at any time. So even if I ahd 4 zombies running around, they could all use the same zombie sprite asset. In the case of my game, I could then reduce the number of assets to something like 24 or 26. And if I consider that candles in Castlevania don't flash, I could take off 1 or 2 more assets. ...Point is, it takes a lot of planning. Or if I went really crazy and combined ALL sprites into one sheet and just used texture definitions, then I'd only need 4 assets. lol
     
  24. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    3,556
    Most SNES and NES era games used image formats without palette information, so you would specify the palette when drawing the sprite, not in the image data, making it essentially a free action. This hasn't existed in most PC engines, since formats like BMP and PNG are the standard these days (and in the early days a single palette was supported, which needed to be used by ALL graphics and couldn't be changed by apps). They also had horizontal and vertical flipping come free, so basically supporting image_xscale and image_yscale.
     
  25. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Are you talkin to me or to him? Because I know what palace whopping is and when I got used. The graphics for this project came from a Sprite ripping website, which means there was a good chance he is using the same Sprite with different pallets applied, thus doubling or tripling the number of sprites he even used. so if he redid his sprites to be able to apply a palette swap or even a hue shift, then that would free up a lot of the memory.

    (If you are a she, sorry but I still say he because I don't call people plural pronouns unless they are fat l
     
  26. SupernaturalCow

    SupernaturalCow Member

    Joined:
    Sep 11, 2016
    Posts:
    142
    Excuse me, but I would like to know about palace whopping :D
     
  27. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    6,788
    Stupid phone... grrr
     
    SupernaturalCow likes this.
  28. Yal

    Yal GMC Memer GMC Elder

    Joined:
    Jun 20, 2016
    Posts:
    3,556
    I can't tell if it's just your autocorrect that has issues or if you're also browsing the forum while drunk :p In either case, body-shaming people isn't a particularly nice thing to do, so please don't.

    (also, side note: If you don't wanna shift between two different pronouns all the time, "they" is the gender-neutral option, people are supposed to accept being referred to with it when there's uncertainty about which pronoun is the most suitable... if someone doesn't accept it and tries to start a fight despite you using common courtesy, you can most likely safely ignore them. Actually, that goes for all fights on the internet, it tends to get less messy in the long run if you try to fight back. Don't stoop to their level)
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice