mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-07 02:34:58 +01:00
Overwritten overwrites
- Add mixinextras and remove overwrite - Merge AsyncFontTexture and FontTexture Signed-off-by: Jozufozu <jozsefaug@gmail.com>
This commit is contained in:
parent
71cf582e97
commit
c1bf31856f
11 changed files with 185 additions and 370 deletions
|
@ -72,6 +72,8 @@ jarSets {
|
||||||
dependencies {
|
dependencies {
|
||||||
modCompileOnly("net.fabricmc:fabric-loader:${property("fabric_loader_version")}")
|
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")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,146 +1,59 @@
|
||||||
package dev.engine_room.flywheel.backend.mixin;
|
package dev.engine_room.flywheel.backend.mixin;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.function.IntFunction;
|
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.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
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;
|
import net.minecraft.client.gui.font.CodepointMap;
|
||||||
|
|
||||||
@Mixin(CodepointMap.class)
|
@Mixin(CodepointMap.class)
|
||||||
public class CodePointMapMixin<T> {
|
public class CodePointMapMixin<T> {
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
private T[][] blockMap;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
private T[] empty;
|
|
||||||
|
|
||||||
@Shadow
|
|
||||||
@Final
|
|
||||||
private IntFunction<T[]> blockConstructor;
|
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private final Object flywheel$lock = new Object();
|
private final Object flywheel$lock = new Object();
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "clear")
|
||||||
* @author
|
private void flywheel$wrapClearAsSynchronized(Operation<Void> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Overwrite
|
|
||||||
public void clear() {
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
Arrays.fill(this.blockMap, this.empty);
|
original.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "get")
|
||||||
* @author
|
private T flywheel$wrapGetAsSynchronized(int index, Operation<T> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
@Overwrite
|
|
||||||
public T get(int index) {
|
|
||||||
int i = index >> 8;
|
|
||||||
int j = index & 0xFF;
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
return this.blockMap[i][j];
|
return original.call(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "put")
|
||||||
* @author
|
private T flywheel$wrapPutAsSynchronized(int index, T value, Operation<T> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
@Overwrite
|
|
||||||
public T put(int index, T value) {
|
|
||||||
int i = index >> 8;
|
|
||||||
int j = index & 0xFF;
|
|
||||||
T object;
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
T[] objects = this.blockMap[i];
|
return original.call(index, value);
|
||||||
if (objects == this.empty) {
|
|
||||||
objects = this.blockConstructor.apply(256);
|
|
||||||
this.blockMap[i] = objects;
|
|
||||||
objects[j] = value;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
object = objects[j];
|
|
||||||
objects[j] = value;
|
|
||||||
}
|
}
|
||||||
return object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "computeIfAbsent")
|
||||||
* @author
|
private T flywheel$wrapComputeIfAbsentAsSynchronized(int index, IntFunction<T> valueIfAbsentGetter, Operation<T> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Overwrite
|
|
||||||
public T computeIfAbsent(int index, IntFunction<T> valueIfAbsentGetter) {
|
|
||||||
int i = index >> 8;
|
|
||||||
int j = index & 0xFF;
|
|
||||||
T out;
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
T[] objects = this.blockMap[i];
|
return original.call(index, valueIfAbsentGetter);
|
||||||
T object = objects[j];
|
|
||||||
if (object != null) {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
if (objects == this.empty) {
|
|
||||||
objects = this.blockConstructor.apply(256);
|
|
||||||
this.blockMap[i] = objects;
|
|
||||||
}
|
|
||||||
out = valueIfAbsentGetter.apply(index);
|
|
||||||
objects[j] = out;
|
|
||||||
}
|
}
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "remove")
|
||||||
* @author
|
private T flywheel$wrapRemoveAsSynchronized(int index, Operation<T> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
@Overwrite
|
|
||||||
public T remove(int index) {
|
|
||||||
int i = index >> 8;
|
|
||||||
int j = index & 0xFF;
|
|
||||||
T object;
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
T[] objects = this.blockMap[i];
|
return original.call(index);
|
||||||
if (objects == this.empty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
object = objects[j];
|
|
||||||
objects[j] = null;
|
|
||||||
}
|
}
|
||||||
return object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@WrapMethod(method = "forEach")
|
||||||
* @author
|
private void flywheel$wrapForEachAsSynchronized(CodepointMap.Output<T> output, Operation<Void> original) {
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Overwrite
|
|
||||||
public void forEach(CodepointMap.Output<T> output) {
|
|
||||||
synchronized (flywheel$lock) {
|
synchronized (flywheel$lock) {
|
||||||
for (int i = 0; i < this.blockMap.length; ++i) {
|
original.call(output);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,121 +1,31 @@
|
||||||
package dev.engine_room.flywheel.backend.mixin;
|
package dev.engine_room.flywheel.backend.mixin;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Overwrite;
|
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
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.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||||
import com.mojang.blaze3d.font.GlyphInfo;
|
import com.llamalad7.mixinextras.sugar.Local;
|
||||||
import com.mojang.blaze3d.font.GlyphProvider;
|
|
||||||
import com.mojang.blaze3d.font.SheetGlyphInfo;
|
|
||||||
|
|
||||||
import dev.engine_room.flywheel.backend.font.AsyncFontTexture;
|
import dev.engine_room.flywheel.lib.internal.FontTextureExtension;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
|
||||||
import net.minecraft.client.gui.font.FontSet;
|
import net.minecraft.client.gui.font.FontSet;
|
||||||
import net.minecraft.client.gui.font.GlyphRenderTypes;
|
import net.minecraft.client.gui.font.FontTexture;
|
||||||
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
|
|
||||||
import net.minecraft.client.renderer.texture.TextureManager;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.util.Mth;
|
|
||||||
import net.minecraft.util.RandomSource;
|
import net.minecraft.util.RandomSource;
|
||||||
|
|
||||||
@Mixin(FontSet.class)
|
@Mixin(FontSet.class)
|
||||||
public abstract class FontSetMixin {
|
public abstract class FontSetMixin {
|
||||||
|
// Replace serial random with thread-local random
|
||||||
@Shadow
|
@Shadow
|
||||||
@Final
|
@Final
|
||||||
private TextureManager textureManager;
|
private static RandomSource RANDOM = RandomSource.createNewThreadLocalInstance();
|
||||||
|
|
||||||
@Shadow
|
@ModifyExpressionValue(method = "stitch", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/FontTexture"))
|
||||||
private BakedGlyph missingGlyph;
|
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.
|
||||||
@Shadow
|
// We need to know that to determine which Material to use when actually setting up instances.
|
||||||
@Final
|
((FontTextureExtension) original).flywheel$setName(name);
|
||||||
private ResourceLocation name;
|
return original;
|
||||||
|
|
||||||
@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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
"AbstractClientPlayerAccessor",
|
"AbstractClientPlayerAccessor",
|
||||||
"CodePointMapMixin",
|
"CodePointMapMixin",
|
||||||
"FontSetMixin",
|
"FontSetMixin",
|
||||||
|
"FontTexture$NodeAccessor",
|
||||||
|
"FontTextureMixin",
|
||||||
"GlStateManagerMixin",
|
"GlStateManagerMixin",
|
||||||
"LevelRendererAccessor",
|
"LevelRendererAccessor",
|
||||||
"OptionsMixin",
|
"OptionsMixin",
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.engine_room.flywheel.lib.internal;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
public interface FontTextureExtension {
|
||||||
|
void flywheel$setName(ResourceLocation value);
|
||||||
|
}
|
|
@ -87,6 +87,9 @@ dependencies {
|
||||||
modCompileOnly("maven.modrinth:embeddium:${property("embeddium_version")}")
|
modCompileOnly("maven.modrinth:embeddium:${property("embeddium_version")}")
|
||||||
modCompileOnly("maven.modrinth:oculus:${property("oculus_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"))
|
"forApi"(project(path = ":common", configuration = "commonApiOnly"))
|
||||||
"forLib"(project(path = ":common", configuration = "commonLib"))
|
"forLib"(project(path = ":common", configuration = "commonLib"))
|
||||||
"forBackend"(project(path = ":common", configuration = "commonBackend"))
|
"forBackend"(project(path = ":common", configuration = "commonBackend"))
|
||||||
|
|
|
@ -26,7 +26,7 @@ parchment_version = 2023.09.03
|
||||||
# Minecraft build dependency versions
|
# Minecraft build dependency versions
|
||||||
minecraft_version = 1.20.1
|
minecraft_version = 1.20.1
|
||||||
forge_version = 47.2.19
|
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
|
fabric_api_version = 0.92.1+1.20.1
|
||||||
|
|
||||||
# Build dependency mod versions
|
# Build dependency mod versions
|
||||||
|
|
Loading…
Reference in a new issue