package com.mojang.blaze3d.systems; import com.google.common.collect.Queues; import com.mojang.blaze3d.DontObfuscate; import com.mojang.blaze3d.pipeline.RenderCall; import com.mojang.blaze3d.platform.GLX; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.shaders.FogShape; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexSorting; import com.mojang.logging.LogUtils; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Locale; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.IntConsumer; import java.util.function.Supplier; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.GraphicsStatus; import net.minecraft.client.Minecraft; import net.minecraft.client.OptionInstance; import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.texture.AbstractTexture; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.util.TimeSource; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; import org.joml.Matrix4fStack; import org.joml.Vector3f; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWErrorCallbackI; import org.slf4j.Logger; @Environment(EnvType.CLIENT) @DontObfuscate public class RenderSystem { static final Logger LOGGER = LogUtils.getLogger(); private static final ConcurrentLinkedQueue recordingQueue = Queues.newConcurrentLinkedQueue(); private static final Tesselator RENDER_THREAD_TESSELATOR = new Tesselator(1536); private static final int MINIMUM_ATLAS_TEXTURE_SIZE = 1024; @Nullable private static Thread renderThread; private static int MAX_SUPPORTED_TEXTURE_SIZE = -1; private static boolean isInInit; private static double lastDrawTime = Double.MIN_VALUE; private static final RenderSystem.AutoStorageIndexBuffer sharedSequential = new RenderSystem.AutoStorageIndexBuffer(1, 1, IntConsumer::accept); private static final RenderSystem.AutoStorageIndexBuffer sharedSequentialQuad = new RenderSystem.AutoStorageIndexBuffer(4, 6, (intConsumer, i) -> { intConsumer.accept(i + 0); intConsumer.accept(i + 1); intConsumer.accept(i + 2); intConsumer.accept(i + 2); intConsumer.accept(i + 3); intConsumer.accept(i + 0); }); private static final RenderSystem.AutoStorageIndexBuffer sharedSequentialLines = new RenderSystem.AutoStorageIndexBuffer(4, 6, (intConsumer, i) -> { intConsumer.accept(i + 0); intConsumer.accept(i + 1); intConsumer.accept(i + 2); intConsumer.accept(i + 3); intConsumer.accept(i + 2); intConsumer.accept(i + 1); }); private static Matrix4f projectionMatrix = new Matrix4f(); private static Matrix4f savedProjectionMatrix = new Matrix4f(); private static VertexSorting vertexSorting = VertexSorting.DISTANCE_TO_ORIGIN; private static VertexSorting savedVertexSorting = VertexSorting.DISTANCE_TO_ORIGIN; private static final Matrix4fStack modelViewStack = new Matrix4fStack(16); private static Matrix4f modelViewMatrix = new Matrix4f(); private static Matrix4f textureMatrix = new Matrix4f(); private static final int[] shaderTextures = new int[12]; private static final float[] shaderColor = new float[]{1.0F, 1.0F, 1.0F, 1.0F}; private static float shaderGlintAlpha = 1.0F; private static float shaderFogStart; private static float shaderFogEnd = 1.0F; private static final float[] shaderFogColor = new float[]{0.0F, 0.0F, 0.0F, 0.0F}; private static FogShape shaderFogShape = FogShape.SPHERE; private static final Vector3f[] shaderLightDirections = new Vector3f[2]; private static float shaderGameTime; private static float shaderLineWidth = 1.0F; private static String apiDescription = "Unknown"; @Nullable private static ShaderInstance shader; private static final AtomicLong pollEventsWaitStart = new AtomicLong(); private static final AtomicBoolean pollingEvents = new AtomicBoolean(false); public static void initRenderThread() { if (renderThread != null) { throw new IllegalStateException("Could not initialize render thread"); } else { renderThread = Thread.currentThread(); } } public static boolean isOnRenderThread() { return Thread.currentThread() == renderThread; } public static boolean isOnRenderThreadOrInit() { return isInInit || isOnRenderThread(); } public static void assertOnRenderThreadOrInit() { if (!isInInit && !isOnRenderThread()) { throw constructThreadException(); } } public static void assertOnRenderThread() { if (!isOnRenderThread()) { throw constructThreadException(); } } private static IllegalStateException constructThreadException() { return new IllegalStateException("Rendersystem called from wrong thread"); } public static void recordRenderCall(RenderCall renderCall) { recordingQueue.add(renderCall); } private static void pollEvents() { pollEventsWaitStart.set(Util.getMillis()); pollingEvents.set(true); GLFW.glfwPollEvents(); pollingEvents.set(false); } public static boolean isFrozenAtPollEvents() { return pollingEvents.get() && Util.getMillis() - pollEventsWaitStart.get() > 200L; } public static void flipFrame(long l) { pollEvents(); replayQueue(); Tesselator.getInstance().clear(); GLFW.glfwSwapBuffers(l); pollEvents(); } public static void replayQueue() { while (!recordingQueue.isEmpty()) { RenderCall renderCall = (RenderCall)recordingQueue.poll(); renderCall.execute(); } } public static void limitDisplayFPS(int i) { double d = lastDrawTime + 1.0 / i; double e; for (e = GLFW.glfwGetTime(); e < d; e = GLFW.glfwGetTime()) { GLFW.glfwWaitEventsTimeout(d - e); } lastDrawTime = e; } public static void disableDepthTest() { assertOnRenderThread(); GlStateManager._disableDepthTest(); } public static void enableDepthTest() { GlStateManager._enableDepthTest(); } public static void enableScissor(int i, int j, int k, int l) { GlStateManager._enableScissorTest(); GlStateManager._scissorBox(i, j, k, l); } public static void disableScissor() { GlStateManager._disableScissorTest(); } public static void depthFunc(int i) { assertOnRenderThread(); GlStateManager._depthFunc(i); } public static void depthMask(boolean bl) { assertOnRenderThread(); GlStateManager._depthMask(bl); } public static void enableBlend() { assertOnRenderThread(); GlStateManager._enableBlend(); } public static void disableBlend() { assertOnRenderThread(); GlStateManager._disableBlend(); } public static void blendFunc(GlStateManager.SourceFactor sourceFactor, GlStateManager.DestFactor destFactor) { assertOnRenderThread(); GlStateManager._blendFunc(sourceFactor.value, destFactor.value); } public static void blendFunc(int i, int j) { assertOnRenderThread(); GlStateManager._blendFunc(i, j); } public static void blendFuncSeparate( GlStateManager.SourceFactor sourceFactor, GlStateManager.DestFactor destFactor, GlStateManager.SourceFactor sourceFactor2, GlStateManager.DestFactor destFactor2 ) { assertOnRenderThread(); GlStateManager._blendFuncSeparate(sourceFactor.value, destFactor.value, sourceFactor2.value, destFactor2.value); } public static void blendFuncSeparate(int i, int j, int k, int l) { assertOnRenderThread(); GlStateManager._blendFuncSeparate(i, j, k, l); } public static void blendEquation(int i) { assertOnRenderThread(); GlStateManager._blendEquation(i); } public static void enableCull() { assertOnRenderThread(); GlStateManager._enableCull(); } public static void disableCull() { assertOnRenderThread(); GlStateManager._disableCull(); } public static void polygonMode(int i, int j) { assertOnRenderThread(); GlStateManager._polygonMode(i, j); } public static void enablePolygonOffset() { assertOnRenderThread(); GlStateManager._enablePolygonOffset(); } public static void disablePolygonOffset() { assertOnRenderThread(); GlStateManager._disablePolygonOffset(); } public static void polygonOffset(float f, float g) { assertOnRenderThread(); GlStateManager._polygonOffset(f, g); } public static void enableColorLogicOp() { assertOnRenderThread(); GlStateManager._enableColorLogicOp(); } public static void disableColorLogicOp() { assertOnRenderThread(); GlStateManager._disableColorLogicOp(); } public static void logicOp(GlStateManager.LogicOp logicOp) { assertOnRenderThread(); GlStateManager._logicOp(logicOp.value); } public static void activeTexture(int i) { assertOnRenderThread(); GlStateManager._activeTexture(i); } public static void texParameter(int i, int j, int k) { GlStateManager._texParameter(i, j, k); } public static void deleteTexture(int i) { GlStateManager._deleteTexture(i); } public static void bindTextureForSetup(int i) { bindTexture(i); } public static void bindTexture(int i) { GlStateManager._bindTexture(i); } public static void viewport(int i, int j, int k, int l) { GlStateManager._viewport(i, j, k, l); } public static void colorMask(boolean bl, boolean bl2, boolean bl3, boolean bl4) { assertOnRenderThread(); GlStateManager._colorMask(bl, bl2, bl3, bl4); } public static void stencilFunc(int i, int j, int k) { assertOnRenderThread(); GlStateManager._stencilFunc(i, j, k); } public static void stencilMask(int i) { assertOnRenderThread(); GlStateManager._stencilMask(i); } public static void stencilOp(int i, int j, int k) { assertOnRenderThread(); GlStateManager._stencilOp(i, j, k); } public static void clearDepth(double d) { GlStateManager._clearDepth(d); } public static void clearColor(float f, float g, float h, float i) { GlStateManager._clearColor(f, g, h, i); } public static void clearStencil(int i) { assertOnRenderThread(); GlStateManager._clearStencil(i); } public static void clear(int i, boolean bl) { GlStateManager._clear(i, bl); } public static void setShaderFogStart(float f) { assertOnRenderThread(); shaderFogStart = f; } public static float getShaderFogStart() { assertOnRenderThread(); return shaderFogStart; } public static void setShaderGlintAlpha(double d) { setShaderGlintAlpha((float)d); } public static void setShaderGlintAlpha(float f) { assertOnRenderThread(); shaderGlintAlpha = f; } public static float getShaderGlintAlpha() { assertOnRenderThread(); return shaderGlintAlpha; } public static void setShaderFogEnd(float f) { assertOnRenderThread(); shaderFogEnd = f; } public static float getShaderFogEnd() { assertOnRenderThread(); return shaderFogEnd; } public static void setShaderFogColor(float f, float g, float h, float i) { assertOnRenderThread(); shaderFogColor[0] = f; shaderFogColor[1] = g; shaderFogColor[2] = h; shaderFogColor[3] = i; } public static void setShaderFogColor(float f, float g, float h) { setShaderFogColor(f, g, h, 1.0F); } public static float[] getShaderFogColor() { assertOnRenderThread(); return shaderFogColor; } public static void setShaderFogShape(FogShape fogShape) { assertOnRenderThread(); shaderFogShape = fogShape; } public static FogShape getShaderFogShape() { assertOnRenderThread(); return shaderFogShape; } public static void setShaderLights(Vector3f vector3f, Vector3f vector3f2) { assertOnRenderThread(); shaderLightDirections[0] = vector3f; shaderLightDirections[1] = vector3f2; } public static void setupShaderLights(ShaderInstance shaderInstance) { assertOnRenderThread(); if (shaderInstance.LIGHT0_DIRECTION != null) { shaderInstance.LIGHT0_DIRECTION.set(shaderLightDirections[0]); } if (shaderInstance.LIGHT1_DIRECTION != null) { shaderInstance.LIGHT1_DIRECTION.set(shaderLightDirections[1]); } } public static void setShaderColor(float f, float g, float h, float i) { if (!isOnRenderThread()) { recordRenderCall(() -> _setShaderColor(f, g, h, i)); } else { _setShaderColor(f, g, h, i); } } private static void _setShaderColor(float f, float g, float h, float i) { shaderColor[0] = f; shaderColor[1] = g; shaderColor[2] = h; shaderColor[3] = i; } public static float[] getShaderColor() { assertOnRenderThread(); return shaderColor; } public static void drawElements(int i, int j, int k) { assertOnRenderThread(); GlStateManager._drawElements(i, j, k, 0L); } public static void lineWidth(float f) { if (!isOnRenderThread()) { recordRenderCall(() -> shaderLineWidth = f); } else { shaderLineWidth = f; } } public static float getShaderLineWidth() { assertOnRenderThread(); return shaderLineWidth; } public static void pixelStore(int i, int j) { GlStateManager._pixelStore(i, j); } public static void readPixels(int i, int j, int k, int l, int m, int n, ByteBuffer byteBuffer) { assertOnRenderThread(); GlStateManager._readPixels(i, j, k, l, m, n, byteBuffer); } public static void getString(int i, Consumer consumer) { assertOnRenderThread(); consumer.accept(GlStateManager._getString(i)); } public static String getBackendDescription() { return String.format(Locale.ROOT, "LWJGL version %s", GLX._getLWJGLVersion()); } public static String getApiDescription() { return apiDescription; } public static TimeSource.NanoTimeSource initBackendSystem() { return GLX._initGlfw()::getAsLong; } public static void initRenderer(int i, boolean bl) { GLX._init(i, bl); apiDescription = GLX.getOpenGLVersionString(); } public static void setErrorCallback(GLFWErrorCallbackI gLFWErrorCallbackI) { GLX._setGlfwErrorCallback(gLFWErrorCallbackI); } public static void renderCrosshair(int i) { assertOnRenderThread(); GLX._renderCrosshair(i, true, true, true); } public static String getCapsString() { assertOnRenderThread(); return "Using framebuffer using OpenGL 3.2"; } public static void setupDefaultState(int i, int j, int k, int l) { GlStateManager._clearDepth(1.0); GlStateManager._enableDepthTest(); GlStateManager._depthFunc(515); projectionMatrix.identity(); savedProjectionMatrix.identity(); modelViewMatrix.identity(); textureMatrix.identity(); GlStateManager._viewport(i, j, k, l); } public static int maxSupportedTextureSize() { if (MAX_SUPPORTED_TEXTURE_SIZE == -1) { assertOnRenderThreadOrInit(); int i = GlStateManager._getInteger(3379); for (int j = Math.max(32768, i); j >= 1024; j >>= 1) { GlStateManager._texImage2D(32868, 0, 6408, j, j, 0, 6408, 5121, null); int k = GlStateManager._getTexLevelParameter(32868, 0, 4096); if (k != 0) { MAX_SUPPORTED_TEXTURE_SIZE = j; return j; } } MAX_SUPPORTED_TEXTURE_SIZE = Math.max(i, 1024); LOGGER.info("Failed to determine maximum texture size by probing, trying GL_MAX_TEXTURE_SIZE = {}", MAX_SUPPORTED_TEXTURE_SIZE); } return MAX_SUPPORTED_TEXTURE_SIZE; } public static void glBindBuffer(int i, int j) { GlStateManager._glBindBuffer(i, j); } public static void glBindVertexArray(int i) { GlStateManager._glBindVertexArray(i); } public static void glBufferData(int i, ByteBuffer byteBuffer, int j) { assertOnRenderThreadOrInit(); GlStateManager._glBufferData(i, byteBuffer, j); } public static void glDeleteBuffers(int i) { assertOnRenderThread(); GlStateManager._glDeleteBuffers(i); } public static void glDeleteVertexArrays(int i) { assertOnRenderThread(); GlStateManager._glDeleteVertexArrays(i); } public static void glUniform1i(int i, int j) { assertOnRenderThread(); GlStateManager._glUniform1i(i, j); } public static void glUniform1(int i, IntBuffer intBuffer) { assertOnRenderThread(); GlStateManager._glUniform1(i, intBuffer); } public static void glUniform2(int i, IntBuffer intBuffer) { assertOnRenderThread(); GlStateManager._glUniform2(i, intBuffer); } public static void glUniform3(int i, IntBuffer intBuffer) { assertOnRenderThread(); GlStateManager._glUniform3(i, intBuffer); } public static void glUniform4(int i, IntBuffer intBuffer) { assertOnRenderThread(); GlStateManager._glUniform4(i, intBuffer); } public static void glUniform1(int i, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniform1(i, floatBuffer); } public static void glUniform2(int i, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniform2(i, floatBuffer); } public static void glUniform3(int i, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniform3(i, floatBuffer); } public static void glUniform4(int i, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniform4(i, floatBuffer); } public static void glUniformMatrix2(int i, boolean bl, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniformMatrix2(i, bl, floatBuffer); } public static void glUniformMatrix3(int i, boolean bl, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniformMatrix3(i, bl, floatBuffer); } public static void glUniformMatrix4(int i, boolean bl, FloatBuffer floatBuffer) { assertOnRenderThread(); GlStateManager._glUniformMatrix4(i, bl, floatBuffer); } public static void setupOverlayColor(int i, int j) { assertOnRenderThread(); setShaderTexture(1, i); } public static void teardownOverlayColor() { assertOnRenderThread(); setShaderTexture(1, 0); } public static void setupLevelDiffuseLighting(Vector3f vector3f, Vector3f vector3f2) { assertOnRenderThread(); setShaderLights(vector3f, vector3f2); } public static void setupGuiFlatDiffuseLighting(Vector3f vector3f, Vector3f vector3f2) { assertOnRenderThread(); GlStateManager.setupGuiFlatDiffuseLighting(vector3f, vector3f2); } public static void setupGui3DDiffuseLighting(Vector3f vector3f, Vector3f vector3f2) { assertOnRenderThread(); GlStateManager.setupGui3DDiffuseLighting(vector3f, vector3f2); } public static void beginInitialization() { isInInit = true; } public static void finishInitialization() { isInInit = false; if (!recordingQueue.isEmpty()) { replayQueue(); } if (!recordingQueue.isEmpty()) { throw new IllegalStateException("Recorded to render queue during initialization"); } } public static void glGenBuffers(Consumer consumer) { if (!isOnRenderThread()) { recordRenderCall(() -> consumer.accept(GlStateManager._glGenBuffers())); } else { consumer.accept(GlStateManager._glGenBuffers()); } } public static void glGenVertexArrays(Consumer consumer) { if (!isOnRenderThread()) { recordRenderCall(() -> consumer.accept(GlStateManager._glGenVertexArrays())); } else { consumer.accept(GlStateManager._glGenVertexArrays()); } } public static Tesselator renderThreadTesselator() { assertOnRenderThread(); return RENDER_THREAD_TESSELATOR; } public static void defaultBlendFunc() { blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO ); } @Deprecated public static void runAsFancy(Runnable runnable) { boolean bl = Minecraft.useShaderTransparency(); if (!bl) { runnable.run(); } else { OptionInstance optionInstance = Minecraft.getInstance().options.graphicsMode(); GraphicsStatus graphicsStatus = optionInstance.get(); optionInstance.set(GraphicsStatus.FANCY); runnable.run(); optionInstance.set(graphicsStatus); } } public static void setShader(Supplier supplier) { if (!isOnRenderThread()) { recordRenderCall(() -> shader = (ShaderInstance)supplier.get()); } else { shader = (ShaderInstance)supplier.get(); } } @Nullable public static ShaderInstance getShader() { assertOnRenderThread(); return shader; } public static void setShaderTexture(int i, ResourceLocation resourceLocation) { if (!isOnRenderThread()) { recordRenderCall(() -> _setShaderTexture(i, resourceLocation)); } else { _setShaderTexture(i, resourceLocation); } } public static void _setShaderTexture(int i, ResourceLocation resourceLocation) { if (i >= 0 && i < shaderTextures.length) { TextureManager textureManager = Minecraft.getInstance().getTextureManager(); AbstractTexture abstractTexture = textureManager.getTexture(resourceLocation); shaderTextures[i] = abstractTexture.getId(); } } public static void setShaderTexture(int i, int j) { if (!isOnRenderThread()) { recordRenderCall(() -> _setShaderTexture(i, j)); } else { _setShaderTexture(i, j); } } public static void _setShaderTexture(int i, int j) { if (i >= 0 && i < shaderTextures.length) { shaderTextures[i] = j; } } public static int getShaderTexture(int i) { assertOnRenderThread(); return i >= 0 && i < shaderTextures.length ? shaderTextures[i] : 0; } public static void setProjectionMatrix(Matrix4f matrix4f, VertexSorting vertexSorting) { Matrix4f matrix4f2 = new Matrix4f(matrix4f); if (!isOnRenderThread()) { recordRenderCall(() -> { projectionMatrix = matrix4f2; RenderSystem.vertexSorting = vertexSorting; }); } else { projectionMatrix = matrix4f2; RenderSystem.vertexSorting = vertexSorting; } } public static void setTextureMatrix(Matrix4f matrix4f) { Matrix4f matrix4f2 = new Matrix4f(matrix4f); if (!isOnRenderThread()) { recordRenderCall(() -> textureMatrix = matrix4f2); } else { textureMatrix = matrix4f2; } } public static void resetTextureMatrix() { if (!isOnRenderThread()) { recordRenderCall(() -> textureMatrix.identity()); } else { textureMatrix.identity(); } } public static void applyModelViewMatrix() { Matrix4f matrix4f = new Matrix4f(modelViewStack); if (!isOnRenderThread()) { recordRenderCall(() -> modelViewMatrix = matrix4f); } else { modelViewMatrix = matrix4f; } } public static void backupProjectionMatrix() { if (!isOnRenderThread()) { recordRenderCall(() -> _backupProjectionMatrix()); } else { _backupProjectionMatrix(); } } private static void _backupProjectionMatrix() { savedProjectionMatrix = projectionMatrix; savedVertexSorting = vertexSorting; } public static void restoreProjectionMatrix() { if (!isOnRenderThread()) { recordRenderCall(() -> _restoreProjectionMatrix()); } else { _restoreProjectionMatrix(); } } private static void _restoreProjectionMatrix() { projectionMatrix = savedProjectionMatrix; vertexSorting = savedVertexSorting; } public static Matrix4f getProjectionMatrix() { assertOnRenderThread(); return projectionMatrix; } public static Matrix4f getModelViewMatrix() { assertOnRenderThread(); return modelViewMatrix; } public static Matrix4fStack getModelViewStack() { return modelViewStack; } public static Matrix4f getTextureMatrix() { assertOnRenderThread(); return textureMatrix; } public static RenderSystem.AutoStorageIndexBuffer getSequentialBuffer(VertexFormat.Mode mode) { assertOnRenderThread(); return switch (mode) { case QUADS -> sharedSequentialQuad; case LINES -> sharedSequentialLines; default -> sharedSequential; }; } public static void setShaderGameTime(long l, float f) { float g = ((float)(l % 24000L) + f) / 24000.0F; if (!isOnRenderThread()) { recordRenderCall(() -> shaderGameTime = g); } else { shaderGameTime = g; } } public static float getShaderGameTime() { assertOnRenderThread(); return shaderGameTime; } public static VertexSorting getVertexSorting() { assertOnRenderThread(); return vertexSorting; } @Environment(EnvType.CLIENT) public static final class AutoStorageIndexBuffer { private final int vertexStride; private final int indexStride; private final RenderSystem.AutoStorageIndexBuffer.IndexGenerator generator; private int name; private VertexFormat.IndexType type = VertexFormat.IndexType.SHORT; private int indexCount; AutoStorageIndexBuffer(int vertexStride, int indexStride, RenderSystem.AutoStorageIndexBuffer.IndexGenerator generator) { this.vertexStride = vertexStride; this.indexStride = indexStride; this.generator = generator; } public boolean hasStorage(int index) { return index <= this.indexCount; } public void bind(int index) { if (this.name == 0) { this.name = GlStateManager._glGenBuffers(); } GlStateManager._glBindBuffer(34963, this.name); this.ensureStorage(index); } private void ensureStorage(int neededIndexCount) { if (!this.hasStorage(neededIndexCount)) { neededIndexCount = Mth.roundToward(neededIndexCount * 2, this.indexStride); RenderSystem.LOGGER.debug("Growing IndexBuffer: Old limit {}, new limit {}.", this.indexCount, neededIndexCount); int i = neededIndexCount / this.indexStride; int j = i * this.vertexStride; VertexFormat.IndexType indexType = VertexFormat.IndexType.least(j); int k = Mth.roundToward(neededIndexCount * indexType.bytes, 4); GlStateManager._glBufferData(34963, k, 35048); ByteBuffer byteBuffer = GlStateManager._glMapBuffer(34963, 35001); if (byteBuffer == null) { throw new RuntimeException("Failed to map GL buffer"); } else { this.type = indexType; it.unimi.dsi.fastutil.ints.IntConsumer intConsumer = this.intConsumer(byteBuffer); for (int l = 0; l < neededIndexCount; l += this.indexStride) { this.generator.accept(intConsumer, l * this.vertexStride / this.indexStride); } GlStateManager._glUnmapBuffer(34963); this.indexCount = neededIndexCount; } } } private it.unimi.dsi.fastutil.ints.IntConsumer intConsumer(ByteBuffer buffer) { switch (this.type) { case SHORT: return i -> buffer.putShort((short)i); case INT: default: return buffer::putInt; } } public VertexFormat.IndexType type() { return this.type; } @Environment(EnvType.CLIENT) interface IndexGenerator { void accept(it.unimi.dsi.fastutil.ints.IntConsumer intConsumer, int i); } } }