mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-07 02:34:58 +01:00
Not a hack at all
- Get text instancing minimally working - Use special glyph instance type to instance all characters under the same mesh - Unfortunately MC's font stuff is not threading friendly so hacks were required to allow visuals to query glyphs from their beginFrame - Mixin to CodePointMap to overwrite most methods and synchronize - Mixin to FontSet to use a custom AsyncFontTexture that defers texture init and upload - Trying to mixin directly to FontTexture or subclass it is very complicated because of the calls to getId, but I think that can be improved
This commit is contained in:
parent
40577420d5
commit
9227631c73
18 changed files with 961 additions and 0 deletions
|
@ -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);
|
||||
|
|
|
@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<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() {
|
||||
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<T> 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<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<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();
|
||||
}
|
||||
|
||||
}
|
|
@ -12,3 +12,5 @@ out vec3 flw_vertexNormal;
|
|||
out float flw_distance;
|
||||
|
||||
FlwMaterial flw_material;
|
||||
|
||||
#define flw_vertexId gl_VertexID
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"refmap": "backend-flywheel.refmap.json",
|
||||
"client": [
|
||||
"AbstractClientPlayerAccessor",
|
||||
"CodePointMapMixin",
|
||||
"FontSetMixin",
|
||||
"GlStateManagerMixin",
|
||||
"LevelRendererAccessor",
|
||||
"OptionsMixin",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -103,6 +103,37 @@ public final class InstanceTypes {
|
|||
.cullShader(Flywheel.rl("instance/cull/shadow.glsl"))
|
||||
.build();
|
||||
|
||||
public static final InstanceType<GlyphInstance> 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() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PoseStack.Pose> getPoseStack(PoseStack stack);
|
||||
|
||||
GlyphExtension getGlyphExtension(BakedGlyph glyph);
|
||||
|
||||
FontSet getFontSet(Font font, ResourceLocation loc);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<GlyphModelKey, GlyphInstance> 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<BakedGlyph.Effect> 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<GlyphModelKey, Model> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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<PoseStack.Pose> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -5,9 +5,11 @@
|
|||
"compatibilityLevel": "JAVA_17",
|
||||
"refmap": "flywheel.refmap.json",
|
||||
"client": [
|
||||
"BakedGlyphMixin",
|
||||
"BlockEntityTypeMixin",
|
||||
"ClientChunkCacheMixin",
|
||||
"EntityTypeMixin",
|
||||
"FontAccessor",
|
||||
"LevelMixin",
|
||||
"LevelRendererMixin",
|
||||
"MinecraftMixin",
|
||||
|
|
Loading…
Reference in a new issue