minecraft-src/com/mojang/blaze3d/opengl/GlDevice.java
2025-07-04 03:45:38 +03:00

312 lines
12 KiB
Java

package com.mojang.blaze3d.opengl;
import com.mojang.blaze3d.GpuOutOfMemoryException;
import com.mojang.blaze3d.buffers.BufferType;
import com.mojang.blaze3d.buffers.BufferUsage;
import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.preprocessor.GlslPreprocessor;
import com.mojang.blaze3d.shaders.ShaderType;
import com.mojang.blaze3d.systems.CommandEncoder;
import com.mojang.blaze3d.systems.GpuDevice;
import com.mojang.blaze3d.textures.GpuTexture;
import com.mojang.blaze3d.textures.TextureFormat;
import com.mojang.logging.LogUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.renderer.ShaderDefines;
import net.minecraft.client.renderer.ShaderManager;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class GlDevice implements GpuDevice {
private static final Logger LOGGER = LogUtils.getLogger();
protected static boolean USE_GL_ARB_vertex_attrib_binding = true;
protected static boolean USE_GL_KHR_debug = true;
protected static boolean USE_GL_EXT_debug_label = true;
protected static boolean USE_GL_ARB_debug_output = true;
protected static boolean USE_GL_ARB_direct_state_access = true;
private final CommandEncoder encoder;
@Nullable
private final GlDebug debugLog;
private final GlDebugLabel debugLabels;
private final int maxSupportedTextureSize;
private final DirectStateAccess directStateAccess;
private final BiFunction<ResourceLocation, ShaderType, String> defaultShaderSource;
private final Map<RenderPipeline, GlRenderPipeline> pipelineCache = new IdentityHashMap();
private final Map<GlDevice.ShaderCompilationKey, GlShaderModule> shaderCache = new HashMap();
private final VertexArrayCache vertexArrayCache;
private final Set<String> enabledExtensions = new HashSet();
public GlDevice(
long window, int debugVerbosity, boolean synchronous, BiFunction<ResourceLocation, ShaderType, String> defaultShaderSource, boolean renderDebugLabels
) {
GLFW.glfwMakeContextCurrent(window);
GLCapabilities gLCapabilities = GL.createCapabilities();
int i = getMaxSupportedTextureSize();
GLFW.glfwSetWindowSizeLimits(window, -1, -1, i, i);
this.debugLog = GlDebug.enableDebugCallback(debugVerbosity, synchronous, this.enabledExtensions);
this.debugLabels = GlDebugLabel.create(gLCapabilities, renderDebugLabels, this.enabledExtensions);
this.vertexArrayCache = VertexArrayCache.create(gLCapabilities, this.debugLabels, this.enabledExtensions);
this.directStateAccess = DirectStateAccess.create(gLCapabilities, this.enabledExtensions);
this.maxSupportedTextureSize = i;
this.defaultShaderSource = defaultShaderSource;
this.encoder = new GlCommandEncoder(this);
}
public GlDebugLabel debugLabels() {
return this.debugLabels;
}
@Override
public CommandEncoder createCommandEncoder() {
return this.encoder;
}
@Override
public GpuTexture createTexture(@Nullable Supplier<String> supplier, TextureFormat textureFormat, int i, int j, int k) {
return this.createTexture(this.debugLabels.exists() && supplier != null ? (String)supplier.get() : null, textureFormat, i, j, k);
}
@Override
public GpuTexture createTexture(@Nullable String string, TextureFormat textureFormat, int i, int j, int k) {
if (k < 1) {
throw new IllegalArgumentException("mipLevels must be at least 1");
} else {
GlStateManager.clearGlErrors();
int l = GlStateManager._genTexture();
if (string == null) {
string = String.valueOf(l);
}
GlStateManager._bindTexture(l);
GlStateManager._texParameter(3553, 33085, k - 1);
GlStateManager._texParameter(3553, 33082, 0);
GlStateManager._texParameter(3553, 33083, k - 1);
if (textureFormat.hasDepthAspect()) {
GlStateManager._texParameter(3553, 34892, 0);
}
for (int m = 0; m < k; m++) {
GlStateManager._texImage2D(
3553, m, GlConst.toGlInternalId(textureFormat), i >> m, j >> m, 0, GlConst.toGlExternalId(textureFormat), GlConst.toGlType(textureFormat), null
);
}
int m = GlStateManager._getError();
if (m == 1285) {
throw new GpuOutOfMemoryException("Could not allocate texture of " + i + "x" + j + " for " + string);
} else if (m != 0) {
throw new IllegalStateException("OpenGL error " + m);
} else {
GlTexture glTexture = new GlTexture(string, textureFormat, i, j, k, l);
this.debugLabels.applyLabel(glTexture);
return glTexture;
}
}
}
@Override
public GpuBuffer createBuffer(@Nullable Supplier<String> supplier, BufferType bufferType, BufferUsage bufferUsage, int i) {
if (i <= 0) {
throw new IllegalArgumentException("Buffer size must be greater than zero");
} else {
return new GlBuffer(this.debugLabels, supplier, bufferType, bufferUsage, i, GlStateManager._glGenBuffers());
}
}
@Override
public GpuBuffer createBuffer(@Nullable Supplier<String> supplier, BufferType bufferType, BufferUsage bufferUsage, ByteBuffer byteBuffer) {
if (!byteBuffer.hasRemaining()) {
throw new IllegalArgumentException("Buffer source must not be empty");
} else {
GlBuffer glBuffer = new GlBuffer(this.debugLabels, supplier, bufferType, bufferUsage, byteBuffer.remaining(), GlStateManager._glGenBuffers());
this.encoder.writeToBuffer(glBuffer, byteBuffer, 0);
return glBuffer;
}
}
@Override
public String getImplementationInformation() {
return GLFW.glfwGetCurrentContext() == 0L
? "NO CONTEXT"
: GlStateManager._getString(7937) + " GL version " + GlStateManager._getString(7938) + ", " + GlStateManager._getString(7936);
}
@Override
public List<String> getLastDebugMessages() {
return this.debugLog == null ? Collections.emptyList() : this.debugLog.getLastOpenGlDebugMessages();
}
@Override
public boolean isDebuggingEnabled() {
return this.debugLog != null;
}
@Override
public String getRenderer() {
return GlStateManager._getString(7937);
}
@Override
public String getVendor() {
return GlStateManager._getString(7936);
}
@Override
public String getBackendName() {
return "OpenGL";
}
@Override
public String getVersion() {
return GlStateManager._getString(7938);
}
private static int getMaxSupportedTextureSize() {
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) {
return j;
}
}
int jx = Math.max(i, 1024);
LOGGER.info("Failed to determine maximum texture size by probing, trying GL_MAX_TEXTURE_SIZE = {}", jx);
return jx;
}
@Override
public int getMaxTextureSize() {
return this.maxSupportedTextureSize;
}
@Override
public void clearPipelineCache() {
for (GlRenderPipeline glRenderPipeline : this.pipelineCache.values()) {
if (glRenderPipeline.program() != GlProgram.INVALID_PROGRAM) {
glRenderPipeline.program().close();
}
}
this.pipelineCache.clear();
for (GlShaderModule glShaderModule : this.shaderCache.values()) {
if (glShaderModule != GlShaderModule.INVALID_SHADER) {
glShaderModule.close();
}
}
this.shaderCache.clear();
}
@Override
public List<String> getEnabledExtensions() {
return new ArrayList(this.enabledExtensions);
}
@Override
public void close() {
this.clearPipelineCache();
}
public DirectStateAccess directStateAccess() {
return this.directStateAccess;
}
protected GlRenderPipeline getOrCompilePipeline(RenderPipeline pipeline) {
return (GlRenderPipeline)this.pipelineCache.computeIfAbsent(pipeline, renderPipeline2 -> this.compilePipeline(pipeline, this.defaultShaderSource));
}
protected GlShaderModule getOrCompileShader(
ResourceLocation shader, ShaderType type, ShaderDefines defines, BiFunction<ResourceLocation, ShaderType, String> shaderSource
) {
GlDevice.ShaderCompilationKey shaderCompilationKey = new GlDevice.ShaderCompilationKey(shader, type, defines);
return (GlShaderModule)this.shaderCache
.computeIfAbsent(shaderCompilationKey, shaderCompilationKey2 -> this.compileShader(shaderCompilationKey, shaderSource));
}
public GlRenderPipeline precompilePipeline(RenderPipeline renderPipeline, @Nullable BiFunction<ResourceLocation, ShaderType, String> biFunction) {
BiFunction<ResourceLocation, ShaderType, String> biFunction2 = biFunction == null ? this.defaultShaderSource : biFunction;
return (GlRenderPipeline)this.pipelineCache.computeIfAbsent(renderPipeline, renderPipeline2 -> this.compilePipeline(renderPipeline, biFunction2));
}
private GlShaderModule compileShader(GlDevice.ShaderCompilationKey key, BiFunction<ResourceLocation, ShaderType, String> shaderSource) {
String string = (String)shaderSource.apply(key.id, key.type);
if (string == null) {
LOGGER.error("Couldn't find source for {} shader ({})", key.type, key.id);
return GlShaderModule.INVALID_SHADER;
} else {
String string2 = GlslPreprocessor.injectDefines(string, key.defines);
int i = GlStateManager.glCreateShader(GlConst.toGl(key.type));
GlStateManager.glShaderSource(i, string2);
GlStateManager.glCompileShader(i);
if (GlStateManager.glGetShaderi(i, 35713) == 0) {
String string3 = StringUtils.trim(GlStateManager.glGetShaderInfoLog(i, 32768));
LOGGER.error("Couldn't compile {} shader ({}): {}", key.type.getName(), key.id, string3);
return GlShaderModule.INVALID_SHADER;
} else {
GlShaderModule glShaderModule = new GlShaderModule(i, key.id, key.type);
this.debugLabels.applyLabel(glShaderModule);
return glShaderModule;
}
}
}
private GlRenderPipeline compilePipeline(RenderPipeline pipeline, BiFunction<ResourceLocation, ShaderType, String> shaderSource) {
GlShaderModule glShaderModule = this.getOrCompileShader(pipeline.getVertexShader(), ShaderType.VERTEX, pipeline.getShaderDefines(), shaderSource);
GlShaderModule glShaderModule2 = this.getOrCompileShader(pipeline.getFragmentShader(), ShaderType.FRAGMENT, pipeline.getShaderDefines(), shaderSource);
if (glShaderModule == GlShaderModule.INVALID_SHADER) {
LOGGER.error("Couldn't compile pipeline {}: vertex shader {} was invalid", pipeline.getLocation(), pipeline.getVertexShader());
return new GlRenderPipeline(pipeline, GlProgram.INVALID_PROGRAM);
} else if (glShaderModule2 == GlShaderModule.INVALID_SHADER) {
LOGGER.error("Couldn't compile pipeline {}: fragment shader {} was invalid", pipeline.getLocation(), pipeline.getFragmentShader());
return new GlRenderPipeline(pipeline, GlProgram.INVALID_PROGRAM);
} else {
GlProgram glProgram;
try {
glProgram = GlProgram.link(glShaderModule, glShaderModule2, pipeline.getVertexFormat(), pipeline.getLocation().toString());
} catch (ShaderManager.CompilationException var7) {
LOGGER.error("Couldn't compile program for pipeline {}: {}", pipeline.getLocation(), var7);
return new GlRenderPipeline(pipeline, GlProgram.INVALID_PROGRAM);
}
glProgram.setupUniforms(pipeline.getUniforms(), pipeline.getSamplers());
this.debugLabels.applyLabel(glProgram);
return new GlRenderPipeline(pipeline, glProgram);
}
}
public VertexArrayCache vertexArrayCache() {
return this.vertexArrayCache;
}
@Environment(EnvType.CLIENT)
record ShaderCompilationKey(ResourceLocation id, ShaderType type, ShaderDefines defines) {
public String toString() {
String string = this.id + " (" + this.type + ")";
return !this.defines.isEmpty() ? string + " with " + this.defines : string;
}
}
}