mojira.dev
MC-144307

VBO delete/create world switching lag + System.gc()

Switching worlds can cause NO LAG! Just remove System.gc() from the code and don't delete all VBOs when changing dimensions. For example, on Hypixel dimension switch is sent 2 times which causes the screen to darken 2 times (especially with render distance 32). This lag can take up to 5 seconds on Windows and 30+ seconds on linux leading to disconnection with the server.

How to reproduce:
Set render distance to 32 and go through the nether portal in singleplayer
or switch subserver in multiplayer.

Solution:
Remove System.gc() Reuse buffers. Sample code below

public class GlBuffer
{
	public static GlBuffersStack glBuffers = new GlBuffersStack(1024*64);
	private static int glBuffersIndex = 0;
	public static int vmem = 0;
	private int index;
	
	public static GlBuffer genBuffer(){
		GlBuffer result = null;
    	if(glBuffersIndex < glBuffers.size()){
    		result = glBuffers.get(glBuffersIndex);
    	}else{
	    	result = new GlBuffer();
	    	glBuffers.push(result);
    	}
    	result.index = glBuffersIndex;
    	
    	glBuffersIndex++;
		return result;
	}
	public static void trimUnused(){
		
		while(glBuffers.size() > 0){
			GlBuffer gb = glBuffers.get(glBuffers.size()-1);
			if(gb.released()){
				gb.deleteGlBuffers();
				glBuffers.popSilently();
			}else{
				break;
			}
		}
	}
	
	
    private int glBufferId;
	private int size;
    
    private static int globalCount = 0;

    public GlBuffer()
    {
        this.glBufferId = OpenGlHelper.glGenBuffers();
        globalCount++;
    }

    public void bindBuffer()
    {
        OpenGlHelper.glBindBuffer(OpenGlHelper.GL_ARRAY_BUFFER, this.glBufferId);
    }

    public void setBufferData(ByteBuffer data)
    {
    	vmem -= this.size;
        this.bindBuffer();
        OpenGlHelper.glBufferData(OpenGlHelper.GL_ARRAY_BUFFER, data, 35044);
        this.unbindBuffer();
        this.size = data.limit();
        vmem += this.size;
    }

    public void drawArrays(int mode, int count)
    {
        GL11.glDrawArrays(mode, 0, count);
    }

    public void unbindBuffer()
    {
        OpenGlHelper.glBindBuffer(OpenGlHelper.GL_ARRAY_BUFFER, 0);
    }

    public void deleteGlBuffers()
    {
        if (this.glBufferId >= 0)
        {
            OpenGlHelper.glDeleteBuffers(this.glBufferId);
            this.glBufferId = -1;
            globalCount--;
        }
    }
    
    public void releaseGenBuffer(){
    	if(this.index == -1){
    		return;
    	}
    	GlBuffer self = glBuffers.get(this.index);
    	if(this != self){
    		throw new RuntimeException("wrong order in releaseGenBuffer");
    	}
    	glBuffersIndex--;
    	GlBuffer last = glBuffers.get(glBuffersIndex);
    	
    	glBuffers.set(this.index, last);
    	glBuffers.set(glBuffersIndex, self);
    	
    	
    	last.index = this.index;
    	this.index = -1;
    	
    }
    
    public boolean released(){
    	return this.index == -1;
    }

	public static int getGlobalCount(){
		return globalCount;
	}
	public int getSize(){
		return this.size;
	}
}




public final class GlBuffersStack {

    public GlBuffer[] stack;
    public int size;

    public GlBuffersStack(int initialCapacity) {
        stack = new GlBuffer[initialCapacity];
    }

    public GlBuffer push(GlBuffer value) {
        if (size + 1 >= stack.length) {
            resizeStack(stack.length * 2);
        }
        stack[size++] = value;
        return value;
    }

    public void popSilently() {
        stack[--size] = null;
    }

    public GlBuffer pop() {
        final GlBuffer result = stack[--size]; 
        stack[size] = null; 
        return result;
    }

    public GlBuffer peek() {
        return size == 0 ? null : stack[size - 1];
    }

    public int size() {
        return size;
    }

    public boolean hasStuff() {
        return size > 0;
    }

    public GlBuffer get(int i) {
        return stack[i];
    }

    private void resizeStack(int newCapacity) {
    	GlBuffer[] newStack = new GlBuffer[newCapacity];
        System.arraycopy(stack, 0, newStack, 0, Math.min(size, newCapacity));
        stack = newStack;
    }

    public String toString() {
        StringBuffer result = new StringBuffer("[");
        for (int i = 0; i < size; i++) {
            if (i > 0) {
                result.append(", ");
            }
            result.append(stack[i]);
        }
        result.append(']');
        return result.toString();
    }

	public void set(int i, GlBuffer value) {
		stack[i] = value;
	}
}

Comments 3

Looks great. I tested and it switches worlds very fast! I think it should be applied to Minecraft.

Can this still be reproduced in 1.16 Release Candidate 1 or later?

Are you sure about this guys? Isn't VBOs removed since 18w44a?

7ERr0r

(Unassigned)

Community Consensus

(Unassigned)

Minecraft 1.13.2, Minecraft 19w05a, Minecraft 19w06a, Minecraft 19w07a, Minecraft 19w08b, ..., Minecraft 1.14 Pre-Release 4, Minecraft 1.14 Pre-Release 5, Minecraft 1.14, 1.15.2, 20w06a

Retrieved