Indigo compatibility/configuration improvements (#205)

This commit is contained in:
Adrian Siekierka 2019-05-26 15:32:07 +02:00
parent 25fd0c5267
commit 27e744c06a
15 changed files with 355 additions and 48 deletions

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-renderer-indigo"
version = getSubprojectVersion(project, "0.1.0")
version = getSubprojectVersion(project, "0.1.1-pre1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -18,20 +18,102 @@ package net.fabricmc.indigo;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.aocalc.AoConfig;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Locale;
import java.util.Properties;
public class Indigo implements ClientModInitializer {
public static final boolean ALWAYS_TESSELATE_INDIGO;
public static final AoConfig AMBIENT_OCCLUSION_MODE;
private static final Logger LOGGER = LogManager.getLogger();
private static boolean asBoolean(String property, boolean defValue) {
switch (asTriState(property)) {
case TRUE:
return true;
case FALSE:
return false;
default:
return defValue;
}
}
private static <T extends Enum> T asEnum(String property, T defValue) {
if (property == null || property.isEmpty()) {
return defValue;
} else {
for (Enum obj : defValue.getClass().getEnumConstants()) {
if (property.equalsIgnoreCase(obj.name())) {
//noinspection unchecked
return (T) obj;
}
}
return defValue;
}
}
private static TriState asTriState(String property) {
if (property == null || property.isEmpty()) {
return TriState.DEFAULT;
} else {
switch (property.toLowerCase(Locale.ROOT)) {
case "true":
return TriState.TRUE;
case "false":
return TriState.FALSE;
case "auto":
default:
return TriState.DEFAULT;
}
}
}
static {
File configDir = new File(FabricLoader.getInstance().getConfigDirectory(), "fabric");
if (!configDir.exists()) {
if (!configDir.mkdir()) {
LOGGER.warn("[Indigo] Could not create configuration directory: " + configDir.getAbsolutePath());
}
}
File configFile = new File(configDir, "indigo-renderer.properties");
Properties properties = new Properties();
if (configFile.exists()) {
try (FileInputStream stream = new FileInputStream(configFile)) {
properties.load(stream);
} catch (IOException e) {
LOGGER.warn("[Indigo] Could not read property file '" + configFile.getAbsolutePath() + "'", e);
}
}
ALWAYS_TESSELATE_INDIGO = asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "enhanced"), AoConfig.ENHANCED);
try (FileOutputStream stream = new FileOutputStream(configFile)) {
properties.store(stream, "Indigo properties file");
} catch (IOException e) {
LOGGER.warn("[Indigo] Could not store property file '" + configFile.getAbsolutePath() + "'", e);
}
}
@Override
public void onInitializeClient() {
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
LOGGER.info("Loading Indigo renderer!");
LOGGER.info("[Indigo] Registering Indigo renderer!");
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
} else {
LOGGER.info("Different rendering plugin detected; not applying Indigo.");
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
}
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.indigo;
public class IndigoConfig {
}

View file

@ -17,8 +17,5 @@
package net.fabricmc.indigo.renderer.accessor;
public interface AccessBufferBuilder {
void fabric_putVanillaData(int[] data, int start);
double fabric_offsetX();
double fabric_offsetY();
double fabric_offsetZ();
void fabric_putVanillaData(int[] data, int start, boolean isItemFormat);
}

View file

@ -29,6 +29,7 @@ import static net.minecraft.util.math.Direction.WEST;
import java.util.function.ToIntBiFunction;
import net.fabricmc.indigo.Indigo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -73,6 +74,9 @@ public class AoCalculator {
}
private static final Logger LOGGER = LogManager.getLogger();
// TODO: make this actually configurable?
private static final boolean fixSmoothLighting = true;
private final VanillaAoCalc vanillaCalc;
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
private final BlockPos.Mutable searchPos = new BlockPos.Mutable();
@ -111,12 +115,8 @@ public class AoCalculator {
/** Set true in dev env to confirm results match vanilla when they should */
private static final boolean DEBUG = Boolean.valueOf(System.getProperty("fabric.debugAoLighting", "false"));
// TODO: make actually configurable
private static boolean fixSmoothLighting = true;
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
// TODO: make this actually configurable
final AoConfig config = AoConfig.ENHANCED;
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
boolean shouldMatch = false;

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.indigo.renderer.helper;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;
import net.minecraft.client.render.VertexFormats;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class BufferBuilderTransformHelper {
/**
* Fast copying mode; used only if the vanilla format is an exact match.
*/
public static final int MODE_COPY_FAST = 0;
/**
* Padded copying mode; used when the vanilla format is an exact match,
* but includes additional data at the end. Will emit a warning.
*/
public static final int MODE_COPY_PADDED = 1;
/**
* ShadersMod compatibility mode; as MODE_COPY_PADDED, but populates in
* the correct normal values as provided by the mod.
*
* Assumes a format of [vertex, color, texture, lmap, normal], all in
* their respective vanilla formats, plus any amount of additional data
* afterwards.
*/
public static final int MODE_COPY_PADDED_SHADERSMOD = 2;
/**
* Unsupported mode; an error will be emitted and no quads will be
* pushed to the buffer builder.
*/
public static final int MODE_UNSUPPORTED = 3;
private static final Map<VertexFormat, Integer> vertexFormatCache = new ConcurrentHashMap<>();
private static final Set<VertexFormat> errorEmittedFormats = Sets.newConcurrentHashSet();
private static final Logger logger = LogManager.getLogger();
public static void emitUnsupportedError(VertexFormat format) {
// This can be slow, as it's only called on unsupported formats - which is already an error condition.
if (errorEmittedFormats.add(format)) {
logger.error("[Indigo] Unsupported vertex format! " + format);
}
}
private static int computeProcessingMode(VertexFormat f) {
if (
f.getElementCount() >= 4 && f.getVertexSizeInteger() >= 7
&& f.getElement(0).equals(VertexFormats.POSITION_ELEMENT)
&& f.getElement(1).equals(VertexFormats.COLOR_ELEMENT)
&& f.getElement(2).equals(VertexFormats.UV_ELEMENT)
) {
if (
f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
|| f.getElement(3).equals(VertexFormats.NORMAL_ELEMENT)
) {
if (
f.getElementCount() >= 5
&& f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
&& f.getElement(4).equals(VertexFormats.NORMAL_ELEMENT)
) {
logger.debug("[Indigo] Classified format as ShadersMod-compatible: " + f);
return MODE_COPY_PADDED_SHADERSMOD;
} else if (f.getElementCount() == 4) {
logger.debug("[Indigo] Classified format as vanilla-like: " + f);
return MODE_COPY_FAST;
} else {
logger.debug("[Indigo] Unsupported but likely vanilla-compliant vertex format. " + f);
return MODE_COPY_PADDED;
}
}
}
return MODE_UNSUPPORTED;
}
public static int getProcessingMode(VertexFormat format) {
// Fast passthrough for the most common vanilla block/item formats.
if (format == VertexFormats.POSITION_COLOR_UV_LMAP || format == VertexFormats.POSITION_COLOR_UV_NORMAL) {
return MODE_COPY_FAST;
} else {
Integer cached = vertexFormatCache.get(format);
if (cached == null) {
// VertexFormats are mutable, so we need to make an immutable copy.
format = new VertexFormat(format);
cached = computeProcessingMode(format);
vertexFormatCache.put(format, cached);
}
return cached;
}
}
}

View file

@ -39,6 +39,7 @@ public abstract class EncodingFormat {
static final int VANILLA_STRIDE = 28;
public static final int NORMALS_OFFSET = VERTEX_START_OFFSET + VANILLA_STRIDE;
static final int NORMALS_STRIDE = 4;
public static final int NORMALS_OFFSET_VANILLA = VANILLA_STRIDE;
// normals are followed by 0-2 sets of color/uv coordinates
static final int TEXTURE_STRIDE = 12;
/** is one tex stride less than the actual base, because when used tex index is >= 1 */

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.indigo.renderer.mixin;
import net.minecraft.client.render.BufferBuilder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BufferBuilder.class)
public interface BufferBuilderOffsetAccessor {
@Accessor
double getOffsetX();
@Accessor
double getOffsetY();
@Accessor
double getOffsetZ();
}

View file

@ -18,47 +18,95 @@ package net.fabricmc.indigo.renderer.mixin;
import java.nio.IntBuffer;
import net.fabricmc.indigo.Indigo;
import net.fabricmc.indigo.renderer.helper.BufferBuilderTransformHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.minecraft.client.render.VertexFormat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.minecraft.client.render.BufferBuilder;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(BufferBuilder.class)
public abstract class MixinBufferBuilder implements AccessBufferBuilder {
@Shadow private IntBuffer bufInt;
@Shadow private int vertexCount;
@Shadow private double offsetX;
@Shadow private double offsetY;
@Shadow private double offsetZ;
@Shadow abstract void grow(int size);
@Shadow abstract int getCurrentSize();
@Shadow public abstract VertexFormat getVertexFormat();
private static final int QUAD_STRIDE_INTS = 28;
private static final int VERTEX_STRIDE_INTS = 7;
private static final int VERTEX_STRIDE_BYTES = VERTEX_STRIDE_INTS * 4;
private static final int QUAD_STRIDE_INTS = VERTEX_STRIDE_INTS * 4;
private static final int QUAD_STRIDE_BYTES = QUAD_STRIDE_INTS * 4;
private int fabric_processingMode;
@Inject(at = @At("RETURN"), method = "begin")
private void afterBegin(int mode, VertexFormat passedFormat, CallbackInfo info) {
fabric_processingMode = BufferBuilderTransformHelper.getProcessingMode(getVertexFormat());
}
/**
* Similar to {@link BufferBuilder#putVertexData(int[])} but
* accepts an array index so that arrays containing more than one
* quad don't have to be copied to a transfer array before the call.
*
* It also always assumes the vanilla data format and is capable of
* transforming data from it to a different, non-vanilla data format.
*/
@Override
public void fabric_putVanillaData(int[] data, int start) {
public void fabric_putVanillaData(int[] data, int start, boolean isItemFormat) {
switch (fabric_processingMode) {
case BufferBuilderTransformHelper.MODE_COPY_FAST: {
this.grow(QUAD_STRIDE_BYTES);
this.bufInt.position(this.getCurrentSize());
this.bufInt.put(data, start, QUAD_STRIDE_INTS);
} break;
case BufferBuilderTransformHelper.MODE_COPY_PADDED: {
int currSize = this.getCurrentSize();
int formatSizeBytes = getVertexFormat().getVertexSize();
int formatSizeInts = formatSizeBytes / 4;
this.grow(formatSizeBytes * 4);
this.bufInt.position(currSize);
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts);
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts * 2);
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts * 3);
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
} break;
case BufferBuilderTransformHelper.MODE_COPY_PADDED_SHADERSMOD: {
int currSize = this.getCurrentSize();
int formatSizeBytes = getVertexFormat().getVertexSize();
int formatSizeInts = formatSizeBytes / 4;
this.grow(formatSizeBytes * 4);
this.bufInt.position(currSize);
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA]);
this.bufInt.position(currSize + formatSizeInts);
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 1]);
this.bufInt.position(currSize + formatSizeInts * 2);
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 2]);
this.bufInt.position(currSize + formatSizeInts * 3);
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 3]);
} break;
case BufferBuilderTransformHelper.MODE_UNSUPPORTED:
// Don't emit any quads.
BufferBuilderTransformHelper.emitUnsupportedError(getVertexFormat());
return;
}
this.vertexCount += 4;
}
@Override
public double fabric_offsetX() {
return offsetX;
}
@Override
public double fabric_offsetY() {
return offsetY;
}
@Override
public double fabric_offsetZ() {
return offsetZ;
}
}

View file

@ -18,6 +18,9 @@ package net.fabricmc.indigo.renderer.mixin;
import java.util.Random;
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.indigo.Indigo;
import net.minecraft.client.render.model.BakedModel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -114,12 +117,15 @@ public abstract class MixinChunkRenderer implements AccessChunkRenderer{
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;tesselateBlock(Lnet/minecraft/block/BlockState;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/ExtendedBlockView;Lnet/minecraft/client/render/BufferBuilder;Ljava/util/Random;)Z"))
private boolean hookChunkBuildTesselate(BlockRenderManager renderManager, BlockState blockState, BlockPos blockPos, ExtendedBlockView blockView, BufferBuilder bufferBuilder, Random random) {
if(blockState.getRenderType() == BlockRenderType.MODEL) {
return ((AccessChunkRendererRegion)blockView).fabric_getRenderer().tesselateBlock(blockState, blockPos);
} else {
return renderManager.tesselateBlock(blockState, blockPos, blockView, bufferBuilder, random);
final BakedModel model = renderManager.getModel(blockState);
if (Indigo.ALWAYS_TESSELATE_INDIGO || !((FabricBakedModel) model).isVanillaAdapter()) {
return ((AccessChunkRendererRegion) blockView).fabric_getRenderer().tesselateBlock(blockState, blockPos, model);
}
}
return renderManager.tesselateBlock(blockState, blockPos, blockView, bufferBuilder, random);
}
/**
* Release all references. Probably not necessary but would be $#%! to debug if it is.
*/

View file

@ -68,7 +68,7 @@ public abstract class AbstractQuadRenderer {
/** final output step, common to all renders */
private void bufferQuad(MutableQuadViewImpl quad, int renderLayer) {
bufferFunc.get(renderLayer).fabric_putVanillaData(quad.data(), quad.vertexStart());
bufferFunc.get(renderLayer).fabric_putVanillaData(quad.data(), quad.vertexStart(), false);
}
// routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop

View file

@ -28,6 +28,7 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.indigo.renderer.mixin.BufferBuilderOffsetAccessor;
import net.minecraft.block.BlockState;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.block.BlockModelRenderer;
@ -102,11 +103,11 @@ public class BlockRenderContext extends AbstractRenderContext implements RenderC
}
private void setupOffsets() {
final AccessBufferBuilder buffer = fabricBuffer;
final BufferBuilderOffsetAccessor buffer = (BufferBuilderOffsetAccessor) fabricBuffer;
final BlockPos pos = blockInfo.blockPos;
offsetX = buffer.fabric_offsetX() + pos.getX();
offsetY = buffer.fabric_offsetY() + pos.getY();
offsetZ = buffer.fabric_offsetZ() + pos.getZ();
offsetX = buffer.getOffsetX() + pos.getX();
offsetY = buffer.getOffsetY() + pos.getY();
offsetZ = buffer.getOffsetZ() + pos.getZ();
}
private class MeshConsumer extends AbstractMeshConsumer {

View file

@ -182,7 +182,7 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
c = ColorHelper.multiplyColor(quadColor, c);
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(c));
}
fabricBuffer.fabric_putVanillaData(quadData, EncodingFormat.VERTEX_START_OFFSET);
fabricBuffer.fabric_putVanillaData(quadData, EncodingFormat.VERTEX_START_OFFSET, true);
}
private void renderQuad() {

View file

@ -70,9 +70,8 @@ public class TerrainRenderContext extends AbstractRenderContext implements Rende
}
/** Called from chunk renderer hook. */
public boolean tesselateBlock(BlockState blockState, BlockPos blockPos) {
public boolean tesselateBlock(BlockState blockState, BlockPos blockPos, final BakedModel model) {
try {
final BakedModel model = blockRenderManager.getModel(blockState);
aoCalc.clear();
blockInfo.prepareForBlock(blockState, blockPos, model.useAmbientOcclusion());
chunkInfo.beginBlock();

View file

@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_8",
"plugin": "net.fabricmc.indigo.IndigoMixinConfigPlugin",
"mixins": [
"BufferBuilderOffsetAccessor",
"MixinBlockModelRenderer",
"MixinBufferBuilder",
"MixinChunkRenderer",