minecraft-src/net/minecraft/client/renderer/ShaderInstance.java
2025-07-04 01:41:11 +03:00

528 lines
16 KiB
Java

package net.minecraft.client.renderer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.preprocessor.GlslPreprocessor;
import com.mojang.blaze3d.shaders.AbstractUniform;
import com.mojang.blaze3d.shaders.Program;
import com.mojang.blaze3d.shaders.ProgramManager;
import com.mojang.blaze3d.shaders.Shader;
import com.mojang.blaze3d.shaders.Uniform;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.FileUtil;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.ChainedJsonException;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceProvider;
import net.minecraft.util.GsonHelper;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class ShaderInstance implements Shader, AutoCloseable {
public static final String SHADER_PATH = "shaders";
private static final String SHADER_CORE_PATH = "shaders/core/";
private static final String SHADER_INCLUDE_PATH = "shaders/include/";
static final Logger LOGGER = LogUtils.getLogger();
private static final AbstractUniform DUMMY_UNIFORM = new AbstractUniform();
private static final boolean ALWAYS_REAPPLY = true;
private static ShaderInstance lastAppliedShader;
private static int lastProgramId = -1;
private final Map<String, Object> samplerMap = Maps.<String, Object>newHashMap();
private final List<String> samplerNames = Lists.<String>newArrayList();
private final List<Integer> samplerLocations = Lists.<Integer>newArrayList();
private final List<Uniform> uniforms = Lists.<Uniform>newArrayList();
private final List<Integer> uniformLocations = Lists.<Integer>newArrayList();
private final Map<String, Uniform> uniformMap = Maps.<String, Uniform>newHashMap();
private final int programId;
private final String name;
private boolean dirty;
private final Program vertexProgram;
private final Program fragmentProgram;
private final VertexFormat vertexFormat;
@Nullable
public final Uniform MODEL_VIEW_MATRIX;
@Nullable
public final Uniform PROJECTION_MATRIX;
@Nullable
public final Uniform TEXTURE_MATRIX;
@Nullable
public final Uniform SCREEN_SIZE;
@Nullable
public final Uniform COLOR_MODULATOR;
@Nullable
public final Uniform LIGHT0_DIRECTION;
@Nullable
public final Uniform LIGHT1_DIRECTION;
@Nullable
public final Uniform GLINT_ALPHA;
@Nullable
public final Uniform FOG_START;
@Nullable
public final Uniform FOG_END;
@Nullable
public final Uniform FOG_COLOR;
@Nullable
public final Uniform FOG_SHAPE;
@Nullable
public final Uniform LINE_WIDTH;
@Nullable
public final Uniform GAME_TIME;
@Nullable
public final Uniform CHUNK_OFFSET;
public ShaderInstance(ResourceProvider resourceProvider, String name, VertexFormat vertexFormat) throws IOException {
this.name = name;
this.vertexFormat = vertexFormat;
ResourceLocation resourceLocation = ResourceLocation.withDefaultNamespace("shaders/core/" + name + ".json");
try {
Reader reader = resourceProvider.openAsReader(resourceLocation);
try {
JsonObject jsonObject = GsonHelper.parse(reader);
String string = GsonHelper.getAsString(jsonObject, "vertex");
String string2 = GsonHelper.getAsString(jsonObject, "fragment");
JsonArray jsonArray = GsonHelper.getAsJsonArray(jsonObject, "samplers", null);
if (jsonArray != null) {
int i = 0;
for (JsonElement jsonElement : jsonArray) {
try {
this.parseSamplerNode(jsonElement);
} catch (Exception var18) {
ChainedJsonException chainedJsonException = ChainedJsonException.forException(var18);
chainedJsonException.prependJsonKey("samplers[" + i + "]");
throw chainedJsonException;
}
i++;
}
}
JsonArray jsonArray2 = GsonHelper.getAsJsonArray(jsonObject, "uniforms", null);
if (jsonArray2 != null) {
int j = 0;
for (JsonElement jsonElement2 : jsonArray2) {
try {
this.parseUniformNode(jsonElement2);
} catch (Exception var17) {
ChainedJsonException chainedJsonException2 = ChainedJsonException.forException(var17);
chainedJsonException2.prependJsonKey("uniforms[" + j + "]");
throw chainedJsonException2;
}
j++;
}
}
this.vertexProgram = getOrCreate(resourceProvider, Program.Type.VERTEX, string);
this.fragmentProgram = getOrCreate(resourceProvider, Program.Type.FRAGMENT, string2);
this.programId = ProgramManager.createProgram();
int j = 0;
for (String string3 : vertexFormat.getElementAttributeNames()) {
Uniform.glBindAttribLocation(this.programId, j, string3);
j++;
}
ProgramManager.linkShader(this);
this.updateLocations();
} catch (Throwable var19) {
if (reader != null) {
try {
reader.close();
} catch (Throwable var16) {
var19.addSuppressed(var16);
}
}
throw var19;
}
if (reader != null) {
reader.close();
}
} catch (Exception var20) {
ChainedJsonException chainedJsonException3 = ChainedJsonException.forException(var20);
chainedJsonException3.setFilenameAndFlush(resourceLocation.getPath());
throw chainedJsonException3;
}
this.markDirty();
this.MODEL_VIEW_MATRIX = this.getUniform("ModelViewMat");
this.PROJECTION_MATRIX = this.getUniform("ProjMat");
this.TEXTURE_MATRIX = this.getUniform("TextureMat");
this.SCREEN_SIZE = this.getUniform("ScreenSize");
this.COLOR_MODULATOR = this.getUniform("ColorModulator");
this.LIGHT0_DIRECTION = this.getUniform("Light0_Direction");
this.LIGHT1_DIRECTION = this.getUniform("Light1_Direction");
this.GLINT_ALPHA = this.getUniform("GlintAlpha");
this.FOG_START = this.getUniform("FogStart");
this.FOG_END = this.getUniform("FogEnd");
this.FOG_COLOR = this.getUniform("FogColor");
this.FOG_SHAPE = this.getUniform("FogShape");
this.LINE_WIDTH = this.getUniform("LineWidth");
this.GAME_TIME = this.getUniform("GameTime");
this.CHUNK_OFFSET = this.getUniform("ChunkOffset");
}
private static Program getOrCreate(ResourceProvider resourceProvider, Program.Type programType, String name) throws IOException {
Program program = (Program)programType.getPrograms().get(name);
Program program2;
if (program == null) {
String string = "shaders/core/" + name + programType.getExtension();
Resource resource = resourceProvider.getResourceOrThrow(ResourceLocation.withDefaultNamespace(string));
InputStream inputStream = resource.open();
try {
final String string2 = FileUtil.getFullResourcePath(string);
program2 = Program.compileShader(programType, name, inputStream, resource.sourcePackId(), new GlslPreprocessor() {
private final Set<String> importedPaths = Sets.<String>newHashSet();
@Override
public String applyImport(boolean useFullPath, String directory) {
directory = FileUtil.normalizeResourcePath((useFullPath ? string2 : "shaders/include/") + directory);
if (!this.importedPaths.add(directory)) {
return null;
} else {
ResourceLocation resourceLocation = ResourceLocation.parse(directory);
try {
Reader reader = resourceProvider.openAsReader(resourceLocation);
String var5;
try {
var5 = IOUtils.toString(reader);
} catch (Throwable var8) {
if (reader != null) {
try {
reader.close();
} catch (Throwable var7) {
var8.addSuppressed(var7);
}
}
throw var8;
}
if (reader != null) {
reader.close();
}
return var5;
} catch (IOException var9) {
ShaderInstance.LOGGER.error("Could not open GLSL import {}: {}", directory, var9.getMessage());
return "#error " + var9.getMessage();
}
}
}
});
} catch (Throwable var11) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var10) {
var11.addSuppressed(var10);
}
}
throw var11;
}
if (inputStream != null) {
inputStream.close();
}
} else {
program2 = program;
}
return program2;
}
public void close() {
for (Uniform uniform : this.uniforms) {
uniform.close();
}
ProgramManager.releaseProgram(this);
}
public void clear() {
RenderSystem.assertOnRenderThread();
ProgramManager.glUseProgram(0);
lastProgramId = -1;
lastAppliedShader = null;
int i = GlStateManager._getActiveTexture();
for (int j = 0; j < this.samplerLocations.size(); j++) {
if (this.samplerMap.get(this.samplerNames.get(j)) != null) {
GlStateManager._activeTexture(33984 + j);
GlStateManager._bindTexture(0);
}
}
GlStateManager._activeTexture(i);
}
public void apply() {
RenderSystem.assertOnRenderThread();
this.dirty = false;
lastAppliedShader = this;
if (this.programId != lastProgramId) {
ProgramManager.glUseProgram(this.programId);
lastProgramId = this.programId;
}
int i = GlStateManager._getActiveTexture();
for (int j = 0; j < this.samplerLocations.size(); j++) {
String string = (String)this.samplerNames.get(j);
if (this.samplerMap.get(string) != null) {
int k = Uniform.glGetUniformLocation(this.programId, string);
Uniform.uploadInteger(k, j);
RenderSystem.activeTexture(33984 + j);
Object object = this.samplerMap.get(string);
int l = -1;
if (object instanceof RenderTarget) {
l = ((RenderTarget)object).getColorTextureId();
} else if (object instanceof AbstractTexture) {
l = ((AbstractTexture)object).getId();
} else if (object instanceof Integer) {
l = (Integer)object;
}
if (l != -1) {
RenderSystem.bindTexture(l);
}
}
}
GlStateManager._activeTexture(i);
for (Uniform uniform : this.uniforms) {
uniform.upload();
}
}
@Override
public void markDirty() {
this.dirty = true;
}
@Nullable
public Uniform getUniform(String name) {
RenderSystem.assertOnRenderThread();
return (Uniform)this.uniformMap.get(name);
}
public AbstractUniform safeGetUniform(String name) {
Uniform uniform = this.getUniform(name);
return (AbstractUniform)(uniform == null ? DUMMY_UNIFORM : uniform);
}
private void updateLocations() {
RenderSystem.assertOnRenderThread();
IntList intList = new IntArrayList();
for (int i = 0; i < this.samplerNames.size(); i++) {
String string = (String)this.samplerNames.get(i);
int j = Uniform.glGetUniformLocation(this.programId, string);
if (j == -1) {
LOGGER.warn("Shader {} could not find sampler named {} in the specified shader program.", this.name, string);
this.samplerMap.remove(string);
intList.add(i);
} else {
this.samplerLocations.add(j);
}
}
for (int ix = intList.size() - 1; ix >= 0; ix--) {
int k = intList.getInt(ix);
this.samplerNames.remove(k);
}
for (Uniform uniform : this.uniforms) {
String string2 = uniform.getName();
int l = Uniform.glGetUniformLocation(this.programId, string2);
if (l == -1) {
LOGGER.warn("Shader {} could not find uniform named {} in the specified shader program.", this.name, string2);
} else {
this.uniformLocations.add(l);
uniform.setLocation(l);
this.uniformMap.put(string2, uniform);
}
}
}
private void parseSamplerNode(JsonElement json) {
JsonObject jsonObject = GsonHelper.convertToJsonObject(json, "sampler");
String string = GsonHelper.getAsString(jsonObject, "name");
if (!GsonHelper.isStringValue(jsonObject, "file")) {
this.samplerMap.put(string, null);
this.samplerNames.add(string);
} else {
this.samplerNames.add(string);
}
}
public void setSampler(String name, Object textureId) {
this.samplerMap.put(name, textureId);
this.markDirty();
}
private void parseUniformNode(JsonElement json) throws ChainedJsonException {
JsonObject jsonObject = GsonHelper.convertToJsonObject(json, "uniform");
String string = GsonHelper.getAsString(jsonObject, "name");
int i = Uniform.getTypeFromString(GsonHelper.getAsString(jsonObject, "type"));
int j = GsonHelper.getAsInt(jsonObject, "count");
float[] fs = new float[Math.max(j, 16)];
JsonArray jsonArray = GsonHelper.getAsJsonArray(jsonObject, "values");
if (jsonArray.size() != j && jsonArray.size() > 1) {
throw new ChainedJsonException("Invalid amount of values specified (expected " + j + ", found " + jsonArray.size() + ")");
} else {
int k = 0;
for (JsonElement jsonElement : jsonArray) {
try {
fs[k] = GsonHelper.convertToFloat(jsonElement, "value");
} catch (Exception var13) {
ChainedJsonException chainedJsonException = ChainedJsonException.forException(var13);
chainedJsonException.prependJsonKey("values[" + k + "]");
throw chainedJsonException;
}
k++;
}
if (j > 1 && jsonArray.size() == 1) {
while (k < j) {
fs[k] = fs[0];
k++;
}
}
int l = j > 1 && j <= 4 && i < 8 ? j - 1 : 0;
Uniform uniform = new Uniform(string, i + l, j, this);
if (i <= 3) {
uniform.setSafe((int)fs[0], (int)fs[1], (int)fs[2], (int)fs[3]);
} else if (i <= 7) {
uniform.setSafe(fs[0], fs[1], fs[2], fs[3]);
} else {
uniform.set(Arrays.copyOfRange(fs, 0, j));
}
this.uniforms.add(uniform);
}
}
@Override
public Program getVertexProgram() {
return this.vertexProgram;
}
@Override
public Program getFragmentProgram() {
return this.fragmentProgram;
}
@Override
public void attachToProgram() {
this.fragmentProgram.attachToShader(this);
this.vertexProgram.attachToShader(this);
}
public VertexFormat getVertexFormat() {
return this.vertexFormat;
}
public String getName() {
return this.name;
}
@Override
public int getId() {
return this.programId;
}
public void setDefaultUniforms(VertexFormat.Mode mode, Matrix4f frustumMatrix, Matrix4f projectionMatrix, Window window) {
for (int i = 0; i < 12; i++) {
int j = RenderSystem.getShaderTexture(i);
this.setSampler("Sampler" + i, j);
}
if (this.MODEL_VIEW_MATRIX != null) {
this.MODEL_VIEW_MATRIX.set(frustumMatrix);
}
if (this.PROJECTION_MATRIX != null) {
this.PROJECTION_MATRIX.set(projectionMatrix);
}
if (this.COLOR_MODULATOR != null) {
this.COLOR_MODULATOR.set(RenderSystem.getShaderColor());
}
if (this.GLINT_ALPHA != null) {
this.GLINT_ALPHA.set(RenderSystem.getShaderGlintAlpha());
}
if (this.FOG_START != null) {
this.FOG_START.set(RenderSystem.getShaderFogStart());
}
if (this.FOG_END != null) {
this.FOG_END.set(RenderSystem.getShaderFogEnd());
}
if (this.FOG_COLOR != null) {
this.FOG_COLOR.set(RenderSystem.getShaderFogColor());
}
if (this.FOG_SHAPE != null) {
this.FOG_SHAPE.set(RenderSystem.getShaderFogShape().getIndex());
}
if (this.TEXTURE_MATRIX != null) {
this.TEXTURE_MATRIX.set(RenderSystem.getTextureMatrix());
}
if (this.GAME_TIME != null) {
this.GAME_TIME.set(RenderSystem.getShaderGameTime());
}
if (this.SCREEN_SIZE != null) {
this.SCREEN_SIZE.set((float)window.getWidth(), (float)window.getHeight());
}
if (this.LINE_WIDTH != null && (mode == VertexFormat.Mode.LINES || mode == VertexFormat.Mode.LINE_STRIP)) {
this.LINE_WIDTH.set(RenderSystem.getShaderLineWidth());
}
RenderSystem.setupShaderLights(this);
}
}