Does it make sense to do a pool over a opengl buffer?

Viewed 78

Let's say a scene can have up to 1024 point lights. Usually, there'd be less, but the worse case is 1024. They will be added and deleted dynamically over some time or after a certain action.

Does it make sense to allocate a uniform buffer with size 1024 * sizeof(PointLight), map the buffer at the beginning and do a pool over it? Or the better idea would be to create a CPU side pool and then commit the CPU side data to the buffer using glNamedBufferSubData every frame?

Something like this, very simple, does not take a uniform buffer alignment into consideration, because it's not related to the question.

template<typename T, size_t MAX_SIZE>
    struct GPUPool
    {
        GPUPool()
        {
            glCreateBuffers(1, &ubo);
            glNamedBufferStorage(ubo, MAX_SIZE * sizeof(T), nullptr, GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT);
            memory = (T*)glMapNamedBufferRange(ubo, 0, MAX_SIZE * sizeof(T), GL_MAP_WRITE_BIT | GL_MAP_COHERENT_BIT | GL_MAP_PERSISTENT_BIT);
        }

        ~GPUPool()
        {
            glDeleteBuffers(1, &ubo);
        }

        [[nodiscard]] inline uint32_t insert(const T& element)
        {
            if (!freeIndexes.empty()) {
                auto index = freeIndexes.back();
                freeIndexes.pop_back();

                memcpy(add(memory, index), &element, sizeof(element));

                ++size;

                return index;
            }

            memcpy(add(memory, backIndex), &element, sizeof(element));

            auto ret = backIndex;
            backIndex += sizeof(element);
            ++size;
            return ret;
        }

        void Bind(uint32_t bindingIndex)
        {
            glBindBuffer(GL_UNIFORM_BUFFER, bindingIndex);
        }

        void erase(size_t index)
        {
            freeIndexes.push_back(index);
            --size;
        }

        GLuint ubo = 0;
        T* memory = nullptr;

        std::vector<size_t> freeIndexes;
        uint32_t backIndex = 0;

        size_t size = 0;
    };

usage:


struct PointLight
{
    vec3 position;
    vec3 color;
    float fallof;
    float radius;
};


GPUPool<PointLight, 1024> pointLightPool;

std::vector<uint32_t> lightIndexes;

while(true) 
{    
    if(Something_Happend_And_Lights_Are_Added()) {
        for(uint32_t i = 0; i < 10; ++i)
            lightIndexes.emplace_back() = pointLightPool.insert(someLight);
    }

    if(Something_Happend_And_Light_2_And_5_Are_Deleted()) {
        //when lights get deleted, lightIndexes should also be updated, but let's leave it
        //for simplicity
        pointLightPool.erase(lightIndexes[1]);
        pointLightPool.erase(lightIndexes[4]);
    }

    if(Something_Happend_And_More_Lights_Are_Added()) {
        //light pool will place a new light into the old memory spot.
        lightIndexes.emplace_back() = pointLightPool.insert(someLight);
    }

    pointLightPool.Bind(0);
    //draw scene...
}

0 Answers
Related