diff options
| author | Linnea Gräf <nea@nea.moe> | 2025-07-29 21:49:33 +0200 |
|---|---|---|
| committer | Linnea Gräf <nea@nea.moe> | 2025-07-29 21:49:33 +0200 |
| commit | a30e0810604ee9122758879205434563ccc94738 (patch) | |
| tree | ccdde75394db0917231dd3fdbcf9763c1a913de6 | |
| parent | 80d97bb0de46b1b0cefd8a5bd1ba22105ee08a50 (diff) | |
| download | Firmament-a30e0810604ee9122758879205434563ccc94738.tar.gz Firmament-a30e0810604ee9122758879205434563ccc94738.tar.bz2 Firmament-a30e0810604ee9122758879205434563ccc94738.zip | |
fix: special element renderers / circle renderers
3 files changed, 234 insertions, 52 deletions
diff --git a/src/main/java/moe/nea/firmament/mixins/render/renderer/MultipleSpecialGuiRenderStates.java b/src/main/java/moe/nea/firmament/mixins/render/renderer/MultipleSpecialGuiRenderStates.java new file mode 100644 index 0000000..cc79591 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/render/renderer/MultipleSpecialGuiRenderStates.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: LGPL-3.0-or-later + * SPDX-FileCopyrightText: 2025 azureaaron via Skyblocker + */ + +package moe.nea.firmament.mixins.render.renderer; + +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import moe.nea.firmament.util.render.MultiSpecialGuiRenderState; +import moe.nea.firmament.util.render.MultiSpecialGuiRenderer; +import net.minecraft.client.gui.render.GuiRenderer; +import net.minecraft.client.gui.render.SpecialGuiElementRenderer; +import net.minecraft.client.gui.render.state.GuiRenderState; +import net.minecraft.client.gui.render.state.special.SpecialGuiElementRenderState; +import net.minecraft.client.render.VertexConsumerProvider; +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 org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.HashMap; +import java.util.Map; + +/** + * The structure of this class was roughly taken from SkyBlocker, retrieved 29.07.2025 + */ +@Mixin(GuiRenderer.class) +public class MultipleSpecialGuiRenderStates { + @Shadow + @Final + private VertexConsumerProvider.Immediate vertexConsumers; + @Shadow + @Final + GuiRenderState state; + @Unique + Map<MultiSpecialGuiRenderState, MultiSpecialGuiRenderer<?>> multiRenderers = new HashMap<>(); + + @Inject(method = "prepareSpecialElement", at = @At("HEAD"), cancellable = true) + private <T extends SpecialGuiElementRenderState> void onPrepareElement(T elementState, int windowScaleFactor, CallbackInfo ci) { + if (elementState instanceof MultiSpecialGuiRenderState multiState) { + @SuppressWarnings({"resource", "unchecked"}) + var renderer = (SpecialGuiElementRenderer<T>) multiRenderers + .computeIfAbsent(multiState, elementState$ -> elementState$.createRenderer(this.vertexConsumers)); + renderer.render(elementState, state, windowScaleFactor); + ci.cancel(); + } + } + + @Inject(method = "close", at = @At("TAIL")) + private void onClose(CallbackInfo ci) { + multiRenderers.values().forEach(SpecialGuiElementRenderer::close); + } + + @Inject(method = "render(Lcom/mojang/blaze3d/buffers/GpuBufferSlice;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/render/GuiRenderer;clearOversizedItems()V")) + private void onAfterRender(GpuBufferSlice fogBuffer, CallbackInfo ci) { + multiRenderers.values().removeIf(it -> { + if (it.consumeRender()) { + return false; + } else { + it.close(); + return true; + } + }); + } +} diff --git a/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt b/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt new file mode 100644 index 0000000..ce56df3 --- /dev/null +++ b/src/main/kotlin/util/render/MultiSpecialGuiRenderState.kt @@ -0,0 +1,48 @@ +package moe.nea.firmament.util.render + +import org.joml.Matrix3x2f +import net.minecraft.client.gui.ScreenRect +import net.minecraft.client.gui.render.SpecialGuiElementRenderer +import net.minecraft.client.gui.render.state.GuiRenderState +import net.minecraft.client.gui.render.state.special.SpecialGuiElementRenderState +import net.minecraft.client.render.VertexConsumerProvider + +abstract class MultiSpecialGuiRenderState : SpecialGuiElementRenderState { + // I wish i had manifolds @Self type here... Maybe i should switch to java after all :( + abstract fun createRenderer(vertexConsumers: VertexConsumerProvider.Immediate): MultiSpecialGuiRenderer<out MultiSpecialGuiRenderState> + abstract val x1: Int + abstract val x2: Int + abstract val y1: Int + abstract val y2: Int + abstract val scale: Float + abstract val bounds: ScreenRect? + abstract val scissorArea: ScreenRect? + override fun x1(): Int = x1 + + override fun x2(): Int = x2 + + override fun y1(): Int = y1 + + override fun y2(): Int = y2 + + override fun scale(): Float = scale + + override fun scissorArea(): ScreenRect? = scissorArea + + override fun bounds(): ScreenRect? = bounds + +} + +abstract class MultiSpecialGuiRenderer<T : MultiSpecialGuiRenderState>( + vertexConsumers: VertexConsumerProvider.Immediate +) : SpecialGuiElementRenderer<T>(vertexConsumers) { + var wasUsedThisFrame = false + fun consumeRender(): Boolean { + return wasUsedThisFrame.also { wasUsedThisFrame = false } + } + + override fun renderElement(element: T, state: GuiRenderState) { + wasUsedThisFrame = true + super.renderElement(element, state) + } +} diff --git a/src/main/kotlin/util/render/RenderCircleProgress.kt b/src/main/kotlin/util/render/RenderCircleProgress.kt index efd99fe..bbc4ace 100644 --- a/src/main/kotlin/util/render/RenderCircleProgress.kt +++ b/src/main/kotlin/util/render/RenderCircleProgress.kt @@ -4,9 +4,12 @@ import com.mojang.blaze3d.vertex.VertexFormat import org.joml.Matrix3x2f import util.render.CustomRenderLayers import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.ScreenRect import net.minecraft.client.render.BufferBuilder import net.minecraft.client.render.RenderLayer +import net.minecraft.client.render.VertexConsumerProvider import net.minecraft.client.util.BufferAllocator +import net.minecraft.client.util.math.MatrixStack import net.minecraft.util.Identifier import moe.nea.firmament.util.MC import moe.nea.firmament.util.collections.nonNegligibleSubSectionsAlignedWith @@ -15,6 +18,105 @@ import moe.nea.firmament.util.mc.CustomRenderPassHelper object RenderCircleProgress { + + data class State( + override val x1: Int, + override val x2: Int, + override val y1: Int, + override val y2: Int, + val layer: RenderLayer.MultiPhase, + val u1: Float, + val u2: Float, + val v1: Float, + val v2: Float, + val angleRadians: ClosedFloatingPointRange<Float>, + val color: Int, + val innerCutoutRadius: Float, + override val scale: Float, + override val bounds: ScreenRect?, + override val scissorArea: ScreenRect?, + ) : MultiSpecialGuiRenderState() { + override fun createRenderer(vertexConsumers: VertexConsumerProvider.Immediate): MultiSpecialGuiRenderer<out MultiSpecialGuiRenderState> { + return Renderer(vertexConsumers) + } + } + + class Renderer(vertexConsumers: VertexConsumerProvider.Immediate) : + MultiSpecialGuiRenderer<State>(vertexConsumers) { + override fun render( + state: State, + matrices: MatrixStack + ) { + matrices.push() + matrices.translate(0F, -1F, 0F) + val sections = state.angleRadians.nonNegligibleSubSectionsAlignedWith((τ / 8f).toFloat()) + .zipWithNext().toList() + val u1 = state.u1 + val u2 = state.u2 + val v1 = state.v1 + val v2 = state.v2 + val color = state.color + val matrix = matrices.peek().positionMatrix + BufferAllocator(state.layer.vertexFormat.vertexSize * sections.size * 3).use { allocator -> + + val bufferBuilder = BufferBuilder(allocator, VertexFormat.DrawMode.TRIANGLES, state.layer.vertexFormat) + + for ((sectionStart, sectionEnd) in sections) { + val firstPoint = Projections.Two.projectAngleOntoUnitBox(sectionStart.toDouble()) + val secondPoint = Projections.Two.projectAngleOntoUnitBox(sectionEnd.toDouble()) + fun ilerp(f: Float): Float = + ilerp(-1f, 1f, f) + + bufferBuilder + .vertex(matrix, secondPoint.x, secondPoint.y, 0F) + .texture(lerp(u1, u2, ilerp(secondPoint.x)), lerp(v1, v2, ilerp(secondPoint.y))) + .color(color) + + bufferBuilder + .vertex(matrix, firstPoint.x, firstPoint.y, 0F) + .texture(lerp(u1, u2, ilerp(firstPoint.x)), lerp(v1, v2, ilerp(firstPoint.y))) + .color(color) + + bufferBuilder + .vertex(matrix, 0F, 0F, 0F) + .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F))) + .color(color) + + } + + bufferBuilder.end().use { buffer -> + if (state.innerCutoutRadius <= 0) { + state.layer.draw(buffer) + return + } + CustomRenderPassHelper( + { "RenderCircleProgress" }, + VertexFormat.DrawMode.TRIANGLES, + state.layer.vertexFormat, + MC.instance.framebuffer, + false, + ).use { renderPass -> + renderPass.uploadVertices(buffer) + renderPass.setPipeline(state.layer.pipeline) + renderPass.setUniform("InnerCutoutRadius", 4) { + it.putFloat(state.innerCutoutRadius) + } + renderPass.draw() + } + } + } + matrices.pop() + } + + override fun getElementClass(): Class<State> { + return State::class.java + } + + override fun getName(): String { + return "Firmament Circle" + } + } + fun renderCircularSlice( drawContext: DrawContext, layer: RenderLayer.MultiPhase, @@ -25,58 +127,22 @@ object RenderCircleProgress { angleRadians: ClosedFloatingPointRange<Float>, color: Int = -1, innerCutoutRadius: Float = 0F - ) { // TODO: this is fixed by adding a special gui element renderer - val sections = angleRadians.nonNegligibleSubSectionsAlignedWith((τ / 8f).toFloat()) - .zipWithNext().toList() - BufferAllocator(layer.vertexFormat.vertexSize * sections.size * 3).use { allocator -> - - val bufferBuilder = BufferBuilder(allocator, VertexFormat.DrawMode.TRIANGLES, layer.vertexFormat) - val matrix: Matrix3x2f = drawContext.matrices - - for ((sectionStart, sectionEnd) in sections) { - val firstPoint = Projections.Two.projectAngleOntoUnitBox(sectionStart.toDouble()) - val secondPoint = Projections.Two.projectAngleOntoUnitBox(sectionEnd.toDouble()) - fun ilerp(f: Float): Float = - ilerp(-1f, 1f, f) - - bufferBuilder - .vertex(matrix, secondPoint.x, secondPoint.y, 0F) - .texture(lerp(u1, u2, ilerp(secondPoint.x)), lerp(v1, v2, ilerp(secondPoint.y))) - .color(color) - - bufferBuilder - .vertex(matrix, firstPoint.x, firstPoint.y, 0F) - .texture(lerp(u1, u2, ilerp(firstPoint.x)), lerp(v1, v2, ilerp(firstPoint.y))) - .color(color) - - bufferBuilder - .vertex(matrix, 0F, 0F, 0F) - .texture(lerp(u1, u2, ilerp(0F)), lerp(v1, v2, ilerp(0F))) - .color(color) - - } - - bufferBuilder.end().use { buffer -> - if (innerCutoutRadius <= 0) { - layer.draw(buffer) - return - } - CustomRenderPassHelper( - { "RenderCircleProgress" }, - VertexFormat.DrawMode.TRIANGLES, - layer.vertexFormat, - MC.instance.framebuffer, - false, - ).use { renderPass -> - renderPass.uploadVertices(buffer) - renderPass.setPipeline(layer.pipeline) - renderPass.setUniform("InnerCutoutRadius", 4) { - it.putFloat(innerCutoutRadius) - } - renderPass.draw() - } - } - } + ) { + val screenRect = ScreenRect(-1, -1, 2, 2).transform(drawContext.matrices) + drawContext.state.addSpecialElement( + State( + screenRect.left, screenRect.right, + screenRect.top, screenRect.bottom, + layer, + u1, u2, v1, v2, + angleRadians, + color, + innerCutoutRadius, + screenRect.width / 2F, + screenRect, + null + ) + ) } fun renderCircle( |
