Today I had the revelation that the shadows cannot work with my new batch.
That's because the batch uses the shaders of the models, while tha shadow must use the same shader for everything...this requires to use the old batch, there's no way around it. I did a few changes to make it a little simpler and faster, because it will never change, so it doesn't need to be versatile.
Lighting however will work pretty well with the new batch I think, because the models will be the bounding boxes of the lights, and each one of them will have a user defined shader.
Right now it is not even using a batch, it draws each model with a single draw call, so everything will require quite a bit of refactoring, but it should work in the end.
giovedì 31 dicembre 2015
mercoledì 30 dicembre 2015
Day 110: Upgrading the last scene
Today I finished once and for all to upgrade all the game-side code.
I updated the last scene, the most complex one, and everything is working fine with the new geometry shader system.
I found out that every resource must be defined as a New pointer in the header file of the scene. Most of them were now declared in the start function, but out of scope they become dangling with the new way of doing things, and garbage is sent to the shader. For this reason this simple declaration rule must be followed carefully.
In the next days I'll have to decide how to handle shadows and ligthing shaders. The idea is to use the same batch for all three, however the shadow shader will not be accessible from the game, because I don't think that it needs to be modified... it will eventually expose a few uniforms, like shadow thinckness or shadow fading, and the rest will be shared among all shadows.
EDIT:
In the evening I had a little more time and I did a few things I was planning to do for a while.
First I made a system to recognize which parts of shaders are loaded (for example if the geometry shader is used or not), which I was manually doing right now.
Then I created a shader folder in the game and moved the geometry shaders in there, because now it's a user job. However the folder in the engine remains, to contain a list of standard shaders, which I'll maybe do in the future.
In the picture I tweaked a little with the second geometry shader. The particles change color randomly each frame (from purple to blue), and at the same time the lerp over time from black to the random color. This would not be possible with the standar shader.
In the next days I'll have to decide how to handle shadows and ligthing shaders. The idea is to use the same batch for all three, however the shadow shader will not be accessible from the game, because I don't think that it needs to be modified... it will eventually expose a few uniforms, like shadow thinckness or shadow fading, and the rest will be shared among all shadows.
EDIT:
In the evening I had a little more time and I did a few things I was planning to do for a while.
First I made a system to recognize which parts of shaders are loaded (for example if the geometry shader is used or not), which I was manually doing right now.
Then I created a shader folder in the game and moved the geometry shaders in there, because now it's a user job. However the folder in the engine remains, to contain a list of standard shaders, which I'll maybe do in the future.
In the picture I tweaked a little with the second geometry shader. The particles change color randomly each frame (from purple to blue), and at the same time the lerp over time from black to the random color. This would not be possible with the standar shader.
martedì 29 dicembre 2015
Day 109: Fixes
Today I wanted to do for lights shaders the same I did for geometry shaders.
But since the engine right now is actually working correctly (i expected that whis would happen only at the end of the whole process), I decided to take a breath and fix a few things.
First I decided to convert all the scenes to the new system, because only the one I was using for testing was working. By doing that I noticed a problem with the normal maps, which i didn't notice in the firework scene because I was using flat materials. (flat = not interacting with light).
But since the engine right now is actually working correctly (i expected that whis would happen only at the end of the whole process), I decided to take a breath and fix a few things.
First I decided to convert all the scenes to the new system, because only the one I was using for testing was working. By doing that I noticed a problem with the normal maps, which i didn't notice in the firework scene because I was using flat materials. (flat = not interacting with light).
It was a little bug inside the batch, and as you can see in the picture, now the light is correctly reflected if I use non-flat materials.
Then I noticed a crash when quitting the game...apparently there is something wrong in the destruction of singleton classes...I managed to fix it, but I'm not really sure yet why it wasn't working before. Probably in the future I'll remove all the singletons though, I really don't like how they integrate with the engine.
lunedì 28 dicembre 2015
Day 108: Vertex attributes abstraction
Today I jumped right into the shadow/lights shaders, in order to make them work with the same batch as the other ones. However I immediatly noticed the first problem: they send less vertex attributes...just the position, instead of position,uvs, normals and tangent.
In the ryno engine the shader has ~4 types of data: vertices attributes, instance attributes, temporary uniforms and global uniforms. The latter 3 are now automatically handled, however I didn't do it for the vertices, because they are consistent when rendering geometry, and I manually specified them.
However in my goal of an olistic system that handles all the shaders with a single batch class, I needed to take care of that as well. I decided to just use a big macro-vertex struct, and then send only the attributes found in the shader. If an attribute is called "in_Position", "in_Uv", "in_Tangent" or "in_Normal", the batch sends data to the shader by sampling the vertex struct.
This works well with the geometry shader, however i'll probably need to tweaks a few things to make it work with meshes that use a different vertex format, but it shouldn't take long.
In the ryno engine the shader has ~4 types of data: vertices attributes, instance attributes, temporary uniforms and global uniforms. The latter 3 are now automatically handled, however I didn't do it for the vertices, because they are consistent when rendering geometry, and I manually specified them.
However in my goal of an olistic system that handles all the shaders with a single batch class, I needed to take care of that as well. I decided to just use a big macro-vertex struct, and then send only the attributes found in the shader. If an attribute is called "in_Position", "in_Uv", "in_Tangent" or "in_Normal", the batch sends data to the shader by sampling the vertex struct.
This works well with the geometry shader, however i'll probably need to tweaks a few things to make it work with meshes that use a different vertex format, but it shouldn't take long.
domenica 27 dicembre 2015
Day 107: Multiple shaders!
Finally I managed to complete the automatization of the batch for geometry shaders. It shouldn't take that much to completely remove the other batches, and make a single, general purpose batch to handle multiple shaders of multiple types.
Today I started by adding the concept of a "global uniform", which is a uniform tht has the same value for all the instances. For example, in a single frame, every model has the same uniform for the camera matrix, so the info can be stored in the shader instead of the material. The global uniforms must be declared as "g_Name", so the system can recognized them, and must be set through the shader instead of the material.
Then I made a switch to send uniforms to the shader according to the type. This was the last step to have an "abstract" batch. So I created a second geometry shader, and the batch take care of them both. The DeferredRenderer class doesn't have a geometry shader, but simply call the render function on the batch. And it also send the same uniforms to all the shaders in that batch (namely the camera matrices).
Ideally the lighting shader will do the same, but send different uniforms.
The system will be hable to handle whatever shader, the only requirement is that the uniforms used by the deferred renderer class are present.
In the picture, the bottom right emitter has a different shader for the particles material. It takes another uniform (the time), and the resulting color is white multiplied by the sin of the time, to give a "pulsating" effect. This could be emulated with the particle material itself, but the difference behind it is substantial.
This also works with different attributes (and in different numbers)
Today I started by adding the concept of a "global uniform", which is a uniform tht has the same value for all the instances. For example, in a single frame, every model has the same uniform for the camera matrix, so the info can be stored in the shader instead of the material. The global uniforms must be declared as "g_Name", so the system can recognized them, and must be set through the shader instead of the material.
Then I made a switch to send uniforms to the shader according to the type. This was the last step to have an "abstract" batch. So I created a second geometry shader, and the batch take care of them both. The DeferredRenderer class doesn't have a geometry shader, but simply call the render function on the batch. And it also send the same uniforms to all the shaders in that batch (namely the camera matrices).
Ideally the lighting shader will do the same, but send different uniforms.
The system will be hable to handle whatever shader, the only requirement is that the uniforms used by the deferred renderer class are present.
In the picture, the bottom right emitter has a different shader for the particles material. It takes another uniform (the time), and the resulting color is white multiplied by the sin of the time, to give a "pulsating" effect. This could be emulated with the particle material itself, but the difference behind it is substantial.
This also works with different attributes (and in different numbers)
giovedì 24 dicembre 2015
Day 106: Automatic batching, another step
Today I continued to automatize the batching process. I'm almost at the point where the batch will become identical for whaterver shader I will decide to use, and that is the goal, so that I will be able to build custom shader and just use them with the same batch.
I did really a lot of stuff today, I'm actually having troubles remembering what I did, and if i did it today. Most of the time was spent fixing bugs and scratching my head, because it's a very delicate work, and each change I make is potentially critical.
Now the system is finally able to automatically divide the models into batches. Before I had a sorting algorithm, where the ">" function was manually defined to suit the default shader. Now the ">" function used by the sorting actually compares the uniforms from the two models, and so it is valid for whathever shader.
The performance is not bad, they fps are slightly lower than before, but there is a reason for that: among the uniforms are also the View matrix and the ViewProjection matrix, so they are compared as well. However they are 100% identical among all the materials. When (and if) everything will be working, I'll introduce a way to declare "global uniforms", which are set to the shader itself, instead of the model. This will reduce the duplication of data and the time used by the sorting algorithm.
In the picture you can se 4 emitters, each one of them spawns particle with a different mesh and texture/normal map. The drawcalls are 4, which is exactly what should be expected.
I did really a lot of stuff today, I'm actually having troubles remembering what I did, and if i did it today. Most of the time was spent fixing bugs and scratching my head, because it's a very delicate work, and each change I make is potentially critical.
Now the system is finally able to automatically divide the models into batches. Before I had a sorting algorithm, where the ">" function was manually defined to suit the default shader. Now the ">" function used by the sorting actually compares the uniforms from the two models, and so it is valid for whathever shader.
The performance is not bad, they fps are slightly lower than before, but there is a reason for that: among the uniforms are also the View matrix and the ViewProjection matrix, so they are compared as well. However they are 100% identical among all the materials. When (and if) everything will be working, I'll introduce a way to declare "global uniforms", which are set to the shader itself, instead of the model. This will reduce the duplication of data and the time used by the sorting algorithm.
In the picture you can se 4 emitters, each one of them spawns particle with a different mesh and texture/normal map. The drawcalls are 4, which is exactly what should be expected.
mercoledì 23 dicembre 2015
Day 105: Huge changes
Today I continued my work on the automatization of shaders and materials.
In the end I managed to make both attributes and uniforms completely "dynamic".
Attributes:
Each material has a chunk of memory. When calling:
material->add_attr(name,value)
the value is inserted at the right position in the chunk. The position is retrieved from the shader class, which holds all the info about the attributes after the query to the gpu shader.
Uniforms:
Since uniforms don't require to be store in a contiguous chunk of memory, each material holds a map of uniforms built by getting infos from the shader class about the uniforms.
In the batch class only those materials with the same uniforms are drawn togheter.
The system still has lots of problems, and tomorrow i'll try and fix most of them. For example I need to write a function that sort the materials based on their uniforms (to be used in the batch class), and the whole system now only works with a single, mock shader I'm using to test everything. However for the first time, I'm actually seeing that this could be possible to achieve
In the end I managed to make both attributes and uniforms completely "dynamic".
Attributes:
Each material has a chunk of memory. When calling:
material->add_attr(name,value)
the value is inserted at the right position in the chunk. The position is retrieved from the shader class, which holds all the info about the attributes after the query to the gpu shader.
Uniforms:
Since uniforms don't require to be store in a contiguous chunk of memory, each material holds a map of uniforms built by getting infos from the shader class about the uniforms.
In the batch class only those materials with the same uniforms are drawn togheter.
The system still has lots of problems, and tomorrow i'll try and fix most of them. For example I need to write a function that sort the materials based on their uniforms (to be used in the batch class), and the whole system now only works with a single, mock shader I'm using to test everything. However for the first time, I'm actually seeing that this could be possible to achieve
martedì 22 dicembre 2015
Day 104: Unpacking a color
Well, today i decided that i really dont like the idea of passing data through the attributes names, so i decided to chose another solution...basically i pack data on the cpu, send them as floats to the shaders, and unpack them there.
I tried it on the color, which is the only attributes using bytes, and it works well, a single int32 is passed for all four channels.
I lost a huge amount of time due to a stupid and counterintuitive opengl function, but now luckily i got it working fine
I tried it on the color, which is the only attributes using bytes, and it works well, a single int32 is passed for all four channels.
I lost a huge amount of time due to a stupid and counterintuitive opengl function, but now luckily i got it working fine
lunedì 21 dicembre 2015
Day 103: Get data from shaders
The engine so far supports a single shader, and does not have a material system.
Adding the support for multiple materials/shaders at this point is a titanic task, because:
1) The engine is developed around the current system, and everything must be changed
1) The engine is developed around the current system, and everything must be changed
2) The engine uses instancing, which add a level of complexity (usually there are attributes and uniforms, with instancing we have attributes, instance attributes with the divisor and uniform).
3) The shaders are more complex "thanks" o the deferred rendering
3) I don't want to force the developer to manually add the attributes/uniforms in c++, so I want to automatize the system to automatically recognize attributes/uniforms.
This is really an huge amount of work, and I'm not even sure that I will be able to get everything done... I have a lot of confusion in mind because, while I know how the single features in the engine work, the big picture is quite blurry, and its pretty painful to think about all the consequences that this big rebuilding will have.
Today I wrote a function to get info about the attributes in the current shader. GLSL stores everything as a 4 byte value (not really true, just to get the idea), there are no shorts or bytes.
So if I query for the type of a color, I get that each component is a GL_FLOAT...which is not ideal because I used to pass just 4 bytes, which where then converted in the gpu. So there is litterally no clean way to tell if something should be a vector of floats or it could be stored in bytes.
Since I really want to save that extra bandwidth, I decided to give a convention to the attributes names in the shader:
This is really an huge amount of work, and I'm not even sure that I will be able to get everything done... I have a lot of confusion in mind because, while I know how the single features in the engine work, the big picture is quite blurry, and its pretty painful to think about all the consequences that this big rebuilding will have.
Today I wrote a function to get info about the attributes in the current shader. GLSL stores everything as a 4 byte value (not really true, just to get the idea), there are no shorts or bytes.
So if I query for the type of a color, I get that each component is a GL_FLOAT...which is not ideal because I used to pass just 4 bytes, which where then converted in the gpu. So there is litterally no clean way to tell if something should be a vector of floats or it could be stored in bytes.
Since I really want to save that extra bandwidth, I decided to give a convention to the attributes names in the shader:
- the first letter of the name is the type (f, b, s, i)
- the second letter tells if the attribute is a number, a vector or a matrix (n, v, m)
- the second letter tells if the attribute is a number, a vector or a matrix (n, v, m)
- the third letter is a number representing the size of the vector/matrix
This is not a clean solution, but it's the only way to pass useful informations from the gpu to the cpu that I know of. With this system I can send bytes instead of floats when, for example, needing a color.
venerdì 18 dicembre 2015
Day 102: Fixed copy constructors
Ok, I managed to fix the bug I had yesterday...it was quite stupid but it took me an awfully long time because I was not searching for it where I should have. However this allowed me to find a few problems in several copy constructors, that could have brought to other problems in the future.
Now the copy constructor of the emitter finally works, and the problem with this was its complexity: each emitter is owner of a vector of particles, which cannot be copied, but should be reinstantiated, so it needed a few tweaks to work correctly, both if used before the emitter initialization or after.
In the picture I created a gameobject with an emitter, and then I just duplicated the game object 3 times (which also copies the emitter component in it). Then I just replaced the update lambda that manages the updates of the particles to have different colors, while in the bottom left one I also modified the creation lambda to spawn different colors from the beginning.
Now the copy constructor of the emitter finally works, and the problem with this was its complexity: each emitter is owner of a vector of particles, which cannot be copied, but should be reinstantiated, so it needed a few tweaks to work correctly, both if used before the emitter initialization or after.
In the picture I created a gameobject with an emitter, and then I just duplicated the game object 3 times (which also copies the emitter component in it). Then I just replaced the update lambda that manages the updates of the particles to have different colors, while in the bottom left one I also modified the creation lambda to spawn different colors from the beginning.
giovedì 17 dicembre 2015
Day 102: Annoying bug
Today I improved a little the emitter class, because its copy constructor was buggy. Now it almost works, but I've this strange annoying bug... Basically the particles are changed in color, but the change is somehow delayed...
It's difficult to explain, but it is really, really strange, one of the most strange bugs I've ever experienced, and I wasn't able to fix it today, I hope to do it tomorrow
It's difficult to explain, but it is really, really strange, one of the most strange bugs I've ever experienced, and I wasn't able to fix it today, I hope to do it tomorrow
mercoledì 16 dicembre 2015
Day 101: Scenes manager improvements
Today I did a few things to improve the user-friendlyness of the scenes manager.
First I created a file in the game folder where you can associate a name to each class:
SceneManager::add_scene("myscene");
If you want to use a scene in the editor, it must be declared there, more or less like in Unity.
This allows to change scene by name, which is way better than what I was doing until now.
In the meanwhile I also fixed a bad bug that made the game crash if no object is rendered, which never occurred until now.
Finally I added e \setscene command to the internal console (picture), so I can easily switch scene instead of cycling to all of them.
First I created a file in the game folder where you can associate a name to each class:
SceneManager::add_scene
If you want to use a scene in the editor, it must be declared there, more or less like in Unity.
This allows to change scene by name, which is way better than what I was doing until now.
In the meanwhile I also fixed a bad bug that made the game crash if no object is rendered, which never occurred until now.
Finally I added e \setscene command to the internal console (picture), so I can easily switch scene instead of cycling to all of them.
martedì 15 dicembre 2015
Day 100: 100!
Well, I managed to reach 100 days!
Today I just did a little of refactoring, and created 2 scenes to test the scene management system (and the memory allocators).
One bad thing I noticed is that the particle engine has a bad deallocation time with really high numbers of particles (picture), because each one of them must also deallocate its components, since a particle is a gameobject. The same problem is avoided in creation, because the components are created runtime when the particle is used, so the load time is diluted between multiple frames.
Tomorrow I'd like to implement a system that associates a string name to each scene, so that they can be created by name, maybe also through the shell command line. Right now they are manually created through the game, which is not really ideal.
I didn't expect that, and I'm pretty satisfied with it... the engine has quite a lot of features, and I really learned a ton of stuff, which is the most important thing.
Today I just did a little of refactoring, and created 2 scenes to test the scene management system (and the memory allocators).
One bad thing I noticed is that the particle engine has a bad deallocation time with really high numbers of particles (picture), because each one of them must also deallocate its components, since a particle is a gameobject. The same problem is avoided in creation, because the components are created runtime when the particle is used, so the load time is diluted between multiple frames.
Tomorrow I'd like to implement a system that associates a string name to each scene, so that they can be created by name, maybe also through the shell command line. Right now they are manually created through the game, which is not really ideal.
lunedì 14 dicembre 2015
Day 99: Stack and Pool allocator
Today I finished all the allocators I had in mind.
The stack allocator prepares a huge chunck of memory, and each object is allocated on top of it. Deallocating an object does nothing, the only way to remove an object from the top is calling the pop method. The actual deallocation is called when the stack pointer is set back to zero.
It's important to notice that the destructor of an object is still called, so if you really want to destroy an object, a hole in the memory will be left, but this is an obvious downside in stack allocators.
The Pool allocator is not a "global" allocator like the stack allocator and the mallocator. You can create multiple pools, and each one of them can be allocated using other allocators.
To test it out I allocated on the Stack Allocator a pool of Particle3D used by the particle emitter.
To wrap things up I gave a complete set of debug messages to each allocator, and they can be activated or deactivated with a preprocessor directive.
The stack allocator prepares a huge chunck of memory, and each object is allocated on top of it. Deallocating an object does nothing, the only way to remove an object from the top is calling the pop method. The actual deallocation is called when the stack pointer is set back to zero.
It's important to notice that the destructor of an object is still called, so if you really want to destroy an object, a hole in the memory will be left, but this is an obvious downside in stack allocators.
The Pool allocator is not a "global" allocator like the stack allocator and the mallocator. You can create multiple pools, and each one of them can be allocated using other allocators.
To test it out I allocated on the Stack Allocator a pool of Particle3D used by the particle emitter.
To wrap things up I gave a complete set of debug messages to each allocator, and they can be activated or deactivated with a preprocessor directive.
domenica 13 dicembre 2015
Day 98: Mallocator
Today I did quite a lot of stuff.
I created a Mallocator, which is a custom allocator which works exacly like malloc, but hides the implementation details. I refactored the whole engine to instantiate objects with it, and this required a lot of tweaks to make the interface looks nice. Before I had:
go = new GameObject();
now i have
go.create(mallocator);
Now everything is working, copyconstructors and destructors included.
While doing this I noticed a few things that were not good in the engine, like a bad management of the emitter components inside the gameobject, and the fact that the game has one camera for all the scenes. I modified them both, so noew the emitter behaves like any other component, and each scene has its own camera.
Finally I changed the TextureManager to keep track of which textures are lifetime objects and which are not, and it's reset method is now working correctly.
I didn't profile it yet, but now I'm quite confident that everything works like it should, and even if I'll find leaks they should be easy to fix.
The system now allows me to create other allocators pretty easily, like a stack allocator, which is what i'll do next.
The only downside is that it uses lots of template classes and functions, which leads to very bad error messages.
I created a Mallocator, which is a custom allocator which works exacly like malloc, but hides the implementation details. I refactored the whole engine to instantiate objects with it, and this required a lot of tweaks to make the interface looks nice. Before I had:
go = new GameObject();
now i have
go.create(mallocator);
Now everything is working, copyconstructors and destructors included.
While doing this I noticed a few things that were not good in the engine, like a bad management of the emitter components inside the gameobject, and the fact that the game has one camera for all the scenes. I modified them both, so noew the emitter behaves like any other component, and each scene has its own camera.
Finally I changed the TextureManager to keep track of which textures are lifetime objects and which are not, and it's reset method is now working correctly.
I didn't profile it yet, but now I'm quite confident that everything works like it should, and even if I'll find leaks they should be easy to fix.
The system now allows me to create other allocators pretty easily, like a stack allocator, which is what i'll do next.
The only downside is that it uses lots of template classes and functions, which leads to very bad error messages.
sabato 12 dicembre 2015
Day 97: GUIObject and scenes improvements
Today I continued to improve the scene management system.
To really test it under the worst conditions I decided to convert the complex scene I used in the last weeks/months to the new pointer system. Thankfully I tought of looking a little into operator overloading, and it turns out that overriding the "=" operator it is possible to assign pointers to smart pointers, making the interface to them identical to normal pointers.
So the conversion of the scene went quite smoothly for the most part, however:
- The emitters were a little problematic during destruction, because the particles didn't go away,but thanks to the smart pointers i fixed that
- The Text and Sprite classes were not going away, because they are not component of a GameObject. So I created a GUIObject, which is an aggregator of components exaclty like the GameObject, but for the GUI. It is not perfect and badly integrated, and should be refactored if the GUI will be expanded, but for now it does its job.
So, now everything is working, but I've not checked yet if there is some leaking of memory, or if the destructors of the various managers are doing exaclty their job. Tomorrow I'll check that with some memory profiler, and the next step is the introduction of custom allocators.
In the picture on the left the "big" scene, on the right the "little" scene created after the "big" one, and with no traces of objects from it.
To really test it under the worst conditions I decided to convert the complex scene I used in the last weeks/months to the new pointer system. Thankfully I tought of looking a little into operator overloading, and it turns out that overriding the "=" operator it is possible to assign pointers to smart pointers, making the interface to them identical to normal pointers.
So the conversion of the scene went quite smoothly for the most part, however:
- The emitters were a little problematic during destruction, because the particles didn't go away,but thanks to the smart pointers i fixed that
- The Text and Sprite classes were not going away, because they are not component of a GameObject. So I created a GUIObject, which is an aggregator of components exaclty like the GameObject, but for the GUI. It is not perfect and badly integrated, and should be refactored if the GUI will be expanded, but for now it does its job.
So, now everything is working, but I've not checked yet if there is some leaking of memory, or if the destructors of the various managers are doing exaclty their job. Tomorrow I'll check that with some memory profiler, and the next step is the introduction of custom allocators.
In the picture on the left the "big" scene, on the right the "little" scene created after the "big" one, and with no traces of objects from it.
venerdì 11 dicembre 2015
Day 96: Scenes 2
Today I managed to make everything work, and it was quite a painful job, but I learned tons of stuff on how memory works in c++, when destructors are called, how are called and so on.
The solution I adopted is the use of a typed custom pointer called New. Each object is created through this pointer. Each gameobject is therefore created as a New ptr, and so each of his components. However references to these objects are passed around the engine by their true pointer.
Now, when a scene is switched, the old one is deleted. Each New ptr calls its destructor, and the job is done.
To thest this out I removed most of the stuff from the scene, and I just left there the skybox, a cone mesh and a directional light. In scene 2 everything is identical, except that the second cube is thinner and a little on the right. As you can see from the picture the switching works perfectly: the old mesh is no more visible (that you can't see, sorry)and the light has the same intensity (which means that there aren't two of them).
The last thing that needs to be done is to reset correclty all the managers in the game: audio, textures, meshes and so on, being careful to not reset lifetime resources. And now that I have a custom pointer I could probably add support for others allocators as well.
The solution I adopted is the use of a typed custom pointer called New. Each object is created through this pointer. Each gameobject is therefore created as a New ptr, and so each of his components. However references to these objects are passed around the engine by their true pointer.
Now, when a scene is switched, the old one is deleted. Each New ptr calls its destructor, and the job is done.
To thest this out I removed most of the stuff from the scene, and I just left there the skybox, a cone mesh and a directional light. In scene 2 everything is identical, except that the second cube is thinner and a little on the right. As you can see from the picture the switching works perfectly: the old mesh is no more visible (that you can't see, sorry)and the light has the same intensity (which means that there aren't two of them).
The last thing that needs to be done is to reset correclty all the managers in the game: audio, textures, meshes and so on, being careful to not reset lifetime resources. And now that I have a custom pointer I could probably add support for others allocators as well.
giovedì 10 dicembre 2015
Day 95: Scenes
In these days I tried to figure out how to correctly handle memory, but in the end I found out that I just had an enormous confusion about that because I never really studied in depth how cpp handles that... One of the things that I learned is that I shouldn't try to create and destruct an object with an allocator, I should just allocate memory, and the destruction should be done with custom pointers: instead of instantiating new objects, I should create special pointers with a destructor that holds the pointers to the data.
Now that this is clear, I understood that I can just create my scene system WITHOUT the stack allocator, and I don't even need to keep track of the objects, they will eventually be detroyed when the special pointer goes out of scope.
I will implement the special allocators, but only once I get this working with mallocs and frees.
Today I wrote the entire scene management cycle, and the ways that the game uses to change scene.
To test it out I created two scenes, identical except for a cone which is one of them. I switch between them by calling a function defined in the main game class. The switching is done correctly, and the start, update and handle_inputs are correctly called.
As you can see from the picture, since I've not created the special pointers yet, all the previously created geometry are still visible in the scene. I'll hopefully fix that tomorrow.
Now that this is clear, I understood that I can just create my scene system WITHOUT the stack allocator, and I don't even need to keep track of the objects, they will eventually be detroyed when the special pointer goes out of scope.
I will implement the special allocators, but only once I get this working with mallocs and frees.
Today I wrote the entire scene management cycle, and the ways that the game uses to change scene.
To test it out I created two scenes, identical except for a cone which is one of them. I switch between them by calling a function defined in the main game class. The switching is done correctly, and the start, update and handle_inputs are correctly called.
As you can see from the picture, since I've not created the special pointers yet, all the previously created geometry are still visible in the scene. I'll hopefully fix that tomorrow.
martedì 8 dicembre 2015
Day 94: Reference allocator and new constructors
Today I implemented the reference allocator for resources that are going to be freed sooner or later.
I also created an Allocator class with an alloc method that takes one enum parameter. Depending on this parameter the resource is allocated with one of the two different allocators I've implemented so far.
The problem with this is that it's really ugly to call somethin like:
Obj* new_obj = nullptr;
Allocator::alloc(&new_obj, STACK_TEMP);
each time an object is created. So I overloaded the "new" constructor of the instantiable classes to allocate memory at a custom address obtained with the allocators instead of a malloc. The code looks like this:
Obj* new_obj = new (STACK_TEMP) Obj();
which is exacly like a normal constructor, but specifies where to get the memory for the allocation.
I'm not happy with copy constructors though, because they often call copy constructors on each pointer inside the class, and there is no way to tell where to allocate memory for these. Right now I'm passing the enum as a parameter, so you can chose where to store all the dependent pointers, but this is both ugly and doesn't allow you to differentiate between different pointers:
Obj* new_obj = new (STACK_TEMP) Obj(old_obj, STACK_TEMP);
I also created an Allocator class with an alloc method that takes one enum parameter. Depending on this parameter the resource is allocated with one of the two different allocators I've implemented so far.
The problem with this is that it's really ugly to call somethin like:
Obj* new_obj = nullptr;
Allocator::alloc(&new_obj, STACK_TEMP);
each time an object is created. So I overloaded the "new" constructor of the instantiable classes to allocate memory at a custom address obtained with the allocators instead of a malloc. The code looks like this:
Obj* new_obj = new (STACK_TEMP) Obj();
which is exacly like a normal constructor, but specifies where to get the memory for the allocation.
I'm not happy with copy constructors though, because they often call copy constructors on each pointer inside the class, and there is no way to tell where to allocate memory for these. Right now I'm passing the enum as a parameter, so you can chose where to store all the dependent pointers, but this is both ugly and doesn't allow you to differentiate between different pointers:
Obj* new_obj = new (STACK_TEMP) Obj(old_obj, STACK_TEMP);
lunedì 7 dicembre 2015
Day 93: Double-stack allocator
Today I decided that I really don't like how I was going to handle memory yesterday. I left a few destructors, for example in the AudioManager or TextureManager classes, because they rely on libraries that allocate memory outside my control.
But for everything else I decided to use a double-stack allocator, instead of keeping a reference for each instanitated object. The idea is that a single chunk of memory is preallocated, and each new object is created on top of one of the two extremities of the chunk.
The resources used by the engine are pushed from the bottom, the resource of a scene from the top. When changing scene it is sufficient to reset the stack pointer to the top, while leaving the engine stack pointer where it is...
Pro:
- No mallocs, just one allocation
- Very easy to erase all the allocated objects without keeping references
- Easy to implement
- Neat division between temporary and persistent resources
Cons:
- It is a stack, so it is not possible to remove an allocated object if it is not on the top
Overall I think that the pros are quite interesting, because most resources are kept until the end of a scene. But I will also add a PoolAllocator and GeneralPurposeAllocator: the first will handle lots of identical objects, the second one will simply malloc those objects that really need to be deallocated sooner or later, and will keep pointers to those. It will be up to the developer which one to use in each case, but I'll provide a simple interface.
Today I implemented the Double-Stack allocator.
But for everything else I decided to use a double-stack allocator, instead of keeping a reference for each instanitated object. The idea is that a single chunk of memory is preallocated, and each new object is created on top of one of the two extremities of the chunk.
The resources used by the engine are pushed from the bottom, the resource of a scene from the top. When changing scene it is sufficient to reset the stack pointer to the top, while leaving the engine stack pointer where it is...
Pro:
- No mallocs, just one allocation
- Very easy to erase all the allocated objects without keeping references
- Easy to implement
- Neat division between temporary and persistent resources
Cons:
- It is a stack, so it is not possible to remove an allocated object if it is not on the top
Overall I think that the pros are quite interesting, because most resources are kept until the end of a scene. But I will also add a PoolAllocator and GeneralPurposeAllocator: the first will handle lots of identical objects, the second one will simply malloc those objects that really need to be deallocated sooner or later, and will keep pointers to those. It will be up to the developer which one to use in each case, but I'll provide a simple interface.
Today I implemented the Double-Stack allocator.
domenica 6 dicembre 2015
Day 92: Destructors
Today I decided to start working on a scene management system.
This requires me to handle the memory correcly when I'm switching scenes, and the amount of things I need to change in order to achieve this is pretty huge...
Today I started by modifying a few classes, for example the audio manager, the profiler and the texture manager. Each one of them now has a reset method that removes everything unnecessary, including gpu memory in case of the texture manager.
The first big problem was the MeshManager, because some of the meshes are loaded by the engine, and should not be freed. So I created a little hacky system to understand if a mesh is for the engine or not without storing the information in the mesh itself.
Now I need to proceed with the most difficult part, which is deleting everything created from within the game. I need to keep track of models, emitters, lights and so on, basically everything I created until now... Deleting only the GameObjects is not an option, even if the components are recursively deleted, because some components could not be attached to anything.
So I guess that for each instantiable class I ever created I will add a static field which holds a list of pointers to the instances, and then I'll just call a Free on each element.
Calling free on a gameobject is usally a bad practice, because all the pointers it holds will not be freed, however if every model, every light and every other thing are also freed, this SHOULD not be a problem.
In absence or more clever ideas, I'll proceed down this road
This requires me to handle the memory correcly when I'm switching scenes, and the amount of things I need to change in order to achieve this is pretty huge...
Today I started by modifying a few classes, for example the audio manager, the profiler and the texture manager. Each one of them now has a reset method that removes everything unnecessary, including gpu memory in case of the texture manager.
The first big problem was the MeshManager, because some of the meshes are loaded by the engine, and should not be freed. So I created a little hacky system to understand if a mesh is for the engine or not without storing the information in the mesh itself.
Now I need to proceed with the most difficult part, which is deleting everything created from within the game. I need to keep track of models, emitters, lights and so on, basically everything I created until now... Deleting only the GameObjects is not an option, even if the components are recursively deleted, because some components could not be attached to anything.
So I guess that for each instantiable class I ever created I will add a static field which holds a list of pointers to the instances, and then I'll just call a Free on each element.
Calling free on a gameobject is usally a bad practice, because all the pointers it holds will not be freed, however if every model, every light and every other thing are also freed, this SHOULD not be a problem.
In absence or more clever ideas, I'll proceed down this road
sabato 5 dicembre 2015
Day 91: Perlin Noise
Today I decided to dig a little deeper in mesh generation, and I wrote a class to generate Perlin noise values for realistic terrain generation (actually I just converted a C# class, because it's a simple but boring straightforward piece of code, and I didn't want to spend to much time on it once I got the idea of how it works).
With the perlin noise class I modified the example I posted two days ago. Instead of picking random values for the height of the terrain, now they are sampled from the perlin texture (not really a texture, the values are dinamically generated).
As you can see from the picture the terrain looks way more natural, and close vertices have similar height values.
To simulate even more realistic terrains, the perlin function is called multiple times, and each time the frequency doubles and the height halves, to get the effect of little bumps and irregularities on the surface of the bigger bumbs....this is difficult to see on the picture, because there are not enough vertices and I used a very low value for the eight of the second perlin function.
With the perlin noise class I modified the example I posted two days ago. Instead of picking random values for the height of the terrain, now they are sampled from the perlin texture (not really a texture, the values are dinamically generated).
As you can see from the picture the terrain looks way more natural, and close vertices have similar height values.
To simulate even more realistic terrains, the perlin function is called multiple times, and each time the frequency doubles and the height halves, to get the effect of little bumps and irregularities on the surface of the bigger bumbs....this is difficult to see on the picture, because there are not enough vertices and I used a very low value for the eight of the second perlin function.
giovedì 3 dicembre 2015
Day 90: Mesh Generation
Today I Implemented a MeshBuilder class, which allows to create meshes from scratch. This was possible thanks to the changes I made yesterday to the loading system of the meshes.
The class allows to create new vertices, to modify their pos, uvs and normals, and it also provides two functions, add_triangle and add_quads to map indices on newly created vertices.
There are also two more important functions: generate_normals and generate_tangents.
The first one can be called if the normals are not manually specified, and it creates them automatically for each triangle. The second one should be always called, because tangents are not added manually, but built from uvs and normals.
That's about it, I started by creating a simple quad, and then I created a bumpy plane to replace the floor of the house i'm always using to test the engine. As you can see from the picture the lighting is correct, thanks to normals and tangent generation, as well as positions as uvs.
Shadows are also correctly casted (except for some moire pattern due to low precision), because the mesh, once created, acts as every other mesh.
The class allows to create new vertices, to modify their pos, uvs and normals, and it also provides two functions, add_triangle and add_quads to map indices on newly created vertices.
There are also two more important functions: generate_normals and generate_tangents.
The first one can be called if the normals are not manually specified, and it creates them automatically for each triangle. The second one should be always called, because tangents are not added manually, but built from uvs and normals.
That's about it, I started by creating a simple quad, and then I created a bumpy plane to replace the floor of the house i'm always using to test the engine. As you can see from the picture the lighting is correct, thanks to normals and tangent generation, as well as positions as uvs.
Shadows are also correctly casted (except for some moire pattern due to low precision), because the mesh, once created, acts as every other mesh.
mercoledì 2 dicembre 2015
Day 89: Indexed meshes
Until now I used to load meshes into a single vertex buffer object. This is bad for performances, because more data need to be sent to the gpu, while an indexed mesh allow to use a single vertex multiple times.
Another big advantage of indexed meshes is the fact that they are easier to read and manually modify, allowing procedural mesh generation.
So today I refactored everything mesh-related, starting from the loading of the mesh, which now produces a vertex array and an index array.
Then I modified all the batches, both 2D and 3D to use the new indexed mesh. Now everything is working and maybe I gained a little fps in all of this, but the real win is the possibility to easily access the mesh data.
Another big advantage of indexed meshes is the fact that they are easier to read and manually modify, allowing procedural mesh generation.
So today I refactored everything mesh-related, starting from the loading of the mesh, which now produces a vertex array and an index array.
Then I modified all the batches, both 2D and 3D to use the new indexed mesh. Now everything is working and maybe I gained a little fps in all of this, but the real win is the possibility to easily access the mesh data.
Tomorrow I'll try to write a mesh_builder class to procedurally generate meshes.
Iscriviti a:
Post (Atom)











