diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/Sdf2d.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/Sdf2d.java index 2851a7cd..b18c3d34 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/Sdf2d.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/Sdf2d.java @@ -7,9 +7,25 @@ @UtilityClass public class Sdf2d { + /** + * @param params Sdf 参数 + * @param pX 点 x 坐标 + * @param pY 点 y 坐标 + * @param rX 矩形 x 起始点 + * @param rY 矩形 y 起始点 + * @param rotation 旋转角度 + * @param centred 居中 + * @return 点距离 sdf 的距离 + */ public static float sd( @NotNull SdfParameters params, - float x, float y + float pX, + float pY, + float rX, + float rY, + float rotation, + boolean centred + ) { var rect = params.getRect(); var round = params.getRound(); @@ -23,7 +39,6 @@ public static float sd( final var hw = width * 0.5f; final var hh = height * 0.5f; - var rotation = params.getRotation(); var radian = rotation * Mth.DEG_TO_RAD; var cos = Mth.cos(radian); var sin = Mth.sin(radian); @@ -31,19 +46,19 @@ public static float sd( float cx; float cy; - if (params.isCenter()) { + if (centred) { - cx = rect.x; - cy = rect.y; + cx = rX; + cy = rY; } else { - cx = rect.x + hw - hw * cos + hh * sin; - cy = rect.y + hh - hw * sin - hh * cos; + cx = rX + hw - hw * cos + hh * sin; + cy = rY + hh - hw * sin - hh * cos; } - var px = x - cx; - var py = y - cy; + var px = pX - cx; + var py = pY - cy; if (rotation != 0f) { diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfGraphics.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfGraphics.java index 57dd0ba7..0060e584 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfGraphics.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfGraphics.java @@ -25,7 +25,6 @@ import net.neoforged.neoforge.client.event.ConfigureMainRenderTargetEvent; import org.jetbrains.annotations.NotNull; import org.joml.Matrix3x2f; -import org.joml.Vector3f; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -35,26 +34,39 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class SdfGraphics { private static final int MAX_SDF_AMOUNT = 256; + private static final int MAX_SHARED_SDF_AMOUNT = 64; private static final long SDF_PARAMETER_SIZE = SdfParameters.DEFINITION.size(BufferLayout.STD140); @Getter public static final SdfGraphics instance = new SdfGraphics(new SdfParameters()); private static boolean debug = false; - private final SdfParameters parameters; + private final SdfParameters[] shared = new SdfParameters[MAX_SHARED_SDF_AMOUNT]; + + private final SdfParameters parameters; + + private int shareCursor = 0; + + private float x, y, rotation; + private int color = -1; + private boolean centred = false; public SdfGraphics box(float x, float y, float width, float height) { this.parameters .getRect() - .set(x, y, width, height); + .set(0, 0, width, height); this.parameters .box(width, height); + this.x = x; + this.y = y; return this; } public SdfGraphics circle(float x, float y, float radius) { this.parameters .getRect() - .set(x, y, radius * 2, radius * 2); + .set(0, 0, radius * 2, radius * 2); this.parameters .circle(radius); + this.x = x; + this.y = y; return this; } @@ -63,24 +75,30 @@ public SdfGraphics arc(float x, float y, float sweep, float radius, float width) var scale = radius * 2 + width; this.parameters .getRect() - .set(x, y, scale, scale); + .set(0, 0, scale, scale); this.parameters .arc(sweep, radius, width); + this.x = x; + this.y = y; return this; } public SdfGraphics sector(float x, float y, float sweep, float radius, float width) { this.parameters .getRect() - .set(x, y, radius * 2, radius * 2); + .set(0, 0, radius * 2, radius * 2); this.parameters .sector(sweep, radius, width); + this.x = x; + this.y = y; return this; } public SdfGraphics pie(float x, float y, float sweep, float radius) { this.parameters .getRect() - .set(x, y, radius * 2, radius * 2); + .set(0, 0, radius * 2, radius * 2); this.parameters .pie(sweep, radius); + this.x = x; + this.y = y; return this; } @@ -93,8 +111,10 @@ public SdfGraphics capsule( var width = Math.max(topRadius, bottomRadius) * 2; this.parameters .getRect() - .set(x, y, width, height + (topRadius + bottomRadius) * 3); + .set(0, 0, width, height + (topRadius + bottomRadius) * 3); this.parameters .capsule(topRadius, bottomRadius, height); + this.x = x; + this.y = y; return this; } @@ -107,8 +127,10 @@ public SdfGraphics egg( var width = Math.max(topRadius, bottomRadius) * 2; this.parameters .getRect() - .set(x, y, width, height + (topRadius + bottomRadius) * 2); + .set(0, 0, width, height + (topRadius + bottomRadius) * 2); this.parameters .egg(topRadius, bottomRadius, height); + this.x = x; + this.y = y; return this; } @@ -128,6 +150,8 @@ public SdfGraphics segment( this.parameters .getRect() .set(left + width / 2, top + height / 2, width, height); this.parameters .segment( -halfWidth, -halfHeight, +halfWidth, +halfHeight); + this.x = left + width / 2; + this.y = top + height / 2; return this; } @@ -136,8 +160,10 @@ public SdfGraphics triangleEquilateral(float x, float y, float radius) { var actual = radius * (1.0f / 1.2f); this.parameters .getRect() - .set(x, y, radius * 2, radius * 2); + .set(0, 0, radius * 2, radius * 2); this.parameters .triangleEquilateral(actual); + this.x = x; + this.y = y; return this; } @@ -145,24 +171,26 @@ public SdfGraphics triangleEquilateral(float x, float y, float radius) { public SdfGraphics triangleIsosceles(float x, float y, float width, float height) { final var factor = 1.0f / 1.2f; this.parameters .getRect() - .set(x, y, width * 2.0f, height); + .set(0, 0, width * 2.0f, height); this.parameters .triangleIsosceles(width * factor, height * factor); + this.x = x; + this.y = y; return this; } public SdfGraphics color(int color) { - this.parameters .color(color); + this.color = color; return this; } public SdfGraphics color(float red, float green, float blue, float alpha) { - this.parameters .color(ARGB.colorFromFloat(red, green, blue, alpha)); + this.color = ARGB.colorFromFloat(red, green, blue, alpha); return this; } public SdfGraphics color(int red, int green, int blue, int alpha) { - this.parameters .color(ARGB.color(alpha, red, green, blue)); + this.color = ARGB.color(alpha, red, green, blue); return this; } @@ -186,12 +214,12 @@ public SdfGraphics stroke(float width) { } public SdfGraphics rotate(float degrees) { - this.parameters .rotate(Mth.wrapDegrees(degrees)); + this.rotation = degrees; return this; } public SdfGraphics center(boolean center) { - this.parameters .center(center); + this.centred = center; return this; } @@ -210,22 +238,127 @@ public SdfGraphics light(float radius) { return this; } - public SdfGraphics draw(@NotNull GuiGraphicsExtractor graphics) { - _draw(graphics, this.parameters); + public SdfGraphics draw( + @NotNull GuiGraphicsExtractor graphics + ) { + _draw( + graphics, + this.parameters, + this.x, + this.y, + this.rotation, + this.color, + this.centred + ); return this; } + public SdfGraphics draw( + @NotNull GuiGraphicsExtractor graphics, + @NotNull SdfParameters parameters, + float x, + float y + ) { + + if (parameters.isShared()) { + _draw( + graphics, + parameters, + x, + y, + this.rotation, + this.color, + this.centred + ); + } + + return this; + } + public SdfGraphics reset() { this.parameters .reset(); + this.x = 0; + this.y = 0; + this.rotation = 0; + this.color = -1; + this.centred = false; return this; } public boolean collide(float x, float y, float threshold) { - return Sdf2d .sd(this.parameters, x, y) < threshold; + return this.collide( + this.parameters, + x, + y, + this.x, + this.y, + threshold + ); + } + + public boolean collide( + @NotNull SdfParameters parameters, + float pointX, + float pointY, + float x, + float y, + float threshold + ) { + return Sdf2d .sd( + parameters, + pointX, + pointY, + x, + y, + this.rotation, + this.centred + ) < threshold; } - public SdfGraphics cache() { - return new SdfGraphics(this.parameters.duplicate()); + public @NonNull SdfParameters cache() { + return this.parameters.duplicate(); + } + + public @NonNull SdfParameters share() { + final var cache = this.cache(); + this.share(cache); + return cache; + } + + public void share(@NotNull SdfParameters cache) { + final var cursor = this.shareCursor; + if (cursor == MAX_SHARED_SDF_AMOUNT) { + return; + } + + this.shared[cursor] = cache; + cache.uboIndex = MAX_SDF_AMOUNT - cursor - 1; + cache.uploaded = false; + + do { + this.shareCursor++; + } while (this.shareCursor < MAX_SHARED_SDF_AMOUNT && this.shared[this.shareCursor] != null); + } + + public void unshare(@NotNull SdfParameters cache) { + var cursor = 0; + // find index + for (; cursor < MAX_SHARED_SDF_AMOUNT; cursor++) { + if (this.shared[cursor] == cache) { + cache.uploaded = false; + cache.uboIndex = -1; + break; + } + } + + if (cursor == MAX_SHARED_SDF_AMOUNT) { + return; + } + + this.shared[cursor] = null; + if (cursor < this.shareCursor) { + this.shareCursor = cursor; + } } public static void flush() { @@ -243,10 +376,10 @@ public static void init(ConfigureMainRenderTargetEvent event) { GpuDevice device = RenderSystem.getDevice(); encoder = device.createCommandEncoder(); ubo = device.createBuffer( - () -> "SDF Parameters", - GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, - SDF_PARAMETER_SIZE * MAX_SDF_AMOUNT - ); + () -> "SDF Parameters", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, + SDF_PARAMETER_SIZE * MAX_SDF_AMOUNT + ); } public static void debug(boolean enable) { @@ -254,115 +387,116 @@ public static void debug(boolean enable) { } private static void _draw( - @NotNull GuiGraphicsExtractor graphics, - @NotNull SdfParameters parameters + @NotNull GuiGraphicsExtractor graphics, + @NotNull SdfParameters parameters, + float x, + float y, + float rotation, + int color, + boolean centred ) { - var round = parameters.getRound(); - var smooth = parameters.getSmooth(); - var stroke = parameters.getStroke(); - - var rect = parameters.getRect(); - var z = rect.z; - var w = rect.w; - - var pose = new Matrix3x2f(graphics.pose()); - var ex = (round + smooth + stroke) * 2.0f; - var width = rect.z + ex; - var height = rect.w + ex; - final var hw = width * 0.5f; - final var hh = height * 0.5f; - - final var rotation = parameters.getRotation(); + var round = parameters.getRound(); + var smooth = parameters.getSmooth(); + var stroke = parameters.getStroke(); + + var rect = parameters.getRect(); + var z = rect.z; + var w = rect.w; + + var pose = new Matrix3x2f(graphics.pose()); + var ex = (round + smooth + stroke) * 2.0f; + var width = rect.z + ex; + var height = rect.w + ex; + final var hw = width * 0.5f; + final var hh = height * 0.5f; + final var radian = rotation * Mth.DEG_TO_RAD; final var cos = Mth.cos(radian); final var sin = Mth.sin(radian); float cx; float cy; - if (parameters.isCenter()) { - pose .translate(rect.x, rect.y); + if (centred) { + pose .translate(x, y); - cx = rect.x; - cy = rect.y; + cx = x; + cy = y; } else { - pose .translate( - rect.x + hw, - rect.y + hh - ); + pose .translate( + x + hw, + y + hh + ); - cx = rect.x + hw - hw * cos + hh * sin; - cy = rect.y + hh - hw * sin - hh * cos; + cx = x + hw - hw * cos + hh * sin; + cy = y + hh - hw * sin - hh * cos; } if (rotation != 0.0f) { - pose.rotate(Mth.DEG_TO_RAD * rotation); + pose .rotate(Mth.DEG_TO_RAD * rotation); } - if (!parameters.isCenter()) { - pose .translate(-hw, -hh); + if (!centred) { + pose .translate(-hw, -hh); } - pose .scale(width, height); - - rect.z = width; - rect.w = height; - - var extX = Mth.abs(hw * cos) + Mth.abs(hh * sin) + 1.0f; - var extY = Mth.abs(hw * sin) + Mth.abs(hh * cos) + 1.0f; - - var x0 = cx - extX; - var x1 = cx + extX; - var y0 = cy - extY; - var y1 = cy + extY; - - var offset = index * SDF_PARAMETER_SIZE; - var slice = ubo.slice(offset, SDF_PARAMETER_SIZE); - var state = new RenderState( - pose, - parameters.getColor(), - index, - ubo.slice(), - graphics.peekScissorStack(), - LibGuiElementRenderState.getBounds( - new Matrix3x2f(graphics.pose()), - x0, y0, - x1, y1, - graphics.peekScissorStack() - ) - ); + pose .scale(width, height); + + rect.z = width; + rect.w = height; + + var extX = Mth.abs(hw * cos) + Mth.abs(hh * sin) + 1.0f; + var extY = Mth.abs(hw * sin) + Mth.abs(hh * cos) + 1.0f; + + var x0 = cx - extX; + var x1 = cx + extX; + var y0 = cy - extY; + var y1 = cy + extY; + + var shared = parameters.isShared(); + var idx = shared ? parameters.uboIndex : index; + var state = new RenderState( + pose, + color, + idx, + ubo.slice(), + graphics.peekScissorStack(), + LibGuiElementRenderState.getBounds( + new Matrix3x2f(graphics.pose()), + x0, y0, + x1, y1, + graphics.peekScissorStack() + ) + ); if (debug) { - graphics.outline( - (int) x0, (int) y0, - (int) (x1 - x0), (int) (y1 - y0), - 0xFF0000FF - ); + graphics .outline( + (int) x0, (int) y0, + (int) (x1 - x0), (int) (y1 - y0), + 0xFF0000FF + ); } - parameters .upload(encoder, slice); + if (!shared || !parameters.uploaded) { + var offset = idx * SDF_PARAMETER_SIZE; + var slice = ubo.slice(offset, SDF_PARAMETER_SIZE); + parameters .upload(encoder, slice); + parameters .uploaded = shared; + } - graphics .submitGuiElementRenderState(state); - rect.z = z; - rect.w = w; + graphics .submitGuiElementRenderState(state); + rect.z = z; + rect.w = w; - SdfGraphics.index++; - } - - private static boolean _collide( - @NotNull SdfParameters parameters, - float x, - float y - ) { - return false; + SdfGraphics .index++; } private record RenderState( - Matrix3x2f pose, - int color, - int index, - GpuBufferSlice sdfParametersUbo, - @Nullable ScreenRectangle scissorArea, - @Nullable ScreenRectangle bounds + Matrix3x2f pose, + int color, + int index, + GpuBufferSlice sdfParametersUbo, + @Nullable ScreenRectangle scissorArea, + @Nullable ScreenRectangle bounds ) implements LibGuiElementRenderState { @Override diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfParameters.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfParameters.java index 8add02dc..2ee2ea8f 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfParameters.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/sdf/SdfParameters.java @@ -25,9 +25,8 @@ public class SdfParameters extends BufferObject { private final Vector4f rect = new Vector4f(); private final Vector4i typeParams = new Vector4i(); - private int color = 0xFFFFFFFF; - private float rotation; - private boolean center; + int uboIndex = -1; + boolean uploaded = false; protected SdfParameters() { super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); @@ -107,18 +106,6 @@ public void round(float radius) { this ._cornerRadius(radius); } - public void color(int color) { - this.color = color; - } - - public void rotate(float rotation) { - this.rotation = rotation; - } - - public void center(boolean center) { - this.center = center; - } - public void fill() { this ._pass(SdfPassType.FILL); } @@ -167,26 +154,32 @@ public boolean isOnion() { private void _smooth(float value) { this.sharedParams.x = value; + this.uploaded = false; } private void _light(float value) { this.sharedParams.w = 4.605f / value; + this.uploaded = false; } private void _width(float value) { this.sharedParams.y = value; + this.uploaded = false; } private void _cornerRadius(float value) { this.sharedParams.z = value; + this.uploaded = false; } private void _pass(SdfPassType value) { - this.typeParams.x = value.ordinal(); + this.typeParams.x = value.ordinal(); + this.uploaded = false; } private void _renderType(SdfRenderType value) { - this.typeParams.y = value.ordinal(); + this.typeParams.y = value.ordinal(); + this.uploaded = false; } @Override @@ -200,8 +193,10 @@ public void reset() { this.rect .set(0.0f, 0.0f, 0.0f, 0.0f); this.typeParams .set(0, 0, 0, 4); - this.color = 0xFFFFFFFF; - this.rotation = 0f; - this.center = false; + this.uploaded = false; + } + + public boolean isShared() { + return this.uboIndex != -1; } }