503 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			503 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package com.mojang.blaze3d.systems;
 | |
| 
 | |
| import com.mojang.blaze3d.DontObfuscate;
 | |
| import com.mojang.blaze3d.ProjectionType;
 | |
| import com.mojang.blaze3d.TracyFrameCapture;
 | |
| import com.mojang.blaze3d.buffers.GpuBuffer;
 | |
| import com.mojang.blaze3d.buffers.GpuBufferSlice;
 | |
| import com.mojang.blaze3d.buffers.GpuFence;
 | |
| import com.mojang.blaze3d.buffers.Std140SizeCalculator;
 | |
| import com.mojang.blaze3d.opengl.GlDevice;
 | |
| import com.mojang.blaze3d.platform.GLX;
 | |
| import com.mojang.blaze3d.shaders.ShaderType;
 | |
| import com.mojang.blaze3d.textures.GpuTextureView;
 | |
| import com.mojang.blaze3d.vertex.BufferBuilder;
 | |
| import com.mojang.blaze3d.vertex.ByteBufferBuilder;
 | |
| import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 | |
| import com.mojang.blaze3d.vertex.MeshData;
 | |
| import com.mojang.blaze3d.vertex.Tesselator;
 | |
| import com.mojang.blaze3d.vertex.VertexFormat;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import java.nio.ByteBuffer;
 | |
| import java.util.Locale;
 | |
| import java.util.concurrent.atomic.AtomicBoolean;
 | |
| import java.util.concurrent.atomic.AtomicLong;
 | |
| import java.util.function.BiFunction;
 | |
| import java.util.function.IntConsumer;
 | |
| import net.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import net.minecraft.Util;
 | |
| import net.minecraft.client.Minecraft;
 | |
| import net.minecraft.client.renderer.DynamicUniforms;
 | |
| import net.minecraft.resources.ResourceLocation;
 | |
| import net.minecraft.util.ArrayListDeque;
 | |
| 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.lwjgl.system.MemoryUtil;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| @DontObfuscate
 | |
| public class RenderSystem {
 | |
| 	static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	public static final int MINIMUM_ATLAS_TEXTURE_SIZE = 1024;
 | |
| 	public static final int PROJECTION_MATRIX_UBO_SIZE = new Std140SizeCalculator().putMat4f().get();
 | |
| 	@Nullable
 | |
| 	private static Thread renderThread;
 | |
| 	@Nullable
 | |
| 	private static GpuDevice DEVICE;
 | |
| 	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);
 | |
| 		intConsumer.accept(i + 1);
 | |
| 		intConsumer.accept(i + 2);
 | |
| 		intConsumer.accept(i + 2);
 | |
| 		intConsumer.accept(i + 3);
 | |
| 		intConsumer.accept(i);
 | |
| 	});
 | |
| 	private static final RenderSystem.AutoStorageIndexBuffer sharedSequentialLines = new RenderSystem.AutoStorageIndexBuffer(4, 6, (intConsumer, i) -> {
 | |
| 		intConsumer.accept(i);
 | |
| 		intConsumer.accept(i + 1);
 | |
| 		intConsumer.accept(i + 2);
 | |
| 		intConsumer.accept(i + 3);
 | |
| 		intConsumer.accept(i + 2);
 | |
| 		intConsumer.accept(i + 1);
 | |
| 	});
 | |
| 	private static ProjectionType projectionType = ProjectionType.PERSPECTIVE;
 | |
| 	private static ProjectionType savedProjectionType = ProjectionType.PERSPECTIVE;
 | |
| 	private static final Matrix4fStack modelViewStack = new Matrix4fStack(16);
 | |
| 	private static Matrix4f textureMatrix = new Matrix4f();
 | |
| 	public static final int TEXTURE_COUNT = 12;
 | |
| 	private static final GpuTextureView[] shaderTextures = new GpuTextureView[12];
 | |
| 	@Nullable
 | |
| 	private static GpuBufferSlice shaderFog = null;
 | |
| 	@Nullable
 | |
| 	private static GpuBufferSlice shaderLightDirections;
 | |
| 	@Nullable
 | |
| 	private static GpuBufferSlice projectionMatrixBuffer;
 | |
| 	@Nullable
 | |
| 	private static GpuBufferSlice savedProjectionMatrixBuffer;
 | |
| 	private static final Vector3f modelOffset = new Vector3f();
 | |
| 	private static float shaderLineWidth = 1.0F;
 | |
| 	private static String apiDescription = "Unknown";
 | |
| 	private static final AtomicLong pollEventsWaitStart = new AtomicLong();
 | |
| 	private static final AtomicBoolean pollingEvents = new AtomicBoolean(false);
 | |
| 	@Nullable
 | |
| 	private static GpuBuffer QUAD_VERTEX_BUFFER;
 | |
| 	private static final ArrayListDeque<RenderSystem.GpuAsyncTask> PENDING_FENCES = new ArrayListDeque<>();
 | |
| 	@Nullable
 | |
| 	public static GpuTextureView outputColorTextureOverride;
 | |
| 	@Nullable
 | |
| 	public static GpuTextureView outputDepthTextureOverride;
 | |
| 	@Nullable
 | |
| 	private static GpuBuffer globalSettingsUniform;
 | |
| 	@Nullable
 | |
| 	private static DynamicUniforms dynamicUniforms;
 | |
| 	private static ScissorState scissorStateForRenderTypeDraws = new ScissorState();
 | |
| 
 | |
| 	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 void assertOnRenderThread() {
 | |
| 		if (!isOnRenderThread()) {
 | |
| 			throw constructThreadException();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static IllegalStateException constructThreadException() {
 | |
| 		return new IllegalStateException("Rendersystem called from wrong thread");
 | |
| 	}
 | |
| 
 | |
| 	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, @Nullable TracyFrameCapture tracyFrameCapture) {
 | |
| 		pollEvents();
 | |
| 		Tesselator.getInstance().clear();
 | |
| 		GLFW.glfwSwapBuffers(l);
 | |
| 		if (tracyFrameCapture != null) {
 | |
| 			tracyFrameCapture.endFrame();
 | |
| 		}
 | |
| 
 | |
| 		dynamicUniforms.reset();
 | |
| 		Minecraft.getInstance().levelRenderer.endFrame();
 | |
| 		pollEvents();
 | |
| 	}
 | |
| 
 | |
| 	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 setShaderFog(GpuBufferSlice gpuBufferSlice) {
 | |
| 		shaderFog = gpuBufferSlice;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuBufferSlice getShaderFog() {
 | |
| 		return shaderFog;
 | |
| 	}
 | |
| 
 | |
| 	public static void setShaderLights(GpuBufferSlice gpuBufferSlice) {
 | |
| 		shaderLightDirections = gpuBufferSlice;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuBufferSlice getShaderLights() {
 | |
| 		return shaderLightDirections;
 | |
| 	}
 | |
| 
 | |
| 	public static void lineWidth(float f) {
 | |
| 		assertOnRenderThread();
 | |
| 		shaderLineWidth = f;
 | |
| 	}
 | |
| 
 | |
| 	public static float getShaderLineWidth() {
 | |
| 		assertOnRenderThread();
 | |
| 		return shaderLineWidth;
 | |
| 	}
 | |
| 
 | |
| 	public static void enableScissorForRenderTypeDraws(int i, int j, int k, int l) {
 | |
| 		scissorStateForRenderTypeDraws.enable(i, j, k, l);
 | |
| 	}
 | |
| 
 | |
| 	public static void disableScissorForRenderTypeDraws() {
 | |
| 		scissorStateForRenderTypeDraws.disable();
 | |
| 	}
 | |
| 
 | |
| 	public static ScissorState getScissorStateForRenderTypeDraws() {
 | |
| 		return scissorStateForRenderTypeDraws;
 | |
| 	}
 | |
| 
 | |
| 	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(long l, int i, boolean bl, BiFunction<ResourceLocation, ShaderType, String> biFunction, boolean bl2) {
 | |
| 		DEVICE = new GlDevice(l, i, bl, biFunction, bl2);
 | |
| 		apiDescription = getDevice().getImplementationInformation();
 | |
| 		dynamicUniforms = new DynamicUniforms();
 | |
| 
 | |
| 		try (ByteBufferBuilder byteBufferBuilder = ByteBufferBuilder.exactlySized(DefaultVertexFormat.POSITION.getVertexSize() * 4)) {
 | |
| 			BufferBuilder bufferBuilder = new BufferBuilder(byteBufferBuilder, VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION);
 | |
| 			bufferBuilder.addVertex(0.0F, 0.0F, 0.0F);
 | |
| 			bufferBuilder.addVertex(1.0F, 0.0F, 0.0F);
 | |
| 			bufferBuilder.addVertex(1.0F, 1.0F, 0.0F);
 | |
| 			bufferBuilder.addVertex(0.0F, 1.0F, 0.0F);
 | |
| 
 | |
| 			try (MeshData meshData = bufferBuilder.buildOrThrow()) {
 | |
| 				QUAD_VERTEX_BUFFER = getDevice().createBuffer(() -> "Quad", 32, meshData.vertexBuffer());
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static void setErrorCallback(GLFWErrorCallbackI gLFWErrorCallbackI) {
 | |
| 		GLX._setGlfwErrorCallback(gLFWErrorCallbackI);
 | |
| 	}
 | |
| 
 | |
| 	public static void setupDefaultState() {
 | |
| 		modelViewStack.clear();
 | |
| 		textureMatrix.identity();
 | |
| 	}
 | |
| 
 | |
| 	public static void setupOverlayColor(@Nullable GpuTextureView gpuTextureView) {
 | |
| 		assertOnRenderThread();
 | |
| 		setShaderTexture(1, gpuTextureView);
 | |
| 	}
 | |
| 
 | |
| 	public static void teardownOverlayColor() {
 | |
| 		assertOnRenderThread();
 | |
| 		setShaderTexture(1, null);
 | |
| 	}
 | |
| 
 | |
| 	public static void setShaderTexture(int i, @Nullable GpuTextureView gpuTextureView) {
 | |
| 		assertOnRenderThread();
 | |
| 		if (i >= 0 && i < shaderTextures.length) {
 | |
| 			shaderTextures[i] = gpuTextureView;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuTextureView getShaderTexture(int i) {
 | |
| 		assertOnRenderThread();
 | |
| 		return i >= 0 && i < shaderTextures.length ? shaderTextures[i] : null;
 | |
| 	}
 | |
| 
 | |
| 	public static void setProjectionMatrix(GpuBufferSlice gpuBufferSlice, ProjectionType projectionType) {
 | |
| 		assertOnRenderThread();
 | |
| 		projectionMatrixBuffer = gpuBufferSlice;
 | |
| 		RenderSystem.projectionType = projectionType;
 | |
| 	}
 | |
| 
 | |
| 	public static void setTextureMatrix(Matrix4f matrix4f) {
 | |
| 		assertOnRenderThread();
 | |
| 		textureMatrix = new Matrix4f(matrix4f);
 | |
| 	}
 | |
| 
 | |
| 	public static void resetTextureMatrix() {
 | |
| 		assertOnRenderThread();
 | |
| 		textureMatrix.identity();
 | |
| 	}
 | |
| 
 | |
| 	public static void backupProjectionMatrix() {
 | |
| 		assertOnRenderThread();
 | |
| 		savedProjectionMatrixBuffer = projectionMatrixBuffer;
 | |
| 		savedProjectionType = projectionType;
 | |
| 	}
 | |
| 
 | |
| 	public static void restoreProjectionMatrix() {
 | |
| 		assertOnRenderThread();
 | |
| 		projectionMatrixBuffer = savedProjectionMatrixBuffer;
 | |
| 		projectionType = savedProjectionType;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuBufferSlice getProjectionMatrixBuffer() {
 | |
| 		assertOnRenderThread();
 | |
| 		return projectionMatrixBuffer;
 | |
| 	}
 | |
| 
 | |
| 	public static Matrix4f getModelViewMatrix() {
 | |
| 		assertOnRenderThread();
 | |
| 		return modelViewStack;
 | |
| 	}
 | |
| 
 | |
| 	public static Matrix4fStack getModelViewStack() {
 | |
| 		assertOnRenderThread();
 | |
| 		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 setGlobalSettingsUniform(GpuBuffer gpuBuffer) {
 | |
| 		globalSettingsUniform = gpuBuffer;
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuBuffer getGlobalSettingsUniform() {
 | |
| 		return globalSettingsUniform;
 | |
| 	}
 | |
| 
 | |
| 	public static ProjectionType getProjectionType() {
 | |
| 		assertOnRenderThread();
 | |
| 		return projectionType;
 | |
| 	}
 | |
| 
 | |
| 	public static GpuBuffer getQuadVertexBuffer() {
 | |
| 		if (QUAD_VERTEX_BUFFER == null) {
 | |
| 			throw new IllegalStateException("Can't getQuadVertexBuffer() before renderer was initialized");
 | |
| 		} else {
 | |
| 			return QUAD_VERTEX_BUFFER;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static void setModelOffset(float f, float g, float h) {
 | |
| 		assertOnRenderThread();
 | |
| 		modelOffset.set(f, g, h);
 | |
| 	}
 | |
| 
 | |
| 	public static void resetModelOffset() {
 | |
| 		assertOnRenderThread();
 | |
| 		modelOffset.set(0.0F, 0.0F, 0.0F);
 | |
| 	}
 | |
| 
 | |
| 	public static Vector3f getModelOffset() {
 | |
| 		assertOnRenderThread();
 | |
| 		return modelOffset;
 | |
| 	}
 | |
| 
 | |
| 	public static void queueFencedTask(Runnable runnable) {
 | |
| 		PENDING_FENCES.addLast(new RenderSystem.GpuAsyncTask(runnable, getDevice().createCommandEncoder().createFence()));
 | |
| 	}
 | |
| 
 | |
| 	public static void executePendingTasks() {
 | |
| 		for (RenderSystem.GpuAsyncTask gpuAsyncTask = PENDING_FENCES.peekFirst(); gpuAsyncTask != null; gpuAsyncTask = PENDING_FENCES.peekFirst()) {
 | |
| 			if (!gpuAsyncTask.fence.awaitCompletion(0L)) {
 | |
| 				return;
 | |
| 			}
 | |
| 
 | |
| 			try {
 | |
| 				gpuAsyncTask.callback.run();
 | |
| 			} finally {
 | |
| 				gpuAsyncTask.fence.close();
 | |
| 			}
 | |
| 
 | |
| 			PENDING_FENCES.removeFirst();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static GpuDevice getDevice() {
 | |
| 		if (DEVICE == null) {
 | |
| 			throw new IllegalStateException("Can't getDevice() before it was initialized");
 | |
| 		} else {
 | |
| 			return DEVICE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static GpuDevice tryGetDevice() {
 | |
| 		return DEVICE;
 | |
| 	}
 | |
| 
 | |
| 	public static DynamicUniforms getDynamicUniforms() {
 | |
| 		if (dynamicUniforms == null) {
 | |
| 			throw new IllegalStateException("Can't getDynamicUniforms() before device was initialized");
 | |
| 		} else {
 | |
| 			return dynamicUniforms;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public static void bindDefaultUniforms(RenderPass renderPass) {
 | |
| 		GpuBufferSlice gpuBufferSlice = getProjectionMatrixBuffer();
 | |
| 		if (gpuBufferSlice != null) {
 | |
| 			renderPass.setUniform("Projection", gpuBufferSlice);
 | |
| 		}
 | |
| 
 | |
| 		GpuBufferSlice gpuBufferSlice2 = getShaderFog();
 | |
| 		if (gpuBufferSlice2 != null) {
 | |
| 			renderPass.setUniform("Fog", gpuBufferSlice2);
 | |
| 		}
 | |
| 
 | |
| 		GpuBuffer gpuBuffer = getGlobalSettingsUniform();
 | |
| 		if (gpuBuffer != null) {
 | |
| 			renderPass.setUniform("Globals", gpuBuffer);
 | |
| 		}
 | |
| 
 | |
| 		GpuBufferSlice gpuBufferSlice3 = getShaderLights();
 | |
| 		if (gpuBufferSlice3 != null) {
 | |
| 			renderPass.setUniform("Lighting", gpuBufferSlice3);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	public static final class AutoStorageIndexBuffer {
 | |
| 		private final int vertexStride;
 | |
| 		private final int indexStride;
 | |
| 		private final RenderSystem.AutoStorageIndexBuffer.IndexGenerator generator;
 | |
| 		@Nullable
 | |
| 		private GpuBuffer buffer;
 | |
| 		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 GpuBuffer getBuffer(int index) {
 | |
| 			this.ensureStorage(index);
 | |
| 			return this.buffer;
 | |
| 		}
 | |
| 
 | |
| 		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);
 | |
| 				ByteBuffer byteBuffer = MemoryUtil.memAlloc(k);
 | |
| 
 | |
| 				try {
 | |
| 					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);
 | |
| 					}
 | |
| 
 | |
| 					byteBuffer.flip();
 | |
| 					if (this.buffer != null) {
 | |
| 						this.buffer.close();
 | |
| 					}
 | |
| 
 | |
| 					this.buffer = RenderSystem.getDevice().createBuffer(() -> "Auto Storage index buffer", 64, byteBuffer);
 | |
| 				} finally {
 | |
| 					MemoryUtil.memFree(byteBuffer);
 | |
| 				}
 | |
| 
 | |
| 				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);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	record GpuAsyncTask(Runnable callback, GpuFence fence) {
 | |
| 	}
 | |
| }
 |