Program Listing for File model.cpp

Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/rendering/data/model.cpp)

#include <rendering/data/model.hpp>
#include <rendering/data/material.hpp>
#include <map>
#include <string>
#include <fstream>
namespace legion::rendering
{
    sparse_map<id_type, model> ModelCache::m_models;
    async::rw_spinlock ModelCache::m_modelLock;

    async::rw_spinlock ModelCache::m_modelNameLock;
    std::unordered_map<id_type, std::string> ModelCache::m_modelNames;
    bool model_handle::is_buffered() const
    {
        return ModelCache::get_model(id).buffered;
    }

    void model_handle::buffer_data(const buffer& matrixBuffer) const
    {
        ModelCache::buffer_model(id, matrixBuffer);
    }

    void model_handle::overwrite_buffer(buffer& newBuffer, uint bufferID, bool perInstance) const
    {
        ModelCache::overwrite_buffer(id, newBuffer, bufferID, perInstance);
    }

    mesh_handle model_handle::get_mesh() const
    {
        return ModelCache::get_mesh(id);
    }

    const model& model_handle::get_model() const
    {
        return ModelCache::get_model(id);
    }

    const model& ModelCache::get_model(id_type id)
    {
        async::readonly_guard guard(m_modelLock);
        return m_models[id];
    }

    std::string ModelCache::get_model_name(id_type id)
    {
        async::readonly_guard guard(m_modelNameLock);
        return m_modelNames[id];
    }

    void ModelCache::overwrite_buffer(id_type id, buffer& newBuffer, uint bufferID, bool perInstance)
    {
        //OPTICK_EVENT();
        if (id == invalid_id)
            return;
        //get mesh handle
        auto mesh_handle = MeshCache::get_handle(id);
        if (!mesh_handle)
            return;
        //get mesh and lock
        auto [lock, mesh] = mesh_handle.get();
        async::readonly_multiguard guard(m_modelLock, lock);
        auto& model = m_models[id];
        if (bufferID == SV_COLOR)
        {
            model.vertexArray.setAttribPointer(newBuffer, SV_COLOR, 4, GL_FLOAT, false, 0, 0);
            model.vertexArray.setAttribDivisor(SV_COLOR, perInstance);
        }
    }

    void ModelCache::buffer_model(id_type id, const buffer& matrixBuffer)
    {
        if (id == invalid_id)
            return;

        auto mesh_handle = MeshCache::get_handle(id);
        if (!mesh_handle)
            return;
        auto [lock, mesh] = mesh_handle.get();

        async::readonly_multiguard guard(m_modelLock, lock);
        model& model = m_models[id];

        model.vertexArray = vertexarray::generate();
        model.indexBuffer = buffer(GL_ELEMENT_ARRAY_BUFFER, mesh.indices, GL_STATIC_DRAW);

        model.vertexBuffer = buffer(GL_ARRAY_BUFFER, mesh.vertices, GL_STATIC_DRAW);
        model.vertexArray.setAttribPointer(model.vertexBuffer, SV_POSITION, 3, GL_FLOAT, false, 0, 0);

        model.colorBuffer = buffer(GL_ARRAY_BUFFER, mesh.colors, GL_STATIC_DRAW);
        model.vertexArray.setAttribPointer(model.colorBuffer, SV_COLOR, 4, GL_FLOAT, false, 0, 0);

        model.normalBuffer = buffer(GL_ARRAY_BUFFER, mesh.normals, GL_STATIC_DRAW);
        model.vertexArray.setAttribPointer(model.normalBuffer, SV_NORMAL, 3, GL_FLOAT, false, 0, 0);

        model.tangentBuffer = buffer(GL_ARRAY_BUFFER, mesh.tangents, GL_STATIC_DRAW);
        model.vertexArray.setAttribPointer(model.tangentBuffer, SV_TANGENT, 3, GL_FLOAT, false, 0, 0);

        model.uvBuffer = buffer(GL_ARRAY_BUFFER, mesh.uvs, GL_STATIC_DRAW);
        model.vertexArray.setAttribPointer(model.uvBuffer, SV_TEXCOORD0, 2, GL_FLOAT, false, 0, 0);

        model.vertexArray.setAttribPointer(matrixBuffer, SV_MODELMATRIX + 0, 4, GL_FLOAT, false, sizeof(math::mat4), 0 * sizeof(math::mat4::col_type));
        model.vertexArray.setAttribPointer(matrixBuffer, SV_MODELMATRIX + 1, 4, GL_FLOAT, false, sizeof(math::mat4), 1 * sizeof(math::mat4::col_type));
        model.vertexArray.setAttribPointer(matrixBuffer, SV_MODELMATRIX + 2, 4, GL_FLOAT, false, sizeof(math::mat4), 2 * sizeof(math::mat4::col_type));
        model.vertexArray.setAttribPointer(matrixBuffer, SV_MODELMATRIX + 3, 4, GL_FLOAT, false, sizeof(math::mat4), 3 * sizeof(math::mat4::col_type));

        model.vertexArray.setAttribDivisor(SV_MODELMATRIX + 0, 1);
        model.vertexArray.setAttribDivisor(SV_MODELMATRIX + 1, 1);
        model.vertexArray.setAttribDivisor(SV_MODELMATRIX + 2, 1);
        model.vertexArray.setAttribDivisor(SV_MODELMATRIX + 3, 1);

        model.buffered = true;
    }

    model_handle ModelCache::create_model(const std::string& name, const fs::view& file, mesh_import_settings settings)
    {
        id_type id = nameHash(name);

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        // Check if the file is valid to load.
        if (!file.is_valid() || !file.file_info().is_file)
            return invalid_model_handle;
        // Load the mesh if it wasn't already. (It's called MeshCache for a reason.)

        model model{};
        std::string meshName;

        if (settings.contextFolder.get_virtual_path().empty())
        {
            settings.contextFolder = file.parent();
        }

        auto handle = MeshCache::create_mesh(name, file, settings);
        if (handle == invalid_mesh_handle)
        {
            log::error("Failed to load model {}", name);
            return invalid_model_handle;
        }

        // Copy the sub-mesh data.
        auto [lock, data] = handle.get();
        async::readonly_guard guard(lock);
        meshName = data.filePath;

        for (auto& submeshData : data.submeshes)
            model.submeshes.push_back(submeshData);

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = name;
        }

        log::debug("Created model {} with mesh: {}", name, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(const std::string& name, const fs::view& file, std::vector<material_handle>& materials, mesh_import_settings settings)
    {
        id_type id = nameHash(name);

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        // Check if the file is valid to load.
        if (!file.is_valid() || !file.file_info().is_file)
            return invalid_model_handle;
        // Load the mesh if it wasn't already. (It's called MeshCache for a reason.)

        model model{};
        std::string meshName;

        material_list matList;
        material_list* loadedMaterials;
        if (settings.materials)
            loadedMaterials = settings.materials;
        else
        {
            loadedMaterials = &matList;
            settings.materials = &matList;
        }

        if (settings.contextFolder.get_virtual_path().empty())
        {
            settings.contextFolder = file.parent();
        }

        auto handle = MeshCache::create_mesh(name, file, settings);
        if (handle == invalid_mesh_handle)
        {
            log::error("Failed to load model {}", name);
            return invalid_model_handle;
        }

        if (loadedMaterials && !loadedMaterials->empty())
        {
            for (auto& mat : *loadedMaterials)
            {
                static auto defaultLitShader = ShaderCache::create_shader("default lit", fs::view("engine://shaders/default_lit.shs"));

                material_handle material = MaterialCache::create_material(name + "/" + mat.name, defaultLitShader);

                if (mat.doubleSided)
                    material.set_variant("double_sided");

                material.set_param("alphaCutoff", mat.alphaCutoff);

                if (mat.albedoMap)
                {
                    material.set_param("useAlbedoTex", true);
                    material.set_param("albedoTex", TextureCache::create_texture_from_image(mat.albedoMap));
                }
                else
                {
                    material.set_param("useAlbedoTex", false);
                    material.set_param("albedoColor", mat.albedoValue);
                }

                if (mat.metallicRoughnessMap)
                {
                    material.set_param("useMetallicRoughness", true);
                    material.set_param("metallicRoughness", TextureCache::create_texture_from_image(mat.metallicRoughnessMap));
                }
                else
                {
                    material.set_param("useMetallicRoughness", false);

                    if (mat.metallicMap)
                    {
                        material.set_param("useMetallicTex", true);
                        material.set_param("metallicTex", TextureCache::create_texture_from_image(mat.metallicMap));
                    }
                    else
                    {
                        material.set_param("useMetallicTex", false);
                        material.set_param("metallicValue", mat.metallicValue);
                    }

                    if (mat.roughnessMap)
                    {
                        material.set_param("useRoughnessTex", true);
                        material.set_param("roughnessTex", TextureCache::create_texture_from_image(mat.roughnessMap));
                    }
                    else
                    {
                        material.set_param("useRoughnessTex", false);
                        material.set_param("roughnessValue", mat.roughnessValue);
                    }
                }

                if (mat.emissiveMap)
                {
                    material.set_param("useEmissiveTex", true);
                    material.set_param("emissiveTex", TextureCache::create_texture_from_image(mat.emissiveMap));
                }
                else
                {
                    material.set_param("useEmissiveTex", false);
                    material.set_param("emissiveColor", mat.emissiveValue);
                }

                if (mat.normalMap)
                {
                    material.set_param("useNormal", true);
                    material.set_param("normalTex", TextureCache::create_texture_from_image(mat.normalMap));
                }
                else
                {
                    material.set_param("useNormal", false);
                }

                if (mat.aoMap)
                {
                    material.set_param("useAmbientOcclusion", true);
                    material.set_param("ambientOcclusionTex", TextureCache::create_texture_from_image(mat.aoMap));
                }
                else
                {
                    material.set_param("useAmbientOcclusion", false);
                }

                if (mat.heightMap)
                {
                    material.set_param("useHeight", true);
                    material.set_param("heightTex", TextureCache::create_texture_from_image(mat.heightMap));
                }
                else
                {
                    material.set_param("useHeight", false);
                }

                material.setLoadOrSaveBit(false);
                materials.push_back(material);
                log::debug("Loaded embedded material {}/{}", name, mat.name);
            }
        }

        // Copy the sub-mesh data.
        {
            auto [lock, data] = handle.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = name;
        }

        log::debug("Created model {} with mesh: {}", name, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(const std::string& name)
    {
        id_type id = nameHash(name);

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        model model{};
        std::string meshName;

        {// Load the mesh if it wasn't already. (It's called MeshCache for a reason.)
            auto handle = MeshCache::get_handle(name);
            if (handle == invalid_mesh_handle)
            {
                log::error("Failed to load model {}", name);
                return invalid_model_handle;
            }

            // Copy the sub-mesh data.
            auto [lock, data] = handle.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = name;
        }

        log::trace("Created model {} with mesh: {}", name, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(const std::string& name, id_type meshId)
    {
        id_type id = nameHash(name);

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        model model{};
        std::string meshName;

        {// Load the mesh if it wasn't already. (It's called MeshCache for a reason.)
            auto handle = MeshCache::get_handle(meshId);
            if (handle == invalid_mesh_handle)
            {
                log::error("Failed to load model {}", name);
                return invalid_model_handle;
            }

            // Copy the sub-mesh data.
            auto [lock, data] = handle.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = name;
        }

        log::trace("Created model {} with mesh: {}", name, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(id_type id)
    {
        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        model model{};
        std::string meshName;

        {// Load the mesh if it wasn't already. (It's called MeshCache for a reason.)
            auto handle = MeshCache::get_handle(id);
            if (handle == invalid_mesh_handle)
            {
                log::error("Failed to load model {}", id);
                return invalid_model_handle;
            }

            // Copy the sub-mesh data.
            auto [lock, data] = handle.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = std::to_string(id);
        }

        log::trace("Created model {} with mesh: {}", id, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(const std::string& name, mesh_handle mesh)
    {
        id_type id = nameHash(name);

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        model model{};
        std::string meshName;

        {// Load the mesh if it wasn't already. (It's called MeshCache for a reason.)
            if (mesh == invalid_mesh_handle)
            {
                log::error("Failed to load model {}", name);
                return invalid_model_handle;
            }

            // Copy the sub-mesh data.
            auto [lock, data] = mesh.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            async::readwrite_guard guard(m_modelNameLock);
            m_modelNames[id] = name;
        }

        log::trace("Created model {} with mesh: {}", name, meshName);

        return { id };
    }

    model_handle ModelCache::create_model(mesh_handle mesh)
    {
        id_type id = mesh.id;

        {// Check if the model already exists.
            async::readonly_guard guard(m_modelLock);
            if (m_models.contains(id))
                return { id };
        }

        model model{};
        std::string meshName;

        {// Load the mesh if it wasn't already. (It's called MeshCache for a reason.)
            if (mesh == invalid_mesh_handle)
            {
                log::error("Failed to load model {}", id);
                return invalid_model_handle;
            }

            // Copy the sub-mesh data.
            auto [lock, data] = mesh.get();
            async::readonly_guard guard(lock);
            meshName = data.filePath;

            for (auto& submeshData : data.submeshes)
                model.submeshes.push_back(submeshData);
        }

        // The model still needs to be buffered on the rendering thread.
        model.buffered = false;

        { // Insert the model into the model list.
            async::readwrite_guard guard(m_modelLock);
            m_models.insert(id, model);
        }

        {
            auto [lock, rawmesh] = mesh.get();
            async::mixed_multiguard guard(m_modelNameLock, async::lock_state_read, lock, async::lock_state_write);
            m_modelNames[id] = rawmesh.filePath;
        }

        log::trace("Created model {} with mesh: {}", id, meshName);

        return { id };
    }

    model_handle ModelCache::get_handle(const std::string& name)
    {
        id_type id = nameHash(name);
        async::readonly_guard guard(m_modelLock);
        if (m_models.contains(id))
            return { id };
        return invalid_model_handle;
    }

    model_handle ModelCache::get_handle(id_type id)
    {
        async::readonly_guard guard(m_modelLock);
        if (m_models.contains(id))
            return { id };
        return invalid_model_handle;
    }

    mesh_handle ModelCache::get_mesh(id_type id)
    {
        return MeshCache::get_handle(id);
    }

    void ModelCache::destroy_model(const std::string& name)
    {
        bool erased = false;
        id_type id = nameHash(name);
        {
            async::readwrite_guard guard(m_modelLock);

            if (!m_models.contains(id))
                return;

            MeshCache::destroy_mesh(id);
            erased = m_models.erase(id);
        }

        if (erased)
            log::debug("Destroyed model {}", name);
    }

    mesh_handle ModelCache::get_mesh(const std::string& name)
    {
        id_type id = nameHash(name);
        return MeshCache::get_handle(id);
    }
}