Ownership of Models + Double Frees
Opened this issue ยท 11 comments
When loading the Models with Mesh the ownership is not clear and the mash(es)(?) got double freed.
Logs
INFO: TEXTURE: [ID 2] Texture loaded successfully (128x128 | GRAY_ALPHA | 1 mipmaps)
INFO: FONT: Default font loaded successfully (224 glyphs)
INFO: FILEIO: [resources/cubicmap.png] File loaded successfully
INFO: IMAGE: Data loaded successfully (32x16 | R8G8B8 | 1 mipmaps)
INFO: TEXTURE: [ID 3] Texture loaded successfully (32x16 | R8G8B8 | 1 mipmaps)
INFO: VAO: [ID 2] Mesh uploaded successfully to VRAM (GPU)
INFO: FILEIO: [resources/cubicmap_atlas.png] File loaded successfully
INFO: IMAGE: Data loaded successfully (256x256 | R8G8B8A8 | 1 mipmaps)
INFO: TEXTURE: [ID 4] Texture loaded successfully (256x256 | R8G8B8A8 | 1 mipmaps)
INFO: TIMER: Target time per frame: 16.667 milliseconds
INFO: TEXTURE: [ID 2] Unloaded texture data from VRAM (GPU)
INFO: SHADER: [ID 3] Default shader unloaded successfully
INFO: TEXTURE: [ID 1] Default texture unloaded successfully
double free or corruption (!prev)
INFO: Window closed successfully
INFO: TEXTURE: [ID 4] Unloaded texture data from VRAM (GPU)
INFO: VAO: [ID 2] Unloaded vertex array data from VRAM (GPU)
INFO: VAO: [ID 2] Unloaded vertex array data from VRAM (GPU)
models example
// Define the camera to look into our 3d world
raylib::Camera camera({ 0.2f, 0.4f, 0.2f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, 45.0f);
raylib::Texture cubicmap;
raylib::Model model;
raylib::Texture texture;
// load from imMap
{
raylib::Image imMap;
imMap.Load("resources/cubicmap.png"); // Load cubicmap image (RAM)
cubicmap.Load(imMap); // Convert image to texture to display (VRAM)
raylib::Mesh mesh = raylib::Mesh::Cubicmap(imMap, Vector3{1.0f, 1.0f, 1.0f});
model.Load(mesh);
// NOTE: By default each cube is mapped to one part of texture atlas
texture.Load("resources/cubicmap_atlas.png"); // Load map texture
model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = texture; // Set map diffuse texture
}
// Error happen at the end of the main
~Model() {
Unload(); ///< error here ::UnloadModel(*this);
}
rmodel.c
// Unload model (meshes/materials) from memory (RAM and/or VRAM)
// NOTE: This function takes care of all model elements, for a detailed control
// over them, use UnloadMesh() and UnloadMaterial()
void UnloadModel(Model model)
{
// Unload meshes
for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); ///< here
...
// Unload mesh from memory (RAM and VRAM)
void UnloadMesh(Mesh mesh)
{
// Unload rlgl mesh vboId data
rlUnloadVertexArray(mesh.vaoId);
if (mesh.vboId != NULL) for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]);
RL_FREE(mesh.vboId);
RL_FREE(mesh.vertices); ///< error here
For Context, LoadModelFromMesh
from rmodels.c
:
// Load model from generated mesh
// WARNING: A shallow copy of mesh is generated, passed by value,
// as long as struct contains pointers to data and some values, we get a copy
// of mesh pointing to same data as original version... be careful!
Model LoadModelFromMesh(Mesh mesh)
{
Model model = { 0 };
model.transform = MatrixIdentity();
model.meshCount = 1;
model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh));
model.meshes[0] = mesh; ///< here
Seems like the mesh
ownership is shared with the model.
Good catch ๐ What would be the best way around this? Something similar to the TextureUnmanaged approach we took?
I'm honestly not sure, my first though would be a Mesh
and SharedMesh
class, the SharedMesh
can only been used by the Model
. (Maybe something like a shared_ptr
), but idk.
The SharedMesh
has a "weak-ptr" to the Model(s) and know when the Model has been unloaded, so it didn't need to unload (itself). (but that's just to much shared memory management ๐ IMO)
Maybe a MeshUnmanaged
can be fine for the first solution.
I also had look into RenderTexture
and they have similar ownership problem, I changed my own code to this:
void SetTexture(const ::Texture& newTexture) = delete;
void SetTexture(::Texture&& newTexture) {
texture = newTexture;
newTexture = NullTexture;
}
rtextures.c
// Unload render texture from GPU memory (VRAM)
void UnloadRenderTexture(RenderTexture2D target)
{
if (target.id > 0)
{
if (target.texture.id > 0)
{
// Color texture attached to FBO is deleted
rlUnloadTexture(target.texture.id);
}
// NOTE: Depth texture/renderbuffer is automatically
// queried and deleted before deleting framebuffer
rlUnloadFramebuffer(target.id);
}
}
texture
in RenderTexture
gets unloaded.
Hello, I noticed this could happen with Shaders as well, as seen in Raylib's rmodels.c:2110
:
// Unload material from memory
void UnloadMaterial(Material material)
{
// Unload material shader (avoid unloading default shader, managed by raylib)
if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader);
...
I suppose a ShaderUnamanged class may also be required.
Good catch, @DenisBelmondo! Let's re-work this issue for Shaders too.
https://github.com/RobLoach/raylib-cpp/blob/master/include/Material.hpp#L54
GETTERSETTER(::Shader, Shader, shader)
GETTERSETTER(::MaterialMap*, Maps, maps)
// TODO(RobLoach): Resolve the Material params being a float[4].
// GETTERSETTER(float[4], Params, params)
Seem like the getter for shader
needs to return ShaderUnamanged
OR just an and the setter needs to move the ownership of ::Shader*
(?, keep in mind the owner is still the Material
, so beware of dangling-pointer...)Shader&&
(or Owner<::Shader>&&
) into the Material.
void SetShader(const Shader&) = delete;
void SetShader(::Shader*) { ... } // keep the old API ?, risk of dangling pointer, who is the owner of this pointer ???
//void SetShader(const ShaderUnamanged&) = delete; // not sure about setter for ShaderUnamanged
void SetShader(Shader&&) { ... }
void SetShader(::Shader&&) { ... }
I guess you load the Shader via raylib (C API) or use the Shader
class and move it into the Material:
raylib::Material mat;
{
raylib::Shader shr (...);
mat.SetShader(shr);
}
{
mat.SetShader(LoadShader(...))
}
EDIT:
@RobLoach void SetShader(::Shader*) = delete
This can to be a Breaking Change for the Shader class
https://github.com/RobLoach/raylib-cpp/blob/master/include/Material.hpp#L75
Maybe when resetting the shader
in Material
, the id
should be set to rlGetShaderIdDefault()
and shader.locs = nullptr;
(just to be safe).
https://github.com/raysan5/raylib/blob/master/examples/shaders/shaders_basic_pbr.c#L266
// Unbind (disconnect) shader from car.material[0]
// to avoid UnloadMaterial() trying to unload it automatically
car.materials[0].shader = (Shader){ 0 };
Rather than making a new issue, let's just keep this rolling to keep the history in place. Thanks a lot, @furudbat, for the work on cleaning up some of the Shader work.
It would be really awesome to "automate" or RAII the ownership of resources. (IMHO it should be one of THE feature of this project)
We already try to use move-semantic and dtor to free resources and memory.
Some resources are really simple like Image
or bit more complex like RenderTexture
, but other like Model
feels more like a mess.
Would be nice to gather some information on HOW the Load
/Unload
C raylib functions works.
Image
This one is very simple, Unload
with the dtor,
beware of data
(pointer) ownership (moved).
AudioStream and Wave
Like Image
, it's self contained.
Used by Sound
and Music
.
Font
Self contained, but a bit special, only Unload non-default fonts, see UnloadFont.
Sound and Music
When using Sound
or Music
, you can use AudioStream
to construct the classes, the ownership of AudioStream
gets moved into Sound
/Music
, see UnloadMusicStream
.
// Unload music stream
void UnloadMusicStream(Music music)
{
UnloadAudioStream(music.stream);
make AudioStream
only movable into Music
/Sound
???
Texture and Shader
- Manages
TextureUnmanaged
, callsUnloadTexture
.- unload texture (id)
- Manages
ShaderUnmanaged
, callsUnloadShader
.- unload shader program (id)
- free locs
Be aware when constructing Shader
with locs
, locs ownership get transfer into Shader
RenderTexture
This one holds an TextureUnmanaged
(indirectly), unload frame buffer and texture (UnloadRenderTexture
).
Be aware when using ::Texture
to construct the RenderTexture
(if you are not the owner).
Maybe forbid calls with const-ref class and only use move:
RenderTexture(unsigned int id, ::Texture&& texture, const ::Texture& depth)
: ::RenderTexture{id, texture, depth} {
texture = { 0 };
}
Not sure about depth
, but the frame buffer (id
) and texture gets unloaded, but only IF target.id > 0
:
// Unload render texture from GPU memory (VRAM)
void UnloadRenderTexture(RenderTexture2D target)
{
if (target.id > 0)
{
if (target.texture.id > 0)
{
// Color texture attached to FBO is deleted
rlUnloadTexture(target.texture.id);
}
// NOTE: Depth texture/renderbuffer is automatically
// queried and deleted before deleting framebuffer
rlUnloadFramebuffer(target.id);
}
}
Material
Shader and maps (pointer) can be set (beware of ownership).
// Unload material from memory
void UnloadMaterial(Material material)
{
// Unload material shader (avoid unloading default shader, managed by raylib)
if (material.shader.id != rlGetShaderIdDefault()) UnloadShader(material.shader);
// Unload loaded texture maps (avoid unloading default texture, managed by raylib)
if (material.maps != NULL)
{
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
{
if (material.maps[i].texture.id != rlGetTextureIdDefault()) rlUnloadTexture(material.maps[i].texture.id);
}
}
RL_FREE(material.maps);
}
- only unload non-default Shader, make
Shader
only movable intoMaterial
??? - Unload non-default textures (who is owner of this textures, the material ?)
- free
maps
Mesh
???
Model
???
Value Classes
Color
, Vector
, etc. ... no RAII needed.
"Global" Management Classes
Some classes like AudioDevice
or Window
, SHOULD be initialized and closed once.
But I personally would not recommend make this classes singletone, the user should be responsible for using this classes once, like in main
or in some sort of Game
class, it's more of a "Guard" class.
Hi there! Sorry, didn't look through issues first and created a pull request for a related problem: #340
Great research here, @furudbat. Raylib could certainly clean up the memory management of some of these objects a bit. Having it automated would be a bit of a challenge, but I'm open to ideas.
Thanks again for the push, @GaniAliguzhinov. Glad to see some clean up of the memory.