Improve upon Texture Filtering Work (#2096)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-09-15 16:00:43 -07:00
1 parent 75639d3b73
commit 26c161f0e8
26 files changed
+595 -261

No files matched your search

+2
View File
@@ -101,3 +101,5 @@ Targets GL 3.3+ core profile
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
* RGSS Terrain Texture Filtering
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
@@ -1290,14 +1290,26 @@ public class SDLGPURenderBackend extends RenderBackend {
return isProxyTarget(target) ? 0 : st.boundTextures[st.activeTextureUnit];
}
private int requestedMipLevels(int glId) {
return Math.max(1, resourceManager.getOrCreateTexSamplerState(glId).maxLevel + 1);
}
private int fullMipLevels(int glId, int width, int height) {
final int full = PixelOps.defaultMipLevels(width, height);
final int maxLevel = resourceManager.getOrCreateTexSamplerState(glId).maxLevel;
return maxLevel >= 0 ? Math.min(full, maxLevel + 1) : full;
}
private boolean growForLevel(int glId, int level) {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
final int wanted = meta != null
? Math.max(level + 1, fullMipLevels(glId, meta.width(), meta.height()))
: level + 1;
return resourceManager.ensureTextureLevels(glId, wanted);
}
private void ensureLevel0Storage(ContextState st, int glId, int target, int internalFormat, int width, int height, int depth, int format, boolean mipmapped) {
final int numLevels;
if (mipmapped) {
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
} else {
numLevels = 1;
}
final int numLevels = mipmapped ? requestedMipLevels(glId) : 1;
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
final int targetSdlFormat = bgraSwizzle
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
@@ -1321,7 +1333,7 @@ public class SDLGPURenderBackend extends RenderBackend {
&& m.width() == w
&& m.height() == h
&& m.depth() == d
&& m.levels() == levels
&& m.levels() >= levels
&& m.glTarget() == target
&& m.glFormat() == glFormat
&& m.sdlFormat() == sdlFormat;
@@ -1338,6 +1350,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
} else if (!growForLevel(glId, level)) {
return;
}
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, 0, 0, width, height, level, format, type);
@@ -1408,6 +1422,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
} else if (!growForLevel(glId, level)) {
return;
}
if (st.boundPixelUnpackBuffer == 0) return;
@@ -1458,6 +1474,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(cs, destGlId, target, internalFormat, width, height, 1, GL11.GL_NONE, true);
} else if (!growForLevel(destGlId, level)) {
return;
}
textureOps.copyTexSubImageImpl(cs, destGlId, level, 0, 0, x, y, width, height);
@@ -1562,6 +1580,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL12.GL_TEXTURE_MAX_LOD -> ss.maxLod = (float) param;
case GL14.GL_TEXTURE_COMPARE_MODE -> ss.compareMode = param;
case GL14.GL_TEXTURE_COMPARE_FUNC -> ss.compareFunc = param;
case EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> ss.maxAnisotropy = param;
default -> { return; }
}
ss.sdlSampler = 0;
@@ -3321,32 +3340,31 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.refreshTextureReferences(glId);
}
@Override public void generateTextureMipmap(int texture) {
final long texHandle = resourceManager.getTextureHandle(texture);
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null || meta.levels() <= 1) return;
if (resourceManager.getTextureHandle(texture) == 0 || frameManager.getCommandBuffer() == 0) return;
ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null) return;
// The texture was created with only the levels GL had defined, so grow it to the chain glGenerateMipmap fills.
final int wanted = fullMipLevels(texture, meta.width(), meta.height());
if (wanted > meta.levels()) {
if (!resourceManager.ensureTextureLevels(texture, wanted)) return;
meta = resourceManager.getTextureMeta(texture);
if (meta == null) return;
}
if (meta.levels() <= 1) return;
// SDL asserts if any pass (render or copy) is active when generating mipmaps.
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive();
frameManager.noteMipGen();
textureOps.clearPendingMipGen(texture);
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), texHandle);
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), resourceManager.getTextureHandle(texture));
}
@Override public void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (texture == 0) return;
final ContextState st = s();
if (level == 0) {
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(texture);
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
textureOps.releaseTextureForRealloc(st, texture);
if (format == GL12.GL_BGRA && (internalformat == GL11.GL_RGBA || internalformat == GL11.GL_RGBA8)) {
resourceManager.createTextureWithSdlFormat(texture, target,
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
internalformat, width, height, 1, numLevels);
} else {
resourceManager.createTexture(texture, target, internalformat, width, height, 1, numLevels);
}
resourceManager.refreshTextureReferences(texture);
ensureLevel0Storage(st, texture, target, internalformat, width, height, 1, format, true);
} else if (!growForLevel(texture, level)) {
return;
}
textureOps.uploadTextureRegion(st, texture, resourceManager.getTextureMeta(texture), resourceManager.getTextureHandle(texture), pixels, 0, 0, width, height, level, format, type);
}
@@ -3611,6 +3629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_MAX_TEXTURE_SIZE -> MAX_TEXTURE_SIZE;
case GL12.GL_MAX_3D_TEXTURE_SIZE -> MAX_3D_TEXTURE_SIZE;
case GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE -> MAX_CUBE_MAP_TEXTURE_SIZE;
case EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT -> (int) MAX_ANISOTROPY;
default -> -1;
};
}
@@ -3620,6 +3639,8 @@ public class SDLGPURenderBackend extends RenderBackend {
static final int MAX_TEXTURE_SIZE = 16384;
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
static final int MAX_3D_TEXTURE_SIZE = 2048;
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
static final float MAX_ANISOTROPY = 16.0f;
private static int unknownGetInteger(int pname) {
synchronized (UNKNOWN_GET_INTEGER_SEEN) {
@@ -3634,11 +3655,22 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void getInteger(int pname, IntBuffer params) {
if (params.remaining() > 0) params.put(params.position(), getInteger(pname));
}
private static float unknownGetFloat(int pname) {
synchronized (UNKNOWN_GET_FLOAT_SEEN) {
if (UNKNOWN_GET_FLOAT_SEEN.add(pname)) {
LOG.warn("getFloat: unhandled pname 0x{}, returning 0; callers reading this as a device limit will treat the feature as unavailable", Integer.toHexString(pname));
}
}
return 0.0f;
}
private static final IntOpenHashSet UNKNOWN_GET_FLOAT_SEEN = new IntOpenHashSet();
@Override public float getFloat(int pname) {
return switch (pname) {
case GL11.GL_LINE_WIDTH -> 1.0f;
case GL11.GL_POINT_SIZE -> 1.0f;
default -> 0.0f;
case EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT -> MAX_ANISOTROPY;
default -> unknownGetFloat(pname);
};
}
@Override public void getFloat(int pname, FloatBuffer params) {
@@ -513,7 +513,7 @@ public final class ResourceManager {
final int newUsage = reconcileUsageAndWarn(glId, meta.usage | requiredUsage);
if ((meta.usage & newUsage) == newUsage) return oldHandle;
final long newHandle = createTexture(glId, meta.glTarget, meta.glFormat, meta.width, meta.height, meta.depth, meta.levels, newUsage);
final long newHandle = createTextureWithSdlFormat(glId, meta.glTarget, meta.sdlFormat, meta.glFormat, meta.width, meta.height, meta.depth, meta.levels, newUsage);
if (newHandle == 0) return oldHandle;
if (oldHandle != 0 && !copyAllMips(oldHandle, newHandle, meta.glTarget, meta.width, meta.height, meta.depth, meta.levels)) {
LOG.warn("[ResourceManager] texture {} usage promote 0x{} -> 0x{} had no copy pass available; restoring the populated texture and skipping the promote. Image load/store on it will not work this frame.", glId, Integer.toHexString(meta.usage), Integer.toHexString(newUsage));
@@ -530,6 +530,41 @@ public final class ResourceManager {
return newHandle;
}
public boolean ensureTextureLevels(int glId, int levels) {
final TextureMeta meta;
final long oldHandle;
if (fastRead()) {
meta = textureMetas.get(glId);
if (meta == null) return false;
if (meta.levels >= levels) return true;
oldHandle = textureHandles.get(glId);
} else {
rLock.lock();
try {
meta = textureMetas.get(glId);
if (meta == null) return false;
if (meta.levels >= levels) return true;
oldHandle = textureHandles.get(glId);
} finally {
rLock.unlock();
}
}
final long newHandle = createTextureWithSdlFormat(glId, meta.glTarget, meta.sdlFormat, meta.glFormat, meta.width, meta.height, meta.depth, levels, meta.usage);
if (newHandle == 0) return false;
if (oldHandle != 0 && !copyAllMips(oldHandle, newHandle, meta.glTarget, meta.width, meta.height, meta.depth, meta.levels)) {
LOG.warn("[ResourceManager] texture {} mip grow {} -> {} had no copy pass available; keeping the populated texture. Levels past {} stay undefined this frame.", glId, meta.levels, levels, meta.levels - 1);
releaseTextureDeferred(newHandle);
restoreTextureHandle(glId, oldHandle, meta);
return false;
}
releaseTextureDeferred(oldHandle);
refreshTextureReferences(glId);
LOG.debug("Recreated texture {} with levels {} -> {} ({}x{})", glId, meta.levels, levels, meta.width, meta.height);
return true;
}
private boolean copyAllMips(long srcTex, long dstTex, int glTarget, int width, int height, int depth, int levels) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return false;
@@ -81,6 +81,7 @@ public final class TextureOps {
case GL12.GL_TEXTURE_MAX_LOD -> ss.maxLod = (float) param;
case GL14.GL_TEXTURE_COMPARE_MODE -> ss.compareMode = param;
case GL14.GL_TEXTURE_COMPARE_FUNC -> ss.compareFunc = param;
case EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> ss.maxAnisotropy = param;
default -> { return; }
}
ss.sdlSampler = 0;
@@ -384,6 +384,7 @@ public enum Mixins implements IMixins {
, "celeritas.features.mipmaps.MixinTextureMetadataSectionSerializer"
, "celeritas.features.textures.MixinTextureMap"
, "celeritas.features.textures.MixinTextureAtlasSprite"
, "celeritas.features.textures.MixinStitcherHolder"
, "celeritas.biome_blending.MixinBlockGrass"
, "celeritas.biome_blending.MixinBlockLeaves"
, "celeritas.biome_blending.MixinBlockLiquid"
@@ -165,6 +165,7 @@ 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;
@@ -255,14 +256,22 @@ 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 (hasCachedMaterial && sprite == lastSprite) {
correctMaterial = lastMaterial;
if ((facesAtBaseMaterial & faceBit) != 0) {
correctMaterial = material;
} else {
correctMaterial = selectMaterial(material, sprite, isShaderPackOverride, useRenderPassOptimization);
lastSprite = sprite;
lastMaterial = correctMaterial;
hasCachedMaterial = true;
if (hasCachedMaterial && sprite == lastSprite) {
correctMaterial = lastMaterial;
} else {
correctMaterial = selectMaterial(material, sprite, isShaderPackOverride, useRenderPassOptimization);
lastSprite = sprite;
lastMaterial = correctMaterial;
hasCachedMaterial = true;
}
if (correctMaterial == material) {
facesAtBaseMaterial |= faceBit;
}
}
final var builder = buffers.get(correctMaterial);
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
import com.gtnewhorizons.angelica.rendering.voxelization.ShadowVoxelizer;
@@ -48,7 +49,9 @@ import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL20;
import java.util.ArrayList;
@@ -61,9 +64,14 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
private GlProgram<? extends ChunkShaderInterface> irisProgram;
private boolean usingIrisProgram;
private int rgssSampler;
private boolean rgssSamplerResolved;
private boolean rgssSamplerBound;
private int terrainSampler;
private boolean terrainSamplerResolved;
private int terrainSamplerAnisotropy = -1;
private boolean terrainSamplerNearest;
private int packTerrainSampler;
private boolean packTerrainSamplerResolved;
private int packTerrainSamplerAnisotropy = -1;
private boolean terrainSamplerBound;
private final GpuTerrainCuller culler;
private final ReusableCachedBatch gpuBatch = new ReusableCachedBatch();
@@ -161,6 +169,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
this.activeProgram = (GlProgram<ChunkShaderInterface>) override;
this.irisProgram = override;
this.usingIrisProgram = true;
bindPackTerrainSampler();
return;
}
}
@@ -169,7 +178,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
this.usingIrisProgram = false;
this.irisProgram = null;
super.begin(pass);
bindRgssSampler();
bindTerrainSampler();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
@@ -177,7 +186,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
@Override
protected void end(TerrainRenderPass pass) {
unbindRgssSampler();
unbindTerrainSampler();
if (usingIrisProgram && irisProgram != null) {
irisProgram.getInterface().restoreState();
@@ -192,32 +201,73 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
super.end(pass);
}
private void bindRgssSampler() {
if (!AngelicaRenderPassConfiguration.isRgssEnabled()) {
return;
}
private void bindTerrainSampler() {
final int anisotropy = SodiumGameOptions.resolvedAnisotropicFiltering();
final boolean nearest = ClientProxy.options().quality.texelSampling.isNearest();
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 (!terrainSamplerResolved || terrainSamplerAnisotropy != anisotropy
|| terrainSamplerNearest != nearest) {
if (!terrainSamplerResolved) {
terrainSampler = RenderSystem.genSampler();
if (terrainSampler == 0) {
AngelicaMod.LOGGER.warn("Sampler objects unavailable; terrain filtering will fall back to the atlas texture's own parameters");
}
}
terrainSamplerResolved = true;
terrainSamplerAnisotropy = anisotropy;
terrainSamplerNearest = nearest;
configureTerrainSampler(terrainSampler, nearest, anisotropy);
}
if (rgssSampler != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, rgssSampler);
rgssSamplerBound = true;
if (terrainSampler != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, terrainSampler);
terrainSamplerBound = true;
}
}
private void unbindRgssSampler() {
if (rgssSamplerBound) {
private void bindPackTerrainSampler() {
final int anisotropy = SodiumGameOptions.resolvedAnisotropicFiltering();
if (!packTerrainSamplerResolved || packTerrainSamplerAnisotropy != anisotropy) {
if (!packTerrainSamplerResolved) {
packTerrainSampler = RenderSystem.genSampler();
if (packTerrainSampler == 0) {
AngelicaMod.LOGGER.warn("Sampler objects unavailable; shader pack terrain filtering will fall back to the atlas texture's own parameters");
}
}
packTerrainSamplerResolved = true;
packTerrainSamplerAnisotropy = anisotropy;
configureTerrainSampler(packTerrainSampler, true, anisotropy);
}
if (packTerrainSampler != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, packTerrainSampler);
terrainSamplerBound = true;
}
}
private static void configureTerrainSampler(int sampler, boolean nearest, int anisotropy) {
if (sampler == 0) {
return;
}
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MIN_FILTER,
nearest ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_LINEAR_MIPMAP_LINEAR);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MAG_FILTER,
nearest ? GL11.GL_NEAREST : GL11.GL_LINEAR);
if (SodiumGameOptions.anisotropySupported()) {
RenderSystem.samplerParameteri(sampler,
EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
}
}
private void unbindTerrainSampler() {
if (terrainSamplerBound) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, 0);
rgssSamplerBound = false;
terrainSamplerBound = false;
}
}
@@ -229,10 +279,16 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
culler.delete();
}
unbindRgssSampler();
RenderSystem.destroySampler(rgssSampler);
rgssSampler = 0;
rgssSamplerResolved = false;
unbindTerrainSampler();
RenderSystem.destroySampler(terrainSampler);
terrainSampler = 0;
terrainSamplerResolved = false;
terrainSamplerAnisotropy = -1;
terrainSamplerNearest = false;
RenderSystem.destroySampler(packTerrainSampler);
packTerrainSampler = 0;
packTerrainSamplerResolved = false;
packTerrainSamplerAnisotropy = -1;
}
@Override
@@ -5,7 +5,6 @@ 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;
@@ -38,7 +37,7 @@ public class AngelicaRenderPassConfiguration {
}
public static RenderPassConfiguration<BlockRenderLayer> build(ChunkVertexType vertexType) {
rgssEnabled = SodiumGameOptions.effectiveTextureFilterMode() == TextureFilterMode.RGSS;
rgssEnabled = SodiumGameOptions.effectiveTextureFilterMode().usesRgss();
SOLID_PASS = builderForRenderType(0, true, vertexType)
.name("solid")
@@ -12,4 +12,9 @@ public interface SpriteExtension {
/** One-shot: returns true if marked, then resets. */
boolean celeritas$shouldUpdate();
void celeritas$setMipmapStrategy(MipmapStrategy strategy, boolean explicit, int textureType);
/** Width of the replicated-edge border reserved around this sprite in its stitcher slot. */
int angelica$getGutterWidth();
void angelica$setGutterWidth(int gutter);
}
@@ -26,11 +26,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
private static final int NORMAL_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("iris_Normal").getPointer();
private static final int MC_ENTITY_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("mc_Entity").getPointer();
private static final int MID_BLOCK_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("at_midBlock").getPointer();
private static final int A_TEXCOORD_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("a_TexCoord").getPointer();
// One unit of UV-space shift toward the quad centroid. Stops bilinear /
// mipmap sampling from bleeding pixels from neighboring atlas cells.
private static final float TEX_CENTROID_BIAS = 1.0f / 32768.0f;
// a_TexCoord used to be here, if something related to it breaks add this back.
private final ChunkVertexEncoder baseEncoder = IrisExtendedChunkVertexType.BASE_TYPE.createEncoder();
private final CeleritasQuadView quad = new CeleritasQuadView();
@@ -133,14 +129,6 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 2, midUV);
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 3, midUV);
for (int vIdx = 0; vIdx < 4; vIdx++) {
final long uvBase = ptr - (long) (3 - vIdx) * STRIDE + A_TEXCOORD_OFFSET;
final float vU = memGetFloat(uvBase);
final float vV = memGetFloat(uvBase + 4L);
memPutFloat(uvBase, vU + (vU < midU ? TEX_CENTROID_BIAS : -TEX_CENTROID_BIAS));
memPutFloat(uvBase + 4L, vV + (vV < midV ? TEX_CENTROID_BIAS : -TEX_CENTROID_BIAS));
}
quad.setup(ptr, STRIDE);
NormalHelper.computeFaceNormal(normal, quad);
final int packedNormal = NormI8.pack(normal);
@@ -12,7 +12,7 @@ import static org.joml.Math.clamp;
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 STRICT_CUTOUT_ALPHA_REF = 0.3f;
private static final float ALPHA_BIAS = 0.025f;
private MipmapGenerator() {
@@ -137,14 +137,14 @@ public final class MipmapGenerator {
* 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) {
static float alphaTestCoverage(int[] image, int width, float alphaRef, float alphaScale) {
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++) {
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width - 1; x++) {
final int i = x + y * width;
final float a00 = scaledAlpha(image[i], alphaFactor);
final float a10 = scaledAlpha(image[i + 1], alphaFactor);
@@ -190,7 +190,7 @@ public final class MipmapGenerator {
return clamp(0.0f, 1.0f, ColorARGB.unpackAlpha(color) * alphaFactor);
}
static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef, int border) {
static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef) {
float min = 0.0f;
float max = 4.0f;
float scale = 1.0f;
@@ -198,7 +198,7 @@ public final class MipmapGenerator {
float bestError = Float.POSITIVE_INFINITY;
for (int i = 0; i < 5; i++) {
final float coverage = alphaTestCoverage(image, width, alphaRef, scale, border);
final float coverage = alphaTestCoverage(image, width, alphaRef, scale);
final float error = Math.abs(coverage - desiredCoverage);
if (error < bestError) {
bestError = error;
@@ -225,7 +225,7 @@ public final class MipmapGenerator {
}
public static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips,
MipmapStrategy strategy, boolean hasTransparentPixel, int border) {
MipmapStrategy strategy, boolean hasTransparentPixel) {
final MipmapStrategy resolved = resolve(strategy, hasTransparentPixel);
final boolean isCutout = isCutoutStrategy(strategy, hasTransparentPixel);
@@ -247,16 +247,8 @@ public final class MipmapGenerator {
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);
}
}
final float cutoutRef = resolved == MipmapStrategy.STRICT_CUTOUT ? STRICT_CUTOUT_ALPHA_REF : CUTOUT_ALPHA_REF;
final float originalCoverage = isCutout ? alphaTestCoverage(base, width, cutoutRef, 1.0f) : 0.0f;
for (int level = 1; level <= mipLevel; level++) {
final int levelWidth = width >> level;
@@ -283,7 +275,7 @@ public final class MipmapGenerator {
}
if (isCutout) {
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, alphaRef, border >> level);
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, cutoutRef);
}
}
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.utils;
import net.minecraft.client.renderer.texture.TextureUtil;
public final class SpritePadding {
private static int[][] scratch;
private SpritePadding() {}
/**
* The gutter has to survive being halved once per mip level, and the stitcher slot has to stay a multiple
* of 2^mipmapLevels so every level's origin lands on a texel, so 1 &lt;&lt; mipmapLevels is the only workable
* width. At mip 0 the terrain atlas still needs one texel for the RGSS taps to land in.
*/
public static int gutterFor(int mipmapLevels, boolean terrainAtlas) {
if (mipmapLevels <= 0) {
return terrainAtlas ? 1 : 0;
}
return 1 << mipmapLevels;
}
public static void uploadPadded(int[][] frameData, int width, int height, int originX, int originY, int gutter,
boolean blur, boolean clamp) {
if (gutter <= 0) {
TextureUtil.uploadTextureMipmap(frameData, width, height, originX, originY, blur, clamp);
return;
}
scratch = padFrame(frameData, width, height, gutter, scratch);
TextureUtil.uploadTextureMipmap(scratch, width + 2 * gutter, height + 2 * gutter,
originX - gutter, originY - gutter, blur, clamp);
}
private static int[][] padFrame(int[][] frameData, int width, int height, int gutter, int[][] scratch) {
final int[][] padded = scratch != null && scratch.length == frameData.length ? scratch
: new int[frameData.length][];
for (int level = 0; level < frameData.length; level++) {
final int[] levelData = frameData[level];
if (levelData == null) {
padded[level] = null;
continue;
}
final int levelWidth = width >> level;
final int levelHeight = height >> level;
final int levelGutter = gutter >> level;
final int size = (levelWidth + 2 * levelGutter) * (levelHeight + 2 * levelGutter);
int[] target = padded[level];
if (target == null || target.length < size) {
target = new int[size];
padded[level] = target;
}
System.arraycopy(levelData, 0, target, 0, Math.min(levelData.length, levelWidth * levelHeight));
padClampToEdge(target, levelWidth, levelHeight, levelGutter);
}
return padded;
}
private static void padClampToEdge(int[] data, int width, int height, int border) {
if (border <= 0 || width <= 0 || height <= 0) {
return;
}
final int stride = width + 2 * border;
for (int y = height - 1; y >= 0; y--) {
System.arraycopy(data, y * width, data, border + (y + border) * stride, width);
}
clampBorderToEdge(data, stride, height + 2 * border, border);
}
private static void clampBorderToEdge(int[] image, int paddedWidth, int paddedHeight, int border) {
if (border <= 0) {
return;
}
final int contentWidth = paddedWidth - 2 * border;
final int contentHeight = paddedHeight - 2 * border;
if (contentWidth <= 0 || contentHeight <= 0) {
return;
}
for (int y = 0; y < contentHeight; y++) {
final int row = border + (y + border) * paddedWidth;
final int left = image[row];
final int right = image[row + contentWidth - 1];
for (int i = 1; i <= border; i++) {
image[row - i] = left;
image[row + contentWidth - 1 + i] = right;
}
}
final int top = border * paddedWidth;
final int bottom = (border + contentHeight - 1) * paddedWidth;
for (int i = 1; i <= border; i++) {
System.arraycopy(image, top, image, top - i * paddedWidth, paddedWidth);
System.arraycopy(image, bottom, image, bottom + i * paddedWidth, paddedWidth);
}
}
}
@@ -2,7 +2,6 @@ package jss.notfine.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
import jss.notfine.config.NotFineConfig;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
@@ -106,8 +105,6 @@ public class NotFineGameOptionPages {
.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)
@@ -140,10 +137,7 @@ public class NotFineGameOptionPages {
.add(Settings.DYNAMIC_FOV.option)
.add(mipmapLevels)
.add(textureFilterMode)
.add(SodiumGameOptionPages.anisotropicFilteringSlider(
vanillaOpts,
textureFilterMode::getValue,
mipmapLevels::getValue),
.add(SodiumGameOptionPages.anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue),
SodiumGameOptions.anisotropySupported())
.build());
@@ -1,9 +1,10 @@
package jss.notfine.render;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import com.gtnewhorizons.angelica.utils.SpritePadding;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureUtil;
/**
* Originally created by ganymedes01 for Et Futurum.
@@ -58,7 +59,8 @@ public class InterpolatedIcon extends TextureAtlasSprite {
}
}
TextureUtil.uploadTextureMipmap(interpolatedFrameData, width, height, originX, originY, false, false);
SpritePadding.uploadPadded(interpolatedFrameData, width, height, originX, originY,
((SpriteExtension) this).angelica$getGutterWidth(), false, false);
}
}
}
@@ -4,7 +4,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import net.minecraft.client.Minecraft;
@@ -40,6 +39,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.TexelSampling;
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;
@@ -194,7 +194,15 @@ public class SodiumGameOptionPages {
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD, OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
textureFilterMode.iris$dynamicallyEnable(() -> mipmapLevels.getValue() > 0);
final OptionImpl<SodiumGameOptions, TexelSampling> texelSampling =
OptionImpl.createBuilder(TexelSampling.class, sodiumOpts)
.setName(I18n.format("sodium.options.texel_sampling.name"))
.setTooltip(I18n.format("sodium.options.texel_sampling.tooltip"))
.setControl(option -> new CyclingControl<>(option, TexelSampling.class))
.setBinding((opts, value) -> opts.quality.texelSampling = value,
opts -> opts.quality.texelSampling)
.setImpact(OptionImpact.LOW)
.build();
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(GraphicsMode.class, vanillaOpts)
@@ -250,7 +258,8 @@ public class SodiumGameOptionPages {
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(textureFilterMode)
.add(anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue, mipmapLevels::getValue),
.add(texelSampling)
.add(anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue),
SodiumGameOptions.anisotropySupported())
// TODO
/*.add(OptionImpl.createBuilder(int.class, vanillaOpts)
@@ -681,7 +690,7 @@ public class SodiumGameOptionPages {
}
public static OptionImpl<GameSettings, Integer> anisotropicFilteringSlider(MinecraftOptionsStorage storage,
Supplier<TextureFilterMode> mode, IntSupplier mipmapLevels) {
Supplier<TextureFilterMode> mode) {
final int min = SodiumGameOptions.minAnisotropyLevel();
final int max = Math.max(SodiumGameOptions.maxAnisotropyLevel(), min + 1);
final OptionImpl<GameSettings, Integer> option = OptionImpl.createBuilder(int.class, storage)
@@ -695,8 +704,7 @@ public class SodiumGameOptionPages {
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
.build();
option.iris$dynamicallyEnable(() -> mipmapLevels.getAsInt() > 0 && mode.get().usesAnisotropy()
&& SodiumGameOptions.hasAnisotropyRange());
option.iris$dynamicallyEnable(() -> mode.get().usesAnisotropy() && SodiumGameOptions.hasAnisotropyRange());
return option;
}
@@ -11,6 +11,7 @@ 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.TexelSampling;
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
import net.coderbot.iris.Iris;
import net.minecraft.client.Minecraft;
@@ -26,7 +27,7 @@ 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 = 2;
private static final int MIN_ANISOTROPY_LEVEL = 1;
private static final int MAX_ANISOTROPY_LEVEL = 4;
private static final Gson GSON = new GsonBuilder()
@@ -35,6 +36,7 @@ public class SodiumGameOptions {
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();
private static int resourcePackMipmapCeiling = Integer.MAX_VALUE;
private static int atlasMipmapLevels = -1;
public final QualitySettings quality = new QualitySettings();
public final AdvancedSettings advanced = new AdvancedSettings();
public final PerformanceSettings performance = new PerformanceSettings();
@@ -45,6 +47,14 @@ public class SodiumGameOptions {
resourcePackMipmapCeiling = actualMipmapLevels < requestedMipmapLevels ? actualMipmapLevels : Integer.MAX_VALUE;
}
public static void recordAtlasMipmapLevels(int mipmapLevels) {
atlasMipmapLevels = mipmapLevels;
}
public static int terrainMipmapLevels() {
return atlasMipmapLevels >= 0 ? atlasMipmapLevels : effectiveMipmapLevelsEstimate();
}
public static void applyAtlasSettings() {
final Minecraft mc = Minecraft.getMinecraft();
final TextureMap atlas = mc.getTextureMapBlocks();
@@ -55,43 +65,29 @@ public class SodiumGameOptions {
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())
return effectiveTextureFilterMode().usesAnisotropy()
? MathHelper.clamp_int(mc.gameSettings.anisotropicFiltering, MIN_ANISOTROPY, 1 << maxAnisotropyLevel())
: 1;
}
private static boolean filteringPossible() {
return effectiveMipmapLevelsEstimate() > 0;
}
private static int effectiveMipmapLevelsEstimate() {
public static int effectiveMipmapLevelsEstimate() {
return Math.min(Minecraft.getMinecraft().gameSettings.mipmapLevels, resourcePackMipmapCeiling);
}
public static boolean needsForcedSpritePadding() {
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;
return resolveFilterMode(ClientProxy.options().quality.textureFilterMode);
}
public static TextureFilterMode resolveFilterMode(TextureFilterMode mode) {
if (mode == null) {
mode = TextureFilterMode.RGSS;
mode = TextureFilterMode.RGSS_ANISOTROPIC;
}
return mode.usesAnisotropy() && !anisotropySupported() ? TextureFilterMode.RGSS : mode;
if (mode.usesAnisotropy() && !anisotropySupported()) {
return mode.usesRgss() ? TextureFilterMode.RGSS : TextureFilterMode.NONE;
}
return mode;
}
public static int maxAnisotropyLevel() {
@@ -143,7 +139,11 @@ public class SodiumGameOptions {
}
if (config.quality.textureFilterMode == null) {
config.quality.textureFilterMode = TextureFilterMode.RGSS;
config.quality.textureFilterMode = TextureFilterMode.RGSS_ANISOTROPIC;
}
if (config.quality.texelSampling == null) {
config.quality.texelSampling = TexelSampling.LINEAR;
}
if (GLStateManager.capabilities != null) {
@@ -241,7 +241,8 @@ public class SodiumGameOptions {
public static class QualitySettings {
public GraphicsQuality grassQuality = GraphicsQuality.DEFAULT;
public boolean useCeleritasSmoothLighting = true;
public TextureFilterMode textureFilterMode = TextureFilterMode.RGSS;
public TextureFilterMode textureFilterMode = TextureFilterMode.RGSS_ANISOTROPIC;
public TexelSampling texelSampling = TexelSampling.LINEAR;
}
public static class ReducerSettings {
@@ -0,0 +1,24 @@
package me.jellysquid.mods.sodium.client.gui.options.named;
/**
* Terrain sampler min/mag filter.
*/
public enum TexelSampling implements NamedState {
LINEAR("sodium.options.texel_sampling.linear"),
NEAREST("sodium.options.texel_sampling.nearest");
private final String name;
TexelSampling(String name) {
this.name = name;
}
@Override
public String getKey() {
return this.name;
}
public boolean isNearest() {
return this == NEAREST;
}
}
@@ -6,7 +6,8 @@ package me.jellysquid.mods.sodium.client.gui.options.named;
public enum TextureFilterMode implements NamedState {
RGSS("sodium.options.texture_filtering.rgss"),
ANISOTROPIC("sodium.options.texture_filtering.anisotropic"),
NONE("options.off");
RGSS_ANISOTROPIC("sodium.options.texture_filtering.rgss_anisotropic"),
NONE("sodium.options.texture_filtering.none");
private static final TextureFilterMode[] WITHOUT_ANISOTROPY = {RGSS, NONE};
private final String name;
@@ -16,8 +17,8 @@ public enum TextureFilterMode implements NamedState {
}
/**
* 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.
* Anisotropic filtering rides on an extension that isn't guaranteed in GL 3.3, so the modes that use it are only
* offered when the driver actually exposes it.
*/
public static TextureFilterMode[] selectableValues(boolean anisotropySupported) {
return anisotropySupported ? values() : WITHOUT_ANISOTROPY;
@@ -28,11 +29,12 @@ public enum TextureFilterMode implements NamedState {
return this.name;
}
public boolean usesAnisotropy() {
return this == ANISOTROPIC;
public boolean usesRgss() {
return this == RGSS || this == RGSS_ANISOTROPIC;
}
public boolean needsSpritePadding() {
return this == RGSS;
public boolean usesAnisotropy() {
return this == ANISOTROPIC || this == RGSS_ANISOTROPIC;
}
}
@@ -2,6 +2,8 @@ package net.coderbot.iris.texture.pbr;
import com.gtnewhorizons.angelica.compat.mojang.AutoClosableAbstractTexture;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import com.gtnewhorizons.angelica.utils.SpritePadding;
import lombok.Getter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.texture.util.TextureExporter;
@@ -109,6 +111,7 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
}
protected void uploadSprite(TextureAtlasSprite sprite) {
final int gutter = ((SpriteExtension) sprite).angelica$getGutterWidth();
if (sprite.animationMetadata.getFrameCount() > 1) {
final AnimationMetadataSection metadata = sprite.animationMetadata;
@@ -116,12 +119,12 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
for (int frame = sprite.frameCounter; frame >= 0; frame--) {
final int frameIndex = metadata.getFrameIndex(frame);
if (frameIndex >= 0 && frameIndex < frameCount) {
TextureUtil.uploadTextureMipmap(sprite.getFrameTextureData(frameIndex), sprite.getIconWidth(), sprite.getIconHeight(), sprite.getOriginX(), sprite.getOriginY(), false, false);
SpritePadding.uploadPadded(sprite.getFrameTextureData(frameIndex), sprite.getIconWidth(), sprite.getIconHeight(), sprite.getOriginX(), sprite.getOriginY(), gutter, false, false);
return;
}
}
}
TextureUtil.uploadTextureMipmap(sprite.getFrameTextureData(0), sprite.getIconWidth(), sprite.getIconHeight(), sprite.getOriginX(), sprite.getOriginY(), false, false);
SpritePadding.uploadPadded(sprite.getFrameTextureData(0), sprite.getIconWidth(), sprite.getIconHeight(), sprite.getOriginX(), sprite.getOriginY(), gutter, false, false);
}
public void cycleAnimationFrames() {
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.mixins.interfaces.ISpriteExt;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import net.coderbot.iris.Iris;
import net.coderbot.iris.texture.format.TextureFormat;
import net.coderbot.iris.texture.format.TextureFormatLoader;
@@ -18,9 +19,9 @@ import net.coderbot.iris.texture.pbr.PBRType;
import net.coderbot.iris.texture.pbr.TextureAtlasSpriteExtension;
import net.coderbot.iris.texture.util.ImageManipulationUtil;
import net.coderbot.iris.texture.util.TextureExporter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.IResource;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.AnimationFrame;
@@ -32,7 +33,6 @@ import javax.annotation.Nullable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -93,34 +93,11 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
}
if (AngelicaConfig.enablePBRDebug && (normalAtlas != null || specularAtlas != null)) {
TextureExporter.exportTextures("pbr_debug/atlas", "base_atlas.png", texMap.getGlTextureId(), mipLevel, atlasWidth, atlasHeight);
final String atlas = texMap == Minecraft.getMinecraft().getTextureMapBlocks() ? "blocks" : "items";
TextureExporter.exportTextures("pbr_debug/atlas", "base_atlas_" + atlas + ".png", texMap.getGlTextureId(), mipLevel, atlasWidth, atlasHeight);
}
}
@Nullable
private static NativeImage padAnisotropic(NativeImage image, int frameWidth, int frameHeight) {
final int width = image.getWidth();
final int height = image.getHeight();
final int frames = (frameWidth == width && frameHeight > 0) ? height / frameHeight : 0;
if (frames < 1) {
return null;
}
final int paddedWidth = frameWidth + 16;
final int paddedHeight = frameHeight + 16;
final int framePixels = paddedWidth * paddedHeight;
final NativeImage padded = new NativeImage(paddedWidth, paddedHeight * frames, false);
final int[] frame = new int[framePixels];
for (int f = 0; f < frames; f++) {
Arrays.fill(frame, 0);
image.getRGB(0, f * frameHeight, frameWidth, frameHeight, frame, 0, frameWidth);
TextureUtil.prepareAnisotropicData(frame, frameWidth, frameHeight, 8);
padded.setRGB(0, f * paddedHeight, paddedWidth, paddedHeight, frame, 0, paddedWidth);
}
return padded;
}
private static AnimationMetadataSection withFrameSize(AnimationMetadataSection src, int frameWidth, int frameHeight) {
final List<AnimationFrame> frames = new ArrayList<>(src.getFrameCount());
for (int f = 0; f < src.getFrameCount(); f++) {
@@ -156,9 +133,8 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
final Pair<Integer, Integer> frameSize = this.getFrameSize(nativeImage.getWidth(), nativeImage.getHeight(), animationMetadata);
int frameWidth = frameSize.getLeft();
int frameHeight = frameSize.getRight();
final int spritePadding = sprite.useAnisotropicFiltering ? 16 : 0;
final int targetFrameWidth = sprite.getIconWidth() - spritePadding;
final int targetFrameHeight = sprite.getIconHeight() - spritePadding;
final int targetFrameWidth = sprite.getIconWidth();
final int targetFrameHeight = sprite.getIconHeight();
if (frameWidth != targetFrameWidth || frameHeight != targetFrameHeight) {
final int imageWidth = nativeImage.getWidth();
final int imageHeight = nativeImage.getHeight();
@@ -190,17 +166,10 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
final ResourceLocation pbrSpriteName = new ResourceLocation(spriteName.getResourceDomain(), spriteName.getResourcePath() + pbrType.getSuffix());
final TextureAtlasSpriteInfo pbrSpriteInfo = new PBRTextureAtlasSpriteInfo(pbrSpriteName, frameWidth, frameHeight, pbrType);
final int x = sprite.getOriginX();
final int y = sprite.getOriginY();
boolean padInLoadSprite = sprite.useAnisotropicFiltering;
if (padInLoadSprite) {
final NativeImage padded = padAnisotropic(nativeImage, frameWidth, frameHeight);
if (padded != null) {
nativeImage = padded;
padInLoadSprite = false;
}
}
pbrSprite = new PBRTextureAtlasSprite(pbrSpriteInfo, animationMetadata, atlasWidth, atlasHeight, x, y, nativeImage, padInLoadSprite, mipLevel);
final int gutter = ((SpriteExtension) sprite).angelica$getGutterWidth();
pbrSprite = new PBRTextureAtlasSprite(pbrSpriteInfo, animationMetadata, atlasWidth, atlasHeight,
sprite.getOriginX(), sprite.getOriginY(), gutter, nativeImage, false, mipLevel);
syncAnimation(sprite, pbrSprite);
} catch (FileNotFoundException e) {
//
@@ -261,10 +230,11 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
public static class PBRTextureAtlasSprite extends TextureAtlasSprite implements CustomMipmapGenerator.Provider {
// This feels super janky
protected PBRTextureAtlasSprite(TextureAtlasSpriteInfo info, AnimationMetadataSection animationMetaDataSection, int atlasWidth, int atlasHeight, int x, int y, NativeImage nativeImage, boolean useAnisotropicFiltering, int miplevel) {
protected PBRTextureAtlasSprite(TextureAtlasSpriteInfo info, AnimationMetadataSection animationMetaDataSection, int atlasWidth, int atlasHeight, int x, int y, int gutter, NativeImage nativeImage, boolean useAnisotropicFiltering, int miplevel) {
super(info.name().toString());
((SpriteExtension) this).angelica$setGutterWidth(gutter);
super.loadSprite(Objects.requireNonNull(getMipmapGenerator(info, atlasWidth, atlasHeight)).generateMipLevels(nativeImage, miplevel), animationMetaDataSection, useAnisotropicFiltering);
super.initSprite(atlasWidth, atlasHeight, x, y, false);
super.initSprite(atlasWidth, atlasHeight, x - gutter, y - gutter, false);
}
@Override
@@ -44,8 +44,14 @@ 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.none=None
sodium.options.texture_filtering.rgss=RGSS
sodium.options.texture_filtering.anisotropic=Anisotropic
sodium.options.texture_filtering.rgss_anisotropic=RGSS & Anisotropic
sodium.options.texel_sampling.name=Texel Blending
sodium.options.texel_sampling.tooltip=Sets how the boundary between two texels of a block texture is drawn. Nearest is sharp, Linear blends.
sodium.options.texel_sampling.linear=Linear
sodium.options.texel_sampling.nearest=Nearest
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
@@ -323,4 +329,4 @@ sodium.options.upload_method.buffer_data=BufferData
sodium.options.upload_method.buffer_subdata=BufferSubData
sodium.options.upload_method.map_buffer=MapBufferRange
sodium.options.enable_deferred_batching.name=Deferred Particle Batching
sodium.options.enable_deferred_batching.tooltip=Batches particle draw calls into fewer merged draws per layer. Disable if particles render incorrectly.
sodium.options.enable_deferred_batching.tooltip=Batches particle draw calls into fewer merged draws per layer. Disable if particles render incorrectly.
@@ -45,7 +45,36 @@ out vec4 fragColor;
#define fragColor gl_FragColor
#endif
vec2 snapToTexelCentre(vec2 uv, vec2 texelSize, vec2 texelsPerPixel) {
vec2 centreCoords = uv / texelSize - 0.5;
vec2 lowerCentre = floor(centreCoords);
vec2 alongSeam = centreCoords - lowerCentre;
vec2 rampWidth = clamp(texelsPerPixel, vec2(1e-8), vec2(1.0));
alongSeam = clamp((alongSeam - 0.5) / rampWidth + 0.5, 0.0, 1.0);
return (lowerCentre + alongSeam + 0.5) * texelSize;
}
vec2 texelsPerPixelOf(vec2 du, vec2 dv, vec2 texelSize) {
return sqrt(du * du + dv * dv) / texelSize;
}
vec4 sampleTexelSnapped(sampler2D tex, vec2 uv, vec2 texelSize) {
vec2 du = dFdx(uv);
vec2 dv = dFdy(uv);
float gradientScale = exp2(v_MaterialMipBias);
return textureGrad(tex, snapToTexelCentre(uv, texelSize, texelsPerPixelOf(du, dv, texelSize)),
du * gradientScale, dv * gradientScale);
}
#ifdef USE_RGSS
#ifndef RGSS_MIP_BIAS
#define RGSS_MIP_BIAS -1.0
#endif
// Standard rotated-grid supersampling offsets, in texels.
const vec2 RGSS_OFFSETS[4] = vec2[4](
vec2( 0.125, 0.375),
@@ -54,53 +83,32 @@ const vec2 RGSS_OFFSETS[4] = vec2[4](
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) {
vec4 sampleRGSS(sampler2D tex, vec2 uv, vec2 texelSize) {
vec2 du = dFdx(uv);
vec2 dv = dFdy(uv);
vec2 duTexels = du * texSize;
vec2 dvTexels = dv * texSize;
vec2 snapped = snapToTexelCentre(uv, texelSize, texelsPerPixelOf(du, dv, texelSize));
vec2 du2 = duTexels * duTexels;
vec2 dv2 = dvTexels * dvTexels;
float gradientScale = exp2(v_MaterialMipBias + RGSS_MIP_BIAS);
vec2 gradU = du * gradientScale;
vec2 gradV = dv * gradientScale;
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;
}
vec4 color = vec4(0.0);
for (int i = 0; i < 4; i++) {
vec2 tap = RGSS_OFFSETS[i].x * du + RGSS_OFFSETS[i].y * dv;
color += textureGrad(tex, snapped + tap, gradU, gradV);
}
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);
return color * 0.25;
}
#endif
void main() {
vec2 texelSize = 1.0 / vec2(textureSize(u_BlockTex, 0));
#ifdef USE_RGSS
vec2 texSize = vec2(textureSize(u_BlockTex, 0));
vec4 diffuseColor = sampleRGSS(u_BlockTex, v_TexCoord, texSize, 1.0 / texSize);
vec4 diffuseColor = sampleRGSS(u_BlockTex, v_TexCoord, texelSize);
#else
vec4 diffuseColor = texture(u_BlockTex, v_TexCoord, v_MaterialMipBias);
vec4 diffuseColor = sampleTexelSnapped(u_BlockTex, v_TexCoord, texelSize);
#endif
#ifdef USE_FRAGMENT_DISCARD
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.FpsReducer;
import com.gtnewhorizons.angelica.rendering.FramePacer;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
@@ -17,6 +18,7 @@ import org.embeddedt.embeddium.impl.render.frame.RenderAheadManager;
import org.lwjgl.input.Keyboard;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@@ -113,4 +115,11 @@ public abstract class MixinMinecraft {
BatchingFontRenderer.endFrame();
ShaderManager.endFrame();
}
@ModifyExpressionValue(
method = "runGameLoop",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/settings/GameSettings;fancyGraphics:Z", opcode = Opcodes.GETFIELD))
private boolean angelica$applyGrassQuality(boolean fancyGraphics) {
return ClientProxy.options().quality.grassQuality.isFancy();
}
}
@@ -5,6 +5,7 @@ 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 com.gtnewhorizons.angelica.utils.SpritePadding;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import org.embeddedt.embeddium.api.util.ColorARGB;
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
@@ -14,6 +15,7 @@ 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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -34,8 +36,6 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
@Unique
private static final int SCAN_BLOCK = 256;
@Shadow
public boolean useAnisotropicFiltering;
@Shadow
public List<int[][]> framesTextureData;
@Shadow
@Final
@@ -52,6 +52,8 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
private boolean celeritas$hasTransparentTexels;
@Unique
private boolean celeritas$alwaysTranslucent;
@Unique
private int angelica$gutterWidth;
@Unique
private static boolean celeritas$isAlwaysTranslucent(String name) {
@@ -142,9 +144,50 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
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);
return MipmapGenerator.generateMipLevels(p_147949_0_, p_147949_1_, p_147949_2_, strategy,
this.celeritas$hasTransparentTexels);
}
/**
* Vanilla's anisotropic path inflates width/height by 16 and insets the UVs by 8, which is the number
* mods subtract to unpad. The gutter goes in the stitcher slot instead, so this always reads false and
* the sprite keeps its real dims.
*/
@ModifyVariable(method = "loadSprite", at = @At("HEAD"), argsOnly = true)
private boolean angelica$neverInflateSprite(boolean useAnisotropicFiltering) {
return false;
}
@ModifyVariable(method = "initSprite", at = @At("HEAD"), ordinal = 2, argsOnly = true)
private int angelica$insetOriginX(int x) {
return x + this.angelica$gutterWidth;
}
@ModifyVariable(method = "initSprite", at = @At("HEAD"), ordinal = 3, argsOnly = true)
private int angelica$insetOriginY(int y) {
return y + this.angelica$gutterWidth;
}
@Redirect(
method = "updateAnimation",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/texture/TextureUtil;uploadTextureMipmap([[IIIIIZZ)V"))
private void angelica$uploadPaddedFrame(int[][] frameData, int width, int height, int originX, int originY,
boolean blur, boolean clamp) {
SpritePadding.uploadPadded(frameData, width, height, originX, originY,
this.angelica$gutterWidth, blur, clamp);
}
@Override
public int angelica$getGutterWidth() {
return this.angelica$gutterWidth;
}
@Override
public void angelica$setGutterWidth(int gutter) {
this.angelica$gutterWidth = gutter;
}
@Override
@@ -0,0 +1,28 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.features.textures;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
/**
* Reserves each sprite's gutter in its stitcher slot.
*/
@Mixin(targets = "net.minecraft.client.renderer.texture.Stitcher$Holder")
public abstract class MixinStitcherHolder {
@Shadow
public abstract TextureAtlasSprite getAtlasSprite();
@ModifyReturnValue(method = "getWidth", at = @At("RETURN"))
private int angelica$reserveGutterWidth(int original) {
return original + 2 * ((SpriteExtension) getAtlasSprite()).angelica$getGutterWidth();
}
@ModifyReturnValue(method = "getHeight", at = @At("RETURN"))
private int angelica$reserveGutterHeight(int original) {
return original + 2 * ((SpriteExtension) getAtlasSprite()).angelica$getGutterWidth();
}
}
@@ -3,9 +3,10 @@ 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.google.common.collect.Lists;
import com.gtnewhorizons.angelica.utils.MipmapStrategies;
import com.gtnewhorizons.angelica.utils.MipmapStrategy;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.gtnewhorizons.angelica.utils.SpritePadding;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
@@ -16,19 +17,18 @@ 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.renderer.texture.TextureUtil;
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;
@@ -44,10 +44,16 @@ public class MixinTextureMap implements TextureMapExtension {
@Final
public Map<String, TextureAtlasSprite> mapUploadedSprites;
@Shadow
@Final
public Map<String, TextureAtlasSprite> mapRegisteredSprites;
@Shadow
public int mipmapLevels;
@Shadow
@Final
private int textureType;
@Shadow
@Final
private TextureAtlasSprite missingImage;
@Unique
private QuadTree<TextureAtlasSprite> celeritas$quadTree;
@Unique
@@ -55,13 +61,26 @@ public class MixinTextureMap implements TextureMapExtension {
@Unique
private int celeritas$height;
@Inject(method = "loadTextureAtlas", at = @At("HEAD"))
@Inject(method = { "loadTexture", "loadTextureAtlas" }, at = @At("HEAD"))
private void angelica$applyAtlasSettings(CallbackInfo ci) {
if ((Object) this == Minecraft.getMinecraft().getTextureMapBlocks()) {
SodiumGameOptions.applyAtlasSettings();
}
}
@Inject(
method = "loadTextureAtlas",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/Stitcher;doStitch()V"))
private void angelica$assignGutters(CallbackInfo ci) {
final boolean blocksAtlas = (Object) this == Minecraft.getMinecraft().getTextureMapBlocks();
final int gutter = SpritePadding.gutterFor(this.mipmapLevels, blocksAtlas);
for (TextureAtlasSprite sprite : this.mapRegisteredSprites.values()) {
((SpriteExtension) sprite).angelica$setGutterWidth(gutter);
}
((SpriteExtension) this.missingImage).angelica$setGutterWidth(gutter);
}
/**
* The block atlas re-gathers its icon list on every reload, so the previous attribution is stale.
*/
@@ -97,30 +116,32 @@ public class MixinTextureMap implements TextureMapExtension {
MipmapStrategies.recordSprite(this.textureType, p_94245_1_);
}
@ModifyArg(
/**
* Forces vanilla's unpadded branch.
*/
@Inject(method = "initMissingImage", at = @At("HEAD"), cancellable = true)
private void angelica$buildMissingImage(CallbackInfo ci) {
final int size = (int) Math.sqrt(TextureUtil.missingTextureData.length);
this.missingImage.useAnisotropicFiltering = false;
this.missingImage.setIconWidth(size);
this.missingImage.setIconHeight(size);
final int[][] mips = new int[this.mipmapLevels + 1][];
mips[0] = TextureUtil.missingTextureData;
this.missingImage.setFramesTextureData(Lists.newArrayList(new int[][][] { mips }));
ci.cancel();
}
@Redirect(
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);
target = "Lnet/minecraft/client/renderer/texture/TextureUtil;uploadTextureMipmap([[IIIIIZZ)V"))
private void angelica$uploadPaddedSprite(int[][] frameData, int width, int height, int originX, int originY,
boolean blur, boolean clamp, @Local(ordinal = 0) TextureAtlasSprite sprite) {
SpritePadding.uploadPadded(frameData, width, height, originX, originY,
((SpriteExtension) sprite).angelica$getGutterWidth(), blur, clamp);
}
/**
@@ -144,21 +165,12 @@ public class MixinTextureMap implements TextureMapExtension {
}
}
@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.mipmapLevels);
SodiumGameOptions.recordAtlasMipmapLevels(this.mipmapLevels);
}
this.celeritas$width = stitcher.getCurrentWidth();