mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Attempt at making mipmaps better (#1952)
This commit is contained in:
1 parent
4910cfd6d3
commit
56d7568fd3
30 files changed
+1411
-289
No files matched your search
@@ -4079,8 +4079,8 @@ public class GLStateManager {
|
||||
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> info.getWidth();
|
||||
case GL11.GL_TEXTURE_HEIGHT -> info.getHeight();
|
||||
case GL11.GL_TEXTURE_WIDTH -> Math.max(info.getWidth() >> level, 1);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> Math.max(info.getHeight() >> level, 1);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
|
||||
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
|
||||
|
||||
@@ -250,7 +250,8 @@ public enum Mixins implements IMixins {
|
||||
, "celeritas.frustum.MixinFrustrum"
|
||||
, "celeritas.features.culling.MixinEffectRenderer"
|
||||
, "celeritas.features.mipmaps.MixinTextureAtlasSprite"
|
||||
, "celeritas.features.mipmaps.MixinTextureUtil"
|
||||
, "celeritas.features.mipmaps.MixinTextureMetadataSection"
|
||||
, "celeritas.features.mipmaps.MixinTextureMetadataSectionSerializer"
|
||||
, "celeritas.features.textures.MixinTextureMap"
|
||||
, "celeritas.features.textures.MixinTextureAtlasSprite"
|
||||
, "celeritas.biome_blending.MixinBlockGrass"
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
|
||||
public interface TextureMetadataExtension {
|
||||
|
||||
void angelica$setMipmapStrategy(MipmapStrategy strategy);
|
||||
|
||||
MipmapStrategy angelica$getMipmapStrategy();
|
||||
}
|
||||
+25
-22
@@ -48,19 +48,18 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
private final WorldSlice worldSlice;
|
||||
@Getter
|
||||
private final BlockRenderContext blockRenderContext = new BlockRenderContext();
|
||||
|
||||
private List<IDynamicLightSource> chunkLightSources;
|
||||
private DynamicLights dynamicLightsInstance;
|
||||
|
||||
private final LightDataCache lightDataCache = new LightDataCache();
|
||||
private final SmoothLightPipeline smoothLightPipeline;
|
||||
private final FlatLightPipeline flatLightPipeline;
|
||||
private final QuadLightData quadLightData = new QuadLightData();
|
||||
private final VertexArrayQuadView quadView;
|
||||
private final boolean hasColoredLight;
|
||||
private final float[] tintResult = new float[3];
|
||||
private List<IDynamicLightSource> chunkLightSources;
|
||||
private DynamicLights dynamicLightsInstance;
|
||||
private boolean lightPipelineReady = false;
|
||||
private int originX, originY, originZ;
|
||||
private float cachedSkylightSubtracted;
|
||||
private final boolean hasColoredLight;
|
||||
|
||||
public AngelicaChunkBuildContext(RenderPassConfiguration<?> renderPassConfiguration, WorldClient world) {
|
||||
super(renderPassConfiguration);
|
||||
@@ -98,19 +97,24 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
}
|
||||
}
|
||||
|
||||
private Material selectMaterial(Material material, TextureAtlasSprite sprite, boolean isShaderPackOverride) {
|
||||
private Material selectMaterial(Material material, TextureAtlasSprite sprite, boolean isShaderPackOverride,
|
||||
boolean useRenderPassOptimization) {
|
||||
// Don't apply transparency-based optimization when shader pack explicitly overrides the material
|
||||
if (!ClientProxy.options().performance.useRenderPassOptimization || isShaderPackOverride) {
|
||||
if (isShaderPackOverride) {
|
||||
return material;
|
||||
}
|
||||
if (sprite != null && sprite.getClass() == TextureAtlasSprite.class && !sprite.hasAnimationMetadata()) {
|
||||
final var transparencyLevel = ((SpriteExtension)sprite).celeritas$getTransparencyLevel();
|
||||
if (transparencyLevel == SpriteTransparencyLevel.OPAQUE && material == AngelicaRenderPassConfiguration.CUTOUT_MIPPED_MATERIAL) {
|
||||
|
||||
if (useRenderPassOptimization
|
||||
&& sprite != null && sprite.getClass() == TextureAtlasSprite.class && !sprite.hasAnimationMetadata()) {
|
||||
final var transparencyLevel = ((SpriteExtension) sprite).celeritas$getTransparencyLevel();
|
||||
if (transparencyLevel == SpriteTransparencyLevel.OPAQUE
|
||||
&& material == AngelicaRenderPassConfiguration.CUTOUT_MIPPED_MATERIAL) {
|
||||
return AngelicaRenderPassConfiguration.SOLID_MATERIAL;
|
||||
} else if (material == AngelicaRenderPassConfiguration.TRANSLUCENT_MATERIAL && transparencyLevel != SpriteTransparencyLevel.TRANSLUCENT) {
|
||||
return AngelicaRenderPassConfiguration.CUTOUT_MIPPED_MATERIAL;
|
||||
}
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
@@ -123,7 +127,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
return;
|
||||
}
|
||||
|
||||
final var animatedSprites = ((MinecraftBuiltRenderSectionData<TextureAtlasSprite, TileEntity>)buffers.getSectionContextBundle()).animatedSprites;
|
||||
final var animatedSprites = ((MinecraftBuiltRenderSectionData<TextureAtlasSprite, TileEntity>) buffers.getSectionContextBundle()).animatedSprites;
|
||||
|
||||
if ((vertexCount & 0x3) != 0) {
|
||||
throw new IllegalStateException("Only quads are supported, got: " + vertexCount);
|
||||
@@ -132,6 +136,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
final boolean hasDynamicLights = chunkLightSources != null && !chunkLightSources.isEmpty();
|
||||
final boolean separateAo = BlockRenderingSettings.INSTANCE.shouldUseSeparateAo();
|
||||
final boolean celeritasSmoothLighting = ClientProxy.options().quality.useCeleritasSmoothLighting;
|
||||
final boolean useRenderPassOptimization = ClientProxy.options().performance.useRenderPassOptimization;
|
||||
final boolean shaderActive = IrisApi.getInstance().isShaderPackInUse();
|
||||
final boolean useAoCalculation = lightPipelineReady && (separateAo || celeritasSmoothLighting || shaderActive);
|
||||
final boolean shouldApplyDiffuse = !BlockRenderingSettings.INSTANCE.shouldDisableDirectionalShading();
|
||||
@@ -150,7 +155,9 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
final short originalBlockId = blockRenderContext.blockId;
|
||||
|
||||
int stateIdx = 0;
|
||||
int facesAtBaseMaterial = 0;
|
||||
boolean hasCachedMaterial = false;
|
||||
TextureAtlasSprite lastSprite = null;
|
||||
Material lastMaterial = null;
|
||||
|
||||
for (int quadIdx = 0; quadIdx < numQuads; quadIdx++) {
|
||||
float uSum = 0, vSum = 0;
|
||||
@@ -243,16 +250,14 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
// Apply RGB block light tint from provider
|
||||
applyBlockLightTint(worldX, worldY, worldZ, vertices);
|
||||
|
||||
final int faceBit = 1 << facing.ordinal();
|
||||
final Material correctMaterial;
|
||||
if ((facesAtBaseMaterial & faceBit) != 0) {
|
||||
// Face already has a non-demoted quad, skip selectMaterial entirely
|
||||
correctMaterial = material;
|
||||
if (hasCachedMaterial && sprite == lastSprite) {
|
||||
correctMaterial = lastMaterial;
|
||||
} else {
|
||||
correctMaterial = selectMaterial(material, sprite, isShaderPackOverride);
|
||||
if (correctMaterial == material) {
|
||||
facesAtBaseMaterial |= faceBit;
|
||||
}
|
||||
correctMaterial = selectMaterial(material, sprite, isShaderPackOverride, useRenderPassOptimization);
|
||||
lastSprite = sprite;
|
||||
lastMaterial = correctMaterial;
|
||||
hasCachedMaterial = true;
|
||||
}
|
||||
final var builder = buffers.get(correctMaterial);
|
||||
|
||||
@@ -267,8 +272,6 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
}
|
||||
}
|
||||
|
||||
private final float[] tintResult = new float[3];
|
||||
|
||||
private void applyBlockLightTint(int x, int y, int z, ChunkVertexEncoder.Vertex[] vertices) {
|
||||
if (!hasColoredLight) return;
|
||||
final ExtQuadLightData ext = (ExtQuadLightData) quadLightData;
|
||||
|
||||
+92
@@ -2,24 +2,43 @@ package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.RenderDevice;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderParser;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.DefaultChunkRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.IndirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.MultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderBindingPoints;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderInterface;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderOptions;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderTextureSlot;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.DefaultChunkShaderInterface;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
private static final int BLOCK_TEXTURE_UNIT = 0;
|
||||
|
||||
private GlProgram<? extends ChunkShaderInterface> irisProgram;
|
||||
private boolean usingIrisProgram;
|
||||
private int rgssSampler;
|
||||
private boolean rgssSamplerResolved;
|
||||
private boolean rgssSamplerBound;
|
||||
|
||||
public AngelicaChunkRenderer(RenderDevice device, RenderPassConfiguration<?> renderPassConfiguration) {
|
||||
super(device, renderPassConfiguration, createEmitter());
|
||||
@@ -48,6 +67,11 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
};
|
||||
}
|
||||
|
||||
private static GlShader loadShader(ShaderType type, String path, ShaderConstants constants) {
|
||||
final String source = ShaderParser.parseShader(ShaderLoader.getShaderSource(path), ShaderLoader::getShaderSource, constants);
|
||||
return new GlShader(type, path, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void begin(TerrainRenderPass pass) {
|
||||
final IrisShaderProvider provider = IrisShaderProviderHolder.getProvider();
|
||||
@@ -70,10 +94,13 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
this.usingIrisProgram = false;
|
||||
this.irisProgram = null;
|
||||
super.begin(pass);
|
||||
bindRgssSampler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void end(TerrainRenderPass pass) {
|
||||
unbindRgssSampler();
|
||||
|
||||
if (usingIrisProgram && irisProgram != null) {
|
||||
irisProgram.getInterface().restoreState();
|
||||
irisProgram.unbind();
|
||||
@@ -87,6 +114,71 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
super.end(pass);
|
||||
}
|
||||
|
||||
private void bindRgssSampler() {
|
||||
if (!AngelicaRenderPassConfiguration.isRgssEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rgssSamplerResolved) {
|
||||
rgssSamplerResolved = true;
|
||||
rgssSampler = RenderSystem.genSampler();
|
||||
if (rgssSampler != 0) {
|
||||
RenderSystem.samplerParameteri(rgssSampler, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
|
||||
RenderSystem.samplerParameteri(rgssSampler, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
} else {
|
||||
AngelicaMod.LOGGER.warn("Sampler objects unavailable; RGSS terrain filtering will fall back to nearest sampling");
|
||||
}
|
||||
}
|
||||
|
||||
if (rgssSampler != 0) {
|
||||
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, rgssSampler);
|
||||
rgssSamplerBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void unbindRgssSampler() {
|
||||
if (rgssSamplerBound) {
|
||||
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, 0);
|
||||
rgssSamplerBound = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(CommandList commandList) {
|
||||
super.delete(commandList);
|
||||
|
||||
unbindRgssSampler();
|
||||
RenderSystem.destroySampler(rgssSampler);
|
||||
rgssSampler = 0;
|
||||
rgssSamplerResolved = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GlProgram<ChunkShaderInterface> createShader(String path, ChunkShaderOptions options) {
|
||||
if (this.enableLegacyGLPatches) {
|
||||
return super.createShader(path, options);
|
||||
}
|
||||
|
||||
final ShaderConstants constants = options.constants();
|
||||
final List<GlShader> loadedShaders = new ArrayList<>();
|
||||
|
||||
try {
|
||||
loadedShaders.add(loadShader(ShaderType.VERTEX, "sodium:" + path + ".vsh", constants));
|
||||
loadedShaders.add(loadShader(ShaderType.FRAGMENT, "angelica:" + path + ".fsh", constants));
|
||||
|
||||
final var builder = GlProgram.builder("sodium:chunk_shader");
|
||||
loadedShaders.forEach(builder::attachShader);
|
||||
int i = 0;
|
||||
for (var attr : options.pass().vertexType().getVertexFormat().getAttributes()) {
|
||||
builder.bindAttribute(attr.getName(), i++);
|
||||
}
|
||||
builder.bindFragmentData("fragColor", ChunkShaderBindingPoints.FRAG_COLOR);
|
||||
return builder.link((shader) -> new DefaultChunkShaderInterface(shader, options));
|
||||
} finally {
|
||||
loadedShaders.forEach(GlShader::delete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useBlockFaceCulling() {
|
||||
return IrisShaderProviderHolder.shouldUseFaceCulling();
|
||||
|
||||
+55
-24
@@ -3,6 +3,9 @@ package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.QuadPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
@@ -16,37 +19,27 @@ import java.util.Map;
|
||||
public class AngelicaRenderPassConfiguration {
|
||||
|
||||
public static TerrainRenderPass SOLID_PASS, CUTOUT_MIPPED_PASS, TRANSLUCENT_PASS;
|
||||
public static Material SOLID_MATERIAL, CUTOUT_MIPPED_MATERIAL, TRANSLUCENT_MATERIAL;
|
||||
public static Material SOLID_MATERIAL, CUTOUT_MIPPED_MATERIAL, CUTOUT_MATERIAL, TRANSLUCENT_MATERIAL;
|
||||
|
||||
private record AngelicaPipelineState(int pass, boolean disableAlphaTest) implements TerrainRenderPass.PipelineState {
|
||||
@Override
|
||||
public void setup() {
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
if (pass == 0) {
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
}
|
||||
if (disableAlphaTest) {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (disableAlphaTest) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Getter
|
||||
private static boolean rgssEnabled;
|
||||
|
||||
private static TerrainRenderPass.TerrainRenderPassBuilder builderForRenderType(int pass, boolean disableAlphaTest, ChunkVertexType vertexType) {
|
||||
return TerrainRenderPass.builder()
|
||||
final TerrainRenderPass.TerrainRenderPassBuilder builder = TerrainRenderPass.builder()
|
||||
.pipelineState(new AngelicaPipelineState(pass, disableAlphaTest))
|
||||
.vertexType(vertexType)
|
||||
.primitiveType(QuadPrimitiveType.TRIANGULATED);
|
||||
|
||||
if (rgssEnabled) {
|
||||
builder.extraDefine("USE_RGSS", "");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static RenderPassConfiguration<BlockRenderLayer> build(ChunkVertexType vertexType) {
|
||||
rgssEnabled = SodiumGameOptions.effectiveTextureFilterMode() == TextureFilterMode.RGSS;
|
||||
|
||||
SOLID_PASS = builderForRenderType(0, true, vertexType)
|
||||
.name("solid")
|
||||
.fragmentDiscard(false)
|
||||
@@ -68,7 +61,8 @@ public class AngelicaRenderPassConfiguration {
|
||||
|
||||
TRANSLUCENT_MATERIAL = new Material(TRANSLUCENT_PASS, AlphaCutoffParameter.ZERO, true);
|
||||
SOLID_MATERIAL = new Material(SOLID_PASS, AlphaCutoffParameter.ZERO, true);
|
||||
CUTOUT_MIPPED_MATERIAL = new Material(CUTOUT_MIPPED_PASS, AlphaCutoffParameter.ONE_TENTH, true);
|
||||
CUTOUT_MIPPED_MATERIAL = new Material(CUTOUT_MIPPED_PASS, AlphaCutoffParameter.HALF, true);
|
||||
CUTOUT_MATERIAL = new Material(CUTOUT_MIPPED_PASS, AlphaCutoffParameter.ONE_TENTH, false);
|
||||
|
||||
final ImmutableListMultimap.Builder<BlockRenderLayer, TerrainRenderPass> vanillaRenderStages = ImmutableListMultimap.builder();
|
||||
vanillaRenderStages.put(BlockRenderLayer.SOLID, SOLID_PASS);
|
||||
@@ -79,10 +73,47 @@ public class AngelicaRenderPassConfiguration {
|
||||
// Material lookup by terrain layer
|
||||
final Map<BlockRenderLayer, Material> renderTypeToMaterialMap = new Reference2ReferenceOpenHashMap<>(4, Reference2ReferenceOpenHashMap.VERY_FAST_LOAD_FACTOR);
|
||||
renderTypeToMaterialMap.put(BlockRenderLayer.SOLID, SOLID_MATERIAL);
|
||||
renderTypeToMaterialMap.put(BlockRenderLayer.CUTOUT, CUTOUT_MIPPED_MATERIAL);
|
||||
renderTypeToMaterialMap.put(BlockRenderLayer.CUTOUT, CUTOUT_MATERIAL);
|
||||
renderTypeToMaterialMap.put(BlockRenderLayer.CUTOUT_MIPPED, CUTOUT_MIPPED_MATERIAL);
|
||||
renderTypeToMaterialMap.put(BlockRenderLayer.TRANSLUCENT, TRANSLUCENT_MATERIAL);
|
||||
|
||||
return new RenderPassConfiguration<>(renderTypeToMaterialMap, vanillaRenderStages.build().asMap(), CUTOUT_MIPPED_MATERIAL, CUTOUT_MIPPED_MATERIAL, TRANSLUCENT_MATERIAL);
|
||||
}
|
||||
|
||||
private static final class AngelicaPipelineState implements TerrainRenderPass.PipelineState {
|
||||
private final int pass;
|
||||
private final boolean disableAlphaTest;
|
||||
private int savedAlphaFunction;
|
||||
private float savedAlphaReference;
|
||||
|
||||
private AngelicaPipelineState(int pass, boolean disableAlphaTest) {
|
||||
this.pass = pass;
|
||||
this.disableAlphaTest = disableAlphaTest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
if (pass == 0) {
|
||||
final var alphaState = GLStateManager.getAlphaState();
|
||||
savedAlphaFunction = alphaState.getFunction();
|
||||
savedAlphaReference = alphaState.getReference();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, AlphaCutoffParameter.HALF.cutoff());
|
||||
}
|
||||
if (disableAlphaTest) {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (pass == 0) {
|
||||
GLStateManager.glAlphaFunc(savedAlphaFunction, savedAlphaReference);
|
||||
}
|
||||
if (disableAlphaTest) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
|
||||
|
||||
public interface SpriteExtension {
|
||||
@@ -10,4 +11,5 @@ public interface SpriteExtension {
|
||||
|
||||
/** One-shot: returns true if marked, then resets. */
|
||||
boolean celeritas$shouldUpdate();
|
||||
void celeritas$setMipmapStrategy(MipmapStrategy strategy, boolean explicit, int textureType);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package com.gtnewhorizons.angelica.utils;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
|
||||
import org.embeddedt.embeddium.api.util.ColorARGB;
|
||||
import org.embeddedt.embeddium.impl.texture.MipmapHelper;
|
||||
|
||||
/**
|
||||
* Mipmap downsampling for atlas sprites, replacing vanilla's {@code TextureUtil.generateMipmapData}.
|
||||
*/
|
||||
public final class MipmapGenerator {
|
||||
|
||||
private static final float CUTOUT_ALPHA_REF = 0.5f;
|
||||
private static final float STRICT_COVERAGE_TARGET = 0.35f;
|
||||
private static final float ALPHA_BIAS = 0.025f;
|
||||
|
||||
private MipmapGenerator() {
|
||||
}
|
||||
|
||||
private static MipmapStrategy resolve(MipmapStrategy strategy, boolean hasTransparentPixel) {
|
||||
if (strategy == null || strategy == MipmapStrategy.AUTO) {
|
||||
return hasTransparentPixel ? MipmapStrategy.CUTOUT : MipmapStrategy.MEAN;
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
static boolean isCutoutStrategy(MipmapStrategy strategy, boolean hasTransparentPixel) {
|
||||
final MipmapStrategy resolved = resolve(strategy, hasTransparentPixel);
|
||||
return resolved == MipmapStrategy.CUTOUT
|
||||
|| resolved == MipmapStrategy.STRICT_CUTOUT
|
||||
|| resolved == MipmapStrategy.DARK_CUTOUT;
|
||||
}
|
||||
|
||||
static void solidify(int[] image, int width, int height) {
|
||||
final int size = Math.min(width * height, image.length);
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasTransparent = false;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (ColorARGB.unpackAlpha(image[i]) == 0) {
|
||||
hasTransparent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTransparent) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int[] color = new int[size];
|
||||
final IntArrayFIFOQueue queue = new IntArrayFIFOQueue(64);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int pixel = image[i];
|
||||
if (ColorARGB.unpackAlpha(pixel) == 0) {
|
||||
continue;
|
||||
}
|
||||
color[i] = pixel;
|
||||
if (hasTransparentNeighbour(image, i, width, size)) {
|
||||
queue.enqueue(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (queue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
final int index = queue.dequeueInt();
|
||||
final int x = index % width;
|
||||
final int source = color[index];
|
||||
|
||||
if (x > 0) {
|
||||
fill(index - 1, source, color, queue);
|
||||
}
|
||||
if (x < width - 1) {
|
||||
fill(index + 1, source, color, queue);
|
||||
}
|
||||
if (index >= width) {
|
||||
fill(index - width, source, color, queue);
|
||||
}
|
||||
if (index + width < size) {
|
||||
fill(index + width, source, color, queue);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (ColorARGB.unpackAlpha(image[i]) == 0) {
|
||||
image[i] = color[i] & 0x00FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasTransparentNeighbour(int[] image, int index, int width, int size) {
|
||||
final int x = index % width;
|
||||
return (x > 0 && ColorARGB.unpackAlpha(image[index - 1]) == 0)
|
||||
|| (x < width - 1 && ColorARGB.unpackAlpha(image[index + 1]) == 0)
|
||||
|| (index >= width && ColorARGB.unpackAlpha(image[index - width]) == 0)
|
||||
|| (index + width < size && ColorARGB.unpackAlpha(image[index + width]) == 0);
|
||||
}
|
||||
|
||||
private static void fill(int neighbour, int source, int[] color, IntArrayFIFOQueue queue) {
|
||||
if (color[neighbour] == 0) {
|
||||
color[neighbour] = source;
|
||||
queue.enqueue(neighbour);
|
||||
}
|
||||
}
|
||||
|
||||
static void fillEmptyAreasWithDarkColor(int[] image) {
|
||||
int darkest = ColorARGB.pack(255, 255, 255, 255);
|
||||
int darkestSum = 255 * 3;
|
||||
|
||||
for (final int pixel : image) {
|
||||
if (ColorARGB.unpackAlpha(pixel) == 0) {
|
||||
continue;
|
||||
}
|
||||
final int sum = ColorARGB.unpackRed(pixel) + ColorARGB.unpackGreen(pixel) + ColorARGB.unpackBlue(pixel);
|
||||
if (sum < darkestSum) {
|
||||
darkestSum = sum;
|
||||
darkest = pixel;
|
||||
}
|
||||
}
|
||||
|
||||
final int fill = ColorARGB.pack(3 * ColorARGB.unpackRed(darkest) / 4,
|
||||
3 * ColorARGB.unpackGreen(darkest) / 4, 3 * ColorARGB.unpackBlue(darkest) / 4, 0);
|
||||
|
||||
for (int i = 0; i < image.length; i++) {
|
||||
if (ColorARGB.unpackAlpha(image[i]) == 0) {
|
||||
image[i] = fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates the fraction of the image that survives, with bilinear filtering modeled by
|
||||
* supersampling each texel quad on a 4x4 interior grid.
|
||||
*/
|
||||
static float alphaTestCoverage(int[] image, int width, float alphaRef, float alphaScale, int border) {
|
||||
final int height = image.length / width;
|
||||
final float alphaFactor = alphaScale / 255.0f;
|
||||
float total = 0.0f;
|
||||
int quads = 0;
|
||||
|
||||
for (int y = border; y < height - border - 1; y++) {
|
||||
for (int x = border; x < width - border - 1; x++) {
|
||||
final int i = x + y * width;
|
||||
final float a00 = scaledAlpha(image[i], alphaFactor);
|
||||
final float a10 = scaledAlpha(image[i + 1], alphaFactor);
|
||||
final float a01 = scaledAlpha(image[i + width], alphaFactor);
|
||||
final float a11 = scaledAlpha(image[i + width + 1], alphaFactor);
|
||||
|
||||
final float lo = Math.min(Math.min(a00, a10), Math.min(a01, a11));
|
||||
if (lo > alphaRef) {
|
||||
total += 1.0f;
|
||||
quads++;
|
||||
continue;
|
||||
}
|
||||
final float hi = Math.max(Math.max(a00, a10), Math.max(a01, a11));
|
||||
if (hi <= alphaRef) {
|
||||
quads++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int hits = 0;
|
||||
for (int sy = 0; sy < 4; sy++) {
|
||||
final float fy = (sy + 0.5f) / 4.0f;
|
||||
for (int sx = 0; sx < 4; sx++) {
|
||||
final float fx = (sx + 0.5f) / 4.0f;
|
||||
final float top = a00 + (a10 - a00) * fx;
|
||||
final float bottom = a01 + (a11 - a01) * fx;
|
||||
if (top + (bottom - top) * fy > alphaRef) {
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
}
|
||||
total += hits / 16.0f;
|
||||
quads++;
|
||||
}
|
||||
}
|
||||
|
||||
if (quads == 0) {
|
||||
return scaledAlpha(image[0], alphaFactor) > alphaRef ? 1.0f : 0.0f;
|
||||
}
|
||||
return total / quads;
|
||||
}
|
||||
|
||||
private static float scaledAlpha(int color, float alphaFactor) {
|
||||
return clamp01(ColorARGB.unpackAlpha(color) * alphaFactor);
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return value < 0.0f ? 0.0f : Math.min(value, 1.0f);
|
||||
}
|
||||
|
||||
static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef, int border) {
|
||||
float min = 0.0f;
|
||||
float max = 4.0f;
|
||||
float scale = 1.0f;
|
||||
float bestScale = 1.0f;
|
||||
float bestError = Float.POSITIVE_INFINITY;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final float coverage = alphaTestCoverage(image, width, alphaRef, scale, border);
|
||||
final float error = Math.abs(coverage - desiredCoverage);
|
||||
if (error < bestError) {
|
||||
bestError = error;
|
||||
bestScale = scale;
|
||||
}
|
||||
|
||||
if (coverage < desiredCoverage) {
|
||||
min = scale;
|
||||
} else if (!(coverage > desiredCoverage)) {
|
||||
break;
|
||||
} else {
|
||||
max = scale;
|
||||
}
|
||||
scale = (min + max) / 2.0f;
|
||||
}
|
||||
|
||||
for (int i = 0; i < image.length; i++) {
|
||||
final int color = image[i];
|
||||
final int rawAlpha = ColorARGB.unpackAlpha(color);
|
||||
final float bias = rawAlpha > 0 ? ALPHA_BIAS : 0.0f;
|
||||
final int alpha = (int) Math.floor(clamp01(rawAlpha / 255.0f * bestScale + bias) * 255.0f);
|
||||
image[i] = ColorARGB.withAlpha(color, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
public static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips,
|
||||
MipmapStrategy strategy, boolean hasTransparentPixel, int border) {
|
||||
|
||||
final MipmapStrategy resolved = resolve(strategy, hasTransparentPixel);
|
||||
final boolean isCutout = isCutoutStrategy(strategy, hasTransparentPixel);
|
||||
final boolean darkCutout = resolved == MipmapStrategy.DARK_CUTOUT;
|
||||
|
||||
final int[][] result = new int[mipLevel + 1][];
|
||||
final int[] base = currentMips[0];
|
||||
|
||||
if (mipLevel > 0 && (currentMips.length == 1 || currentMips[1] == null)) {
|
||||
if (darkCutout) {
|
||||
fillEmptyAreasWithDarkColor(base);
|
||||
} else {
|
||||
solidify(base, width, base.length / width);
|
||||
}
|
||||
}
|
||||
|
||||
result[0] = base;
|
||||
if (mipLevel <= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final boolean autoResolved = strategy == null || strategy == MipmapStrategy.AUTO;
|
||||
final boolean strictCoverage = resolved == MipmapStrategy.STRICT_CUTOUT;
|
||||
final float alphaRef = CUTOUT_ALPHA_REF;
|
||||
float originalCoverage = 0.0f;
|
||||
if (isCutout) {
|
||||
originalCoverage = alphaTestCoverage(base, width, alphaRef, 1.0f, border);
|
||||
if (strictCoverage || (autoResolved && originalCoverage < STRICT_COVERAGE_TARGET)) {
|
||||
originalCoverage = Math.max(originalCoverage, STRICT_COVERAGE_TARGET);
|
||||
}
|
||||
}
|
||||
|
||||
for (int level = 1; level <= mipLevel; level++) {
|
||||
final int levelWidth = width >> level;
|
||||
final int[] provided = level < currentMips.length ? currentMips[level] : null;
|
||||
|
||||
if (provided != null) {
|
||||
result[level] = provided;
|
||||
} else {
|
||||
final int[] input = result[level - 1];
|
||||
final int prevWidth = width >> (level - 1);
|
||||
final int prevHeight = input.length / prevWidth;
|
||||
final int levelHeight = prevHeight >> 1;
|
||||
final int[] output = new int[levelWidth * levelHeight];
|
||||
|
||||
for (int y = 0; y < levelHeight; y++) {
|
||||
for (int x = 0; x < levelWidth; x++) {
|
||||
final int i = x * 2 + y * 2 * prevWidth;
|
||||
output[x + y * levelWidth] = MipmapHelper.weightedAverageColor(
|
||||
MipmapHelper.weightedAverageColor(input[i], input[i + 1]),
|
||||
MipmapHelper.weightedAverageColor(input[i + prevWidth], input[i + prevWidth + 1]));
|
||||
}
|
||||
}
|
||||
result[level] = output;
|
||||
}
|
||||
|
||||
if (isCutout) {
|
||||
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, alphaRef, border >> level);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.gtnewhorizons.angelica.utils;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockFlower;
|
||||
import net.minecraft.block.BlockGlass;
|
||||
import net.minecraft.block.BlockLeaves;
|
||||
import net.minecraft.block.BlockMushroom;
|
||||
import net.minecraft.block.BlockRedstoneWire;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Fallback {@link MipmapStrategy} assignments for sprites that carry no metadata.
|
||||
*/
|
||||
public final class MipmapStrategies {
|
||||
|
||||
private static final Map<Class<?>, MipmapStrategy> CLASS_RULES = new Object2ObjectOpenHashMap<>();
|
||||
private static final Map<String, MipmapStrategy> SPRITE_RULES = new Object2ObjectOpenHashMap<>();
|
||||
private static final LinkedList<MipmapStrategy> STRATEGY_STACK = new LinkedList<>();
|
||||
|
||||
static {
|
||||
CLASS_RULES.put(BlockLeaves.class, MipmapStrategy.DARK_CUTOUT);
|
||||
CLASS_RULES.put(BlockFlower.class, MipmapStrategy.STRICT_CUTOUT);
|
||||
CLASS_RULES.put(BlockMushroom.class, MipmapStrategy.STRICT_CUTOUT);
|
||||
CLASS_RULES.put(BlockGlass.class, MipmapStrategy.MEAN);
|
||||
CLASS_RULES.put(BlockRedstoneWire.class, MipmapStrategy.MEAN);
|
||||
}
|
||||
|
||||
private MipmapStrategies() {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
SPRITE_RULES.clear();
|
||||
STRATEGY_STACK.clear();
|
||||
}
|
||||
|
||||
public static void beginBlock(Block block) {
|
||||
STRATEGY_STACK.push(resolve(block.getClass()));
|
||||
}
|
||||
|
||||
public static void endBlock() {
|
||||
if (!STRATEGY_STACK.isEmpty()) {
|
||||
STRATEGY_STACK.pop();
|
||||
}
|
||||
}
|
||||
|
||||
public static void recordSprite(int textureType, String iconName) {
|
||||
if (iconName == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MipmapStrategy strategy = STRATEGY_STACK.peek();
|
||||
if (strategy == null && textureType == 0 && iconName.contains("leaves")) {
|
||||
strategy = MipmapStrategy.DARK_CUTOUT;
|
||||
}
|
||||
if (strategy == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MipmapStrategy existing = SPRITE_RULES.get(iconName);
|
||||
if (existing == null || strategy.conflictPrecedence() > existing.conflictPrecedence()) {
|
||||
SPRITE_RULES.put(iconName, strategy);
|
||||
}
|
||||
}
|
||||
|
||||
public static MipmapStrategy inheritedFor(int textureType, String iconName) {
|
||||
if (iconName == null || textureType != 0) {
|
||||
return null;
|
||||
}
|
||||
return SPRITE_RULES.get(iconName);
|
||||
}
|
||||
|
||||
private static MipmapStrategy resolve(Class<?> type) {
|
||||
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
|
||||
final MipmapStrategy rule = CLASS_RULES.get(c);
|
||||
if (rule != null) {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizons.angelica.utils;
|
||||
|
||||
public enum MipmapStrategy {
|
||||
AUTO,
|
||||
MEAN,
|
||||
CUTOUT,
|
||||
STRICT_CUTOUT,
|
||||
DARK_CUTOUT;
|
||||
|
||||
private static final MipmapStrategy[] VALUES = values();
|
||||
|
||||
public static MipmapStrategy byName(String name) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
for (MipmapStrategy strategy : VALUES) {
|
||||
if (strategy.name().equalsIgnoreCase(name)) {
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int conflictPrecedence() {
|
||||
return switch (this) {
|
||||
case MEAN -> 4;
|
||||
case DARK_CUTOUT -> 3;
|
||||
case STRICT_CUTOUT -> 2;
|
||||
case CUTOUT -> 1;
|
||||
case AUTO -> 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import jss.notfine.config.NotFineConfig;
|
||||
import jss.notfine.core.Settings;
|
||||
import jss.notfine.core.SettingsManager;
|
||||
import jss.notfine.gui.options.control.NotFineControlValueFormatter;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptionPages;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
|
||||
@@ -20,6 +21,7 @@ import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
|
||||
import net.coderbot.iris.Iris;
|
||||
@@ -55,7 +57,7 @@ public class NotFineGameOptionPages {
|
||||
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.renderDistance"))
|
||||
.setTooltip(I18n.format("sodium.options.view_distance.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 2, (int)GameSettings.Options.RENDER_DISTANCE.getValueMax(), 1, ControlValueFormatter.quantity("options.chunks")))
|
||||
.setControl(option -> new SliderControl(option, 2, (int) GameSettings.Options.RENDER_DISTANCE.getValueMax(), 1, ControlValueFormatter.quantity("options.chunks")))
|
||||
.setBinding((options, value) -> options.renderDistanceChunks = value, options -> options.renderDistanceChunks)
|
||||
.setImpact(OptionImpact.HIGH)
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
@@ -95,6 +97,29 @@ public class NotFineGameOptionPages {
|
||||
.build())
|
||||
.build());
|
||||
|
||||
final OptionImpl<GameSettings, Integer> mipmapLevels = OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.mipmapLevels"))
|
||||
.setTooltip(I18n.format("sodium.options.mipmap_levels.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier()))
|
||||
.setBinding((opts, value) -> opts.mipmapLevels = value, opts -> opts.mipmapLevels)
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build();
|
||||
|
||||
final OptionImpl<SodiumGameOptions, TextureFilterMode> textureFilterMode =
|
||||
OptionImpl.createBuilder(TextureFilterMode.class, sodiumOpts)
|
||||
.setName(I18n.format("sodium.options.texture_filtering.name"))
|
||||
.setTooltip(I18n.format("sodium.options.texture_filtering.tooltip"))
|
||||
.setControl(option -> new CyclingControl<>(option, TextureFilterMode.class,
|
||||
TextureFilterMode.selectableValues(SodiumGameOptions.anisotropySupported())))
|
||||
.setBinding((opts, value) -> opts.quality.textureFilterMode = value,
|
||||
opts -> SodiumGameOptions.resolveFilterMode(opts.quality.textureFilterMode))
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD, OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build();
|
||||
|
||||
textureFilterMode.iris$dynamicallyEnable(() -> mipmapLevels.getValue() > 0);
|
||||
|
||||
int maxGuiScale = Math.max(3, Math.min(Minecraft.getMinecraft().displayWidth / 320, Minecraft.getMinecraft().displayHeight / 240));
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
@@ -125,29 +150,16 @@ public class NotFineGameOptionPages {
|
||||
.build())
|
||||
.add(Settings.MODE_LIGHT_FLICKER.option)
|
||||
.add(Settings.DYNAMIC_FOV.option)
|
||||
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.mipmapLevels"))
|
||||
.setTooltip(I18n.format("sodium.options.mipmap_levels.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier()))
|
||||
//mc.getTextureMapBlocks().setMipmapLevels(this.mipmapLevels); ?
|
||||
.setBinding((opts, value) -> opts.mipmapLevels = value, opts -> opts.mipmapLevels)
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.anisotropicFiltering"))
|
||||
.setTooltip(I18n.format("sodium.options.anisotropic_filtering.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 4, 1, NotFineControlValueFormatter.powerOfTwo()))
|
||||
.setBinding(
|
||||
//mc.getTextureMapBlocks().setAnisotropicFiltering(this.anisotropicFiltering); ?
|
||||
(opts, value) -> opts.anisotropicFiltering = value == 0 ? 1 : (int)Math.pow(2, value),
|
||||
(opts) -> opts.anisotropicFiltering == 1 ? 0 : (int)(Math.log(opts.anisotropicFiltering) / Math.log(2)))
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build())
|
||||
.add(mipmapLevels)
|
||||
.add(textureFilterMode)
|
||||
.add(SodiumGameOptionPages.anisotropicFilteringSlider(
|
||||
vanillaOpts,
|
||||
textureFilterMode::getValue,
|
||||
mipmapLevels::getValue),
|
||||
SodiumGameOptions.anisotropySupported())
|
||||
.build());
|
||||
|
||||
if(Iris.enabled) {
|
||||
if (Iris.enabled) {
|
||||
final OptionImpl<GameSettings, Integer> maxShadowDistanceSlider = OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.iris.shadowDistance"))
|
||||
.setTooltip(I18n.format("options.iris.shadowDistance.sodium_tooltip"))
|
||||
@@ -200,7 +212,7 @@ public class NotFineGameOptionPages {
|
||||
.add(Settings.MODE_DROPPED_ITEMS.option)
|
||||
.add(Settings.MODE_GLINT_WORLD.option)
|
||||
.add(Settings.MODE_GLINT_INV.option)
|
||||
.build());
|
||||
.build());
|
||||
return new OptionPage(I18n.format("options.button.detail"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
@@ -219,7 +231,7 @@ public class NotFineGameOptionPages {
|
||||
.add(Settings.HORIZON.option)
|
||||
.add(Settings.TERRAIN_FOG.option)
|
||||
.add(Settings.FOG_NEAR_DISTANCE.option)
|
||||
.build());
|
||||
.build());
|
||||
return new OptionPage(I18n.format("options.button.sky"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
@@ -235,7 +247,7 @@ public class NotFineGameOptionPages {
|
||||
.build())
|
||||
.add(Settings.PARTICLES_VOID.option)
|
||||
.add(Settings.PARTICLES_ENC_TABLE.option)
|
||||
.build());
|
||||
.build());
|
||||
return new OptionPage(I18n.format("options.button.particle"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
@@ -274,11 +286,11 @@ public class NotFineGameOptionPages {
|
||||
.setBinding((opts, value) -> opts.showCape = value, opts -> opts.showCape)
|
||||
.build())
|
||||
.add(Settings.MODE_GUI_BACKGROUND.option)
|
||||
.build());
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.GUI_BACKGROUND.option)
|
||||
.build());
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
|
||||
|
||||
@@ -25,7 +25,7 @@ public class CyclingControlElement<T extends Enum<T>> extends NotFineControlElem
|
||||
@Override
|
||||
public String getLabel() {
|
||||
Enum<T> value = option.getValue();
|
||||
return super.getLabel() + names[value.ordinal()];
|
||||
return super.getLabel() + formatValue(names[value.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jss.notfine.gui.options.control.element;
|
||||
|
||||
import jss.notfine.gui.GuiCustomMenu;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
|
||||
@@ -22,6 +23,10 @@ public class NotFineControlElement<T> extends GuiButton implements ControlElemen
|
||||
enabled = option.isAvailable();
|
||||
}
|
||||
|
||||
private static String disabledStyle() {
|
||||
return String.valueOf(EnumChatFormatting.GRAY) + EnumChatFormatting.STRIKETHROUGH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawButton(Minecraft mc, int mouseX, int mouseY) {
|
||||
displayString = getLabel();
|
||||
@@ -34,7 +39,9 @@ public class NotFineControlElement<T> extends GuiButton implements ControlElemen
|
||||
String name = option.getName();
|
||||
String label;
|
||||
enabled = option.isAvailable();
|
||||
if(enabled && option.hasChanged()) {
|
||||
if (!enabled) {
|
||||
label = disabledStyle() + name;
|
||||
} else if (option.hasChanged()) {
|
||||
label = EnumChatFormatting.ITALIC + name;
|
||||
} else {
|
||||
label = name;
|
||||
@@ -43,17 +50,20 @@ public class NotFineControlElement<T> extends GuiButton implements ControlElemen
|
||||
return label;
|
||||
}
|
||||
|
||||
protected String formatValue(String value) {
|
||||
return option.isAvailable() ? value : disabledStyle() + value;
|
||||
}
|
||||
|
||||
protected void onOptionValueChanged() {
|
||||
option.applyChanges();
|
||||
|
||||
Collection<OptionFlag> flags = option.getFlags();
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if(flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
if(flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
|
||||
mc.getTextureMapBlocks().setMipmapLevels(mc.gameSettings.mipmapLevels);
|
||||
if (flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
|
||||
SodiumGameOptions.applyAtlasSettings();
|
||||
mc.refreshResources();
|
||||
} else if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
GuiCustomMenu.dirtyStorages.add(option.getStorage());
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class SliderControlElement extends NotFineControlElement<Integer> {
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return super.getLabel() + formatter.format(option.getValue());
|
||||
return super.getLabel() + formatValue(formatter.format(option.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,7 +12,7 @@ public class TickBoxControlElement extends NotFineControlElement<Boolean> {
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return super.getLabel() + I18n.format(option.getValue() ? "options.on" : "options.off");
|
||||
return super.getLabel() + formatValue(I18n.format(option.getValue() ? "options.on" : "options.off"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-5
@@ -10,6 +10,7 @@ import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.components.Sea
|
||||
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.tab.Tab;
|
||||
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.tab.TabFrame;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptionPages;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.Option;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
|
||||
@@ -239,13 +240,11 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
|
||||
}));
|
||||
|
||||
|
||||
if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
this.mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
|
||||
if (flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
|
||||
this.mc.getTextureMapBlocks().setMipmapLevels(this.mc.gameSettings.mipmapLevels);
|
||||
SodiumGameOptions.applyAtlasSettings();
|
||||
this.mc.refreshResources();
|
||||
} else if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
this.mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
|
||||
for (OptionStorage<?> storage : dirtyStorages) {
|
||||
|
||||
@@ -3,16 +3,18 @@ package me.jellysquid.mods.sodium.client.gui;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
||||
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
@@ -35,6 +37,7 @@ import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.AngelicaOptionsStorage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.CubicChunksOptionStorage;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
|
||||
@@ -61,11 +64,11 @@ public class SodiumGameOptionPages {
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build());
|
||||
|
||||
|
||||
|
||||
if (ModStatus.isCubicChunksLoaded) {
|
||||
firstGroupBuilder.add(getCCVerticalViewDistance());
|
||||
}
|
||||
|
||||
|
||||
if(Iris.enabled) {
|
||||
|
||||
final OptionImpl<GameSettings, Integer> maxShadowDistanceSlider = OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
@@ -169,6 +172,29 @@ public class SodiumGameOptionPages {
|
||||
public static OptionPage quality() {
|
||||
final List<OptionGroup> groups = new ArrayList<>();
|
||||
|
||||
final OptionImpl<GameSettings, Integer> mipmapLevels = OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.mipmapLevels"))
|
||||
.setTooltip(I18n.format("sodium.options.mipmap_levels.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier()))
|
||||
.setBinding((opts, value) -> opts.mipmapLevels = value, opts -> opts.mipmapLevels)
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build();
|
||||
|
||||
final OptionImpl<SodiumGameOptions, TextureFilterMode> textureFilterMode =
|
||||
OptionImpl.createBuilder(TextureFilterMode.class, sodiumOpts)
|
||||
.setName(I18n.format("sodium.options.texture_filtering.name"))
|
||||
.setTooltip(I18n.format("sodium.options.texture_filtering.tooltip"))
|
||||
.setControl(option -> new CyclingControl<>(option, TextureFilterMode.class,
|
||||
TextureFilterMode.selectableValues(SodiumGameOptions.anisotropySupported())))
|
||||
.setBinding((opts, value) -> opts.quality.textureFilterMode = value,
|
||||
opts -> SodiumGameOptions.resolveFilterMode(opts.quality.textureFilterMode))
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD, OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build();
|
||||
|
||||
textureFilterMode.iris$dynamicallyEnable(() -> mipmapLevels.getValue() > 0);
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(GraphicsMode.class, vanillaOpts)
|
||||
.setName(I18n.format("options.graphics"))
|
||||
@@ -222,6 +248,9 @@ public class SodiumGameOptionPages {
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.build())
|
||||
.add(textureFilterMode)
|
||||
.add(anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue, mipmapLevels::getValue),
|
||||
SodiumGameOptions.anisotropySupported())
|
||||
// TODO
|
||||
/*.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(new TranslatableText("options.biomeBlendRadius"))
|
||||
@@ -245,14 +274,7 @@ public class SodiumGameOptionPages {
|
||||
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
|
||||
.setName(I18n.format("options.mipmapLevels"))
|
||||
.setTooltip(I18n.format("sodium.options.mipmap_levels.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier()))
|
||||
.setBinding((opts, value) -> opts.mipmapLevels = value, opts -> opts.mipmapLevels)
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build())
|
||||
.add(mipmapLevels)
|
||||
.build());
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(Settings.MODE_GLINT_INV.option)
|
||||
@@ -512,6 +534,27 @@ public class SodiumGameOptionPages {
|
||||
return new OptionPage(I18n.format("sodium.options.pages.performance"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
public static OptionImpl<GameSettings, Integer> anisotropicFilteringSlider(MinecraftOptionsStorage storage,
|
||||
Supplier<TextureFilterMode> mode, IntSupplier mipmapLevels) {
|
||||
final int min = SodiumGameOptions.minAnisotropyLevel();
|
||||
final int max = Math.max(SodiumGameOptions.maxAnisotropyLevel(), min + 1);
|
||||
final OptionImpl<GameSettings, Integer> option = OptionImpl.createBuilder(int.class, storage)
|
||||
.setName(I18n.format("sodium.options.anisotropic_filtering.name"))
|
||||
.setTooltip(I18n.format("sodium.options.anisotropic_filtering.tooltip"))
|
||||
.setControl(opt -> new SliderControl(opt, min, max, 1, value -> (1 << value) + "x"))
|
||||
.setBinding(
|
||||
(opts, value) -> opts.anisotropicFiltering = 1 << MathHelper.clamp_int(value, min, max),
|
||||
opts -> MathHelper.clamp_int(Integer.numberOfTrailingZeros(Math.max(opts.anisotropicFiltering, 1)), min, max))
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
|
||||
.build();
|
||||
|
||||
option.iris$dynamicallyEnable(() -> mipmapLevels.getAsInt() > 0 && mode.get().usesAnisotropy()
|
||||
&& SodiumGameOptions.hasAnisotropyRange());
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
@Method(modid = "cubicchunks")
|
||||
private static OptionImpl<?, ?> getCCVerticalViewDistance() {
|
||||
CubicChunksOptionStorage storage = new CubicChunksOptionStorage();
|
||||
|
||||
@@ -5,27 +5,198 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class SodiumGameOptions {
|
||||
private static final int MIN_ANISOTROPY_FOR_PADDING = 2;
|
||||
private static final int MIN_ANISOTROPY_LEVEL = 1;
|
||||
private static final int MAX_ANISOTROPY_LEVEL = 4;
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||
.create();
|
||||
private static int resourcePackMipmapCeiling = Integer.MAX_VALUE;
|
||||
public final QualitySettings quality = new QualitySettings();
|
||||
public final AdvancedSettings advanced = new AdvancedSettings();
|
||||
public final PerformanceSettings performance = new PerformanceSettings();
|
||||
|
||||
private Path configPath;
|
||||
|
||||
public static void recordAtlasMipmapClamp(int requestedMipmapLevels, int actualMipmapLevels) {
|
||||
resourcePackMipmapCeiling = actualMipmapLevels < requestedMipmapLevels ? actualMipmapLevels : Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public static void applyAtlasSettings() {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final TextureMap atlas = mc.getTextureMapBlocks();
|
||||
|
||||
atlas.setMipmapLevels(mc.gameSettings.mipmapLevels);
|
||||
atlas.setAnisotropicFiltering(resolvedAnisotropicFiltering());
|
||||
}
|
||||
|
||||
public static int resolvedAnisotropicFiltering() {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final TextureFilterMode mode = filteringPossible()
|
||||
? resolveFilterMode(ClientProxy.options().quality.textureFilterMode)
|
||||
: TextureFilterMode.NONE;
|
||||
|
||||
return mode.usesAnisotropy()
|
||||
? MathHelper.clamp_int(mc.gameSettings.anisotropicFiltering, MIN_ANISOTROPY_FOR_PADDING, 1 << maxAnisotropyLevel())
|
||||
: 1;
|
||||
}
|
||||
|
||||
private static boolean filteringPossible() {
|
||||
return effectiveMipmapLevelsEstimate() > 0;
|
||||
}
|
||||
|
||||
private static int effectiveMipmapLevelsEstimate() {
|
||||
return Math.min(Minecraft.getMinecraft().gameSettings.mipmapLevels, resourcePackMipmapCeiling);
|
||||
}
|
||||
|
||||
public static boolean needsForcedSpritePadding() {
|
||||
if (!AngelicaConfig.enableCeleritas) {
|
||||
return false;
|
||||
}
|
||||
final TextureFilterMode mode = filteringPossible()
|
||||
? resolveFilterMode(ClientProxy.options().quality.textureFilterMode)
|
||||
: TextureFilterMode.NONE;
|
||||
return mode.needsSpritePadding() && !mode.usesAnisotropy();
|
||||
}
|
||||
|
||||
public static TextureFilterMode effectiveTextureFilterMode() {
|
||||
final TextureFilterMode mode = resolveFilterMode(ClientProxy.options().quality.textureFilterMode);
|
||||
if (mode == TextureFilterMode.NONE) {
|
||||
return mode;
|
||||
}
|
||||
return Minecraft.getMinecraft().getTextureMapBlocks().mipmapLevels > 0 ? mode : TextureFilterMode.NONE;
|
||||
}
|
||||
|
||||
public static TextureFilterMode resolveFilterMode(TextureFilterMode mode) {
|
||||
if (mode == null) {
|
||||
mode = TextureFilterMode.RGSS;
|
||||
}
|
||||
return mode.usesAnisotropy() && !anisotropySupported() ? TextureFilterMode.RGSS : mode;
|
||||
}
|
||||
|
||||
public static int maxAnisotropyLevel() {
|
||||
final int driverMax = OpenGlHelper.anisotropicFilteringMax;
|
||||
if (driverMax < 1 << MIN_ANISOTROPY_LEVEL) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(31 - Integer.numberOfLeadingZeros(driverMax), MAX_ANISOTROPY_LEVEL);
|
||||
}
|
||||
|
||||
public static boolean anisotropySupported() {
|
||||
return maxAnisotropyLevel() > 0;
|
||||
}
|
||||
|
||||
public static boolean hasAnisotropyRange() {
|
||||
return maxAnisotropyLevel() > MIN_ANISOTROPY_LEVEL;
|
||||
}
|
||||
|
||||
public static int minAnisotropyLevel() {
|
||||
return MIN_ANISOTROPY_LEVEL;
|
||||
}
|
||||
|
||||
public static SodiumGameOptions load(Path path) {
|
||||
SodiumGameOptions config;
|
||||
boolean resaveConfig = true;
|
||||
|
||||
if (Files.exists(path)) {
|
||||
try (FileReader reader = new FileReader(path.toFile())) {
|
||||
config = GSON.fromJson(reader, SodiumGameOptions.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not parse config", e);
|
||||
} catch (JsonSyntaxException e) {
|
||||
AngelicaMod.LOGGER.error("Could not parse config, will fallback to default settings", e);
|
||||
config = new SodiumGameOptions();
|
||||
resaveConfig = false;
|
||||
}
|
||||
} else {
|
||||
config = new SodiumGameOptions();
|
||||
// Lwjgl2 doesn't expose multidraw and actually does individual, so try indirect if available
|
||||
if (isLwjgl2() && !isMacOS()) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.INDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
config.configPath = path;
|
||||
|
||||
if (config.advanced.streamingUploadStrategy == null) {
|
||||
config.advanced.streamingUploadStrategy = StreamingUploader.UploadStrategy.MAP_BUFFER_RANGE;
|
||||
}
|
||||
|
||||
if (config.quality.textureFilterMode == null) {
|
||||
config.quality.textureFilterMode = TextureFilterMode.RGSS;
|
||||
}
|
||||
|
||||
// Clamp render-ahead to 0 if GL 3.2 fences aren't available
|
||||
if (GLStateManager.capabilities == null || !GLStateManager.capabilities.OpenGL32) {
|
||||
config.performance.cpuRenderAheadLimit = 0;
|
||||
}
|
||||
|
||||
// Downgrade INDIRECT if hardware doesn't support it
|
||||
if (config.advanced.multiDrawMode == MultiDrawMode.INDIRECT && (GLStateManager.capabilities == null || (!GLStateManager.capabilities.OpenGL43 && !GLStateManager.capabilities.GL_ARB_multi_draw_indirect))) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.DIRECT;
|
||||
}
|
||||
|
||||
try {
|
||||
if (resaveConfig)
|
||||
config.writeChanges();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't update config file", e);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static boolean isLwjgl2() {
|
||||
return SodiumGameOptions.class.getClassLoader().getResource("org/lwjgl/Sys.class") != null;
|
||||
}
|
||||
|
||||
private static boolean isMacOS() {
|
||||
return System.getProperty("os.name", "").toLowerCase().contains("mac");
|
||||
}
|
||||
|
||||
public void writeChanges() throws IOException {
|
||||
final Path dir = this.configPath.getParent();
|
||||
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
} else if (!Files.isDirectory(dir)) {
|
||||
throw new IOException("Not a directory: " + dir);
|
||||
}
|
||||
|
||||
Files.writeString(this.configPath, GSON.toJson(this));
|
||||
if (Iris.enabled) {
|
||||
try {
|
||||
if (Iris.getIrisConfig() != null) {
|
||||
Iris.getIrisConfig().save();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedSettings {
|
||||
public boolean useVertexArrayObjects = true;
|
||||
public boolean useChunkMultidraw = true;
|
||||
@@ -61,95 +232,15 @@ public class SodiumGameOptions {
|
||||
public static double getRenderDistanceMult() {
|
||||
return entityRenderDistanceMultiplier;
|
||||
}
|
||||
|
||||
public static void setRenderDistanceMult(double value) {
|
||||
entityRenderDistanceMultiplier = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class QualitySettings {
|
||||
public GraphicsQuality grassQuality = GraphicsQuality.DEFAULT;
|
||||
public boolean useCeleritasSmoothLighting = true;
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.setPrettyPrinting()
|
||||
.excludeFieldsWithModifiers(Modifier.PRIVATE)
|
||||
.create();
|
||||
|
||||
public static SodiumGameOptions load(Path path) {
|
||||
SodiumGameOptions config;
|
||||
boolean resaveConfig = true;
|
||||
|
||||
if (Files.exists(path)) {
|
||||
try (FileReader reader = new FileReader(path.toFile())) {
|
||||
config = GSON.fromJson(reader, SodiumGameOptions.class);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not parse config", e);
|
||||
} catch (JsonSyntaxException e) {
|
||||
AngelicaMod.LOGGER.error("Could not parse config, will fallback to default settings", e);
|
||||
config = new SodiumGameOptions();
|
||||
resaveConfig = false;
|
||||
}
|
||||
} else {
|
||||
config = new SodiumGameOptions();
|
||||
// Lwjgl2 doesn't expose multidraw and actually does individual, so try indirect if available
|
||||
if (isLwjgl2() && !isMacOS()) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.INDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
config.configPath = path;
|
||||
|
||||
if (config.advanced.streamingUploadStrategy == null) {
|
||||
config.advanced.streamingUploadStrategy = StreamingUploader.UploadStrategy.MAP_BUFFER_RANGE;
|
||||
}
|
||||
|
||||
// Clamp render-ahead to 0 if GL 3.2 fences aren't available
|
||||
if (GLStateManager.capabilities == null || !GLStateManager.capabilities.OpenGL32) {
|
||||
config.performance.cpuRenderAheadLimit = 0;
|
||||
}
|
||||
|
||||
// Downgrade INDIRECT if hardware doesn't support it
|
||||
if (config.advanced.multiDrawMode == MultiDrawMode.INDIRECT && (GLStateManager.capabilities == null || (!GLStateManager.capabilities.OpenGL43 && !GLStateManager.capabilities.GL_ARB_multi_draw_indirect))) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.DIRECT;
|
||||
}
|
||||
|
||||
try {
|
||||
if(resaveConfig)
|
||||
config.writeChanges();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't update config file", e);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private static boolean isLwjgl2() {
|
||||
return SodiumGameOptions.class.getClassLoader().getResource("org/lwjgl/Sys.class") != null;
|
||||
}
|
||||
|
||||
private static boolean isMacOS() {
|
||||
return System.getProperty("os.name", "").toLowerCase().contains("mac");
|
||||
}
|
||||
|
||||
public void writeChanges() throws IOException {
|
||||
final Path dir = this.configPath.getParent();
|
||||
|
||||
if (!Files.exists(dir)) {
|
||||
Files.createDirectories(dir);
|
||||
} else if (!Files.isDirectory(dir)) {
|
||||
throw new IOException("Not a directory: " + dir);
|
||||
}
|
||||
|
||||
Files.write(this.configPath, GSON.toJson(this).getBytes(StandardCharsets.UTF_8));
|
||||
if(Iris.enabled) {
|
||||
try {
|
||||
if (Iris.getIrisConfig() != null) {
|
||||
Iris.getIrisConfig().save();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
public TextureFilterMode textureFilterMode = TextureFilterMode.RGSS;
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
}
|
||||
|
||||
// Just use the first page for now
|
||||
this.currentPage = this.pages.get(0);
|
||||
this.currentPage = this.pages.getFirst();
|
||||
}
|
||||
|
||||
this.rebuildGUIPages();
|
||||
@@ -272,13 +272,11 @@ public class SodiumOptionsGUI extends ScrollableGuiScreen {
|
||||
dirtyStorages.add(option.getStorage());
|
||||
}));
|
||||
|
||||
if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
this.mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
|
||||
if (flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
|
||||
this.mc.getTextureMapBlocks().setMipmapLevels(this.mc.gameSettings.mipmapLevels);
|
||||
SodiumGameOptions.applyAtlasSettings();
|
||||
this.mc.refreshResources();
|
||||
} else if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
|
||||
this.mc.renderGlobal.loadRenderers();
|
||||
}
|
||||
|
||||
for (OptionStorage<?> storage : dirtyStorages) {
|
||||
|
||||
+5
@@ -16,6 +16,11 @@ public class SodiumControlElement<T> extends AbstractWidget implements ControlEl
|
||||
this.dim = dim;
|
||||
}
|
||||
|
||||
protected String formatValue(String value) {
|
||||
return this.option.isAvailable() ? value
|
||||
: String.valueOf(EnumChatFormatting.GRAY) + EnumChatFormatting.STRIKETHROUGH + value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(int mouseX, int mouseY, float delta) {
|
||||
String name = this.option.getName();
|
||||
|
||||
+4
-4
@@ -49,7 +49,7 @@ public class SodiumControlElementFactory implements ControlElementFactory {
|
||||
super.render(mouseX, mouseY, delta);
|
||||
|
||||
Enum<T> value = this.option.getValue();
|
||||
String name = this.names[value.ordinal()];
|
||||
String name = this.formatValue(this.names[value.ordinal()]);
|
||||
|
||||
int strWidth = this.getTextWidth(name);
|
||||
this.drawString(name, this.dim.getLimitX() - strWidth - 6, this.dim.getCenterY() - 4, 0xFFFFFFFF);
|
||||
@@ -120,7 +120,7 @@ public class SodiumControlElementFactory implements ControlElementFactory {
|
||||
int sliderWidth = this.sliderBounds.getWidth();
|
||||
int sliderHeight = this.sliderBounds.getHeight();
|
||||
|
||||
String label = this.formatter.format(this.option.getValue());
|
||||
String label = this.formatValue(this.formatter.format(this.option.getValue()));
|
||||
int labelWidth = this.font.getStringWidth(label);
|
||||
|
||||
this.drawString(label, sliderX + sliderWidth - labelWidth, sliderY + (sliderHeight / 2) - 4, 0xFFFFFFFF);
|
||||
@@ -137,12 +137,12 @@ public class SodiumControlElementFactory implements ControlElementFactory {
|
||||
double thumbOffset = MathHelper.clamp_double((double) (this.getIntValue() - this.min) / this.range * sliderWidth, 0, sliderWidth);
|
||||
|
||||
double thumbX = sliderX + thumbOffset - THUMB_WIDTH;
|
||||
double trackY = sliderY + (sliderHeight / 2) - ((double) TRACK_HEIGHT / 2);
|
||||
double trackY = sliderY + ((double) sliderHeight / 2) - ((double) TRACK_HEIGHT / 2);
|
||||
|
||||
this.drawRect(thumbX, sliderY, thumbX + (THUMB_WIDTH * 2), sliderY + sliderHeight, 0xFFFFFFFF);
|
||||
this.drawRect(sliderX, trackY, sliderX + sliderWidth, trackY + TRACK_HEIGHT, 0xFFFFFFFF);
|
||||
|
||||
String label = String.valueOf(this.getIntValue());
|
||||
String label = this.formatter.format(this.getIntValue());
|
||||
|
||||
int labelWidth = this.font.getStringWidth(label);
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package me.jellysquid.mods.sodium.client.gui.options.named;
|
||||
|
||||
/**
|
||||
* Selects how the terrain fragment shader samples the block atlas.
|
||||
*/
|
||||
public enum TextureFilterMode implements NamedState {
|
||||
RGSS("sodium.options.texture_filtering.rgss"),
|
||||
ANISOTROPIC("sodium.options.texture_filtering.anisotropic"),
|
||||
NONE("options.off");
|
||||
|
||||
private static final TextureFilterMode[] WITHOUT_ANISOTROPY = {RGSS, NONE};
|
||||
private final String name;
|
||||
|
||||
TextureFilterMode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anisotropic filtering rides on an extension that isn't guaranteed in GL 3.3, so it is only offered when the
|
||||
* driver actually exposes it.
|
||||
*/
|
||||
public static TextureFilterMode[] selectableValues(boolean anisotropySupported) {
|
||||
return anisotropySupported ? values() : WITHOUT_ANISOTROPY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean usesAnisotropy() {
|
||||
return this == ANISOTROPIC;
|
||||
}
|
||||
|
||||
public boolean needsSpritePadding() {
|
||||
return this == RGSS;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.compat.mojang.NativeImage;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ISpriteExt;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.texture.format.TextureFormat;
|
||||
import net.coderbot.iris.texture.format.TextureFormatLoader;
|
||||
@@ -219,7 +220,9 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
protected PBRTextureAtlasSprite(TextureAtlasSpriteInfo info, AnimationMetadataSection animationMetaDataSection, int atlasWidth, int atlasHeight, int x, int y, NativeImage nativeImage, TextureMap texMap, int miplevel) {
|
||||
super(info.name().toString());
|
||||
super.initSprite(atlasWidth, atlasHeight, x, y, false);
|
||||
super.loadSprite(getMipmapGenerator(info, atlasWidth, atlasHeight).generateMipLevels(nativeImage, miplevel), animationMetaDataSection, texMap.anisotropicFiltering > 1);
|
||||
final boolean useAnisotropicFiltering = texMap.anisotropicFiltering > 1
|
||||
|| SodiumGameOptions.needsForcedSpritePadding();
|
||||
super.loadSprite(getMipmapGenerator(info, atlasWidth, atlasHeight).generateMipLevels(nativeImage, miplevel), animationMetaDataSection, useAnisotropicFiltering);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -38,6 +38,12 @@ sodium.options.grass_quality.name=Grass Quality
|
||||
sodium.options.grass_quality.tooltip=Controls the quality of grass block sides.
|
||||
sodium.options.celeritas_smooth_lighting.name=Celeritas Smooth Lighting
|
||||
sodium.options.celeritas_smooth_lighting.tooltip=If enabled, Celeritas's lighting pipeline will be used for smooth lighting. This only has an effect if smooth lighting is enabled.
|
||||
sodium.options.texture_filtering.name=Texture Filtering
|
||||
sodium.options.texture_filtering.tooltip=Controls how block textures are sampled at a distance.
|
||||
sodium.options.texture_filtering.rgss=RGSS
|
||||
sodium.options.texture_filtering.anisotropic=Anisotropic
|
||||
sodium.options.anisotropic_filtering.name=Anisotropic Filtering
|
||||
sodium.options.anisotropic_filtering.tooltip=Sharpens textures viewed at steep angles.
|
||||
sodium.options.particle_quality.name=Particle Quality
|
||||
sodium.options.particle_quality.tooltip=Controls the maximum number of particles which can be present on screen at any one time.
|
||||
sodium.options.smooth_lighting.tooltip=Controls whether blocks will be smoothly lit and shaded. This slightly increases the amount of time needed to re-build a chunk, but doesn't affect frame rates.
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#version 330 core
|
||||
|
||||
#import <sodium:include/fog.glsl>
|
||||
|
||||
in vec4 v_Color;
|
||||
in vec2 v_TexCoord;
|
||||
|
||||
in float v_ChunkAgeMs;
|
||||
|
||||
in float v_MaterialMipBias;
|
||||
#ifdef USE_FRAGMENT_DISCARD
|
||||
in float v_MaterialAlphaCutoff;
|
||||
#endif
|
||||
|
||||
#if defined(USE_FOG_POSTMODERN)
|
||||
in float v_SphericalFragDistance;
|
||||
in float v_CylindricalFragDistance;
|
||||
#elif defined(USE_FOG)
|
||||
in float v_FragDistance;
|
||||
#endif
|
||||
|
||||
uniform sampler2D u_BlockTex;
|
||||
|
||||
uniform vec4 u_FogColor;
|
||||
|
||||
#ifdef USE_FOG_SMOOTH
|
||||
uniform float u_FogStart;
|
||||
uniform float u_FogEnd;
|
||||
#endif
|
||||
|
||||
#ifdef USE_FOG_POSTMODERN
|
||||
uniform float u_RenderDistFogStart;
|
||||
uniform float u_RenderDistFogEnd;
|
||||
uniform float u_EnvFogStart;
|
||||
uniform float u_EnvFogEnd;
|
||||
#endif
|
||||
|
||||
#ifdef USE_FOG_EXP2
|
||||
uniform float u_FogDensity;
|
||||
#endif
|
||||
|
||||
#ifndef LEGACY
|
||||
out vec4 fragColor;
|
||||
#else
|
||||
#define fragColor gl_FragColor
|
||||
#endif
|
||||
|
||||
#ifdef USE_RGSS
|
||||
// Standard rotated-grid supersampling offsets, in texels.
|
||||
const vec2 RGSS_OFFSETS[4] = vec2[4](
|
||||
vec2( 0.125, 0.375),
|
||||
vec2(-0.125, -0.375),
|
||||
vec2( 0.375, -0.125),
|
||||
vec2(-0.375, 0.125)
|
||||
);
|
||||
|
||||
// Sprites are padded so RGSS taps don't cross into a neighbouring atlas sprite
|
||||
const float MAX_TAP_SCALE = 8.0;
|
||||
|
||||
vec4 sampleRGSS(sampler2D tex, vec2 uv, vec2 texSize, vec2 pixelSize) {
|
||||
vec2 du = dFdx(uv);
|
||||
vec2 dv = dFdy(uv);
|
||||
|
||||
vec2 duTexels = du * texSize;
|
||||
vec2 dvTexels = dv * texSize;
|
||||
|
||||
vec2 du2 = duTexels * duTexels;
|
||||
vec2 dv2 = dvTexels * dvTexels;
|
||||
|
||||
vec2 texelScreenSize = sqrt(du2 + dv2);
|
||||
float blendFactor = smoothstep(1.0, 2.0, max(texelScreenSize.x, texelScreenSize.y));
|
||||
float lod = max(0.0, 0.25 * log2((du2.x + du2.y) * (dv2.x + dv2.y)) + v_MaterialMipBias);
|
||||
vec4 rgss = vec4(0.0);
|
||||
|
||||
if (blendFactor > 0.0) {
|
||||
vec2 tapSize = pixelSize * min(exp2(lod), MAX_TAP_SCALE);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rgss += textureLod(tex, uv + RGSS_OFFSETS[i] * tapSize, lod);
|
||||
}
|
||||
rgss *= 0.25;
|
||||
|
||||
if (blendFactor >= 1.0) {
|
||||
return rgss;
|
||||
}
|
||||
}
|
||||
|
||||
vec2 t = uv * texSize;
|
||||
vec2 c = round(t) - 0.5;
|
||||
vec2 o = t - c;
|
||||
o = clamp((o - 0.5) / max(texelScreenSize, vec2(1e-8)) + 0.5, 0.0, 1.0);
|
||||
vec4 nearest = textureLod(tex, (c + o) * pixelSize, lod);
|
||||
|
||||
return mix(nearest, rgss, blendFactor);
|
||||
}
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
#ifdef USE_RGSS
|
||||
vec2 texSize = vec2(textureSize(u_BlockTex, 0));
|
||||
vec4 diffuseColor = sampleRGSS(u_BlockTex, v_TexCoord, texSize, 1.0 / texSize);
|
||||
#else
|
||||
vec4 diffuseColor = texture(u_BlockTex, v_TexCoord, v_MaterialMipBias);
|
||||
#endif
|
||||
|
||||
#ifdef USE_FRAGMENT_DISCARD
|
||||
if (diffuseColor.a < v_MaterialAlphaCutoff) {
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 m_color = v_Color;
|
||||
|
||||
#ifdef USE_VANILLA_COLOR_FORMAT
|
||||
diffuseColor *= m_color;
|
||||
#else
|
||||
diffuseColor.rgb *= m_color.rgb;
|
||||
diffuseColor.rgb *= m_color.a;
|
||||
#endif
|
||||
|
||||
#ifdef USE_FOG
|
||||
#if defined(CHUNK_FADE_IN_DURATION_MS) && CHUNK_FADE_IN_DURATION_MS > 0
|
||||
diffuseColor = vec4(mix(u_FogColor.rgb, diffuseColor.rgb, (clamp(v_ChunkAgeMs, 0, CHUNK_FADE_IN_DURATION_MS) / CHUNK_FADE_IN_DURATION_MS)), diffuseColor.a);
|
||||
#endif
|
||||
|
||||
#ifdef USE_FOG_POSTMODERN
|
||||
float fogValue = max(_linearFogValue(v_CylindricalFragDistance, u_RenderDistFogStart, u_RenderDistFogEnd),
|
||||
_linearFogValue(v_SphericalFragDistance, u_EnvFogStart, u_EnvFogEnd));
|
||||
|
||||
fragColor = vec4(mix(diffuseColor.rgb, u_FogColor.rgb, fogValue * u_FogColor.a), diffuseColor.a);
|
||||
#elif defined(USE_FOG_EXP2)
|
||||
fragColor = _exp2Fog(diffuseColor, v_FragDistance, u_FogColor, u_FogDensity);
|
||||
#elif defined(USE_FOG_SMOOTH)
|
||||
fragColor = _linearFog(diffuseColor, v_FragDistance, u_FogColor, u_FogStart, u_FogEnd);
|
||||
#endif
|
||||
#else
|
||||
fragColor = diffuseColor;
|
||||
#endif
|
||||
}
|
||||
+117
-65
@@ -2,108 +2,160 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.features.mipmaps;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapGenerator;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategies;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.embeddedt.embeddium.api.util.ColorABGR;
|
||||
import org.embeddedt.embeddium.api.util.ColorARGB;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
|
||||
import org.embeddedt.embeddium.impl.util.color.ColorSRGB;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Calculates sprite transparency level during mipmap generation. */
|
||||
/**
|
||||
* Calculates sprite transparency and routes mipmap generation through MipmapGenerator.
|
||||
*/
|
||||
@Mixin(TextureAtlasSprite.class)
|
||||
public abstract class MixinTextureAtlasSprite implements SpriteExtension {
|
||||
|
||||
@Shadow protected List<int[][]> framesTextureData;
|
||||
@Shadow @Final private String iconName;
|
||||
|
||||
@Unique private SpriteTransparencyLevel celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSLUCENT;
|
||||
|
||||
@Inject(method = "generateMipmaps", at = @At("HEAD"))
|
||||
private void celeritas$processSprite(int level, CallbackInfo ci) {
|
||||
if (this.framesTextureData.isEmpty() || this.framesTextureData.get(0) == null) {
|
||||
return;
|
||||
}
|
||||
if (celeritas$isAlwaysTranslucent(this.iconName)) {
|
||||
this.celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSLUCENT;
|
||||
return;
|
||||
}
|
||||
celeritas$processTransparentImages(this.framesTextureData.get(0)[0], level > 0 && !iconName.contains("leaves"));
|
||||
}
|
||||
@Unique
|
||||
private static final int SEEN_TRANSPARENT = 1;
|
||||
@Unique
|
||||
private static final int SEEN_TRANSLUCENT = 2;
|
||||
@Unique
|
||||
private static final int SEEN_ALL = SEEN_TRANSPARENT | SEEN_TRANSLUCENT;
|
||||
@Unique
|
||||
private static final int SCAN_BLOCK = 256;
|
||||
@Shadow
|
||||
public boolean useAnisotropicFiltering;
|
||||
@Shadow
|
||||
public List<int[][]> framesTextureData;
|
||||
@Shadow
|
||||
@Final
|
||||
private String iconName;
|
||||
@Unique
|
||||
private SpriteTransparencyLevel celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSLUCENT;
|
||||
@Unique
|
||||
private MipmapStrategy celeritas$mipmapStrategy;
|
||||
@Unique
|
||||
private boolean celeritas$mipmapStrategyExplicit;
|
||||
@Unique
|
||||
private int celeritas$textureType;
|
||||
@Unique
|
||||
private boolean celeritas$hasTransparentTexels;
|
||||
@Unique
|
||||
private boolean celeritas$alwaysTranslucent;
|
||||
|
||||
@Unique
|
||||
private static boolean celeritas$isAlwaysTranslucent(String name) {
|
||||
final String[] list = AngelicaConfig.alwaysTranslucentSprites;
|
||||
if (list == null || list.length == 0) return false;
|
||||
if (list == null) return false;
|
||||
for (String s : list) {
|
||||
if (name.equals(s)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ordinals: OPAQUE=0, TRANSPARENT=1, TRANSLUCENT=2
|
||||
@Unique private static final int ORDINAL_OPAQUE = SpriteTransparencyLevel.OPAQUE.ordinal();
|
||||
@Unique private static final int ORDINAL_TRANSPARENT = SpriteTransparencyLevel.TRANSPARENT.ordinal();
|
||||
@Unique private static final int ORDINAL_TRANSLUCENT = SpriteTransparencyLevel.TRANSLUCENT.ordinal();
|
||||
@Unique private static final SpriteTransparencyLevel[] LEVELS_BY_ORDINAL = SpriteTransparencyLevel.values();
|
||||
@Inject(method = "generateMipmaps", at = @At("HEAD"))
|
||||
private void celeritas$processSprite(int level, CallbackInfo ci) {
|
||||
this.celeritas$hasTransparentTexels = false;
|
||||
this.celeritas$alwaysTranslucent = celeritas$isAlwaysTranslucent(this.iconName);
|
||||
|
||||
@Unique
|
||||
private void celeritas$processTransparentImages(int[] nativeImage, boolean shouldRewriteColors) {
|
||||
float r = 0.0f, g = 0.0f, b = 0.0f;
|
||||
float totalWeight = 0.0f;
|
||||
// Use primitive int instead of loop-carried enum reference to avoid JDK 25 C2 SuperWord non-convergence bug
|
||||
int maxLevel = ORDINAL_OPAQUE;
|
||||
|
||||
for (int y = 0; y < nativeImage.length; y++) {
|
||||
final int color = nativeImage[y];
|
||||
final int alpha = ColorABGR.unpackAlpha(color);
|
||||
|
||||
if (alpha > 0) {
|
||||
if (alpha < 255) {
|
||||
maxLevel = Math.max(maxLevel, ORDINAL_TRANSLUCENT);
|
||||
}
|
||||
|
||||
if (shouldRewriteColors) {
|
||||
final float weight = (float) alpha;
|
||||
r += ColorSRGB.srgbToLinear(ColorABGR.unpackRed(color)) * weight;
|
||||
g += ColorSRGB.srgbToLinear(ColorABGR.unpackGreen(color)) * weight;
|
||||
b += ColorSRGB.srgbToLinear(ColorABGR.unpackBlue(color)) * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
} else {
|
||||
maxLevel = Math.max(maxLevel, ORDINAL_TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
this.celeritas$transparencyLevel = LEVELS_BY_ORDINAL[maxLevel];
|
||||
|
||||
if (!shouldRewriteColors || totalWeight == 0.0f) {
|
||||
if (this.framesTextureData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
celeritas$classifyFrames();
|
||||
if (this.celeritas$alwaysTranslucent) {
|
||||
this.celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
|
||||
r /= totalWeight;
|
||||
g /= totalWeight;
|
||||
b /= totalWeight;
|
||||
@Unique
|
||||
private MipmapStrategy celeritas$resolvedStrategy() {
|
||||
if (this.celeritas$alwaysTranslucent) {
|
||||
return MipmapStrategy.MEAN;
|
||||
}
|
||||
|
||||
final int averageColor = ColorSRGB.linearToSrgb(r, g, b, 0);
|
||||
MipmapStrategy strategy = this.celeritas$mipmapStrategy;
|
||||
boolean explicit = this.celeritas$mipmapStrategyExplicit;
|
||||
|
||||
for (int y = 0; y < nativeImage.length; y++) {
|
||||
final int color = nativeImage[y];
|
||||
final int alpha = ColorABGR.unpackAlpha(color);
|
||||
if (alpha == 0) {
|
||||
nativeImage[y] = averageColor;
|
||||
if (strategy == null) {
|
||||
strategy = MipmapStrategies.inheritedFor(this.celeritas$textureType, this.iconName);
|
||||
explicit = false;
|
||||
}
|
||||
|
||||
if (strategy != null && !explicit && !this.celeritas$hasTransparentTexels) {
|
||||
strategy = null;
|
||||
}
|
||||
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void celeritas$classifyFrames() {
|
||||
int seen = 0;
|
||||
|
||||
outer:
|
||||
for (final int[][] frame : this.framesTextureData) {
|
||||
if (frame == null || frame.length == 0 || frame[0] == null) {
|
||||
continue;
|
||||
}
|
||||
final int[] pixels = frame[0];
|
||||
for (int base = 0; base < pixels.length; base += SCAN_BLOCK) {
|
||||
final int end = Math.min(base + SCAN_BLOCK, pixels.length);
|
||||
for (int i = base; i < end; i++) {
|
||||
final int alpha = ColorARGB.unpackAlpha(pixels[i]);
|
||||
if (alpha == 0) {
|
||||
seen |= SEEN_TRANSPARENT;
|
||||
} else if (alpha < 255) {
|
||||
seen |= SEEN_TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
if (seen == SEEN_ALL) {
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.celeritas$hasTransparentTexels = (seen & SEEN_TRANSPARENT) != 0;
|
||||
|
||||
if ((seen & SEEN_TRANSLUCENT) != 0) {
|
||||
this.celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSLUCENT;
|
||||
} else if ((seen & SEEN_TRANSPARENT) != 0) {
|
||||
this.celeritas$transparencyLevel = SpriteTransparencyLevel.TRANSPARENT;
|
||||
} else {
|
||||
this.celeritas$transparencyLevel = SpriteTransparencyLevel.OPAQUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "generateMipmaps",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/texture/TextureUtil;generateMipmapData(II[[I)[[I"))
|
||||
private int[][] celeritas$generateMipmapData(int p_147949_0_, int p_147949_1_, int[][] p_147949_2_) {
|
||||
final MipmapStrategy strategy = celeritas$resolvedStrategy();
|
||||
final int border = this.useAnisotropicFiltering ? 8 : 0;
|
||||
|
||||
return MipmapGenerator.generateMipLevels(p_147949_0_, p_147949_1_, p_147949_2_, strategy, this.celeritas$hasTransparentTexels, border);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpriteTransparencyLevel celeritas$getTransparencyLevel() {
|
||||
return this.celeritas$transparencyLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void celeritas$setMipmapStrategy(MipmapStrategy strategy, boolean explicit, int textureType) {
|
||||
this.celeritas$mipmapStrategy = strategy;
|
||||
this.celeritas$mipmapStrategyExplicit = explicit;
|
||||
this.celeritas$textureType = textureType;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.mipmaps;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.TextureMetadataExtension;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSection;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
||||
@Mixin(TextureMetadataSection.class)
|
||||
public class MixinTextureMetadataSection implements TextureMetadataExtension {
|
||||
|
||||
@Unique
|
||||
private MipmapStrategy angelica$mipmapStrategy;
|
||||
|
||||
@Override
|
||||
public void angelica$setMipmapStrategy(MipmapStrategy strategy) {
|
||||
this.angelica$mipmapStrategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MipmapStrategy angelica$getMipmapStrategy() {
|
||||
return this.angelica$mipmapStrategy;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.mipmaps;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.TextureMetadataExtension;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSection;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSectionSerializer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Reads the modern {@code "mipmap_strategy"} key out of a sprite's {@code texture} mcmeta section.
|
||||
*/
|
||||
@Mixin(TextureMetadataSectionSerializer.class)
|
||||
public class MixinTextureMetadataSectionSerializer {
|
||||
|
||||
@Inject(
|
||||
method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/resources/data/TextureMetadataSection;",
|
||||
at = @At("RETURN"))
|
||||
private void angelica$readMipmapStrategy(JsonElement json, Type type, JsonDeserializationContext context,
|
||||
CallbackInfoReturnable<TextureMetadataSection> cir) {
|
||||
final TextureMetadataSection section = cir.getReturnValue();
|
||||
if (section == null || !json.isJsonObject()) {
|
||||
return;
|
||||
}
|
||||
final JsonObject object = json.getAsJsonObject();
|
||||
if (!object.has("mipmap_strategy")) {
|
||||
return;
|
||||
}
|
||||
final JsonElement value = object.get("mipmap_strategy");
|
||||
if (!value.isJsonPrimitive()) {
|
||||
return;
|
||||
}
|
||||
((TextureMetadataExtension) section).angelica$setMipmapStrategy(MipmapStrategy.byName(value.getAsString()));
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.mipmaps;
|
||||
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
import static org.embeddedt.embeddium.impl.texture.MipmapHelper.weightedAverageColor;
|
||||
|
||||
@Mixin(TextureUtil.class)
|
||||
public class MixinTextureUtil {
|
||||
/**
|
||||
* @author coderbot
|
||||
* @reason replace the vanilla blending function with our improved function
|
||||
*/
|
||||
@Overwrite
|
||||
private static int func_147943_a(int one, int two, int three, int four, boolean checkAlpha) {
|
||||
// First blend horizontally, then blend vertically.
|
||||
// This works well for the case where our change is the most impactful (grass side overlays)
|
||||
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
|
||||
}
|
||||
}
|
||||
+136
-5
@@ -1,34 +1,165 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.textures;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.TextureMetadataExtension;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.TextureMapExtension;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategies;
|
||||
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
import net.minecraft.client.renderer.texture.Stitcher;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.resources.data.TextureMetadataSection;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.util.collections.quadtree.QuadTree;
|
||||
import org.embeddedt.embeddium.impl.util.collections.quadtree.Rect2i;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** QuadTree for sprite UV lookup. */
|
||||
/**
|
||||
* QuadTree for sprite UV lookup, plus per-sprite mipmap strategy assignment.
|
||||
*/
|
||||
@Mixin(TextureMap.class)
|
||||
public class MixinTextureMap implements TextureMapExtension {
|
||||
|
||||
@Shadow @Final private Map<String, TextureAtlasSprite> mapUploadedSprites;
|
||||
@Shadow
|
||||
@Final
|
||||
public Map<String, TextureAtlasSprite> mapUploadedSprites;
|
||||
@Shadow
|
||||
public int mipmapLevels;
|
||||
@Shadow
|
||||
@Final
|
||||
private int textureType;
|
||||
@Unique
|
||||
private QuadTree<TextureAtlasSprite> celeritas$quadTree;
|
||||
@Unique
|
||||
private int celeritas$width;
|
||||
@Unique
|
||||
private int celeritas$height;
|
||||
|
||||
@Unique private QuadTree<TextureAtlasSprite> celeritas$quadTree;
|
||||
@Unique private int celeritas$width;
|
||||
@Unique private int celeritas$height;
|
||||
@Inject(method = "loadTextureAtlas", at = @At("HEAD"))
|
||||
private void angelica$applyAtlasSettings(CallbackInfo ci) {
|
||||
if ((Object) this == Minecraft.getMinecraft().getTextureMapBlocks()) {
|
||||
SodiumGameOptions.applyAtlasSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The block atlas re-gathers its icon list on every reload, so the previous attribution is stale.
|
||||
*/
|
||||
@Inject(method = "registerIcons", at = @At("HEAD"))
|
||||
private void angelica$resetMipmapStrategies(CallbackInfo ci) {
|
||||
if (this.textureType == 0) {
|
||||
MipmapStrategies.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Brackets each block's icon registration so the sprites it registers can be attributed to it.
|
||||
*/
|
||||
@Redirect(
|
||||
method = "registerIcons",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/block/Block;registerBlockIcons(Lnet/minecraft/client/renderer/texture/IIconRegister;)V"))
|
||||
private void angelica$trackBlockIcons(Block block, IIconRegister reg) {
|
||||
MipmapStrategies.beginBlock(block);
|
||||
try {
|
||||
block.registerBlockIcons(reg);
|
||||
} finally {
|
||||
MipmapStrategies.endBlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes the icon to whichever block is registering it.
|
||||
*/
|
||||
@Inject(method = "registerIcon", at = @At("HEAD"))
|
||||
private void angelica$recordSpriteOwner(String p_94245_1_, CallbackInfoReturnable<IIcon> cir) {
|
||||
MipmapStrategies.recordSprite(this.textureType, p_94245_1_);
|
||||
}
|
||||
|
||||
@ModifyArg(
|
||||
method = "loadTextureAtlas",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;loadSprite([Ljava/awt/image/BufferedImage;Lnet/minecraft/client/resources/data/AnimationMetadataSection;Z)V"))
|
||||
private boolean angelica$forceSpritePadding(boolean useAnisotropicFiltering) {
|
||||
if ((Object) this != Minecraft.getMinecraft().getTextureMapBlocks()) {
|
||||
return useAnisotropicFiltering;
|
||||
}
|
||||
return useAnisotropicFiltering || SodiumGameOptions.needsForcedSpritePadding();
|
||||
}
|
||||
|
||||
@ModifyExpressionValue(
|
||||
method = "initMissingImage",
|
||||
at = @At(
|
||||
value = "FIELD",
|
||||
target = "Lnet/minecraft/client/renderer/texture/TextureMap;anisotropicFiltering:I",
|
||||
opcode = Opcodes.GETFIELD))
|
||||
private int angelica$forceMissingImagePadding(int anisotropicFiltering) {
|
||||
if ((Object) this != Minecraft.getMinecraft().getTextureMapBlocks()) {
|
||||
return anisotropicFiltering;
|
||||
}
|
||||
final int forced = SodiumGameOptions.needsForcedSpritePadding() ? 2 : 1;
|
||||
return Math.max(SodiumGameOptions.resolvedAnisotropicFiltering(), forced);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the sprite's mipmap strategy.
|
||||
*/
|
||||
@Inject(
|
||||
method = "loadTextureAtlas",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;loadSprite([Ljava/awt/image/BufferedImage;Lnet/minecraft/client/resources/data/AnimationMetadataSection;Z)V"))
|
||||
private void celeritas$assignMipmapStrategy(CallbackInfo ci, @Local(ordinal = 0) TextureAtlasSprite sprite,
|
||||
@Local(ordinal = 0) TextureMetadataSection metadata) {
|
||||
final MipmapStrategy declared = metadata != null
|
||||
? ((TextureMetadataExtension) metadata).angelica$getMipmapStrategy()
|
||||
: null;
|
||||
if (declared != null) {
|
||||
((SpriteExtension) sprite).celeritas$setMipmapStrategy(declared, true, this.textureType);
|
||||
} else {
|
||||
((SpriteExtension) sprite).celeritas$setMipmapStrategy(
|
||||
MipmapStrategies.inheritedFor(this.textureType, sprite.getIconName()), false, this.textureType);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private int celeritas$unpaddedMipmapCeiling() {
|
||||
int minDimension = Integer.MAX_VALUE;
|
||||
for (TextureAtlasSprite sprite : this.mapUploadedSprites.values()) {
|
||||
final int padding = sprite.useAnisotropicFiltering ? 16 : 0;
|
||||
minDimension = Math.min(minDimension, Math.min(sprite.getIconWidth(), sprite.getIconHeight()) - padding);
|
||||
}
|
||||
return minDimension == Integer.MAX_VALUE ? Integer.MAX_VALUE : MathHelper.calculateLogBaseTwo(minDimension);
|
||||
}
|
||||
|
||||
@Inject(method = "loadTextureAtlas", at = @At("RETURN"))
|
||||
private void celeritas$generateQuadTree(CallbackInfo ci, @Local(ordinal = 0) Stitcher stitcher) {
|
||||
if ((Object) this == Minecraft.getMinecraft().getTextureMapBlocks()) {
|
||||
SodiumGameOptions.recordAtlasMipmapClamp(Minecraft.getMinecraft().gameSettings.mipmapLevels,
|
||||
Math.min(this.mipmapLevels, celeritas$unpaddedMipmapCeiling()));
|
||||
}
|
||||
|
||||
this.celeritas$width = stitcher.getCurrentWidth();
|
||||
this.celeritas$height = stitcher.getCurrentHeight();
|
||||
|
||||
|
||||
Reference in new issue
Block a user