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 defaultShaderSource; private final Map pipelineCache = new IdentityHashMap(); private final Map shaderCache = new HashMap(); private final VertexArrayCache vertexArrayCache; private final Set enabledExtensions = new HashSet(); public GlDevice( long window, int debugVerbosity, boolean synchronous, BiFunction 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 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 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 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 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 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 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 biFunction) { BiFunction biFunction2 = biFunction == null ? this.defaultShaderSource : biFunction; return (GlRenderPipeline)this.pipelineCache.computeIfAbsent(renderPipeline, renderPipeline2 -> this.compilePipeline(renderPipeline, biFunction2)); } private GlShaderModule compileShader(GlDevice.ShaderCompilationKey key, BiFunction 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 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; } } }