/* * Copyright (C) 2022 NotEnoughUpdates contributors * * This file is part of NotEnoughUpdates. * * NotEnoughUpdates is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * NotEnoughUpdates is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with NotEnoughUpdates. If not, see . */ package io.github.moulberry.notenoughupdates.cosmetics; import net.minecraft.client.Minecraft; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.util.ResourceLocation; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL43; import org.lwjgl.util.vector.Vector2f; import org.lwjgl.util.vector.Vector3f; import org.lwjgl.util.vector.Vector4f; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; public class ShaderManager implements IResourceManagerReloadListener { private final ResourceLocation shaderLocation = new ResourceLocation("notenoughupdates:shaders"); private final HashMap shaderMap = new HashMap<>(); private static final ShaderManager INSTANCE = new ShaderManager(); public static ShaderManager getInstance() { return INSTANCE; } @Override public void onResourceManagerReload(IResourceManager iResourceManager) { shaderMap.values().forEach(it -> { GL20.glDeleteProgram(it.program); }); shaderMap.clear(); } public static class Shader { public final int program; public Shader(int program) { this.program = program; } } public int getShader(String name) { if (!shaderMap.containsKey(name)) { reloadShader(name); } return shaderMap.get(name).program; } public int loadShader(String name) { if (!shaderMap.containsKey(name)) { reloadShader(name); } GL20.glUseProgram(shaderMap.get(name).program); return shaderMap.get(name).program; } public void loadData(String name, String var, Object value) { int location = GL20.glGetUniformLocation(shaderMap.get(name).program, var); if (value instanceof Integer) { GL20.glUniform1i(location, (Integer) value); } else if (value instanceof Float) { GL20.glUniform1f(location, (Float) value); } else if (value instanceof Vector2f) { Vector2f vec = (Vector2f) value; GL20.glUniform2f(location, vec.x, vec.y); } else if (value instanceof Vector3f) { Vector3f vec = (Vector3f) value; GL20.glUniform3f(location, vec.x, vec.y, vec.z); } else if (value instanceof Vector4f) { Vector4f vec = (Vector4f) value; GL20.glUniform4f(location, vec.x, vec.y, vec.z, vec.w); } else { throw new UnsupportedOperationException("Failed to load data into shader: Unsupported data type."); } } private void reloadShader(String name) { int vertex = -1; String sourceVert = getShaderSource(name, GL20.GL_VERTEX_SHADER); if (!sourceVert.isEmpty()) { vertex = GL20.glCreateShader(GL20.GL_VERTEX_SHADER); GL20.glShaderSource(vertex, sourceVert); GL20.glCompileShader(vertex); if (GL20.glGetShaderi(vertex, 35713) == 0) { System.err.println(GL20.glGetShaderInfoLog(vertex, 100)); } } int fragment = -1; String sourceFrag = getShaderSource(name, GL20.GL_FRAGMENT_SHADER); if (!sourceFrag.isEmpty()) { fragment = GL20.glCreateShader(GL20.GL_FRAGMENT_SHADER); GL20.glShaderSource(fragment, sourceFrag); GL20.glCompileShader(fragment); if (GL20.glGetShaderi(fragment, 35713) == 0) { System.err.println(GL20.glGetShaderInfoLog(fragment, 100)); } } int compute = -1; String sourceCompute = getShaderSource(name, GL43.GL_COMPUTE_SHADER); if (!sourceCompute.isEmpty()) { compute = GL20.glCreateShader(GL43.GL_COMPUTE_SHADER); GL20.glShaderSource(compute, sourceCompute); GL20.glCompileShader(compute); if (GL20.glGetShaderi(compute, 35713) == 0) { System.err.println(GL20.glGetShaderInfoLog(compute, 100)); } } int program = GL20.glCreateProgram(); if (vertex != -1) GL20.glAttachShader(program, vertex); if (fragment != -1) GL20.glAttachShader(program, fragment); if (compute != -1) GL20.glAttachShader(program, compute); GL20.glLinkProgram(program); if (vertex != -1) GL20.glDeleteShader(vertex); if (fragment != -1) GL20.glDeleteShader(fragment); if (compute != -1) GL20.glDeleteShader(compute); if (GL20.glGetProgrami(program, 35714) == 0) { System.err.println(GL20.glGetProgramInfoLog(program, 100)); } GL20.glValidateProgram(program); if (GL20.glGetProgrami(program, 35715) == 0) { System.err.println(GL20.glGetProgramInfoLog(program, 100)); } shaderMap.put(name, new Shader(program)); } public String getShaderSource(String name, int type) { String ext = ""; if (type == GL20.GL_VERTEX_SHADER) { ext = ".vert"; } else if (type == GL20.GL_FRAGMENT_SHADER) { ext = ".frag"; } else if (type == GL43.GL_COMPUTE_SHADER) { ext = ".compute"; } else { return ""; } ResourceLocation location = new ResourceLocation( shaderLocation.getResourceDomain(), shaderLocation.getResourcePath() + "/" + name + ext ); try (InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(location).getInputStream()) { StringBuilder source = new StringBuilder(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String line; while ((line = br.readLine()) != null) { source.append(line).append("\n"); } return source.toString(); } catch (IOException ignored) { } return ""; } }