diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 3a84e3a13..338a63991 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -2,6 +2,8 @@ package dev.engine_room.flywheel.backend.engine; import java.util.List; +import com.mojang.blaze3d.systems.RenderSystem; + import dev.engine_room.flywheel.api.RenderContext; import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.instance.Instance; @@ -90,6 +92,7 @@ public class EngineImpl implements Engine { @Override public void setupRender(RenderContext context) { try (var state = GlStateTracker.getRestoreState()) { + RenderSystem.replayQueue(); Uniforms.update(context); environmentStorage.flush(); drawManager.flush(lightStorage, environmentStorage); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java b/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java new file mode 100644 index 000000000..4189aa1d7 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java @@ -0,0 +1,159 @@ +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 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); + } + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java new file mode 100644 index 000000000..27159cd61 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java @@ -0,0 +1,146 @@ +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 net.minecraft.client.gui.font.CodepointMap; + +@Mixin(CodepointMap.class) +public class CodePointMapMixin { + @Shadow + @Final + private T[][] blockMap; + + @Shadow + @Final + private T[] empty; + + @Shadow + @Final + private IntFunction blockConstructor; + + @Unique + private final Object flywheel$lock = new Object(); + + /** + * @author + * @reason + */ + @Overwrite + public void clear() { + synchronized (flywheel$lock) { + Arrays.fill(this.blockMap, this.empty); + } + } + + /** + * @author + * @reason + */ + @Nullable + @Overwrite + public T get(int index) { + int i = index >> 8; + int j = index & 0xFF; + synchronized (flywheel$lock) { + return this.blockMap[i][j]; + } + } + + /** + * @author + * @reason + */ + @Nullable + @Overwrite + public T put(int index, T value) { + int i = index >> 8; + int j = index & 0xFF; + T object; + 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; + } + object = objects[j]; + objects[j] = value; + } + return object; + } + + /** + * @author + * @reason + */ + @Overwrite + public T computeIfAbsent(int index, IntFunction valueIfAbsentGetter) { + int i = index >> 8; + int j = index & 0xFF; + T out; + synchronized (flywheel$lock) { + T[] objects = this.blockMap[i]; + 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; + } + + /** + * @author + * @reason + */ + @Nullable + @Overwrite + public T remove(int index) { + int i = index >> 8; + int j = index & 0xFF; + T object; + synchronized (flywheel$lock) { + T[] objects = this.blockMap[i]; + if (objects == this.empty) { + return null; + } + object = objects[j]; + objects[j] = null; + } + return object; + } + + /** + * @author + * @reason + */ + @Overwrite + public void forEach(CodepointMap.Output output) { + 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); + } + } + } + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java new file mode 100644 index 000000000..28a7cf5f7 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java @@ -0,0 +1,121 @@ +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 dev.engine_room.flywheel.backend.font.AsyncFontTexture; +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.GlyphRenderTypes; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; + +@Mixin(FontSet.class) +public abstract class FontSetMixin { + @Shadow + @Final + private TextureManager textureManager; + + @Shadow + private BakedGlyph missingGlyph; + + @Shadow + @Final + private ResourceLocation name; + + @Shadow + @Final + private Int2ObjectMap glyphsByWidth; + + @Shadow + public abstract BakedGlyph getGlyph(int character); + + @Unique + private static final RandomSource RANDOM = RandomSource.createNewThreadLocalInstance(); + + @Unique + private List flywheel$textures; + + + @Inject(method = "", 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 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(); + } + +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert index d1e751929..019df0083 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert @@ -12,3 +12,5 @@ out vec3 flw_vertexNormal; out float flw_distance; FlwMaterial flw_material; + +#define flw_vertexId gl_VertexID diff --git a/common/src/backend/resources/flywheel.backend.mixins.json b/common/src/backend/resources/flywheel.backend.mixins.json index 2d924bef0..da4f3cfe7 100644 --- a/common/src/backend/resources/flywheel.backend.mixins.json +++ b/common/src/backend/resources/flywheel.backend.mixins.json @@ -6,6 +6,8 @@ "refmap": "backend-flywheel.refmap.json", "client": [ "AbstractClientPlayerAccessor", + "CodePointMapMixin", + "FontSetMixin", "GlStateManagerMixin", "LevelRendererAccessor", "OptionsMixin", diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java new file mode 100644 index 000000000..902ece1a1 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/GlyphInstance.java @@ -0,0 +1,100 @@ +package dev.engine_room.flywheel.lib.instance; + +import org.joml.Matrix4f; + +import dev.engine_room.flywheel.api.instance.InstanceHandle; +import dev.engine_room.flywheel.api.instance.InstanceType; +import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.util.FastColor; + +public class GlyphInstance extends AbstractInstance { + public final Matrix4f pose = new Matrix4f(); + + public float u0; + public float u1; + public float v0; + public float v1; + + public float x0; + public float x1; + public float x2; + public float x3; + + public float y0; + public float y1; + + public byte red = (byte) 0xFF; + public byte green = (byte) 0xFF; + public byte blue = (byte) 0xFF; + public byte alpha = (byte) 0xFF; + + public int light = 0; + + public GlyphInstance(InstanceType type, InstanceHandle handle) { + super(type, handle); + } + + public GlyphInstance setGlyph(BakedGlyph glyph, float x, float y, boolean italic) { + var glyphReader = FlwLibLink.INSTANCE.getGlyphExtension(glyph); + + u0 = glyphReader.flywheel$u0(); + u1 = glyphReader.flywheel$u1(); + v0 = glyphReader.flywheel$v0(); + v1 = glyphReader.flywheel$v1(); + float left = glyphReader.flywheel$left(); + float right = glyphReader.flywheel$right(); + float up = glyphReader.flywheel$up(); + float down = glyphReader.flywheel$down(); + + float f = x + left; + float g = x + right; + float h = up - 3.0f; + float j = down - 3.0f; + float k = y + h; + float l = y + j; + float m = italic ? 1.0f - 0.25f * h : 0.0f; + float n = italic ? 1.0f - 0.25f * j : 0.0f; + + y0 = k; + y1 = l; + + x0 = f + m; + x1 = f + n; + x2 = g + n; + x3 = g + m; + + return this; + } + + public GlyphInstance colorArgb(int argb) { + return color(FastColor.ARGB32.red(argb), FastColor.ARGB32.green(argb), FastColor.ARGB32.blue(argb), FastColor.ARGB32.alpha(argb)); + } + + public GlyphInstance colorRgb(int rgb) { + return color(FastColor.ARGB32.red(rgb), FastColor.ARGB32.green(rgb), FastColor.ARGB32.blue(rgb)); + } + + public GlyphInstance color(int red, int green, int blue, int alpha) { + return color((byte) red, (byte) green, (byte) blue, (byte) alpha); + } + + public GlyphInstance color(int red, int green, int blue) { + return color((byte) red, (byte) green, (byte) blue); + } + + public GlyphInstance color(byte red, byte green, byte blue, byte alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + return this; + } + + public GlyphInstance color(byte red, byte green, byte blue) { + this.red = red; + this.green = green; + this.blue = blue; + return this; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java index da093b634..0d271fcce 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java @@ -103,6 +103,37 @@ public final class InstanceTypes { .cullShader(Flywheel.rl("instance/cull/shadow.glsl")) .build(); + public static final InstanceType GLYPH = SimpleInstanceType.builder(GlyphInstance::new) + .layout(LayoutBuilder.create() + .matrix("pose", FloatRepr.FLOAT, 4) + .vector("u0u1v0v1", FloatRepr.FLOAT, 4) + .vector("x", FloatRepr.FLOAT, 4) + .vector("y", FloatRepr.FLOAT, 2) + .vector("color", FloatRepr.UNSIGNED_BYTE, 4) + .vector("light", FloatRepr.UNSIGNED_SHORT, 2) + .build()) + .writer((ptr, instance) -> { + ExtraMemoryOps.putMatrix4f(ptr, instance.pose); + MemoryUtil.memPutFloat(ptr + 64, instance.u0); + MemoryUtil.memPutFloat(ptr + 68, instance.u1); + MemoryUtil.memPutFloat(ptr + 72, instance.v0); + MemoryUtil.memPutFloat(ptr + 76, instance.v1); + MemoryUtil.memPutFloat(ptr + 80, instance.x0); + MemoryUtil.memPutFloat(ptr + 84, instance.x1); + MemoryUtil.memPutFloat(ptr + 88, instance.x2); + MemoryUtil.memPutFloat(ptr + 92, instance.x3); + MemoryUtil.memPutFloat(ptr + 96, instance.y0); + MemoryUtil.memPutFloat(ptr + 100, instance.y1); + MemoryUtil.memPutByte(ptr + 104, instance.red); + MemoryUtil.memPutByte(ptr + 105, instance.green); + MemoryUtil.memPutByte(ptr + 106, instance.blue); + MemoryUtil.memPutByte(ptr + 107, instance.alpha); + ExtraMemoryOps.put2x16(ptr + 108, instance.light); + }) + .vertexShader(Flywheel.rl("instance/glyph.vert")) + .cullShader(Flywheel.rl("instance/cull/glyph.glsl")) + .build(); + private InstanceTypes() { } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 9a112c07d..4899298c6 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -10,7 +10,11 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.api.internal.DependencyInjection; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.font.FontSet; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.resources.ResourceLocation; public interface FlwLibLink { FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl"); @@ -24,4 +28,8 @@ public interface FlwLibLink { void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha); Deque getPoseStack(PoseStack stack); + + GlyphExtension getGlyphExtension(BakedGlyph glyph); + + FontSet getFontSet(Font font, ResourceLocation loc); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java new file mode 100644 index 000000000..6d62472e1 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/GlyphExtension.java @@ -0,0 +1,25 @@ +package dev.engine_room.flywheel.lib.internal; + +import net.minecraft.resources.ResourceLocation; + +public interface GlyphExtension { + float flywheel$u0(); + + float flywheel$u1(); + + float flywheel$v0(); + + float flywheel$v1(); + + float flywheel$left(); + + float flywheel$right(); + + float flywheel$up(); + + float flywheel$down(); + + ResourceLocation flywheel$texture(); + + void flywheel$texture(ResourceLocation location); +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java index cf271ed43..214712af4 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/material/SimpleMaterial.java @@ -178,6 +178,7 @@ public class SimpleMaterial implements Material { shaders = material.shaders(); fog = material.fog(); cutout = material.cutout(); + light = material.light(); texture = material.texture(); blur = material.blur(); mipmap = material.mipmap(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java new file mode 100644 index 000000000..3fb42a933 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/NameplateVisual.java @@ -0,0 +1,213 @@ +package dev.engine_room.flywheel.lib.visual; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.joml.Vector4fc; + +import com.google.common.collect.Lists; +import com.mojang.blaze3d.font.GlyphInfo; + +import dev.engine_room.flywheel.api.instance.InstancerProvider; +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import dev.engine_room.flywheel.lib.instance.GlyphInstance; +import dev.engine_room.flywheel.lib.instance.InstanceTypes; +import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import dev.engine_room.flywheel.lib.material.CutoutShaders; +import dev.engine_room.flywheel.lib.material.SimpleMaterial; +import dev.engine_room.flywheel.lib.model.QuadMesh; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.visual.util.SmartRecycler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.font.FontSet; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.client.gui.font.glyphs.EmptyGlyph; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextColor; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSink; + +public class NameplateVisual { + + public final Matrix4f pose = new Matrix4f(); + private final InstancerProvider provider; + private final InstanceEmitter instanceEmitter; + + @Nullable + private Component name; + + public NameplateVisual(InstancerProvider provider) { + this.provider = provider; + + instanceEmitter = new InstanceEmitter(provider); + instanceEmitter.font = Minecraft.getInstance().font; + } + + public void name(Component name) { + this.name = name; + } + + public void update() { + if (name == null) { + return; + } + + instanceEmitter.recycler.resetCount(); + instanceEmitter.x = 0; + instanceEmitter.y = 0; + name.getVisualOrderText() + .accept(instanceEmitter); + instanceEmitter.recycler.discardExtra(); + + } + + private class InstanceEmitter implements FormattedCharSink { + private final SmartRecycler recycler; + + Font font; + private boolean dropShadow; + private float dimFactor; + private byte r; + private byte g; + private byte b; + private byte a; + private Font.DisplayMode mode; + private int packedLightCoords; + float x; + float y; + + private InstanceEmitter(InstancerProvider instancerProvider) { + recycler = new SmartRecycler<>(key -> instancerProvider.instancer(InstanceTypes.GLYPH, GLYPH_CACHE.get(key)) + .createInstance()); + } + + @Nullable + private List effects; + + private void addEffect(BakedGlyph.Effect effect) { + if (this.effects == null) { + this.effects = Lists.newArrayList(); + } + this.effects.add(effect); + } + + @Override + public boolean accept(int i, Style style, int j) { + float n; + float l; + float h; + float g; + FontSet fontSet = FlwLibLink.INSTANCE.getFontSet(font, style.getFont()); + GlyphInfo glyphInfo = fontSet.getGlyphInfo(j, false); + BakedGlyph bakedGlyph = style.isObfuscated() && j != 32 ? fontSet.getRandomGlyph(glyphInfo) : fontSet.getGlyph(j); + boolean bl = style.isBold(); + float f = this.a; + TextColor textColor = style.getColor(); + if (textColor != null) { + int k = textColor.getValue(); + g = (float) (k >> 16 & 0xFF) / 255.0f * this.dimFactor; + h = (float) (k >> 8 & 0xFF) / 255.0f * this.dimFactor; + l = (float) (k & 0xFF) / 255.0f * this.dimFactor; + } else { + g = this.r; + h = this.g; + l = this.b; + } + if (!(bakedGlyph instanceof EmptyGlyph)) { + // var renderType = bakedGlyph.renderType(Font.DisplayMode.NORMAL); + + var glyphExtension = FlwLibLink.INSTANCE.getGlyphExtension(bakedGlyph); + + GlyphInstance glyph = recycler.get(new GlyphModelKey(glyphExtension.flywheel$texture(), bl, dropShadow)); + + glyph.setGlyph(bakedGlyph, this.x, this.y, style.isItalic()); + glyph.pose.set(pose); + glyph.light = LightTexture.FULL_BRIGHT; + glyph.setChanged(); + } + float m = glyphInfo.getAdvance(bl); + float f2 = n = this.dropShadow ? 1.0f : 0.0f; + // if (style.isStrikethrough()) { + // this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0f, this.y + n + 4.5f, this.x + n + m, this.y + n + 4.5f - 1.0f, 0.01f, g, h, l, f)); + // } + // if (style.isUnderlined()) { + // this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0f, this.y + n + 9.0f, this.x + n + m, this.y + n + 9.0f - 1.0f, 0.01f, g, h, l, f)); + // } + this.x += m; + return true; + } + + public float finish(int backgroundColor, float x) { + if (backgroundColor != 0) { + float f = (float) (backgroundColor >> 24 & 0xFF) / 255.0f; + float g = (float) (backgroundColor >> 16 & 0xFF) / 255.0f; + float h = (float) (backgroundColor >> 8 & 0xFF) / 255.0f; + float i = (float) (backgroundColor & 0xFF) / 255.0f; + this.addEffect(new BakedGlyph.Effect(x - 1.0f, this.y + 9.0f, this.x + 1.0f, this.y - 1.0f, 0.01f, g, h, i, f)); + } + // if (this.effects != null) { + // BakedGlyph bakedGlyph = font.getFontSet(Style.DEFAULT_FONT).whiteGlyph(); + // VertexConsumer vertexConsumer = this.bufferSource.getBuffer(bakedGlyph.renderType(this.mode)); + // for (BakedGlyph.Effect effect : this.effects) { + // bakedGlyph.renderEffect(effect, this.pose, vertexConsumer, this.packedLightCoords); + // } + // } + return this.x; + } + } + + private static final ResourceReloadCache GLYPH_CACHE = new ResourceReloadCache<>(GlyphModelKey::into); + + private static final Material GLYPH_MATERIAL = SimpleMaterial.builder() + .build(); + + private record GlyphModelKey(ResourceLocation font, boolean bold, boolean shadow) { + private Model into() { + // bold -> x + 1 + // shadow -> x + 1, y + 1 + return new SingleMeshModel(new GlyphMesh(new Vector3f[]{new Vector3f(0, 0, 0),}), SimpleMaterial.builderOf(GLYPH_MATERIAL) + .texture(font) + .cutout(CutoutShaders.ONE_TENTH) + .build()); + } + } + + public record GlyphMesh(Vector3f[] quads) implements QuadMesh { + + @Override + public int vertexCount() { + return 4 * quads.length; + } + + @Override + public void write(MutableVertexList vertexList) { + for (int i = 0; i < quads.length; i++) { + Vector3f quad = quads[i]; + var quadStart = i * 4; + + for (int j = 0; j < 4; j++) { + vertexList.x(quadStart + j, quad.x); + vertexList.y(quadStart + j, quad.y); + vertexList.z(quadStart + j, quad.z); + vertexList.normalX(quadStart + j, 0); + vertexList.normalY(quadStart + j, 0); + vertexList.normalZ(quadStart + j, 1); + } + } + } + + @Override + public Vector4fc boundingSphere() { + return new Vector4f(0, 0, 0, 2); + } + } +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/glyph.glsl b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/glyph.glsl new file mode 100644 index 000000000..8f49e6898 --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/glyph.glsl @@ -0,0 +1,9 @@ +#include "flywheel:util/matrix.glsl" + +void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) { + + radius += abs(i.x[2] - i.x[0]) + abs(i.y[1] - i.y[0]); + center += vec3((i.x[0] + i.x[2]) * 0.5, (i.y[0] + i.y[1]) * 0.5, 0.); + + transformBoundingSphere(i.pose, center, radius); +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert new file mode 100644 index 000000000..89978637e --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/glyph.vert @@ -0,0 +1,18 @@ +void flw_instanceVertex(in FlwInstance i) { + uint vertexInGlyph = flw_vertexId % 4; + + uint yIndex = ((vertexInGlyph + 1u) >> 1u) & 1u; + + flw_vertexPos.x += i.x[vertexInGlyph]; + flw_vertexPos.y += i.y[yIndex]; + + flw_vertexPos = i.pose * flw_vertexPos; + + flw_vertexTexCoord.s = i.u0u1v0v1[(vertexInGlyph & 2u) >> 1u]; + flw_vertexTexCoord.t = i.u0u1v0v1[2u + yIndex]; + + flw_vertexColor = i.color; + + // Some drivers have a bug where uint over float division is invalid, so use an explicit cast. + flw_vertexLight = vec2(i.light) / 256.0; +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 47fffeeb6..635ecc506 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -9,11 +9,17 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.impl.extension.PoseStackExtension; +import dev.engine_room.flywheel.impl.mixin.FontAccessor; import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor; import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import dev.engine_room.flywheel.lib.internal.GlyphExtension; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.font.FontSet; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.resources.ResourceLocation; public class FlwLibLinkImpl implements FlwLibLink { @Override @@ -40,4 +46,14 @@ public class FlwLibLinkImpl implements FlwLibLink { public Deque getPoseStack(PoseStack stack) { return ((PoseStackAccessor) stack).flywheel$getPoseStack(); } + + @Override + public GlyphExtension getGlyphExtension(BakedGlyph glyph) { + return (GlyphExtension) glyph; + } + + @Override + public FontSet getFontSet(Font font, ResourceLocation loc) { + return ((FontAccessor) font).flywheel$getFontSet(loc); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BakedGlyphMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BakedGlyphMixin.java new file mode 100644 index 000000000..11dc3c27a --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BakedGlyphMixin.java @@ -0,0 +1,91 @@ +package dev.engine_room.flywheel.impl.mixin; + +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 dev.engine_room.flywheel.lib.internal.GlyphExtension; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.resources.ResourceLocation; + +@Mixin(BakedGlyph.class) +public class BakedGlyphMixin implements GlyphExtension { + @Shadow + @Final + private float u0; + @Shadow + @Final + private float u1; + @Shadow + @Final + private float v0; + @Shadow + @Final + private float v1; + @Shadow + @Final + private float left; + @Shadow + @Final + private float right; + @Shadow + @Final + private float up; + @Shadow + @Final + private float down; + + @Unique + private ResourceLocation flywheel$texture; + + @Override + public float flywheel$u0() { + return u0; + } + + @Override + public float flywheel$u1() { + return u1; + } + + @Override + public float flywheel$v0() { + return v0; + } + + @Override + public float flywheel$v1() { + return v1; + } + + @Override + public float flywheel$left() { + return left; + } + + @Override + public float flywheel$right() { + return right; + } + + @Override + public float flywheel$up() { + return up; + } + + @Override + public float flywheel$down() { + return down; + } + + @Override + public ResourceLocation flywheel$texture() { + return flywheel$texture; + } + + @Override + public void flywheel$texture(ResourceLocation location) { + flywheel$texture = location; + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/FontAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/FontAccessor.java new file mode 100644 index 000000000..dbea26326 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/FontAccessor.java @@ -0,0 +1,14 @@ +package dev.engine_room.flywheel.impl.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.font.FontSet; +import net.minecraft.resources.ResourceLocation; + +@Mixin(Font.class) +public interface FontAccessor { + @Invoker("getFontSet") + FontSet flywheel$getFontSet(ResourceLocation fontLocation); +} diff --git a/common/src/main/resources/flywheel.impl.mixins.json b/common/src/main/resources/flywheel.impl.mixins.json index a1953a127..afae1ba3b 100644 --- a/common/src/main/resources/flywheel.impl.mixins.json +++ b/common/src/main/resources/flywheel.impl.mixins.json @@ -5,9 +5,11 @@ "compatibilityLevel": "JAVA_17", "refmap": "flywheel.refmap.json", "client": [ + "BakedGlyphMixin", "BlockEntityTypeMixin", "ClientChunkCacheMixin", "EntityTypeMixin", + "FontAccessor", "LevelMixin", "LevelRendererMixin", "MinecraftMixin",