mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-26 14:24:30 +00:00
Improve upon Texture Filtering Work (#2096)
This commit is contained in:
1 parent
75639d3b73
commit
26c161f0e8
26 files changed
+595
-261
No files matched your search
@@ -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) {
|
||||
|
||||
+36
-1
@@ -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"
|
||||
|
||||
+15
-6
@@ -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);
|
||||
|
||||
|
||||
+83
-27
@@ -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
|
||||
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
+1
-13
@@ -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 << 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;
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
+47
-4
@@ -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
|
||||
|
||||
+28
@@ -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();
|
||||
}
|
||||
}
|
||||
+49
-37
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user