diff --git a/src/main/java/org/embeddedt/embeddium/api/options/structure/StandardOptions.java b/src/main/java/org/embeddedt/embeddium/api/options/structure/StandardOptions.java
index f9190abb7..a7fe0e613 100644
--- a/src/main/java/org/embeddedt/embeddium/api/options/structure/StandardOptions.java
+++ b/src/main/java/org/embeddedt/embeddium/api/options/structure/StandardOptions.java
@@ -61,5 +61,6 @@ public static class Option {
public static final ResourceLocation CPU_FRAMES_AHEAD = ResourceLocation.fromNamespaceAndPath(Embeddium.MODID, "cpu_render_ahead_limit");
public static final ResourceLocation TRANSLUCENT_FACE_SORTING = ResourceLocation.fromNamespaceAndPath(Embeddium.MODID, "translucent_face_sorting");
public static final ResourceLocation USE_QUAD_NORMALS_FOR_LIGHTING = ResourceLocation.fromNamespaceAndPath(Embeddium.MODID, "use_quad_normals_for_lighting");
+ public static final ResourceLocation RENDER_PASS_OPTIMIZATION = ResourceLocation.fromNamespaceAndPath(Embeddium.MODID, "render_pass_optimization");
}
}
diff --git a/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumGameOptionPages.java b/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumGameOptionPages.java
index 3a4da71ee..403840cdd 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumGameOptionPages.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumGameOptionPages.java
@@ -391,6 +391,15 @@ public static OptionPage performance() {
.setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE)
.build()
)
+ .add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
+ .setId(StandardOptions.Option.RENDER_PASS_OPTIMIZATION)
+ .setName(Component.translatable("embeddium.options.use_render_pass_optimization.name"))
+ .setTooltip(Component.translatable("embeddium.options.use_render_pass_optimization.tooltip"))
+ .setControl(TickBoxControl::new)
+ .setImpact(OptionImpact.LOW)
+ .setBinding((opts, value) -> opts.performance.useRenderPassOptimization = value, opts -> opts.performance.useRenderPassOptimization)
+ .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
+ .build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setId(StandardOptions.Option.NO_ERROR_CONTEXT)
.setName(Component.translatable("sodium.options.use_no_error_context.name"))
diff --git a/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumOptions.java b/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumOptions.java
index 2edc3e5ed..0c3c843f2 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumOptions.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/gui/EmbeddiumOptions.java
@@ -51,6 +51,7 @@ public static class PerformanceSettings {
public boolean useCompactVertexFormat = true;
@SerializedName("use_translucent_face_sorting_v2")
public boolean useTranslucentFaceSorting = true;
+ public boolean useRenderPassOptimization = true;
public boolean useNoErrorGLContext = true;
}
diff --git a/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadFactoryMixin.java b/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadFactoryMixin.java
new file mode 100644
index 000000000..750b41d12
--- /dev/null
+++ b/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadFactoryMixin.java
@@ -0,0 +1,43 @@
+package org.embeddedt.embeddium.impl.mixin.core.model.quad;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.block.model.BlockElementFace;
+import net.minecraft.client.renderer.block.model.FaceBakery;
+import net.minecraft.client.renderer.texture.SpriteContents;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import org.embeddedt.embeddium.impl.model.quad.BakedQuadView;
+import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFlags;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(FaceBakery.class)
+public class BakedQuadFactoryMixin {
+ /**
+ * @author embeddedt
+ * @reason Check if quad's UVs are contained within the sprite's boundaries; if so, mark it as having a trusted sprite
+ * (meaning the particle sprite matches the encoded UVs)
+ */
+ @ModifyReturnValue(method = "bakeQuad", at = @At("RETURN"))
+ private BakedQuad setMaterialClassification(BakedQuad quad, @Local(ordinal = 0, argsOnly = true) BlockElementFace face, @Local(ordinal = 0, argsOnly = true) TextureAtlasSprite sprite) {
+ if (sprite.getClass() == TextureAtlasSprite.class && sprite.contents().getClass() == SpriteContents.class) {
+ float[] uvs = face.uv().uvs;
+ float minUV = Float.MAX_VALUE, maxUV = Float.MIN_VALUE;
+
+ for (float uv : uvs) {
+ minUV = Math.min(minUV, uv);
+ maxUV = Math.max(maxUV, uv);
+ }
+
+ if (minUV >= 0 && maxUV <= 16) {
+ // Quad UVs do not extend outside texture boundary, we can trust the given sprite
+ BakedQuadView view = (BakedQuadView)quad;
+ view.setFlags(view.getFlags() | ModelQuadFlags.IS_TRUSTED_SPRITE);
+ }
+
+ }
+
+ return quad;
+ }
+}
diff --git a/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadMixin.java b/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadMixin.java
index 166231cbc..92a62157e 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadMixin.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/mixin/core/model/quad/BakedQuadMixin.java
@@ -97,8 +97,8 @@ public int getForgeNormal(int idx) {
@Override
public int getFlags() {
int f = this.flags;
- if (f == 0) {
- this.flags = f = ModelQuadFlags.getQuadFlags(this, direction);
+ if ((f & ModelQuadFlags.IS_POPULATED) == 0) {
+ this.flags = f = (f | ModelQuadFlags.getQuadFlags(this, direction));
}
return f;
}
diff --git a/src/main/java/org/embeddedt/embeddium/impl/mixin/features/textures/mipmaps/SpriteContentsMixin.java b/src/main/java/org/embeddedt/embeddium/impl/mixin/features/textures/mipmaps/SpriteContentsMixin.java
index f79fdc1fc..fb6a9f755 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/mixin/features/textures/mipmaps/SpriteContentsMixin.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/mixin/features/textures/mipmaps/SpriteContentsMixin.java
@@ -7,6 +7,8 @@
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FastColor;
+import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
+import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevelHolder;
import org.lwjgl.system.MemoryUtil;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
@@ -18,10 +20,10 @@
import org.spongepowered.asm.mixin.injection.Redirect;
/**
- * This Mixin is ported from Iris at MixinTextureAtlasSprite.
+ * This Mixin is partially ported from Iris at MixinTextureAtlasSprite.
*/
@Mixin(SpriteContents.class)
-public class SpriteContentsMixin {
+public class SpriteContentsMixin implements SpriteTransparencyLevelHolder {
@Mutable
@Shadow
@Final
@@ -31,6 +33,9 @@ public class SpriteContentsMixin {
@Final
private ResourceLocation name;
+ @Unique
+ private SpriteTransparencyLevel embeddium$transparencyLevel;
+
// While Fabric allows us to @Inject into the constructor here, that's just a specific detail of FabricMC's mixin
// fork. Upstream Mixin doesn't allow arbitrary @Inject usage in constructor. However, we can use @ModifyVariable
// just fine, in a way that hopefully doesn't conflict with other mods.
@@ -41,11 +46,9 @@ public class SpriteContentsMixin {
// cross-platform.
@Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;originalImage:Lcom/mojang/blaze3d/platform/NativeImage;", opcode = Opcodes.PUTFIELD))
private void sodium$beforeGenerateMipLevels(SpriteContents instance, NativeImage nativeImage, ResourceLocation identifier) {
- // Embeddium: Only fill in transparent colors if mipmaps are on and the texture name does not contain "leaves".
+ // Only fill in transparent colors if mipmaps are on and the texture name does not contain "leaves".
// We're injecting after the "name" field has been set, so this is safe even though we're in a constructor.
- if (Minecraft.getInstance().options.mipmapLevels().get() > 0 && !this.name.getPath().contains("leaves")) {
- sodium$fillInTransparentPixelColors(nativeImage);
- }
+ embeddium$processTransparentImages(nativeImage, Minecraft.getInstance().options.mipmapLevels().get() > 0 && !this.name.getPath().contains("leaves"));
this.originalImage = nativeImage;
}
@@ -59,7 +62,7 @@ public class SpriteContentsMixin {
* black color does not leak over into sampling.
*/
@Unique
- private static void sodium$fillInTransparentPixelColors(NativeImage nativeImage) {
+ private void embeddium$processTransparentImages(NativeImage nativeImage, boolean shouldRewriteColors) {
final long ppPixel = NativeImageHelper.getPointerRGBA(nativeImage);
final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth();
@@ -71,6 +74,8 @@ public class SpriteContentsMixin {
float totalWeight = 0.0f;
+ SpriteTransparencyLevel level = SpriteTransparencyLevel.OPAQUE;
+
for (int pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) {
long pPixel = ppPixel + (pixelIndex * 4);
@@ -78,20 +83,32 @@ public class SpriteContentsMixin {
int alpha = FastColor.ABGR32.alpha(color);
// Ignore all fully-transparent pixels for the purposes of computing an average color.
- if (alpha != 0) {
- float weight = (float) alpha;
-
- // Make sure to convert to linear space so that we don't lose brightness.
- r += ColorSRGB.srgbToLinear(FastColor.ABGR32.red(color)) * weight;
- g += ColorSRGB.srgbToLinear(FastColor.ABGR32.green(color)) * weight;
- b += ColorSRGB.srgbToLinear(FastColor.ABGR32.blue(color)) * weight;
-
- totalWeight += weight;
+ if (alpha > 0) {
+ if(alpha < 255) {
+ level = level.chooseNextLevel(SpriteTransparencyLevel.TRANSLUCENT);
+ } else {
+ level = level.chooseNextLevel(SpriteTransparencyLevel.OPAQUE);
+ }
+
+ if (shouldRewriteColors) {
+ float weight = (float) alpha;
+
+ // Make sure to convert to linear space so that we don't lose brightness.
+ r += ColorSRGB.srgbToLinear(FastColor.ABGR32.red(color)) * weight;
+ g += ColorSRGB.srgbToLinear(FastColor.ABGR32.green(color)) * weight;
+ b += ColorSRGB.srgbToLinear(FastColor.ABGR32.blue(color)) * weight;
+
+ totalWeight += weight;
+ }
+ } else {
+ level = level.chooseNextLevel(SpriteTransparencyLevel.TRANSPARENT);
}
}
- // Bail if none of the pixels are semi-transparent.
- if (totalWeight == 0.0f) {
+ this.embeddium$transparencyLevel = level;
+
+ // Bail if none of the pixels are semi-transparent or we aren't supposed to rewrite colors.
+ if (!shouldRewriteColors || totalWeight == 0.0f) {
return;
}
@@ -115,4 +132,9 @@ public class SpriteContentsMixin {
}
}
}
+
+ @Override
+ public SpriteTransparencyLevel embeddium$getTransparencyLevel() {
+ return this.embeddium$transparencyLevel;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/org/embeddedt/embeddium/impl/model/quad/properties/ModelQuadFlags.java b/src/main/java/org/embeddedt/embeddium/impl/model/quad/properties/ModelQuadFlags.java
index 2f3ece438..13faedcf3 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/model/quad/properties/ModelQuadFlags.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/model/quad/properties/ModelQuadFlags.java
@@ -26,6 +26,10 @@ public class ModelQuadFlags {
* the normals of each vertex.
*/
public static final int IS_VANILLA_SHADED = 0b1000;
+ /**
+ * Indicates that the particle sprite on this quad can be trusted to be the only sprite it shows.
+ */
+ public static final int IS_TRUSTED_SPRITE = (1 << 4);
/**
* Indicates that the flags are populated for the quad.
*/
diff --git a/src/main/java/org/embeddedt/embeddium/impl/render/chunk/compile/pipeline/BlockRenderer.java b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/compile/pipeline/BlockRenderer.java
index 9d9f0a33a..2438223f1 100644
--- a/src/main/java/org/embeddedt/embeddium/impl/render/chunk/compile/pipeline/BlockRenderer.java
+++ b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/compile/pipeline/BlockRenderer.java
@@ -3,6 +3,7 @@
import com.mojang.blaze3d.vertex.PoseStack;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.embeddedt.embeddium.api.render.chunk.BlockRenderContext;
+import org.embeddedt.embeddium.impl.Embeddium;
import org.embeddedt.embeddium.impl.model.color.ColorProvider;
import org.embeddedt.embeddium.impl.model.color.ColorProviderRegistry;
import org.embeddedt.embeddium.impl.model.light.LightMode;
@@ -11,9 +12,13 @@
import org.embeddedt.embeddium.impl.model.light.data.QuadLightData;
import org.embeddedt.embeddium.impl.model.quad.BakedQuadView;
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
+import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFlags;
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadOrientation;
+import org.embeddedt.embeddium.impl.render.ShaderModBridge;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
import org.embeddedt.embeddium.impl.render.chunk.compile.buffers.ChunkModelBuilder;
+import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
+import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevelHolder;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.DefaultMaterials;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material;
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexEncoder;
@@ -76,6 +81,8 @@ public class BlockRenderer {
private final ChunkColorWriter colorEncoder = ChunkColorWriter.get();
+ private final boolean useRenderPassOptimization;
+
public BlockRenderer(ColorProviderRegistry colorRegistry, LightPipelineProvider lighters) {
this.colorProviderRegistry = colorRegistry;
this.lighters = lighters;
@@ -83,6 +90,7 @@ public BlockRenderer(ColorProviderRegistry colorRegistry, LightPipelineProvider
this.occlusionCache = new BlockOcclusionCache();
this.useAmbientOcclusion = Minecraft.useAmbientOcclusion();
this.fabricModelRenderingHandler = FRAPIRenderHandler.INDIGO_PRESENT ? new IndigoBlockRenderContext(this.occlusionCache, lighters.getLightData()) : null;
+ this.useRenderPassOptimization = Embeddium.options().performance.useRenderPassOptimization && !ShaderModBridge.areShadersEnabled();
}
/**
@@ -136,7 +144,7 @@ public void renderModel(BlockRenderContext ctx, ChunkBuildBuffers buffers) {
if (!quads.isEmpty() && this.isFaceVisible(ctx, face)) {
this.useReorienting = true;
- this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, meshBuilder, quads, face);
+ this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, buffers, meshBuilder, quads, face);
if (!this.useReorienting) {
// Reorienting was disabled on this side, make sure it's disabled for the null cullface too, in case
// a mod layers textures in different lists
@@ -149,7 +157,7 @@ public void renderModel(BlockRenderContext ctx, ChunkBuildBuffers buffers) {
if (!all.isEmpty()) {
this.useReorienting = canReorientNullCullface;
- this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, meshBuilder, all, null);
+ this.renderQuadList(ctx, material, lighter, colorizer, renderOffset, buffers, meshBuilder, all, null);
}
}
@@ -202,8 +210,28 @@ private static boolean checkQuadsHaveSameLightingConfig(List quads) {
return true;
}
+ private ChunkModelBuilder chooseOptimalBuilder(Material defaultMaterial, ChunkBuildBuffers buffers, ChunkModelBuilder defaultBuilder, BakedQuadView quad) {
+ if (defaultMaterial == DefaultMaterials.SOLID || !this.useRenderPassOptimization || (quad.getFlags() & ModelQuadFlags.IS_TRUSTED_SPRITE) == 0 || quad.getSprite() == null) {
+ // No improvement possible
+ return defaultBuilder;
+ }
+
+ SpriteTransparencyLevel level = SpriteTransparencyLevelHolder.getTransparencyLevel(quad.getSprite().contents());
+
+ if (level == SpriteTransparencyLevel.OPAQUE && defaultMaterial.pass.supportsFragmentDiscard()) {
+ // Can use solid with no visual difference
+ return buffers.get(DefaultMaterials.SOLID);
+ } else if (level == SpriteTransparencyLevel.TRANSPARENT && defaultMaterial == DefaultMaterials.TRANSLUCENT) {
+ // Can use cutout_mipped with no visual difference
+ return buffers.get(DefaultMaterials.CUTOUT_MIPPED);
+ } else {
+ // Have to use default
+ return defaultBuilder;
+ }
+ }
+
private void renderQuadList(BlockRenderContext ctx, Material material, LightPipeline lighter, ColorProvider colorizer, Vec3 offset,
- ChunkModelBuilder builder, List quads, Direction cullFace) {
+ ChunkBuildBuffers buffers, ChunkModelBuilder defaultBuilder, List quads, Direction cullFace) {
if(!checkQuadsHaveSameLightingConfig(quads)) {
// Disable reorienting if quads use different light configurations, as otherwise layered quads
@@ -219,6 +247,8 @@ private void renderQuadList(BlockRenderContext ctx, Material material, LightPipe
final var lightData = this.getVertexLight(ctx, quad.hasAmbientOcclusion() ? lighter : this.lighters.getLighter(LightMode.FLAT), cullFace, quad);
final var vertexColors = this.getVertexColors(ctx, colorizer, quad);
+ ChunkModelBuilder builder = this.chooseOptimalBuilder(material, buffers, defaultBuilder, quad);
+
this.writeGeometry(ctx, builder, offset, material, quad, vertexColors, lightData);
TextureAtlasSprite sprite = quad.getSprite();
diff --git a/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevel.java b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevel.java
new file mode 100644
index 000000000..db7af4d2b
--- /dev/null
+++ b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevel.java
@@ -0,0 +1,14 @@
+package org.embeddedt.embeddium.impl.render.chunk.sprite;
+
+public enum SpriteTransparencyLevel {
+ OPAQUE,
+ TRANSPARENT,
+ TRANSLUCENT;
+
+ /**
+ * {@return whichever level has a higher ordinal, i.e. requires a better render pass}
+ */
+ public SpriteTransparencyLevel chooseNextLevel(SpriteTransparencyLevel level) {
+ return level.ordinal() >= this.ordinal() ? level : this;
+ }
+}
diff --git a/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevelHolder.java b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevelHolder.java
new file mode 100644
index 000000000..7a3a376cc
--- /dev/null
+++ b/src/main/java/org/embeddedt/embeddium/impl/render/chunk/sprite/SpriteTransparencyLevelHolder.java
@@ -0,0 +1,11 @@
+package org.embeddedt.embeddium.impl.render.chunk.sprite;
+
+import net.minecraft.client.renderer.texture.SpriteContents;
+
+public interface SpriteTransparencyLevelHolder {
+ SpriteTransparencyLevel embeddium$getTransparencyLevel();
+
+ static SpriteTransparencyLevel getTransparencyLevel(SpriteContents contents) {
+ return ((SpriteTransparencyLevelHolder)contents).embeddium$getTransparencyLevel();
+ }
+}
diff --git a/src/main/resources/assets/embeddium/lang/en_us.json b/src/main/resources/assets/embeddium/lang/en_us.json
index 5316266b2..42b04211d 100644
--- a/src/main/resources/assets/embeddium/lang/en_us.json
+++ b/src/main/resources/assets/embeddium/lang/en_us.json
@@ -70,5 +70,7 @@
"embeddium.options.added_by_mod_string": "Added by mod: %s",
"embeddium.options.fullscreen.resolution.tooltip": "Controls the resolution of the game in fullscreen mode.",
"embeddium.options.use_quad_normals_for_lighting.name": "Use Accurate Quad Shading",
- "embeddium.options.use_quad_normals_for_lighting.tooltip": "When enabled, Embeddium will apply shading to non-vanilla block faces based on the true direction they are facing, not their axis-aligned direction. This can improve lighting quality when the Forge experimental light pipeline is disabled (which is recommended for best performance).\n\nIt has no effect if the experimental light pipeline is enabled."
+ "embeddium.options.use_quad_normals_for_lighting.tooltip": "When enabled, Embeddium will apply shading to non-vanilla block faces based on the true direction they are facing, not their axis-aligned direction. This can improve lighting quality when the Forge experimental light pipeline is disabled (which is recommended for best performance).\n\nIt has no effect if the experimental light pipeline is enabled.",
+ "embeddium.options.use_render_pass_optimization.name": "Use Render Pass Optimization",
+ "embeddium.options.use_render_pass_optimization.tooltip": "When enabled, Embeddium will detect block model faces that are marked as transparent (or translucent) when the texture is actually opaque (or transparent) and automatically use a more optimal render pass for them.\n\nThis optimization has no effect when a shader pack is active."
}
diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png
index 46ffbf262..5c27a83bc 100644
Binary files a/src/main/resources/icon.png and b/src/main/resources/icon.png differ