312 lines
12 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|