This commit is contained in:
JozsefA 2021-06-19 00:29:18 -07:00
parent d612cfdd34
commit 652111c233
184 changed files with 56 additions and 9533 deletions

View file

@ -41,6 +41,7 @@ minecraft {
//jvmArgs '-XX:+UnlockCommercialFeatures' // uncomment for profiling //jvmArgs '-XX:+UnlockCommercialFeatures' // uncomment for profiling
property 'forge.logging.console.level', 'info' property 'forge.logging.console.level', 'info'
property 'fml.earlyprogresswindow', 'false' property 'fml.earlyprogresswindow', 'false'
property 'mixin.env.disableRefMap', 'true'
mods { mods {
create { create {
source sourceSets.main source sourceSets.main
@ -101,11 +102,14 @@ repositories {
} }
maven { maven {
//location of the maven for mixed mappings and registrate //location of the maven for mixed mappings and registrate
name = "tterrag maven" name "tterrag maven"
url = "https://maven.tterrag.com/" url "https://maven.tterrag.com/"
} }
maven { maven {
url = "https://www.cursemaven.com" url "https://www.cursemaven.com"
content {
includeGroup "curse.maven"
}
} }
} }
@ -125,6 +129,8 @@ dependencies {
// at runtime, use the full JEI jar // at runtime, use the full JEI jar
runtimeOnly fg.deobf("mezz.jei:jei-1.16.4:${jei_version}") runtimeOnly fg.deobf("mezz.jei:jei-1.16.4:${jei_version}")
compile fg.deobf("curse.maven:flywheel-486392:3357100") // cursemaven for now
// implementation fg.deobf("curse.maven:druidcraft-340991:3101903") // implementation fg.deobf("curse.maven:druidcraft-340991:3101903")
// i'll leave this here commented for easier testing // i'll leave this here commented for easier testing

View file

@ -1,6 +0,0 @@
package com.jozufozu.flywheel;
public class Flywheel {
public static final String ID = "flywheel";
}

View file

@ -1,184 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.MaterialSpec;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.simibubi.create.foundation.config.AllConfigs;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
public class Backend {
public static final Logger log = LogManager.getLogger(Backend.class);
protected static final Backend INSTANCE = new Backend();
public static Backend getInstance() {
return INSTANCE;
}
public final Minecraft minecraft;
public ShaderSources sources;
public GLCapabilities capabilities;
public GlCompat compat;
private Matrix4f projectionMatrix = new Matrix4f();
private boolean instancedArrays;
private boolean enabled;
private final List<IShaderContext<?>> contexts = new ArrayList<>();
private final Map<ResourceLocation, MaterialSpec<?>> materialRegistry = new HashMap<>();
private final Map<ResourceLocation, ProgramSpec> programSpecRegistry = new HashMap<>();
protected Backend() {
// Can be null when running datagenerators due to the unfortunate time we call this
minecraft = Minecraft.getInstance();
if (minecraft == null) return;
sources = new ShaderSources(this);
OptifineHandler.init();
}
void clearContexts() {
SpecMetaRegistry.clear();
contexts.forEach(IShaderContext::delete);
materialRegistry.clear();
}
/**
* Get a string describing the Flywheel backend. When there are eventually multiple backends
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
*/
public String getBackendDescriptor() {
if (canUseInstancing()) {
return "GL33 Instanced Arrays";
}
if (canUseVBOs()) {
return "VBOs";
}
return "Disabled";
}
/**
* Register a shader program.
*/
public ProgramSpec register(ProgramSpec spec) {
ResourceLocation name = spec.name;
if (programSpecRegistry.containsKey(name)) {
throw new IllegalStateException("Program spec '" + name + "' already registered.");
}
programSpecRegistry.put(name, spec);
return spec;
}
/**
* Register a shader context.
*/
public <C extends ShaderContext<?>> C register(C spec) {
contexts.add(spec);
return spec;
}
/**
* Register an instancing material.
*/
public <D extends InstanceData> MaterialSpec<D> register(MaterialSpec<D> spec) {
ResourceLocation name = spec.name;
if (materialRegistry.containsKey(name)) {
throw new IllegalStateException("Material spec '" + name + "' already registered.");
}
materialRegistry.put(name, spec);
return spec;
}
public ProgramSpec getSpec(ResourceLocation name) {
return programSpecRegistry.get(name);
}
public boolean available() {
return canUseVBOs();
}
public boolean canUseInstancing() {
return enabled && instancedArrays;
}
public boolean canUseVBOs() {
return enabled && gl20();
}
public boolean gl33() {
return capabilities.OpenGL33;
}
public boolean gl20() {
return capabilities.OpenGL20;
}
public void refresh() {
OptifineHandler.refresh();
capabilities = GL.createCapabilities();
compat = new GlCompat(capabilities);
instancedArrays = compat.vertexArrayObjectsSupported() &&
compat.drawInstancedSupported() &&
compat.instancedArraysSupported();
enabled = AllConfigs.CLIENT.experimentalRendering.get() && !OptifineHandler.usingShaders();
}
public boolean canUseInstancing(World world) {
return canUseInstancing() && isFlywheelWorld(world);
}
public Collection<MaterialSpec<?>> allMaterials() {
return materialRegistry.values();
}
public Collection<ProgramSpec> allPrograms() {
return programSpecRegistry.values();
}
public Collection<IShaderContext<?>> allContexts() {
return contexts;
}
public Matrix4f getProjectionMatrix() {
return projectionMatrix;
}
public void setProjectionMatrix(Matrix4f projectionMatrix) {
this.projectionMatrix = projectionMatrix;
}
/**
* Used to avoid calling Flywheel functions on (fake) worlds that don't specifically support it.
*/
public static boolean isFlywheelWorld(IWorld world) {
return (world instanceof IFlywheelWorld && ((IFlywheelWorld) world).supportsFlywheel()) || world == Minecraft.getInstance().world;
}
public static void reloadWorldRenderers() {
RenderWork.enqueue(Minecraft.getInstance().worldRenderer::loadRenderers);
}
}

View file

@ -1,13 +0,0 @@
package com.jozufozu.flywheel.backend;
/**
* A marker interface custom worlds can override to indicate
* that tiles inside the world should render with Flywheel.
*
* <code>Minecraft.getInstance().world</code> is special cased and will support Flywheel by default.
*/
public interface IFlywheelWorld {
default boolean supportsFlywheel() {
return true;
}
}

View file

@ -1,23 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.util.ResourceLocation;
public interface IShaderContext<P extends GlProgram> {
default P getProgram(ResourceLocation loc) {
return this.getProgramSupplier(loc).get();
}
Supplier<P> getProgramSupplier(ResourceLocation loc);
/**
* Load all programs associated with this context. This might be just one, if the context is very specialized.
*/
void load();
void delete();
}

View file

@ -1,86 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Optional;
import net.minecraft.client.Minecraft;
public class OptifineHandler {
public static final String OPTIFINE_ROOT_PACKAGE = "net.optifine";
public static final String SHADER_PACKAGE = "net.optifine.shaders";
private static Package optifine;
private static OptifineHandler handler;
public final boolean usingShaders;
public OptifineHandler(boolean usingShaders) {
this.usingShaders = usingShaders;
}
/**
* Get information about the current Optifine configuration.
*
* @return {@link Optional#empty()} if Optifine is not installed.
*/
public static Optional<OptifineHandler> get() {
return Optional.ofNullable(handler);
}
public static boolean optifineInstalled() {
return optifine != null;
}
public static boolean usingShaders() {
return OptifineHandler.get()
.map(OptifineHandler::isUsingShaders)
.orElse(false);
}
public static void init() {
optifine = Package.getPackage(OPTIFINE_ROOT_PACKAGE);
if (optifine == null) {
Backend.log.info("Optifine not detected.");
} else {
Backend.log.info("Optifine detected.");
refresh();
}
}
public static void refresh() {
if (optifine == null) return;
File dir = Minecraft.getInstance().gameDir;
File shaderOptions = new File(dir, "optionsshaders.txt");
boolean shadersOff = true;
try {
BufferedReader reader = new BufferedReader(new FileReader(shaderOptions));
shadersOff = reader.lines()
.anyMatch(it -> {
String line = it.replaceAll("\\s", "");
if (line.startsWith("shaderPack=")) {
String setting = line.substring("shaderPack=".length());
return setting.equals("OFF") || setting.equals("(internal)");
}
return false;
});
} catch (FileNotFoundException e) {
Backend.log.info("No shader config found.");
}
handler = new OptifineHandler(!shadersOff);
}
public boolean isUsingShaders() {
return usingShaders;
}
}

View file

@ -1,30 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT)
public class RenderWork {
private static final Queue<Runnable> runs = new ConcurrentLinkedQueue<>();
@SubscribeEvent(priority = EventPriority.LOWEST)
public static void onRenderWorldLast(RenderWorldLastEvent event) {
while (!runs.isEmpty()) {
runs.remove().run();
}
}
/**
* Queue work to be executed at the end of a frame
*/
public static void enqueue(Runnable run) {
runs.add(run);
}
}

View file

@ -1,19 +0,0 @@
package com.jozufozu.flywheel.backend;
import net.minecraft.util.ResourceLocation;
public class ResourceUtil {
public static ResourceLocation subPath(ResourceLocation root, String subPath) {
return new ResourceLocation(root.getNamespace(), root.getPath() + subPath);
}
public static ResourceLocation removePrefixUnchecked(ResourceLocation full, String root) {
return new ResourceLocation(full.getNamespace(), full.getPath().substring(root.length()));
}
public static ResourceLocation trim(ResourceLocation loc, String prefix, String suffix) {
String path = loc.getPath();
return new ResourceLocation(loc.getNamespace(), path.substring(prefix.length(), path.length() - suffix.length()));
}
}

View file

@ -1,88 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.core.shader.IMultiProgram;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
import net.minecraft.util.ResourceLocation;
public abstract class ShaderContext<P extends GlProgram> implements IShaderContext<P> {
protected final Map<ResourceLocation, IMultiProgram<P>> programs = new HashMap<>();
public final Backend backend;
public ShaderContext(Backend backend) {
this.backend = backend;
}
@Override
public Supplier<P> getProgramSupplier(ResourceLocation spec) {
return programs.get(spec);
}
public Program loadAndLink(ProgramSpec spec, @Nullable ProgramState state) {
Shader vertexFile = getSource(ShaderType.VERTEX, spec.vert);
Shader fragmentFile = getSource(ShaderType.FRAGMENT, spec.frag);
if (state != null) {
vertexFile.defineAll(state.getDefines());
fragmentFile.defineAll(state.getDefines());
}
return link(buildProgram(spec.name, vertexFile, fragmentFile));
}
protected Shader getSource(ShaderType type, ResourceLocation name) {
return backend.sources.source(name, type);
}
protected Program link(Program program) {
return program.link();
}
@Override
public void delete() {
programs.values().forEach(IMultiProgram::delete);
}
/**
* Ingests the given shaders, compiling them and linking them together after applying the transformer to the source.
*
* @param name What should we call this program if something goes wrong?
* @param shaders What are the different shader stages that should be linked together?
* @return A program with all provided shaders attached
*/
protected static Program buildProgram(ResourceLocation name, Shader... shaders) {
List<GlShader> compiled = new ArrayList<>(shaders.length);
try {
Program builder = new Program(name);
for (Shader shader : shaders) {
GlShader sh = new GlShader(shader);
compiled.add(sh);
builder.attachShader(shader, sh);
}
return builder;
} finally {
compiled.forEach(GlObject::delete);
}
}
}

View file

@ -1,212 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import org.lwjgl.system.MemoryUtil;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.JsonOps;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.resources.IResource;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.resource.IResourceType;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
@ParametersAreNonnullByDefault
public class ShaderSources implements ISelectiveResourceReloadListener {
public static final String SHADER_DIR = "flywheel/shaders/";
public static final String PROGRAM_DIR = "flywheel/programs/";
public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl");
private static final Gson GSON = new GsonBuilder().create();
private final Map<ResourceLocation, String> shaderSource = new HashMap<>();
private boolean shouldCrash;
private final Backend backend;
public ShaderSources(Backend backend) {
this.backend = backend;
IResourceManager manager = backend.minecraft.getResourceManager();
if (manager instanceof IReloadableResourceManager) {
((IReloadableResourceManager) manager).addReloadListener(this);
}
}
@Override
public void onResourceManagerReload(IResourceManager manager, Predicate<IResourceType> predicate) {
if (predicate.test(VanillaResourceType.SHADERS)) {
backend.refresh();
if (backend.gl20()) {
shaderSource.clear();
shouldCrash = false;
backend.clearContexts();
ModLoader.get().postEvent(new GatherContextEvent(backend));
loadProgramSpecs(manager);
loadShaderSources(manager);
for (IShaderContext<?> context : backend.allContexts()) {
context.load();
}
if (shouldCrash) {
throw new ShaderLoadingException("Could not load all shaders, see log for details");
}
Backend.log.info("Loaded all shader programs.");
// no need to hog all that memory
shaderSource.clear();
}
}
}
private void loadProgramSpecs(IResourceManager manager) {
Collection<ResourceLocation> programSpecs = manager.getAllResourceLocations(PROGRAM_DIR, s -> s.endsWith(".json"));
for (ResourceLocation location : programSpecs) {
try {
IResource file = manager.getResource(location);
String s = readToString(file.getInputStream());
ResourceLocation specName = ResourceUtil.trim(location, PROGRAM_DIR, ".json");
DataResult<Pair<ProgramSpec, JsonElement>> result = ProgramSpec.CODEC.decode(JsonOps.INSTANCE, GSON.fromJson(s, JsonElement.class));
ProgramSpec spec = result.get().orThrow().getFirst();
spec.setName(specName);
backend.register(spec);
} catch (Exception e) {
Backend.log.error(e);
}
}
}
public void notifyError() {
shouldCrash = true;
}
@Nonnull
public String getShaderSource(ResourceLocation loc) {
String source = shaderSource.get(loc);
if (source == null) {
throw new ShaderLoadingException(String.format("shader '%s' does not exist", loc));
}
return source;
}
private void loadShaderSources(IResourceManager manager) {
Collection<ResourceLocation> allShaders = manager.getAllResourceLocations(SHADER_DIR, s -> {
for (String ext : EXTENSIONS) {
if (s.endsWith(ext)) return true;
}
return false;
});
for (ResourceLocation location : allShaders) {
try {
IResource resource = manager.getResource(location);
String file = readToString(resource.getInputStream());
ResourceLocation name = ResourceUtil.removePrefixUnchecked(location, SHADER_DIR);
shaderSource.put(name, file);
} catch (IOException e) {
}
}
}
public Shader source(ResourceLocation name, ShaderType type) {
return new Shader(this, type, name, getShaderSource(name));
}
public static Stream<String> lines(String s) {
return new BufferedReader(new StringReader(s)).lines();
}
public String readToString(InputStream is) {
RenderSystem.assertThread(RenderSystem::isOnRenderThread);
ByteBuffer bytebuffer = null;
try {
bytebuffer = readToBuffer(is);
int i = bytebuffer.position();
bytebuffer.rewind();
return MemoryUtil.memASCII(bytebuffer, i);
} catch (IOException e) {
} finally {
if (bytebuffer != null) {
MemoryUtil.memFree(bytebuffer);
}
}
return null;
}
public ByteBuffer readToBuffer(InputStream is) throws IOException {
ByteBuffer bytebuffer;
if (is instanceof FileInputStream) {
FileInputStream fileinputstream = (FileInputStream) is;
FileChannel filechannel = fileinputstream.getChannel();
bytebuffer = MemoryUtil.memAlloc((int) filechannel.size() + 1);
while (filechannel.read(bytebuffer) != -1) {
}
} else {
bytebuffer = MemoryUtil.memAlloc(8192);
ReadableByteChannel readablebytechannel = Channels.newChannel(is);
while (readablebytechannel.read(bytebuffer) != -1) {
if (bytebuffer.remaining() == 0) {
bytebuffer = MemoryUtil.memRealloc(bytebuffer, bytebuffer.capacity() * 2);
}
}
}
return bytebuffer;
}
}

View file

@ -1,56 +0,0 @@
package com.jozufozu.flywheel.backend;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.core.shader.extension.IProgramExtension;
import com.jozufozu.flywheel.core.shader.gamestate.IGameStateProvider;
import net.minecraft.util.ResourceLocation;
public class SpecMetaRegistry {
private static final Map<ResourceLocation, IProgramExtension> registeredExtensions = new HashMap<>();
private static final Map<ResourceLocation, IGameStateProvider> registeredStateProviders = new HashMap<>();
static void clear() {
registeredExtensions.clear();
registeredStateProviders.clear();
}
public static IGameStateProvider getStateProvider(ResourceLocation location) {
IGameStateProvider out = registeredStateProviders.get(location);
if (out == null) {
throw new IllegalArgumentException("State provider '" + location + "' does not exist.");
}
return out;
}
public static IProgramExtension getExtension(ResourceLocation location) {
IProgramExtension out = registeredExtensions.get(location);
if (out == null) {
throw new IllegalArgumentException("Extension '" + location + "' does not exist.");
}
return out;
}
public static void register(IGameStateProvider context) {
if (registeredStateProviders.containsKey(context.getID())) {
throw new IllegalStateException("Duplicate game state provider: " + context.getID());
}
registeredStateProviders.put(context.getID(), context);
}
public static void register(IProgramExtension extender) {
if (registeredStateProviders.containsKey(extender.getID())) {
throw new IllegalStateException("Duplicate shader extension: " + extender.getID());
}
registeredExtensions.put(extender.getID(), extender);
}
}

View file

@ -1,67 +0,0 @@
package com.jozufozu.flywheel.backend.gl;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.lwjgl.opengl.GL11;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public enum GlNumericType {
FLOAT(4, "float", GL11.GL_FLOAT),
UBYTE(1, "ubyte", GL11.GL_UNSIGNED_BYTE),
BYTE(1, "byte", GL11.GL_BYTE),
USHORT(2, "ushort", GL11.GL_UNSIGNED_SHORT),
SHORT(2, "short", GL11.GL_SHORT),
UINT(4, "uint", GL11.GL_UNSIGNED_INT),
INT(4, "int", GL11.GL_INT),
;
private static final GlNumericType[] VALUES = values();
private static final Map<String, GlNumericType> NAME_LOOKUP = Arrays.stream(VALUES)
.collect(Collectors.toMap(GlNumericType::getDisplayName, type -> type));
private final int byteWidth;
private final String displayName;
private final int glEnum;
GlNumericType(int bytes, String name, int glEnum) {
this.byteWidth = bytes;
this.displayName = name;
this.glEnum = glEnum;
}
public int getByteWidth() {
return this.byteWidth;
}
public String getDisplayName() {
return this.displayName;
}
public int getGlEnum() {
return this.glEnum;
}
public void castAndBuffer(ByteBuffer buf, int val) {
if (this == UBYTE || this == BYTE) {
buf.put((byte) val);
} else if (this == USHORT || this == SHORT) {
buf.putShort((short) val);
} else if (this == UINT || this == INT) {
buf.putInt(val);
}
}
@Nullable
public static GlNumericType byName(String name) {
return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT));
}
}

View file

@ -1,43 +0,0 @@
package com.jozufozu.flywheel.backend.gl;
// Utility class for safely dealing with gl object handles.
public abstract class GlObject {
private static final int INVALID_HANDLE = Integer.MIN_VALUE;
private int handle = INVALID_HANDLE;
protected final void setHandle(int handle) {
this.handle = handle;
}
public final int handle() {
this.checkHandle();
return this.handle;
}
protected final void checkHandle() {
if (!this.isHandleValid()) {
throw new IllegalStateException("Handle is not valid");
}
}
protected final boolean isHandleValid() {
return this.handle != INVALID_HANDLE;
}
protected final void invalidateHandle() {
this.handle = INVALID_HANDLE;
}
public void delete() {
if (!isHandleValid()) {
throw new IllegalStateException("Handle already deleted.");
}
deleteInternal(handle);
invalidateHandle();
}
protected abstract void deleteInternal(int handle);
}

View file

@ -1,23 +0,0 @@
package com.jozufozu.flywheel.backend.gl;
import org.lwjgl.opengl.GL11;
public enum GlPrimitive {
POINTS(GL11.GL_POINTS),
LINES(GL11.GL_LINES),
LINE_LOOP(GL11.GL_LINE_LOOP),
LINE_STRIP(GL11.GL_LINE_STRIP),
TRIANGLES(GL11.GL_TRIANGLES),
TRIANGLE_STRIP(GL11.GL_TRIANGLE_STRIP),
TRIANGLE_FAN(GL11.GL_TRIANGLE_FAN),
QUADS(GL11.GL_QUADS),
QUAD_STRIP(GL11.GL_QUAD_STRIP),
POLYGON(GL11.GL_POLYGON),
;
public final int glEnum;
GlPrimitive(int glEnum) {
this.glEnum = glEnum;
}
}

View file

@ -1,25 +0,0 @@
package com.jozufozu.flywheel.backend.gl;
import org.lwjgl.opengl.GL20;
public class GlTexture extends GlObject {
private final int textureType;
public GlTexture(int textureType) {
this.textureType = textureType;
setHandle(GL20.glGenTextures());
}
@Override
protected void deleteInternal(int handle) {
GL20.glDeleteTextures(handle);
}
public void bind() {
GL20.glBindTexture(textureType, handle());
}
public void unbind() {
GL20.glBindTexture(textureType, 0);
}
}

View file

@ -1,21 +0,0 @@
package com.jozufozu.flywheel.backend.gl;
import com.jozufozu.flywheel.backend.Backend;
public class GlVertexArray extends GlObject {
public GlVertexArray() {
setHandle(Backend.getInstance().compat.vao.genVertexArrays());
}
public void bind() {
Backend.getInstance().compat.vao.bindVertexArray(handle());
}
public void unbind() {
Backend.getInstance().compat.vao.bindVertexArray(0);
}
protected void deleteInternal(int handle) {
Backend.getInstance().compat.vao.deleteVertexArrays(handle);
}
}

View file

@ -1,21 +0,0 @@
package com.jozufozu.flywheel.backend.gl.attrib;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
public class CommonAttributes {
public static final VertexAttribSpec VEC4 = new VertexAttribSpec(GlNumericType.FLOAT, 4);
public static final VertexAttribSpec VEC3 = new VertexAttribSpec(GlNumericType.FLOAT, 3);
public static final VertexAttribSpec VEC2 = new VertexAttribSpec(GlNumericType.FLOAT, 2);
public static final VertexAttribSpec FLOAT = new VertexAttribSpec(GlNumericType.FLOAT, 1);
public static final VertexAttribSpec QUATERNION = new VertexAttribSpec(GlNumericType.FLOAT, 4);
public static final VertexAttribSpec NORMAL = new VertexAttribSpec(GlNumericType.BYTE, 3, true);
public static final VertexAttribSpec UV = new VertexAttribSpec(GlNumericType.FLOAT, 2);
public static final VertexAttribSpec RGBA = new VertexAttribSpec(GlNumericType.UBYTE, 4, true);
public static final VertexAttribSpec RGB = new VertexAttribSpec(GlNumericType.UBYTE, 3, true);
public static final VertexAttribSpec LIGHT = new VertexAttribSpec(GlNumericType.UBYTE, 2, true);
public static final VertexAttribSpec NORMALIZED_BYTE = new VertexAttribSpec(GlNumericType.BYTE, 1, true);
}

View file

@ -1,10 +0,0 @@
package com.jozufozu.flywheel.backend.gl.attrib;
public interface IAttribSpec {
void vertexAttribPointer(int stride, int index, int pointer);
int getSize();
int getAttributeCount();
}

View file

@ -1,37 +0,0 @@
package com.jozufozu.flywheel.backend.gl.attrib;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
public enum MatrixAttributes implements IAttribSpec {
MAT3(3, 3),
MAT4(4, 4),
;
private final int rows;
private final int cols;
MatrixAttributes(int rows, int cols) {
this.rows = rows;
this.cols = cols;
}
@Override
public void vertexAttribPointer(int stride, int index, int pointer) {
for (int i = 0; i < rows; i++) {
long attribPointer = pointer + (long) i * cols * GlNumericType.FLOAT.getByteWidth();
GL20.glVertexAttribPointer(index + i, cols, GlNumericType.FLOAT.getGlEnum(), false, stride, attribPointer);
}
}
@Override
public int getSize() {
return GlNumericType.FLOAT.getByteWidth() * rows * cols;
}
@Override
public int getAttributeCount() {
return rows;
}
}

View file

@ -1,41 +0,0 @@
package com.jozufozu.flywheel.backend.gl.attrib;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
public class VertexAttribSpec implements IAttribSpec {
private final GlNumericType type;
private final int count;
private final int size;
private final int attributeCount;
private final boolean normalized;
public VertexAttribSpec(GlNumericType type, int count) {
this(type, count, false);
}
public VertexAttribSpec(GlNumericType type, int count, boolean normalized) {
this.type = type;
this.count = count;
this.size = type.getByteWidth() * count;
this.attributeCount = (this.size + 15) / 16; // ceiling division. GLSL vertex attributes can only be 16 bytes wide
this.normalized = normalized;
}
@Override
public void vertexAttribPointer(int stride, int index, int pointer) {
GL20.glVertexAttribPointer(index, count, type.getGlEnum(), normalized, stride, pointer);
}
@Override
public int getSize() {
return size;
}
@Override
public int getAttributeCount() {
return attributeCount;
}
}

View file

@ -1,61 +0,0 @@
package com.jozufozu.flywheel.backend.gl.attrib;
import java.util.ArrayList;
import java.util.Collections;
public class VertexFormat {
private final ArrayList<IAttribSpec> allAttributes;
private final int numAttributes;
private final int stride;
public VertexFormat(ArrayList<IAttribSpec> allAttributes) {
this.allAttributes = allAttributes;
int numAttributes = 0, stride = 0;
for (IAttribSpec spec : allAttributes) {
numAttributes += spec.getAttributeCount();
stride += spec.getSize();
}
this.numAttributes = numAttributes;
this.stride = stride;
}
public int getAttributeCount() {
return numAttributes;
}
public int getStride() {
return stride;
}
public void vertexAttribPointers(int index) {
int offset = 0;
for (IAttribSpec spec : this.allAttributes) {
spec.vertexAttribPointer(stride, index, offset);
index += spec.getAttributeCount();
offset += spec.getSize();
}
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private final ArrayList<IAttribSpec> allAttributes = new ArrayList<>();
public Builder() {
}
public Builder addAttributes(IAttribSpec... attributes) {
Collections.addAll(allAttributes, attributes);
return this;
}
public VertexFormat build() {
return new VertexFormat(allAttributes);
}
}
}

View file

@ -1,69 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.MapBufferRange;
public class GlBuffer extends GlObject {
protected final GlBufferType type;
protected final GlBufferUsage usage;
public GlBuffer(GlBufferType type) {
this(type, GlBufferUsage.STATIC_DRAW);
}
public GlBuffer(GlBufferType type, GlBufferUsage usage) {
setHandle(GL20.glGenBuffers());
this.type = type;
this.usage = usage;
}
public GlBufferType getBufferTarget() {
return type;
}
public void bind() {
bind(type);
}
public void bind(GlBufferType type) {
GL20.glBindBuffer(type.glEnum, handle());
}
public void unbind() {
unbind(type);
}
public void unbind(GlBufferType bufferType) {
GL20.glBindBuffer(bufferType.glEnum, 0);
}
public void alloc(int size) {
GL15.glBufferData(type.glEnum, size, usage.glEnum);
}
public void upload(ByteBuffer directBuffer) {
GL15.glBufferData(type.glEnum, directBuffer, usage.glEnum);
}
public MappedBuffer getBuffer(int offset, int length) {
if (Backend.getInstance().compat.mapBufferRange != MapBufferRange.UNSUPPORTED) {
return new MappedBufferRange(this, offset, length, GL30.GL_MAP_WRITE_BIT);
} else {
MappedFullBuffer fullBuffer = new MappedFullBuffer(this, MappedBufferUsage.WRITE_ONLY);
fullBuffer.position(offset);
return fullBuffer;
}
}
protected void deleteInternal(int handle) {
GL20.glDeleteBuffers(handle);
}
}

View file

@ -1,32 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
public enum GlBufferType {
ARRAY_BUFFER(GL15C.GL_ARRAY_BUFFER),
ELEMENT_ARRAY_BUFFER(GL15C.GL_ELEMENT_ARRAY_BUFFER),
PIXEL_PACK_BUFFER(GL21.GL_PIXEL_PACK_BUFFER),
PIXEL_UNPACK_BUFFER(GL21.GL_PIXEL_UNPACK_BUFFER),
TRANSFORM_FEEDBACK_BUFFER(GL30.GL_TRANSFORM_FEEDBACK_BUFFER),
UNIFORM_BUFFER(GL31.GL_UNIFORM_BUFFER),
TEXTURE_BUFFER(GL31.GL_TEXTURE_BUFFER),
COPY_READ_BUFFER(GL31.GL_COPY_READ_BUFFER),
COPY_WRITE_BUFFER(GL31.GL_COPY_WRITE_BUFFER),
DRAW_INDIRECT_BUFFER(GL40.GL_DRAW_INDIRECT_BUFFER),
ATOMIC_COUNTER_BUFFER(GL42.GL_ATOMIC_COUNTER_BUFFER),
DISPATCH_INDIRECT_BUFFER(GL43.GL_DISPATCH_INDIRECT_BUFFER),
SHADER_STORAGE_BUFFER(GL43.GL_SHADER_STORAGE_BUFFER),
;
public final int glEnum;
GlBufferType(int glEnum) {
this.glEnum = glEnum;
}
}

View file

@ -1,22 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import org.lwjgl.opengl.GL15;
public enum GlBufferUsage {
STREAM_DRAW(GL15.GL_STREAM_DRAW),
STREAM_READ(GL15.GL_STREAM_READ),
STREAM_COPY(GL15.GL_STREAM_COPY),
STATIC_DRAW(GL15.GL_STATIC_DRAW),
STATIC_READ(GL15.GL_STATIC_READ),
STATIC_COPY(GL15.GL_STATIC_COPY),
DYNAMIC_DRAW(GL15.GL_DYNAMIC_DRAW),
DYNAMIC_READ(GL15.GL_DYNAMIC_READ),
DYNAMIC_COPY(GL15.GL_DYNAMIC_COPY),
;
public final int glEnum;
GlBufferUsage(int glEnum) {
this.glEnum = glEnum;
}
}

View file

@ -1,110 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL15;
public abstract class MappedBuffer extends VecBuffer implements AutoCloseable {
protected boolean mapped;
protected final GlBuffer owner;
public MappedBuffer(GlBuffer owner) {
this.owner = owner;
}
protected abstract void checkAndMap();
/**
* Make the changes in client memory available to the GPU.
*/
public void flush() {
if (mapped) {
GL15.glUnmapBuffer(owner.type.glEnum);
mapped = false;
setInternal(null);
}
}
@Override
public void close() throws Exception {
flush();
}
public MappedBuffer putFloatArray(float[] floats) {
checkAndMap();
super.putFloatArray(floats);
return this;
}
public MappedBuffer putByteArray(byte[] bytes) {
checkAndMap();
super.putByteArray(bytes);
return this;
}
/**
* Position this buffer relative to the 0-index in GPU memory.
*
* @return This buffer.
*/
public MappedBuffer position(int p) {
checkAndMap();
super.position(p);
return this;
}
public MappedBuffer putFloat(float f) {
checkAndMap();
super.putFloat(f);
return this;
}
public MappedBuffer putInt(int i) {
checkAndMap();
super.putInt(i);
return this;
}
public MappedBuffer put(byte b) {
checkAndMap();
super.put(b);
return this;
}
public MappedBuffer put(ByteBuffer b) {
checkAndMap();
super.put(b);
return this;
}
public MappedBuffer putVec4(float x, float y, float z, float w) {
checkAndMap();
super.putVec4(x, y, z, w);
return this;
}
public MappedBuffer putVec3(float x, float y, float z) {
checkAndMap();
super.putVec3(x, y, z);
return this;
}
public MappedBuffer putVec2(float x, float y) {
checkAndMap();
super.putVec2(x, y);
return this;
}
public MappedBuffer putVec3(byte x, byte y, byte z) {
checkAndMap();
super.putVec3(x, y, z);
return this;
}
public MappedBuffer putVec2(byte x, byte y) {
checkAndMap();
super.putVec2(x, y);
return this;
}
}

View file

@ -1,33 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import com.jozufozu.flywheel.backend.Backend;
public class MappedBufferRange extends MappedBuffer {
long offset, length;
int access;
public MappedBufferRange(GlBuffer buffer, long offset, long length, int access) {
super(buffer);
this.offset = offset;
this.length = length;
this.access = access;
}
@Override
public MappedBuffer position(int p) {
if (p < offset || p >= offset + length) {
throw new IndexOutOfBoundsException("Index " + p + " is not mapped");
}
return super.position(p - (int) offset);
}
@Override
protected void checkAndMap() {
if (!mapped) {
setInternal(Backend.getInstance().compat.mapBufferRange.mapBuffer(owner.type, offset, length, access));
mapped = true;
}
}
}

View file

@ -1,16 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import org.lwjgl.opengl.GL15C;
public enum MappedBufferUsage {
READ_ONLY(GL15C.GL_READ_ONLY),
WRITE_ONLY(GL15C.GL_WRITE_ONLY),
READ_WRITE(GL15C.GL_READ_WRITE),
;
int glEnum;
MappedBufferUsage(int glEnum) {
this.glEnum = glEnum;
}
}

View file

@ -1,21 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import org.lwjgl.opengl.GL15;
public class MappedFullBuffer extends MappedBuffer {
MappedBufferUsage usage;
public MappedFullBuffer(GlBuffer buffer, MappedBufferUsage usage) {
super(buffer);
this.usage = usage;
}
@Override
protected void checkAndMap() {
if (!mapped) {
setInternal(GL15.glMapBuffer(owner.type.glEnum, usage.glEnum));
mapped = true;
}
}
}

View file

@ -1,116 +0,0 @@
package com.jozufozu.flywheel.backend.gl.buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class VecBuffer {
protected ByteBuffer internal;
public VecBuffer() {
}
public VecBuffer(ByteBuffer internal) {
this.internal = internal;
}
public static VecBuffer allocate(int bytes) {
ByteBuffer buffer = ByteBuffer.allocate(bytes);
buffer.order(ByteOrder.nativeOrder());
return new VecBuffer(buffer);
}
protected void setInternal(ByteBuffer internal) {
this.internal = internal;
}
public ByteBuffer unwrap() {
return internal;
}
public VecBuffer rewind() {
internal.rewind();
return this;
}
public VecBuffer putFloatArray(float[] floats) {
for (float f : floats) {
internal.putFloat(f);
}
// internal.asFloatBuffer().put(floats);
// internal.position(internal.position() + floats.length * 4);
return this;
}
public VecBuffer putByteArray(byte[] bytes) {
internal.put(bytes);
return this;
}
/**
* Position this buffer relative to the 0-index in GPU memory.
*
* @return This buffer.
*/
public VecBuffer position(int p) {
internal.position(p);
return this;
}
public VecBuffer putFloat(float f) {
internal.putFloat(f);
return this;
}
public VecBuffer putInt(int i) {
internal.putInt(i);
return this;
}
public VecBuffer put(byte b) {
internal.put(b);
return this;
}
public VecBuffer put(ByteBuffer b) {
internal.put(b);
return this;
}
public VecBuffer putVec4(float x, float y, float z, float w) {
internal.putFloat(x);
internal.putFloat(y);
internal.putFloat(z);
internal.putFloat(w);
return this;
}
public VecBuffer putVec3(float x, float y, float z) {
internal.putFloat(x);
internal.putFloat(y);
internal.putFloat(z);
return this;
}
public VecBuffer putVec2(float x, float y) {
internal.putFloat(x);
internal.putFloat(y);
return this;
}
public VecBuffer putVec3(byte x, byte y, byte z) {
internal.put(x);
internal.put(y);
internal.put(z);
return this;
}
public VecBuffer putVec2(byte x, byte y) {
internal.put(x);
internal.put(y);
return this;
}
}

View file

@ -1,77 +0,0 @@
package com.jozufozu.flywheel.backend.gl.shader;
import static org.lwjgl.opengl.GL20.glDeleteProgram;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL20.glUseProgram;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.util.RenderUtil;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.Matrix4f;
public abstract class GlProgram extends GlObject {
public final ResourceLocation name;
protected GlProgram(Program program) {
setHandle(program.program);
this.name = program.name;
}
public void bind() {
glUseProgram(handle());
}
public void unbind() {
glUseProgram(0);
}
/**
* Retrieves the index of the uniform with the given name.
*
* @param uniform The name of the uniform to find the index of
* @return The uniform's index
*/
public int getUniformLocation(String uniform) {
int index = glGetUniformLocation(this.handle(), uniform);
if (index < 0) {
Backend.log.debug("No active uniform '{}' exists in program '{}'. Could be unused.", uniform, this.name);
}
return index;
}
/**
* Binds a sampler uniform to the given texture unit.
*
* @param name The name of the sampler uniform.
* @param binding The index of the texture unit.
* @return The sampler uniform's index.
* @throws NullPointerException If no uniform exists with the given name.
*/
public int setSamplerBinding(String name, int binding) {
int samplerUniform = getUniformLocation(name);
if (samplerUniform >= 0) {
glUniform1i(samplerUniform, binding);
}
return samplerUniform;
}
protected static void uploadMatrixUniform(int uniform, Matrix4f mat) {
glUniformMatrix4fv(uniform, false, RenderUtil.writeMatrix(mat));
}
@Override
protected void deleteInternal(int handle) {
glDeleteProgram(handle);
}
}

View file

@ -1,46 +0,0 @@
package com.jozufozu.flywheel.backend.gl.shader;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.loading.Shader;
import net.minecraft.util.ResourceLocation;
public class GlShader extends GlObject {
public final ResourceLocation name;
public final ShaderType type;
public GlShader(Shader shader) {
this(shader.type, shader.name, shader.getSource());
}
public GlShader(ShaderType type, ResourceLocation name, String source) {
this.type = type;
this.name = name;
int handle = GL20.glCreateShader(type.glEnum);
GlCompat.safeShaderSource(handle, source);
GL20.glCompileShader(handle);
String log = GL20.glGetShaderInfoLog(handle);
if (!log.isEmpty()) {
Backend.log.error("Shader compilation log for " + name + ": " + log);
}
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
throw new RuntimeException("Could not compile " + name + ". See log for details.");
}
setHandle(handle);
}
@Override
protected void deleteInternal(int handle) {
GL20.glDeleteShader(handle);
}
}

View file

@ -1,17 +0,0 @@
package com.jozufozu.flywheel.backend.gl.shader;
import org.lwjgl.opengl.GL20;
public enum ShaderType {
VERTEX("vertex", GL20.GL_VERTEX_SHADER),
FRAGMENT("fragment", GL20.GL_FRAGMENT_SHADER),
;
public final String name;
public final int glEnum;
ShaderType(String name, int glEnum) {
this.name = name;
this.glEnum = glEnum;
}
}

View file

@ -1,113 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.backend.gl.versioned.framebuffer.Blit;
import com.jozufozu.flywheel.backend.gl.versioned.framebuffer.Framebuffer;
import com.jozufozu.flywheel.backend.gl.versioned.instancing.DrawInstanced;
import com.jozufozu.flywheel.backend.gl.versioned.instancing.InstancedArrays;
import com.jozufozu.flywheel.backend.gl.versioned.instancing.VertexArrayObject;
/**
* An instance of this class stores information about what OpenGL features are available.
* <br>
* Each field stores an enum variant that provides access to the most appropriate version of a feature for the current
* system.
*/
public class GlCompat {
public final MapBufferRange mapBufferRange;
public final VertexArrayObject vao;
public final InstancedArrays instancedArrays;
public final DrawInstanced drawInstanced;
public final Blit blit;
public final Framebuffer fbo;
public final RGPixelFormat pixelFormat;
public GlCompat(GLCapabilities caps) {
mapBufferRange = getLatest(MapBufferRange.class, caps);
vao = getLatest(VertexArrayObject.class, caps);
instancedArrays = getLatest(InstancedArrays.class, caps);
drawInstanced = getLatest(DrawInstanced.class, caps);
blit = getLatest(Blit.class, caps);
fbo = getLatest(Framebuffer.class, caps);
pixelFormat = getLatest(RGPixelFormat.class, caps);
}
public boolean vertexArrayObjectsSupported() {
return vao != VertexArrayObject.UNSUPPORTED;
}
public boolean instancedArraysSupported() {
return instancedArrays != InstancedArrays.UNSUPPORTED;
}
public boolean drawInstancedSupported() {
return drawInstanced != DrawInstanced.UNSUPPORTED;
}
public boolean fbosSupported() {
return fbo != Framebuffer.UNSUPPORTED;
}
public boolean blitSupported() {
return blit != Blit.UNSUPPORTED;
}
/**
* Get the most compatible version of a specific OpenGL feature by iterating over enum constants in order.
*
* @param clazz The class of the versioning enum.
* @param caps The current system's supported features.
* @param <V> The type of the versioning enum.
* @return The first defined enum variant to return true.
*/
public static <V extends Enum<V> & GlVersioned> V getLatest(Class<V> clazz, GLCapabilities caps) {
V[] constants = clazz.getEnumConstants();
V last = constants[constants.length - 1];
if (!last.supported(caps)) {
throw new IllegalStateException("");
}
return Arrays.stream(constants).filter(it -> it.supported(caps)).findFirst().get();
}
/**
* Copied from:
* <br> https://github.com/grondag/canvas/commit/820bf754092ccaf8d0c169620c2ff575722d7d96
*
* <p>Identical in function to {@link GL20C#glShaderSource(int, CharSequence)} but
* passes a null pointer for string length to force the driver to rely on the null
* terminator for string length. This is a workaround for an apparent flaw with some
* AMD drivers that don't receive or interpret the length correctly, resulting in
* an access violation when the driver tries to read past the string memory.
*
* <p>Hat tip to fewizz for the find and the fix.
*/
public static void safeShaderSource(int glId, CharSequence source) {
final MemoryStack stack = MemoryStack.stackGet();
final int stackPointer = stack.getPointer();
try {
final ByteBuffer sourceBuffer = MemoryUtil.memUTF8(source, true);
final PointerBuffer pointers = stack.mallocPointer(1);
pointers.put(sourceBuffer);
GL20C.nglShaderSource(glId, 1, pointers.address0(), 0);
org.lwjgl.system.APIUtil.apiArrayFree(pointers.address0(), 1);
} finally {
stack.setPointer(stackPointer);
}
}
}

View file

@ -1,17 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned;
import org.lwjgl.opengl.GLCapabilities;
/**
* This interface should be implemented by enums such that the
* last defined variant <em>always</em> returns <code>true</code>.
*/
public interface GlVersioned {
/**
* Queries whether this variant is supported by the current system.
*
* @param caps The {@link GLCapabilities} reported by the current system.
* @return <code>true</code> if this variant is supported, or if this is the last defined variant.
*/
boolean supported(GLCapabilities caps);
}

View file

@ -1,49 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.ARBMapBufferRange;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
public enum MapBufferRange implements GlVersioned {
GL30_RANGE {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL30;
}
@Override
public ByteBuffer mapBuffer(GlBufferType target, long offset, long length, int access) {
return GL30.glMapBufferRange(target.glEnum, offset, length, access);
}
},
ARB_RANGE {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_ARB_map_buffer_range;
}
@Override
public ByteBuffer mapBuffer(GlBufferType target, long offset, long length, int access) {
return ARBMapBufferRange.glMapBufferRange(target.glEnum, offset, length, access);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public ByteBuffer mapBuffer(GlBufferType target, long offset, long length, int access) {
throw new UnsupportedOperationException("glMapBuffer not supported");
}
};
public abstract ByteBuffer mapBuffer(GlBufferType target, long offset, long length, int access);
}

View file

@ -1,77 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLCapabilities;
public enum RGPixelFormat implements GlVersioned {
GL30_RG {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL30;
}
@Override
public int internalFormat() {
return GL30.GL_RG8;
}
@Override
public int format() {
return GL30.GL_RG;
}
@Override
public int byteCount() {
return 2;
}
},
GL11_RGB {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL11;
}
@Override
public int internalFormat() {
return GL11.GL_RGB8;
}
@Override
public int format() {
return GL11.GL_RGB;
}
@Override
public int byteCount() {
return 3;
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public int internalFormat() {
throw new UnsupportedOperationException();
}
@Override
public int format() {
throw new UnsupportedOperationException();
}
@Override
public int byteCount() {
throw new UnsupportedOperationException();
}
};
public abstract int internalFormat();
public abstract int format();
public abstract int byteCount();
}

View file

@ -1,45 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned.framebuffer;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlVersioned;
public enum Blit implements GlVersioned {
CORE {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL30;
}
@Override
public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
},
EXT {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_EXT_framebuffer_blit;
}
@Override
public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
throw new UnsupportedOperationException("Framebuffer blitting not supported.");
}
};
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
}

View file

@ -1,57 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned.framebuffer;
import org.lwjgl.opengl.ARBFramebufferObject;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlVersioned;
public enum Framebuffer implements GlVersioned {
CORE {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL30;
}
@Override
public void bindFramebuffer(int target, int framebuffer) {
GL30C.glBindFramebuffer(target, framebuffer);
}
},
ARB {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_ARB_framebuffer_object;
}
@Override
public void bindFramebuffer(int target, int framebuffer) {
ARBFramebufferObject.glBindFramebuffer(target, framebuffer);
}
},
EXT {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_EXT_framebuffer_object;
}
@Override
public void bindFramebuffer(int target, int framebuffer) {
EXTFramebufferObject.glBindFramebufferEXT(target, framebuffer);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public void bindFramebuffer(int target, int framebuffer) {
throw new UnsupportedOperationException("Framebuffers not supported");
}
};
public abstract void bindFramebuffer(int target, int framebuffer);
}

View file

@ -1,76 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned.instancing;
import org.lwjgl.opengl.ARBDrawInstanced;
import org.lwjgl.opengl.EXTDrawInstanced;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.versioned.GlVersioned;
public enum DrawInstanced implements GlVersioned {
GL31_DRAW_INSTANCED {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL31;
}
@Override
public void drawArraysInstanced(GlPrimitive mode, int first, int count, int primcount) {
GL31.glDrawArraysInstanced(mode.glEnum, first, count, primcount);
}
@Override
public void drawElementsInstanced(GlPrimitive mode, int elementCount, GlNumericType type, long indices, int primcount) {
GL31.glDrawElementsInstanced(mode.glEnum, elementCount, type.getGlEnum(), indices, primcount);
}
},
ARB_DRAW_INSTANCED {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_ARB_draw_instanced;
}
@Override
public void drawArraysInstanced(GlPrimitive mode, int first, int count, int primcount) {
ARBDrawInstanced.glDrawArraysInstancedARB(mode.glEnum, first, count, primcount);
}
@Override
public void drawElementsInstanced(GlPrimitive mode, int elementCount, GlNumericType type, long indices, int primcount) {
ARBDrawInstanced.glDrawElementsInstancedARB(mode.glEnum, elementCount, type.getGlEnum(), indices, primcount);
}
},
EXT_DRAW_INSTANCED {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_EXT_draw_instanced;
}
@Override
public void drawArraysInstanced(GlPrimitive mode, int first, int count, int primcount) {
EXTDrawInstanced.glDrawArraysInstancedEXT(mode.glEnum, first, count, primcount);
}
@Override
public void drawElementsInstanced(GlPrimitive mode, int elementCount, GlNumericType type, long indices, int primcount) {
EXTDrawInstanced.glDrawElementsInstancedEXT(mode.glEnum, elementCount, type.getGlEnum(), indices, primcount);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
};
public void drawArraysInstanced(GlPrimitive mode, int first, int count, int primcount) {
throw new UnsupportedOperationException();
}
public void drawElementsInstanced(GlPrimitive mode, int elementCount, GlNumericType type, long indices, int primcount) {
throw new UnsupportedOperationException();
}
}

View file

@ -1,45 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned.instancing;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlVersioned;
public enum InstancedArrays implements GlVersioned {
GL33_INSTANCED_ARRAYS {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL33;
}
@Override
public void vertexAttribDivisor(int index, int divisor) {
GL33.glVertexAttribDivisor(index, divisor);
}
},
ARB_INSTANCED_ARRAYS {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_ARB_instanced_arrays;
}
@Override
public void vertexAttribDivisor(int index, int divisor) {
ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public void vertexAttribDivisor(int index, int divisor) {
throw new UnsupportedOperationException();
}
};
public abstract void vertexAttribDivisor(int index, int divisor);
}

View file

@ -1,79 +0,0 @@
package com.jozufozu.flywheel.backend.gl.versioned.instancing;
import org.lwjgl.opengl.ARBVertexArrayObject;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLCapabilities;
import com.jozufozu.flywheel.backend.gl.versioned.GlVersioned;
public enum VertexArrayObject implements GlVersioned {
GL30_VAO {
@Override
public boolean supported(GLCapabilities caps) {
return caps.OpenGL30;
}
@Override
public int genVertexArrays() {
return GL30.glGenVertexArrays();
}
@Override
public void bindVertexArray(int array) {
GL30.glBindVertexArray(array);
}
@Override
public void deleteVertexArrays(int array) {
GL30.glDeleteVertexArrays(array);
}
},
ARB_VAO {
@Override
public boolean supported(GLCapabilities caps) {
return caps.GL_ARB_vertex_array_object;
}
@Override
public int genVertexArrays() {
return ARBVertexArrayObject.glGenVertexArrays();
}
@Override
public void bindVertexArray(int array) {
ARBVertexArrayObject.glBindVertexArray(array);
}
@Override
public void deleteVertexArrays(int array) {
ARBVertexArrayObject.glDeleteVertexArrays(array);
}
},
UNSUPPORTED {
@Override
public boolean supported(GLCapabilities caps) {
return true;
}
@Override
public int genVertexArrays() {
throw new UnsupportedOperationException();
}
@Override
public void bindVertexArray(int array) {
throw new UnsupportedOperationException();
}
@Override
public void deleteVertexArrays(int array) {
throw new UnsupportedOperationException();
}
};
public abstract int genVertexArrays();
public abstract void bindVertexArray(int array);
public abstract void deleteVertexArrays(int array);
}

View file

@ -1,32 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.instancing.tile.TileEntityInstance;
/**
* An interface giving {@link TileEntityInstance}s a hook to have a function called at
* the start of a frame. By implementing {@link IDynamicInstance}, a {@link TileEntityInstance}
* can animate its models in ways that could not be easily achieved by shader attribute
* parameterization.
*
* <br><br> If your goal is offloading work to shaders, but you're unsure exactly how you need
* to parameterize the instances, you're encouraged to implement this for prototyping.
*/
public interface IDynamicInstance extends IInstance {
/**
* Called every frame.
*/
void beginFrame();
/**
* As a further optimization, dynamic instances that are far away are ticked less often.
* This behavior can be disabled by returning false.
*
* <br> You might want to opt out of this if you want your animations to remain smooth
* even when far away from the camera. It is recommended to keep this as is, however.
*
* @return <code>true</code> if your instance should be slow ticked.
*/
default boolean decreaseFramerateWithDistance() {
return true;
}
}

View file

@ -1,22 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import net.minecraft.util.math.BlockPos;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
* Right now, that's only {@link TileInstanceManager}, but there could be an entity equivalent in the future.
*/
public interface IInstance {
BlockPos getWorldPosition();
void updateLight();
void remove();
boolean shouldReset();
void update();
}

View file

@ -1,5 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
public interface IInstanceFactory<D extends InstanceData> {
D create(Instancer<? super D> owner);
}

View file

@ -1,18 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import net.minecraft.world.World;
/**
* Something (a TileEntity or Entity) that can be rendered using the instancing API.
*/
public interface IInstanceRendered {
/**
* @return true if there are parts of the renderer that cannot be implemented with Flywheel.
*/
default boolean shouldRenderNormally() {
return false;
}
World getWorld();
}

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.instancing.tile.TileEntityInstance;
/**
* An interface giving {@link TileEntityInstance}s a hook to have a function called at
* the end of every tick. By implementing {@link ITickableInstance}, a {@link TileEntityInstance}
* can update frequently, but not every frame.
* <br> There are a few cases in which this should be considered over {@link IDynamicInstance}:
* <ul>
* <li>
* You'd like to change something about the instance every now and then.
* eg. adding or removing parts, snapping to a different rotation, etc.
* </li>
* <li>
* Your TileEntity does animate, but the animation doesn't have
* to be smooth, in which case this could be an optimization.
* </li>
* </ul>
*/
public interface ITickableInstance extends IInstance {
/**
* Called every tick.
*/
void tick();
/**
* As a further optimization, tickable instances that are far away are ticked less often.
* This behavior can be disabled by returning false.
*
* <br> You might want to opt out of this if you want your animations to remain smooth
* even when far away from the camera. It is recommended to keep this as is, however.
*
* @return <code>true</code> if your instance should be slow ticked.
*/
default boolean decreaseTickRateWithDistance() {
return true;
}
}

View file

@ -1,28 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
public abstract class InstanceData {
protected final Instancer<?> owner;
boolean dirty;
boolean removed;
protected InstanceData(Instancer<?> owner) {
this.owner = owner;
}
public abstract void write(MappedBuffer buf);
public void markDirty() {
owner.anyToUpdate = true;
dirty = true;
}
public void delete() {
owner.anyToRemove = true;
removed = true;
}
}

View file

@ -1,245 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3f;
public abstract class InstanceManager<T> implements MaterialManager.OriginShiftListener {
public final MaterialManager<?> materialManager;
protected final ArrayList<T> queuedAdditions;
protected final ConcurrentHashMap.KeySetView<T, Boolean> queuedUpdates;
protected final Map<T, IInstance> instances;
protected final Object2ObjectOpenHashMap<T, ITickableInstance> tickableInstances;
protected final Object2ObjectOpenHashMap<T, IDynamicInstance> dynamicInstances;
protected int frame;
protected int tick;
public InstanceManager(MaterialManager<?> materialManager) {
this.materialManager = materialManager;
this.queuedUpdates = ConcurrentHashMap.newKeySet(64);
this.queuedAdditions = new ArrayList<>(64);
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
materialManager.onOriginShift(this);
}
public void tick(double cameraX, double cameraY, double cameraZ) {
tick++;
// integer camera pos
int cX = (int) cameraX;
int cY = (int) cameraY;
int cZ = (int) cameraZ;
if (tickableInstances.size() > 0) {
for (ITickableInstance instance : tickableInstances.values()) {
if (!instance.decreaseTickRateWithDistance()) {
instance.tick();
continue;
}
BlockPos pos = instance.getWorldPosition();
int dX = pos.getX() - cX;
int dY = pos.getY() - cY;
int dZ = pos.getZ() - cZ;
if ((tick % getUpdateDivisor(dX, dY, dZ)) == 0)
instance.tick();
}
}
queuedUpdates.forEach(te -> {
queuedUpdates.remove(te);
update(te);
});
}
public void beginFrame(ActiveRenderInfo info) {
frame++;
processQueuedAdditions();
Vector3f look = info.getHorizontalPlane();
float lookX = look.getX();
float lookY = look.getY();
float lookZ = look.getZ();
// integer camera pos
int cX = (int) info.getProjectedView().x;
int cY = (int) info.getProjectedView().y;
int cZ = (int) info.getProjectedView().z;
if (dynamicInstances.size() > 0) {
dynamicInstances.object2ObjectEntrySet().fastForEach(e -> {
IDynamicInstance dyn = e.getValue();
if (!dyn.decreaseFramerateWithDistance() || shouldFrameUpdate(dyn.getWorldPosition(), lookX, lookY, lookZ, cX, cY, cZ))
dyn.beginFrame();
});
}
}
public void add(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
if (obj instanceof IInstanceRendered) {
addInternal(obj);
}
}
public synchronized void queueAdd(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
queuedAdditions.add(obj);
}
public void update(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
if (obj instanceof IInstanceRendered) {
IInstance instance = getInstance(obj, false);
if (instance != null) {
if (instance.shouldReset()) {
removeInternal(obj, instance);
createInternal(obj);
} else {
instance.update();
}
}
}
}
public synchronized void queueUpdate(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
queuedUpdates.add(obj);
}
public void onLightUpdate(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
if (obj instanceof IInstanceRendered) {
IInstance instance = getInstance(obj, false);
if (instance != null)
instance.updateLight();
}
}
public void remove(T obj) {
if (!Backend.getInstance().canUseInstancing()) return;
if (obj instanceof IInstanceRendered) {
IInstance instance = getInstance(obj, false);
if (instance != null)
removeInternal(obj, instance);
}
}
public void invalidate() {
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@SuppressWarnings("unchecked")
@Nullable
protected <I extends T> IInstance getInstance(I obj, boolean create) {
if (!Backend.getInstance().canUseInstancing()) return null;
IInstance instance = instances.get(obj);
if (instance != null) {
return instance;
} else if (create && canCreateInstance(obj)) {
return createInternal(obj);
} else {
return null;
}
}
protected synchronized void processQueuedAdditions() {
if (queuedAdditions.size() > 0) {
queuedAdditions.forEach(this::addInternal);
queuedAdditions.clear();
}
}
protected boolean shouldFrameUpdate(BlockPos worldPos, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) {
int dX = worldPos.getX() - cX;
int dY = worldPos.getY() - cY;
int dZ = worldPos.getZ() - cZ;
// is it more than 2 blocks behind the camera?
int dist = 2;
float dot = (dX + lookX * dist) * lookX + (dY + lookY * dist) * lookY + (dZ + lookZ * dist) * lookZ;
if (dot < 0) return false;
return (frame % getUpdateDivisor(dX, dY, dZ)) == 0;
}
protected int getUpdateDivisor(int dX, int dY, int dZ) {
int dSq = dX * dX + dY * dY + dZ * dZ;
return (dSq / 1024) + 1;
}
protected void addInternal(T tile) {
getInstance(tile, true);
}
protected void removeInternal(T obj, IInstance instance) {
instance.remove();
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
}
protected IInstance createInternal(T obj) {
IInstance renderer = createRaw(obj);
if (renderer != null) {
renderer.updateLight();
instances.put(obj, renderer);
if (renderer instanceof IDynamicInstance)
dynamicInstances.put(obj, (IDynamicInstance) renderer);
if (renderer instanceof ITickableInstance)
tickableInstances.put(obj, ((ITickableInstance) renderer));
}
return renderer;
}
@Override
public void onOriginShift() {
ArrayList<T> instancedTiles = new ArrayList<>(instances.keySet());
invalidate();
instancedTiles.forEach(this::add);
}
protected abstract IInstance createRaw(T obj);
protected abstract boolean canCreateInstance(T entity);
}

View file

@ -1,170 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.opengl.GL11;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.backend.model.IndexedModel;
import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.util.BufferBuilderReader;
import com.jozufozu.flywheel.util.RenderUtil;
import com.jozufozu.flywheel.util.VirtualEmptyModelData;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3i;
public class InstanceMaterial<D extends InstanceData> {
protected final Supplier<Vector3i> originCoordinate;
protected final Cache<Object, Instancer<D>> models;
protected final MaterialSpec<D> spec;
private final VertexFormat modelFormat;
public InstanceMaterial(Supplier<Vector3i> renderer, MaterialSpec<D> spec) {
this.originCoordinate = renderer;
this.spec = spec;
this.models = CacheBuilder.newBuilder()
.removalListener(notification -> {
Instancer<?> instancer = (Instancer<?>) notification.getValue();
RenderWork.enqueue(instancer::delete);
})
.build();
modelFormat = this.spec.getModelFormat();
}
public boolean nothingToRender() {
return models.size() > 0 && models.asMap().values().stream().allMatch(Instancer::empty);
}
public void delete() {
models.invalidateAll();
}
/**
* Clear all instance data without freeing resources.
*/
public void clear() {
models.asMap().values().forEach(Instancer::clear);
}
public void forEachInstancer(Consumer<Instancer<D>> f) {
for (Instancer<D> model : models.asMap().values()) {
f.accept(model);
}
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState) {
return get(partial, () -> buildModel(partial.get(), referenceState));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir) {
return getModel(partial, referenceState, dir, RenderUtil.rotateToFace(dir));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir, Supplier<MatrixStack> modelTransform) {
return get(Pair.of(dir, partial),
() -> buildModel(partial.get(), referenceState, modelTransform.get()));
}
public Instancer<D> getModel(BlockState toRender) {
return get(toRender, () -> buildModel(toRender));
}
public Instancer<D> get(Object key, Supplier<BufferedModel> supplier) {
try {
return models.get(key, () -> new Instancer<>(supplier.get(), originCoordinate, spec));
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
private BufferedModel buildModel(BlockState renderedState) {
BlockRendererDispatcher dispatcher = Minecraft.getInstance().getBlockRendererDispatcher();
return buildModel(dispatcher.getModelForState(renderedState), renderedState);
}
private BufferedModel buildModel(IBakedModel model, BlockState renderedState) {
return buildModel(model, renderedState, new MatrixStack());
}
private BufferedModel buildModel(IBakedModel model, BlockState referenceState, MatrixStack ms) {
BufferBuilderReader reader = new BufferBuilderReader(getBufferBuilder(model, referenceState, ms));
int vertexCount = reader.getVertexCount();
ByteBuffer vertices = ByteBuffer.allocate(vertexCount * modelFormat.getStride());
vertices.order(ByteOrder.nativeOrder());
for (int i = 0; i < vertexCount; i++) {
vertices.putFloat(reader.getX(i));
vertices.putFloat(reader.getY(i));
vertices.putFloat(reader.getZ(i));
vertices.put(reader.getNX(i));
vertices.put(reader.getNY(i));
vertices.put(reader.getNZ(i));
vertices.putFloat(reader.getU(i));
vertices.putFloat(reader.getV(i));
}
vertices.rewind();
// return new BufferedModel(GlPrimitive.QUADS, format, vertices, vertexCount);
return IndexedModel.fromSequentialQuads(modelFormat, vertices, vertexCount);
}
// DOWN, UP, NORTH, SOUTH, WEST, EAST, null
private static final Direction[] dirs;
static {
Direction[] directions = Direction.values();
dirs = Arrays.copyOf(directions, directions.length + 1);
}
public static BufferBuilder getBufferBuilder(IBakedModel model, BlockState referenceState, MatrixStack ms) {
Minecraft mc = Minecraft.getInstance();
BlockRendererDispatcher dispatcher = mc.getBlockRendererDispatcher();
BlockModelRenderer blockRenderer = dispatcher.getBlockModelRenderer();
BufferBuilder builder = new BufferBuilder(512);
// BakedQuadWrapper quadReader = new BakedQuadWrapper();
//
// IModelData modelData = model.getModelData(mc.world, BlockPos.ZERO.up(255), referenceState, VirtualEmptyModelData.INSTANCE);
// List<BakedQuad> quads = Arrays.stream(dirs)
// .flatMap(dir -> model.getQuads(referenceState, dir, mc.world.rand, modelData).stream())
// .collect(Collectors.toList());
builder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
blockRenderer.renderModel(mc.world, model, referenceState, BlockPos.ZERO.up(255), ms, builder, true,
mc.world.rand, 42, OverlayTexture.DEFAULT_UV, VirtualEmptyModelData.INSTANCE);
builder.finishDrawing();
return builder;
}
}

View file

@ -1,180 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.GL_TEXTURE4;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import java.util.BitSet;
import java.util.SortedSet;
import java.util.Vector;
import javax.annotation.Nonnull;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.CrumblingInstanceManager;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import com.jozufozu.flywheel.util.WorldAttached;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.DestroyBlockProgress;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.Texture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.LazyValue;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.IWorld;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT)
public class InstancedRenderDispatcher {
private static final WorldAttached<EntityInstanceManager> entityInstanceManager = new WorldAttached<>(world -> new EntityInstanceManager(Contexts.WORLD.getMaterialManager(world)));
private static final WorldAttached<TileInstanceManager> tileInstanceManager = new WorldAttached<>(world -> new TileInstanceManager(Contexts.WORLD.getMaterialManager(world)));
private static final LazyValue<Vector<CrumblingInstanceManager>> blockBreaking = new LazyValue<>(() -> {
Vector<CrumblingInstanceManager> renderers = new Vector<>(10);
for (int i = 0; i < 10; i++) {
renderers.add(new CrumblingInstanceManager());
}
return renderers;
});
@Nonnull
public static TileInstanceManager getTiles(IWorld world) {
return tileInstanceManager.get(world);
}
@Nonnull
public static EntityInstanceManager getEntities(IWorld world) {
return entityInstanceManager.get(world);
}
public static void tick() {
Minecraft mc = Minecraft.getInstance();
ClientWorld world = mc.world;
Entity renderViewEntity = mc.renderViewEntity != null ? mc.renderViewEntity : mc.player;
if (renderViewEntity == null) return;
getTiles(world).tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
getEntities(world).tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
}
public static void enqueueUpdate(TileEntity te) {
getTiles(te.getWorld()).queueUpdate(te);
}
public static void enqueueUpdate(Entity entity) {
getEntities(entity.world).queueUpdate(entity);
}
@SubscribeEvent
public static void onBeginFrame(BeginFrameEvent event) {
Contexts.WORLD.getMaterialManager(event.getWorld())
.checkAndShiftOrigin(event.getInfo());
getTiles(event.getWorld()).beginFrame(event.getInfo());
getEntities(event.getWorld()).beginFrame(event.getInfo());
}
@SubscribeEvent
public static void renderLayer(RenderLayerEvent event) {
ClientWorld world = event.getWorld();
if (!Backend.getInstance().canUseInstancing(world)) return;
event.type.startDrawing();
Contexts.WORLD.getMaterialManager(world)
.render(event.type, event.viewProjection, event.camX, event.camY, event.camZ);
event.type.endDrawing();
}
@SubscribeEvent
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientWorld world = event.getWorld();
if (Backend.getInstance().canUseInstancing() && world != null) {
Contexts.WORLD.getMaterialManager(world).delete();
TileInstanceManager tiles = getTiles(world);
tiles.invalidate();
world.loadedTileEntityList.forEach(tiles::add);
EntityInstanceManager entities = getEntities(world);
entities.invalidate();
world.getAllEntities().forEach(entities::add);
}
}
private static final RenderType crumblingLayer = ModelBakery.BLOCK_DESTRUCTION_RENDER_LAYERS.get(0);
public static void renderBreaking(ClientWorld world, Matrix4f viewProjection, double cameraX, double cameraY, double cameraZ) {
if (!Backend.getInstance().canUseInstancing(world)) return;
WorldRenderer worldRenderer = Minecraft.getInstance().worldRenderer;
Long2ObjectMap<SortedSet<DestroyBlockProgress>> breakingProgressions = worldRenderer.blockBreakingProgressions;
if (breakingProgressions.isEmpty()) return;
Vector<CrumblingInstanceManager> renderers = blockBreaking.getValue();
BitSet bitSet = new BitSet(10);
for (Long2ObjectMap.Entry<SortedSet<DestroyBlockProgress>> entry : breakingProgressions.long2ObjectEntrySet()) {
BlockPos breakingPos = BlockPos.fromLong(entry.getLongKey());
SortedSet<DestroyBlockProgress> progresses = entry.getValue();
if (progresses != null && !progresses.isEmpty()) {
int blockDamage = progresses.last().getPartialBlockDamage();
bitSet.set(blockDamage);
renderers.get(blockDamage).add(world.getTileEntity(breakingPos));
}
}
TextureManager textureManager = Minecraft.getInstance().textureManager;
ActiveRenderInfo info = Minecraft.getInstance().gameRenderer.getActiveRenderInfo();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, textureManager.getTexture(PlayerContainer.BLOCK_ATLAS_TEXTURE).getGlTextureId());
glActiveTexture(GL_TEXTURE4);
crumblingLayer.startDrawing();
bitSet.stream().forEach(i -> {
Texture breaking = textureManager.getTexture(ModelBakery.BLOCK_DESTRUCTION_STAGE_TEXTURES.get(i));
CrumblingInstanceManager renderer = renderers.get(i);
renderer.beginFrame(info);
if (breaking != null) {
glBindTexture(GL_TEXTURE_2D, breaking.getGlTextureId());
renderer.materialManager.render(RenderType.getCutoutMipped(), viewProjection, cameraX, cameraY, cameraZ);
}
renderer.invalidate();
});
crumblingLayer.endDrawing();
glActiveTexture(GL_TEXTURE0);
Texture breaking = textureManager.getTexture(ModelBakery.BLOCK_DESTRUCTION_STAGE_TEXTURES.get(0));
if (breaking != null)
glBindTexture(GL_TEXTURE_2D, breaking.getGlTextureId());
}
}

View file

@ -1,63 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.Map;
import javax.annotation.Nullable;
import com.google.common.collect.Maps;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstance;
import com.jozufozu.flywheel.backend.instancing.entity.IEntityInstanceFactory;
import com.jozufozu.flywheel.backend.instancing.tile.ITileInstanceFactory;
import com.jozufozu.flywheel.backend.instancing.tile.TileEntityInstance;
import com.simibubi.create.AllEntityTypes;
import com.simibubi.create.content.contraptions.components.structureMovement.glue.GlueInstance;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
public class InstancedRenderRegistry {
private static final InstancedRenderRegistry INSTANCE = new InstancedRenderRegistry();
public static InstancedRenderRegistry getInstance() {
return INSTANCE;
}
private final Map<TileEntityType<?>, ITileInstanceFactory<?>> tiles = Maps.newHashMap();
private final Map<EntityType<?>, IEntityInstanceFactory<?>> entities = Maps.newHashMap();
public <T extends TileEntity> void register(TileEntityType<? extends T> type, ITileInstanceFactory<? super T> rendererFactory) {
this.tiles.put(type, rendererFactory);
}
public <T extends Entity> void register(EntityType<? extends T> type, IEntityInstanceFactory<? super T> rendererFactory) {
this.entities.put(type, rendererFactory);
}
static {
INSTANCE.register(AllEntityTypes.SUPER_GLUE.get(), GlueInstance::new);
}
@SuppressWarnings("unchecked")
@Nullable
public <T extends TileEntity> TileEntityInstance<? super T> create(MaterialManager<?> manager, T tile) {
TileEntityType<?> type = tile.getType();
ITileInstanceFactory<? super T> factory = (ITileInstanceFactory<? super T>) this.tiles.get(type);
if (factory == null) return null;
else return factory.create(manager, tile);
}
@SuppressWarnings("unchecked")
@Nullable
public <T extends Entity> EntityInstance<? super T> create(MaterialManager<?> manager, T tile) {
EntityType<?> type = tile.getType();
IEntityInstanceFactory<? super T> factory = (IEntityInstanceFactory<? super T>) this.entities.get(type);
if (factory == null) return null;
else return factory.create(manager, tile);
}
}

View file

@ -1,252 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.util.AttribUtil;
import net.minecraft.util.math.vector.Vector3i;
public class Instancer<D extends InstanceData> {
public final Supplier<Vector3i> originCoordinate;
protected final BufferedModel model;
protected final VertexFormat instanceFormat;
protected final IInstanceFactory<D> factory;
protected GlVertexArray vao;
protected GlBuffer instanceVBO;
protected int glBufferSize = -1;
protected int glInstanceCount = 0;
private boolean deleted;
protected final ArrayList<D> data = new ArrayList<>();
boolean anyToRemove;
boolean anyToUpdate;
public Instancer(BufferedModel model, Supplier<Vector3i> originCoordinate, MaterialSpec<D> spec) {
this.model = model;
this.factory = spec.getInstanceFactory();
this.instanceFormat = spec.getInstanceFormat();
this.originCoordinate = originCoordinate;
if (model.getVertexCount() <= 0)
throw new IllegalArgumentException("Refusing to instance a model with no vertices.");
vao = new GlVertexArray();
instanceVBO = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vao.bind();
// bind the model's vbo to our vao
model.setupState();
AttribUtil.enableArrays(model.getAttributeCount() + instanceFormat.getAttributeCount());
vao.unbind();
model.clearState();
}
public void render() {
if (deleted) return;
vao.bind();
renderSetup();
if (glInstanceCount > 0)
model.drawInstances(glInstanceCount);
vao.unbind();
}
public D createInstance() {
D instanceData = factory.create(this);
instanceData.dirty = true;
anyToUpdate = true;
data.add(instanceData);
return instanceData;
}
public boolean empty() {
return !anyToUpdate && !anyToRemove && glInstanceCount == 0;
}
/**
* Clear all instance data without freeing resources.
*/
public void clear() {
data.clear();
anyToRemove = true;
}
/**
* Free acquired resources. Attempting to use this after calling delete is undefined behavior.
*/
public void delete() {
if (deleted) return;
deleted = true;
model.delete();
instanceVBO.delete();
vao.delete();
}
protected void renderSetup() {
if (anyToRemove) {
removeDeletedInstances();
}
instanceVBO.bind();
if (!realloc()) {
if (anyToRemove) {
clearBufferTail();
}
if (anyToUpdate) {
updateBuffer();
}
glInstanceCount = data.size();
}
instanceVBO.unbind();
anyToRemove = anyToUpdate = false;
}
private void clearBufferTail() {
int size = data.size();
final int offset = size * instanceFormat.getStride();
final int length = glBufferSize - offset;
if (length > 0) {
instanceVBO.getBuffer(offset, length)
.putByteArray(new byte[length])
.flush();
}
}
private void updateBuffer() {
final int size = data.size();
if (size <= 0) return;
final int stride = instanceFormat.getStride();
final BitSet dirtySet = getDirtyBitSet();
if (dirtySet.isEmpty()) return;
final int firstDirty = dirtySet.nextSetBit(0);
final int lastDirty = dirtySet.previousSetBit(size);
final int offset = firstDirty * stride;
final int length = (1 + lastDirty - firstDirty) * stride;
if (length > 0) {
MappedBuffer mapped = instanceVBO.getBuffer(offset, length);
dirtySet.stream().forEach(i -> {
final D d = data.get(i);
mapped.position(i * stride);
d.write(mapped);
});
mapped.flush();
}
}
private BitSet getDirtyBitSet() {
final int size = data.size();
final BitSet dirtySet = new BitSet(size);
for (int i = 0; i < size; i++) {
D element = data.get(i);
if (element.dirty) {
dirtySet.set(i);
element.dirty = false;
}
}
return dirtySet;
}
private boolean realloc() {
int size = this.data.size();
int stride = instanceFormat.getStride();
int requiredSize = size * stride;
if (requiredSize > glBufferSize) {
glBufferSize = requiredSize + stride * 16;
instanceVBO.alloc(glBufferSize);
MappedBuffer buffer = instanceVBO.getBuffer(0, glBufferSize);
for (D datum : data) {
datum.write(buffer);
}
buffer.flush();
glInstanceCount = size;
informAttribDivisors();
return true;
}
return false;
}
private void removeDeletedInstances() {
// Figure out which elements are to be removed.
final int oldSize = this.data.size();
int removeCount = 0;
final BitSet removeSet = new BitSet(oldSize);
for (int i = 0; i < oldSize; i++) {
final D element = this.data.get(i);
if (element.removed) {
removeSet.set(i);
removeCount++;
}
}
final int newSize = oldSize - removeCount;
// shift surviving elements left over the spaces left by removed elements
for (int i = 0, j = 0; (i < oldSize) && (j < newSize); i++, j++) {
i = removeSet.nextClearBit(i);
if (i != j) {
D element = data.get(i);
data.set(j, element);
element.dirty = true;
}
}
anyToUpdate = true;
data.subList(newSize, oldSize).clear();
}
private void informAttribDivisors() {
int staticAttributes = model.getAttributeCount();
instanceFormat.vertexAttribPointers(staticAttributes);
for (int i = 0; i < instanceFormat.getAttributeCount(); i++) {
Backend.getInstance().compat.instancedArrays.vertexAttribDivisor(i + staticAttributes, 1);
}
}
}

View file

@ -1,161 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.WorldContext;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import com.jozufozu.flywheel.core.shader.IProgramCallback;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.util.WeakHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3i;
public class MaterialManager<P extends WorldProgram> {
public static int MAX_ORIGIN_DISTANCE = 100;
protected final WorldContext<P> context;
protected final Map<MaterialSpec<?>, InstanceMaterial<?>> atlasMaterials;
protected final ArrayList<MaterialRenderer<P>> atlasRenderers;
protected final Map<ResourceLocation, ArrayList<MaterialRenderer<P>>> renderers;
protected final Map<ResourceLocation, Map<MaterialSpec<?>, InstanceMaterial<?>>> materials;
private BlockPos originCoordinate = BlockPos.ZERO;
private final WeakHashSet<OriginShiftListener> listeners;
public MaterialManager(WorldContext<P> context) {
this.context = context;
this.atlasMaterials = new HashMap<>();
this.atlasRenderers = new ArrayList<>(Backend.getInstance().allMaterials().size());
this.materials = new HashMap<>();
this.renderers = new HashMap<>();
this.listeners = new WeakHashSet<>();
}
/**
* Render every model for every material.
*
* @param layer Which vanilla {@link RenderType} is being drawn?
* @param viewProjection How do we get from camera space to clip space?
*/
public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ) {
render(layer, viewProjection, camX, camY, camZ, null);
}
/**
* Render every model for every material.
*
* @param layer Which vanilla {@link RenderType} is being drawn?
* @param viewProjection How do we get from camera space to clip space?
* @param callback Provide additional uniforms or state here.
*/
public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ, IProgramCallback<P> callback) {
camX -= originCoordinate.getX();
camY -= originCoordinate.getY();
camZ -= originCoordinate.getZ();
Matrix4f translate = Matrix4f.translate((float) -camX, (float) -camY, (float) -camZ);
translate.multiplyBackward(viewProjection);
for (MaterialRenderer<P> material : atlasRenderers) {
material.render(layer, translate, camX, camY, camZ, callback);
}
for (Map.Entry<ResourceLocation, ArrayList<MaterialRenderer<P>>> entry : renderers.entrySet()) {
Minecraft.getInstance().textureManager.bindTexture(entry.getKey());
for (MaterialRenderer<P> materialRenderer : entry.getValue()) {
materialRenderer.render(layer, translate, camX, camY, camZ, callback);
}
}
}
public void delete() {
atlasMaterials.values().forEach(InstanceMaterial::delete);
materials.values().stream().flatMap(m -> m.values().stream()).forEach(InstanceMaterial::delete);
}
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType) {
return (InstanceMaterial<D>) this.atlasMaterials.computeIfAbsent(materialType, type -> {
InstanceMaterial<?> material = new InstanceMaterial<>(this::getOriginCoordinate, type);
this.atlasRenderers.add(new MaterialRenderer<>(context.getProgramSupplier(type.getProgramName()), material));
return material;
});
}
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType, ResourceLocation texture) {
return (InstanceMaterial<D>) materials.computeIfAbsent(texture, $ -> new HashMap<>())
.computeIfAbsent(materialType, type -> {
InstanceMaterial<?> material = new InstanceMaterial<>(this::getOriginCoordinate, type);
this.renderers.computeIfAbsent(texture, $ -> new ArrayList<>())
.add(new MaterialRenderer<>(context.getProgramSupplier(type.getProgramName()), material));
return material;
});
}
public InstanceMaterial<ModelData> getTransformMaterial() {
return getMaterial(Materials.TRANSFORMED);
}
public InstanceMaterial<OrientedData> getOrientedMaterial() {
return getMaterial(Materials.ORIENTED);
}
public Vector3i getOriginCoordinate() {
return originCoordinate;
}
public void onOriginShift(OriginShiftListener listener) {
listeners.add(listener);
}
public void checkAndShiftOrigin(ActiveRenderInfo info) {
int cX = MathHelper.floor(info.getProjectedView().x);
int cY = MathHelper.floor(info.getProjectedView().y);
int cZ = MathHelper.floor(info.getProjectedView().z);
int dX = cX - originCoordinate.getX();
int dY = cY - originCoordinate.getY();
int dZ = cZ - originCoordinate.getZ();
if (Math.abs(dX) > MAX_ORIGIN_DISTANCE || Math.abs(dY) > MAX_ORIGIN_DISTANCE || Math.abs(dZ) > MAX_ORIGIN_DISTANCE) {
originCoordinate = new BlockPos(cX, cY, cZ);
materials.values().stream().flatMap(m -> m.values().stream()).forEach(InstanceMaterial::clear);
atlasMaterials.values().forEach(InstanceMaterial::clear);
listeners.forEach(OriginShiftListener::onOriginShift);
}
}
@FunctionalInterface
public interface OriginShiftListener {
void onOriginShift();
}
}

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.function.Supplier;
import com.jozufozu.flywheel.core.shader.IProgramCallback;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.math.vector.Matrix4f;
public class MaterialRenderer<P extends WorldProgram> {
private final Supplier<P> program;
private final InstanceMaterial<?> material;
public MaterialRenderer(Supplier<P> programSupplier, InstanceMaterial<?> material) {
this.program = programSupplier;
this.material = material;
}
public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ, IProgramCallback<P> setup) {
if (!(layer == RenderType.getCutoutMipped())) return;
if (material.nothingToRender()) return;
P program = this.program.get();
program.bind();
program.uploadViewProjection(viewProjection);
program.uploadCameraPos(camX, camY, camZ);
if (setup != null) setup.call(program);
makeRenderCalls();
}
protected void makeRenderCalls() {
material.forEachInstancer(Instancer::render);
}
}

View file

@ -1,47 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
public class MaterialSpec<D extends InstanceData> {
public final ResourceLocation name;
private final ResourceLocation programSpec;
private final VertexFormat modelFormat;
private final VertexFormat instanceFormat;
private final IInstanceFactory<D> instanceFactory;
private final ResourceLocation texture;
public MaterialSpec(ResourceLocation name, ResourceLocation programSpec, VertexFormat modelFormat, VertexFormat instanceFormat, IInstanceFactory<D> instanceFactory) {
this(name, programSpec, modelFormat, instanceFormat, PlayerContainer.BLOCK_ATLAS_TEXTURE, instanceFactory);
}
public MaterialSpec(ResourceLocation name, ResourceLocation programSpec, VertexFormat modelFormat, VertexFormat instanceFormat, ResourceLocation texture, IInstanceFactory<D> instanceFactory) {
this.name = name;
this.programSpec = programSpec;
this.modelFormat = modelFormat;
this.instanceFormat = instanceFormat;
this.instanceFactory = instanceFactory;
this.texture = texture;
}
public ResourceLocation getProgramName() {
return programSpec;
}
public VertexFormat getModelFormat() {
return modelFormat;
}
public VertexFormat getInstanceFormat() {
return instanceFormat;
}
public IInstanceFactory<D> getInstanceFactory() {
return instanceFactory;
}
}

View file

@ -1,133 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.entity;
import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceMaterial;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.util.math.vector.Vector3i;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
/**
* The layer between a {@link TileEntity} and the Flywheel backend.
**
* <br><br> There are a few additional features that overriding classes can opt in to:
* <ul>
* <li>{@link IDynamicInstance}</li>
* <li>{@link ITickableInstance}</li>
* </ul>
* See the interfaces' documentation for more information about each one.
*
* <br> Implementing one or more of these will give a {@link EntityInstance} access
* to more interesting and regular points within a tick or a frame.
*
* @param <E> The type of {@link Entity} your class is an instance of.
*/
public abstract class EntityInstance<E extends Entity> implements IInstance {
protected final MaterialManager<?> materialManager;
protected final E entity;
protected final World world;
public EntityInstance(MaterialManager<?> materialManager, E entity) {
this.materialManager = materialManager;
this.entity = entity;
this.world = entity.world;
}
/**
* Free any acquired resources.
*/
public abstract void remove();
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
* Don't query lighting data here, that's handled separately in {@link #updateLight()}.
*
* <br><br> If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}.
*/
public void update() {
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
/**
* Just before {@link #update()} would be called, <code>shouldReset()</code> is checked.
* If this function returns <code>true</code>, then this instance will be {@link #remove removed},
* and another instance will be constructed to replace it. This allows for more sane resource
* acquisition compared to trying to update everything within the lifetime of an instance.
*
* @return <code>true</code> if this instance should be discarded and refreshed.
*/
public boolean shouldReset() {
return false;
}
/**
* In order to accommodate for floating point precision errors at high coordinates,
* {@link TileInstanceManager}s are allowed to arbitrarily adjust the origin, and
* shift the world matrix provided as a shader uniform accordingly.
*
* @return The {@link BlockPos position} of the {@link Entity} this instance
* represents should be rendered at to appear in the correct location.
*/
public Vector3f getInstancePosition() {
Vector3d pos = entity.getPositionVec();
Vector3i origin = materialManager.getOriginCoordinate();
return new Vector3f(
(float) (pos.x - origin.getX()),
(float) (pos.y - origin.getY()),
(float) (pos.z - origin.getZ())
);
}
@Override
public BlockPos getWorldPosition() {
return entity.getBlockPos();
}
protected void relight(BlockPos pos, IFlatLight<?>... models) {
relight(world.getLightLevel(LightType.BLOCK, pos), world.getLightLevel(LightType.SKY, pos), models);
}
protected <L extends IFlatLight<?>> void relight(BlockPos pos, Stream<L> models) {
relight(world.getLightLevel(LightType.BLOCK, pos), world.getLightLevel(LightType.SKY, pos), models);
}
protected void relight(int block, int sky, IFlatLight<?>... models) {
relight(block, sky, Arrays.stream(models));
}
protected <L extends IFlatLight<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block).setSkyLight(sky));
}
protected InstanceMaterial<ModelData> getTransformMaterial() {
return materialManager.getTransformMaterial();
}
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
return materialManager.getOrientedMaterial();
}
}

View file

@ -1,43 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.entity;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
public class EntityInstanceManager extends InstanceManager<Entity> {
public EntityInstanceManager(MaterialManager<?> materialManager) {
super(materialManager);
}
@Override
protected IInstance createRaw(Entity obj) {
return InstancedRenderRegistry.getInstance().create(materialManager, obj);
}
@Override
protected boolean canCreateInstance(Entity entity) {
if (!entity.isAlive()) return false;
World world = entity.world;
if (world == null) return false;
if (Backend.isFlywheelWorld(world)) {
BlockPos pos = entity.getBlockPos();
IBlockReader existingChunk = world.getExistingChunk(pos.getX() >> 4, pos.getZ() >> 4);
return existingChunk != null;
}
return false;
}
}

View file

@ -1,10 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.entity;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import net.minecraft.entity.Entity;
@FunctionalInterface
public interface IEntityInstanceFactory<E extends Entity> {
EntityInstance<? super E> create(MaterialManager<?> manager, E te);
}

View file

@ -1,10 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.tile;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import net.minecraft.tileentity.TileEntity;
@FunctionalInterface
public interface ITileInstanceFactory<T extends TileEntity> {
TileEntityInstance<? super T> create(MaterialManager<?> manager, T te);
}

View file

@ -1,130 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.tile;
import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.IDynamicInstance;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.ITickableInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceMaterial;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
/**
* The layer between a {@link TileEntity} and the Flywheel backend.
*
* <br><br> {@link #updateLight()} is called after construction.
*
* <br><br> There are a few additional features that overriding classes can opt in to:
* <ul>
* <li>{@link IDynamicInstance}</li>
* <li>{@link ITickableInstance}</li>
* </ul>
* See the interfaces' documentation for more information about each one.
*
* <br> Implementing one or more of these will give a {@link TileEntityInstance} access
* to more interesting and regular points within a tick or a frame.
*
* @param <T> The type of {@link TileEntity} your class is an instance of.
*/
public abstract class TileEntityInstance<T extends TileEntity> implements IInstance {
protected final MaterialManager<?> materialManager;
protected final T tile;
protected final World world;
protected final BlockPos pos;
protected final BlockPos instancePos;
protected final BlockState blockState;
public TileEntityInstance(MaterialManager<?> materialManager, T tile) {
this.materialManager = materialManager;
this.tile = tile;
this.world = tile.getWorld();
this.pos = tile.getPos();
this.blockState = tile.getBlockState();
this.instancePos = pos.subtract(materialManager.getOriginCoordinate());
}
/**
* Update instance data here. Good for when data doesn't change very often and when animations are GPU based.
* Don't query lighting data here, that's handled separately in {@link #updateLight()}.
*
* <br><br> If your animations are complex or more CPU driven, see {@link IDynamicInstance} or {@link ITickableInstance}.
*/
public void update() {
}
/**
* Called after construction and when a light update occurs in the world.
*
* <br> If your model needs it, update light here.
*/
public void updateLight() {
}
/**
* Free any acquired resources.
*/
public abstract void remove();
/**
* Just before {@link #update()} would be called, <code>shouldReset()</code> is checked.
* If this function returns <code>true</code>, then this instance will be {@link #remove removed},
* and another instance will be constructed to replace it. This allows for more sane resource
* acquisition compared to trying to update everything within the lifetime of an instance.
*
* @return <code>true</code> if this instance should be discarded and refreshed.
*/
public boolean shouldReset() {
return tile.getBlockState() != blockState;
}
/**
* In order to accommodate for floating point precision errors at high coordinates,
* {@link TileInstanceManager}s are allowed to arbitrarily adjust the origin, and
* shift the world matrix provided as a shader uniform accordingly.
*
* @return The {@link BlockPos position} of the {@link TileEntity} this instance
* represents should be rendered at to appear in the correct location.
*/
public BlockPos getInstancePosition() {
return pos.subtract(materialManager.getOriginCoordinate());
}
@Override
public BlockPos getWorldPosition() {
return pos;
}
protected void relight(BlockPos pos, IFlatLight<?>... models) {
relight(world.getLightLevel(LightType.BLOCK, pos), world.getLightLevel(LightType.SKY, pos), models);
}
protected <L extends IFlatLight<?>> void relight(BlockPos pos, Stream<L> models) {
relight(world.getLightLevel(LightType.BLOCK, pos), world.getLightLevel(LightType.SKY, pos), models);
}
protected void relight(int block, int sky, IFlatLight<?>... models) {
relight(block, sky, Arrays.stream(models));
}
protected <L extends IFlatLight<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block).setSkyLight(sky));
}
protected InstanceMaterial<ModelData> getTransformMaterial() {
return materialManager.getTransformMaterial();
}
protected InstanceMaterial<OrientedData> getOrientedMaterial() {
return materialManager.getOrientedMaterial();
}
}

View file

@ -1,45 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.tile;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.IInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
public class TileInstanceManager extends InstanceManager<TileEntity> {
public TileInstanceManager(MaterialManager<?> materialManager) {
super(materialManager);
}
@Override
protected IInstance createRaw(TileEntity obj) {
return InstancedRenderRegistry.getInstance().create(materialManager, obj);
}
@Override
protected boolean canCreateInstance(TileEntity tile) {
if (tile.isRemoved()) return false;
World world = tile.getWorld();
if (world == null) return false;
if (world.isAirBlock(tile.getPos())) return false;
if (Backend.isFlywheelWorld(world)) {
BlockPos pos = tile.getPos();
IBlockReader existingChunk = world.getExistingChunk(pos.getX() >> 4, pos.getZ() >> 4);
return existingChunk != null;
}
return false;
}
}

View file

@ -1,7 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
@FunctionalInterface
public interface IProcessingStage {
void process(Shader shader);
}

View file

@ -1,39 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class InstancedArraysTemplate extends ProgramTemplate {
public static final String vertexData = "VertexData";
public static final String instanceData = "InstanceData";
public static final String fragment = "Fragment";
public static final String vertexPrefix = "a_v_";
public static final String instancePrefix = "a_i_";
public static final String[] requiredVert = new String[]{instanceData, vertexData, fragment};
public static final String[] requiredFrag = {fragment};
public static final ResourceLocation vert = new ResourceLocation(Flywheel.ID, "template/instanced/instanced.vert");
public static final ResourceLocation frag = new ResourceLocation(Flywheel.ID, "template/instanced/instanced.frag");
public InstancedArraysTemplate(ShaderSources loader) {
super(loader);
templates.put(ShaderType.VERTEX, new ShaderTemplate(requiredVert, loader.getShaderSource(vert)));
templates.put(ShaderType.FRAGMENT, new ShaderTemplate(requiredFrag, loader.getShaderSource(frag)));
}
@Override
public void attachAttributes(Program builder) {
Shader shader = builder.attached.get(ShaderType.VERTEX);
shader.getTag(vertexData).addPrefixedAttributes(builder, vertexPrefix);
shader.getTag(instanceData).addPrefixedAttributes(builder, instancePrefix);
}
}

View file

@ -1,19 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
public class LayoutTag {
public static final Pattern pattern = Pattern.compile("Layout\\((\\w+)(?:\\s*,\\s*(\\w*))?\\)");
final GlNumericType type;
final boolean normalized;
public LayoutTag(Matcher matcher) {
type = GlNumericType.byName(matcher.group(1));
normalized = Boolean.parseBoolean(matcher.group(2));
}
}

View file

@ -1,35 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class ModelTemplate extends ProgramTemplate {
public static final String vertexData = "VertexData";
public static final String fragment = "Fragment";
public static final String vertexPrefix = "a_v_";
public static final String[] requiredVert = new String[]{vertexData, fragment};
public static final String[] requiredFrag = {fragment};
public static final ResourceLocation vert = new ResourceLocation(Flywheel.ID, "template/model/model.vert");
public static final ResourceLocation frag = new ResourceLocation(Flywheel.ID, "template/model/model.frag");
public ModelTemplate(ShaderSources loader) {
super(loader);
templates.put(ShaderType.VERTEX, new ShaderTemplate(requiredVert, loader.getShaderSource(vert)));
templates.put(ShaderType.FRAGMENT, new ShaderTemplate(requiredFrag, loader.getShaderSource(frag)));
}
@Override
public void attachAttributes(Program builder) {
Shader shader = builder.attached.get(ShaderType.VERTEX);
shader.getTag(vertexData).addPrefixedAttributes(builder, vertexPrefix);
}
}

View file

@ -1,69 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_TRUE;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glBindAttribLocation;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import java.util.EnumMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class Program {
public final ResourceLocation name;
public final int program;
private int attributeIndex;
public final Map<ShaderType, Shader> attached;
public Program(ResourceLocation name) {
this.name = name;
this.program = glCreateProgram();
attached = new EnumMap<>(ShaderType.class);
}
public Program attachShader(Shader shader, GlShader glShader) {
glAttachShader(this.program, glShader.handle());
attached.put(shader.type, shader);
return this;
}
public Program addAttribute(String name, int attributeCount) {
glBindAttribLocation(this.program, attributeIndex, name);
attributeIndex += attributeCount;
return this;
}
/**
* Links the attached shaders to this program.
*/
public Program link() {
glLinkProgram(this.program);
String log = glGetProgramInfoLog(this.program);
if (!log.isEmpty()) {
Backend.log.debug("Program link log for " + this.name + ": " + log);
}
int result = glGetProgrami(this.program, GL_LINK_STATUS);
if (result != GL_TRUE) {
throw new RuntimeException("Shader program linking failed, see log for details");
}
return this;
}
}

View file

@ -1,30 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.EnumMap;
import java.util.Map;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
public abstract class ProgramTemplate implements IProcessingStage {
protected final ShaderSources loader;
protected Map<ShaderType, ShaderTemplate> templates = new EnumMap<>(ShaderType.class);
public ProgramTemplate(ShaderSources loader) {
this.loader = loader;
}
@Override
public void process(Shader shader) {
ShaderTemplate template = templates.get(shader.type);
if (template == null) return;
shader.setSource(template.apply(shader));
}
public void attachAttributes(Program builder) {
}
}

View file

@ -1,149 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import net.minecraft.util.ResourceLocation;
public class Shader {
// #flwinclude <"valid_namespace:valid/path_to_file.glsl">
private static final Pattern includePattern = Pattern.compile("#flwinclude <\"([\\w\\d_]+:[\\w\\d_./]+)\">");
public static final Pattern versionDetector = Pattern.compile("#version[^\\n]*");
private static final Pattern decorator = Pattern.compile("#\\[([\\w_]*)]");
public final ResourceLocation name;
public ShaderType type;
private String source;
private final ShaderSources loader;
private boolean parsed = false;
final List<TaggedStruct> structs = new ArrayList<>(3);
final Map<String, TaggedStruct> tag2Struct = new HashMap<>();
final Map<String, TaggedStruct> name2Struct = new HashMap<>();
public Shader(ShaderSources loader, ShaderType type, ResourceLocation name, String source) {
this.loader = loader;
this.type = type;
this.name = name;
this.source = source;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public TaggedStruct getTag(String tag) {
checkAndParse();
return tag2Struct.get(tag);
}
public TaggedStruct getStruct(String name) {
checkAndParse();
return name2Struct.get(name);
}
private void checkAndParse() {
if (!parsed) {
parsed = true;
parseStructs();
}
}
public void defineAll(Collection<String> defines) {
Matcher matcher = versionDetector.matcher(source);
if (matcher.find()) {
StringBuffer sourceWithDefines = new StringBuffer();
String lines = defines.stream().map(it -> "#define " + it).collect(Collectors.joining("\n"));
matcher.appendReplacement(sourceWithDefines, matcher.group() + '\n' + lines);
matcher.appendTail(sourceWithDefines);
source = sourceWithDefines.toString();
}
}
public void parseStructs() {
Matcher structMatcher = TaggedStruct.taggedStruct.matcher(source);
StringBuffer strippedSrc = new StringBuffer();
while (structMatcher.find()) {
TaggedStruct struct = new TaggedStruct(structMatcher);
structs.add(struct);
structMatcher.appendReplacement(strippedSrc, decorator.matcher(struct.source).replaceFirst(""));
tag2Struct.put(struct.tag, struct);
name2Struct.put(struct.name, struct);
}
structMatcher.appendTail(strippedSrc);
this.source = strippedSrc.toString();
}
public void processIncludes() {
HashSet<ResourceLocation> seen = new HashSet<>();
seen.add(name);
source = includeRecursive(source, seen).collect(Collectors.joining("\n"));
}
private Stream<String> includeRecursive(String source, Set<ResourceLocation> seen) {
return lines(source).flatMap(line -> {
Matcher matcher = includePattern.matcher(line);
if (matcher.find()) {
String includeName = matcher.group(1);
ResourceLocation include = new ResourceLocation(includeName);
if (seen.add(include)) {
try {
return includeRecursive(loader.getShaderSource(include), seen);
} catch (ShaderLoadingException e) {
throw new ShaderLoadingException("could not resolve import: " + e.getMessage());
}
}
}
return Stream.of(line);
});
}
public void printSource() {
Backend.log.debug("Source for shader '" + name + "':");
int i = 1;
for (String s : source.split("\n")) {
Backend.log.debug(String.format("%1$4s: ", i++) + s);
}
}
public static Stream<String> lines(String s) {
return new BufferedReader(new StringReader(s)).lines();
}
}

View file

@ -1,22 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
public class ShaderLoadingException extends RuntimeException {
public ShaderLoadingException() {
}
public ShaderLoadingException(String message) {
super(message);
}
public ShaderLoadingException(String message, Throwable cause) {
super(message, cause);
}
public ShaderLoadingException(Throwable cause) {
super(cause);
}
public ShaderLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,121 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ShaderTemplate {
private static final String delimiter = "#flwbeginbody";
private static final Pattern headerFinder = Pattern.compile(delimiter);
private static final Pattern prefixer = Pattern.compile("#FLWPrefixFields\\((\\w+),\\s*(\\w+),\\s*([\\w\\d]+)\\)");
private static final Pattern assigner = Pattern.compile("#FLWAssignFields\\(([\\w\\d_]+),\\s*([\\w\\d_.]+),\\s*([\\w\\d_.]+)\\)");
final String[] requiredStructs;
final String header;
final String body;
public ShaderTemplate(String[] requiredStructs, String templateSrc) {
this.requiredStructs = requiredStructs;
Matcher matcher = headerFinder.matcher(templateSrc);
if (!matcher.find()) {
throw new RuntimeException("Shader template must have a header and footer delimited by '" + delimiter + "'");
}
this.header = templateSrc.substring(0, matcher.start());
this.body = templateSrc.substring(matcher.end());
}
public String apply(Shader shader) {
return header +
shader.getSource() +
processBody(shader);
}
public String processBody(Shader shader) {
String s = body;
List<String> missing = new ArrayList<>();
for (String name : requiredStructs) {
TaggedStruct struct = shader.getTag(name);
if (struct != null) {
s = s.replace(name, struct.name);
} else {
missing.add(name);
}
}
if (!missing.isEmpty()) {
String err = shader.name + " is missing: " + String.join(", ", missing);
throw new RuntimeException(err);
}
s = fillPrefixes(shader, s);
s = fillAssigns(shader, s);
return s;
}
private String fillPrefixes(Shader shader, String s) {
Matcher prefixMatches = prefixer.matcher(s);
StringBuffer out = new StringBuffer();
while (prefixMatches.find()) {
String structName = prefixMatches.group(1);
String modifier = prefixMatches.group(2);
String prefix = prefixMatches.group(3);
TaggedStruct struct = shader.getStruct(structName);
StringBuilder builder = new StringBuilder();
for (TaggedField field : struct.fields) {
builder.append(modifier);
builder.append(' ');
builder.append(field.getType());
builder.append(' ');
builder.append(prefix);
builder.append(field.getName());
builder.append(";\n");
}
prefixMatches.appendReplacement(out, builder.toString());
}
prefixMatches.appendTail(out);
return out.toString();
}
private String fillAssigns(Shader shader, String s) {
Matcher assignMatches = assigner.matcher(s);
StringBuffer out = new StringBuffer();
while (assignMatches.find()) {
String structName = assignMatches.group(1);
String lhs = assignMatches.group(2);
String rhs = assignMatches.group(3);
TaggedStruct struct = shader.getStruct(structName);
StringBuilder builder = new StringBuilder();
for (TaggedField field : struct.fields) {
builder.append(lhs);
builder.append(field.getName());
builder.append(" = ");
builder.append(rhs);
builder.append(field.getName());
builder.append(";\n");
}
assignMatches.appendReplacement(out, builder.toString());
}
assignMatches.appendTail(out);
return out.toString();
}
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.LinkedList;
public class ShaderTransformer {
private final LinkedList<IProcessingStage> stages = new LinkedList<>();
public ShaderTransformer() {
}
public ShaderTransformer pushStage(IProcessingStage stage) {
if (stage != null) {
stages.addLast(stage);
}
return this;
}
public ShaderTransformer popStage() {
stages.removeLast();
return this;
}
public ShaderTransformer prependStage(IProcessingStage stage) {
if (stage != null) {
stages.addFirst(stage);
}
return this;
}
public void transformSource(Shader shader) {
for (IProcessingStage stage : this.stages) {
stage.process(shader);
}
}
}

View file

@ -1,48 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TaggedField {
public static final Pattern fieldPattern = Pattern.compile("(?:#\\[([^\\n]*)]\\s*)?(\\S+)\\s*(\\S+);");
public String annotation;
public String name;
public String type;
public LayoutTag layout;
public TaggedField(Matcher fieldMatcher) {
annotation = fieldMatcher.group(1);
type = fieldMatcher.group(2);
name = fieldMatcher.group(3);
if (annotation != null) {
Matcher matcher = LayoutTag.pattern.matcher(annotation);
if (matcher.find()) {
layout = new LayoutTag(matcher);
}
}
}
public String getAnnotation() {
return annotation;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
@Override
public String toString() {
return "TaggedField{" +
"name='" + name + '\'' +
", type='" + type + '\'' +
'}';
}
}

View file

@ -1,49 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TaggedStruct {
// https://regexr.com/5t207
static final Pattern taggedStruct = Pattern.compile("#\\[(\\w*)]\\s*struct\\s+([\\w\\d]*)\\s*\\{([\\w\\d \\t#\\[\\](),;\\n]*)}\\s*;");
int srcStart, srcEnd;
String source;
String tag;
String name;
String body;
List<TaggedField> fields = new ArrayList<>(4);
Map<String, String> fields2Types = new HashMap<>();
public TaggedStruct(Matcher foundMatcher) {
this.source = foundMatcher.group();
srcStart = foundMatcher.start();
srcEnd = foundMatcher.end();
tag = foundMatcher.group(1);
name = foundMatcher.group(2);
body = foundMatcher.group(3);
Matcher fielder = TaggedField.fieldPattern.matcher(body);
while (fielder.find()) {
fields.add(new TaggedField(fielder));
fields2Types.put(fielder.group(2), fielder.group(1));
}
}
public void addPrefixedAttributes(Program builder, String prefix) {
for (TaggedField field : fields) {
int attributeCount = TypeHelper.getAttributeCount(field.type);
builder.addAttribute(prefix + field.name, attributeCount);
}
}
}

View file

@ -1,37 +0,0 @@
package com.jozufozu.flywheel.backend.loading;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TypeHelper {
public static final Pattern vecType = Pattern.compile("^[biud]?vec([234])$");
public static final Pattern matType = Pattern.compile("^mat([234])(?:x([234]))?$");
public static int getElementCount(String type) {
Matcher vec = vecType.matcher(type);
if (vec.find()) return Integer.parseInt(vec.group(1));
Matcher mat = matType.matcher(type);
if (mat.find()) {
int n = Integer.parseInt(mat.group(1));
String m = mat.group(2);
if (m != null) return Integer.parseInt(m) * n;
return n;
}
return 1;
}
public static int getAttributeCount(String type) {
Matcher mat = matType.matcher(type);
if (mat.find()) {
return Integer.parseInt(mat.group(1));
}
return 1;
}
}

View file

@ -1,28 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
public class ArrayModelRenderer extends ModelRenderer {
protected GlVertexArray vao;
public ArrayModelRenderer(BufferedModel model) {
super(model);
vao = new GlVertexArray();
vao.bind();
model.setupState();
vao.unbind();
model.clearState();
}
public void draw() {
if (!model.valid()) return;
vao.bind();
model.drawCall();
vao.unbind();
}
}

View file

@ -1,93 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import static org.lwjgl.opengl.GL20.glDrawArrays;
import java.nio.ByteBuffer;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.util.AttribUtil;
public class BufferedModel {
protected final GlPrimitive primitiveMode;
protected final ByteBuffer data;
protected final VertexFormat format;
protected final int vertexCount;
protected GlBuffer vbo;
protected boolean deleted;
public BufferedModel(GlPrimitive primitiveMode, VertexFormat format, ByteBuffer data, int vertices) {
this.primitiveMode = primitiveMode;
this.data = data;
this.format = format;
this.vertexCount = vertices;
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
// allocate the buffer on the gpu
vbo.alloc(this.data.capacity());
// mirror it in system memory so we can write to it, and upload our model.
vbo.getBuffer(0, this.data.capacity())
.put(this.data)
.flush();
vbo.unbind();
}
public VertexFormat getFormat() {
return format;
}
public int getVertexCount() {
return vertexCount;
}
public boolean valid() {
return vertexCount > 0 && !deleted;
}
/**
* The VBO/VAO should be bound externally.
*/
public void setupState() {
vbo.bind();
AttribUtil.enableArrays(getAttributeCount());
format.vertexAttribPointers(0);
}
public void clearState() {
AttribUtil.disableArrays(getAttributeCount());
vbo.unbind();
}
public void drawCall() {
glDrawArrays(primitiveMode.glEnum, 0, vertexCount);
}
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
public void drawInstances(int instanceCount) {
if (!valid()) return;
Backend.getInstance().compat.drawInstanced.drawArraysInstanced(primitiveMode, 0, vertexCount, instanceCount);
}
public void delete() {
if (deleted) return;
deleted = true;
vbo.delete();
}
public int getAttributeCount() {
return format.getAttributeCount();
}
}

View file

@ -1,25 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
public class ElementBuffer {
private final GlBuffer buffer;
public final int elementCount;
public final GlNumericType eboIndexType;
public ElementBuffer(GlBuffer backing, int elementCount, GlNumericType indexType) {
this.buffer = backing;
this.eboIndexType = indexType;
this.elementCount = elementCount;
}
public void bind() {
buffer.bind();
}
public void unbind() {
buffer.unbind();
}
}

View file

@ -1,59 +0,0 @@
package com.jozufozu.flywheel.backend.model;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.QuadConverter;
/**
* An indexed triangle model. Just what the driver ordered.
*
* <br><em>This should be favored over a normal BufferedModel.</em>
*/
public class IndexedModel extends BufferedModel {
protected ElementBuffer ebo;
public IndexedModel(VertexFormat modelFormat, ByteBuffer buf, int vertices, ElementBuffer ebo) {
super(GlPrimitive.TRIANGLES, modelFormat, buf, vertices);
this.ebo = ebo;
}
public static IndexedModel fromSequentialQuads(VertexFormat modelFormat, ByteBuffer quads, int vertices) {
return new IndexedModel(modelFormat, quads, vertices, QuadConverter.getInstance().quads2Tris(vertices / 4));
}
@Override
public void setupState() {
super.setupState();
ebo.bind();
}
@Override
public void clearState() {
super.clearState();
ebo.unbind();
}
@Override
public void drawCall() {
GL20.glDrawElements(primitiveMode.glEnum, ebo.elementCount, ebo.eboIndexType.getGlEnum(), 0);
}
@Override
public void drawInstances(int instanceCount) {
if (vertexCount <= 0 || deleted) return;
Backend.getInstance().compat.drawInstanced.drawElementsInstanced(primitiveMode, ebo.elementCount, ebo.eboIndexType, 0, instanceCount);
}
@Override
public void delete() {
super.delete();
}
}

View file

@ -1,25 +0,0 @@
package com.jozufozu.flywheel.backend.model;
public class ModelRenderer {
protected BufferedModel model;
public ModelRenderer(BufferedModel model) {
this.model = model;
}
/**
* Renders this model, checking first if there is anything to render.
*/
public void draw() {
if (!model.valid()) return;
model.setupState();
model.drawCall();
model.clearState();
}
public void delete() {
model.delete();
}
}

View file

@ -1,40 +0,0 @@
package com.jozufozu.flywheel.core;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
/**
* This is primarily for hacking entity textures into the block atlas.
*/
public class AtlasStitcher {
protected static final AtlasStitcher INSTANCE = new AtlasStitcher();
public static AtlasStitcher getInstance() {
return INSTANCE;
}
private final List<StitchedSprite> sprites = new ArrayList<>();
public StitchedSprite get(ResourceLocation loc) {
StitchedSprite sprite = new StitchedSprite(loc);
sprites.add(sprite);
return sprite;
}
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (!event.getMap()
.getId()
.equals(PlayerContainer.BLOCK_ATLAS_TEXTURE))
return;
sprites.stream()
.map(StitchedSprite::getLoc)
.forEach(event::addSprite);
}
}

View file

@ -1,49 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.SpecMetaRegistry;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.shader.WorldFog;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.gamestate.FogStateProvider;
import com.jozufozu.flywheel.core.shader.gamestate.NormalDebugStateProvider;
import com.jozufozu.flywheel.event.GatherContextEvent;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Contexts {
public static WorldContext<WorldProgram> WORLD;
public static WorldContext<CrumblingProgram> CRUMBLING;
@SubscribeEvent
public static void flwInit(GatherContextEvent event) {
Backend backend = event.getBackend();
SpecMetaRegistry.register(FogStateProvider.INSTANCE);
SpecMetaRegistry.register(NormalDebugStateProvider.INSTANCE);
SpecMetaRegistry.register(WorldFog.LINEAR);
SpecMetaRegistry.register(WorldFog.EXP2);
CRUMBLING = backend.register(new WorldContext<>(backend, CrumblingProgram::new)
.withName(Names.CRUMBLING)
.withBuiltin(ShaderType.FRAGMENT, Names.CRUMBLING, "/builtin.frag")
.withBuiltin(ShaderType.VERTEX, Names.CRUMBLING, "/builtin.vert"));
WORLD = backend.register(new WorldContext<>(backend, WorldProgram::new)
.withName(Names.WORLD)
.withBuiltin(ShaderType.FRAGMENT, Names.WORLD, "/builtin.frag")
.withBuiltin(ShaderType.VERTEX, Names.WORLD, "/builtin.vert"));
}
public static class Names {
public static final ResourceLocation CRUMBLING = new ResourceLocation(Flywheel.ID, "context/crumbling");
public static final ResourceLocation WORLD = new ResourceLocation(Flywheel.ID, "context/world");
}
}

View file

@ -1,17 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import net.minecraft.util.math.BlockPos;
public class CrumblingInstanceManager extends TileInstanceManager {
public CrumblingInstanceManager() {
super(new MaterialManager<>(Contexts.CRUMBLING));
}
@Override
protected boolean shouldFrameUpdate(BlockPos worldPos, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) {
return true;
}
}

View file

@ -1,31 +0,0 @@
package com.jozufozu.flywheel.core;
import static org.lwjgl.opengl.GL20.glUniform2f;
import java.util.List;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.core.shader.extension.IProgramExtension;
public class CrumblingProgram extends WorldProgram {
protected final int uTextureScale;
protected int uCrumbling;
public CrumblingProgram(Program program, List<IProgramExtension> extensions) {
super(program, extensions);
uTextureScale = getUniformLocation("uTextureScale");
}
@Override
protected void registerSamplers() {
super.registerSamplers();
uCrumbling = setSamplerBinding("uCrumbling", 4);
}
public void setTextureScale(float x, float y) {
glUniform2f(uTextureScale, x, y);
}
}

View file

@ -1,24 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.gl.attrib.CommonAttributes;
import com.jozufozu.flywheel.backend.gl.attrib.MatrixAttributes;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
public class Formats {
public static final VertexFormat UNLIT_MODEL = VertexFormat.builder()
.addAttributes(CommonAttributes.VEC3, CommonAttributes.NORMAL, CommonAttributes.UV)
.build();
public static final VertexFormat TRANSFORMED = litInstance()
.addAttributes(MatrixAttributes.MAT4,
MatrixAttributes.MAT3)
.build();
public static final VertexFormat ORIENTED = litInstance()
.addAttributes(CommonAttributes.VEC3, CommonAttributes.VEC3, CommonAttributes.QUATERNION)
.build();
public static VertexFormat.Builder litInstance() {
return VertexFormat.builder()
.addAttributes(CommonAttributes.LIGHT, CommonAttributes.RGBA);
}
}

View file

@ -1,61 +0,0 @@
package com.jozufozu.flywheel.core;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import net.minecraftforge.common.util.Lazy;
public class FullscreenQuad {
public static final Lazy<FullscreenQuad> INSTANCE = Lazy.of(FullscreenQuad::new);
private static final float[] vertices = {
// pos // tex
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
private static final int bufferSize = vertices.length * 4;
private final GlVertexArray vao;
private final GlBuffer vbo;
private FullscreenQuad() {
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
vbo.alloc(bufferSize);
vbo.getBuffer(0, bufferSize)
.putFloatArray(vertices)
.flush();
vao = new GlVertexArray();
vao.bind();
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 4, GlNumericType.FLOAT.getGlEnum(), false, 4 * 4, 0);
vao.unbind();
vbo.unbind();
}
public void draw() {
vao.bind();
GL20.glDrawArrays(GL20.GL_TRIANGLES, 0, 6);
vao.unbind();
}
public void delete() {
vao.delete();
vbo.delete();
}
}

View file

@ -1,35 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.MaterialSpec;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import com.jozufozu.flywheel.event.GatherContextEvent;
import com.simibubi.create.foundation.render.AllMaterialSpecs;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Materials {
public static final MaterialSpec<OrientedData> ORIENTED = AllMaterialSpecs.register(new MaterialSpec<>(Locations.ORIENTED, Programs.ORIENTED, Formats.UNLIT_MODEL, Formats.ORIENTED, OrientedData::new));
public static final MaterialSpec<ModelData> TRANSFORMED = AllMaterialSpecs.register(new MaterialSpec<>(Locations.MODEL, Programs.TRANSFORMED, Formats.UNLIT_MODEL, Formats.TRANSFORMED, ModelData::new));
public static <D extends InstanceData> MaterialSpec<D> register(MaterialSpec<D> spec) {
return Backend.getInstance().register(spec);
}
@SubscribeEvent
public static void flwInit(GatherContextEvent event) {
register(ORIENTED);
register(TRANSFORMED);
}
public static class Locations {
public static final ResourceLocation MODEL = new ResourceLocation("create", "model");
public static final ResourceLocation ORIENTED = new ResourceLocation("create", "oriented");
}
}

View file

@ -1,56 +0,0 @@
package com.jozufozu.flywheel.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.model.ModelLoader;
/**
* A helper class for loading and accessing json models.
* <p>
* Creating a PartialModel will make the associated modelLocation automatically load.
* As such, PartialModels must be initialized at or before {@link ModelRegistryEvent}.
* Once {@link ModelBakeEvent} finishes, all PartialModels (with valid modelLocations)
* will have their bakedModel fields populated.
* <p>
* Attempting to create a PartialModel after ModelRegistryEvent will cause an error.
*/
public class PartialModel {
private static boolean tooLate = false;
private static final List<PartialModel> all = new ArrayList<>();
protected final ResourceLocation modelLocation;
protected IBakedModel bakedModel;
public PartialModel(ResourceLocation modelLocation) {
if (tooLate) throw new RuntimeException("PartialModel '" + modelLocation + "' loaded after ModelRegistryEvent");
this.modelLocation = modelLocation;
all.add(this);
}
public static void onModelRegistry(ModelRegistryEvent event) {
for (PartialModel partial : all)
ModelLoader.addSpecialModel(partial.modelLocation);
tooLate = true;
}
public static void onModelBake(ModelBakeEvent event) {
Map<ResourceLocation, IBakedModel> modelRegistry = event.getModelRegistry();
for (PartialModel partial : all)
partial.bakedModel = modelRegistry.get(partial.modelLocation);
}
public IBakedModel get() {
return bakedModel;
}
}

View file

@ -1,10 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import net.minecraft.util.ResourceLocation;
public class Programs {
public static final ResourceLocation TRANSFORMED = new ResourceLocation(Flywheel.ID, "model");
public static final ResourceLocation ORIENTED = new ResourceLocation(Flywheel.ID, "oriented");
}

View file

@ -1,174 +0,0 @@
package com.jozufozu.flywheel.core;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.EnumMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.backend.gl.GlNumericType;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.model.ElementBuffer;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
/**
* A class to manage EBOs that index quads as triangles.
*/
@Mod.EventBusSubscriber(value = Dist.CLIENT)
public class QuadConverter {
public static final int STARTING_CAPACITY = 42;
private static QuadConverter INSTANCE;
@Nonnull
public static QuadConverter getInstance() {
if (INSTANCE == null) {
INSTANCE = new QuadConverter(STARTING_CAPACITY); // 255 / 6 = 42
}
return INSTANCE;
}
@Nullable
public static QuadConverter getNullable() {
return INSTANCE;
}
Map<GlNumericType, GlBuffer> ebos;
int[] capacities;
public QuadConverter(int initialCapacity) {
this.ebos = new EnumMap<>(GlNumericType.class);
initCapacities();
fillBuffer(initialCapacity);
}
public ElementBuffer quads2Tris(int quads) {
int indexCount = quads * 6;
GlNumericType type = getSmallestIndexType(indexCount);
if (quads > getCapacity(type)) {
fillBuffer(quads, indexCount, type);
}
return new ElementBuffer(getBuffer(type), indexCount, type);
}
private void initCapacities() {
this.capacities = new int[GlNumericType.values().length];
}
private int getCapacity(GlNumericType type) {
return capacities[type.ordinal()];
}
private void updateCapacity(GlNumericType type, int capacity) {
if (getCapacity(type) < capacity) {
capacities[type.ordinal()] = capacity;
}
}
public void free() {
ebos.values().forEach(GlBuffer::delete);
ebos.clear();
initCapacities();
}
private void fillBuffer(int quads) {
int indexCount = quads * 6;
fillBuffer(quads, indexCount, getSmallestIndexType(indexCount));
}
private void fillBuffer(int quads, int indexCount, GlNumericType type) {
MemoryStack stack = MemoryStack.stackPush();
int bytes = indexCount * type.getByteWidth();
ByteBuffer indices;
if (bytes > stack.getSize()) {
indices = MemoryUtil.memAlloc(bytes); // not enough space on the preallocated stack
} else {
stack.push();
indices = stack.malloc(bytes);
}
indices.order(ByteOrder.nativeOrder());
fillBuffer(indices, type, quads);
GlBuffer buffer = getBuffer(type);
buffer.bind();
buffer.upload(indices);
buffer.unbind();
if (bytes > stack.getSize()) {
MemoryUtil.memFree(indices);
} else {
stack.pop();
}
updateCapacity(type, quads);
}
private void fillBuffer(ByteBuffer indices, GlNumericType type, int quads) {
for (int i = 0, max = 4 * quads; i < max; i += 4) {
// triangle a
type.castAndBuffer(indices, i);
type.castAndBuffer(indices, i + 1);
type.castAndBuffer(indices, i + 2);
// triangle b
type.castAndBuffer(indices, i);
type.castAndBuffer(indices, i + 2);
type.castAndBuffer(indices, i + 3);
}
indices.flip();
}
private GlBuffer getBuffer(GlNumericType type) {
return ebos.computeIfAbsent(type, $ -> new GlBuffer(GlBufferType.ELEMENT_ARRAY_BUFFER));
}
/**
* Given the needed number of indices, what is the smallest bit width type that can index everything? <br>
*
* <pre>
* | indexCount | type |
* |--------------|-------|
* | [0, 255) | byte |
* | [256, 65536) | short |
* | [65537, ) | int |
* </pre>
*/
private static GlNumericType getSmallestIndexType(int indexCount) {
indexCount = indexCount >>> 8;
if (indexCount == 0) {
return GlNumericType.UBYTE;
}
indexCount = indexCount >>> 8;
if (indexCount == 0) {
return GlNumericType.USHORT;
}
return GlNumericType.UINT;
}
// make sure this gets reset first so it has a chance to repopulate
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void onRendererReload(ReloadRenderersEvent event) {
if (INSTANCE != null) INSTANCE.free();
}
}

View file

@ -1,31 +0,0 @@
package com.jozufozu.flywheel.core;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
public class StitchedSprite {
private final ResourceLocation loc;
TextureAtlasSprite sprite;
public StitchedSprite(ResourceLocation loc) {
this.loc = loc;
}
public ResourceLocation getLoc() {
return loc;
}
public TextureAtlasSprite getSprite() {
if (sprite == null) {
sprite = Minecraft.getInstance()
.getSpriteAtlas(PlayerContainer.BLOCK_ATLAS_TEXTURE)
.apply(loc);
}
return sprite;
}
}

View file

@ -1,165 +0,0 @@
package com.jozufozu.flywheel.core;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.ResourceUtil;
import com.jozufozu.flywheel.backend.ShaderContext;
import com.jozufozu.flywheel.backend.ShaderSources;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.instancing.MaterialManager;
import com.jozufozu.flywheel.backend.instancing.MaterialSpec;
import com.jozufozu.flywheel.backend.loading.InstancedArraysTemplate;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.backend.loading.ProgramTemplate;
import com.jozufozu.flywheel.backend.loading.Shader;
import com.jozufozu.flywheel.backend.loading.ShaderLoadingException;
import com.jozufozu.flywheel.backend.loading.ShaderTransformer;
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
import com.jozufozu.flywheel.core.shader.IMultiProgram;
import com.jozufozu.flywheel.core.shader.StateSensitiveMultiProgram;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.util.WorldAttached;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IWorld;
public class WorldContext<P extends WorldProgram> extends ShaderContext<P> {
private static final String declaration = "#flwbuiltins";
private static final Pattern builtinPattern = Pattern.compile(declaration);
protected ResourceLocation name;
protected Supplier<Stream<ResourceLocation>> specStream;
protected TemplateFactory templateFactory;
private final WorldAttached<MaterialManager<P>> materialManager = new WorldAttached<>($ -> new MaterialManager<>(this));
private final Map<ShaderType, ResourceLocation> builtins = new EnumMap<>(ShaderType.class);
private final Map<ShaderType, String> builtinSources = new EnumMap<>(ShaderType.class);
private final ExtensibleGlProgram.Factory<P> factory;
public WorldContext(Backend backend, ExtensibleGlProgram.Factory<P> factory) {
super(backend);
this.factory = factory;
specStream = () -> backend.allMaterials()
.stream()
.map(MaterialSpec::getProgramName);
templateFactory = InstancedArraysTemplate::new;
}
public WorldContext<P> withName(ResourceLocation name) {
this.name = name;
return this;
}
public WorldContext<P> withBuiltin(ShaderType shaderType, ResourceLocation folder, String file) {
return withBuiltin(shaderType, ResourceUtil.subPath(folder, file));
}
public WorldContext<P> withBuiltin(ShaderType shaderType, ResourceLocation file) {
builtins.put(shaderType, file);
return this;
}
public MaterialManager<P> getMaterialManager(IWorld world) {
return materialManager.get(world);
}
public WorldContext<P> withSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
this.specStream = specStream;
return this;
}
public WorldContext<P> withTemplateFactory(TemplateFactory templateFactory) {
this.templateFactory = templateFactory;
return this;
}
protected ShaderTransformer transformer;
protected ProgramTemplate template;
@Override
public void load() {
programs.values().forEach(IMultiProgram::delete);
programs.clear();
Backend.log.info("Loading context '{}'", name);
try {
builtins.forEach((type, resourceLocation) -> builtinSources.put(type, backend.sources.getShaderSource(resourceLocation)));
} catch (ShaderLoadingException e) {
backend.sources.notifyError();
Backend.log.error(String.format("Could not find builtin: %s", e.getMessage()));
return;
}
template = templateFactory.create(backend.sources);
transformer = new ShaderTransformer()
.pushStage(this::injectBuiltins)
.pushStage(Shader::processIncludes)
.pushStage(template)
.pushStage(Shader::processIncludes);
specStream.get()
.map(backend::getSpec)
.forEach(spec -> {
try {
programs.put(spec.name, new StateSensitiveMultiProgram<>(factory, this, spec));
Backend.log.debug("Loaded program {}", spec.name);
} catch (Exception e) {
Backend.log.error("Program '{}': {}", spec.name, e);
backend.sources.notifyError();
}
});
}
@Override
public void delete() {
super.delete();
materialManager.forEach(MaterialManager::delete);
}
@Override
protected Shader getSource(ShaderType type, ResourceLocation name) {
Shader source = super.getSource(type, name);
transformer.transformSource(source);
return source;
}
@Override
protected Program link(Program program) {
template.attachAttributes(program);
return super.link(program);
}
/**
* Replace #flwbuiltins with whatever expansion this context provides for the given shader.
*/
public void injectBuiltins(Shader shader) {
Matcher matcher = builtinPattern.matcher(shader.getSource());
if (matcher.find())
shader.setSource(matcher.replaceFirst(builtinSources.get(shader.type)));
else
throw new ShaderLoadingException(String.format("%s is missing %s, cannot use in World Context", shader.type.name, declaration));
}
public interface TemplateFactory {
ProgramTemplate create(ShaderSources loader);
}
}

View file

@ -1,61 +0,0 @@
package com.jozufozu.flywheel.core.instancing;
import java.util.Optional;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.Instancer;
public class ConditionalInstance<D extends InstanceData> {
final Instancer<D> model;
ICondition condition;
Consumer<D> setupFunc;
@Nullable
private D instance;
public ConditionalInstance(Instancer<D> model) {
this.model = model;
this.condition = () -> true;
}
public ConditionalInstance<D> withSetupFunc(Consumer<D> setupFunc) {
this.setupFunc = setupFunc;
return this;
}
public ConditionalInstance<D> withCondition(ICondition condition) {
this.condition = condition;
return this;
}
public ConditionalInstance<D> update() {
boolean shouldShow = condition.shouldShow();
if (shouldShow && instance == null) {
instance = model.createInstance();
if (setupFunc != null) setupFunc.accept(instance);
} else if (!shouldShow && instance != null) {
instance.delete();
instance = null;
}
return this;
}
public Optional<D> get() {
return Optional.ofNullable(instance);
}
public void delete() {
if (instance != null) instance.delete();
}
@FunctionalInterface
public interface ICondition {
boolean shouldShow();
}
}

View file

@ -1,84 +0,0 @@
package com.jozufozu.flywheel.core.instancing;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.Instancer;
public class GroupInstance<D extends InstanceData> extends AbstractCollection<D> {
final Instancer<D> model;
final List<D> backing;
public GroupInstance(Instancer<D> model) {
this.model = model;
this.backing = new ArrayList<>();
}
public GroupInstance(Instancer<D> model, int size) {
this.model = model;
this.backing = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
addInstance();
}
}
/**
* @param count
* @return True if the number of elements changed.
*/
public boolean resize(int count) {
int size = size();
if (count == size) return false;
if (count <= 0) {
clear();
return size > 0;
}
if (count > size) {
for (int i = size; i < count; i++) {
addInstance();
}
} else {
List<D> unnecessary = backing.subList(count, size);
unnecessary.forEach(InstanceData::delete);
unnecessary.clear();
}
return true;
}
public InstanceData addInstance() {
D instance = model.createInstance();
backing.add(instance);
return instance;
}
public D get(int index) {
return backing.get(index);
}
@Override
public Iterator<D> iterator() {
return backing.iterator();
}
@Override
public int size() {
return backing.size();
}
@Override
public void clear() {
backing.forEach(InstanceData::delete);
backing.clear();
}
}

View file

@ -1,61 +0,0 @@
package com.jozufozu.flywheel.core.instancing;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.Instancer;
public class SelectInstance<D extends InstanceData> {
final List<Instancer<D>> models;
ModelSelector selector;
private int last = -1;
@Nullable
private D current;
public SelectInstance(ModelSelector selector) {
this.models = new ArrayList<>();
this.selector = selector;
}
public SelectInstance<D> addModel(Instancer<D> model) {
models.add(model);
return this;
}
public SelectInstance<D> update() {
int i = selector.modelIndexToShow();
if (i < 0 || i >= models.size()) {
if (current != null) {
current.delete();
current = null;
}
} else if (i != last) {
if (current != null) current.delete();
current = models.get(i).createInstance();
}
last = i;
return this;
}
public Optional<D> get() {
return Optional.ofNullable(current);
}
public void delete() {
if (current != null) current.delete();
}
public interface ModelSelector {
int modelIndexToShow();
}
}

View file

@ -1,77 +0,0 @@
package com.jozufozu.flywheel.core.materials;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.Instancer;
public abstract class BasicData extends InstanceData implements IFlatLight<BasicData> {
protected byte blockLight;
protected byte skyLight;
protected byte r = (byte) 0xFF;
protected byte g = (byte) 0xFF;
protected byte b = (byte) 0xFF;
protected byte a = (byte) 0xFF;
public BasicData(Instancer<?> owner) {
super(owner);
}
@Override
public BasicData setBlockLight(int blockLight) {
this.blockLight = (byte) (blockLight << 4);
markDirty();
return this;
}
@Override
public BasicData setSkyLight(int skyLight) {
this.skyLight = (byte) (skyLight << 4);
markDirty();
return this;
}
public BasicData setColor(int color) {
return setColor(color, false);
}
public BasicData setColor(int color, boolean alpha) {
byte r = (byte) ((color >> 16) & 0xFF);
byte g = (byte) ((color >> 8) & 0xFF);
byte b = (byte) (color & 0xFF);
if (alpha) {
byte a = (byte) ((color >> 24) & 0xFF);
return setColor(r, g, b, a);
} else {
return setColor(r, g, b);
}
}
public BasicData setColor(int r, int g, int b) {
return setColor((byte) r, (byte) g, (byte) b);
}
public BasicData setColor(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
markDirty();
return this;
}
public BasicData setColor(byte r, byte g, byte b, byte a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
markDirty();
return this;
}
@Override
public void write(MappedBuffer buf) {
buf.putByteArray(new byte[]{blockLight, skyLight, r, g, b, a});
}
}

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.core.materials;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
/**
* An interface that implementors of {@link InstanceData} should also implement
* if they wish to make use of Flywheel's provided light update methods.
* <p>
* This only covers flat lighting, smooth lighting is still TODO.
*
* @param <D> The name of the class that implements this interface.
*/
public interface IFlatLight<D extends InstanceData & IFlatLight<D>> {
/**
* @param blockLight An integer in the range [0, 15] representing the
* amount of block light this instance should receive.
* @return <code>this</code>
*/
D setBlockLight(int blockLight);
/**
* @param skyLight An integer in the range [0, 15] representing the
* amount of sky light this instance should receive.
* @return <code>this</code>
*/
D setSkyLight(int skyLight);
}

View file

@ -1,28 +0,0 @@
package com.jozufozu.flywheel.core.materials;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.instancing.Instancer;
import com.jozufozu.flywheel.util.RenderUtil;
import com.mojang.blaze3d.matrix.MatrixStack;
public class ModelData extends BasicData {
private static final float[] empty = new float[25];
private float[] matrices = empty;
public ModelData(Instancer<?> owner) {
super(owner);
}
public ModelData setTransform(MatrixStack stack) {
matrices = RenderUtil.writeMatrixStack(stack);
markDirty();
return this;
}
@Override
public void write(MappedBuffer buf) {
super.write(buf);
buf.putFloatArray(matrices);
}
}

View file

@ -1,99 +0,0 @@
package com.jozufozu.flywheel.core.materials;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.instancing.Instancer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Quaternion;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
public class OrientedData extends BasicData {
private float posX;
private float posY;
private float posZ;
private float pivotX = 0.5f;
private float pivotY = 0.5f;
private float pivotZ = 0.5f;
private float qX;
private float qY;
private float qZ;
private float qW;
public OrientedData(Instancer<?> owner) {
super(owner);
}
public OrientedData setPosition(BlockPos pos) {
return setPosition(pos.getX(), pos.getY(), pos.getZ());
}
public OrientedData setPosition(Vector3f pos) {
return setPosition(pos.getX(), pos.getY(), pos.getZ());
}
public OrientedData setPosition(float x, float y, float z) {
this.posX = x;
this.posY = y;
this.posZ = z;
markDirty();
return this;
}
public OrientedData nudge(float x, float y, float z) {
this.posX += x;
this.posY += y;
this.posZ += z;
markDirty();
return this;
}
public OrientedData setPivot(Vector3f pos) {
return setPosition(pos.getX(), pos.getY(), pos.getZ());
}
public OrientedData setPivot(Vector3d pos) {
return setPosition((float) pos.getX(), (float) pos.getY(), (float) pos.getZ());
}
public OrientedData setPivot(float x, float y, float z) {
this.pivotX = x;
this.pivotY = y;
this.pivotZ = z;
markDirty();
return this;
}
public OrientedData setRotation(Quaternion q) {
return setRotation(q.getX(), q.getY(), q.getZ(), q.getW());
}
public OrientedData setRotation(float x, float y, float z, float w) {
this.qX = x;
this.qY = y;
this.qZ = z;
this.qW = w;
markDirty();
return this;
}
@Override
public void write(MappedBuffer buf) {
super.write(buf);
buf.putFloatArray(new float[]{
posX,
posY,
posZ,
pivotX,
pivotY,
pivotZ,
qX,
qY,
qZ,
qW
});
}
}

View file

@ -1,71 +0,0 @@
package com.jozufozu.flywheel.core.shader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.loading.Program;
import com.jozufozu.flywheel.core.shader.extension.IExtensionInstance;
import com.jozufozu.flywheel.core.shader.extension.IProgramExtension;
/**
* A shader program that be arbitrarily "extended". This class can take in any number of program extensions, and
* will initialize them and then call their {@link IExtensionInstance#bind() bind} function every subsequent time this
* program is bound. An "extension" is something that interacts with the shader program in a way that is invisible to
* the caller using the program. This is used by some programs to implement the different fog modes. Other uses might
* include binding extra textures to allow for blocks to have normal maps, for example. As the extensions are
* per-program, this also allows for same extra specialization within a
* {@link com.jozufozu.flywheel.backend.ShaderContext ShaderContext}.
*/
public class ExtensibleGlProgram extends GlProgram {
protected final List<IExtensionInstance> extensions;
public ExtensibleGlProgram(Program program, @Nullable List<IProgramExtension> extensions) {
super(program);
if (extensions != null) {
List<IExtensionInstance> list = new ArrayList<>();
for (IProgramExtension e : extensions) {
IExtensionInstance extension = e.create(this);
list.add(extension);
}
this.extensions = list;
} else {
this.extensions = Collections.emptyList();
}
}
@Override
public void bind() {
super.bind();
extensions.forEach(IExtensionInstance::bind);
}
@Override
public String toString() {
return "ExtensibleGlProgram{" +
"name=" + name +
", extensions=" + extensions +
'}';
}
/**
* A factory interface to create {@link GlProgram}s parameterized by a list of extensions. This doesn't necessarily
* have to return an {@link ExtensibleGlProgram} if implementors want more flexibility for whatever reason.
*/
public interface Factory<P extends GlProgram> {
@Nonnull
P create(Program program, @Nullable List<IProgramExtension> extensions);
default P create(Program program) {
return create(program, null);
}
}
}

View file

@ -1,60 +0,0 @@
package com.jozufozu.flywheel.core.shader;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.shader.extension.IExtensionInstance;
import net.minecraft.util.ResourceLocation;
public abstract class FogMode {
public static class Linear implements IExtensionInstance {
public static final ResourceLocation NAME = new ResourceLocation(Flywheel.ID, "fog_linear");
private final int uFogColor;
private final int uFogRange;
public Linear(GlProgram program) {
this.uFogColor = program.getUniformLocation("uFogColor");
this.uFogRange = program.getUniformLocation("uFogRange");
}
@Override
public void bind() {
GL20.glUniform2f(uFogRange, GlFog.getFogStart(), GlFog.getFogEnd());
GL20.glUniform4fv(uFogColor, GlFog.FOG_COLOR);
}
@Override
public ResourceLocation name() {
return NAME;
}
}
public static class Exp2 implements IExtensionInstance {
public static final ResourceLocation NAME = new ResourceLocation(Flywheel.ID, "fog_exp2");
private final int uFogColor;
private final int uFogDensity;
public Exp2(GlProgram program) {
this.uFogColor = program.getUniformLocation("uFogColor");
this.uFogDensity = program.getUniformLocation("uFogDensity");
}
@Override
public void bind() {
GL20.glUniform1f(uFogDensity, GlFog.getFogDensity());
GL20.glUniform4fv(uFogColor, GlFog.FOG_COLOR);
}
@Override
public ResourceLocation name() {
return NAME;
}
}
}

Some files were not shown because too many files have changed in this diff Show more