minecraft-src/com/mojang/blaze3d/systems/RenderSystem.java
2025-09-18 12:27:44 +00:00

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) {
}
}