3
\$\begingroup\$

I currently have a simple Minecraft-like grid system setup.
The game has a world -> a world has multiple chunks -> a chunk has blocks.


The world asks the chunks to render (if needed), but the render function in the chunk class takes up 97.4% of the application!
72.8% of that method is checking whether a block is at a specific location. This is needed, because I want to check if the block is surrounded by other blocks. If so the block is not visible, so it doesn't need to be rendered.

World.cpp

//97.4% of the application
void World::render()
{
    m_Renderer.prepare();
    for (int i = 0; i < CHUNKS_AMOUNT; i++) {
        m_Chunks[i]->render();
    }
}

//33.8% of the application
bool World::hasBlockAtLocation(const int& x, const int& y, const int& z)
{
    int chunkX = x / CHUNK_WIDTH;
    int chunkZ = z / CHUNK_WIDTH;

    Chunk* const& chunk = getChunk(chunkX, chunkZ);

    if (chunk) {

        int blockX = x - chunkX*CHUNK_WIDTH;
        int blockZ = z - chunkZ*CHUNK_WIDTH;

        if (chunk->hasBlockAtLocation(blockX, y, blockZ))
            return 1;
    }
    return 0;
}

Chunk.cpp

//97.4% of the application
void Chunk::render()
{
    int xMin = xLoc * CHUNK_WIDTH;
    int xMax = xLoc * CHUNK_WIDTH + CHUNK_WIDTH;

    int zMin = zLoc * CHUNK_WIDTH;
    int zMax = zLoc * CHUNK_WIDTH + CHUNK_WIDTH;

    if (xMax < m_Camera->getXMin()) return;
    if (xMin > m_Camera->getXMax()) return;

    if (zMax < m_Camera->getZMin()) return;
    if (zMin > m_Camera->getZMax()) return;

    for (int i = 0; i < CHUNK_SIZE; i++) {
        if (m_Blocks[i] != nullptr) {
            glm::vec3& pos = *m_Blocks[i]->getPosition();

            //If statement takes 72.6% of the application!
            if (!m_World->hasBlockAtLocation(pos.x - 1, pos.y, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x + 1, pos.y, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y - 1, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y + 1, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y, pos.z - 1) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y, pos.z + 1))

            {
                m_Renderer->addToRenderQueue(m_Blocks[i]);
            }
        }
    }
    m_Renderer->render();
}

//13.3% of the application
bool Chunk::hasBlockAtLocation(const int& x, const int& y, const int& z)
{
    if (x < 0 || y < 0 || z < 0) return 0;
    if (x >= CHUNK_WIDTH || y >= CHUNK_HEIGHT || z >= CHUNK_WIDTH) return 0;

    if (m_Blocks[CHUNK_WIDTH * CHUNK_WIDTH * y + CHUNK_WIDTH * z + x])
        return 1;
    return 0;
}

So how can I increase this speed? Because it's a real bottleneck and I ran out of ideas...

Any help would be appreciated!
If any additional information is required please ask.

\$\endgroup\$
6
\$\begingroup\$

You're going about this in a way that will prevent you from being able to optimise that away. In other words, you're at a dead end, code-wise.

The way this is supposed to be done is not to render every individual block (not even just the surface blocks like you are trying to do) but rather to build a single large mesh manifold that collectively describes all air-exposed voxel surfaces for the current chunk.

This way you only do those expensive "surrounds" checks during mesh construction and when the mesh is updated, which is relatively infrequent. Then you render the entire chunk manifold every frame, without per-frame visibility checks.

\$\endgroup\$
1
  • \$\begingroup\$ @DamyvanValenberg Thanks for the quick accept! :) Good luck. \$\endgroup\$
    – Engineer
    Nov 3 '15 at 21:25
2
\$\begingroup\$

There is one optimization you can still do, which is to create a cache of polygons that need to be rendered:

//only called when a (neighboring) chunk changes or is loaded
private void Chunk::buildRenderQueue(){
    int xMin = xLoc * CHUNK_WIDTH;
    int xMax = xLoc * CHUNK_WIDTH + CHUNK_WIDTH;

    int zMin = zLoc * CHUNK_WIDTH;
    int zMax = zLoc * CHUNK_WIDTH + CHUNK_WIDTH;

    if (xMax < m_Camera->getXMin()) return;
    if (xMin > m_Camera->getXMax()) return;

    if (zMax < m_Camera->getZMin()) return;
    if (zMin > m_Camera->getZMax()) return;

    for (int i = 0; i < CHUNK_SIZE; i++) {
        if (m_Blocks[i] != nullptr) {
            glm::vec3& pos = *m_Blocks[i]->getPosition();

            //If statement takes 72.6% of the application!
            if (!m_World->hasBlockAtLocation(pos.x - 1, pos.y, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x + 1, pos.y, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y - 1, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y + 1, pos.z) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y, pos.z - 1) ||
                !m_World->hasBlockAtLocation(pos.x, pos.y, pos.z + 1))

            {
                //push into vector<Block*> as cache
                m_renderableBlocks->push_back(&m_Blocks[i]);
            }
        }
    }
}

and rendering becomes the simple:

void Chunk::render()
{
    for(auto it = m_renderableBlocks.begin(); it != m_renderableBlocks.end(); ++it){
        //get blocks to render from cache
        m_Renderer.addToRenderQueue(*(*it));
    }
    m_Renderer->render();
}

Beyond that if your chunk size is a power of 2 then the optimizer can change the math in World::hasBlockAtLocation and Chunk::hasBlockAtLocation by bit operations. You can do that explicitly if you don't trust the optimizer to catch it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.