Block breaking animations for arbitrary flywheel tiles.

- Kinda jank so far, but it's great progress.
 - This is also a really good showcase for how flexible the ShaderContext system is.
This commit is contained in:
JozsefA 2021-05-06 18:38:05 -07:00
parent 9736ba19b5
commit 3171a0b7e5
15 changed files with 224 additions and 43 deletions

View file

@ -1,17 +1,29 @@
package com.jozufozu.flywheel.backend;
import static org.lwjgl.opengl.GL11.GL_REPEAT;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
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.core.BasicInstancedTileRenderer;
import com.jozufozu.flywheel.backend.core.BasicProgram;
import com.jozufozu.flywheel.backend.core.CrumblingProgram;
import com.jozufozu.flywheel.backend.core.EffectsContext;
import com.jozufozu.flywheel.backend.core.WorldContext;
import com.jozufozu.flywheel.backend.core.WorldTileRenderer;
import com.jozufozu.flywheel.backend.effects.EffectsHandler;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.ProgramSpec;
@ -19,18 +31,28 @@ import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.IFlywheelWorld;
import com.jozufozu.flywheel.backend.instancing.InstancedModel;
import com.jozufozu.flywheel.backend.instancing.MaterialSpec;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.simibubi.create.content.contraptions.KineticDebugger;
import com.simibubi.create.foundation.config.AllConfigs;
import com.simibubi.create.foundation.utility.WorldAttached;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.client.Minecraft;
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.resources.IReloadableResourceManager;
import net.minecraft.resources.IResourceManager;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.world.World;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
@ -45,7 +67,8 @@ public class Backend {
public static GlCompat compat;
public static EffectsHandler effects;
public static WorldAttached<BasicInstancedTileRenderer> tileRenderer = new WorldAttached<>(BasicInstancedTileRenderer::new);
public static WorldAttached<WorldTileRenderer<BasicProgram>> tileRenderer = new WorldAttached<>(() -> new WorldTileRenderer<>(WorldContext.INSTANCE));
public static WorldAttached<WorldTileRenderer<CrumblingProgram>> blockBreaking = new WorldAttached<>(() -> new WorldTileRenderer<>(WorldContext.CRUMBLING));
private static Matrix4f projectionMatrix = new Matrix4f();
private static boolean instancingAvailable;
@ -57,18 +80,18 @@ public class Backend {
static {
register(WorldContext.INSTANCE);
register(WorldContext.CRUMBLING);
register(EffectsContext.INSTANCE);
listeners.refreshListener(world -> {
if (canUseInstancing() && world != null) {
BasicInstancedTileRenderer tileRenderer = Backend.tileRenderer.get(world);
WorldTileRenderer<BasicProgram> tileRenderer = Backend.tileRenderer.get(world);
tileRenderer.invalidate();
world.loadedTileEntityList.forEach(tileRenderer::add);
}
});
listeners.setupFrameListener((world, stack, info, gameRenderer, lightTexture) -> {
Backend.tileRenderer.get(world)
.beginFrame(info);
});
@ -174,7 +197,7 @@ public class Backend {
Minecraft mc = Minecraft.getInstance();
ClientWorld world = mc.world;
BasicInstancedTileRenderer instancer = tileRenderer.get(world);
WorldTileRenderer<BasicProgram> instancer = tileRenderer.get(world);
Entity renderViewEntity = mc.renderViewEntity;
instancer.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
@ -182,15 +205,79 @@ public class Backend {
public static void renderLayer(ClientWorld world, RenderType layer, Matrix4f viewProjection, double cameraX, double cameraY, double cameraZ) {
if (!canUseInstancing(world)) return;
WorldTileRenderer<BasicProgram> renderer = tileRenderer.get(world);
if (renderer == null) return;
layer.startDrawing();
tileRenderer.get(world)
.render(layer, viewProjection, cameraX, cameraY, cameraZ);
renderer.render(layer, viewProjection, cameraX, cameraY, cameraZ);
layer.endDrawing();
}
public static void renderBreaking(ClientWorld world, Matrix4f viewProjection, double cameraX, double cameraY, double cameraZ) {
if (!canUseInstancing(world)) return;
WorldTileRenderer<CrumblingProgram> renderer = blockBreaking.get(world);
if (renderer == null) return;
WorldRenderer worldRenderer = Minecraft.getInstance().worldRenderer;
Long2ObjectMap<SortedSet<DestroyBlockProgress>> breakingProgressions = worldRenderer.blockBreakingProgressions;
if (breakingProgressions.isEmpty()) return;
for (Long2ObjectMap.Entry<SortedSet<DestroyBlockProgress>> entry : breakingProgressions.long2ObjectEntrySet()) {
BlockPos breakingPos = BlockPos.fromLong(entry.getLongKey());
SortedSet<DestroyBlockProgress> sortedset1 = entry.getValue();
if (sortedset1 != null && !sortedset1.isEmpty()) {
renderer.add(world.getTileEntity(breakingPos));
}
}
renderer.beginFrame(Minecraft.getInstance().gameRenderer.getActiveRenderInfo());
RenderType layer = RenderType.getCutoutMipped();
layer.startDrawing();
glActiveTexture(GL_TEXTURE0);
TextureManager textureManager = Minecraft.getInstance().textureManager;
Texture breaking = textureManager.getTexture(ModelBakery.BLOCK_DESTRUCTION_STAGE_TEXTURES.get(5));
if (breaking != null) {
glBindTexture(GL_TEXTURE_2D, breaking.getGlTextureId());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
RenderSystem.enableAlphaTest();
RenderSystem.alphaFunc(516, 0.003921569F);
RenderSystem.polygonOffset(-1.0F, -10.0F);
RenderSystem.enablePolygonOffset();
}
renderer.render(layer, viewProjection, cameraX, cameraY, cameraZ, program -> program.setTextureScale(64, 64));
if (breaking != null) {
glBindTexture(GL_TEXTURE_2D, textureManager.getTexture(PlayerContainer.BLOCK_ATLAS_TEXTURE).getGlTextureId());
RenderSystem.disableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.polygonOffset(0.0F, 0.0F);
RenderSystem.disablePolygonOffset();
}
layer.endDrawing();
renderer.invalidate();
}
public static void enqueueUpdate(TileEntity te) {
tileRenderer.get(te.getWorld()).queueUpdate(te);
}

View file

@ -1,6 +1,8 @@
package com.jozufozu.flywheel.backend.core;
import org.lwjgl.opengl.GL20;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniform3f;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.ProgramFogMode;
@ -42,11 +44,11 @@ public class BasicProgram extends GlProgram {
public void bind(Matrix4f viewProjection, double camX, double camY, double camZ, int debugMode) {
super.bind();
GL20.glUniform1i(uDebug, debugMode);
GL20.glUniform1f(uTime, AnimationTickHolder.getRenderTime());
glUniform1i(uDebug, debugMode);
glUniform1f(uTime, AnimationTickHolder.getRenderTime());
uploadMatrixUniform(uViewProjection, viewProjection);
GL20.glUniform3f(uCameraPos, (float) camX, (float) camY, (float) camZ);
glUniform3f(uCameraPos, (float) camX, (float) camY, (float) camZ);
fogMode.bind();
}

View file

@ -0,0 +1,22 @@
package com.jozufozu.flywheel.backend.core;
import static org.lwjgl.opengl.GL20.glUniform2f;
import com.jozufozu.flywheel.backend.gl.shader.ProgramFogMode;
import net.minecraft.util.ResourceLocation;
public class CrumblingProgram extends BasicProgram {
protected final int uTextureScale;
public CrumblingProgram(ResourceLocation name, int handle, ProgramFogMode.Factory fogFactory) {
super(name, handle, fogFactory);
uTextureScale = getUniformLocation("uTextureScale");
}
public void setTextureScale(float x, float y) {
glUniform2f(uTextureScale, x, y);
}
}

View file

@ -21,6 +21,7 @@ public class WorldContext<P extends BasicProgram> extends ShaderContext<P> {
private static final Pattern builtinPattern = Pattern.compile("#flwbuiltins");
public static final WorldContext<BasicProgram> INSTANCE = new WorldContext<>(new ResourceLocation("create", "std"), new FogSensitiveProgram.SpecLoader<>(BasicProgram::new));
public static final WorldContext<CrumblingProgram> CRUMBLING = new WorldContext<>(new ResourceLocation("create", "crumbling"), new FogSensitiveProgram.SpecLoader<>(CrumblingProgram::new));
private final ShaderSpecLoader<P> loader;

View file

@ -12,13 +12,13 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Matrix4f;
public class BasicInstancedTileRenderer extends InstancedTileRenderer<BasicProgram> {
public class WorldTileRenderer<P extends BasicProgram> extends InstancedTileRenderer<P> {
public static int MAX_ORIGIN_DISTANCE = 100;
public BlockPos originCoordinate = BlockPos.ZERO;
public BasicInstancedTileRenderer() {
super(WorldContext.INSTANCE);
public WorldTileRenderer(WorldContext<P> context) {
super(context);
}
@Override
@ -50,7 +50,7 @@ public class BasicInstancedTileRenderer extends InstancedTileRenderer<BasicProgr
@Override
public void render(RenderType layer, Matrix4f viewProjection, double camX, double camY, double camZ,
ShaderCallback<BasicProgram> callback) {
ShaderCallback<P> callback) {
BlockPos originCoordinate = getOriginCoordinate();
camX -= originCoordinate.getX();

View file

@ -1,8 +1,20 @@
package com.jozufozu.flywheel.backend.gl.shader;
import java.util.Collection;
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.glDeleteProgram;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL20.glUseProgram;
import org.lwjgl.opengl.GL20;
import java.util.Collection;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderUtil;
@ -26,11 +38,11 @@ public abstract class GlProgram extends GlObject {
}
public void bind() {
GL20.glUseProgram(handle());
glUseProgram(handle());
}
public void unbind() {
GL20.glUseProgram(0);
glUseProgram(0);
}
/**
@ -40,7 +52,7 @@ public abstract class GlProgram extends GlObject {
* @return The uniform's index
*/
public int getUniformLocation(String uniform) {
int index = GL20.glGetUniformLocation(this.handle(), 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);
@ -61,19 +73,19 @@ public abstract class GlProgram extends GlObject {
int samplerUniform = getUniformLocation(name);
if (samplerUniform >= 0) {
GL20.glUniform1i(samplerUniform, binding);
glUniform1i(samplerUniform, binding);
}
return samplerUniform;
}
protected static void uploadMatrixUniform(int uniform, Matrix4f mat) {
GL20.glUniformMatrix4fv(uniform, false, RenderUtil.writeMatrix(mat));
glUniformMatrix4fv(uniform, false, RenderUtil.writeMatrix(mat));
}
@Override
protected void deleteInternal(int handle) {
GL20.glDeleteProgram(handle);
glDeleteProgram(handle);
}
public static class Builder {
@ -84,11 +96,11 @@ public abstract class GlProgram extends GlObject {
public Builder(ResourceLocation name) {
this.name = name;
this.program = GL20.glCreateProgram();
this.program = glCreateProgram();
}
public Builder attachShader(GlShader shader) {
GL20.glAttachShader(this.program, shader.handle());
glAttachShader(this.program, shader.handle());
return this;
}
@ -99,7 +111,7 @@ public abstract class GlProgram extends GlObject {
}
public <A extends IVertexAttrib> Builder addAttribute(A attrib) {
GL20.glBindAttribLocation(this.program, attributeIndex, attrib.attribName());
glBindAttribLocation(this.program, attributeIndex, attrib.attribName());
attributeIndex += attrib.attribSpec().getAttributeCount();
return this;
}
@ -108,17 +120,17 @@ public abstract class GlProgram extends GlObject {
* Links the attached shaders to this program.
*/
public Builder link() {
GL20.glLinkProgram(this.program);
glLinkProgram(this.program);
String log = GL20.glGetProgramInfoLog(this.program);
String log = glGetProgramInfoLog(this.program);
if (!log.isEmpty()) {
Backend.log.debug("Program link log for " + this.name + ": " + log);
}
int result = GL20.glGetProgrami(this.program, GL20.GL_LINK_STATUS);
int result = glGetProgrami(this.program, GL_LINK_STATUS);
if (result != GL20.GL_TRUE) {
if (result != GL_TRUE) {
throw new RuntimeException("Shader program linking failed, see log for details");
}

View file

@ -5,7 +5,8 @@ import java.util.List;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
import com.jozufozu.flywheel.backend.core.BasicProgram;
import com.jozufozu.flywheel.backend.core.WorldTileRenderer;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.systems.RenderSystem;
import com.simibubi.create.AllFluids;
@ -144,7 +145,7 @@ public class ClientEvents {
if (world.isRemote() && world instanceof ClientWorld && !(world instanceof WrappedClientWorld)) {
CreateClient.invalidateRenderers(world);
AnimationTickHolder.reset();
BasicInstancedTileRenderer renderer = Backend.tileRenderer.get(world);
WorldTileRenderer<BasicProgram> renderer = Backend.tileRenderer.get(world);
renderer.invalidate();
((ClientWorld) world).loadedTileEntityList.forEach(renderer::add);
}

View file

@ -21,6 +21,7 @@ import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@ -66,6 +67,23 @@ public class RenderHooksMixin {
Backend.listeners.refresh(world);
}
@Inject(at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;long2ObjectEntrySet()Lit/unimi/dsi/fastutil/objects/ObjectSet;"), method = "render")
private void renderBlockBreaking(MatrixStack stack, float p_228426_2_, long p_228426_3_, boolean p_228426_5_,
ActiveRenderInfo info, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f p_228426_9_,
CallbackInfo ci) {
if (!Backend.available())
return;
Matrix4f view = stack.peek()
.getModel();
Matrix4f viewProjection = view.copy();
viewProjection.multiplyBackward(Backend.getProjectionMatrix());
Vector3d cameraPos = info.getProjectedView();
Backend.renderBreaking(world, viewProjection, cameraPos.x, cameraPos.y, cameraPos.z);
GL20.glUseProgram(0);
}
// Effects system
@Inject(method = "render", at = @At(value = "INVOKE", ordinal = 1, target = "Lnet/minecraft/client/shader/ShaderGroup;render(F)V"))

View file

@ -11,7 +11,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.core.BasicInstancedTileRenderer;
import com.jozufozu.flywheel.backend.core.BasicProgram;
import com.jozufozu.flywheel.backend.core.WorldTileRenderer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
@ -46,7 +47,7 @@ public class TileWorldHookMixin {
@Inject(at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", ordinal = 0), method = "tickBlockEntities")
private void onChunkUnload(CallbackInfo ci) {
if (isRemote) {
BasicInstancedTileRenderer kineticRenderer = Backend.tileRenderer.get(self);
WorldTileRenderer<BasicProgram> kineticRenderer = Backend.tileRenderer.get(self);
for (TileEntity tile : tileEntitiesToBeRemoved) {
kineticRenderer.remove(tile);
}

View file

@ -89,3 +89,5 @@ public net.minecraft.util.math.vector.Matrix4f field_226587_m_ #a30
public net.minecraft.util.math.vector.Matrix4f field_226588_n_ #a31
public net.minecraft.util.math.vector.Matrix4f field_226589_o_ #a32
public net.minecraft.util.math.vector.Matrix4f field_226590_p_ #a33
public net.minecraft.client.renderer.WorldRenderer field_228407_B_ #blockBreakingProgressions

View file

@ -7,13 +7,10 @@ varying vec2 Light;
varying float Diffuse;
varying vec4 Color;
uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap;
void main() {
vec4 tex = texture2D(uBlockAtlas, TexCoords);
vec4 tex = FLWBlockTexture(TexCoords);
vec4 color = vec4(tex.rgb * FLWLight(Light, uLightMap).rgb * Diffuse, tex.a) * Color;
vec4 color = vec4(tex.rgb * FLWLight(Light).rgb * Diffuse, tex.a) * Color;
FLWFinalizeColor(color);

View file

@ -3,6 +3,13 @@
varying vec3 BoxCoord;
uniform sampler3D uLightVolume;
uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap;
vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords);
}
void FLWFinalizeColor(inout vec4 color) {
#if defined(USE_FOG)
float a = color.a;
@ -13,7 +20,7 @@ void FLWFinalizeColor(inout vec4 color) {
#endif
}
vec4 FLWLight(vec2 lightCoords, sampler2D lightMap) {
vec4 FLWLight(vec2 lightCoords) {
vec2 lm = max(lightCoords, texture3D(uLightVolume, BoxCoord).rg);
return texture2D(lightMap, lm * 0.9375 + 0.03125);
return texture2D(uLightMap, lm * 0.9375 + 0.03125);
}

View file

@ -0,0 +1,23 @@
#flwinclude <"create:std/fog.glsl">
uniform vec2 uTextureScale;
uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap;
vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords * uTextureScale);
}
void FLWFinalizeColor(inout vec4 color) {
#if defined(USE_FOG)
float a = color.a;
float fog = clamp(FLWFogFactor(), 0., 1.);
color = mix(uFogColor, color, fog);
color.a = a;
#endif
}
vec4 FLWLight(vec2 lightCoords) {
return vec4(1.);
}

View file

@ -0,0 +1 @@
#flwinclude <"create:std/builtin.vert">

View file

@ -1,5 +1,12 @@
#flwinclude <"create:std/fog.glsl">
uniform sampler2D uBlockAtlas;
uniform sampler2D uLightMap;
vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords);
}
void FLWFinalizeColor(inout vec4 color) {
#if defined(USE_FOG)
float a = color.a;
@ -10,7 +17,7 @@ void FLWFinalizeColor(inout vec4 color) {
#endif
}
vec4 FLWLight(vec2 lightCoords, sampler2D lightMap) {
vec4 FLWLight(vec2 lightCoords) {
vec2 lm = lightCoords * 0.9375 + 0.03125;
return texture2D(lightMap, lm);
return texture2D(uLightMap, lm);
}