Overwritten overwrites

- Add mixinextras and remove overwrite
- Merge AsyncFontTexture and FontTexture

Signed-off-by: Jozufozu <jozsefaug@gmail.com>
This commit is contained in:
IThundxr 2024-09-25 19:36:32 -07:00 committed by Jozufozu
parent 71cf582e97
commit c1bf31856f
11 changed files with 185 additions and 370 deletions

View file

@ -72,6 +72,8 @@ jarSets {
dependencies {
modCompileOnly("net.fabricmc:fabric-loader:${property("fabric_loader_version")}")
compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")!!)
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
}

View file

@ -1,159 +0,0 @@
package dev.engine_room.flywheel.backend.font;
import java.nio.file.Path;
import java.util.List;
import org.apache.commons.compress.utils.Lists;
import org.jetbrains.annotations.Nullable;
import com.mojang.blaze3d.font.SheetGlyphInfo;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.platform.TextureUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.lib.internal.GlyphExtension;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.font.GlyphRenderTypes;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.renderer.texture.Dumpable;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
public class AsyncFontTexture extends AbstractTexture implements Dumpable {
private static final int SIZE = 256;
private final GlyphRenderTypes renderTypes;
private final ResourceLocation name;
private final boolean colored;
private final Node root;
private final List<Upload> uploads = Lists.newArrayList();
private boolean flushScheduled = false;
public AsyncFontTexture(ResourceLocation name, GlyphRenderTypes renderTypes, boolean colored) {
this.name = name;
this.colored = colored;
this.root = new Node(0, 0, 256, 256);
this.renderTypes = renderTypes;
if (RenderSystem.isOnRenderThreadOrInit()) {
this.init();
} else {
RenderSystem.recordRenderCall(this::init);
}
}
@Override
public void load(ResourceManager resourceManager) {
}
@Override
public void close() {
this.releaseId();
}
@Nullable
public BakedGlyph add(SheetGlyphInfo glyphInfo) {
if (glyphInfo.isColored() != this.colored) {
return null;
}
Node node = this.root.insert(glyphInfo);
if (node != null) {
if (RenderSystem.isOnRenderThreadOrInit()) {
this.bind();
glyphInfo.upload(node.x, node.y);
} else {
uploads.add(new Upload(glyphInfo, node.x, node.y));
if (!flushScheduled) {
RenderSystem.recordRenderCall(this::flush);
flushScheduled = true;
}
}
var out = new BakedGlyph(this.renderTypes, ((float) node.x + 0.01f) / 256.0f, ((float) node.x - 0.01f + (float) glyphInfo.getPixelWidth()) / 256.0f, ((float) node.y + 0.01f) / 256.0f, ((float) node.y - 0.01f + (float) glyphInfo.getPixelHeight()) / 256.0f, glyphInfo.getLeft(), glyphInfo.getRight(), glyphInfo.getUp(), glyphInfo.getDown());
((GlyphExtension) out).flywheel$texture(name);
return out;
}
return null;
}
@Override
public void dumpContents(ResourceLocation resourceLocation, Path path) {
String string = resourceLocation.toDebugFileName();
TextureUtil.writeAsPNG(path, string, this.getId(), 0, 256, 256, i -> (i & 0xFF000000) == 0 ? -16777216 : i);
}
public void init() {
TextureUtil.prepareImage(colored ? NativeImage.InternalGlFormat.RGBA : NativeImage.InternalGlFormat.RED, this.getId(), 256, 256);
}
public void flush() {
this.bind();
for (Upload upload : this.uploads) {
upload.info.upload(upload.x, upload.y);
}
uploads.clear();
flushScheduled = false;
}
public record Upload(SheetGlyphInfo info, int x, int y) {
}
@Environment(value = EnvType.CLIENT)
static class Node {
final int x;
final int y;
private final int width;
private final int height;
@Nullable
private Node left;
@Nullable
private Node right;
private boolean occupied;
Node(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Nullable Node insert(SheetGlyphInfo glyphInfo) {
if (this.left != null && this.right != null) {
Node node = this.left.insert(glyphInfo);
if (node == null) {
node = this.right.insert(glyphInfo);
}
return node;
}
if (this.occupied) {
return null;
}
int i = glyphInfo.getPixelWidth();
int j = glyphInfo.getPixelHeight();
if (i > this.width || j > this.height) {
return null;
}
if (i == this.width && j == this.height) {
this.occupied = true;
return this;
}
int k = this.width - i;
int l = this.height - j;
if (k > l) {
this.left = new Node(this.x, this.y, i, this.height);
this.right = new Node(this.x + i + 1, this.y, this.width - i - 1, this.height);
} else {
this.left = new Node(this.x, this.y, this.width, j);
this.right = new Node(this.x, this.y + j + 1, this.width, this.height - j - 1);
}
return this.left.insert(glyphInfo);
}
}
}

View file

@ -1,146 +1,59 @@
package dev.engine_room.flywheel.backend.mixin;
import java.util.Arrays;
import java.util.function.IntFunction;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.client.gui.font.CodepointMap;
@Mixin(CodepointMap.class)
public class CodePointMapMixin<T> {
@Shadow
@Final
private T[][] blockMap;
@Shadow
@Final
private T[] empty;
@Shadow
@Final
private IntFunction<T[]> blockConstructor;
@Unique
private final Object flywheel$lock = new Object();
/**
* @author
* @reason
*/
@Overwrite
public void clear() {
@WrapMethod(method = "clear")
private void flywheel$wrapClearAsSynchronized(Operation<Void> original) {
synchronized (flywheel$lock) {
Arrays.fill(this.blockMap, this.empty);
original.call();
}
}
/**
* @author
* @reason
*/
@Nullable
@Overwrite
public T get(int index) {
int i = index >> 8;
int j = index & 0xFF;
@WrapMethod(method = "get")
private T flywheel$wrapGetAsSynchronized(int index, Operation<T> original) {
synchronized (flywheel$lock) {
return this.blockMap[i][j];
return original.call(index);
}
}
/**
* @author
* @reason
*/
@Nullable
@Overwrite
public T put(int index, T value) {
int i = index >> 8;
int j = index & 0xFF;
T object;
@WrapMethod(method = "put")
private T flywheel$wrapPutAsSynchronized(int index, T value, Operation<T> original) {
synchronized (flywheel$lock) {
T[] objects = this.blockMap[i];
if (objects == this.empty) {
objects = this.blockConstructor.apply(256);
this.blockMap[i] = objects;
objects[j] = value;
return null;
return original.call(index, value);
}
object = objects[j];
objects[j] = value;
}
return object;
}
/**
* @author
* @reason
*/
@Overwrite
public T computeIfAbsent(int index, IntFunction<T> valueIfAbsentGetter) {
int i = index >> 8;
int j = index & 0xFF;
T out;
@WrapMethod(method = "computeIfAbsent")
private T flywheel$wrapComputeIfAbsentAsSynchronized(int index, IntFunction<T> valueIfAbsentGetter, Operation<T> original) {
synchronized (flywheel$lock) {
T[] objects = this.blockMap[i];
T object = objects[j];
if (object != null) {
return object;
return original.call(index, valueIfAbsentGetter);
}
if (objects == this.empty) {
objects = this.blockConstructor.apply(256);
this.blockMap[i] = objects;
}
out = valueIfAbsentGetter.apply(index);
objects[j] = out;
}
return out;
}
/**
* @author
* @reason
*/
@Nullable
@Overwrite
public T remove(int index) {
int i = index >> 8;
int j = index & 0xFF;
T object;
@WrapMethod(method = "remove")
private T flywheel$wrapRemoveAsSynchronized(int index, Operation<T> original) {
synchronized (flywheel$lock) {
T[] objects = this.blockMap[i];
if (objects == this.empty) {
return null;
return original.call(index);
}
object = objects[j];
objects[j] = null;
}
return object;
}
/**
* @author
* @reason
*/
@Overwrite
public void forEach(CodepointMap.Output<T> output) {
@WrapMethod(method = "forEach")
private void flywheel$wrapForEachAsSynchronized(CodepointMap.Output<T> output, Operation<Void> original) {
synchronized (flywheel$lock) {
for (int i = 0; i < this.blockMap.length; ++i) {
T[] objects = this.blockMap[i];
if (objects == this.empty) continue;
for (int j = 0; j < objects.length; ++j) {
T object = objects[j];
if (object == null) continue;
int k = i << 8 | j;
output.accept(k, object);
}
}
original.call(output);
}
}
}

View file

@ -1,121 +1,31 @@
package dev.engine_room.flywheel.backend.mixin;
import java.util.List;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.font.GlyphInfo;
import com.mojang.blaze3d.font.GlyphProvider;
import com.mojang.blaze3d.font.SheetGlyphInfo;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import dev.engine_room.flywheel.backend.font.AsyncFontTexture;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntList;
import dev.engine_room.flywheel.lib.internal.FontTextureExtension;
import net.minecraft.client.gui.font.FontSet;
import net.minecraft.client.gui.font.GlyphRenderTypes;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.gui.font.FontTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
@Mixin(FontSet.class)
public abstract class FontSetMixin {
// Replace serial random with thread-local random
@Shadow
@Final
private TextureManager textureManager;
private static RandomSource RANDOM = RandomSource.createNewThreadLocalInstance();
@Shadow
private BakedGlyph missingGlyph;
@Shadow
@Final
private ResourceLocation name;
@Shadow
@Final
private Int2ObjectMap<IntList> glyphsByWidth;
@Shadow
public abstract BakedGlyph getGlyph(int character);
@Unique
private static final RandomSource RANDOM = RandomSource.createNewThreadLocalInstance();
@Unique
private List<AsyncFontTexture> flywheel$textures;
@Inject(method = "<init>", at = @At("TAIL"))
public void init(TextureManager textureManager, ResourceLocation name, CallbackInfo ci) {
flywheel$textures = Lists.newArrayList();
@ModifyExpressionValue(method = "stitch", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/FontTexture"))
private FontTexture flywheel$setNameAfterCreate(FontTexture original, @Local ResourceLocation name) {
// Forward the name to the FontTexture so we can forward the name to the BakedGlyphs it creates.
// We need to know that to determine which Material to use when actually setting up instances.
((FontTextureExtension) original).flywheel$setName(name);
return original;
}
@Inject(method = "reload", at = @At("TAIL"))
public void reload(List<GlyphProvider> glyphProviders, CallbackInfo ci) {
flywheel$closeTextures();
}
/**
* @author Jozufozu
* @reason Use thread safe random
*/
@Overwrite
public BakedGlyph getRandomGlyph(GlyphInfo glyph) {
IntList intList = this.glyphsByWidth.get(Mth.ceil(glyph.getAdvance(false)));
if (intList != null && !intList.isEmpty()) {
// Override to use thread safe random
// FIXME: can we just replace the static field instead?
return this.getGlyph(intList.getInt(RANDOM.nextInt(intList.size())));
}
return this.missingGlyph;
}
/**
* @author Jozufozu
* @reason Use our stitching
*/
@Overwrite
private BakedGlyph stitch(SheetGlyphInfo glyphInfo) {
for (AsyncFontTexture fontTexture : flywheel$textures) {
BakedGlyph bakedGlyph = fontTexture.add(glyphInfo);
if (bakedGlyph == null) continue;
return bakedGlyph;
}
ResourceLocation resourceLocation = this.name.withSuffix("/" + flywheel$textures.size());
boolean bl = glyphInfo.isColored();
GlyphRenderTypes glyphRenderTypes = bl ? GlyphRenderTypes.createForColorTexture(resourceLocation) : GlyphRenderTypes.createForIntensityTexture(resourceLocation);
AsyncFontTexture fontTexture2 = new AsyncFontTexture(resourceLocation, glyphRenderTypes, bl);
flywheel$textures.add(fontTexture2);
BakedGlyph bakedGlyph2 = fontTexture2.add(glyphInfo);
this.textureManager.register(resourceLocation, fontTexture2);
return bakedGlyph2 == null ? this.missingGlyph : bakedGlyph2;
}
@Inject(method = "close", at = @At("TAIL"))
private void flywheel$close(CallbackInfo ci) {
flywheel$closeTextures();
}
@Unique
private void flywheel$closeTextures() {
for (AsyncFontTexture texture : flywheel$textures) {
texture.close();
}
flywheel$textures.clear();
}
}

View file

@ -0,0 +1,13 @@
package dev.engine_room.flywheel.backend.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(targets = "net.minecraft.client.gui.font.FontTexture$Node")
public interface FontTexture$NodeAccessor {
@Accessor("x")
int flywheel$getX();
@Accessor("y")
int flywheel$getY();
}

View file

@ -0,0 +1,114 @@
package dev.engine_room.flywheel.backend.mixin;
import java.util.ArrayList;
import java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import com.mojang.blaze3d.font.SheetGlyphInfo;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.backend.util.FontTextureUpload;
import dev.engine_room.flywheel.lib.internal.FontTextureExtension;
import dev.engine_room.flywheel.lib.internal.GlyphExtension;
import net.minecraft.client.gui.font.FontTexture;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.resources.ResourceLocation;
@Mixin(FontTexture.class)
public abstract class FontTextureMixin extends AbstractTexture implements FontTextureExtension {
@Unique
private final List<FontTextureUpload> flywheel$uploads = new ArrayList<>();
@Unique
private boolean flywheel$flushScheduled = false;
@Unique
private ResourceLocation flywheel$name;
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/FontTexture;getId()I"))
private int flywheel$skipGetId(FontTexture instance, Operation<Integer> original) {
// getId lazily creates the texture id, which is good,
// but it doesn't check for the render thread, which explodes.
if (RenderSystem.isOnRenderThreadOrInit()) {
return original.call(instance);
}
// We'll call getId manually in the recorded render call below.
return 0;
}
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/TextureUtil;prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;III)V"))
private void flywheel$skipPrepareImage(NativeImage.InternalGlFormat arg, int i, int j, int k, Operation<Void> original) {
if (RenderSystem.isOnRenderThreadOrInit()) {
original.call(arg, i, j, k);
} else {
RenderSystem.recordRenderCall(() -> original.call(arg, getId(), j, k));
}
}
@WrapWithCondition(method = "add", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/FontTexture;bind()V"))
private boolean flywheel$onlyOnRenderThreadOrInitBindAndUpload(FontTexture instance) {
return RenderSystem.isOnRenderThreadOrInit();
}
@WrapWithCondition(method = "add", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/font/SheetGlyphInfo;upload(II)V"))
private boolean flywheel$onlyOnRenderThreadOrInitBindAndUpload2(SheetGlyphInfo instance, int x, int y) {
return RenderSystem.isOnRenderThreadOrInit();
}
@WrapOperation(method = "add", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/font/FontTexture$Node;x:I", ordinal = 0))
private int flywheel$shareNode(@Coerce Object instance, Operation<Integer> original, @Share("node") LocalRef<Object> node) {
node.set(instance);
return original.call(instance);
}
@Inject(method = "add", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/font/SheetGlyphInfo;upload(II)V", shift = At.Shift.AFTER))
private void flywheel$uploadOrFlush(SheetGlyphInfo glyphInfo, CallbackInfoReturnable<BakedGlyph> cir, @Share("node") LocalRef<Object> node) {
FontTexture$NodeAccessor accessor = ((FontTexture$NodeAccessor) node.get());
// Shove all the uploads into a list to be processed as a batch.
// Saves a lot of lambda allocations that would be spent binding the same texture over and over.
flywheel$uploads.add(new FontTextureUpload(glyphInfo, accessor.flywheel$getX(), accessor.flywheel$getY()));
if (!flywheel$flushScheduled) {
RenderSystem.recordRenderCall(this::flywheel$flush);
flywheel$flushScheduled = true;
}
}
@ModifyExpressionValue(method = "add", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/glyphs/BakedGlyph"))
private BakedGlyph flywheel$setGlyphExtensionName(BakedGlyph original) {
((GlyphExtension) original).flywheel$texture(flywheel$name);
return original;
}
@Unique
public void flywheel$flush() {
this.bind();
for (FontTextureUpload upload : flywheel$uploads) {
upload.info()
.upload(upload.x(), upload.y());
}
flywheel$uploads.clear();
flywheel$flushScheduled = false;
}
@Override
public void flywheel$setName(ResourceLocation value) {
flywheel$name = value;
}
}

View file

@ -0,0 +1,10 @@
package dev.engine_room.flywheel.backend.util;
import com.mojang.blaze3d.font.SheetGlyphInfo;
/**
* For use in {@link dev.engine_room.flywheel.backend.mixin.FontTextureMixin}
* to batch glyph uploads when they're created in a flywheel worker thread.
*/
public record FontTextureUpload(SheetGlyphInfo info, int x, int y) {
}

View file

@ -8,6 +8,8 @@
"AbstractClientPlayerAccessor",
"CodePointMapMixin",
"FontSetMixin",
"FontTexture$NodeAccessor",
"FontTextureMixin",
"GlStateManagerMixin",
"LevelRendererAccessor",
"OptionsMixin",

View file

@ -0,0 +1,7 @@
package dev.engine_room.flywheel.lib.internal;
import net.minecraft.resources.ResourceLocation;
public interface FontTextureExtension {
void flywheel$setName(ResourceLocation value);
}

View file

@ -87,6 +87,9 @@ dependencies {
modCompileOnly("maven.modrinth:embeddium:${property("embeddium_version")}")
modCompileOnly("maven.modrinth:oculus:${property("oculus_version")}")
compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")!!)
implementation(include("io.github.llamalad7:mixinextras-forge:0.4.1")!!)
"forApi"(project(path = ":common", configuration = "commonApiOnly"))
"forLib"(project(path = ":common", configuration = "commonLib"))
"forBackend"(project(path = ":common", configuration = "commonBackend"))

View file

@ -26,7 +26,7 @@ parchment_version = 2023.09.03
# Minecraft build dependency versions
minecraft_version = 1.20.1
forge_version = 47.2.19
fabric_loader_version = 0.15.9
fabric_loader_version=0.16.5
fabric_api_version = 0.92.1+1.20.1
# Build dependency mod versions