Skip to content

Commit

Permalink
Implement 4x subsampling for alpha coverage calculation
Browse files Browse the repository at this point in the history
Very poorly optimized so far. But this improves the
rendering of some blocks significantly, especially the
sides of grass blocks.
  • Loading branch information
jellysquid3 committed Dec 17, 2024
1 parent f8c8bf4 commit cd8704d
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 471 deletions.
Original file line number Diff line number Diff line change
@@ -1,34 +1,144 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Original source: https://github.com/IrisShaders/Iris/blob/41095ac23ea0add664afd1b85c414d1f1ed94066/src/main/java/net/coderbot/iris/mixin/bettermipmaps/MixinTextureAtlasSprite.java
*/

package net.caffeinemc.mods.sodium.mixin.features.textures.mipmaps;

import com.mojang.blaze3d.platform.NativeImage;
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
import net.caffeinemc.mods.sodium.api.util.ColorU8;
import net.caffeinemc.mods.sodium.client.util.NativeImageHelper;
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.resources.metadata.animation.FrameSize;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceMetadata;
import org.lwjgl.system.MemoryUtil;
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.callback.CallbackInfo;

@Mixin(SpriteContents.class)
public class SpriteContentsMixin {
public abstract class SpriteContentsMixin {
@Unique
private static final int SUB_SAMPLE_COUNT = 4;

@Inject(method = "<init>", at = @At(value = "RETURN"))
private void sodium$beforeGenerateMipLevels(ResourceLocation name, FrameSize frameSize, NativeImage originalImage, ResourceMetadata metadata, CallbackInfo ci) {
sodium$fillInTransparentPixelColors(originalImage);
}

@Inject(method = "increaseMipLevel", at = @At("RETURN"))
private void modifyMipMaps(int mipLevels, CallbackInfo ci) {
float originalCoverage = this.calculateAlphaCoverage(this.originalImage, this.width, this.height, 0, 1.0f);

for (int frameIndex = 0; frameIndex < this.getFrameCount(); frameIndex++) {
for (int mipIndex = 1; mipIndex < mipLevels; mipIndex++) {
this.scaleAlphaToCoverage(
this.byMipLevel[mipIndex],
this.width >> mipLevels,
this.height >> mipLevels,
frameIndex,
originalCoverage);
}
}
}

@Unique
private void scaleAlphaToCoverage(NativeImage image, int frameWidth, int frameHeight, int frameIndex, float targetCoverage) {
float minAlphaScale = 0.0f;
float maxAlphaScale = 4.0f;
float alphaScale = 1.0f;

float bestAlphaScale = 1.0f;
float bestError = Float.MAX_VALUE;

for (int step = 0; step < 10; step++) {
final float coverage = this.calculateAlphaCoverage(image, frameWidth, frameHeight, frameIndex, alphaScale);
final float error = Math.abs(coverage - targetCoverage);

if (error < bestError) {
bestError = error;
bestAlphaScale = alphaScale;
}

if (coverage < targetCoverage) {
minAlphaScale = alphaScale;
} else if (coverage > targetCoverage) {
maxAlphaScale = alphaScale;
} else {
break;
}

alphaScale = (minAlphaScale + maxAlphaScale) * 0.5f;
}

scaleAlpha(image, bestAlphaScale);
}

@Unique
private float calculateAlphaCoverage(NativeImage image, int frameWidth, int frameHeight, int frameIndex, float alphaScale) {
int frameCount = image.getWidth() / frameWidth;
int frameX = ((frameIndex % frameCount) * frameWidth);
int frameY = ((frameIndex / frameCount) * frameHeight);

float coverage = 0.0f;

for (int y = frameY; y < (frameY + frameHeight) - 1; y++) {
for (int x = frameX; x < (frameX + frameWidth) - 1; x++) {
float alpha00 = alpha(image.getPixel(x + 0, y + 0)) * alphaScale;
float alpha10 = alpha(image.getPixel(x + 1, y + 0)) * alphaScale;
float alpha01 = alpha(image.getPixel(x + 0, y + 1)) * alphaScale;
float alpha11 = alpha(image.getPixel(x + 1, y + 1)) * alphaScale;

coverage += getTexelCoverage(alpha00, alpha10, alpha01, alpha11);
}
}

return coverage / ((frameWidth - 1) * (frameHeight - 1));
}

@Unique
private static float getTexelCoverage(float alpha00, float alpha10, float alpha01, float alpha11) {
float coverage = 0.0f;

for (int sY = 0; sY < SUB_SAMPLE_COUNT; sY++) {
for (int sX = 0; sX < SUB_SAMPLE_COUNT; sX++) {
float fY = (sY + 0.5f) / SUB_SAMPLE_COUNT;
float fX = (sX + 0.5f) / SUB_SAMPLE_COUNT;

float alpha = 0.0f;
alpha += alpha00 * (1 - fX) * (1 - fY);
alpha += alpha10 * ( fX) * (1 - fY);
alpha += alpha01 * (1 - fX) * ( fY);
alpha += alpha11 * ( fX) * ( fY);

if (alpha > 0.5f) {
coverage += 1.0f;
}
}
}

return coverage / (SUB_SAMPLE_COUNT * SUB_SAMPLE_COUNT);
}

@Unique
private static void scaleAlpha(NativeImage nativeImage, float scale) {
NativeImageHelper.forEachPixel(nativeImage, (ptr) -> {
int color = MemoryUtil.memGetInt(ptr);
int alpha = ColorABGR.unpackAlpha(color);

MemoryUtil.memPutInt(ptr,
ColorABGR.withAlpha(color, ColorU8.normalizedFloatToByte(ColorU8.byteToNormalizedFloat(alpha) * scale)));
});
}

@Unique
private static float alpha(int rgba) {
return ColorU8.byteToNormalizedFloat(ColorABGR.unpackAlpha(rgba));
}

/**
* Fixes a common issue in image editing programs where fully transparent pixels are saved with fully black colors.
*
Expand Down Expand Up @@ -94,4 +204,21 @@ public class SpriteContentsMixin {
}
}
}

@Shadow
@Final
private NativeImage originalImage;

@Shadow
@Final
int height;

@Shadow @Final
int width;

@Shadow
NativeImage[] byMipLevel;

@Shadow
protected abstract int getFrameCount();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package net.caffeinemc.mods.sodium.mixin.features.textures.scan;

import com.mojang.blaze3d.platform.NativeImage;
import net.caffeinemc.mods.sodium.api.util.ColorABGR;
import net.caffeinemc.mods.sodium.api.util.ColorU8;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.SpriteContentsExtension;
import net.caffeinemc.mods.sodium.client.util.NativeImageHelper;
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.resources.metadata.animation.FrameSize;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceMetadata;
import org.lwjgl.system.MemoryUtil;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
Expand Down Expand Up @@ -40,72 +37,6 @@ private void analyzePixels(ResourceLocation name, FrameSize frameSize, NativeIma
this.sodium$needsAlphaBlending |= analyzer.needsAlphaBlending();
}

@Inject(method = "increaseMipLevel", at = @At("RETURN"))
private void modifyMipMaps(int mipLevels, CallbackInfo ci) {
for (int frameIndex = 0; frameIndex < this.getFrameCount(); frameIndex++) {
float originalCoverage = this.calculateAlphaCoverage(this.originalImage, this.width, this.height, frameIndex, 1.0f);

for (int mipIndex = 1; mipIndex < mipLevels; mipIndex++) {
this.scaleAlphaToCoverage(
this.byMipLevel[mipIndex],
this.width >> mipLevels,
this.height >> mipLevels,
frameIndex,
originalCoverage);
}
}
}

@Unique
private void scaleAlphaToCoverage(NativeImage image, int frameWidth, int frameHeight, int frameIndex, float targetCoverage) {
float minAlphaScale = 0.0f;
float maxAlphaScale = 4.0f;
float alphaScale = 1.0f;

float bestAlphaScale = 1.0f;
float bestError = Float.MAX_VALUE;

for (int step = 0; step < 10; step++) {
final float coverage = this.calculateAlphaCoverage(image, frameWidth, frameHeight, frameIndex, alphaScale);
final float error = Math.abs(coverage - targetCoverage);

if (error < bestError) {
bestError = error;
bestAlphaScale = alphaScale;
}

if (coverage < targetCoverage) {
minAlphaScale = alphaScale;
} else if (coverage > targetCoverage) {
maxAlphaScale = alphaScale;
} else {
break;
}

alphaScale = (minAlphaScale + maxAlphaScale) * 0.5f;
}

scaleAlpha(image, bestAlphaScale);
}

@Unique
private float calculateAlphaCoverage(NativeImage image, int frameWidth, int frameHeight, int frameIndex, float alphaScale) {
var analyzer = new NativeImageHelper.AlphaCoverageAnalyzer(alphaScale);
NativeImageHelper.forEachPixelInFrame(image, frameWidth, frameHeight, frameIndex, analyzer);

return analyzer.alphaCoverage(frameWidth * frameHeight);
}

@Unique
private static void scaleAlpha(NativeImage nativeImage, float scale) {
NativeImageHelper.forEachPixel(nativeImage, (ptr) -> {
int color = MemoryUtil.memGetInt(ptr);
int alpha = ColorABGR.unpackAlpha(color);

MemoryUtil.memPutInt(ptr,
ColorABGR.withAlpha(color, ColorU8.normalizedFloatToByte(ColorU8.byteToNormalizedFloat(alpha) * scale)));
});
}

@Override
public boolean sodium$hasTransparentPixels() {
Expand All @@ -119,16 +50,4 @@ private static void scaleAlpha(NativeImage nativeImage, float scale) {

@Shadow @Final
private NativeImage originalImage;

@Shadow @Final
int height;

@Shadow @Final
int width;

@Shadow
NativeImage[] byMipLevel;

@Shadow
protected abstract int getFrameCount();
}
10 changes: 1 addition & 9 deletions thirdparty/NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,4 @@ The complete text for each software license can be found in the directory "third
* Original Source
* https://github.com/lodborg/interval-tree/tree/v1.0.0
* Files:
* common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree

* coderbot's mip-map generator patches for Minecraft
* Copyright (c) 2021 Coderbot
* License: Mozilla Public License (SPDX: MPL2)
* Original Source:
* https://github.com/IrisShaders/Iris/tree/41095ac23ea0add664afd1b85c414d1f1ed94066/src/main/java/net/coderbot/iris/mixin/bettermipmaps
* Files:
* common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps
* common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree
Loading

0 comments on commit cd8704d

Please sign in to comment.