diff --git a/buildSrc/src/main/kotlin/multiloader-common.gradle.kts b/buildSrc/src/main/kotlin/multiloader-common.gradle.kts index a06369cb..219ef3fd 100644 --- a/buildSrc/src/main/kotlin/multiloader-common.gradle.kts +++ b/buildSrc/src/main/kotlin/multiloader-common.gradle.kts @@ -11,6 +11,7 @@ val modAuthor = project.findProperty("mod_author")?.toString() ?: "" val minecraftVersionRange = project.property("minecraft_version_range").toString() val fabricVersion = project.property("fabric_version").toString() val fabricLoaderVersion = project.property("fabric_loader_version").toString() +val fabricMinecraftVersionRange = project.findProperty("fabric_minecraft_version_range")?.toString() ?: "~$minecraftVersion" val license = project.property("license").toString() val neoforgeVersion = project.property("neoforge_version").toString() val neoforgeLoaderVersionRange = project.property("neoforge_loader_version_range").toString() @@ -76,6 +77,7 @@ tasks.named("processResources") { "minecraft_version_range" to minecraftVersionRange, "fabric_version" to fabricVersion, "fabric_loader_version" to fabricLoaderVersion, + "fabric_minecraft_version_range" to fabricMinecraftVersionRange, "mod_name" to modName, "mod_author" to modAuthor, "mod_id" to modId, diff --git a/common/src/main/java/com/github/epsilon/assets/i18n/DefaultTranslateComponent.java b/common/src/main/java/com/github/epsilon/assets/i18n/DefaultTranslateComponent.java index 4dc96379..91a9d16a 100644 --- a/common/src/main/java/com/github/epsilon/assets/i18n/DefaultTranslateComponent.java +++ b/common/src/main/java/com/github/epsilon/assets/i18n/DefaultTranslateComponent.java @@ -3,6 +3,7 @@ import com.github.epsilon.assets.holders.TranslateHolder; import com.github.epsilon.modules.impl.ClientSetting; import net.minecraft.client.resources.language.I18n; +import net.minecraft.locale.Language; public class DefaultTranslateComponent implements TranslateComponent { @@ -38,7 +39,7 @@ public void refresh() { } private static String resolveTranslation(String key) { - if (I18n.exists(key)) { + if (Language.getInstance().has(key)) { return I18n.get(key); } return ClientSetting.INSTANCE.i18nFallback.getValue() ? formatKey(key) : key; diff --git a/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java b/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java index aaada90d..5031f231 100644 --- a/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java +++ b/common/src/main/java/com/github/epsilon/graphics/LuminRenderPipelines.java @@ -1,45 +1,55 @@ package com.github.epsilon.graphics; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.pipeline.BindGroupLayout; import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.ColorTargetState; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.shaders.UniformType; import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.renderer.BindGroupLayouts; import net.minecraft.client.renderer.RenderPipelines; public class LuminRenderPipelines { - private final static RenderPipeline.Snippet NO_BLEND_DEPTH_SNIPPET = RenderPipeline.builder(RenderPipelines.MATRICES_PROJECTION_SNIPPET) + private final static BindGroupLayout TTF_INFO_UBO = BindGroupLayout.builder() + .withUniform("TtfInfo", UniformType.UNIFORM_BUFFER) + .build(); + + private final static RenderPipeline.Snippet NO_BLEND_DEPTH_SNIPPET = RenderPipeline.builder() + .withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION) .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT)) .buildSnippet(); public final static RenderPipeline RECTANGLE = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/rectangle")) - .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS) + .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("rectangle")) .withFragmentShader(ResourceLocationUtils.getIdentifier("rectangle")) .withCull(false) .build(); private final static RenderPipeline.Snippet TTF_SNIPPET = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) - .withUniform("TtfInfo", UniformType.UNIFORM_BUFFER) + .withBindGroupLayout(TTF_INFO_UBO) .buildSnippet(); public final static RenderPipeline TTF_FONT = RenderPipeline.builder(TTF_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/ttf_font")) - .withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, VertexFormat.Mode.QUADS) + .withVertexBinding(0, DefaultVertexFormat.POSITION_TEX_COLOR) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("ttf_font")) .withFragmentShader(ResourceLocationUtils.getIdentifier("ttf_font")) - .withSampler("Sampler0") + .withBindGroupLayout(BindGroupLayouts.SAMPLER0) .withCull(false) .build(); public final static RenderPipeline ROUND_RECT = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/round_rectangle")) - .withVertexFormat(LuminVertexFormats.ROUND_RECT, VertexFormat.Mode.QUADS) + .withVertexBinding(0, LuminVertexFormats.ROUND_RECT) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("round_rectangle")) .withFragmentShader(ResourceLocationUtils.getIdentifier("round_rectangle")) .withCull(false) @@ -47,7 +57,8 @@ public class LuminRenderPipelines { public final static RenderPipeline ROUND_RECT_OUTLINE = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/round_rectangle_outline")) - .withVertexFormat(LuminVertexFormats.ROUND_RECT_OUTLINE, VertexFormat.Mode.QUADS) + .withVertexBinding(0, LuminVertexFormats.ROUND_RECT_OUTLINE) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("round_rectangle_outline")) .withFragmentShader(ResourceLocationUtils.getIdentifier("round_rectangle_outline")) .withCull(false) @@ -55,7 +66,8 @@ public class LuminRenderPipelines { public final static RenderPipeline SHADOW = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/shadow")) - .withVertexFormat(LuminVertexFormats.ROUND_RECT, VertexFormat.Mode.QUADS) + .withVertexBinding(0, LuminVertexFormats.ROUND_RECT) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("shadow")) .withFragmentShader(ResourceLocationUtils.getIdentifier("shadow")) .withCull(false) @@ -63,16 +75,18 @@ public class LuminRenderPipelines { public final static RenderPipeline TEXTURE = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/texture")) - .withVertexFormat(LuminVertexFormats.TEXTURE, VertexFormat.Mode.QUADS) + .withVertexBinding(0, LuminVertexFormats.TEXTURE) + .withPrimitiveTopology(PrimitiveTopology.QUADS) .withVertexShader(ResourceLocationUtils.getIdentifier("texture")) .withFragmentShader(ResourceLocationUtils.getIdentifier("texture")) - .withSampler("Sampler0") + .withBindGroupLayout(BindGroupLayouts.SAMPLER0) .withCull(false) .build(); public final static RenderPipeline TRIANGLE = RenderPipeline.builder(NO_BLEND_DEPTH_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipelines/triangle")) - .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLES) + .withVertexBinding(0, DefaultVertexFormat.POSITION_COLOR) + .withPrimitiveTopology(PrimitiveTopology.TRIANGLES) .withVertexShader(ResourceLocationUtils.getIdentifier("triangle")) .withFragmentShader(ResourceLocationUtils.getIdentifier("triangle")) .withCull(false) diff --git a/common/src/main/java/com/github/epsilon/graphics/LuminRenderSystem.java b/common/src/main/java/com/github/epsilon/graphics/LuminRenderSystem.java index 0ddab103..5092d833 100644 --- a/common/src/main/java/com/github/epsilon/graphics/LuminRenderSystem.java +++ b/common/src/main/java/com/github/epsilon/graphics/LuminRenderSystem.java @@ -3,12 +3,17 @@ import com.github.epsilon.assets.holders.RenderTargetHolder; import com.github.epsilon.assets.holders.RendererHolder; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.text.StaticFontLoader; +import com.github.epsilon.graphics.vulkan.LuminVulkanContext; +import com.github.epsilon.modules.impl.player.ComputeTest; +import com.mojang.blaze3d.GpuFormat; +import com.mojang.blaze3d.PrimitiveTopology; import com.mojang.blaze3d.ProjectionType; import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.*; -import com.mojang.blaze3d.vertex.VertexFormat; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Projection; import net.minecraft.client.renderer.ProjectionMatrixBuffer; import net.minecraft.client.renderer.rendertype.TextureTransform; @@ -31,14 +36,20 @@ public class LuminRenderSystem { @Nullable private static LuminRenderTarget activeTarget = null; + public static final LuminVulkanContext vulkanContext = new LuminVulkanContext(); + public static void setActiveTarget(@Nullable LuminRenderTarget target) { activeTarget = target; } public static void destroyAll() { + ComputeTest.INSTANCE.destroy(); + guiProjectionMatrixBuffer.close(); RenderTargetHolder.INSTANCE.destroyAll(); + StaticFontLoader.destroyAll(); RendererHolder.INSTANCE.destroyAll(); + vulkanContext.destroy(); } @Nullable @@ -47,7 +58,7 @@ public static LuminRenderTarget getActiveTarget() { } public static void applyOrthoProjection() { - WindowRenderState windowState = mc.gameRenderer.getGameRenderState().windowRenderState; + WindowRenderState windowState = mc.gameRenderer.gameRenderState().windowRenderState; guiOrthoProjection .setupOrtho(-1000.0F, 1000.0F, @@ -65,13 +76,13 @@ public static void applyOrthoProjection() { */ public static GpuTextureView resolveColorView() { if (activeTarget != null) return activeTarget.colorView(); - return mc.getMainRenderTarget().getColorTextureView(); + return mc.gameRenderer.mainRenderTarget().getColorTextureView(); } @Nullable public static GpuTextureView resolveDepthView() { if (activeTarget != null) return activeTarget.depthView(); - return mc.getMainRenderTarget().getDepthTextureView(); + return Minecraft.getInstance().gameRenderer.mainRenderTarget().getDepthTextureView(); } public static QuadRenderingInfo prepareQuadRendering(int vertexCount) { @@ -84,14 +95,14 @@ public static QuadRenderingInfo prepareQuadRendering(int vertexCount) { final var indexCount = vertexCount / 4 * 6; RenderSystem.AutoStorageIndexBuffer autoIndices = - RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); + RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS); GpuBuffer ibo = autoIndices.getBuffer(indexCount); GpuBufferSlice dynamicUniforms = RenderSystem.getDynamicUniforms().writeTransform( - RenderSystem.getModelViewMatrix(), + RenderSystem.getModelViewMatrixCopy(), new Vector4f(1, 1, 1, 1), new Vector3f(0, 0, 0), - TextureTransform.DEFAULT_TEXTURING.getMatrix() + TextureTransform.DEFAULT_TEXTURING.createMatrix() ); return new QuadRenderingInfo(colorView, depthView, autoIndices, ibo, indexCount, dynamicUniforms); @@ -119,7 +130,7 @@ public static final class LuminRenderTarget implements AutoCloseable { private LuminRenderTarget(String name, int width, int height) { this.width = width; this.height = height; - this.identifier = ResourceLocationUtils.getIdentifier("lumin-rt" + name); + this.identifier = ResourceLocationUtils.getIdentifier("epsilon-rt" + name); createTextures(); } @@ -133,7 +144,7 @@ private void createTextures() { final var colorTexture = device.createTexture( "lumin-rt-color", GpuTexture.USAGE_TEXTURE_BINDING | GpuTexture.USAGE_RENDER_ATTACHMENT | GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_COPY_SRC, - TextureFormat.RGBA8, + GpuFormat.RGBA8_UNORM, width, height, 1, 1 ); final var colorView = device.createTextureView(colorTexture); @@ -141,7 +152,7 @@ private void createTextures() { depthTexture = device.createTexture( "lumin-rt-depth", GpuTexture.USAGE_TEXTURE_BINDING | GpuTexture.USAGE_RENDER_ATTACHMENT | GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_COPY_SRC, - TextureFormat.DEPTH32, + GpuFormat.D32_FLOAT, width, height, 1, 1 ); depthView = device.createTextureView(depthTexture); @@ -171,7 +182,7 @@ public Identifier getIdentifier() { public void clear() { var encoder = RenderSystem.getDevice().createCommandEncoder(); - encoder.clearColorAndDepthTextures(colorTexture.getTexture(), 0, depthTexture, 1.0); + encoder.clearColorAndDepthTextures(colorTexture.getTexture(), new Vector4f(0, 0, 0, 0), depthTexture, 1.0); } public GpuTextureView colorView() { diff --git a/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java b/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java index 40b80b6d..3495ebff 100644 --- a/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java +++ b/common/src/main/java/com/github/epsilon/graphics/LuminVertexFormats.java @@ -1,52 +1,31 @@ package com.github.epsilon.graphics; +import com.mojang.blaze3d.GpuFormat; import com.mojang.blaze3d.vertex.VertexFormat; -import com.mojang.blaze3d.vertex.VertexFormatElement; public class LuminVertexFormats { - private static final int ROUND_INNER_RECT_ID = findNextId(); - private static final int ROUND_RADIUS_ID = findNextId(ROUND_INNER_RECT_ID + 1); - private static final int ROUND_OUTLINE_WIDTH_ID = findNextId(ROUND_RADIUS_ID + 1); - - public static final VertexFormatElement ROUND_INNER_RECT = VertexFormatElement.register(ROUND_INNER_RECT_ID, 2, VertexFormatElement.Type.FLOAT, false, 4); - public static final VertexFormatElement ROUND_RADIUS = VertexFormatElement.register(ROUND_RADIUS_ID, 4, VertexFormatElement.Type.FLOAT, false, 4); - public static final VertexFormatElement ROUND_OUTLINE_WIDTH = VertexFormatElement.register(ROUND_OUTLINE_WIDTH_ID, 1, VertexFormatElement.Type.FLOAT, false, 1); - - public static final VertexFormat ROUND_RECT = VertexFormat.builder() - .add("Position", VertexFormatElement.POSITION) - .add("Color", VertexFormatElement.COLOR) - .add("InnerRect", ROUND_INNER_RECT) - .add("Radius", ROUND_RADIUS) + public static final VertexFormat ROUND_RECT = VertexFormat.builder(0) + .addAttribute("Position", GpuFormat.RGB32_FLOAT) + .addAttribute("Color", GpuFormat.RGBA8_UNORM) + .addAttribute("InnerRect", GpuFormat.RGBA32_FLOAT) + .addAttribute("Radius", GpuFormat.RGBA32_FLOAT) .build(); - public static final VertexFormat ROUND_RECT_OUTLINE = VertexFormat.builder() - .add("Position", VertexFormatElement.POSITION) - .add("Color", VertexFormatElement.COLOR) - .add("InnerRect", ROUND_INNER_RECT) - .add("Radius", ROUND_RADIUS) - .add("OutlineWidth", ROUND_OUTLINE_WIDTH) + public static final VertexFormat ROUND_RECT_OUTLINE = VertexFormat.builder(0) + .addAttribute("Position", GpuFormat.RGB32_FLOAT) + .addAttribute("Color", GpuFormat.RGBA8_UNORM) + .addAttribute("InnerRect", GpuFormat.RGBA32_FLOAT) + .addAttribute("Radius", GpuFormat.RGBA32_FLOAT) + .addAttribute("OutlineWidth", GpuFormat.R32_FLOAT) .build(); - public static final VertexFormat TEXTURE = VertexFormat.builder() - .add("Position", VertexFormatElement.POSITION) - .add("Color", VertexFormatElement.COLOR) - .add("UV0", VertexFormatElement.UV0) - .add("InnerRect", ROUND_INNER_RECT) - .add("Radius", ROUND_RADIUS) + public static final VertexFormat TEXTURE = VertexFormat.builder(0) + .addAttribute("Position", GpuFormat.RGB32_FLOAT) + .addAttribute("Color", GpuFormat.RGBA8_UNORM) + .addAttribute("UV0", GpuFormat.RG32_FLOAT) + .addAttribute("InnerRect", GpuFormat.RGBA32_FLOAT) + .addAttribute("Radius", GpuFormat.RGBA32_FLOAT) .build(); - private static int findNextId() { - return findNextId(0); - } - - private static int findNextId(int start) { - for (int i = Math.max(0, start); i < VertexFormatElement.MAX_COUNT; i++) { - if (VertexFormatElement.byId(i) == null) { - return i; - } - } - throw new IllegalStateException("VertexFormatElement count limit exceeded"); - } - } diff --git a/common/src/main/java/com/github/epsilon/graphics/README.md b/common/src/main/java/com/github/epsilon/graphics/README.md index 4b9783d4..6a1f2f9a 100644 --- a/common/src/main/java/com/github/epsilon/graphics/README.md +++ b/common/src/main/java/com/github/epsilon/graphics/README.md @@ -55,7 +55,7 @@ Minecraft libraries) to ensure safe and lazy initialization. ```java // Recommended initialization method -private final Supplier rectRenderer = Suppliers.memoize(RectRenderer::new); +private final Supplier rectRenderer = Suppliers.memoize(RectRenderer::create); // Use .get() to access the renderer instance rectRenderer.get().addRect(10f,10f,100f,100f,Color.WHITE); diff --git a/common/src/main/java/com/github/epsilon/graphics/README_zh.md b/common/src/main/java/com/github/epsilon/graphics/README_zh.md index 0eb1e1b4..b0e90075 100644 --- a/common/src/main/java/com/github/epsilon/graphics/README_zh.md +++ b/common/src/main/java/com/github/epsilon/graphics/README_zh.md @@ -47,7 +47,7 @@ Lumin Graphics 的所有渲染操作均通过专门的 **Renderer(渲染器) ```java // 推荐的初始化方式 -private final Supplier rectRenderer = Suppliers.memoize(RectRenderer::new); +private final Supplier rectRenderer = Suppliers.memoize(RectRenderer::create); // 使用 .get() 获取渲染器实例 rectRenderer.get().addRect(10f,10f,100f,100f,Color.WHITE); diff --git a/common/src/main/java/com/github/epsilon/graphics/buffer/LuminRingBuffer.java b/common/src/main/java/com/github/epsilon/graphics/buffer/LuminRingBuffer.java index 88e51116..ec221109 100644 --- a/common/src/main/java/com/github/epsilon/graphics/buffer/LuminRingBuffer.java +++ b/common/src/main/java/com/github/epsilon/graphics/buffer/LuminRingBuffer.java @@ -1,6 +1,7 @@ package com.github.epsilon.graphics.buffer; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.renderer.MappableRingBuffer; @@ -15,7 +16,7 @@ public class LuminRingBuffer { private final MappableRingBuffer ringBuffer; - private GpuBuffer.MappedView mappedBuffer; + private GpuBufferSlice.MappedView mappedBuffer; private boolean mapped; @@ -41,8 +42,8 @@ public ByteBuffer getMappedBuffer() { */ public void tryMap() { if (mapped) return; - mappedBuffer = RenderSystem.getDevice().createCommandEncoder().mapBuffer( - ringBuffer.currentBuffer(), false, true + mappedBuffer = ringBuffer.currentBuffer().map( + false, true ); mapped = true; } diff --git a/common/src/main/java/com/github/epsilon/graphics/immediate/LuminImmediateRenderer.java b/common/src/main/java/com/github/epsilon/graphics/immediate/LuminImmediateRenderer.java new file mode 100644 index 00000000..91402fd2 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/immediate/LuminImmediateRenderer.java @@ -0,0 +1,327 @@ +package com.github.epsilon.graphics.immediate; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.buffer.LuminRingBuffer; +import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.systems.RenderPass; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.GpuTextureView; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.blaze3d.vertex.VertexFormatElement; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.rendertype.TextureTransform; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.resources.Identifier; +import net.minecraft.util.ARGB; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.lwjgl.system.MemoryUtil; + +import javax.annotation.Nullable; +import java.nio.ByteOrder; +import java.util.Optional; +import java.util.OptionalDouble; + +public final class LuminImmediateRenderer { + + private static final Minecraft MC = Minecraft.getInstance(); + private static final long DEFAULT_BUFFER_SIZE = 1024 * 1024; + private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN; + + private static final Channel POS_COLOR_QUADS = new Channel(DefaultVertexFormat.POSITION_COLOR, PrimitiveTopology.QUADS); + private static final Channel POS_TEX_COLOR_QUADS = new Channel(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.QUADS); + private static final Channel POS_COLOR_NORMAL_LINE_WIDTH_LINES = new Channel(DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH, PrimitiveTopology.LINES); + + private LuminImmediateRenderer() { + } + + public static PosColorQuads beginPosColorQuads(RenderPipeline pipeline) { + return new PosColorQuads(POS_COLOR_QUADS.begin(pipeline, null)); + } + + public static PosTexColorQuads beginPosTexColorQuads(RenderPipeline pipeline, Identifier texture) { + return new PosTexColorQuads(POS_TEX_COLOR_QUADS.begin(pipeline, texture)); + } + + public static Lines beginLines(RenderPipeline pipeline) { + return new Lines(POS_COLOR_NORMAL_LINE_WIDTH_LINES.begin(pipeline, null)); + } + + public static final class PosColorQuads { + + private final Channel channel; + + private PosColorQuads(Channel channel) { + this.channel = channel; + } + + public void vertex(Matrix4f matrix, float x, float y, float z, int color) { + this.channel.putPosition(matrix, x, y, z); + this.channel.putColor(color); + this.channel.finishVertex(); + } + + public void end() { + this.channel.drawAndReset(); + } + } + + public static final class PosTexColorQuads { + + private final Channel channel; + + private PosTexColorQuads(Channel channel) { + this.channel = channel; + } + + public void vertex(Matrix4f matrix, float x, float y, float z, float u, float v, int color) { + this.channel.putPosition(matrix, x, y, z); + this.channel.putUv(u, v); + this.channel.putColor(color); + this.channel.finishVertex(); + } + + public void end() { + this.channel.drawAndReset(); + } + } + + public static final class Lines { + + private final Channel channel; + private final Vector3f normalTmp = new Vector3f(); + + private Lines(Channel channel) { + this.channel = channel; + } + + public void vertex(Matrix4f matrix, PoseStack.Pose pose, float x, float y, float z, int color, float nx, float ny, float nz, float width) { + this.channel.putPosition(matrix, x, y, z); + this.channel.putColor(color); + + pose.transformNormal(nx, ny, nz, this.normalTmp).normalize(); + this.channel.putNormal(this.normalTmp.x, this.normalTmp.y, this.normalTmp.z); + this.channel.putLineWidth(width); + this.channel.finishVertex(); + } + + public void end() { + this.channel.drawAndReset(); + } + } + + private static final class Channel { + + private final LuminRingBuffer ringBuffer; + private final VertexFormat format; + private final PrimitiveTopology mode; + private final int stride; + + private final int positionOffset; + private final int colorOffset; + private final int uvOffset; + private final int normalOffset; + private final int lineWidthOffset; + + private final Vector3f posTmp = new Vector3f(); + + private boolean building; + private long currentOffset; + private int vertexCount; + + private long vertexBaseAddr; + + private RenderPipeline pipeline; + @Nullable + private Identifier texture; + + private Channel(VertexFormat format, PrimitiveTopology mode) { + this.ringBuffer = new LuminRingBuffer(DEFAULT_BUFFER_SIZE, GpuBuffer.USAGE_VERTEX); + this.format = format; + this.mode = mode; + this.stride = format.getVertexSize(); + + this.positionOffset = resolveOffset(format, "Position"); + this.colorOffset = resolveOffset(format, "Color"); + this.uvOffset = resolveOffset(format, "UV0"); + this.normalOffset = resolveOffset(format, "Normal"); + this.lineWidthOffset = resolveOffset(format, "LineWidth"); + } + + private static int resolveOffset(VertexFormat format, String elementName) { + VertexFormatElement element = format.getElement(elementName); + return element != null ? element.offset() : -1; + } + + private Channel begin(RenderPipeline pipeline, @Nullable Identifier texture) { + if (this.building) { + throw new IllegalStateException("Immediate channel is already building"); + } + this.building = true; + this.currentOffset = 0; + this.vertexCount = 0; + this.pipeline = pipeline; + this.texture = texture; + + this.ringBuffer.tryMap(); + return this; + } + + private void putPosition(Matrix4f matrix, float x, float y, float z) { + if (this.positionOffset < 0 || !ensureCapacity()) { + return; + } + matrix.transformPosition(x, y, z, this.posTmp); + long p = this.vertexBaseAddr + this.positionOffset; + MemoryUtil.memPutFloat(p, this.posTmp.x); + MemoryUtil.memPutFloat(p + 4L, this.posTmp.y); + MemoryUtil.memPutFloat(p + 8L, this.posTmp.z); + } + + private void putColor(int color) { + if (this.colorOffset < 0 || !ensureCapacity()) { + return; + } + int abgr = ARGB.toABGR(color); + long p = this.vertexBaseAddr + this.colorOffset; + MemoryUtil.memPutInt(p, LITTLE_ENDIAN ? abgr : Integer.reverseBytes(abgr)); + } + + private void putUv(float u, float v) { + if (this.uvOffset < 0 || !ensureCapacity()) { + return; + } + long p = this.vertexBaseAddr + this.uvOffset; + MemoryUtil.memPutFloat(p, u); + MemoryUtil.memPutFloat(p + 4L, v); + } + + private void putNormal(float nx, float ny, float nz) { + if (this.normalOffset < 0 || !ensureCapacity()) { + return; + } + long p = this.vertexBaseAddr + this.normalOffset; + MemoryUtil.memPutByte(p, packNormal(nx)); + MemoryUtil.memPutByte(p + 1L, packNormal(ny)); + MemoryUtil.memPutByte(p + 2L, packNormal(nz)); + } + + private void putLineWidth(float width) { + if (this.lineWidthOffset < 0 || !ensureCapacity()) { + return; + } + MemoryUtil.memPutFloat(this.vertexBaseAddr + this.lineWidthOffset, width); + } + + private void finishVertex() { + if (!this.building || this.vertexBaseAddr == 0L) { + return; + } + long completedVertexBaseAddr = this.vertexBaseAddr; + this.currentOffset += this.stride; + this.vertexCount++; + + if (this.mode == PrimitiveTopology.LINES) { + long duplicateVertexBaseAddr = MemoryUtil.memAddress(this.ringBuffer.getMappedBuffer()) + this.currentOffset; + MemoryUtil.memCopy(completedVertexBaseAddr, duplicateVertexBaseAddr, this.stride); + this.currentOffset += this.stride; + this.vertexCount++; + } + + this.vertexBaseAddr = 0L; + } + + private boolean ensureCapacity() { + if (!this.building) { + return false; + } + if (this.vertexBaseAddr != 0L) { + return true; + } + long requiredBytes = this.mode == PrimitiveTopology.LINES ? this.stride * 2L : this.stride; + if (this.currentOffset + requiredBytes > DEFAULT_BUFFER_SIZE) { + this.vertexBaseAddr = 0L; + return false; + } + this.vertexBaseAddr = MemoryUtil.memAddress(this.ringBuffer.getMappedBuffer()) + this.currentOffset; + return true; + } + + private void drawAndReset() { + try { + if (this.vertexCount <= 0) { + return; + } + + if (this.ringBuffer.isMapped()) { + this.ringBuffer.unmap(); + } + + GpuTextureView colorView = LuminRenderSystem.resolveColorView(); + GpuTextureView depthView = LuminRenderSystem.resolveDepthView(); + if (colorView == null) { + return; + } + + GpuBufferSlice dynamicUniforms = RenderSystem.getDynamicUniforms().writeTransform( + RenderSystem.getModelViewMatrixCopy(), + new Vector4f(1, 1, 1, 1), + new Vector3f(0, 0, 0), + TextureTransform.DEFAULT_TEXTURING.createMatrix() + ); + + try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( + () -> "Lumin Immediate Draw", + colorView, Optional.empty(), + depthView, OptionalDouble.empty()) + ) { + pass.setPipeline(this.pipeline); + RenderSystem.bindDefaultUniforms(pass); + pass.setUniform("DynamicTransforms", dynamicUniforms); + pass.setVertexBuffer(0, new GpuBufferSlice(this.ringBuffer.getGpuBuffer(), 0, this.ringBuffer.getGpuBuffer().size())); + + if (this.texture != null) { + AbstractTexture textureObject = MC.getTextureManager().getTexture(this.texture); + pass.bindTexture("Sampler0", textureObject.getTextureView(), textureObject.getSampler()); + } + + switch (this.mode) { + case LINES, QUADS -> { + int indexCount = this.mode.indexCount(this.vertexCount); + if (indexCount > 0) { + RenderSystem.AutoStorageIndexBuffer autoIndices = RenderSystem.getSequentialBuffer(this.mode); + GpuBuffer ibo = autoIndices.getBuffer(indexCount); + pass.setIndexBuffer(ibo, autoIndices.type()); + pass.drawIndexed(indexCount, 1, 0, 0, 0); + } + } + default -> pass.draw(this.vertexCount, 1, 0, 0); + } + } + } finally { + if (this.ringBuffer.isMapped()) { + this.ringBuffer.unmap(); + } + this.ringBuffer.rotate(); + + this.building = false; + this.currentOffset = 0; + this.vertexCount = 0; + this.vertexBaseAddr = 0L; + this.pipeline = null; + this.texture = null; + } + } + + private static byte packNormal(float value) { + float clamped = Math.max(-1.0f, Math.min(1.0f, value)); + return (byte) ((int) (clamped * 127.0f) & 0xFF); + } + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/immediate/LuminTessellator.java b/common/src/main/java/com/github/epsilon/graphics/immediate/LuminTessellator.java new file mode 100644 index 00000000..8c4d731a --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/immediate/LuminTessellator.java @@ -0,0 +1,26 @@ +package com.github.epsilon.graphics.immediate; + +import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.ByteBufferBuilder; +import com.mojang.blaze3d.vertex.VertexFormat; + +public final class LuminTessellator { + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024; + private static final LuminTessellator INSTANCE = new LuminTessellator(DEFAULT_BUFFER_SIZE); + + private final ByteBufferBuilder allocator; + + private LuminTessellator(int bufferSize) { + this.allocator = new ByteBufferBuilder(bufferSize); + } + + public static LuminTessellator getInstance() { + return INSTANCE; + } + + public BufferBuilder begin(PrimitiveTopology mode, VertexFormat format) { + return new BufferBuilder(this.allocator, mode, format); + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/RectRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/RectRenderer.java index 0fe7e0bf..7cd4d925 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/RectRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/RectRenderer.java @@ -6,14 +6,15 @@ import com.github.epsilon.graphics.buffer.LuminRingBuffer; import com.github.epsilon.graphics.elements.RectElement; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.util.ARGB; import org.lwjgl.system.MemoryUtil; import java.awt.*; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class RectRenderer implements IRenderer { @@ -97,7 +98,7 @@ private void addVertex(float vx, float vy, int color) { } public void setScissor(int x, int y, int width, int height) { - if (x < 0 || y < 0) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { return; } @@ -125,7 +126,7 @@ public void draw() { try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( () -> "Rect Draw", - info.colorView(), OptionalInt.empty(), + info.colorView(), Optional.empty(), info.depthView(), OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.RECTANGLE); @@ -136,9 +137,9 @@ public void draw() { RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", info.dynamicUniforms()); - pass.setVertexBuffer(0, buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(buffer.getGpuBuffer(), 0, buffer.getGpuBuffer().size())); pass.setIndexBuffer(info.ibo(), info.autoIndices().type()); - pass.drawIndexed(0, 0, info.indexCount(), 1); + pass.drawIndexed(info.indexCount(), 1, 0, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java index 22c5dad8..11f8311f 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectOutlineRenderer.java @@ -5,14 +5,15 @@ import com.github.epsilon.graphics.LuminRenderSystem; import com.github.epsilon.graphics.buffer.LuminRingBuffer; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.util.ARGB; import org.lwjgl.system.MemoryUtil; import java.awt.*; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class RoundRectOutlineRenderer implements IRenderer { @@ -106,6 +107,10 @@ private void addVertex(float vx, float vy, float rx1, float ry1, float rx2, floa } public void setScissor(int x, int y, int width, int height) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + return; + } + scissorEnabled = true; scissorX = x; scissorY = y; @@ -126,16 +131,16 @@ public void draw() { if (info == null || info.colorView() == null) return; try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( - () -> "Round Rect Outline Draw", info.colorView(), OptionalInt.empty(), + () -> "Round Rect Outline Draw", info.colorView(), Optional.empty(), info.depthView(), OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.ROUND_RECT_OUTLINE); if (scissorEnabled) pass.enableScissor(scissorX, scissorY, scissorW, scissorH); RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", info.dynamicUniforms()); - pass.setVertexBuffer(0, buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(buffer.getGpuBuffer(), 0, buffer.getGpuBuffer().size())); pass.setIndexBuffer(info.ibo(), info.autoIndices().type()); - pass.drawIndexed(0, 0, info.indexCount(), 1); + pass.drawIndexed(info.indexCount(), 1, 0, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectRenderer.java index 6f1679bb..b944570b 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/RoundRectRenderer.java @@ -6,14 +6,15 @@ import com.github.epsilon.graphics.buffer.LuminRingBuffer; import com.github.epsilon.graphics.elements.RoundRectElement; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.util.ARGB; import org.lwjgl.system.MemoryUtil; import java.awt.*; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class RoundRectRenderer implements IRenderer { @@ -121,16 +122,16 @@ public void draw() { if (info == null || info.colorView() == null) return; try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( - () -> "Round Rect Draw", info.colorView(), OptionalInt.empty(), + () -> "Round Rect Draw", info.colorView(), Optional.empty(), info.depthView(), OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.ROUND_RECT); if (scissorEnabled) pass.enableScissor(scissorX, scissorY, scissorW, scissorH); RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", info.dynamicUniforms()); - pass.setVertexBuffer(0, buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(buffer.getGpuBuffer(), 0, buffer.getGpuBuffer().size())); pass.setIndexBuffer(info.ibo(), info.autoIndices().type()); - pass.drawIndexed(0, 0, info.indexCount(), 1); + pass.drawIndexed(info.indexCount(), 1, 0, 0, 0); } } @@ -150,7 +151,7 @@ public void close() { } public void setScissor(int x, int y, int width, int height) { - if (x < 0 || y < 0) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { return; } diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/ShadowRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/ShadowRenderer.java index f3575362..67da8ba8 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/ShadowRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/ShadowRenderer.java @@ -6,14 +6,15 @@ import com.github.epsilon.graphics.buffer.LuminRingBuffer; import com.github.epsilon.graphics.elements.ShadowElement; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.util.ARGB; import org.lwjgl.system.MemoryUtil; import java.awt.*; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class ShadowRenderer implements IRenderer { @@ -100,7 +101,7 @@ private void addVertex(float vx, float vy, float rx1, float ry1, float rx2, floa } public void setScissor(int x, int y, int width, int height) { - if (x < 0 || y < 0) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { return; } @@ -124,16 +125,16 @@ public void draw() { if (info == null || info.colorView() == null) return; try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( - () -> "Lumin Shadow Draw", info.colorView(), OptionalInt.empty(), + () -> "Lumin Shadow Draw", info.colorView(), Optional.empty(), info.depthView(), OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.SHADOW); if (scissorEnabled) pass.enableScissor(scissorX, scissorY, scissorW, scissorH); RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", info.dynamicUniforms()); - pass.setVertexBuffer(0, buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(buffer.getGpuBuffer(), 0, buffer.getGpuBuffer().size())); pass.setIndexBuffer(info.ibo(), info.autoIndices().type()); - pass.drawIndexed(0, 0, info.indexCount(), 1); + pass.drawIndexed(info.indexCount(), 1, 0, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/TextRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/TextRenderer.java index f58acaf6..d0ffac76 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/TextRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/TextRenderer.java @@ -88,7 +88,7 @@ public float getWidth(String text, float scale, TtfFontLoader fontLoader) { } public void setScissor(int x, int y, int width, int height) { - if (x < 0 || y < 0) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { return; } textRenderer.setScissor(x, y, width, height); diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/TextureRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/TextureRenderer.java index 7fa9dc2b..de88610e 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/TextureRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/TextureRenderer.java @@ -6,6 +6,8 @@ import com.github.epsilon.graphics.LuminRenderSystem; import com.github.epsilon.graphics.LuminTexture; import com.github.epsilon.graphics.buffer.LuminRingBuffer; +import com.mojang.blaze3d.GpuFormat; +import com.mojang.blaze3d.PrimitiveTopology; import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.platform.NativeImage; @@ -26,8 +28,8 @@ import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; import static com.github.epsilon.Constants.mc; @@ -145,10 +147,10 @@ public void draw() { if (colorView == null) return; GpuBufferSlice dynamicUniforms = RenderSystem.getDynamicUniforms().writeTransform( - RenderSystem.getModelViewMatrix(), + RenderSystem.getModelViewMatrixCopy(), new Vector4f(1, 1, 1, 1), new Vector3f(0, 0, 0), - TextureTransform.DEFAULT_TEXTURING.getMatrix() + TextureTransform.DEFAULT_TEXTURING.createMatrix() ); for (Map.Entry entry : batches.entrySet()) { @@ -161,7 +163,7 @@ public void draw() { } int indexCount = (batch.vertexCount / 4) * 6; - RenderSystem.AutoStorageIndexBuffer autoIndices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); + RenderSystem.AutoStorageIndexBuffer autoIndices = RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS); GpuBuffer ibo = autoIndices.getBuffer(indexCount); LuminTexture texture; @@ -177,7 +179,7 @@ public void draw() { try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( () -> "Rounded Texture Draw", - colorView, OptionalInt.empty(), + colorView, Optional.empty(), null, OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.TEXTURE); @@ -185,12 +187,11 @@ public void draw() { RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", dynamicUniforms); - // 使用 RingBuffer 当前指向的 GpuBuffer - pass.setVertexBuffer(0, batch.buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(batch.buffer.getGpuBuffer(), 0, batch.buffer.getGpuBuffer().size())); pass.setIndexBuffer(ibo, autoIndices.type()); pass.bindTexture("Sampler0", texture.getTextureView(), texture.getSampler()); - pass.drawIndexed(0, 0, indexCount, 1); + pass.drawIndexed(indexCount, 1, 0, 0, 0); } } } @@ -217,7 +218,7 @@ private LuminTexture loadTexture(Identifier identifier, boolean useLinearFilter) } var device = RenderSystem.getDevice(); - GpuTexture texture = device.createTexture(identifier.toString(), GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, TextureFormat.RGBA8, image.getWidth(), image.getHeight(), 1, 1); + GpuTexture texture = device.createTexture(identifier.toString(), GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, GpuFormat.RGBA8_UNORM, image.getWidth(), image.getHeight(), 1, 1); device.createCommandEncoder().writeToTexture(texture, image); diff --git a/common/src/main/java/com/github/epsilon/graphics/renderers/TriangleRenderer.java b/common/src/main/java/com/github/epsilon/graphics/renderers/TriangleRenderer.java index 26383be9..46fc5990 100644 --- a/common/src/main/java/com/github/epsilon/graphics/renderers/TriangleRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/renderers/TriangleRenderer.java @@ -16,8 +16,8 @@ import org.lwjgl.system.MemoryUtil; import java.awt.*; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class TriangleRenderer implements IRenderer { @@ -32,13 +32,13 @@ public class TriangleRenderer implements IRenderer { private boolean scissorEnabled = false; private int scissorX, scissorY, scissorW, scissorH; - private TriangleRenderer() { - } - public static TriangleRenderer create() { return RendererHolder.INSTANCE.register(new TriangleRenderer()); } + private TriangleRenderer() { + } + public void addChevronTriangle(float centerX, float centerY, float size, float progress, Color color) { buffer.tryMap(); @@ -94,6 +94,10 @@ private void addVertex(float vx, float vy, int color) { } public void setScissor(int x, int y, int width, int height) { + if (x < 0 || y < 0 || width <= 0 || height <= 0) { + return; + } + scissorEnabled = true; scissorX = x; scissorY = y; @@ -120,15 +124,15 @@ public void draw() { if (colorView == null) return; GpuBufferSlice dynamicUniforms = RenderSystem.getDynamicUniforms().writeTransform( - RenderSystem.getModelViewMatrix(), + RenderSystem.getModelViewMatrixCopy(), new Vector4f(1, 1, 1, 1), new Vector3f(0, 0, 0), - TextureTransform.DEFAULT_TEXTURING.getMatrix() + TextureTransform.DEFAULT_TEXTURING.createMatrix() ); try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( () -> "Triangle Draw", - colorView, OptionalInt.empty(), + colorView, Optional.empty(), depthView, OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.TRIANGLE); @@ -137,8 +141,8 @@ public void draw() { } RenderSystem.bindDefaultUniforms(pass); pass.setUniform("DynamicTransforms", dynamicUniforms); - pass.setVertexBuffer(0, buffer.getGpuBuffer()); - pass.draw(0, vertexCount); + pass.setVertexBuffer(0, new GpuBufferSlice(buffer.getGpuBuffer(), 0, buffer.getGpuBuffer().size())); + pass.draw(vertexCount, 1, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/shaders/BlurShader.java b/common/src/main/java/com/github/epsilon/graphics/shaders/BlurShader.java index 936bfa92..1c957788 100644 --- a/common/src/main/java/com/github/epsilon/graphics/shaders/BlurShader.java +++ b/common/src/main/java/com/github/epsilon/graphics/shaders/BlurShader.java @@ -1,7 +1,9 @@ package com.github.epsilon.graphics.shaders; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.mojang.blaze3d.GpuFormat; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.Std140Builder; import com.mojang.blaze3d.buffers.Std140SizeCalculator; import com.mojang.blaze3d.pipeline.*; @@ -14,7 +16,7 @@ import net.minecraft.resources.Identifier; import java.awt.*; -import java.util.OptionalInt; +import java.util.Optional; import static com.github.epsilon.Constants.mc; @@ -44,8 +46,8 @@ private void ensureProgram() { .withLocation(ResourceLocationUtils.getIdentifier("pipeline/blur")) .withVertexShader(identifier) .withFragmentShader(identifier) - .withUniform("BlurUniforms", UniformType.UNIFORM_BUFFER) - .withSampler("InputSampler") + .withBindGroupLayout(BindGroupLayout.builder().withUniform("BlurUniforms", UniformType.UNIFORM_BUFFER).build()) + .withBindGroupLayout(BindGroupLayout.builder().withSampler("InputSampler").build()) .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT)) .withCull(false) .build(); @@ -55,7 +57,7 @@ private void ensureProgram() { public void render(float x, float y, float width, float height, float rTL, float rTR, float rBR, float rBL, Color color, float blurStrength) { this.ensureProgram(); - RenderTarget fb = mc.getMainRenderTarget(); + RenderTarget fb = mc.gameRenderer.mainRenderTarget(); if (fb.getColorTexture() == null || fb.getColorTextureView() == null) { return; } @@ -64,7 +66,7 @@ public void render(float x, float y, float width, float height, float rTL, float int fbHeight = mc.getWindow().getHeight(); if (input == null) { - input = new TextureTarget("Lumin Blur Input", fbWidth, fbHeight, false); + input = new TextureTarget("Lumin Blur Input", fbWidth, fbHeight, false, GpuFormat.RGBA8_UNORM); } if (this.input.width != fbWidth || this.input.height != fbHeight) { @@ -92,7 +94,7 @@ public void render(float x, float y, float width, float height, float rTL, float fb.width, fb.height ); - try (GpuBuffer.MappedView view = encoder.mapBuffer(this.uniforms, false, true)) { + try (GpuBufferSlice.MappedView view = this.uniforms.map(false, true)) { Std140Builder builder = Std140Builder.intoBuffer(view.data()); builder.putVec3(fb.width, fb.height, quality); builder.putVec4(pxW, pxH, pxX, pxY); @@ -103,14 +105,14 @@ public void render(float x, float y, float width, float height, float rTL, float try (RenderPass renderPass = encoder.createRenderPass( () -> "Lumin Blur", fb.getColorTextureView(), - OptionalInt.empty() + Optional.empty() )) { renderPass.setPipeline(pipeline); renderPass.enableScissor((int) pxX, (int) pxY, Math.max(0, (int) pxW), Math.max(0, (int) pxH)); RenderSystem.bindDefaultUniforms(renderPass); renderPass.setUniform("BlurUniforms", this.uniforms); renderPass.bindTexture("InputSampler", input.getColorTextureView(), RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR)); - renderPass.draw(0, 3); + renderPass.draw(3, 1, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/shaders/FXAAShader.java b/common/src/main/java/com/github/epsilon/graphics/shaders/FXAAShader.java index d38bda22..868fff84 100644 --- a/common/src/main/java/com/github/epsilon/graphics/shaders/FXAAShader.java +++ b/common/src/main/java/com/github/epsilon/graphics/shaders/FXAAShader.java @@ -1,9 +1,12 @@ package com.github.epsilon.graphics.shaders; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.mojang.blaze3d.GpuFormat; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.Std140Builder; import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import com.mojang.blaze3d.pipeline.BindGroupLayout; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.pipeline.TextureTarget; @@ -15,7 +18,7 @@ import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.resources.Identifier; -import java.util.OptionalInt; +import java.util.Optional; import static com.github.epsilon.Constants.mc; @@ -43,8 +46,8 @@ private void ensureProgram() { .withLocation(ResourceLocationUtils.getIdentifier("pipeline/fxaa")) .withVertexShader(vertexShader) .withFragmentShader(fragmentShader) - .withUniform("FxaaInfo", UniformType.UNIFORM_BUFFER) - .withSampler("InputSampler") + .withBindGroupLayout(BindGroupLayout.builder().withUniform("FxaaInfo", UniformType.UNIFORM_BUFFER).build()) + .withBindGroupLayout(BindGroupLayout.builder().withSampler("InputSampler").build()) .withCull(false) .build(); } @@ -55,7 +58,7 @@ private void ensureInput(RenderTarget framebuffer) { int fbHeight = framebuffer.height; if (this.input == null) { - this.input = new TextureTarget("Epsilon FXAA Input", fbWidth, fbHeight, false); + this.input = new TextureTarget("Epsilon FXAA Input", fbWidth, fbHeight, false, GpuFormat.RGBA8_UNORM); } if (this.input.width != fbWidth || this.input.height != fbHeight) { @@ -64,7 +67,7 @@ private void ensureInput(RenderTarget framebuffer) { } public void renderMainTarget() { - render(mc.getMainRenderTarget()); + render(mc.gameRenderer.mainRenderTarget()); } public void render(RenderTarget framebuffer) { @@ -92,7 +95,7 @@ public void render(RenderTarget framebuffer) { framebuffer.width, framebuffer.height ); - try (GpuBuffer.MappedView view = encoder.mapBuffer(this.uniforms, false, true)) { + try (GpuBufferSlice.MappedView view = this.uniforms.map(false, true)) { Std140Builder.intoBuffer(view.data()) .putVec4(framebuffer.width, framebuffer.height, 1.0f / framebuffer.width, 1.0f / framebuffer.height); } @@ -100,13 +103,13 @@ public void render(RenderTarget framebuffer) { try (RenderPass renderPass = encoder.createRenderPass( () -> "Epsilon FXAA", framebuffer.getColorTextureView(), - OptionalInt.empty() + Optional.empty() )) { renderPass.setPipeline(this.pipeline); RenderSystem.bindDefaultUniforms(renderPass); renderPass.setUniform("FxaaInfo", this.uniforms); renderPass.bindTexture("InputSampler", this.input.getColorTextureView(), RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR)); - renderPass.draw(0, 6); + renderPass.draw(6, 1, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/shaders/FilterShader.java b/common/src/main/java/com/github/epsilon/graphics/shaders/FilterShader.java index b6872641..b4ae7231 100644 --- a/common/src/main/java/com/github/epsilon/graphics/shaders/FilterShader.java +++ b/common/src/main/java/com/github/epsilon/graphics/shaders/FilterShader.java @@ -1,9 +1,12 @@ package com.github.epsilon.graphics.shaders; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.mojang.blaze3d.GpuFormat; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.Std140Builder; import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import com.mojang.blaze3d.pipeline.BindGroupLayout; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.pipeline.TextureTarget; @@ -15,7 +18,7 @@ import net.minecraft.client.renderer.RenderPipelines; import java.awt.*; -import java.util.OptionalInt; +import java.util.Optional; import static com.github.epsilon.Constants.mc; @@ -40,8 +43,8 @@ private void ensureProgram() { .withLocation(ResourceLocationUtils.getIdentifier("pipeline/filter")) .withVertexShader(ResourceLocationUtils.getIdentifier("fullscreen")) .withFragmentShader(ResourceLocationUtils.getIdentifier("filter")) - .withUniform("FilterColor", UniformType.UNIFORM_BUFFER) - .withSampler("InputSampler") + .withBindGroupLayout(BindGroupLayout.builder().withUniform("FilterColor", UniformType.UNIFORM_BUFFER).build()) + .withBindGroupLayout(BindGroupLayout.builder().withSampler("InputSampler").build()) .withCull(false) .build(); } @@ -52,7 +55,7 @@ private void ensureInput(RenderTarget framebuffer) { int fbHeight = framebuffer.height; if (this.input == null) { - this.input = new TextureTarget("Epsilon Filter Input", fbWidth, fbHeight, false); + this.input = new TextureTarget("Epsilon Filter Input", fbWidth, fbHeight, false, GpuFormat.RGBA8_UNORM); } if (this.input.width != fbWidth || this.input.height != fbHeight) { @@ -61,7 +64,7 @@ private void ensureInput(RenderTarget framebuffer) { } public void renderMainTarget(Color color) { - render(mc.getMainRenderTarget(), color); + render(mc.gameRenderer.mainRenderTarget(), color); } public void render(RenderTarget framebuffer, Color color) { @@ -89,7 +92,7 @@ public void render(RenderTarget framebuffer, Color color) { framebuffer.width, framebuffer.height ); - try (GpuBuffer.MappedView view = encoder.mapBuffer(this.uniforms, false, true)) { + try (GpuBufferSlice.MappedView view = this.uniforms.map(false, true)) { Std140Builder.intoBuffer(view.data()) .putVec4(color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f, color.getAlpha() / 255.0f); } @@ -97,13 +100,13 @@ public void render(RenderTarget framebuffer, Color color) { try (RenderPass renderPass = encoder.createRenderPass( () -> "Epsilon Filter", framebuffer.getColorTextureView(), - OptionalInt.empty() + Optional.empty() )) { renderPass.setPipeline(this.pipeline); RenderSystem.bindDefaultUniforms(renderPass); renderPass.setUniform("FilterColor", this.uniforms); renderPass.bindTexture("InputSampler", this.input.getColorTextureView(), RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR)); - renderPass.draw(0, 6); + renderPass.draw(6, 1, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/shaders/GlslSandBox.java b/common/src/main/java/com/github/epsilon/graphics/shaders/GlslSandBox.java index b80c2e43..c2298e2d 100644 --- a/common/src/main/java/com/github/epsilon/graphics/shaders/GlslSandBox.java +++ b/common/src/main/java/com/github/epsilon/graphics/shaders/GlslSandBox.java @@ -3,8 +3,10 @@ import com.github.epsilon.assets.resources.ResourceLocationUtils; import com.github.epsilon.graphics.LuminRenderSystem; import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.Std140Builder; import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import com.mojang.blaze3d.pipeline.BindGroupLayout; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.shaders.UniformType; import com.mojang.blaze3d.systems.RenderPass; @@ -16,8 +18,8 @@ import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; import static com.github.epsilon.Constants.mc; @@ -46,7 +48,7 @@ private RenderPipeline getOrCreatePipeline(Identifier fragmentShader) { .withLocation(Identifier.fromNamespaceAndPath(shader.getNamespace(), "pipelines/glsl_sandbox/" + shader.getPath().replace('/', '_'))) .withVertexShader(FULLSCREEN_VERTEX) .withFragmentShader(shader) - .withUniform("GlslSandboxInfo", UniformType.UNIFORM_BUFFER) + .withBindGroupLayout(BindGroupLayout.builder().withUniform("GlslSandboxInfo", UniformType.UNIFORM_BUFFER).build()) .withCull(false) .build() ); @@ -77,8 +79,8 @@ public void render(Identifier fragmentShader, double mouseX, double mouseY, long ensureUniformBuffer(); final var activeTarget = LuminRenderSystem.getActiveTarget(); - final int targetWidth = activeTarget != null ? activeTarget.width() : mc.getMainRenderTarget().width; - final int targetHeight = activeTarget != null ? activeTarget.height() : mc.getMainRenderTarget().height; + final int targetWidth = activeTarget != null ? activeTarget.width() : mc.gameRenderer.mainRenderTarget().width; + final int targetHeight = activeTarget != null ? activeTarget.height() : mc.gameRenderer.mainRenderTarget().height; if (targetWidth <= 0 || targetHeight <= 0) return; @@ -92,7 +94,7 @@ public void render(Identifier fragmentShader, double mouseX, double mouseY, long float elapsedTime = (Util.getMillis() - startTimeMs) / 1000.0f; final var encoder = RenderSystem.getDevice().createCommandEncoder(); - try (GpuBuffer.MappedView mappedView = encoder.mapBuffer(sandboxInfoUniformBuf, false, true)) { + try (GpuBufferSlice.MappedView mappedView = sandboxInfoUniformBuf.map(false, true)) { Std140Builder.intoBuffer(mappedView.data()) .putVec4(targetWidth, targetHeight, elapsedTime, 0.0f) .putVec4(mouseUvX, mouseUvY, mousePxX, mousePxY); @@ -100,13 +102,13 @@ public void render(Identifier fragmentShader, double mouseX, double mouseY, long try (RenderPass pass = encoder.createRenderPass( () -> "Lumin GLSL Sandbox", - colorView, OptionalInt.empty(), + colorView, Optional.empty(), LuminRenderSystem.resolveDepthView(), OptionalDouble.empty()) ) { pass.setPipeline(getOrCreatePipeline(fragmentShader)); RenderSystem.bindDefaultUniforms(pass); pass.setUniform("GlslSandboxInfo", sandboxInfoUniformBuf); - pass.draw(0, 6); + pass.draw(6, 1, 0, 0); } } diff --git a/common/src/main/java/com/github/epsilon/graphics/text/StaticFontLoader.java b/common/src/main/java/com/github/epsilon/graphics/text/StaticFontLoader.java index 814b8b77..69b91555 100644 --- a/common/src/main/java/com/github/epsilon/graphics/text/StaticFontLoader.java +++ b/common/src/main/java/com/github/epsilon/graphics/text/StaticFontLoader.java @@ -1,5 +1,6 @@ package com.github.epsilon.graphics.text; +import com.github.epsilon.assets.holders.RendererHolder; import com.github.epsilon.assets.resources.ResourceLocationUtils; import com.github.epsilon.graphics.text.ttf.TtfFontLoader; @@ -13,4 +14,11 @@ public class StaticFontLoader { public static final TtfFontLoader OSAKA_CHIPS = new TtfFontLoader(ResourceLocationUtils.getIdentifier("fonts/osakachips.ttf")); + public static void destroyAll() { + DEFAULT.destroy(); + ICONS.destroy(); + JURA_LIGHT.destroy(); + OSAKA_CHIPS.destroy(); + } + } diff --git a/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfGlyphAtlas.java b/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfGlyphAtlas.java index 994653d5..b64b257d 100644 --- a/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfGlyphAtlas.java +++ b/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfGlyphAtlas.java @@ -1,12 +1,12 @@ package com.github.epsilon.graphics.text.ttf; import com.github.epsilon.graphics.LuminTexture; +import com.mojang.blaze3d.GpuFormat; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.AddressMode; import com.mojang.blaze3d.textures.FilterMode; import com.mojang.blaze3d.textures.GpuTexture; -import com.mojang.blaze3d.textures.TextureFormat; import java.util.OptionalDouble; @@ -23,7 +23,7 @@ public TtfGlyphAtlas(int atlasId) { final var texture = RenderSystem.getDevice().createTexture( () -> "Lumin-TtfGlyphAtlas", GpuTexture.USAGE_TEXTURE_BINDING | GpuTexture.USAGE_COPY_DST, - TextureFormat.RED8, + GpuFormat.R8_UNORM, SIZE, SIZE, 1, 1 ); diff --git a/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfTextRenderer.java b/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfTextRenderer.java index de94e17f..11b89546 100644 --- a/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfTextRenderer.java +++ b/common/src/main/java/com/github/epsilon/graphics/text/ttf/TtfTextRenderer.java @@ -7,6 +7,7 @@ import com.github.epsilon.graphics.text.GlyphDescriptor; import com.github.epsilon.graphics.text.ITextRenderer; import com.github.epsilon.modules.impl.ClientSetting; +import com.mojang.blaze3d.PrimitiveTopology; import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.buffers.Std140Builder; @@ -14,7 +15,6 @@ import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.GpuTextureView; -import com.mojang.blaze3d.vertex.VertexFormat; import net.minecraft.client.renderer.rendertype.TextureTransform; import net.minecraft.util.ARGB; import org.joml.Vector3f; @@ -24,8 +24,8 @@ import java.awt.*; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Optional; import java.util.OptionalDouble; -import java.util.OptionalInt; public class TtfTextRenderer implements ITextRenderer { @@ -108,7 +108,7 @@ public void draw() { ttfInfoUniformBuf = RenderSystem.getDevice().createBuffer(() -> "Lumin TTF UBO", GpuBuffer.USAGE_UNIFORM | GpuBuffer.USAGE_MAP_WRITE, size); } - try (GpuBuffer.MappedView mappedView = RenderSystem.getDevice().createCommandEncoder().mapBuffer(ttfInfoUniformBuf, false, true)) { + try (GpuBufferSlice.MappedView mappedView = ttfInfoUniformBuf.map(false, true)) { Std140Builder.intoBuffer(mappedView.data()).putFloat(0.5f).putFloat(ClientSetting.INSTANCE.fontAntiAliasing.getValue() ? 1.0f : 0.0f); } @@ -117,8 +117,8 @@ public void draw() { if (colorView == null) return; GpuBufferSlice dynamicUniforms = RenderSystem.getDynamicUniforms().writeTransform( - RenderSystem.getModelViewMatrix(), new Vector4f(1, 1, 1, 1), - new Vector3f(0, 0, 0), TextureTransform.DEFAULT_TEXTURING.getMatrix() + RenderSystem.getModelViewMatrixCopy(), new Vector4f(1, 1, 1, 1), + new Vector3f(0, 0, 0), TextureTransform.DEFAULT_TEXTURING.createMatrix() ); for (Map.Entry entry : batches.entrySet()) { @@ -135,12 +135,12 @@ public void draw() { int indexCount = (vertexCount / 4) * 6; RenderSystem.AutoStorageIndexBuffer autoIndices = - RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); + RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS); GpuBuffer ibo = autoIndices.getBuffer(indexCount); try (RenderPass pass = RenderSystem.getDevice().createCommandEncoder().createRenderPass( () -> "Lumin TTF Draw", - colorView, OptionalInt.empty(), + colorView, Optional.empty(), depthView, OptionalDouble.empty()) ) { pass.setPipeline(LuminRenderPipelines.TTF_FONT); @@ -152,11 +152,11 @@ public void draw() { pass.setUniform("DynamicTransforms", dynamicUniforms); pass.setUniform("TtfInfo", ttfInfoUniformBuf); - pass.setVertexBuffer(0, batch.buffer.getGpuBuffer()); + pass.setVertexBuffer(0, new GpuBufferSlice(batch.buffer.getGpuBuffer(), 0, batch.buffer.getGpuBuffer().size())); pass.setIndexBuffer(ibo, autoIndices.type()); pass.bindTexture("Sampler0", atlas.getTexture().getTextureView(), atlas.getTexture().getSampler()); - pass.drawIndexed(0, 0, indexCount, 1); + pass.drawIndexed(indexCount, 1, 0, 0, 0); } } } diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/LuminVulkanContext.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/LuminVulkanContext.java new file mode 100644 index 00000000..868fb598 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/LuminVulkanContext.java @@ -0,0 +1,147 @@ +package com.github.epsilon.graphics.vulkan; + +import com.mojang.blaze3d.systems.GpuDevice; +import com.mojang.blaze3d.systems.GpuDeviceBackend; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vulkan.VulkanDevice; +import com.mojang.blaze3d.vulkan.VulkanQueue; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandPoolCreateInfo; +import org.lwjgl.vulkan.VkDevice; + +import javax.annotation.Nullable; + +import java.lang.reflect.Field; +import java.nio.LongBuffer; + +import static org.lwjgl.vulkan.VK10.*; + +/** + * Vulkan 上下文封装。 + *

+ * 负责从 Blaze3D 后端提取 Vulkan 设备、VMA 分配器、队列与命令池句柄。 + * 当当前后端不是 Vulkan 时,本上下文会保持未初始化状态。 + */ +public class LuminVulkanContext { + + private @Nullable VulkanDevice blz3dDevice; + private @Nullable VkDevice device; + private long vma; + + // Queues + private VulkanQueue graphicsQueue; + private VulkanQueue computeQueue; + private VulkanQueue transferQueue; + + private long cmdPool; + + public LuminVulkanContext() { + GpuDeviceBackend backend = getBackend(); + if (!(backend instanceof VulkanDevice)) { + return; + } + + VulkanDevice blz3dDevice = (VulkanDevice) backend; + + this.blz3dDevice = blz3dDevice; + this.device = blz3dDevice.vkDevice(); + this.vma = blz3dDevice.vma(); + + this.graphicsQueue = blz3dDevice.graphicsQueue(); + this.computeQueue = blz3dDevice.computeQueue(); + this.transferQueue = blz3dDevice.transferQueue(); + + try (MemoryStack stack = MemoryStack.stackPush()) { + + VkCommandPoolCreateInfo poolInfo = VkCommandPoolCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO) + .flags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT) + .queueFamilyIndex(graphicsQueue.queueFamilyIndex()); + + final var cmdPool = stack.callocLong(1); + + VulkanUtils.crashIfFailure(blz3dDevice, + vkCreateCommandPool(device, poolInfo, null, cmdPool), + "Failed to create command pool" + ); + + this.cmdPool = cmdPool.get(); + + } + } + + /** + * 获取 Vulkan 逻辑设备。 + * + * @return Vulkan 设备句柄包装 + * @throws IllegalStateException 当当前后端不是 Vulkan 或尚未初始化时抛出 + */ + public VkDevice device() { + if (this.device == null) { + throw new IllegalStateException("Vulkan device is not initialized. Make sure to initialize the Vulkan context properly."); + } + return this.device; + } + + /** + * 获取 Blaze3D VulkanDevice 引用。 + */ + public VulkanDevice blz3dDevice() { + if (this.blz3dDevice == null) { + throw new IllegalStateException("VulkanDevice is not initialized. Make sure to initialize the Vulkan context properly."); + } + return this.blz3dDevice; + } + + /** + * 获取 VMA 分配器原生句柄。 + */ + public long vma() { + return this.vma; + } + + /** + * 获取用于分配命令缓冲区的命令池句柄。 + */ + public long cmdPool() { + return this.cmdPool; + } + + /** + * 判断当前是否处于可用的 Vulkan 后端。 + */ + public boolean isAvailable() { + return this.device != null; + } + + /** + * 获取图形队列。 + */ + public VulkanQueue graphicsQueue() { + return this.graphicsQueue; + } + + /** + * 获取计算队列。 + */ + public VulkanQueue computeQueue() { + return this.computeQueue; + } + + /** + * 获取传输队列。 + */ + public VulkanQueue transferQueue() { + return this.transferQueue; + } + + public void destroy() { + vkDestroyCommandPool(device, cmdPool, null); + } + + private static GpuDeviceBackend getBackend() { + return RenderSystem.getDevice().backend; + } + +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/Std430Writer.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/Std430Writer.java new file mode 100644 index 00000000..4413cfa4 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/Std430Writer.java @@ -0,0 +1,134 @@ +package com.github.epsilon.graphics.vulkan.buffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * std430 布局写入器。 + *

+ * 将常见标量/向量/矩阵按 std430 对齐规则写入目标 ByteBuffer。 + */ +public final class Std430Writer { + + private final ByteBuffer target; + + /** + * 创建写入器,并将目标缓冲区设为小端序。 + */ + public Std430Writer(ByteBuffer target) { + this.target = target.order(ByteOrder.LITTLE_ENDIAN); + } + + /** + * 清空写入位置。 + */ + public void clear() { + target.clear(); + } + + /** + * 当前写入位置。 + */ + public int position() { + return target.position(); + } + + /** + * 已写入字节数。 + */ + public int writtenBytes() { + return target.position(); + } + + /** + * 按给定对齐值补齐当前位置。 + */ + public Std430Writer align(int alignment) { + if (alignment <= 0 || (alignment & (alignment - 1)) != 0) { + throw new IllegalArgumentException("alignment must be a positive power of two"); + } + + int p = target.position(); + int aligned = (p + alignment - 1) & -alignment; + while (target.position() < aligned) { + target.put((byte) 0); + } + return this; + } + + /** + * 写入 int(4 字节对齐)。 + */ + public Std430Writer putInt(int value) { + align(4); + target.putInt(value); + return this; + } + + /** + * 写入 uint(按 int 存储)。 + */ + public Std430Writer putUInt(int value) { + return putInt(value); + } + + /** + * 写入 float(4 字节对齐)。 + */ + public Std430Writer putFloat(float value) { + align(4); + target.putFloat(value); + return this; + } + + /** + * 写入 vec2(8 字节对齐)。 + */ + public Std430Writer putVec2(float x, float y) { + align(8); + target.putFloat(x).putFloat(y); + return this; + } + + /** + * 写入 vec3(按 16 字节槽位写入,补齐一个 float)。 + */ + public Std430Writer putVec3(float x, float y, float z) { + // std430 vec3 has 16-byte base alignment in structs. + align(16); + target.putFloat(x).putFloat(y).putFloat(z).putFloat(0.0f); + return this; + } + + /** + * 写入 vec4(16 字节对齐)。 + */ + public Std430Writer putVec4(float x, float y, float z, float w) { + align(16); + target.putFloat(x).putFloat(y).putFloat(z).putFloat(w); + return this; + } + + /** + * 写入 mat4(16 个 float,16 字节对齐)。 + */ + public Std430Writer putMat4(float[] matrix16) { + if (matrix16.length != 16) { + throw new IllegalArgumentException("mat4 expects 16 floats"); + } + align(16); + for (float value : matrix16) { + target.putFloat(value); + } + return this; + } + + /** + * 按指定对齐写入原始字节。 + */ + public Std430Writer putBytes(ByteBuffer src, int alignment) { + align(alignment); + target.put(src.duplicate()); + return this; + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanBuffer.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanBuffer.java new file mode 100644 index 00000000..65d8b114 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanBuffer.java @@ -0,0 +1,143 @@ +package com.github.epsilon.graphics.vulkan.buffer; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.vma.VmaAllocationCreateInfo; +import org.lwjgl.util.vma.VmaAllocationInfo; +import org.lwjgl.vulkan.VkBufferCreateInfo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static org.lwjgl.util.vma.Vma.*; +import static org.lwjgl.vulkan.VK12.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK12.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + +/** + * Vulkan Buffer + VMA Allocation 封装。 + *

+ * 支持可选持久映射,适用于上传缓冲或回读缓冲等场景。 + */ +public final class VulkanBuffer implements AutoCloseable { + + private final long allocator; + private final long buffer; + private final long allocation; + private final long size; + private ByteBuffer mappedData; + + private VulkanBuffer(long allocator, long buffer, long allocation, long size, ByteBuffer mappedData) { + this.allocator = allocator; + this.buffer = buffer; + this.allocation = allocation; + this.size = size; + this.mappedData = mappedData; + } + + /** + * 创建一个 VulkanBuffer。 + */ + public static VulkanBuffer create( + long allocator, + long size, + int usage, + int memoryUsage, + int allocationFlags, + boolean mapped + ) { + try (MemoryStack stack = MemoryStack.stackPush()) { + VkBufferCreateInfo bufferCreateInfo = VkBufferCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO) + .size(size) + .usage(usage) + .sharingMode(VK_SHARING_MODE_EXCLUSIVE); + + VmaAllocationCreateInfo allocationCreateInfo = VmaAllocationCreateInfo.calloc(stack) + .usage(memoryUsage) + .flags(allocationFlags); + + var pBuffer = stack.mallocLong(1); + PointerBuffer pAllocation = stack.mallocPointer(1); + VmaAllocationInfo allocationInfo = mapped ? VmaAllocationInfo.calloc(stack) : null; + + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vmaCreateBuffer(allocator, bufferCreateInfo, allocationCreateInfo, pBuffer, pAllocation, allocationInfo), + "Can't create Vulkan buffer" + ); + + ByteBuffer mappedData = null; + if (mapped) { + long mappedPtr = allocationInfo.pMappedData(); + if (mappedPtr == MemoryUtil.NULL) { + PointerBuffer pMapped = stack.mallocPointer(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vmaMapMemory(allocator, pAllocation.get(0), pMapped), + "Can't map Vulkan buffer memory" + ); + mappedPtr = pMapped.get(0); + } + mappedData = MemoryUtil.memByteBuffer(mappedPtr, Math.toIntExact(size)).order(ByteOrder.LITTLE_ENDIAN); + } + + return new VulkanBuffer(allocator, pBuffer.get(0), pAllocation.get(0), size, mappedData); + } + } + + /** + * 返回 VkBuffer 句柄。 + */ + public long handle() { + return buffer; + } + + /** + * 返回 VMA Allocation 句柄。 + */ + public long allocation() { + return allocation; + } + + /** + * 返回 buffer 大小(字节)。 + */ + public long size() { + return size; + } + + /** + * 获取映射后的内存视图。 + * + * @throws IllegalStateException 当该 buffer 未映射时抛出 + */ + public ByteBuffer mappedData() { + if (mappedData == null) { + throw new IllegalStateException("Buffer is not mapped"); + } + return mappedData; + } + + /** + * 将 CPU 写入刷新到设备可见(用于非 coherent 内存)。 + */ + public void flush(long offset, long byteCount) { + vmaFlushAllocation(allocator, allocation, offset, byteCount); + } + + /** + * 将设备写入失效到 CPU 可见(用于回读)。 + */ + public void invalidate(long offset, long byteCount) { + vmaInvalidateAllocation(allocator, allocation, offset, byteCount); + } + + @Override + public void close() { + vmaDestroyBuffer(allocator, buffer, allocation); + mappedData = null; + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanOutputBuffer.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanOutputBuffer.java new file mode 100644 index 00000000..f5190ceb --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanOutputBuffer.java @@ -0,0 +1,149 @@ +package com.github.epsilon.graphics.vulkan.buffer; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkBufferCopy; +import org.lwjgl.vulkan.VkCommandBuffer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +import static org.lwjgl.util.vma.Vma.*; +import static org.lwjgl.vulkan.VK12.*; + +/** + * 计算输出回读缓冲封装。 + *

+ * 内部维护 GPU 输出 buffer 与 CPU 可读 readback buffer, + * 通过 vkCmdCopyBuffer 将结果复制到映射内存。 + * + *

同步由调用方负责: + *

    + *
  • 命令录制时插入合适的 pipeline barrier
  • + *
  • CPU 读取前等待 fence 完成
  • + *
+ */ +public final class VulkanOutputBuffer implements AutoCloseable { + + private final VulkanBuffer gpu; + private final VulkanBuffer readback; + + /** + * 创建输出回读缓冲。 + */ + public VulkanOutputBuffer(long allocator, long sizeBytes, int gpuUsageFlags) { + this.gpu = VulkanBuffer.create( + allocator, + sizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | gpuUsageFlags, + VMA_MEMORY_USAGE_GPU_ONLY, + 0, + false + ); + + this.readback = VulkanBuffer.create( + allocator, + sizeBytes, + VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VMA_MEMORY_USAGE_GPU_TO_CPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT, + true + ); + } + + /** + * 快速创建 storage buffer 用途的输出回读缓冲。 + */ + public static VulkanOutputBuffer storageBuffer(long allocator, long sizeBytes) { + return new VulkanOutputBuffer(allocator, sizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + } + + /** + * 返回 GPU 输出 buffer 句柄。 + */ + public long gpuBuffer() { + return gpu.handle(); + } + + /** + * 返回 readback buffer 句柄。 + */ + public long readbackBuffer() { + return readback.handle(); + } + + /** + * 返回缓冲总字节数。 + */ + public long size() { + return gpu.size(); + } + + /** + * 兼容方法:复制全部字节到 readback。 + */ + public void map(VkCommandBuffer cmdBuf) { + copyToReadback(cmdBuf, size()); + } + + /** + * 复制全部字节到 readback。 + */ + public void copyToReadback(VkCommandBuffer cmdBuf) { + copyToReadback(cmdBuf, size()); + } + + /** + * 复制指定字节数到 readback。 + */ + public void copyToReadback(VkCommandBuffer cmdBuf, long byteCount) { + Objects.requireNonNull(cmdBuf, "cmdBuf"); + validateRange(0, byteCount); + + try (MemoryStack stack = MemoryStack.stackPush()) { + VkBufferCopy.Buffer region = VkBufferCopy.calloc(1, stack) + .srcOffset(0) + .dstOffset(0) + .size(byteCount); + vkCmdCopyBuffer(cmdBuf, gpu.handle(), readback.handle(), region); + } + } + + /** + * 读取 [0, byteCount) 范围数据。 + */ + public ByteBuffer readMapped(long byteCount) { + return readMapped(0, byteCount); + } + + /** + * 读取指定范围数据,返回小端序切片。 + */ + public ByteBuffer readMapped(long offset, long byteCount) { + validateRange(offset, byteCount); + + readback.invalidate(offset, byteCount); + + ByteBuffer source = readback.mappedData().duplicate(); + int start = Math.toIntExact(offset); + int end = Math.toIntExact(offset + byteCount); + source.position(start); + source.limit(end); + return source.slice().order(ByteOrder.LITTLE_ENDIAN); + } + + private void validateRange(long offset, long byteCount) { + if (offset < 0 || byteCount < 0 || offset + byteCount > size()) { + throw new IllegalArgumentException("Invalid range: offset=" + offset + ", byteCount=" + byteCount); + } + } + + /** + * 销毁 readback 与 GPU buffer。 + */ + @Override + public void close() { + readback.close(); + gpu.close(); + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanStd430Buffer.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanStd430Buffer.java new file mode 100644 index 00000000..6f8219ad --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/buffer/VulkanStd430Buffer.java @@ -0,0 +1,130 @@ +package com.github.epsilon.graphics.vulkan.buffer; + +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkBufferCopy; +import org.lwjgl.vulkan.VkCommandBuffer; + +import java.util.Objects; + +import static org.lwjgl.util.vma.Vma.*; +import static org.lwjgl.vulkan.VK12.*; + +/** + * std430 数据上传缓冲封装。 + *

+ * 内部包含 staging(host visible) 与 gpu(device local) 两个 buffer, + * 通过 vkCmdCopyBuffer 将 CPU 写入数据上传到 GPU。 + */ +public final class VulkanStd430Buffer implements AutoCloseable { + + private final VulkanBuffer staging; + private final VulkanBuffer gpu; + private final Std430Writer writer; + + /** + * 创建 std430 上传缓冲。 + * + * @param gpuUsageFlags 目标 GPU buffer 用途位(会自动附加 TRANSFER_DST) + */ + public VulkanStd430Buffer(long allocator, long sizeBytes, int gpuUsageFlags) { + this.staging = VulkanBuffer.create( + allocator, + sizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT, + true + ); + + this.gpu = VulkanBuffer.create( + allocator, + sizeBytes, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | gpuUsageFlags, + VMA_MEMORY_USAGE_GPU_ONLY, + 0, + false + ); + + this.writer = new Std430Writer(staging.mappedData()); + } + + /** + * 快速创建 storage buffer 用途的 std430 上传缓冲。 + */ + public static VulkanStd430Buffer storageBuffer(long allocator, long sizeBytes) { + return new VulkanStd430Buffer(allocator, sizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + } + + /** + * 获取 std430 写入器。 + */ + public Std430Writer writer() { + return writer; + } + + /** + * 返回 GPU 侧 buffer 句柄。 + */ + public long gpuBuffer() { + return gpu.handle(); + } + + /** + * 返回 staging buffer 句柄。 + */ + public long stagingBuffer() { + return staging.handle(); + } + + /** + * 返回缓冲总字节数。 + */ + public long size() { + return gpu.size(); + } + + /** + * 兼容方法:将当前已写入数据复制到 GPU。 + */ + public void map(VkCommandBuffer cmdBuf) { + copy(cmdBuf, writer.writtenBytes()); + } + + /** + * 将当前已写入字节数复制到 GPU。 + */ + public void copy(VkCommandBuffer cmdBuf) { + copy(cmdBuf, writer.writtenBytes()); + } + + /** + * 复制指定字节数到 GPU。 + */ + public void copy(VkCommandBuffer cmdBuf, long byteCount) { + Objects.requireNonNull(cmdBuf, "cmdBuf"); + + if (byteCount < 0 || byteCount > size()) { + throw new IllegalArgumentException("byteCount out of range: " + byteCount); + } + + staging.flush(0, byteCount); + + try (MemoryStack stack = MemoryStack.stackPush()) { + VkBufferCopy.Buffer regions = VkBufferCopy.calloc(1, stack) + .srcOffset(0) + .dstOffset(0) + .size(byteCount); + + vkCmdCopyBuffer(cmdBuf, staging.handle(), gpu.handle(), regions); + } + } + + /** + * 销毁 staging 与 GPU buffer。 + */ + @Override + public void close() { + gpu.close(); + staging.close(); + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputePipeline.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputePipeline.java new file mode 100644 index 00000000..5e5666ad --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputePipeline.java @@ -0,0 +1,152 @@ +package com.github.epsilon.graphics.vulkan.compute; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorLayout; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorLayoutSpec; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkComputePipelineCreateInfo; +import org.lwjgl.vulkan.VkDevice; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineShaderStageCreateInfo; +import org.lwjgl.vulkan.VkShaderModuleCreateInfo; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import static org.lwjgl.vulkan.VK12.*; + +/** + * Vulkan 计算管线封装。 + *

+ * 内部负责创建 shader module、descriptor set layout、pipeline layout 与 compute pipeline。 + */ +public final class VulkanComputePipeline implements AutoCloseable { + + private final VkDevice device; + private final DescriptorLayout descriptorLayout; + private final long shaderModule; + private final long pipelineLayout; + private final long pipeline; + + /** + * 使用默认入口点 main 创建计算管线。 + */ + public VulkanComputePipeline(VkDevice device, ByteBuffer computeShaderSpirv, DescriptorLayoutSpec descriptorLayoutSpec) { + this(device, computeShaderSpirv, "main", descriptorLayoutSpec); + } + + /** + * 创建计算管线。 + * + * @param entryPoint shader 入口点名称 + */ + public VulkanComputePipeline(VkDevice device, ByteBuffer computeShaderSpirv, String entryPoint, DescriptorLayoutSpec descriptorLayoutSpec) { + this.device = Objects.requireNonNull(device, "device"); + Objects.requireNonNull(computeShaderSpirv, "computeShaderSpirv"); + Objects.requireNonNull(entryPoint, "entryPoint"); + Objects.requireNonNull(descriptorLayoutSpec, "descriptorLayoutSpec"); + + this.descriptorLayout = DescriptorLayout.create(device, descriptorLayoutSpec); + this.shaderModule = createShaderModule(device, computeShaderSpirv); + this.pipelineLayout = createPipelineLayout(device, this.descriptorLayout.handle()); + this.pipeline = createComputePipeline(device, this.shaderModule, this.pipelineLayout, entryPoint); + } + + /** + * 返回 VkPipeline 句柄。 + */ + public long pipeline() { + return pipeline; + } + + /** + * 返回 VkPipelineLayout 句柄。 + */ + public long pipelineLayout() { + return pipelineLayout; + } + + /** + * 返回 VkDescriptorSetLayout 句柄。 + */ + public long descriptorSetLayout() { + return descriptorLayout.handle(); + } + + /** + * 返回 DescriptorLayout 封装对象。 + */ + public DescriptorLayout descriptorLayout() { + return descriptorLayout; + } + + @Override + /** + * 销毁计算管线相关 Vulkan 资源。 + */ + public void close() { + vkDestroyPipeline(device, pipeline, null); + vkDestroyPipelineLayout(device, pipelineLayout, null); + vkDestroyShaderModule(device, shaderModule, null); + descriptorLayout.close(); + } + + private long createShaderModule(VkDevice device, ByteBuffer shaderSpirv) { + try (MemoryStack stack = MemoryStack.stackPush()) { + var createInfo = VkShaderModuleCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) + .pCode(shaderSpirv); + + var pShaderModule = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreateShaderModule(device, createInfo, null, pShaderModule), + "Can't create compute shader module" + ); + return pShaderModule.get(0); + } + } + + private long createPipelineLayout(VkDevice device, long descriptorSetLayout) { + try (MemoryStack stack = MemoryStack.stackPush()) { + var descriptorLayouts = stack.mallocLong(1).put(0, descriptorSetLayout); + + var createInfo = VkPipelineLayoutCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO) + .pSetLayouts(descriptorLayouts); + + var pPipelineLayout = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreatePipelineLayout(device, createInfo, null, pPipelineLayout), + "Can't create compute pipeline layout" + ); + return pPipelineLayout.get(0); + } + } + + private long createComputePipeline(VkDevice device, long shaderModule, long pipelineLayout, String entryPoint) { + try (MemoryStack stack = MemoryStack.stackPush()) { + var stage = VkPipelineShaderStageCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO) + .stage(VK_SHADER_STAGE_COMPUTE_BIT) + .module(shaderModule) + .pName(stack.UTF8(entryPoint)); + + var pipelineInfo = VkComputePipelineCreateInfo.calloc(1, stack) + .sType(VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO) + .stage(stage) + .layout(pipelineLayout); + + var pPipeline = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreateComputePipelines(device, VK_NULL_HANDLE, pipelineInfo, null, pPipeline), + "Can't create compute pipeline" + ); + return pPipeline.get(0); + } + } + +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputeUtils.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputeUtils.java new file mode 100644 index 00000000..83bd423d --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/compute/VulkanComputeUtils.java @@ -0,0 +1,226 @@ +package com.github.epsilon.graphics.vulkan.compute; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.vulkan.buffer.VulkanOutputBuffer; +import com.github.epsilon.graphics.vulkan.buffer.VulkanStd430Buffer; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.*; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static org.lwjgl.vulkan.KHRSynchronization2.*; +import static org.lwjgl.vulkan.VK12.*; + +public final class VulkanComputeUtils implements AutoCloseable { + + private final VkDevice device; + private final long cmdPool; + private final VkQueue queue; + private final VkCommandBuffer cmdBuf; + private final long fence; + + public VulkanComputeUtils() { + this( + LuminRenderSystem.vulkanContext.device(), + LuminRenderSystem.vulkanContext.cmdPool(), + LuminRenderSystem.vulkanContext.graphicsQueue().vkQueue() + ); + } + + public VulkanComputeUtils(VkDevice device, long cmdPool, VkQueue queue) { + this.device = device; + this.cmdPool = cmdPool; + this.queue = queue; + this.cmdBuf = allocateCommandBuffer(); + this.fence = createFence(); + } + + /** + * 录制并提交一次完整的「上传 → compute → 回读」流程,阻塞等待完成。 + * + * @param inputs 需要上传到 GPU 的输入缓冲(staging → gpu copy) + * @param output 计算结果输出缓冲(gpu → readback copy) + * @param pipeline 已创建的 compute pipeline + * @param descriptorSet 已绑定所有 buffer 的 descriptor set 句柄 + * @param groupCountX dispatch X 轴工作组数 + * @param groupCountY dispatch Y 轴工作组数 + * @param groupCountZ dispatch Z 轴工作组数 + * @param readbackBytes 需要回读的字节数(从 output gpu buffer offset 0 起) + */ + public void dispatchAndWait( + VulkanStd430Buffer[] inputs, + VulkanOutputBuffer output, + VulkanComputePipeline pipeline, + long descriptorSet, + int groupCountX, + int groupCountY, + int groupCountZ, + long readbackBytes + ) { + try (MemoryStack stack = MemoryStack.stackPush()) { + vkResetCommandBuffer(cmdBuf, 0); + vkResetFences(device, stack.longs(fence)); + + var beginInfo = VkCommandBufferBeginInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO) + .flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkBeginCommandBuffer(cmdBuf, beginInfo), + "Failed to begin command buffer" + ); + + for (VulkanStd430Buffer input : inputs) { + input.map(cmdBuf); + } + + var barriers = VkBufferMemoryBarrier2KHR.calloc(inputs.length, stack); + + for (int i = 0; i < inputs.length; i++) { + barriers.get(i) + .sType(VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR) + .srcStageMask(VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR) + .srcAccessMask(VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR) + .dstStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR) + .dstAccessMask(VK_ACCESS_2_SHADER_STORAGE_READ_BIT_KHR) + .srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .buffer(inputs[i].gpuBuffer()) + .offset(0) + .size(inputs[i].size()); + } + + var preComputeDep = VkDependencyInfoKHR.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR) + .pBufferMemoryBarriers(barriers); + vkCmdPipelineBarrier2KHR(cmdBuf, preComputeDep); + + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipeline()); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, + pipeline.pipelineLayout(), 0, + stack.longs(descriptorSet), null); + vkCmdDispatch(cmdBuf, groupCountX, groupCountY, groupCountZ); + + var postCompute = VkBufferMemoryBarrier2KHR.calloc(1, stack) + .sType(VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR) + .srcStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT_KHR) + .srcAccessMask(VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT_KHR) + .dstStageMask(VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR) + .dstAccessMask(VK_ACCESS_2_TRANSFER_READ_BIT_KHR) + .srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED) + .buffer(output.gpuBuffer()) + .offset(0) + .size(readbackBytes); + + var postComputeDep = VkDependencyInfoKHR.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR) + .pBufferMemoryBarriers(postCompute); + vkCmdPipelineBarrier2KHR(cmdBuf, postComputeDep); + + output.copyToReadback(cmdBuf, readbackBytes); + + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkEndCommandBuffer(cmdBuf), + "Failed to end command buffer" + ); + + var submitInfo = VkSubmitInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO) + .pCommandBuffers(stack.pointers(cmdBuf)); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkQueueSubmit(queue, submitInfo, fence), + "Failed to submit compute queue" + ); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkWaitForFences(device, stack.longs(fence), true, Long.MAX_VALUE), + "Failed to wait for compute fence" + ); + } + } + + /** + * 读取 output buffer 中 [offset, offset+byteCount) 区间的数据。 + *

+ * 必须在 {@link #dispatchAndWait} 返回之后调用,此时 fence 已确保 + * GPU → readback copy 完成,{@code readMapped} 内部会 invalidate + * host cache 以保证 CPU 看到最新数据。 + */ + public ByteBuffer readOutput(VulkanOutputBuffer output, long offset, long byteCount) { + return output.readMapped(offset, byteCount); + } + + /** + * 读取 output buffer 从 offset 0 起的 byteCount 字节。 + */ + public ByteBuffer readOutput(VulkanOutputBuffer output, long byteCount) { + return output.readMapped(0, byteCount); + } + + /** + * 读取 output buffer 中单个 float。 + */ + public float readFloat(VulkanOutputBuffer output, int index) { + ByteBuffer buf = output.readMapped((long) index * Float.BYTES, Float.BYTES); + return buf.order(ByteOrder.LITTLE_ENDIAN).getFloat(0); + } + + /** + * 批量读取 output buffer 中 count 个 float。 + */ + public float[] readFloats(VulkanOutputBuffer output, int count) { + if (count <= 0) return new float[0]; + ByteBuffer buf = output.readMapped(0, (long) count * Float.BYTES); + buf.order(ByteOrder.LITTLE_ENDIAN); + float[] result = new float[count]; + for (int i = 0; i < count; i++) { + result[i] = buf.getFloat(i * Float.BYTES); + } + return result; + } + + // ─── 内部 ─────────────────────────────────────────────────────────────── + + private VkCommandBuffer allocateCommandBuffer() { + try (MemoryStack stack = MemoryStack.stackPush()) { + var pCmd = stack.mallocPointer(1); + var allocInfo = VkCommandBufferAllocateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO) + .commandPool(cmdPool) + .level(VK_COMMAND_BUFFER_LEVEL_PRIMARY) + .commandBufferCount(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkAllocateCommandBuffers(device, allocInfo, pCmd), + "Failed to allocate compute command buffer" + ); + return new VkCommandBuffer(pCmd.get(0), device); + } + } + + private long createFence() { + try (MemoryStack stack = MemoryStack.stackPush()) { + var pFence = stack.mallocLong(1); + var info = VkFenceCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_FENCE_CREATE_INFO); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreateFence(device, info, null, pFence), + "Failed to create compute fence" + ); + return pFence.get(0); + } + } + + @Override + public void close() { + vkDestroyFence(device, fence, null); + vkFreeCommandBuffers(device, cmdPool, cmdBuf); + } +} + diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorBindingSpec.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorBindingSpec.java new file mode 100644 index 00000000..60a85f88 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorBindingSpec.java @@ -0,0 +1,65 @@ +package com.github.epsilon.graphics.vulkan.descriptor; + +import static org.lwjgl.vulkan.VK12.*; + +/** + * 描述 DescriptorSetLayout 中的单个 binding 规格。 + * + * @param binding binding 槽位 + * @param descriptorType Vulkan descriptor 类型 + * @param descriptorCount 数量(数组长度) + * @param stageFlags 可见 shader stage + */ +public record DescriptorBindingSpec(int binding, int descriptorType, int descriptorCount, int stageFlags) { + + public DescriptorBindingSpec { + if (binding < 0) { + throw new IllegalArgumentException("binding must be >= 0"); + } + if (descriptorCount <= 0) { + throw new IllegalArgumentException("descriptorCount must be > 0"); + } + } + + /** + * 创建计算阶段常用的 SSBO binding(数量为 1)。 + */ + public static DescriptorBindingSpec ssbo(int binding) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT); + } + + /** + * 创建 SSBO binding。 + */ + public static DescriptorBindingSpec ssbo(int binding, int descriptorCount, int stageFlags) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount, stageFlags); + } + + /** + * 创建 UBO binding。 + */ + public static DescriptorBindingSpec uniformBuffer(int binding, int descriptorCount, int stageFlags) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount, stageFlags); + } + + /** + * 创建组合采样纹理 binding(sampler + image)。 + */ + public static DescriptorBindingSpec combinedImageSampler(int binding, int descriptorCount, int stageFlags) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, descriptorCount, stageFlags); + } + + /** + * 创建 sampled image binding(不含 sampler)。 + */ + public static DescriptorBindingSpec sampledImage(int binding, int descriptorCount, int stageFlags) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, descriptorCount, stageFlags); + } + + /** + * 创建 storage image binding。 + */ + public static DescriptorBindingSpec storageImage(int binding, int descriptorCount, int stageFlags) { + return new DescriptorBindingSpec(binding, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, descriptorCount, stageFlags); + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayout.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayout.java new file mode 100644 index 00000000..9d813c98 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayout.java @@ -0,0 +1,74 @@ +package com.github.epsilon.graphics.vulkan.descriptor; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkDevice; + +import static org.lwjgl.vulkan.VK10.*; + +/** + * Vulkan DescriptorSetLayout 句柄封装。 + *

+ * 负责根据 {@link DescriptorLayoutSpec} 创建布局,并在 close 时销毁句柄。 + */ +public final class DescriptorLayout implements AutoCloseable { + + private final VkDevice device; + private final long handle; + + private DescriptorLayout(VkDevice device, long handle) { + this.device = device; + this.handle = handle; + } + + @SuppressWarnings("resource") + /** + * 根据布局规格创建 DescriptorSetLayout。 + */ + public static DescriptorLayout create(VkDevice device, DescriptorLayoutSpec spec) { + try (MemoryStack stack = MemoryStack.stackPush()) { + var bindings = VkDescriptorSetLayoutBinding.calloc(spec.bindings().size(), stack); + + for (int i = 0; i < spec.bindings().size(); i++) { + DescriptorBindingSpec binding = spec.bindings().get(i); + bindings.get(i) + .binding(binding.binding()) + .descriptorType(binding.descriptorType()) + .descriptorCount(binding.descriptorCount()) + .stageFlags(binding.stageFlags()) + .pImmutableSamplers(null); + } + + var createInfo = VkDescriptorSetLayoutCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO) + .pBindings(bindings); + + var pDescriptorSetLayout = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreateDescriptorSetLayout(device, createInfo, null, pDescriptorSetLayout), + "Can't create descriptor set layout for SSBO(std430)" + ); + + return new DescriptorLayout(device, pDescriptorSetLayout.get(0)); + } + } + + /** + * 返回底层 VkDescriptorSetLayout 句柄。 + */ + public long handle() { + return handle; + } + + @Override + /** + * 销毁 DescriptorSetLayout 句柄。 + */ + public void close() { + vkDestroyDescriptorSetLayout(device, handle, null); + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayoutSpec.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayoutSpec.java new file mode 100644 index 00000000..084b0aae --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorLayoutSpec.java @@ -0,0 +1,100 @@ +package com.github.epsilon.graphics.vulkan.descriptor; + +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.vulkan.VK12.VK_SHADER_STAGE_COMPUTE_BIT; + +/** + * 不可变的 DescriptorSetLayout 规格描述。 + */ +public final class DescriptorLayoutSpec { + + private final List bindings; + + private DescriptorLayoutSpec(List bindings) { + this.bindings = List.copyOf(bindings); + if (this.bindings.isEmpty()) { + throw new IllegalArgumentException("At least one descriptor binding is required"); + } + } + + /** + * 创建布局构建器。 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 返回布局中所有 binding 规格。 + */ + public List bindings() { + return bindings; + } + + /** + * DescriptorLayoutSpec 构建器。 + */ + public static final class Builder { + + private final List bindings = new ArrayList<>(); + + /** + * 追加一个通用 binding 规格。 + */ + public Builder addBinding(DescriptorBindingSpec binding) { + bindings.add(binding); + return this; + } + + /** + * 追加一个默认计算阶段 SSBO binding。 + */ + public Builder addSsbo(int binding) { + return addSsbo(binding, 1, VK_SHADER_STAGE_COMPUTE_BIT); + } + + /** + * 追加一个 SSBO binding。 + */ + public Builder addSsbo(int binding, int descriptorCount, int stageFlags) { + return addBinding(DescriptorBindingSpec.ssbo(binding, descriptorCount, stageFlags)); + } + + /** + * 追加一个组合采样纹理 binding。 + */ + public Builder addTexture(int binding, int descriptorCount, int stageFlags) { + return addBinding(DescriptorBindingSpec.combinedImageSampler(binding, descriptorCount, stageFlags)); + } + + /** + * 追加一个 sampled image binding。 + */ + public Builder addSampledImage(int binding, int descriptorCount, int stageFlags) { + return addBinding(DescriptorBindingSpec.sampledImage(binding, descriptorCount, stageFlags)); + } + + /** + * 追加一个 storage image binding。 + */ + public Builder addStorageImage(int binding, int descriptorCount, int stageFlags) { + return addBinding(DescriptorBindingSpec.storageImage(binding, descriptorCount, stageFlags)); + } + + /** + * 追加一个 UBO binding。 + */ + public Builder addUniformBuffer(int binding, int descriptorCount, int stageFlags) { + return addBinding(DescriptorBindingSpec.uniformBuffer(binding, descriptorCount, stageFlags)); + } + + /** + * 构建不可变布局规格。 + */ + public DescriptorLayoutSpec build() { + return new DescriptorLayoutSpec(bindings); + } + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorSetWrite.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorSetWrite.java new file mode 100644 index 00000000..a0fbef89 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/DescriptorSetWrite.java @@ -0,0 +1,82 @@ +package com.github.epsilon.graphics.vulkan.descriptor; + +import static org.lwjgl.vulkan.VK12.*; + +/** + * 描述一次 vkUpdateDescriptorSets 的单个写入项。 + * + *

该结构同时支持 buffer 与 image 类型 descriptor, + * 实际使用时由 descriptorType 决定字段生效范围。

+ */ +public record DescriptorSetWrite( + int binding, + int descriptorType, + int descriptorCount, + long buffer, + long offset, + long range, + long sampler, + long imageView, + int imageLayout +) { + + public DescriptorSetWrite { + if (binding < 0) { + throw new IllegalArgumentException("binding must be >= 0"); + } + if (descriptorCount <= 0) { + throw new IllegalArgumentException("descriptorCount must be > 0"); + } + } + + /** + * 创建 storage buffer 写入项。 + */ + public static DescriptorSetWrite storageBuffer(int binding, long buffer, long range) { + return new DescriptorSetWrite(binding, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, buffer, 0L, range, VK_NULL_HANDLE, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED); + } + + /** + * 创建 uniform buffer 写入项。 + */ + public static DescriptorSetWrite uniformBuffer(int binding, long buffer, long range) { + return new DescriptorSetWrite(binding, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, buffer, 0L, range, VK_NULL_HANDLE, VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED); + } + + /** + * 创建 combined image sampler 写入项。 + */ + public static DescriptorSetWrite combinedImageSampler(int binding, long sampler, long imageView, int imageLayout) { + return new DescriptorSetWrite(binding, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_NULL_HANDLE, 0L, 0L, sampler, imageView, imageLayout); + } + + /** + * 创建 sampled image 写入项。 + */ + public static DescriptorSetWrite sampledImage(int binding, long imageView, int imageLayout) { + return new DescriptorSetWrite(binding, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1, VK_NULL_HANDLE, 0L, 0L, VK_NULL_HANDLE, imageView, imageLayout); + } + + /** + * 创建 storage image 写入项。 + */ + public static DescriptorSetWrite storageImage(int binding, long imageView, int imageLayout) { + return new DescriptorSetWrite(binding, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_NULL_HANDLE, 0L, 0L, VK_NULL_HANDLE, imageView, imageLayout); + } + + /** + * 判断是否为 buffer 类 descriptor。 + */ + public boolean isBufferDescriptor() { + return descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + } + + /** + * 判断是否为 image 类 descriptor。 + */ + public boolean isImageDescriptor() { + return descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + || descriptorType == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE + || descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/VulkanResourceManager.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/VulkanResourceManager.java new file mode 100644 index 00000000..5840d376 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/descriptor/VulkanResourceManager.java @@ -0,0 +1,312 @@ +package com.github.epsilon.graphics.vulkan.descriptor; + +import com.github.epsilon.graphics.LuminRenderSystem; +import com.mojang.blaze3d.vulkan.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.*; + +import java.util.*; + +import static org.lwjgl.vulkan.VK12.*; + +/** + * Vulkan 资源管理器:负责 DescriptorPool/DescriptorSet 的复用与回收。 + *

+ * 该类会按布局规格聚合为池键进行分桶,优先复用可用池, + * 从而避免频繁创建与销毁 descriptor pool。 + */ +public final class VulkanResourceManager implements AutoCloseable { + + private static final int DEFAULT_POOL_MAX_SETS = 32; + + private final VkDevice device; + private final int poolMaxSets; + private final Map> poolsByKey = new HashMap<>(); + private boolean closed; + + /** + * 使用默认池容量创建资源管理器。 + */ + public VulkanResourceManager(VkDevice device) { + this(device, DEFAULT_POOL_MAX_SETS); + } + + /** + * 创建资源管理器。 + * + * @param poolMaxSets 单个 descriptor pool 可分配的 set 上限 + */ + public VulkanResourceManager(VkDevice device, int poolMaxSets) { + this.device = Objects.requireNonNull(device, "device"); + if (poolMaxSets <= 0) { + throw new IllegalArgumentException("poolMaxSets must be > 0"); + } + this.poolMaxSets = poolMaxSets; + } + + /** + * 分配并写入一个 descriptor set。 + *

+ * 返回的 {@link ManagedDescriptorSet} 需在不用时 close,以便归还到池。 + */ + public synchronized ManagedDescriptorSet allocateDescriptorSet( + long descriptorSetLayout, + DescriptorLayoutSpec layoutSpec, + List writes + ) { + ensureOpen(); + Objects.requireNonNull(layoutSpec, "layoutSpec"); + Objects.requireNonNull(writes, "writes"); + + PoolKey key = PoolKey.fromLayoutSpec(layoutSpec); + DescriptorPoolState pool = findOrCreatePool(key); + long descriptorSet = allocateFromPool(pool, descriptorSetLayout); + + try { + updateDescriptorSet(descriptorSet, layoutSpec.bindings(), writes); + pool.allocatedSets++; + return new ManagedDescriptorSet(this, pool, descriptorSet); + } catch (RuntimeException e) { + freeDescriptorSet(pool, descriptorSet); + throw e; + } + } + + private DescriptorPoolState findOrCreatePool(PoolKey key) { + List candidates = poolsByKey.computeIfAbsent(key, ignored -> new ArrayList<>()); + for (DescriptorPoolState candidate : candidates) { + if (candidate.allocatedSets < candidate.maxSets) { + return candidate; + } + } + + DescriptorPoolState created = createPoolState(key); + candidates.add(created); + return created; + } + + private DescriptorPoolState createPoolState(PoolKey key) { + try (MemoryStack stack = MemoryStack.stackPush()) { + VkDescriptorPoolSize.Buffer poolSizes = VkDescriptorPoolSize.calloc(key.descriptorCounts.size(), stack); + + int idx = 0; + for (var entry : key.descriptorCounts.entrySet()) { + int totalCount = Math.multiplyExact(entry.getValue(), poolMaxSets); + poolSizes.get(idx) + .type(entry.getKey()) + .descriptorCount(totalCount); + idx++; + } + + VkDescriptorPoolCreateInfo poolInfo = VkDescriptorPoolCreateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO) + .flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT) + .pPoolSizes(poolSizes) + .maxSets(poolMaxSets); + + var pPool = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkCreateDescriptorPool(device, poolInfo, null, pPool), + "Can't create reusable descriptor pool" + ); + + return new DescriptorPoolState(pPool.get(0), poolMaxSets); + } + } + + private long allocateFromPool(DescriptorPoolState pool, long descriptorSetLayout) { + try (MemoryStack stack = MemoryStack.stackPush()) { + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.calloc(stack) + .sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO) + .descriptorPool(pool.pool) + .pSetLayouts(stack.longs(descriptorSetLayout)); + + var pSet = stack.mallocLong(1); + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkAllocateDescriptorSets(device, allocInfo, pSet), + "Can't allocate descriptor set" + ); + return pSet.get(0); + } + } + + private void updateDescriptorSet( + long descriptorSet, + List bindingSpecs, + List writes + ) { + try (MemoryStack stack = MemoryStack.stackPush()) { + Map specByBinding = new LinkedHashMap<>(); + for (DescriptorBindingSpec spec : bindingSpecs) { + specByBinding.put(spec.binding(), spec); + } + + VkWriteDescriptorSet.Buffer vkWrites = VkWriteDescriptorSet.calloc(writes.size(), stack); + + for (int i = 0; i < writes.size(); i++) { + DescriptorSetWrite write = writes.get(i); + DescriptorBindingSpec expected = specByBinding.get(write.binding()); + if (expected == null) { + throw new IllegalArgumentException("Binding " + write.binding() + " is not declared in layout spec"); + } + if (expected.descriptorType() != write.descriptorType()) { + throw new IllegalArgumentException("Descriptor type mismatch at binding " + write.binding()); + } + if (write.descriptorCount() > expected.descriptorCount()) { + throw new IllegalArgumentException("Descriptor count exceeds layout declaration at binding " + write.binding()); + } + + VkWriteDescriptorSet vkWrite = vkWrites.get(i) + .sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET) + .dstSet(descriptorSet) + .dstBinding(write.binding()) + .dstArrayElement(0) + .descriptorType(write.descriptorType()) + .descriptorCount(write.descriptorCount()); + + if (write.isBufferDescriptor()) { + if (write.buffer() == VK_NULL_HANDLE) { + throw new IllegalArgumentException("Buffer handle is required for binding " + write.binding()); + } + VkDescriptorBufferInfo.Buffer bufferInfo = VkDescriptorBufferInfo.calloc(1, stack) + .buffer(write.buffer()) + .offset(write.offset()) + .range(write.range()); + vkWrite.pBufferInfo(bufferInfo); + } else if (write.isImageDescriptor()) { + if (write.imageView() == VK_NULL_HANDLE) { + throw new IllegalArgumentException("Image view is required for binding " + write.binding()); + } + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.calloc(1, stack) + .sampler(write.sampler()) + .imageView(write.imageView()) + .imageLayout(write.imageLayout()); + vkWrite.pImageInfo(imageInfo); + } else { + throw new IllegalArgumentException("Unsupported descriptor type " + write.descriptorType()); + } + } + + vkUpdateDescriptorSets(device, vkWrites, null); + } + } + + private void freeDescriptorSet(DescriptorPoolState pool, long descriptorSet) { + try (MemoryStack stack = MemoryStack.stackPush()) { + VulkanUtils.crashIfFailure( + LuminRenderSystem.vulkanContext.blz3dDevice(), + vkFreeDescriptorSets(device, pool.pool, stack.longs(descriptorSet)), + "Can't free descriptor set" + ); + } + } + + private synchronized void release(DescriptorPoolState pool, long descriptorSet) { + if (closed || descriptorSet == VK_NULL_HANDLE) { + return; + } + + freeDescriptorSet(pool, descriptorSet); + pool.allocatedSets = Math.max(0, pool.allocatedSets - 1); + } + + private void ensureOpen() { + if (closed) { + throw new IllegalStateException("VulkanResourceManager is already closed"); + } + } + + @Override + public synchronized void close() { + if (closed) return; + + for (List states : poolsByKey.values()) { + for (DescriptorPoolState state : states) { + vkDestroyDescriptorPool(device, state.pool, null); + } + } + + poolsByKey.clear(); + closed = true; + } + + /** + * 由资源管理器分配的 descriptor set 租约对象。 + *

+ * close 后会自动执行 vkFreeDescriptorSets 归还到所属池。 + */ + public static final class ManagedDescriptorSet implements AutoCloseable { + + private final VulkanResourceManager manager; + private final DescriptorPoolState pool; + private long descriptorSet; + + private ManagedDescriptorSet(VulkanResourceManager manager, DescriptorPoolState pool, long descriptorSet) { + this.manager = manager; + this.pool = pool; + this.descriptorSet = descriptorSet; + } + + /** + * 返回底层 VkDescriptorSet 句柄。 + */ + public long handle() { + return descriptorSet; + } + + /** + * 归还 descriptor set 到资源管理器。 + */ + @Override + public void close() { + if (descriptorSet == VK_NULL_HANDLE) { + return; + } + manager.release(pool, descriptorSet); + descriptorSet = VK_NULL_HANDLE; + } + } + + private static final class DescriptorPoolState { + + private final long pool; + private final int maxSets; + private int allocatedSets; + + private DescriptorPoolState(long pool, int maxSets) { + this.pool = pool; + this.maxSets = maxSets; + } + } + + private static final class PoolKey { + + private final Map descriptorCounts; + + private PoolKey(Map descriptorCounts) { + this.descriptorCounts = descriptorCounts; + } + + private static PoolKey fromLayoutSpec(DescriptorLayoutSpec spec) { + Map counts = new TreeMap<>(); + for (DescriptorBindingSpec binding : spec.bindings()) { + counts.merge(binding.descriptorType(), binding.descriptorCount(), Integer::sum); + } + return new PoolKey(Map.copyOf(counts)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PoolKey poolKey)) return false; + return Objects.equals(descriptorCounts, poolKey.descriptorCounts); + } + + @Override + public int hashCode() { + return Objects.hash(descriptorCounts); + } + } +} diff --git a/common/src/main/java/com/github/epsilon/graphics/vulkan/shader/Glsl2SpirVCompiler.java b/common/src/main/java/com/github/epsilon/graphics/vulkan/shader/Glsl2SpirVCompiler.java new file mode 100644 index 00000000..e62f9301 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/graphics/vulkan/shader/Glsl2SpirVCompiler.java @@ -0,0 +1,120 @@ +package com.github.epsilon.graphics.vulkan.shader; + +import org.lwjgl.BufferUtils; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import static org.lwjgl.util.shaderc.Shaderc.*; + +/** + * GLSL -> SPIR-V 编译器封装(基于 shaderc)。 + */ +public final class Glsl2SpirVCompiler implements AutoCloseable { + + private final String glslShaderSource; + private final int shaderKind; + private final String sourceName; + private final String entryPoint; + + private final long compiler; + private final long options; + private long result; + + /** + * 使用默认 compute 类型与 main 入口创建编译器。 + */ + public Glsl2SpirVCompiler(String glslShaderSource) { + this(glslShaderSource, shaderc_glsl_compute_shader, "inline.comp", "main"); + } + + /** + * 创建编译器。 + * + * @param shaderKind shaderc 的 shader 类型常量 + */ + public Glsl2SpirVCompiler(String glslShaderSource, int shaderKind, String sourceName, String entryPoint) { + this.glslShaderSource = Objects.requireNonNull(glslShaderSource, "glslShaderSource"); + this.shaderKind = shaderKind; + this.sourceName = Objects.requireNonNull(sourceName, "sourceName"); + this.entryPoint = Objects.requireNonNull(entryPoint, "entryPoint"); + + this.compiler = shaderc_compiler_initialize(); + if (this.compiler == 0L) { + throw new IllegalStateException("Failed to initialize shaderc compiler"); + } + + this.options = shaderc_compile_options_initialize(); + if (this.options == 0L) { + shaderc_compiler_release(this.compiler); + throw new IllegalStateException("Failed to initialize shaderc compile options"); + } + + shaderc_compile_options_set_target_env(this.options, shaderc_target_env_vulkan, shaderc_env_version_vulkan_1_2); + shaderc_compile_options_set_target_spirv(this.options, shaderc_spirv_version_1_5); + } + + /** + * 执行编译。 + * + * @throws IllegalStateException 当编译失败时抛出并携带错误日志 + */ + public void compile() { + if (result != 0L) { + shaderc_result_release(result); + result = 0L; + } + + result = shaderc_compile_into_spv( + compiler, + glslShaderSource, + shaderKind, + sourceName, + entryPoint, + options + ); + + if (result == 0L) { + throw new IllegalStateException("shaderc returned null result handle"); + } + + int status = shaderc_result_get_compilation_status(result); + if (status != shaderc_compilation_status_success) { + String error = shaderc_result_get_error_message(result); + throw new IllegalStateException("Shader compilation failed: " + error); + } + } + + /** + * 获取编译结果 SPIR-V 字节码副本。 + * + * @return 可独立持有的 ByteBuffer + */ + public ByteBuffer getSpirV() { + if (result == 0L) { + throw new IllegalStateException("Shader is not compiled. Call compile() first."); + } + + ByteBuffer compiled = shaderc_result_get_bytes(result); + if (compiled == null) { + throw new IllegalStateException("shaderc returned empty SPIR-V data"); + } + ByteBuffer copy = BufferUtils.createByteBuffer(compiled.remaining()); + copy.put(compiled.duplicate()); + copy.flip(); + return copy; + } + + /** + * 释放 shaderc 编译器相关资源。 + */ + @Override + public void close() { + if (result != 0L) { + shaderc_result_release(result); + result = 0L; + } + shaderc_compile_options_release(options); + shaderc_compiler_release(compiler); + } +} diff --git a/common/src/main/java/com/github/epsilon/gui/dropdown/DropdownRenderer.java b/common/src/main/java/com/github/epsilon/gui/dropdown/DropdownRenderer.java index b597f5d8..d2899653 100644 --- a/common/src/main/java/com/github/epsilon/gui/dropdown/DropdownRenderer.java +++ b/common/src/main/java/com/github/epsilon/gui/dropdown/DropdownRenderer.java @@ -1,6 +1,7 @@ package com.github.epsilon.gui.dropdown; import com.github.epsilon.graphics.renderers.*; +import net.minecraft.client.Minecraft; import static com.github.epsilon.Constants.mc; @@ -58,6 +59,15 @@ public void setScissor(float guiX, float guiY, float guiW, float guiH, int guiHe int y = Math.round((guiHeight - guiY - guiH) * scale); int w = Math.round(guiW * scale); int h = Math.round(guiH * scale); + + int fbWidth = Minecraft.getInstance().getWindow().getWidth(); + int fbHeight = Minecraft.getInstance().getWindow().getHeight(); + if (x < 0) { w += x; x = 0; } + if (y < 0) { h += y; y = 0; } + if (x + w > fbWidth) w = fbWidth - x; + if (y + h > fbHeight) h = fbHeight - y; + if (w <= 0 || h <= 0) return; + Slot slot = current(); setScissorOn(slot.shadow, x, y, w, h); setScissorOn(slot.roundRect, x, y, w, h); diff --git a/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorScreen.java b/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorScreen.java index 85882aca..2feff7b6 100644 --- a/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorScreen.java +++ b/common/src/main/java/com/github/epsilon/gui/hudeditor/HudEditorScreen.java @@ -247,7 +247,7 @@ public void onClose() { IMEFocusHelper.deactivate(); super.onClose(); - minecraft.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { + minecraft.gui.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { case Panel -> PanelScreen.INSTANCE; case Dropdown -> DropdownScreen.INSTANCE; }); diff --git a/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java b/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java index 558314c1..74ae87e8 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/PanelScreen.java @@ -217,7 +217,7 @@ public boolean mouseClicked(MouseButtonEvent event, boolean isDoubleClick) { PanelLayout.Layout layout = PanelLayout.compute(width, height, categoryRailPanel.getAnimatedWidth()); if (!layout.panel().contains(mouseX, mouseY)) { - if (ClientSetting.INSTANCE.closeOnOutside.getValue()) minecraft.setScreen(null); + if (ClientSetting.INSTANCE.closeOnOutside.getValue()) minecraft.gui.setScreen(null); return true; } if (!state.isClientSettingMode()) { diff --git a/common/src/main/java/com/github/epsilon/gui/panel/utils/IMEFocusHelper.java b/common/src/main/java/com/github/epsilon/gui/panel/utils/IMEFocusHelper.java index 33c4a6d0..3c0a5693 100644 --- a/common/src/main/java/com/github/epsilon/gui/panel/utils/IMEFocusHelper.java +++ b/common/src/main/java/com/github/epsilon/gui/panel/utils/IMEFocusHelper.java @@ -17,7 +17,7 @@ private IMEFocusHelper() { * Call this whenever a custom text field gains focus. */ public static void activate() { - Screen screen = mc.screen; + Screen screen = mc.gui.screen(); if (screen != null) { mc.onTextInputFocusChange(screen, true); } @@ -33,7 +33,7 @@ public static void activate() { * releasing the IME composition lock.

*/ public static void deactivate() { - Screen screen = mc.screen; + Screen screen = mc.gui.screen(); if (screen != null) { mc.onTextInputFocusChange(screen, false); } diff --git a/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java b/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java index a4017cce..2f43a57d 100644 --- a/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java +++ b/common/src/main/java/com/github/epsilon/gui/screen/MainMenuScreen.java @@ -41,12 +41,12 @@ public class MainMenuScreen extends Screen { private MainMenuScreen() { super(Component.literal("MainMenuScreen")); - entries.add(new MenuEntry("Singleplayer", () -> minecraft.setScreen(new SelectWorldScreen(this)))); + entries.add(new MenuEntry("Singleplayer", () -> minecraft.gui.setScreen(new SelectWorldScreen(this)))); entries.add(new MenuEntry("Multiplayer", () -> { Screen screen = this.minecraft.options.skipMultiplayerWarning ? new JoinMultiplayerScreen(this) : new SafetyScreen(this); - this.minecraft.setScreen(screen); + this.minecraft.gui.setScreen(screen); })); - entries.add(new MenuEntry("Options", () -> minecraft.setScreen(new OptionsScreen(this, minecraft.options, false)))); + entries.add(new MenuEntry("Options", () -> minecraft.gui.setScreen(new OptionsScreen(this, minecraft.options, false)))); entries.add(new MenuEntry("Quit", minecraft::stop)); } diff --git a/common/src/main/java/com/github/epsilon/managers/ModuleManager.java b/common/src/main/java/com/github/epsilon/managers/ModuleManager.java index 20a38697..2cf96d57 100644 --- a/common/src/main/java/com/github/epsilon/managers/ModuleManager.java +++ b/common/src/main/java/com/github/epsilon/managers/ModuleManager.java @@ -103,18 +103,18 @@ public void initModules() { Velocity.INSTANCE, // Render - AntiAlias.INSTANCE, AspectRatio.INSTANCE, CameraClip.INSTANCE, Chams.INSTANCE, ESP.INSTANCE, - Filter.INSTANCE, Fullbright.INSTANCE, HandsView.INSTANCE, Hat.INSTANCE, NameTags.INSTANCE, NoRender.INSTANCE, - //PopChams.INSTANCE, + PopChams.INSTANCE, + AntiAlias.INSTANCE, + Filter.INSTANCE, // Hud NotificationsHUD.INSTANCE, @@ -145,7 +145,7 @@ public List getModules() { @EventHandler private void onRender2D(Render2DEvent.HUD event) { - if (ClientUtils.isLoading() || mc.level == null || mc.screen instanceof HudEditorScreen) return; + if (ClientUtils.isLoading() || mc.level == null || mc.gui.screen() instanceof HudEditorScreen) return; for (Module m : modules) { if (m instanceof HudModule module && module.isEnabled()) { @@ -158,14 +158,14 @@ private void onRender2D(Render2DEvent.HUD event) { @EventHandler private void onKeyPress(KeyPressEvent event) { - if (mc.level == null || mc.screen != null || event.getKey() == GLFW.GLFW_KEY_UNKNOWN) return; + if (mc.level == null || mc.gui.screen() != null || event.getKey() == GLFW.GLFW_KEY_UNKNOWN) return; int keyCode = event.getKey(); int action = event.getAction(); ClientSetting cs = ClientSetting.INSTANCE; if (keyCode == cs.guiKeybind.getValue() && action == InputConstants.PRESS) { - mc.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { + mc.gui.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { case Panel -> PanelScreen.INSTANCE; case Dropdown -> DropdownScreen.INSTANCE; }); @@ -176,7 +176,7 @@ private void onKeyPress(KeyPressEvent event) { @EventHandler private void onMousePress(MousePressEvent event) { - if (mc.level != null && mc.screen == null) { + if (mc.level != null && mc.gui.screen() == null) { dispatchKeyBind(KeybindUtils.encodeMouseButton(event.getButton()), event.getAction()); } } diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinEquipmentLayerRenderer.java b/common/src/main/java/com/github/epsilon/mixins/MixinEquipmentLayerRenderer.java index ef27c09b..b9719a0e 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinEquipmentLayerRenderer.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinEquipmentLayerRenderer.java @@ -11,6 +11,7 @@ import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.equipment.EquipmentAsset; import org.spongepowered.asm.mixin.Mixin; @@ -21,7 +22,7 @@ public class MixinEquipmentLayerRenderer { @WrapOperation(method = "renderLayers(Lnet/minecraft/client/resources/model/EquipmentClientInfo$LayerType;Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/client/model/Model;Ljava/lang/Object;Lnet/minecraft/world/item/ItemStack;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;ILnet/minecraft/resources/Identifier;II)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/rendertype/RenderTypes;armorCutoutNoCull(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/rendertype/RenderType;")) private RenderType redirectRenderType(Identifier texture, Operation original, EquipmentClientInfo.LayerType layerType, ResourceKey equipmentAssetId, Model model, Object state, ItemStack itemStack) { - if (state instanceof EntityRenderState entityState && entityState.entityType == EntityType.PLAYER) { + if (state instanceof EntityRenderState entityState && entityState.entityType == EntityTypes.PLAYER) { Chams chamsModule = Chams.INSTANCE; if (chamsModule.isEnabled()) { return Chams.INSTANCE.getRenderType(texture); diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinGuiRenderer.java b/common/src/main/java/com/github/epsilon/mixins/MixinGuiRenderer.java index b76bc8ba..5f7c2508 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinGuiRenderer.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinGuiRenderer.java @@ -6,8 +6,6 @@ import com.mojang.blaze3d.buffers.GpuBufferSlice; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.render.GuiRenderer; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.SubmitNodeCollector; import net.minecraft.client.renderer.feature.FeatureRenderDispatcher; import net.minecraft.client.renderer.state.gui.GuiRenderState; import org.spongepowered.asm.mixin.Final; @@ -23,14 +21,6 @@ @Mixin(GuiRenderer.class) public class MixinGuiRenderer { - @Shadow - @Final - private MultiBufferSource.BufferSource bufferSource; - - @Shadow - @Final - private SubmitNodeCollector submitNodeCollector; - @Shadow @Final private FeatureRenderDispatcher featureRenderDispatcher; @@ -48,7 +38,7 @@ public class MixinGuiRenderer { private EpsilonGuiRenderer epsilon$guiRenderer; @Inject(method = "draw", at = @At("HEAD")) - private void onDrawHead(GpuBufferSlice fogBuffer, CallbackInfo ci) { + private void onDrawHead(CallbackInfo ci) { epsilon$ensureRenderers(); int mouseX = (int) mc.mouseHandler.getScaledXPos(mc.getWindow()); @@ -56,13 +46,13 @@ private void onDrawHead(GpuBufferSlice fogBuffer, CallbackInfo ci) { GuiGraphicsExtractor levelGuiGraphics = new GuiGraphicsExtractor(mc, epsilon$levelRenderState, mouseX, mouseY); EventBus.INSTANCE.post(new Render2DEvent.Level(levelGuiGraphics)); - epsilon$levelGuiRenderer.render(fogBuffer); + epsilon$levelGuiRenderer.render(); epsilon$levelGuiRenderer.endFrame(); GuiGraphicsExtractor guiGraphics = new GuiGraphicsExtractor(mc, epsilon$renderState, mouseX, mouseY); EventBus.INSTANCE.post(new Render2DEvent.HUD(guiGraphics)); - epsilon$guiRenderer.render(fogBuffer); + epsilon$guiRenderer.render(); epsilon$guiRenderer.endFrame(); } @@ -73,8 +63,6 @@ private void onDrawHead(GpuBufferSlice fogBuffer, CallbackInfo ci) { this.epsilon$levelRenderState = new GuiRenderState(); this.epsilon$levelGuiRenderer = new EpsilonGuiRenderer( this.epsilon$levelRenderState, - this.bufferSource, - this.submitNodeCollector, this.featureRenderDispatcher ); } @@ -82,8 +70,6 @@ private void onDrawHead(GpuBufferSlice fogBuffer, CallbackInfo ci) { this.epsilon$renderState = new GuiRenderState(); this.epsilon$guiRenderer = new EpsilonGuiRenderer( this.epsilon$renderState, - this.bufferSource, - this.submitNodeCollector, this.featureRenderDispatcher ); } diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinGui.java b/common/src/main/java/com/github/epsilon/mixins/MixinHud.java similarity index 90% rename from common/src/main/java/com/github/epsilon/mixins/MixinGui.java rename to common/src/main/java/com/github/epsilon/mixins/MixinHud.java index 383df5e0..0535f8d1 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinGui.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinHud.java @@ -2,15 +2,15 @@ import com.github.epsilon.modules.impl.render.NoRender; import net.minecraft.client.DeltaTracker; -import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.Hud; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(Gui.class) -public class MixinGui { +@Mixin(Hud.class) +public class MixinHud { @Inject(method = "extractEffects", at = @At("HEAD"), cancellable = true) private void onExtractEffects(GuiGraphicsExtractor graphics, DeltaTracker deltaTracker, CallbackInfo ci) { diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinItemInHandRenderer.java b/common/src/main/java/com/github/epsilon/mixins/MixinItemInHandRenderer.java index 88ccb8e5..f3bd8910 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinItemInHandRenderer.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinItemInHandRenderer.java @@ -47,12 +47,12 @@ public abstract class MixinItemInHandRenderer { @Shadow public ItemStack offHandItem; - @Inject(method = "renderArmWithItem", at = @At("HEAD")) + @Inject(method = "submitArmWithItem", at = @At("HEAD")) private void cacheBlockingState(AbstractClientPlayer player, float frameInterp, float xRot, InteractionHand hand, float attack, ItemStack itemStack, float inverseArmHeight, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, int lightCoords, CallbackInfo ci) { epsilon$blocked = HandsView.INSTANCE.shouldApplyBlockingAnimation(hand, itemStack); } - @Inject(method = "renderArmWithItem", at = @At("RETURN")) + @Inject(method = "submitArmWithItem", at = @At("RETURN")) private void clearBlockingState(AbstractClientPlayer player, float frameInterp, float xRot, InteractionHand hand, float attack, ItemStack itemStack, float inverseArmHeight, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, int lightCoords, CallbackInfo ci) { epsilon$blocked = false; } @@ -74,7 +74,7 @@ private void hideHotbarSwitchAnimation(CallbackInfo ci) { } } - @Inject(method = "renderArmWithItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemInHandRenderer;applyItemArmTransform(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/entity/HumanoidArm;F)V", ordinal = 2, shift = At.Shift.AFTER)) + @Inject(method = "submitArmWithItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemInHandRenderer;applyItemArmTransform(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/world/entity/HumanoidArm;F)V", ordinal = 2, shift = At.Shift.AFTER)) private void addSwingToEating(AbstractClientPlayer player, float frameInterp, float xRot, InteractionHand hand, float attack, ItemStack itemStack, float inverseArmHeight, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, int lightCoords, CallbackInfo ci) { HandsView handsView = HandsView.INSTANCE; if (handsView.isEnabled() && handsView.swingWhileUsing.getValue() && attack > 0.0F) { @@ -88,7 +88,7 @@ private void cancelSwingForBlocking(float attack, PoseStack poseStack, int inver if (epsilon$blocked) ci.cancel(); } - @Inject(method = "renderArmWithItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemInHandRenderer;renderItem(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;I)V", ordinal = 1, shift = At.Shift.BEFORE)) + @Inject(method = "submitArmWithItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemInHandRenderer;renderItem(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemDisplayContext;Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/SubmitNodeCollector;I)V", ordinal = 1, shift = At.Shift.BEFORE)) private void applyBlockingAnimation(AbstractClientPlayer player, float frameInterp, float xRot, InteractionHand hand, float attack, ItemStack itemStack, float inverseArmHeight, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, int lightCoords, CallbackInfo ci) { if (!epsilon$blocked) return; HumanoidArm arm = hand == InteractionHand.MAIN_HAND ? player.getMainArm() : player.getMainArm().getOpposite(); diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinLevelRenderer.java b/common/src/main/java/com/github/epsilon/mixins/MixinLevelRenderer.java index 01092551..a4cbca17 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinLevelRenderer.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinLevelRenderer.java @@ -20,8 +20,8 @@ @Mixin(LevelRenderer.class) public class MixinLevelRenderer { - @Inject(method = "renderLevel", at = @At("RETURN")) - private void onPostRenderLevel(GraphicsResourceAllocator resourceAllocator, DeltaTracker deltaTracker, boolean renderOutline, CameraRenderState cameraState, Matrix4fc modelViewMatrix, GpuBufferSlice terrainFog, Vector4f fogColor, boolean shouldRenderSky, ChunkSectionsToRender chunkSectionsToRender, CallbackInfo ci) { + @Inject(method = "render", at = @At("RETURN")) + private void onPostRenderLevel(GraphicsResourceAllocator resourceAllocator, DeltaTracker deltaTracker, boolean renderOutline, CameraRenderState cameraState, Matrix4fc modelViewMatrix, GpuBufferSlice terrainFog, Vector4f fogColor, boolean shouldRenderSky, CallbackInfo ci) { PoseStack poseStack = new PoseStack(); poseStack.mulPose(modelViewMatrix); EventBus.INSTANCE.post(new Render3DEvent(poseStack)); diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinLightmap.java b/common/src/main/java/com/github/epsilon/mixins/MixinLightmap.java index 378a528c..20b31561 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinLightmap.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinLightmap.java @@ -5,6 +5,8 @@ import com.mojang.blaze3d.textures.GpuTexture; import net.minecraft.client.renderer.Lightmap; import net.minecraft.client.renderer.state.LightmapRenderState; +import org.joml.Vector4f; +import org.joml.Vector4fc; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -22,7 +24,7 @@ public class MixinLightmap { @Inject(method = "render", at = @At("HEAD"), cancellable = true) private void onRender(LightmapRenderState renderState, CallbackInfo ci) { if (!Fullbright.INSTANCE.isGammaMode()) return; - RenderSystem.getDevice().createCommandEncoder().clearColorTexture(this.texture, -1); + RenderSystem.getDevice().createCommandEncoder().clearColorTexture(this.texture, new Vector4f(1f, 1f, 1f, 1f)); ci.cancel(); } diff --git a/common/src/main/java/com/github/epsilon/mixins/MixinLivingEntityRenderer.java b/common/src/main/java/com/github/epsilon/mixins/MixinLivingEntityRenderer.java index b2651394..7ec40afb 100644 --- a/common/src/main/java/com/github/epsilon/mixins/MixinLivingEntityRenderer.java +++ b/common/src/main/java/com/github/epsilon/mixins/MixinLivingEntityRenderer.java @@ -10,6 +10,7 @@ import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.LivingEntity; import org.joml.Vector2f; import org.spongepowered.asm.mixin.Mixin; @@ -28,7 +29,7 @@ public abstract class MixinLivingEntityRenderer guiMode = enumSetting("Gui Mode", GuiMode.Dropdown, _ -> { - mc.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { + mc.gui.setScreen(switch (ClientSetting.INSTANCE.guiMode.getValue()) { case Panel -> PanelScreen.INSTANCE; case Dropdown -> DropdownScreen.INSTANCE; }); }).group(sgGeneral); - private final ButtonSetting openHudEditor = buttonSetting("Open Hud Editor", () -> mc.setScreen(HudEditorScreen.INSTANCE)).group(sgGeneral); + private final ButtonSetting openHudEditor = buttonSetting("Open Hud Editor", () -> mc.gui.setScreen(HudEditorScreen.INSTANCE)).group(sgGeneral); public final BoolSetting i18nFallback = boolSetting("I18n Fallback", true, _ -> { TranslateHolder.INSTANCE.refresh(); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/AntiBot.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/AntiBot.java index f990d015..95f3f1de 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/AntiBot.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/AntiBot.java @@ -11,6 +11,7 @@ import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.level.GameType; import java.util.HashSet; @@ -64,7 +65,7 @@ public void onPacket(PacketEvent.Receive event) { } } } - } else if (event.getPacket() instanceof ClientboundAddEntityPacket packet && packet.getType() == EntityType.PLAYER) { + } else if (event.getPacket() instanceof ClientboundAddEntityPacket packet && packet.getType() == EntityTypes.PLAYER) { if (uuids.containsKey(packet.getUUID())) { uuids.remove(packet.getUUID()); ids.add(packet.getId()); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoClicker.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoClicker.java index 1b04e6ca..ee036f4d 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoClicker.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoClicker.java @@ -60,7 +60,7 @@ protected void onDisable() { @EventHandler public void onTick(TickEvent.Pre event) { - if (nullCheck() || mc.screen != null) return; + if (nullCheck() || mc.gui.screen() != null) return; long currentTime = System.currentTimeMillis(); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoHitCrystal.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoHitCrystal.java index b1020247..a0df36e4 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoHitCrystal.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/AutoHitCrystal.java @@ -99,7 +99,7 @@ protected void onEnable() { private void onTick(TickEvent.Pre event) { if (nullCheck()) return; - if (mc.screen != null) { + if (mc.gui.screen() != null) { return; } diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/CrystalAura.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/CrystalAura.java index 15b32b18..2e912366 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/CrystalAura.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/CrystalAura.java @@ -23,7 +23,7 @@ import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.boss.enderdragon.EndCrystal; -import net.minecraft.world.entity.monster.Slime; +import net.minecraft.world.entity.monster.cubemob.Slime; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; @@ -79,7 +79,7 @@ protected void onDisable() { @EventHandler public void onTick(TickEvent.Pre event) { - if (nullCheck() || mc.screen != null) return; + if (nullCheck() || mc.gui.screen() != null) return; boolean dontPlace = placeClock != 0; boolean dontBreak = breakClock != 0; diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/HoverTotem.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/HoverTotem.java index 35891e01..669a2cc2 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/HoverTotem.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/HoverTotem.java @@ -55,7 +55,7 @@ private int getRandomDelay() { private void onTick(TickEvent.Pre event) { if (nullCheck()) return; - if (mc.screen instanceof InventoryScreen inv) { + if (mc.gui.screen() instanceof InventoryScreen inv) { Slot hoveredSlot = inv.hoveredSlot; if (this.autoSwitch.getValue()) { diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/KeyPearl.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/KeyPearl.java index a93e3b74..a27ac5a3 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/KeyPearl.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/KeyPearl.java @@ -42,7 +42,7 @@ protected void onEnable() { @EventHandler private void onTick(TickEvent.Pre event) { - if (nullCheck() || mc.screen != null) return; + if (nullCheck() || mc.gui.screen() != null) return; boolean pressed = KeybindUtils.isPressed(activateKey.getValue()); if (!pressed) { diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/MaceAura.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/MaceAura.java index 88305a24..46d99eac 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/MaceAura.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/MaceAura.java @@ -19,7 +19,7 @@ import net.minecraft.util.Mth; import net.minecraft.world.InteractionHand; import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.monster.Slime; +import net.minecraft.world.entity.monster.cubemob.Slime; import net.minecraft.world.item.Items; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/PacketMine.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/PacketMine.java index 29c59b26..523ed0d0 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/PacketMine.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/PacketMine.java @@ -18,6 +18,8 @@ import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundSetCarriedItemPacket; import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; import net.minecraft.world.InteractionHand; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; @@ -238,7 +240,7 @@ private void onRender(Render3DEvent event) { renderFadeBoxes(event.getPoseStack()); if (secondPos != null && doubleBreak.getValue()) { - if (farCancel.getValue() && Math.sqrt(mc.player.getEyePosition().distanceToSqr(secondPos.getCenter())) > range.getValue()) { + if (farCancel.getValue() && Math.sqrt(mc.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(secondPos))) > range.getValue()) { secondPos = null; return; } @@ -281,7 +283,7 @@ private void onRender(Render3DEvent event) { } } if (targetPos != null) { - if (farCancel.getValue() && Math.sqrt(mc.player.getEyePosition().distanceToSqr(targetPos.getCenter())) > range.getValue()) { + if (farCancel.getValue() && Math.sqrt(mc.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(targetPos))) > range.getValue()) { targetPos = null; return; } @@ -567,7 +569,7 @@ private void mainBlockRender(PoseStack stack) { Render3DUtils.drawOutlineBox(stack, targetPos, color2); } case Normal -> { - AABB box = AABB.ofSize(targetPos.getCenter(), rawProgress, rawProgress, rawProgress); + AABB box = AABB.ofSize(Vec3.atCenterOf(targetPos), rawProgress, rawProgress, rawProgress); Render3DUtils.drawFilledBox(box, color1); Render3DUtils.drawOutlineBox(stack, box, color2); } @@ -601,7 +603,7 @@ private void secondBlockRender(PoseStack stack) { Render3DUtils.drawOutlineBox(stack, secondPos, color2); } case Normal -> { - AABB box = AABB.ofSize(secondPos.getCenter(), rawProgress, rawProgress, rawProgress); + AABB box = AABB.ofSize(Vec3.atCenterOf(secondPos), rawProgress, rawProgress, rawProgress); Render3DUtils.drawFilledBox(box, color1); Render3DUtils.drawOutlineBox(stack, box, color2); } diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/SafeAnchor.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/SafeAnchor.java index 0ec565ce..e9ff1b7f 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/SafeAnchor.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/SafeAnchor.java @@ -570,7 +570,7 @@ private BlockPos findPlacePos() { } Vec3 playerPos = mc.player.position(); - Vec3 anchorPosVec = currentAnchorPos.getCenter(); + Vec3 anchorPosVec = Vec3.atCenterOf(currentAnchorPos); for (double i = 0.3; i <= 0.7; i += 0.1) { Vec3 posVec = playerPos.lerp(anchorPosVec, i); @@ -621,7 +621,7 @@ private boolean isValidPlacePos(BlockPos pos) { } private boolean isExplosionSafe() { - float damage = DamageUtils.anchorDamage(mc.player, currentAnchorPos.getCenter(), DamageUtils.ArmorEnchantmentMode.PPBP); + float damage = DamageUtils.anchorDamage(mc.player, Vec3.atCenterOf(currentAnchorPos), DamageUtils.ArmorEnchantmentMode.PPBP); float health = mc.player.getHealth() + mc.player.getAbsorptionAmount(); return (health - damage) > minHealth.getValue().floatValue(); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/TriggerBot.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/TriggerBot.java index df900a2e..15121414 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/TriggerBot.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/TriggerBot.java @@ -51,7 +51,7 @@ protected void onDisable() { @EventHandler public void onTick(TickEvent.Pre event) { - if (nullCheck() || mc.screen != null) return; + if (nullCheck() || mc.gui.screen() != null) return; if (mc.player.isUsingItem() || mc.player.isBlocking()) { return; diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/ZealotCrystalPlus.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/ZealotCrystalPlus.java index 40bdcd74..87428b68 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/combat/ZealotCrystalPlus.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/ZealotCrystalPlus.java @@ -39,6 +39,7 @@ import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EntityTypes; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.attributes.Attributes; @@ -301,7 +302,7 @@ private void onPacketReceive(PacketEvent.Receive event) { if (nullCheck() || !isEnabled()) return; Packet packet = event.getPacket(); - if (packet instanceof ClientboundAddEntityPacket addPacket && addPacket.getType() == EntityType.END_CRYSTAL) { + if (packet instanceof ClientboundAddEntityPacket addPacket && addPacket.getType() == EntityTypes.END_CRYSTAL) { handleSpawnPacket(addPacket); } else if (packet instanceof ClientboundSoundPacket soundPacket) { handleExplosionPacket(soundPacket); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/CrystalDamageCompute.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/CrystalDamageCompute.java new file mode 100644 index 00000000..93e6e20d --- /dev/null +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/CrystalDamageCompute.java @@ -0,0 +1,275 @@ +package com.github.epsilon.modules.impl.combat.crystalaura; + +import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.vulkan.buffer.VulkanOutputBuffer; +import com.github.epsilon.graphics.vulkan.buffer.VulkanStd430Buffer; +import com.github.epsilon.graphics.vulkan.compute.VulkanComputePipeline; +import com.github.epsilon.graphics.vulkan.compute.VulkanComputeUtils; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorLayoutSpec; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorSetWrite; +import com.github.epsilon.graphics.vulkan.descriptor.VulkanResourceManager; +import com.github.epsilon.graphics.vulkan.shader.Glsl2SpirVCompiler; +import net.minecraft.world.Difficulty; +import net.minecraft.world.phys.Vec3; +import org.lwjgl.system.MemoryUtil; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.vulkan.VK12.*; + +/** + * GPU 加速水晶爆炸伤害计算器。 + * + *

SSBO 布局

+ *
    + *
  • binding 0: 体素网格({@link TerrainVoxelization})
  • + *
  • binding 1: 任务缓冲(放置点 + 目标信息)
  • + *
  • binding 2: 结果缓冲(每任务一个 float)
  • + *
+ */ +public class CrystalDamageCompute { + + /** 每个 Task 的 std430 大小:5 × vec4 = 80 bytes */ + private static final int TASK_STRIDE = 80; + + /** Task header: uint taskCount + 3×uint padding = 16 bytes */ + private static final int TASK_HEADER_BYTES = 16; + + /** 最大并行任务数 */ + public static final int MAX_TASKS = 512; + + /** Task buffer 总大小 */ + private static final long TASK_BUFFER_SIZE = TASK_HEADER_BYTES + (long) MAX_TASKS * TASK_STRIDE; + + /** Result buffer 总大小 */ + private static final long RESULT_BUFFER_SIZE = (long) MAX_TASKS * Float.BYTES; + + private boolean initialized; + private @Nullable VulkanStd430Buffer voxelBuffer; + private @Nullable VulkanStd430Buffer taskBuffer; + private @Nullable VulkanOutputBuffer resultBuffer; + private @Nullable VulkanComputePipeline pipeline; + private @Nullable VulkanResourceManager resourceManager; + private @Nullable VulkanResourceManager.ManagedDescriptorSet descriptorSet; + private @Nullable VulkanComputeUtils computeUtils; + + private final DescriptorLayoutSpec layoutSpec = DescriptorLayoutSpec.builder() + .addSsbo(0) + .addSsbo(1) + .addSsbo(2) + .build(); + + private final TerrainVoxelization voxelization = new TerrainVoxelization(); + + private int taskCount; + private final List pendingTasks = new ArrayList<>(MAX_TASKS); + + private record GpuTask( + Vec3 crystalPos, float radius, + Vec3 targetPos, float halfWidth, float height, + float armor, float toughness, float enchantProt, + float difficulty, float resistanceMultiplier, float applyDifficulty + ) {} + + public void ensureInitialized() { + if (initialized) return; + + try { + ByteBuffer shaderSource = ResourceLocationUtils.loadResource( + ResourceLocationUtils.getIdentifier("shaders/compute/crystal_damage.csh") + ); + String glsl; + try { + glsl = MemoryUtil.memUTF8(shaderSource); + } finally { + MemoryUtil.memFree(shaderSource); + } + + ByteBuffer spirv; + try (Glsl2SpirVCompiler compiler = new Glsl2SpirVCompiler(glsl)) { + compiler.compile(); + spirv = compiler.getSpirV(); + } + + voxelBuffer = new VulkanStd430Buffer( + LuminRenderSystem.vulkanContext.vma(), + TerrainVoxelization.BUFFER_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + ); + + taskBuffer = new VulkanStd430Buffer( + LuminRenderSystem.vulkanContext.vma(), + TASK_BUFFER_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + ); + + resultBuffer = new VulkanOutputBuffer( + LuminRenderSystem.vulkanContext.vma(), + RESULT_BUFFER_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + ); + + pipeline = new VulkanComputePipeline( + LuminRenderSystem.vulkanContext.device(), + spirv, + layoutSpec + ); + + resourceManager = new VulkanResourceManager(LuminRenderSystem.vulkanContext.device()); + + descriptorSet = resourceManager.allocateDescriptorSet( + pipeline.descriptorSetLayout(), + layoutSpec, + List.of( + DescriptorSetWrite.storageBuffer(0, voxelBuffer.gpuBuffer(), TerrainVoxelization.BUFFER_SIZE), + DescriptorSetWrite.storageBuffer(1, taskBuffer.gpuBuffer(), TASK_BUFFER_SIZE), + DescriptorSetWrite.storageBuffer(2, resultBuffer.gpuBuffer(), RESULT_BUFFER_SIZE) + ) + ); + + computeUtils = new VulkanComputeUtils(); + initialized = true; + } catch (Exception e) { + e.printStackTrace(); + destroy(); + } + } + + public boolean isInitialized() { + return initialized; + } + + /** + * 开始新一轮任务提交:更新体素网格,清空任务列表。 + */ + public void beginFrame() { + if (!initialized) return; + voxelization.update(); + taskCount = 0; + pendingTasks.clear(); + } + + /** + * 添加一个伤害计算任务。 + * + * @param crystalPos 水晶爆炸中心 + * @param radius 爆炸半径 + * @param targetPos 目标脚底位置 + * @param halfWidth 目标半宽 + * @param height 目标高度 + * @param armor 目标护甲值 + * @param toughness 目标护甲韧性 + * @param enchantProt 附魔保护等级总和 (clamped 0-20) + * @param difficulty 难度 (0=Peaceful,1=Easy,2=Normal,3=Hard) + * @return 任务索引,用于后续读取结果;-1 表示已满 + */ + public int addTask(Vec3 crystalPos, float radius, + Vec3 targetPos, float halfWidth, float height, + float armor, float toughness, float enchantProt, + float difficulty, float resistanceMultiplier, float applyDifficulty) { + if (taskCount >= MAX_TASKS) return -1; + int idx = taskCount++; + pendingTasks.add(new GpuTask( + crystalPos, radius, targetPos, halfWidth, height, + armor, toughness, enchantProt, difficulty, + resistanceMultiplier, applyDifficulty + )); + return idx; + } + + /** + * 将难度枚举转为 shader 使用的 float。 + */ + public static float difficultyToFloat(Difficulty d) { + return switch (d) { + case PEACEFUL -> 0f; + case EASY -> 1f; + case NORMAL -> 2f; + case HARD -> 3f; + }; + } + + /** + * 写入所有 buffer → dispatch → fence wait → 可安全 readResult。 + */ + public void dispatch() { + if (!initialized || taskCount == 0) return; + if (voxelBuffer == null || taskBuffer == null || resultBuffer == null + || pipeline == null || descriptorSet == null || computeUtils == null) return; + + // 写入体素数据 + voxelBuffer.writer().clear(); + voxelization.writeTo(voxelBuffer.writer()); + + // 写入任务数据 + writeTaskBuffer(); + + computeUtils.dispatchAndWait( + new VulkanStd430Buffer[]{ voxelBuffer, taskBuffer }, + resultBuffer, + pipeline, + descriptorSet.handle(), + taskCount, 1, 1, + (long) taskCount * Float.BYTES + ); + } + + private void writeTaskBuffer() { + var writer = taskBuffer.writer(); + writer.clear(); + + writer.putInt(taskCount); + writer.putInt(0); + writer.putInt(0); + writer.putInt(0); + + for (GpuTask task : pendingTasks) { + writer.putVec4((float) task.crystalPos.x, (float) task.crystalPos.y, (float) task.crystalPos.z, task.radius); + writer.putVec4((float) task.targetPos.x, (float) task.targetPos.y, (float) task.targetPos.z, 0f); + writer.putVec4(task.halfWidth, task.height, 0f, 0f); + writer.putVec4(task.armor, task.toughness, task.enchantProt, task.difficulty); + writer.putVec4(task.resistanceMultiplier, task.applyDifficulty, 0f, 0f); + } + } + + /** + * 读取指定任务的计算结果。 + * 必须在 {@link #dispatch()} 返回后调用。 + */ + public float readResult(int taskIndex) { + if (!initialized || resultBuffer == null || computeUtils == null + || taskIndex < 0 || taskIndex >= taskCount) return 0f; + return computeUtils.readFloat(resultBuffer, taskIndex); + } + + /** + * 批量读取所有任务结果。 + */ + public float[] readAllResults() { + if (!initialized || resultBuffer == null || computeUtils == null || taskCount == 0) return new float[0]; + return computeUtils.readFloats(resultBuffer, taskCount); + } + + public int getTaskCount() { + return taskCount; + } + + public void invalidateTerrain() { + voxelization.invalidate(); + } + + public void destroy() { + if (computeUtils != null) { computeUtils.close(); computeUtils = null; } + if (descriptorSet != null) { descriptorSet.close(); descriptorSet = null; } + if (resourceManager != null) { resourceManager.close(); resourceManager = null; } + if (pipeline != null) { pipeline.close(); pipeline = null; } + if (resultBuffer != null) { resultBuffer.close(); resultBuffer = null; } + if (taskBuffer != null) { taskBuffer.close(); taskBuffer = null; } + if (voxelBuffer != null) { voxelBuffer.close(); voxelBuffer = null; } + initialized = false; + } +} diff --git a/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/TerrainVoxelization.java b/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/TerrainVoxelization.java new file mode 100644 index 00000000..79693d47 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/modules/impl/combat/crystalaura/TerrainVoxelization.java @@ -0,0 +1,320 @@ +package com.github.epsilon.modules.impl.combat.crystalaura; + +import com.github.epsilon.graphics.vulkan.buffer.Std430Writer; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; + +import java.util.Arrays; + +/** + *

数据布局(std430 SSBO)

+ *
+ *   Header (16 bytes):
+ *     int gridOriginX
+ *     int gridOriginY
+ *     int gridOriginZ
+ *     int gridSize
+ *
+ *   Body:
+ *     uint voxelBits[BITMAP_UINT_COUNT]
+ *     — 每 uint 存 32 个体素
+ *     — flatIndex(x,y,z) = (z * GRID_SIZE + y) * GRID_SIZE + x
+ * 
+ * + *

写入字节数

+ * Header: 4 × int = 16 bytes(Std430Writer 的 putInt 按 4 字节对齐,连续写无间隙)。 + * Body: BITMAP_UINT_COUNT × 4 bytes = 4096 bytes。 + * 合计: {@link #BUFFER_SIZE} = 4112 bytes。 + */ +public class TerrainVoxelization { + + private static final Minecraft mc = Minecraft.getInstance(); + + /** 玩家中心两侧各保留 16 格,总体素边长为 32。 */ + public static final int HALF_EXTENT = 16; + + public static final int GRID_SIZE = HALF_EXTENT * 2; + + public static final int TOTAL_VOXELS = GRID_SIZE * GRID_SIZE * GRID_SIZE; + + public static final int BITMAP_UINT_COUNT = (TOTAL_VOXELS + 31) / 32; + + public static final int HEADER_BYTES = 4 * Integer.BYTES; + + public static final int BODY_BYTES = BITMAP_UINT_COUNT * Integer.BYTES; + + public static final int BUFFER_SIZE = HEADER_BYTES + BODY_BYTES; + + /** 每个 Z 切片占用的 uint 数:GRID_SIZE² / 32 = 32 */ + private static final int SLICE_UINTS = GRID_SIZE * GRID_SIZE / 32; + + /** 每个 Y 行(固定 y, z)占用的 uint 数。 */ + private static final int ROW_UINTS = (GRID_SIZE + 31) / 32; + + private int originX, originY, originZ; + private final int[] voxelBits = new int[BITMAP_UINT_COUNT]; + private boolean needsFullRebuild = true; + + /** + * 每帧调用:以玩家位置为中心更新体素网格。 + *

+ * 当网格原点发生偏移时,采用滚动窗口增量更新策略:先对 {@code voxelBits} + * 做各轴位移以保留不变体素,再仅对新进入视野的切片/行/列做世界查询重建。 + * 对于位移 ≥ GRID_SIZE 的大跳跃,回退到全量重建。 + *

+ * 典型情况(三轴各偏移 1 块):约 3,000 次 getBlockState 调用, + * 相较全量重建的 32,768 次节省约 90%。 + * + * @return true 表示数据已变更 + */ + public boolean update() { + if (mc.player == null || mc.level == null) return false; + + int cx = (int) Math.floor(mc.player.getX()) - HALF_EXTENT; + int cy = (int) Math.floor(mc.player.getY()) - HALF_EXTENT; + int cz = (int) Math.floor(mc.player.getZ()) - HALF_EXTENT; + + if (needsFullRebuild) { + originX = cx; + originY = cy; + originZ = cz; + rebuildFull(); + needsFullRebuild = false; + return true; + } + + int dx = cx - originX; + int dy = cy - originY; + int dz = cz - originZ; + + if (dx == 0 && dy == 0 && dz == 0) return false; + + originX = cx; + originY = cy; + originZ = cz; + + if (Math.abs(dx) >= GRID_SIZE || Math.abs(dy) >= GRID_SIZE || Math.abs(dz) >= GRID_SIZE) { + rebuildFull(); + } else { + rebuildIncremental(dx, dy, dz); + } + return true; + } + + /** + * 完整重建体素网格(GRID_SIZE³ 次 getBlockState 调用)。 + */ + private void rebuildFull() { + Arrays.fill(voxelBits, 0); + + BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); + for (int lz = 0; lz < GRID_SIZE; lz++) { + int wz = originZ + lz; + for (int ly = 0; ly < GRID_SIZE; ly++) { + int wy = originY + ly; + for (int lx = 0; lx < GRID_SIZE; lx++) { + int wx = originX + lx; + mutable.set(wx, wy, wz); + + BlockState state = mc.level.getBlockState(mutable); + if (!state.isAir() + && !state.getCollisionShape(mc.level, mutable, CollisionContext.empty()).isEmpty()) { + int idx = flatIndex(lx, ly, lz); + voxelBits[idx >> 5] |= (1 << (idx & 31)); + } + } + } + } + } + + /** + * 滚动窗口增量重建:先对 voxelBits 做各轴位移(保留不变体素), + * 再仅查询新进入视野的切片/行/列。 + * 调用时 originX/Y/Z 已更新至新位置。 + * + * @param dx 原点在 X 轴的位移(正 = 向 +X 移动) + * @param dy 原点在 Y 轴的位移 + * @param dz 原点在 Z 轴的位移 + */ + private void rebuildIncremental(int dx, int dy, int dz) { + shiftZ(dz); + shiftY(dy); + shiftX(dx); + + BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); + + // 新进入的 Z 切片范围(已被 shiftZ 清零) + int zSurviveStart = dz > 0 ? 0 : -dz; + int zSurviveEnd = dz > 0 ? GRID_SIZE - dz : GRID_SIZE; + + if (dz > 0) { + for (int lz = GRID_SIZE - dz; lz < GRID_SIZE; lz++) + rebuildZSlice(lz, mutable); + } else if (dz < 0) { + for (int lz = 0; lz < -dz; lz++) + rebuildZSlice(lz, mutable); + } + + // 新进入的 Y 行(仅在存活 Z 范围内,已被 shiftY 清零) + int ySurviveStart = dy > 0 ? 0 : -dy; + int ySurviveEnd = dy > 0 ? GRID_SIZE - dy : GRID_SIZE; + + if (dy > 0) { + for (int lz = zSurviveStart; lz < zSurviveEnd; lz++) + for (int ly = GRID_SIZE - dy; ly < GRID_SIZE; ly++) + rebuildYRow(lz, ly, mutable); + } else if (dy < 0) { + for (int lz = zSurviveStart; lz < zSurviveEnd; lz++) + for (int ly = 0; ly < -dy; ly++) + rebuildYRow(lz, ly, mutable); + } + + // 新进入的 X 列(仅在存活 Z/Y 范围内,已被 shiftX 清零) + if (dx > 0) { + for (int lz = zSurviveStart; lz < zSurviveEnd; lz++) + for (int ly = ySurviveStart; ly < ySurviveEnd; ly++) + for (int lx = GRID_SIZE - dx; lx < GRID_SIZE; lx++) + rebuildVoxel(lx, ly, lz, mutable); + } else if (dx < 0) { + for (int lz = zSurviveStart; lz < zSurviveEnd; lz++) + for (int ly = ySurviveStart; ly < ySurviveEnd; ly++) + for (int lx = 0; lx < -dx; lx++) + rebuildVoxel(lx, ly, lz, mutable); + } + } + + /** + * 将 voxelBits 在 Z 轴方向平移 dz 个切片。 + * dz > 0:原点向 +Z 移动,保留高索引切片(整体向低索引平移),低端清零。 + * dz < 0:原点向 -Z 移动,保留低索引切片(整体向高索引平移),高端清零。 + */ + private void shiftZ(int dz) { + if (dz == 0) return; + if (dz > 0) { + System.arraycopy(voxelBits, dz * SLICE_UINTS, voxelBits, 0, (GRID_SIZE - dz) * SLICE_UINTS); + Arrays.fill(voxelBits, (GRID_SIZE - dz) * SLICE_UINTS, BITMAP_UINT_COUNT, 0); + } else { + int adz = -dz; + System.arraycopy(voxelBits, 0, voxelBits, adz * SLICE_UINTS, (GRID_SIZE - adz) * SLICE_UINTS); + Arrays.fill(voxelBits, 0, adz * SLICE_UINTS, 0); + } + } + + /** + * 将每个 Z 切片内的 Y 行在 Y 轴方向平移 dy 行。 + * dy > 0:每切片内保留低 Y 行(向低索引平移),高端清零。 + * dy < 0:每切片内保留高 Y 行(向高索引平移),低端清零。 + */ + private void shiftY(int dy) { + if (dy == 0) return; + for (int lz = 0; lz < GRID_SIZE; lz++) { + int sliceBase = lz * SLICE_UINTS; + if (dy > 0) { + System.arraycopy(voxelBits, sliceBase + dy * ROW_UINTS, voxelBits, sliceBase, + (GRID_SIZE - dy) * ROW_UINTS); + Arrays.fill(voxelBits, sliceBase + (GRID_SIZE - dy) * ROW_UINTS, sliceBase + SLICE_UINTS, 0); + } else { + int ady = -dy; + System.arraycopy(voxelBits, sliceBase, voxelBits, sliceBase + ady * ROW_UINTS, + (GRID_SIZE - ady) * ROW_UINTS); + Arrays.fill(voxelBits, sliceBase, sliceBase + ady * ROW_UINTS, 0); + } + } + } + + /** + * 将每个 (y, z) 行的体素比特在 X 轴方向平移 dx 位。 + * 支持任意 {@link #ROW_UINTS},不再假定一行恰好是 64 bit。 + * dx > 0:右移(低 bit 方向),高 dx bit 清零供重建。 + * dx < 0:左移(高 bit 方向),低 |dx| bit 清零供重建。 + */ + private void shiftX(int dx) { + if (dx == 0) return; + int totalRows = GRID_SIZE * GRID_SIZE; + + for (int row = 0; row < totalRows; row++) { + int rowBase = row * ROW_UINTS; + int rowBits = voxelBits[rowBase]; + voxelBits[rowBase] = dx > 0 ? rowBits >>> dx : rowBits << -dx; + } + } + + /** 重建整个 Z 切片(lz 为本地坐标)。 */ + private void rebuildZSlice(int lz, BlockPos.MutableBlockPos mutable) { + int wz = originZ + lz; + for (int ly = 0; ly < GRID_SIZE; ly++) { + int wy = originY + ly; + for (int lx = 0; lx < GRID_SIZE; lx++) { + mutable.set(originX + lx, wy, wz); + if (isSolid(mutable)) setBit(lx, ly, lz); + } + } + } + + /** 重建单行(固定 lz, ly,遍历所有 lx)。 */ + private void rebuildYRow(int lz, int ly, BlockPos.MutableBlockPos mutable) { + int wy = originY + ly; + int wz = originZ + lz; + for (int lx = 0; lx < GRID_SIZE; lx++) { + mutable.set(originX + lx, wy, wz); + if (isSolid(mutable)) setBit(lx, ly, lz); + } + } + + /** 重建单个体素。 */ + private void rebuildVoxel(int lx, int ly, int lz, BlockPos.MutableBlockPos mutable) { + mutable.set(originX + lx, originY + ly, originZ + lz); + if (isSolid(mutable)) setBit(lx, ly, lz); + } + + private boolean isSolid(BlockPos.MutableBlockPos pos) { + BlockState state = mc.level.getBlockState(pos); + return !state.isAir() && !state.getCollisionShape(mc.level, pos, CollisionContext.empty()).isEmpty(); + } + + private void setBit(int lx, int ly, int lz) { + int idx = flatIndex(lx, ly, lz); + voxelBits[idx >> 5] |= (1 << (idx & 31)); + } + + /** + * Z-major 线性索引,与 shader 中 sampleVoxel 一致。 + */ + private static int flatIndex(int x, int y, int z) { + return (z * GRID_SIZE + y) * GRID_SIZE + x; + } + + /** + * 将体素数据写入 {@link Std430Writer}。 + *

+ * 写入精确 {@link #BUFFER_SIZE} 字节(16 header + 4096 body)。 + * 使用 {@code putInt} 而非 {@code putUInt},二者等价(4 字节对齐 + 4 字节写入)。 + * 由于连续写 int 且起始 position 为 0(由调用方 clear),不会产生对齐填充。 + */ + public void writeTo(Std430Writer writer) { + // Header: 4 × int = 16 bytes + writer.putInt(originX); + writer.putInt(originY); + writer.putInt(originZ); + writer.putInt(GRID_SIZE); + + // Body: BITMAP_UINT_COUNT × int = 32768 bytes + for (int i = 0; i < BITMAP_UINT_COUNT; i++) { + writer.putInt(voxelBits[i]); + } + // 此时 writer.writtenBytes() == BUFFER_SIZE + } + + /** + * 标记需要在下一次进行完整重建。 + */ + public void invalidate() { + needsFullRebuild = true; + } + + public int getOriginX() { return originX; } + public int getOriginY() { return originY; } + public int getOriginZ() { return originZ; } +} diff --git a/common/src/main/java/com/github/epsilon/modules/impl/hud/TargetHUD.java b/common/src/main/java/com/github/epsilon/modules/impl/hud/TargetHUD.java index 963b8e1e..b876d8e7 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/hud/TargetHUD.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/hud/TargetHUD.java @@ -164,7 +164,7 @@ private LivingEntity resolveTarget() { if (isRenderableTarget(target)) { return target; } - return mc.screen instanceof HudEditorScreen ? mc.player : null; + return mc.gui.screen() instanceof HudEditorScreen ? mc.player : null; } private boolean isRenderableTarget(LivingEntity target) { diff --git a/common/src/main/java/com/github/epsilon/modules/impl/movement/Blink.java b/common/src/main/java/com/github/epsilon/modules/impl/movement/Blink.java index 0eae4b40..d508c89a 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/movement/Blink.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/movement/Blink.java @@ -159,7 +159,7 @@ public void onRender(Render3DEvent event) { @EventHandler public void onMotion(SendPositionEvent event) { - if (mc.screen instanceof RecoverWorldDataScreen && this.isEnabled()) { + if (mc.gui.screen() instanceof RecoverWorldDataScreen && this.isEnabled()) { this.setEnabled(false); } if (nullCheck()) return; diff --git a/common/src/main/java/com/github/epsilon/modules/impl/movement/Phase.java b/common/src/main/java/com/github/epsilon/modules/impl/movement/Phase.java index 05c01014..18ac4212 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/movement/Phase.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/movement/Phase.java @@ -23,6 +23,7 @@ import net.minecraft.network.protocol.game.ServerboundSwingPacket; import net.minecraft.world.InteractionHand; import net.minecraft.world.item.Items; +import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.phys.AABB; @@ -202,7 +203,7 @@ public void onClientTickPre(TickEvent.Pre event) { return; } - Vector2f angle = RotationUtils.calculate(block.getCenter()); + Vector2f angle = RotationUtils.calculate(Vec3.atCenterOf(block)); FindItemResult result = swapMode.is(SwapMode.Silent) ? InvUtils.findInHotbar(Items.ENDER_PEARL) : InvUtils.find(Items.ENDER_PEARL); if (result.found()) { float prevYaw = mc.player.getYRot(); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/movement/Scaffold.java b/common/src/main/java/com/github/epsilon/modules/impl/movement/Scaffold.java index 63f00d8c..dc853bb4 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/movement/Scaffold.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/movement/Scaffold.java @@ -76,7 +76,7 @@ private Scaffold() { AABB renderBox = getRenderBox(box, scale); Render3DUtils.drawFilledBox(renderBox, side); - Render3DUtils.drawOutlineBox(event.getPoseStack(), renderBox, line); + Render3DUtils.drawOutlineBox(event.getPoseStack(), renderBox, line.getRGB(), 3f); } } )); @@ -353,7 +353,7 @@ private boolean checkBlock(Vec3 baseVec, BlockPos pos) { } private Vector2f getRotation(BlockInfo blockCache) { - Vector2f calculate = onAir() ? RotationUtils.calculate(blockCache.position, blockCache.dir) : RotationUtils.calculate(blockCache.position.getCenter()); + Vector2f calculate = onAir() ? RotationUtils.calculate(blockCache.position, blockCache.dir) : RotationUtils.calculate(Vec3.atCenterOf(blockCache.position)); Vector2f reverseYaw = new Vector2f(Mth.wrapDegrees(mc.player.getYRot() - 180), calculate.y); boolean hasRotated = RaytraceUtils.overBlock(reverseYaw, blockCache.position, blockCache.dir); if (hasRotated) return reverseYaw; diff --git a/common/src/main/java/com/github/epsilon/modules/impl/player/ComputeTest.java b/common/src/main/java/com/github/epsilon/modules/impl/player/ComputeTest.java new file mode 100644 index 00000000..f2bd0651 --- /dev/null +++ b/common/src/main/java/com/github/epsilon/modules/impl/player/ComputeTest.java @@ -0,0 +1,210 @@ +package com.github.epsilon.modules.impl.player; + +import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.events.bus.EventHandler; +import com.github.epsilon.events.impl.TickEvent; +import com.github.epsilon.graphics.LuminRenderSystem; +import com.github.epsilon.graphics.vulkan.buffer.VulkanOutputBuffer; +import com.github.epsilon.graphics.vulkan.buffer.VulkanStd430Buffer; +import com.github.epsilon.graphics.vulkan.compute.VulkanComputePipeline; +import com.github.epsilon.graphics.vulkan.compute.VulkanComputeUtils; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorSetWrite; +import com.github.epsilon.graphics.vulkan.descriptor.DescriptorLayoutSpec; +import com.github.epsilon.graphics.vulkan.descriptor.VulkanResourceManager; +import com.github.epsilon.graphics.vulkan.shader.Glsl2SpirVCompiler; +import com.github.epsilon.modules.Category; +import com.github.epsilon.modules.Module; +import org.lwjgl.system.MemoryUtil; + +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.util.List; + +import static org.lwjgl.vulkan.VK12.*; + +public class ComputeTest extends Module { + + public static final ComputeTest INSTANCE = new ComputeTest(); + + private static final int ELEMENT_COUNT = 128; + private static final int BYTES_PER_FLOAT = Float.BYTES; + private static final int BUFFER_SIZE = ELEMENT_COUNT * BYTES_PER_FLOAT; + + private boolean initialized; + private boolean dispatched; + + private @Nullable VulkanOutputBuffer outputBuffer; + private @Nullable VulkanStd430Buffer inputBuffer; + private @Nullable VulkanComputePipeline pipeline; + private @Nullable VulkanResourceManager resourceManager; + private @Nullable VulkanResourceManager.ManagedDescriptorSet descriptorSet; + private @Nullable VulkanComputeUtils computeUtils; + + private final DescriptorLayoutSpec layoutSpec = DescriptorLayoutSpec.builder() + .addSsbo(0) + .addSsbo(1) + .build(); + + private ComputeTest() { + super("Compute Test", Category.PLAYER); + } + + @Override + protected void onEnable() { + dispatched = false; + } + + @Override + protected void onDisable() { + dispatched = false; + } + + @EventHandler + public void onTick(TickEvent.Pre event) { + if (nullCheck()) return; + if (dispatched) return; + + ensureInitialized(); + if (!initialized || pipeline == null || inputBuffer == null + || outputBuffer == null || descriptorSet == null || computeUtils == null) { + return; + } + + generateInput(); + + final int computeLocalSizeX = 64; + final int groupCountX = (ELEMENT_COUNT + computeLocalSizeX - 1) / computeLocalSizeX; + + computeUtils.dispatchAndWait( + new VulkanStd430Buffer[]{ inputBuffer }, + outputBuffer, + pipeline, + descriptorSet.handle(), + groupCountX, 1, 1, + BUFFER_SIZE + ); + + readOutput(); + dispatched = true; + } + + private void ensureInitialized() { + if (initialized) return; + + try { + String computeSource = loadComputeShaderSource(); + ByteBuffer spirv; + try (Glsl2SpirVCompiler compiler = new Glsl2SpirVCompiler(computeSource)) { + compiler.compile(); + spirv = compiler.getSpirV(); + } + + inputBuffer = new VulkanStd430Buffer( + LuminRenderSystem.vulkanContext.vma(), + BUFFER_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + ); + + outputBuffer = new VulkanOutputBuffer( + LuminRenderSystem.vulkanContext.vma(), + BUFFER_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT + ); + + pipeline = new VulkanComputePipeline( + LuminRenderSystem.vulkanContext.device(), + spirv, + layoutSpec + ); + + if (resourceManager == null) { + resourceManager = new VulkanResourceManager(LuminRenderSystem.vulkanContext.device()); + } + + descriptorSet = resourceManager.allocateDescriptorSet( + pipeline.descriptorSetLayout(), + layoutSpec, + List.of( + DescriptorSetWrite.storageBuffer(0, inputBuffer.gpuBuffer(), BUFFER_SIZE), + DescriptorSetWrite.storageBuffer(1, outputBuffer.gpuBuffer(), BUFFER_SIZE) + ) + ); + + computeUtils = new VulkanComputeUtils(); + initialized = true; + } catch (Exception e) { + e.printStackTrace(); + destroyResources(); + initialized = false; + } + } + + private String loadComputeShaderSource() { + ByteBuffer shaderSource = ResourceLocationUtils.loadResource( + ResourceLocationUtils.getIdentifier("shaders/compute/compute_test.csh") + ); + try { + return MemoryUtil.memUTF8(shaderSource); + } finally { + MemoryUtil.memFree(shaderSource); + } + } + + private void readOutput() { + if (outputBuffer == null || computeUtils == null) return; + float[] results = computeUtils.readFloats(outputBuffer, 8); + for (int i = 0; i < results.length; i++) { + System.out.println("[ComputeTest] out[" + i + "] = " + results[i]); + } + } + + private void generateInput() { + if (inputBuffer == null) return; + inputBuffer.writer().clear(); + for (int i = 0; i < ELEMENT_COUNT; i++) { + inputBuffer.writer().putFloat(i); + } + } + + private void destroyResources() { + + if (computeUtils != null) { + computeUtils.close(); + computeUtils = null; + } + + if (descriptorSet != null) { + descriptorSet.close(); + descriptorSet = null; + } + + if (resourceManager != null) { + resourceManager.close(); + resourceManager = null; + } + + if (pipeline != null) { + pipeline.close(); + pipeline = null; + } + + if (outputBuffer != null) { + outputBuffer.close(); + outputBuffer = null; + } + + if (inputBuffer != null) { + inputBuffer.close(); + inputBuffer = null; + } + + initialized = false; + dispatched = false; + } + + public void destroy() { + if (initialized) { + destroyResources(); + } + } +} diff --git a/common/src/main/java/com/github/epsilon/modules/impl/player/ElytraSwap.java b/common/src/main/java/com/github/epsilon/modules/impl/player/ElytraSwap.java index 6cf4be9d..3c9f1c0c 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/player/ElytraSwap.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/player/ElytraSwap.java @@ -51,7 +51,7 @@ protected void onEnable() { private void onTick(TickEvent.Pre event) { if (nullCheck()) return; - if (mc.screen != null) return; + if (mc.gui.screen() != null) return; boolean pressed = KeybindUtils.isPressed(activateKey.getValue()); if (!pressed) { diff --git a/common/src/main/java/com/github/epsilon/modules/impl/player/GhostHand.java b/common/src/main/java/com/github/epsilon/modules/impl/player/GhostHand.java index 479db32b..0d0c8b32 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/player/GhostHand.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/player/GhostHand.java @@ -48,7 +48,7 @@ private void onStartUseItem(StartUseItemEvent event) { if (mc.level.getBlockState(pos).hasBlockEntity()) { for (InteractionHand hand : InteractionHand.values()) { - InteractionResult result = mc.gameMode.useItemOn(mc.player, hand, new BlockHitResult(pos.getCenter(), RotationUtils.getDirection(pos), pos, true)); + InteractionResult result = mc.gameMode.useItemOn(mc.player, hand, new BlockHitResult(Vec3.atCenterOf(pos), RotationUtils.getDirection(pos), pos, true)); if (result instanceof InteractionResult.Success || result instanceof InteractionResult.Fail) { mc.player.swing(hand); event.setCancelled(true); diff --git a/common/src/main/java/com/github/epsilon/modules/impl/player/InvManager.java b/common/src/main/java/com/github/epsilon/modules/impl/player/InvManager.java index 53cdf4c2..62f94577 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/player/InvManager.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/player/InvManager.java @@ -234,7 +234,7 @@ private boolean checkConfig() { private void onTick(TickEvent.Pre event) { if (nullCheck()) return; - if (!(mc.screen instanceof PanelScreen) && !this.checkConfig()) { + if (!(mc.gui.screen() instanceof PanelScreen) && !this.checkConfig()) { ChatUtils.addChatMessage("Duplicate slot config in Inventory Manager! Please check your config!"); this.toggle(); return; @@ -252,12 +252,12 @@ private void onTick(TickEvent.Pre event) { if (Stealer.INSTANCE.isWorking() || Scaffold.INSTANCE.isEnabled() - || (this.inventoryOnly.getValue() ? !(mc.screen instanceof InventoryScreen) : this.noMoveTicks <= 1)) { + || (this.inventoryOnly.getValue() ? !(mc.gui.screen() instanceof InventoryScreen) : this.noMoveTicks <= 1)) { this.clickOffHand = false; return; } - if (mc.screen instanceof AbstractContainerScreen container && container.getMenu().containerId != mc.player.inventoryMenu.containerId) { + if (mc.gui.screen() instanceof AbstractContainerScreen container && container.getMenu().containerId != mc.player.inventoryMenu.containerId) { return; } diff --git a/common/src/main/java/com/github/epsilon/modules/impl/player/Stealer.java b/common/src/main/java/com/github/epsilon/modules/impl/player/Stealer.java index c2b85fed..562e1c59 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/player/Stealer.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/player/Stealer.java @@ -109,7 +109,7 @@ public static boolean isItemUseful(ItemStack stack) { public void onTick(TickEvent.Pre event) { if (nullCheck()) return; - Screen currentScreen = mc.screen; + Screen currentScreen = mc.gui.screen(); if (currentScreen instanceof ContainerScreen container) { ChestMenu menu = container.getMenu(); if (currentScreen != this.lastTickScreen) { diff --git a/common/src/main/java/com/github/epsilon/modules/impl/render/Chams.java b/common/src/main/java/com/github/epsilon/modules/impl/render/Chams.java index 8722d7f7..91ae1933 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/render/Chams.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/render/Chams.java @@ -7,6 +7,7 @@ import com.mojang.blaze3d.pipeline.DepthStencilState; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.platform.CompareOp; +import net.minecraft.client.renderer.BindGroupLayouts; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.renderer.rendertype.RenderSetup; import net.minecraft.client.renderer.rendertype.RenderType; @@ -27,7 +28,7 @@ private Chams() { .withLocation("pipeline/epsilon_entity_chams") .withShaderDefine("ALPHA_CUTOUT", 0.1f) .withShaderDefine("PER_FACE_LIGHTING") - .withSampler("Sampler1") + .withBindGroupLayout(BindGroupLayouts.SAMPLER1) .withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT)) .withCull(false) .withDepthStencilState(new DepthStencilState(CompareOp.LESS_THAN_OR_EQUAL, true, -1.0f, -1100000.0f)) diff --git a/common/src/main/java/com/github/epsilon/modules/impl/render/Hat.java b/common/src/main/java/com/github/epsilon/modules/impl/render/Hat.java index 6b43cd99..a82a699a 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/render/Hat.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/render/Hat.java @@ -5,8 +5,13 @@ import com.github.epsilon.modules.Category; import com.github.epsilon.modules.Module; import com.github.epsilon.settings.impl.*; +import com.mojang.blaze3d.PrimitiveTopology; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; import com.mojang.math.Axis; +import net.minecraft.client.renderer.rendertype.PreparedRenderType; +import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.client.renderer.rendertype.RenderTypes; import net.minecraft.util.Mth; import net.minecraft.world.entity.player.Player; @@ -79,7 +84,7 @@ public void drawHat(PoseStack stack, Player player) { colors[i] = this.fadeBetween(colorMode, this.offset.getValue(), (double) i * ((double) this.offset.getValue() / this.points.getValue())); } - Vec3 camera = mc.gameRenderer.getMainCamera().position(); + Vec3 camera = mc.gameRenderer.mainCamera().position(); float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(false); double x = Mth.lerp(tickDelta, player.xOld, player.getX()) - camera.x; double y = Mth.lerp(tickDelta, player.yOld, player.getY()) - camera.y; @@ -101,10 +106,10 @@ public void drawHat(PoseStack stack, Player player) { stack.translate(0, 0, pitch / 270.0); Matrix4f matrix = stack.last().pose(); - Tesselator tesselator = Tesselator.getInstance(); float lineWidth = 2.0f; - BufferBuilder outlineBuffer = tesselator.begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH); + ByteBufferBuilder lineByteBuf = new ByteBufferBuilder(4096); + BufferBuilder outlineBuffer = new BufferBuilder(lineByteBuf, PrimitiveTopology.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH); for (int i = 0; i < pointCount; i++) { int next = (i + 1) % pointCount; @@ -123,9 +128,10 @@ public void drawHat(PoseStack stack, Player player) { outlineBuffer.addVertex(matrix, (float) p1[0], 0.0f, (float) p1[1]).setColor(c1.getRed(), c1.getGreen(), c1.getBlue(), 255).setNormal(nx, 0.0f, nz).setLineWidth(lineWidth); outlineBuffer.addVertex(matrix, (float) p2[0], 0.0f, (float) p2[1]).setColor(c2.getRed(), c2.getGreen(), c2.getBlue(), 255).setNormal(nx, 0.0f, nz).setLineWidth(lineWidth); } - RenderTypes.lines().draw(outlineBuffer.buildOrThrow()); + drawMesh(outlineBuffer); - BufferBuilder coneBuffer = tesselator.begin(VertexFormat.Mode.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR); + ByteBufferBuilder coneByteBuf = new ByteBufferBuilder(4096); + BufferBuilder coneBuffer = new BufferBuilder(coneByteBuf, PrimitiveTopology.TRIANGLE_FAN, DefaultVertexFormat.POSITION_COLOR); coneBuffer.addVertex(matrix, 0, (float) (radius / 2), 0).setColor(255, 255, 255, 128); @@ -134,11 +140,39 @@ public void drawHat(PoseStack stack, Player player) { Color clr = colors[i % colors.length]; coneBuffer.addVertex(matrix, (float) pos[0], 0, (float) pos[1]).setColor(clr.getRed(), clr.getGreen(), clr.getBlue(), 128); } - RenderTypes.debugTriangleFan().draw(coneBuffer.buildOrThrow()); + drawMesh(coneBuffer); stack.popPose(); } + private static void drawMesh(BufferBuilder buffer) { + MeshData mesh = buffer.buildOrThrow(); + if (mesh != null) { + try { + MeshData.DrawState drawState = mesh.drawState(); + RenderSystem.AutoStorageIndexBuffer autoIndices = RenderSystem.getSequentialBuffer(drawState.primitiveTopology()); + GpuBuffer indexBuf = autoIndices.getBuffer(drawState.indexCount()); + + GpuBuffer vertexBuf = RenderSystem.getDevice().createBuffer( + () -> "hat_vb", GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_COPY_DST, mesh.vertexBuffer().remaining()); + RenderSystem.getDevice().createCommandEncoder().writeToBuffer( + vertexBuf.slice(), mesh.vertexBuffer()); + + RenderType renderType; + if (drawState.primitiveTopology() == PrimitiveTopology.LINES) { + renderType = RenderTypes.lines(); + } else { + renderType = RenderTypes.debugTriangleFan(); + } + PreparedRenderType prepared = renderType.prepare(); + prepared.drawFromBuffer(vertexBuf, indexBuf, drawState.indexType(), 0, 0, drawState.indexCount()); + vertexBuf.close(); + } finally { + mesh.close(); + } + } + } + private Color[] getColorMode() { return switch (this.mode.getValue()) { case Astolfo -> new Color[]{ diff --git a/common/src/main/java/com/github/epsilon/modules/impl/render/NameTags.java b/common/src/main/java/com/github/epsilon/modules/impl/render/NameTags.java index c3ede30f..3d04b344 100644 --- a/common/src/main/java/com/github/epsilon/modules/impl/render/NameTags.java +++ b/common/src/main/java/com/github/epsilon/modules/impl/render/NameTags.java @@ -104,7 +104,7 @@ public void onRender3D(Render3DEvent event) { final var currentPosition = WorldToScreen.interpolate(target, partialTick); final var projectedPosition = WorldToScreen.getWorldPositionToScreen(currentPosition.add(0.0f, 0.5f + target.getEyeHeight(), 0.0f)); - if (projectedPosition.z > 1.0f || projectedPosition.z < 0.0f) continue; + if (projectedPosition.z > 1.0f || projectedPosition.z < 0.5f) continue; float guiScale = mc.getWindow().getGuiScale(); diff --git a/common/src/main/java/com/github/epsilon/utils/player/ChatUtils.java b/common/src/main/java/com/github/epsilon/utils/player/ChatUtils.java index 89a9cd27..6ebe7c98 100644 --- a/common/src/main/java/com/github/epsilon/utils/player/ChatUtils.java +++ b/common/src/main/java/com/github/epsilon/utils/player/ChatUtils.java @@ -20,7 +20,7 @@ public static void addChatMessage(String message) { } public static void addChatMessage(boolean prefix, String message) { - mc.gui.getChat().addClientSystemMessage(buildClientMessage(prefix, message)); + mc.gui.hud.getChat().addClientSystemMessage(buildClientMessage(prefix, message)); } public static Component buildClientMessage(boolean prefix, String message) { diff --git a/common/src/main/java/com/github/epsilon/utils/render/EpsilonGuiRenderer.java b/common/src/main/java/com/github/epsilon/utils/render/EpsilonGuiRenderer.java index d6e29f07..cd7ac1e8 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/EpsilonGuiRenderer.java +++ b/common/src/main/java/com/github/epsilon/utils/render/EpsilonGuiRenderer.java @@ -1,23 +1,17 @@ package com.github.epsilon.utils.render; +import com.google.common.collect.ImmutableMap; import com.mojang.blaze3d.ProjectionType; -import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.pipeline.RenderTarget; -import com.mojang.blaze3d.systems.CommandEncoder; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.FilterMode; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.ByteBufferBuilder; -import com.mojang.blaze3d.vertex.MeshData; -import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.logging.LogUtils; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.SharedConstants; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.font.TextRenderable; import net.minecraft.client.gui.navigation.ScreenRectangle; @@ -31,6 +25,7 @@ import net.minecraft.client.renderer.state.WindowRenderState; import net.minecraft.client.renderer.state.gui.*; import net.minecraft.client.renderer.state.gui.pip.OversizedItemRenderState; +import net.minecraft.client.renderer.state.gui.pip.PictureInPictureRenderState; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.Identifier; import net.minecraft.util.profiling.Profiler; @@ -38,19 +33,14 @@ import org.apache.commons.lang3.mutable.MutableBoolean; import org.joml.Matrix3x2fc; import org.joml.Matrix4f; -import org.joml.Vector3f; import org.joml.Vector4f; +import org.joml.Vector4fc; import org.jspecify.annotations.Nullable; -import org.lwjgl.system.MemoryUtil; import org.slf4j.Logger; -import java.nio.ByteBuffer; import java.util.*; -import java.util.Map.Entry; import java.util.function.Supplier; -import static com.github.epsilon.Constants.mc; - public class EpsilonGuiRenderer implements AutoCloseable { private static final Logger LOGGER = LogUtils.getLogger(); private static final float MAX_GUI_Z = 10000.0F; @@ -59,7 +49,7 @@ public class EpsilonGuiRenderer implements AutoCloseable { public static final int GUI_3D_Z_FAR = 1000; public static final int GUI_3D_Z_NEAR = -1000; public static final int DEFAULT_ITEM_SIZE = 16; - public static final int CLEAR_COLOR = 0; + public static final Vector4fc CLEAR_COLOR = new Vector4f(0.0F); private static final Comparator SCISSOR_COMPARATOR = Comparator.nullsFirst( Comparator.comparing(ScreenRectangle::top) .thenComparing(ScreenRectangle::bottom) @@ -74,33 +64,22 @@ public class EpsilonGuiRenderer implements AutoCloseable { .thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR); private final Map oversizedItemRenderers = new Object2ObjectOpenHashMap<>(); private final GuiRenderState renderState; - private final List draws = new ArrayList<>(); - private final List meshesToDraw = new ArrayList<>(); - private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432); - private final Map vertexBuffers = new Object2ObjectOpenHashMap<>(); + private final List draws = new ArrayList<>(); + private final StagedVertexBuffer vertexBuffer = new StagedVertexBuffer(() -> "GUI Vertex Buffer", 786432); private int firstDrawIndexAfterBlur = Integer.MAX_VALUE; private final Projection guiProjection = new Projection(); private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui"); - private final MultiBufferSource.BufferSource bufferSource; - private final SubmitNodeCollector submitNodeCollector; private final FeatureRenderDispatcher featureRenderDispatcher; - private GuiItemAtlas itemAtlas; + private @Nullable GuiItemAtlas itemAtlas; private int cachedGuiScale; private final CubeMap cubeMap = new CubeMap(Identifier.withDefaultNamespace("textures/gui/title/background/panorama")); - private ScreenRectangle previousScissorArea = null; - private RenderPipeline previousPipeline = null; - private TextureSetup previousTextureSetup = null; - private BufferBuilder bufferBuilder = null; - - public EpsilonGuiRenderer( - GuiRenderState renderState, - MultiBufferSource.BufferSource bufferSource, - SubmitNodeCollector submitNodeCollector, - FeatureRenderDispatcher featureRenderDispatcher - ) { + private @Nullable ScreenRectangle previousScissorArea = null; + private @Nullable RenderPipeline previousPipeline = null; + private @Nullable TextureSetup previousTextureSetup = null; + private StagedVertexBuffer.@Nullable Draw previousDraw; + + public EpsilonGuiRenderer(GuiRenderState renderState, FeatureRenderDispatcher featureRenderDispatcher) { this.renderState = renderState; - this.bufferSource = bufferSource; - this.submitNodeCollector = submitNodeCollector; this.featureRenderDispatcher = featureRenderDispatcher; } @@ -110,7 +89,7 @@ public void endFrame() { } } - public void render(GpuBufferSlice fogBuffer) { + public void render() { ProfilerFiller profiler = Profiler.get(); if (this.renderState.panoramaRenderState != null) { this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin()); @@ -118,17 +97,14 @@ public void render(GpuBufferSlice fogBuffer) { profiler.push("prepare"); this.prepare(); + profiler.popPush("upload"); + this.vertexBuffer.upload(); profiler.popPush("draw"); - this.draw(fogBuffer); - profiler.popPush("vertexBufferRotate"); - - for (MappableRingBuffer buffer : this.vertexBuffers.values()) { - buffer.rotate(); - } - - profiler.pop(); + this.draw(); + profiler.popPush("endFrame"); + this.vertexBuffer.endDraw(); + this.vertexBuffer.endFrame(); this.draws.clear(); - this.meshesToDraw.clear(); this.renderState.reset(); this.firstDrawIndexAfterBlur = Integer.MAX_VALUE; this.clearUnusedOversizedItemRenderers(); @@ -136,13 +112,15 @@ public void render(GpuBufferSlice fogBuffer) { RenderPipeline.updateSortKeySeed(); TextureSetup.updateSortKeySeed(); } + + profiler.pop(); } private void clearUnusedOversizedItemRenderers() { - Iterator> oversizedItemRendererIterator = this.oversizedItemRenderers.entrySet().iterator(); + Iterator> oversizedItemRendererIterator = this.oversizedItemRenderers.entrySet().iterator(); while (oversizedItemRendererIterator.hasNext()) { - Entry next = oversizedItemRendererIterator.next(); + Map.Entry next = oversizedItemRendererIterator.next(); OversizedItemRenderer renderer = next.getValue(); if (!renderer.usedOnThisFrame()) { renderer.close(); @@ -154,103 +132,61 @@ private void clearUnusedOversizedItemRenderers() { } private void prepare() { - this.bufferSource.endBatch(); this.prepareItemElements(); this.prepareText(); this.renderState.sortElements(ELEMENT_SORT_COMPARATOR); this.addElementsToMeshes(GuiRenderState.TraverseRange.BEFORE_BLUR); - this.firstDrawIndexAfterBlur = this.meshesToDraw.size(); + this.firstDrawIndexAfterBlur = this.draws.size(); this.addElementsToMeshes(GuiRenderState.TraverseRange.AFTER_BLUR); - this.recordDraws(); } private void addElementsToMeshes(GuiRenderState.TraverseRange range) { this.previousScissorArea = null; this.previousPipeline = null; this.previousTextureSetup = null; - this.bufferBuilder = null; + this.previousDraw = null; this.renderState.forEachElement(this::addElementToMesh, range); - if (this.bufferBuilder != null) { - this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea); - } } - private void draw(GpuBufferSlice fogBuffer) { + private void draw() { if (!this.draws.isEmpty()) { - WindowRenderState windowState = mc.gameRenderer.getGameRenderState().windowRenderState; + Minecraft minecraft = Minecraft.getInstance(); + WindowRenderState windowState = minecraft.gameRenderer.gameRenderState().windowRenderState; this.guiProjection - .setupOrtho(1000.0F, 11000.0F, (float) windowState.width / windowState.guiScale, (float) windowState.height / windowState.guiScale, true); + .setupOrtho(1000.0F, 11000.0F, (float)windowState.width / windowState.guiScale, (float)windowState.height / windowState.guiScale, true); RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC); - RenderTarget mainRenderTarget = mc.getMainRenderTarget(); - int maxIndexCount = 0; - - for (EpsilonGuiRenderer.Draw draw : this.draws) { - if (draw.indexCount > maxIndexCount) { - maxIndexCount = draw.indexCount; - } - } - - RenderSystem.AutoStorageIndexBuffer autoIndices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); - GpuBuffer indexBuffer = autoIndices.getBuffer(maxIndexCount); - VertexFormat.IndexType indexType = autoIndices.type(); - GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms() - .writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F), new Vector4f(1.0F, 1.0F, 1.0F, 1.0F), new Vector3f(), new Matrix4f()); + RenderTarget mainRenderTarget = minecraft.gameRenderer.mainRenderTarget(); + GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F)); if (this.firstDrawIndexAfterBlur > 0) { this.executeDrawRange( - () -> "GUI before blur", - mainRenderTarget, - fogBuffer, - dynamicTransforms, - indexBuffer, - indexType, - 0, - Math.min(this.firstDrawIndexAfterBlur, this.draws.size()) + () -> "GUI before blur", mainRenderTarget, dynamicTransforms, 0, Math.min(this.firstDrawIndexAfterBlur, this.draws.size()) ); } if (this.draws.size() > this.firstDrawIndexAfterBlur) { - RenderSystem.getDevice().createCommandEncoder().clearDepthTexture(mainRenderTarget.getDepthTexture(), 1.0); - mc.gameRenderer.processBlurEffect(); - this.executeDrawRange( - () -> "GUI after blur", - mainRenderTarget, - fogBuffer, - dynamicTransforms, - indexBuffer, - indexType, - this.firstDrawIndexAfterBlur, - this.draws.size() - ); + RenderSystem.getDevice().createCommandEncoder().clearDepthTexture(mainRenderTarget.getDepthTexture(), 0.0); + minecraft.gameRenderer.processBlurEffect(); + this.executeDrawRange(() -> "GUI after blur", mainRenderTarget, dynamicTransforms, this.firstDrawIndexAfterBlur, this.draws.size()); } } } - private void executeDrawRange( - Supplier label, - RenderTarget mainRenderTarget, - GpuBufferSlice fogBuffer, - GpuBufferSlice dynamicTransforms, - GpuBuffer indexBuffer, - VertexFormat.IndexType indexType, - int startIndex, - int endIndex - ) { + private void executeDrawRange(Supplier label, RenderTarget mainRenderTarget, GpuBufferSlice dynamicTransforms, int startIndex, int endIndex) { try (RenderPass renderPass = RenderSystem.getDevice() .createCommandEncoder() .createRenderPass( label, mainRenderTarget.getColorTextureView(), - OptionalInt.empty(), + Optional.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty() )) { RenderSystem.bindDefaultUniforms(renderPass); - renderPass.setUniform("Fog", fogBuffer); renderPass.setUniform("DynamicTransforms", dynamicTransforms); for (int i = startIndex; i < endIndex; i++) { - EpsilonGuiRenderer.Draw draw = this.draws.get(i); - this.executeDraw(draw, renderPass, indexBuffer, indexType); + Draw draw = this.draws.get(i); + this.executeDraw(draw, renderPass); } } } @@ -259,18 +195,18 @@ private void addElementToMesh(GuiElementRenderState elementState) { RenderPipeline pipeline = elementState.pipeline(); TextureSetup textureSetup = elementState.textureSetup(); ScreenRectangle scissorArea = elementState.scissorArea(); - if (pipeline != this.previousPipeline || this.scissorChanged(scissorArea, this.previousScissorArea) || !textureSetup.equals(this.previousTextureSetup)) { - if (this.bufferBuilder != null) { - this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea); - } - - this.bufferBuilder = this.getBufferBuilder(pipeline); + if (this.previousDraw == null + || pipeline != this.previousPipeline + || this.scissorChanged(scissorArea, this.previousScissorArea) + || !textureSetup.equals(this.previousTextureSetup)) { this.previousPipeline = pipeline; this.previousTextureSetup = textureSetup; this.previousScissorArea = scissorArea; + this.previousDraw = this.vertexBuffer.appendDraw(pipeline.getVertexFormatBinding(0), pipeline.getPrimitiveTopology()); + this.draws.add(new Draw(this.previousDraw, pipeline, textureSetup, scissorArea)); } - elementState.buildVertices(this.bufferBuilder); + elementState.buildVertices(this.vertexBuffer.getVertexBuilder(Objects.requireNonNull(this.previousDraw))); } private void prepareText() { @@ -278,22 +214,9 @@ private void prepareText() { final Matrix3x2fc pose = text.pose; final ScreenRectangle scissor = text.scissor; text.ensurePrepared().visit(new Font.GlyphVisitor() { - { - Objects.requireNonNull(EpsilonGuiRenderer.this); - } - @Override - public void acceptGlyph(TextRenderable.Styled glyph) { - this.accept(glyph); - } - - @Override - public void acceptEffect(TextRenderable effect) { - this.accept(effect); - } - - private void accept(TextRenderable glyph) { - EpsilonGuiRenderer.this.renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, glyph, scissor)); + public void acceptRenderable(TextRenderable renderable) { + renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, renderable, scissor)); } }); }); @@ -322,12 +245,12 @@ private void prepareItemElements() { if (itemState.oversizedItemBounds() != null) { TrackingItemStackRenderState itemStackRenderState = itemState.itemStackRenderState(); OversizedItemRenderer oversizedItemRenderer = this.oversizedItemRenderers - .computeIfAbsent(itemStackRenderState.getModelIdentity(), key -> new OversizedItemRenderer(this.bufferSource)); + .computeIfAbsent(itemStackRenderState.getModelIdentity(), var0 -> new OversizedItemRenderer()); ScreenRectangle actualItemBounds = itemState.oversizedItemBounds(); OversizedItemRenderState oversizedItemRenderState = new OversizedItemRenderState( itemState, actualItemBounds.left(), actualItemBounds.top(), actualItemBounds.right(), actualItemBounds.bottom() ); - oversizedItemRenderer.prepare(oversizedItemRenderState, this.renderState, guiScale); + oversizedItemRenderer.prepare(oversizedItemRenderState, this.renderState, this.featureRenderDispatcher, guiScale); } } ); @@ -360,29 +283,26 @@ private void submitBlitFromItemAtlas(GuiItemRenderState itemState, GuiItemAtlas. private GuiItemAtlas prepareItemAtlas(Set itemsInFrame, int slotTextureSize) { if (this.itemAtlas != null && this.itemAtlas.tryPrepareFor(itemsInFrame)) { return this.itemAtlas; - } else { - int newTextureSize = GuiItemAtlas.computeTextureSizeFor(slotTextureSize, itemsInFrame.size()); - if (this.itemAtlas != null && this.itemAtlas.textureSize() == newTextureSize) { - LOGGER.warn( - "Too many items ({}) in UI, some will be skipped! (Reached maximum texture size {}x{})", - itemsInFrame.size(), - newTextureSize, - newTextureSize - ); - return this.itemAtlas; - } else { - if (this.itemAtlas != null) { - this.itemAtlas.close(); - } + } - this.itemAtlas = new GuiItemAtlas(this.submitNodeCollector, this.featureRenderDispatcher, this.bufferSource, newTextureSize, slotTextureSize); - return this.itemAtlas; - } + int newTextureSize = GuiItemAtlas.computeTextureSizeFor(slotTextureSize, itemsInFrame.size()); + if (this.itemAtlas != null && this.itemAtlas.textureSize() == newTextureSize) { + LOGGER.warn( + "Too many items ({}) in UI, some will be skipped! (Reached maximum texture size {}x{})", itemsInFrame.size(), newTextureSize, newTextureSize + ); + return this.itemAtlas; } + + if (this.itemAtlas != null) { + this.itemAtlas.close(); + } + + this.itemAtlas = new GuiItemAtlas(this.featureRenderDispatcher, newTextureSize, slotTextureSize); + return this.itemAtlas; } private int getGuiScaleInvalidatingItemAtlasIfChanged() { - int guiScale = mc.gameRenderer.getGameRenderState().windowRenderState.guiScale; + int guiScale = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState.guiScale; if (guiScale != this.cachedGuiScale) { this.invalidateItemAtlas(); @@ -403,114 +323,34 @@ private void invalidateItemAtlas() { } } - private void recordMesh(BufferBuilder bufferBuilder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) { - MeshData mesh = bufferBuilder.build(); - if (mesh != null) { - this.meshesToDraw.add(new EpsilonGuiRenderer.MeshToDraw(mesh, pipeline, textureSetup, scissorArea)); - } - } - - private void recordDraws() { - this.ensureVertexBufferSizes(); - CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); - Object2IntMap offsets = new Object2IntOpenHashMap<>(); - - for (EpsilonGuiRenderer.MeshToDraw meshToDraw : this.meshesToDraw) { - MeshData mesh = meshToDraw.mesh; - MeshData.DrawState drawState = mesh.drawState(); - VertexFormat format = drawState.format(); - MappableRingBuffer vertexBuffer = this.vertexBuffers.get(format); - if (!offsets.containsKey(format)) { - offsets.put(format, 0); + private void executeDraw(Draw draw, RenderPass renderPass) { + StagedVertexBuffer.ExecuteInfo executeInfo = this.vertexBuffer.getExecuteInfo(draw.draw); + if (executeInfo != null) { + RenderPipeline pipeline = draw.pipeline(); + renderPass.setPipeline(pipeline); + renderPass.setVertexBuffer(0, executeInfo.vertexBuffer().slice()); + ScreenRectangle scissorArea = draw.scissorArea(); + if (scissorArea != null) { + this.enableScissor(scissorArea, renderPass); + } else { + renderPass.disableScissor(); } - ByteBuffer meshVertexBuffer = mesh.vertexBuffer(); - int meshBufferSize = meshVertexBuffer.remaining(); - int offset = offsets.getInt(format); - - try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(vertexBuffer.currentBuffer().slice(offset, meshBufferSize), false, true)) { - MemoryUtil.memCopy(meshVertexBuffer, mappedView.data()); + if (draw.textureSetup.texure0() != null) { + renderPass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0()); } - offsets.put(format, offset + meshBufferSize); - this.draws - .add( - new EpsilonGuiRenderer.Draw( - vertexBuffer.currentBuffer(), - offset / format.getVertexSize(), - drawState.mode(), - drawState.indexCount(), - meshToDraw.pipeline, - meshToDraw.textureSetup, - meshToDraw.scissorArea - ) - ); - meshToDraw.close(); - } - } - - private void ensureVertexBufferSizes() { - Object2IntMap requiredSizes = this.calculatedRequiredVertexBufferSizes(); - - for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry entry : requiredSizes.object2IntEntrySet()) { - VertexFormat vertexFormat = entry.getKey(); - int requiredSize = entry.getIntValue(); - MappableRingBuffer vertexBuffer = this.vertexBuffers.get(vertexFormat); - if (vertexBuffer == null || vertexBuffer.size() < requiredSize) { - if (vertexBuffer != null) { - vertexBuffer.close(); - } - - this.vertexBuffers.put(vertexFormat, new MappableRingBuffer(() -> "GUI vertex buffer for " + vertexFormat, 34, requiredSize)); + if (draw.textureSetup.texure1() != null) { + renderPass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1()); } - } - } - - private Object2IntMap calculatedRequiredVertexBufferSizes() { - Object2IntMap requiredVertexBufferSizes = new Object2IntOpenHashMap<>(); - for (EpsilonGuiRenderer.MeshToDraw meshToDraw : this.meshesToDraw) { - MeshData.DrawState drawState = meshToDraw.mesh.drawState(); - VertexFormat format = drawState.format(); - if (!requiredVertexBufferSizes.containsKey(format)) { - requiredVertexBufferSizes.put(format, 0); + if (draw.textureSetup.texure2() != null) { + renderPass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2()); } - requiredVertexBufferSizes.put(format, requiredVertexBufferSizes.getInt(format) + drawState.vertexCount() * format.getVertexSize()); + renderPass.setIndexBuffer(executeInfo.indexBuffer(), executeInfo.indexType()); + renderPass.drawIndexed(executeInfo.indexCount(), 1, executeInfo.firstIndex(), executeInfo.baseVertex(), 0); } - - return requiredVertexBufferSizes; - } - - private void executeDraw(EpsilonGuiRenderer.Draw draw, RenderPass renderPass, GpuBuffer indexBuffer, VertexFormat.IndexType indexType) { - RenderPipeline pipeline = draw.pipeline(); - renderPass.setPipeline(pipeline); - renderPass.setVertexBuffer(0, draw.vertexBuffer); - ScreenRectangle scissorArea = draw.scissorArea(); - if (scissorArea != null) { - this.enableScissor(scissorArea, renderPass); - } else { - renderPass.disableScissor(); - } - - if (draw.textureSetup.texure0() != null) { - renderPass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0()); - } - - if (draw.textureSetup.texure1() != null) { - renderPass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1()); - } - - if (draw.textureSetup.texure2() != null) { - renderPass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2()); - } - - renderPass.setIndexBuffer(indexBuffer, indexType); - renderPass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1); - } - - private BufferBuilder getBufferBuilder(RenderPipeline pipeline) { - return new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat()); } private boolean scissorChanged(@Nullable ScreenRectangle newScissor, @Nullable ScreenRectangle oldScissor) { @@ -522,14 +362,13 @@ private boolean scissorChanged(@Nullable ScreenRectangle newScissor, @Nullable S } private void enableScissor(ScreenRectangle rectangle, RenderPass renderPass) { - WindowRenderState windowState = mc.gameRenderer.getGameRenderState().windowRenderState; - int windowHeight = windowState.height; - int guiScale = windowState.guiScale; + WindowRenderState window = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState; + int guiScale = window.guiScale; double left = rectangle.left() * guiScale; - double bottom = windowHeight - rectangle.bottom() * guiScale; - double width = rectangle.width() * guiScale; - double height = rectangle.height() * guiScale; - renderPass.enableScissor((int) left, (int) bottom, Math.max(0, (int) width), Math.max(0, (int) height)); + double top = rectangle.top() * guiScale; + double right = Math.min(rectangle.right() * guiScale, window.width); + double bottom = Math.min(rectangle.bottom() * guiScale, window.height); + renderPass.enableScissor((int)left, window.height - (int)bottom, Math.max(0, (int)(right - left)), Math.max(0, (int)(bottom - top))); } public void registerPanoramaTextures(TextureManager textureManager) { @@ -538,39 +377,17 @@ public void registerPanoramaTextures(TextureManager textureManager) { @Override public void close() { - this.byteBufferBuilder.close(); + this.vertexBuffer.close(); if (this.itemAtlas != null) { this.itemAtlas.close(); this.itemAtlas = null; } this.guiProjectionMatrixBuffer.close(); - - for (MappableRingBuffer buffer : this.vertexBuffers.values()) { - buffer.close(); - } - this.oversizedItemRenderers.values().forEach(PictureInPictureRenderer::close); this.cubeMap.close(); } - private record Draw( - GpuBuffer vertexBuffer, - int baseVertex, - VertexFormat.Mode mode, - int indexCount, - RenderPipeline pipeline, - TextureSetup textureSetup, - ScreenRectangle scissorArea - ) { + private record Draw(StagedVertexBuffer.Draw draw, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) { } - - private record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup, - ScreenRectangle scissorArea) implements AutoCloseable { - @Override - public void close() { - this.mesh.close(); - } - } - } diff --git a/common/src/main/java/com/github/epsilon/utils/render/Render3DUtils.java b/common/src/main/java/com/github/epsilon/utils/render/Render3DUtils.java index 401ea285..0f8ce0fb 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/Render3DUtils.java +++ b/common/src/main/java/com/github/epsilon/utils/render/Render3DUtils.java @@ -1,15 +1,12 @@ package com.github.epsilon.utils.render; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.immediate.LuminImmediateRenderer; import com.mojang.blaze3d.pipeline.DepthStencilState; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.platform.CompareOp; import com.mojang.blaze3d.vertex.*; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.client.renderer.rendertype.LayeringTransform; -import net.minecraft.client.renderer.rendertype.OutputTarget; -import net.minecraft.client.renderer.rendertype.RenderSetup; -import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; import net.minecraft.world.phys.AABB; @@ -29,38 +26,22 @@ public class Render3DUtils { .withCull(false) .build(); - private static final RenderType FILLED_BOX = RenderType.create("sakura_filled_box", - RenderSetup.builder(FILLED_BOX_PIPELINE) - .sortOnUpload() - .setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING) - .createRenderSetup()); - private static final RenderPipeline LINES_PIPELINE = RenderPipeline.builder(RenderPipelines.LINES_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipeline/lines")) .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false)) .withCull(false) .build(); - public static final RenderType LINES = RenderType.create("sakura_lines", RenderSetup.builder(LINES_PIPELINE) - .setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING) - .setOutputTarget(OutputTarget.ITEM_ENTITY_TARGET) - .createRenderSetup()); - public static void drawFilledBox(BlockPos blockPos, Color color) { drawFilledBox(new AABB(blockPos), color.getRGB()); } public static void drawFilledBox(AABB box, Color color) { - int c = color.getRGB(); - drawFilledFadeBox(box, c, c); + drawFilledBox(box, color.getRGB()); } public static void drawFilledBox(AABB box, int c) { - drawFilledFadeBox(box, c, c); - } - - public static void drawFilledFadeBox(AABB box, int c, int c1) { - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + LuminImmediateRenderer.PosColorQuads builder = LuminImmediateRenderer.beginPosColorQuads(FILLED_BOX_PIPELINE); Vec3 camPos = mc.getEntityRenderDispatcher().camera.position(); float minX = (float) (box.minX - camPos.x); @@ -70,55 +51,55 @@ public static void drawFilledFadeBox(AABB box, int c, int c1) { float maxY = (float) (box.maxY - camPos.y); float maxZ = (float) (box.maxZ - camPos.z); - Matrix4f matrix = mc.gameRenderer.getGameRenderState().levelRenderState.cameraRenderState.viewRotationMatrix; - - vertex(buffer, matrix, minX, minY, minZ, c); - vertex(buffer, matrix, minX, minY, maxZ, c); - vertex(buffer, matrix, maxX, minY, maxZ, c); - vertex(buffer, matrix, maxX, minY, minZ, c); - - vertex(buffer, matrix, minX, maxY, minZ, c1); - vertex(buffer, matrix, maxX, maxY, minZ, c1); - vertex(buffer, matrix, maxX, maxY, maxZ, c1); - vertex(buffer, matrix, minX, maxY, maxZ, c); - - vertex(buffer, matrix, minX, minY, minZ, c); - vertex(buffer, matrix, minX, maxY, minZ, c1); - vertex(buffer, matrix, maxX, maxY, minZ, c1); - vertex(buffer, matrix, maxX, minY, minZ, c); - - vertex(buffer, matrix, maxX, minY, minZ, c); - vertex(buffer, matrix, maxX, maxY, minZ, c1); - vertex(buffer, matrix, maxX, maxY, maxZ, c1); - vertex(buffer, matrix, maxX, minY, maxZ, c); - - vertex(buffer, matrix, minX, minY, maxZ, c); - vertex(buffer, matrix, maxX, minY, maxZ, c); - vertex(buffer, matrix, maxX, maxY, maxZ, c1); - vertex(buffer, matrix, minX, maxY, maxZ, c1); - - vertex(buffer, matrix, minX, minY, minZ, c); - vertex(buffer, matrix, minX, minY, maxZ, c); - vertex(buffer, matrix, minX, maxY, maxZ, c1); - vertex(buffer, matrix, minX, maxY, minZ, c1); - - FILLED_BOX.draw(buffer.buildOrThrow()); - } - - public static void drawOutlineBox(PoseStack stack, BlockPos blockPos, Color color) { - drawOutlineBox(stack, new AABB(blockPos), color); - } - - public static void drawOutlineBox(PoseStack stack, AABB box, Color color) { - drawOutlineBox(stack, box, color.getRGB(), 2f); - } - - public static void drawOutlineBox(PoseStack stack, AABB box, Color color, float thickness) { - drawOutlineBox(stack, box, color.getRGB(), thickness); + Matrix4f matrix = mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.viewRotationMatrix; + + quad(builder, matrix, + minX, minY, minZ, c, + minX, minY, maxZ, c, + maxX, minY, maxZ, c, + maxX, minY, minZ, c + ); + + quad(builder, matrix, + minX, maxY, minZ, c, + maxX, maxY, minZ, c, + maxX, maxY, maxZ, c, + minX, maxY, maxZ, c + ); + + quad(builder, matrix, + minX, minY, minZ, c, + maxX, minY, minZ, c, + maxX, maxY, minZ, c, + minX, maxY, minZ, c + ); + + quad(builder, matrix, + maxX, minY, minZ, c, + maxX, minY, maxZ, c, + maxX, maxY, maxZ, c, + maxX, maxY, minZ, c + ); + + quad(builder, matrix, + minX, minY, maxZ, c, + minX, maxY, maxZ, c, + maxX, maxY, maxZ, c, + maxX, minY, maxZ, c + ); + + quad(builder, matrix, + minX, minY, minZ, c, + minX, maxY, minZ, c, + minX, maxY, maxZ, c, + minX, minY, maxZ, c + ); + + builder.end(); } public static void drawOutlineBox(PoseStack stack, AABB box, int color, float thickness) { - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH); + LuminImmediateRenderer.Lines builder = LuminImmediateRenderer.beginLines(LINES_PIPELINE); Vec3 camPos = mc.getEntityRenderDispatcher().camera.position(); float minX = (float) (box.minX - camPos.x); @@ -128,35 +109,51 @@ public static void drawOutlineBox(PoseStack stack, AABB box, int color, float th float maxY = (float) (box.maxY - camPos.y); float maxZ = (float) (box.maxZ - camPos.z); + Matrix4f matrix = mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.viewRotationMatrix; PoseStack.Pose entry = stack.last(); - Matrix4f matrix = entry.pose(); - vertexLine(buffer, matrix, entry, minX, minY, minZ, maxX, minY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, minY, minZ, maxX, minY, maxZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, minY, maxZ, minX, minY, maxZ, color, thickness); - vertexLine(buffer, matrix, entry, minX, minY, maxZ, minX, minY, minZ, color, thickness); + vertexLine(builder, matrix, entry, minX, minY, minZ, maxX, minY, minZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, minZ, maxX, minY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, maxZ, minX, minY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, minX, minY, maxZ, minX, minY, minZ, color, thickness); + + vertexLine(builder, matrix, entry, minX, minY, minZ, maxX, minY, minZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, minZ, maxX, minY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, maxZ, minX, minY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, minX, minY, maxZ, minX, minY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, minX, maxY, minZ, maxX, maxY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, maxY, minZ, maxX, maxY, maxZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, maxY, maxZ, minX, maxY, maxZ, color, thickness); - vertexLine(buffer, matrix, entry, minX, maxY, maxZ, minX, maxY, minZ, color, thickness); + vertexLine(builder, matrix, entry, minX, maxY, minZ, maxX, maxY, minZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, maxY, minZ, maxX, maxY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, maxY, maxZ, minX, maxY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, minX, maxY, maxZ, minX, maxY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, minX, minY, minZ, minX, maxY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, minY, minZ, maxX, maxY, minZ, color, thickness); - vertexLine(buffer, matrix, entry, maxX, minY, maxZ, maxX, maxY, maxZ, color, thickness); - vertexLine(buffer, matrix, entry, minX, minY, maxZ, minX, maxY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, minX, minY, minZ, minX, maxY, minZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, minZ, maxX, maxY, minZ, color, thickness); + vertexLine(builder, matrix, entry, maxX, minY, maxZ, maxX, maxY, maxZ, color, thickness); + vertexLine(builder, matrix, entry, minX, minY, maxZ, minX, maxY, maxZ, color, thickness); - LINES.draw(buffer.buildOrThrow()); + builder.end(); } - private static void vertex(BufferBuilder buffer, Matrix4f matrix, float x, float y, float z, int color) { - buffer.addVertex(matrix, x, y, z).setColor(color); + private static void vertex(LuminImmediateRenderer.PosColorQuads builder, Matrix4f matrix, float x, float y, float z, int color) { + builder.vertex(matrix, x, y, z, color); } - private static void vertexLine(BufferBuilder buffer, Matrix4f matrix, PoseStack.Pose entry, float x1, float y1, float z1, float x2, float y2, float z2, int color, float thickness) { + private static void quad(LuminImmediateRenderer.PosColorQuads builder, Matrix4f matrix, + float x1, float y1, float z1, int c1, + float x2, float y2, float z2, int c2, + float x3, float y3, float z3, int c3, + float x4, float y4, float z4, int c4) { + vertex(builder, matrix, x1, y1, z1, c1); + vertex(builder, matrix, x2, y2, z2, c2); + vertex(builder, matrix, x3, y3, z3, c3); + vertex(builder, matrix, x4, y4, z4, c4); + } + + private static void vertexLine(LuminImmediateRenderer.Lines builder, Matrix4f matrix, PoseStack.Pose entry, float x1, float y1, float z1, float x2, float y2, float z2, int color, float thickness) { Vector3f normal = getNormal(x1, y1, z1, x2, y2, z2); - buffer.addVertex(matrix, x1, y1, z1).setColor(color).setNormal(entry, normal.x, normal.y, normal.z).setLineWidth(thickness); - buffer.addVertex(matrix, x2, y2, z2).setColor(color).setNormal(entry, normal.x, normal.y, normal.z).setLineWidth(thickness); + builder.vertex(matrix, entry, x1, y1, z1, color, normal.x, normal.y, normal.z, thickness); + builder.vertex(matrix, entry, x2, y2, z2, color, normal.x, normal.y, normal.z, thickness); } private static Vector3f getNormal(float x1, float y1, float z1, float x2, float y2, float z2) { @@ -167,4 +164,12 @@ private static Vector3f getNormal(float x1, float y1, float z1, float x2, float return new Vector3f(xNormal / normalSqrt, yNormal / normalSqrt, zNormal / normalSqrt); } + public static void drawOutlineBox(PoseStack stack, AABB box, Color color) { + drawOutlineBox(stack, box, color.getRGB(), 1.5f); + } + + public static void drawOutlineBox(PoseStack stack, BlockPos pos, Color color) { + drawOutlineBox(stack, new AABB(pos), color.getRGB(), 1.5f); + } + } diff --git a/common/src/main/java/com/github/epsilon/utils/render/WorldToScreen.java b/common/src/main/java/com/github/epsilon/utils/render/WorldToScreen.java index d0938e23..52c048eb 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/WorldToScreen.java +++ b/common/src/main/java/com/github/epsilon/utils/render/WorldToScreen.java @@ -15,14 +15,14 @@ public class WorldToScreen { public static Vector3f getWorldPositionToScreen(Vec3 pos) { - final var camera = mc.gameRenderer.getMainCamera(); + final var camera = mc.gameRenderer.mainCamera(); final Vector3f position = new Vec3( pos.x - camera.position().x, pos.y - camera.position().y, pos.z - camera.position().z ).toVector3f(); - CameraRenderState cameraState = mc.gameRenderer.getGameRenderState().levelRenderState.cameraRenderState; + CameraRenderState cameraState = mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState; Matrix4f viewProjectionMatrix = new Matrix4f(cameraState.projectionMatrix).mul(cameraState.viewRotationMatrix); final int[] viewport = new int[]{0, 0, mc.getWindow().getWidth(), mc.getWindow().getHeight()}; @@ -49,9 +49,9 @@ public static Vector4d getEntityPositionsOn2D(LivingEntity target, float tickDel public static Vector4d projectAbsoluteAABBOn2D(AABB absoluteBoundingBox) { final int[] viewport = new int[]{0, 0, mc.getWindow().getWidth(), mc.getWindow().getHeight()}; - CameraRenderState cameraState = mc.gameRenderer.getGameRenderState().levelRenderState.cameraRenderState; + CameraRenderState cameraState = mc.gameRenderer.gameRenderState().levelRenderState.cameraRenderState; Matrix4f viewProjectionMatrix = new Matrix4f(cameraState.projectionMatrix).mul(cameraState.viewRotationMatrix); - Vec3 cameraPos = mc.gameRenderer.getMainCamera().position(); + Vec3 cameraPos = mc.gameRenderer.mainCamera().position(); final Vector4d projection = projectEntity(viewport, viewProjectionMatrix, absoluteBoundingBox, cameraPos); if (projection == null) return null; @@ -102,7 +102,7 @@ public static Vector4d projectEntity(final int[] viewport, final Matrix4f matrix } public static Vector4d projectEntity(final int[] viewport, final Matrix4f matrix, final AABB absoluteBoundingBox) { - Vec3 cameraPos = mc.gameRenderer.getMainCamera().position(); + Vec3 cameraPos = mc.gameRenderer.mainCamera().position(); return projectEntity(viewport, matrix, absoluteBoundingBox, cameraPos); } diff --git a/common/src/main/java/com/github/epsilon/utils/render/esp/CaptureMarkESP.java b/common/src/main/java/com/github/epsilon/utils/render/esp/CaptureMarkESP.java index ea695800..e7e1adf8 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/esp/CaptureMarkESP.java +++ b/common/src/main/java/com/github/epsilon/utils/render/esp/CaptureMarkESP.java @@ -1,6 +1,7 @@ package com.github.epsilon.utils.render.esp; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.immediate.LuminImmediateRenderer; import com.github.epsilon.utils.render.ColorUtils; import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.ColorTargetState; @@ -57,26 +58,25 @@ public static void render(PoseStack poseStack, LivingEntity target, double espSi poseStack.pushPose(); poseStack.translate(ex, ey + target.getBbHeight() * 0.5, ez); - Camera camera = mc.gameRenderer.getMainCamera(); + Camera camera = mc.gameRenderer.mainCamera(); poseStack.mulPose(Axis.YP.rotationDegrees(-camera.yRot())); poseStack.mulPose(Axis.XP.rotationDegrees(camera.xRot())); poseStack.mulPose(Axis.ZP.rotationDegrees(rotation)); - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); - Matrix4f matrix = poseStack.last().pose(); + LuminImmediateRenderer.PosTexColorQuads builder = LuminImmediateRenderer.beginPosTexColorQuads(TARGET_ICON_PIPELINE, CAPTUREMARK_TEX); - Color c1 = getColorForProgress(0f, waveSpeed, color1, color2, timeSeconds); + Color c1 = getColorForProgress(0, waveSpeed, color1, color2, timeSeconds); Color c2 = getColorForProgress(0.25f, waveSpeed, color1, color2, timeSeconds); Color c3 = getColorForProgress(0.5f, waveSpeed, color1, color2, timeSeconds); Color c4 = getColorForProgress(0.75f, waveSpeed, color1, color2, timeSeconds); - buffer.addVertex(matrix, -size, -size, 0).setUv(0, 0).setColor(c1.getRGB()); - buffer.addVertex(matrix, -size, size, 0).setUv(0, 1).setColor(c2.getRGB()); - buffer.addVertex(matrix, size, size, 0).setUv(1, 1).setColor(c3.getRGB()); - buffer.addVertex(matrix, size, -size, 0).setUv(1, 0).setColor(c4.getRGB()); + builder.vertex(matrix, -size, -size, 0, 0, 0, c1.getRGB()); + builder.vertex(matrix, -size, size, 0, 0, 1, c2.getRGB()); + builder.vertex(matrix, size, size, 0, 1, 1, c3.getRGB()); + builder.vertex(matrix, size, -size, 0, 1, 0, c4.getRGB()); - TARGET_ICON_LAYER.draw(buffer.buildOrThrow()); + builder.end(); poseStack.popPose(); } diff --git a/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java b/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java index 7c9504fb..9ac99577 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java +++ b/common/src/main/java/com/github/epsilon/utils/render/esp/CircleESP.java @@ -1,21 +1,18 @@ package com.github.epsilon.utils.render.esp; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.immediate.LuminImmediateRenderer; import com.mojang.blaze3d.pipeline.DepthStencilState; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.platform.CompareOp; import com.mojang.blaze3d.vertex.*; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.client.renderer.rendertype.RenderSetup; -import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.util.Mth; -import net.minecraft.util.Util; import net.minecraft.world.entity.LivingEntity; import org.joml.Matrix4f; import org.joml.Vector2f; import java.awt.*; -import java.util.function.Function; import static com.github.epsilon.Constants.mc; @@ -25,21 +22,14 @@ public class CircleESP { .withLocation(ResourceLocationUtils.getIdentifier("pipeline/triangle_strip")) .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false)) .withCull(false) - .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_STRIP) .build(); private static final RenderPipeline TRIANGLE_STRIP_PIPELINE = RenderPipeline.builder(RenderPipelines.DEBUG_FILLED_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipeline/triangle_strip")) .withDepthStencilState(new DepthStencilState(CompareOp.LESS_THAN_OR_EQUAL, false)) .withCull(false) - .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLE_STRIP) .build(); - private static final Function TRIANGLE_STRIP = Util.memoize( - renderPipeline -> RenderType.create("epsilon_triangle_strip", RenderSetup.builder(renderPipeline) - .createRenderSetup()) - ); - private static final RenderPipeline CIRCLE_LINES_NO_DEPTH_PIPELINE = RenderPipeline.builder(RenderPipelines.LINES_SNIPPET) .withLocation(ResourceLocationUtils.getIdentifier("pipeline/circle_lines")) .withDepthStencilState(new DepthStencilState(CompareOp.ALWAYS_PASS, false)) @@ -52,11 +42,6 @@ public class CircleESP { .withCull(false) .build(); - private static final Function CIRCLE_LINES = Util.memoize( - renderPipeline -> RenderType.create("epsilon_circle_lines", RenderSetup.builder(renderPipeline) - .createRenderSetup()) - ); - public static void render(PoseStack poseStack, LivingEntity target, float radius, Color sideColor, Color lineColor, float alphaFactor) { boolean canSee = mc.player.hasLineOfSight(target); @@ -72,20 +57,23 @@ public static void render(PoseStack poseStack, LivingEntity target, float radius poseStack.pushPose(); poseStack.translate(x, y, z); - BufferBuilder triBuffer = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLE_STRIP, DefaultVertexFormat.POSITION_COLOR); Matrix4f matrix = poseStack.last().pose(); + RenderPipeline triPipeline = canSee ? TRIANGLE_STRIP_PIPELINE : TRIANGLE_STRIP_NO_DEPTH_PIPELINE; + LuminImmediateRenderer.PosColorQuads triBuilder = LuminImmediateRenderer.beginPosColorQuads(triPipeline); + for (float i = 0; i <= (Math.PI * 2); i += ((float) Math.PI * 2) / 64.F) { float vecX = (float) (radius * Math.cos(i)); float vecZ = (float) (radius * Math.sin(i)); - triBuffer.addVertex(matrix, vecX, (float) (-Math.sin(ticks + 1) / 2.7f), vecZ).setColor(sideColor.getAlpha() / 255.0f, sideColor.getGreen() / 255.0f, sideColor.getBlue() / 255.0f, 0.0f); - triBuffer.addVertex(matrix, vecX, 0, vecZ).setColor(sideColor.getAlpha() / 255.0f, sideColor.getGreen() / 255.0f, sideColor.getBlue() / 255.0f, 0.52f * alpha); + triBuilder.vertex(matrix, vecX, (float) (-Math.sin(ticks + 1) / 2.7f), vecZ, new Color(sideColor.getAlpha() / 255.0f, sideColor.getGreen() / 255.0f, sideColor.getBlue() / 255.0f, 0.0f).getRGB()); + triBuilder.vertex(matrix, vecX, 0, vecZ, new Color(sideColor.getAlpha() / 255.0f, sideColor.getGreen() / 255.0f, sideColor.getBlue() / 255.0f, 0.52f * alpha).getRGB()); } - TRIANGLE_STRIP.apply(canSee ? TRIANGLE_STRIP_PIPELINE : TRIANGLE_STRIP_NO_DEPTH_PIPELINE).draw(triBuffer.buildOrThrow()); + triBuilder.end(); - BufferBuilder lineBuffer = Tesselator.getInstance().begin(VertexFormat.Mode.LINES, DefaultVertexFormat.POSITION_COLOR_NORMAL_LINE_WIDTH); + RenderPipeline linePipeline = canSee ? CIRCLE_LINES_PIPELINE : CIRCLE_LINES_NO_DEPTH_PIPELINE; + LuminImmediateRenderer.Lines lineBuilder = LuminImmediateRenderer.beginLines(linePipeline); PoseStack.Pose entry = poseStack.last(); for (int i = 0; i <= 180; i++) { @@ -96,11 +84,11 @@ public static void render(PoseStack poseStack, LivingEntity target, float radius Vector2f linePoint = getPoint(radAngle, radius); Vector2f normal = getNormal(radAngle); - lineBuffer.addVertex(entry, linePoint.x, 0f, linePoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, 0f, normal.y).setLineWidth(2f); - lineBuffer.addVertex(entry, nextPoint.x, 0f, nextPoint.y).setColor(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).setNormal(entry, normal.x, 0f, normal.y).setLineWidth(2f); + lineBuilder.vertex(matrix, entry, linePoint.x, 0f, linePoint.y, new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).getRGB(), normal.x, 0f, normal.y, 2f); + lineBuilder.vertex(matrix, entry, nextPoint.x, 0f, nextPoint.y, new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), Math.round(lineColor.getAlpha() * alpha)).getRGB(), normal.x, 0f, normal.y, 2f); } - CIRCLE_LINES.apply(canSee ? CIRCLE_LINES_PIPELINE : CIRCLE_LINES_NO_DEPTH_PIPELINE).draw(lineBuffer.buildOrThrow()); + lineBuilder.end(); poseStack.popPose(); } diff --git a/common/src/main/java/com/github/epsilon/utils/render/esp/FireflyESP.java b/common/src/main/java/com/github/epsilon/utils/render/esp/FireflyESP.java index e23db0c8..2de9c8f0 100644 --- a/common/src/main/java/com/github/epsilon/utils/render/esp/FireflyESP.java +++ b/common/src/main/java/com/github/epsilon/utils/render/esp/FireflyESP.java @@ -1,6 +1,7 @@ package com.github.epsilon.utils.render.esp; import com.github.epsilon.assets.resources.ResourceLocationUtils; +import com.github.epsilon.graphics.immediate.LuminImmediateRenderer; import com.github.epsilon.utils.render.ColorUtils; import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.ColorTargetState; @@ -11,18 +12,12 @@ import com.mojang.math.Axis; import net.minecraft.client.Camera; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.client.renderer.rendertype.LayeringTransform; -import net.minecraft.client.renderer.rendertype.OutputTarget; -import net.minecraft.client.renderer.rendertype.RenderSetup; -import net.minecraft.client.renderer.rendertype.RenderType; import net.minecraft.resources.Identifier; import net.minecraft.util.Mth; -import net.minecraft.util.Util; import net.minecraft.world.entity.LivingEntity; import org.joml.Matrix4f; import java.awt.*; -import java.util.function.Function; import static com.github.epsilon.Constants.mc; @@ -50,19 +45,10 @@ public enum ColorMode { .withCull(false) .build(); - private static final Function TARGET_ICON_LAYER = Util.memoize( - renderPipeline -> RenderType.create("epsilon_target_icon", RenderSetup.builder(renderPipeline) - .withTexture("Sampler0", FIREFLY_TEX) - .sortOnUpload() - .setLayeringTransform(LayeringTransform.VIEW_OFFSET_Z_LAYERING) - .setOutputTarget(OutputTarget.MAIN_TARGET) - .createRenderSetup()) - ); - public static void render(PoseStack stack, LivingEntity target, int espLength, int factor, double shaking, double amplitude, Color color, ColorMode colorMode, Color secondColor, double colorMix, double colorSpeed, double rainbowSpeed, double rainbowSaturation, double rainbowBrightness) { boolean canSee = mc.player.hasLineOfSight(target); - Camera camera = mc.gameRenderer.getMainCamera(); + Camera camera = mc.gameRenderer.mainCamera(); float tickDelta = mc.getDeltaTracker().getGameTimeDeltaPartialTick(true); double tPosX = Mth.lerp(tickDelta, target.xOld, target.getX()) - camera.position().x; @@ -70,7 +56,8 @@ public static void render(PoseStack stack, LivingEntity target, int espLength, i double tPosZ = Mth.lerp(tickDelta, target.zOld, target.getZ()) - camera.position().z; float iAge = (float) (target.tickCount - 1) + tickDelta; - BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); + RenderPipeline usePipeline = canSee ? TARGET_ICON_PIPELINE : TARGET_ICON_NO_DEPTH_PIPELINE; + LuminImmediateRenderer.PosTexColorQuads builder = LuminImmediateRenderer.beginPosTexColorQuads(usePipeline, FIREFLY_TEX); for (int j = 0; j < 3; j++) { for (int i = 0; i <= espLength; i++) { @@ -103,16 +90,16 @@ public static void render(PoseStack stack, LivingEntity target, int espLength, i (float) rainbowBrightness ).getRGB(); - buffer.addVertex(matrix, -scale, scale, 0).setUv(0f, 1f).setColor(renderColor); - buffer.addVertex(matrix, scale, scale, 0).setUv(1f, 1f).setColor(renderColor); - buffer.addVertex(matrix, scale, -scale, 0).setUv(1f, 0f).setColor(renderColor); - buffer.addVertex(matrix, -scale, -scale, 0).setUv(0f, 0f).setColor(renderColor); + builder.vertex(matrix, -scale, scale, 0, 0f, 1f, renderColor); + builder.vertex(matrix, scale, scale, 0, 1f, 1f, renderColor); + builder.vertex(matrix, scale, -scale, 0, 1f, 0f, renderColor); + builder.vertex(matrix, -scale, -scale, 0, 0f, 0f, renderColor); stack.popPose(); } } - TARGET_ICON_LAYER.apply(canSee ? TARGET_ICON_PIPELINE : TARGET_ICON_NO_DEPTH_PIPELINE).draw(buffer.buildOrThrow()); + builder.end(); } private static Color resolveColor(float age, int index, int ringIndex, int espLength, ColorMode mode, Color primaryColor, Color secondaryColor, float mixAmount, float blendSpeed, float rainbowSpeed, float rainbowSaturation, float rainbowBrightness) { diff --git a/common/src/main/java/com/github/epsilon/utils/rotation/RotationUtils.java b/common/src/main/java/com/github/epsilon/utils/rotation/RotationUtils.java index d01b5905..61279dca 100644 --- a/common/src/main/java/com/github/epsilon/utils/rotation/RotationUtils.java +++ b/common/src/main/java/com/github/epsilon/utils/rotation/RotationUtils.java @@ -76,7 +76,7 @@ private static Direction findBestDirection(BlockPos pos, boolean useGrimCheck) { } public static boolean canSee(BlockPos pos, Direction side) { - Vec3 testVec = pos.getCenter().add(side.getUnitVec3i().getX() * 0.5, side.getUnitVec3i().getY() * 0.5, side.getUnitVec3i().getZ() * 0.5); + Vec3 testVec = Vec3.atCenterOf(pos).add(side.getUnitVec3i().getX() * 0.5, side.getUnitVec3i().getY() * 0.5, side.getUnitVec3i().getZ() * 0.5); HitResult result = mc.level.clip(new ClipContext(mc.player.getEyePosition(), testVec, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, mc.player)); return result.getType() == HitResult.Type.MISS; } @@ -217,7 +217,7 @@ public static Vector2f calculate(Vec3 to) { } public static Vector2f calculate(BlockPos to) { - return calculate(mc.player.position().add(0, mc.player.getEyeHeight(mc.player.getPose()), 0), to.getCenter()); + return calculate(mc.player.position().add(0, mc.player.getEyeHeight(mc.player.getPose()), 0), Vec3.atCenterOf(to)); } public static Vector2f calculate(Vec3 to, Direction direction) { diff --git a/common/src/main/resources/META-INF/accesstransformer.cfg b/common/src/main/resources/META-INF/accesstransformer.cfg index 4f248683..ffcce2f3 100644 --- a/common/src/main/resources/META-INF/accesstransformer.cfg +++ b/common/src/main/resources/META-INF/accesstransformer.cfg @@ -34,3 +34,6 @@ public net.minecraft.client.renderer.ItemInHandRenderer mainHandHeight public net.minecraft.client.renderer.ItemInHandRenderer offHandHeight public net.minecraft.client.renderer.ItemInHandRenderer mainHandItem public net.minecraft.client.renderer.ItemInHandRenderer offHandItem + +# Blaze3D Vulkan +public com.mojang.blaze3d.systems.GpuDevice backend \ No newline at end of file diff --git a/common/src/main/resources/assets/open_epsilon/shaders/compute/compute_test.csh b/common/src/main/resources/assets/open_epsilon/shaders/compute/compute_test.csh new file mode 100644 index 00000000..8993f3e6 --- /dev/null +++ b/common/src/main/resources/assets/open_epsilon/shaders/compute/compute_test.csh @@ -0,0 +1,23 @@ +#version 450 + +layout(local_size_x = 64) in; + +layout(std430, set = 0, binding = 0) readonly buffer InputBuffer { + float data[]; +} inputData; + +layout(std430, set = 0, binding = 1) writeonly buffer OutputBuffer { + float data[]; +} outputData; + +void main() { + uint index = gl_GlobalInvocationID.x; + uint inputLength = uint(inputData.data.length()); + uint outputLength = uint(outputData.data.length()); + + if (index >= inputLength || index >= outputLength) { + return; + } + + outputData.data[index] = inputData.data[index]; +} \ No newline at end of file diff --git a/common/src/main/resources/assets/open_epsilon/shaders/compute/crystal_damage.csh b/common/src/main/resources/assets/open_epsilon/shaders/compute/crystal_damage.csh new file mode 100644 index 00000000..974e5f98 --- /dev/null +++ b/common/src/main/resources/assets/open_epsilon/shaders/compute/crystal_damage.csh @@ -0,0 +1,210 @@ +#version 450 + +/* + * gl_WorkGroupID.x = taskId(每个 workgroup 处理一个任务) + * gl_LocalInvocationID.x = rayId(每个线程最多追踪一条射线) + */ + +layout(local_size_x = 64) in; + +layout(std430, set = 0, binding = 0) readonly buffer VoxelGrid { + ivec4 header; // xyz = gridOrigin, w = gridSize + uint voxelBits[]; +} grid; + +struct Task { + vec4 crystalPos; // xyz = 爆炸中心, w = 半径 + vec4 targetPos; // xyz = 目标脚底, w = unused + vec4 targetSize; // x = halfWidth, y = height + vec4 params; // x = armor, y = toughness, z = enchantProt, w = difficulty + vec4 extra; // x = resistanceMultiplier, y = applyDifficulty(0/1) +}; + +layout(std430, set = 0, binding = 1) readonly buffer TaskBuffer { + uint taskCount; + uint _pad0; + uint _pad1; + uint _pad2; + Task tasks[]; +} taskBuf; + +layout(std430, set = 0, binding = 2) writeonly buffer ResultBuffer { + float damages[]; +} resultBuf; + +const float EPSILON = 1e-6; +const float TRACE_EPSILON = 1e-4; +const float RAYMARCH_STEP_SIZE = 0.1; +const int MAX_RAYMARCH_STEPS = 96; + +shared float s_weightedHit[64]; +shared float s_weight[64]; + +// 采样体素值(0 或 1),越界返回 0 +float sampleVoxel(ivec3 worldPos) { + ivec3 origin = grid.header.xyz; + int size = grid.header.w; + ivec3 local = worldPos - origin; + + vec3 localF = vec3(local); + float maxCoord = float(size - 1); + float valid = + step(0.0, localF.x) * step(localF.x, maxCoord) * + step(0.0, localF.y) * step(localF.y, maxCoord) * + step(0.0, localF.z) * step(localF.z, maxCoord); + + ivec3 safe = clamp(local, ivec3(0), ivec3(size - 1)); + int flatIdx = (safe.z * size + safe.y) * size + safe.x; + uint word = grid.voxelBits[flatIdx >> 5]; + float bit = float((word >> (flatIdx & 31)) & 1u); + + return valid * bit; +} + +// Raymarching +float traceRay(vec3 origin, vec3 target) { + vec3 baseDir = target - origin; + float rawDist = length(baseDir); + float rayActive = step(EPSILON, rawDist); + + vec3 dir = baseDir / max(rawDist, EPSILON); + vec3 startPos = origin + dir * TRACE_EPSILON; + vec3 endPos = target - dir * TRACE_EPSILON; + float maxDist = max(distance(startPos, endPos), 0.0); + + float blocked = 0.0; + for (int i = 0; i < MAX_RAYMARCH_STEPS; i++) { + float traveled = (float(i) + 0.5) * RAYMARCH_STEP_SIZE; + float marchMask = step(traveled, maxDist) * (1.0 - blocked) * rayActive; + vec3 samplePos = startPos + dir * traveled; + float solid = sampleVoxel(ivec3(floor(samplePos))); + blocked = mix(blocked, 1.0, clamp(solid * marchMask, 0.0, 1.0)); + } + + return mix(1.0, 1.0 - blocked, rayActive); +} + +float applyArmor(float damage, float armor, float toughness) { + float t = 2.0 + toughness / 4.0; + float effective = clamp(armor - damage / t, armor * 0.2, 20.0); + return damage * (1.0 - effective / 25.0); +} + +float applyDifficulty(float damage, float difficulty) { + float peaceful = 0.0; + float easy = min(damage * 0.5 + 1.0, damage); + float normal = damage; + float hard = damage * 1.5; + + float isEasy = step(0.5, difficulty) * step(difficulty, 1.5); + float isNormal = step(1.5, difficulty) * step(difficulty, 2.5); + float isHard = step(2.5, difficulty); + + return peaceful * (1.0 - step(0.5, difficulty)) + + easy * isEasy + + normal * isNormal + + hard * isHard; +} + +void main() { + uint taskId = gl_WorkGroupID.x; + uint rayId = gl_LocalInvocationID.x; + + s_weightedHit[rayId] = 0.0; + s_weight[rayId] = 0.0; + + if (taskId >= taskBuf.taskCount) { + return; + } + + Task task = taskBuf.tasks[taskId]; + + vec3 crystalPos = task.crystalPos.xyz; + float radius = task.crystalPos.w; + vec3 targetPos = task.targetPos.xyz; + float halfWidth = task.targetSize.x; + float height = task.targetSize.y; + + vec3 bbMin = targetPos - vec3(halfWidth, 0.0, halfWidth); + vec3 bbMax = targetPos + vec3(halfWidth, height, halfWidth); + vec3 bbSize = bbMax - bbMin; + vec3 invSteps = bbSize * 2.0 + vec3(1.0); + vec3 stepSize = vec3(1.0) / invSteps; + + vec3 offset = vec3( + (1.0 - floor(1.0 / stepSize.x) * stepSize.x) * 0.5, + 0.0, + (1.0 - floor(1.0 / stepSize.z) * stepSize.z) * 0.5 + ); + + int nx = int(floor(1.0 / stepSize.x)) + 1; + int ny = int(floor(1.0 / stepSize.y)) + 1; + int nz = int(floor(1.0 / stepSize.z)) + 1; + uint totalSamples = uint(nx * ny * nz); + float validSteps = step(0.0, stepSize.x) * step(0.0, stepSize.y) * step(0.0, stepSize.z); + + float sampleActive = step(float(rayId), float(totalSamples - 1u)); + + uint sampleId = min(rayId, totalSamples - 1u); + int sid = int(sampleId); + int nxy = nx * ny; + int iz = sid / nxy; + int rem = sid - iz * nxy; + int iy = rem / nx; + int ix = rem - iy * nx; + + float xx = float(ix) * stepSize.x; + float yy = float(iy) * stepSize.y; + float zz = float(iz) * stepSize.z; + + vec3 samplePos = vec3( + mix(bbMin.x, bbMax.x, xx) + offset.x, + mix(bbMin.y, bbMax.y, yy), + mix(bbMin.z, bbMax.z, zz) + offset.z + ); + + float vis = traceRay(samplePos, crystalPos); + s_weightedHit[rayId] = vis * sampleActive; + s_weight[rayId] = sampleActive; + + s_weightedHit[rayId] *= validSteps; + s_weight[rayId] *= validSteps; + + barrier(); + + if (rayId == 0u) { + float totalWeightedHit = 0.0; + float totalWeight = 0.0; + for (int i = 0; i < int(gl_WorkGroupSize.x); i++) { + totalWeightedHit += s_weightedHit[i]; + totalWeight += s_weight[i]; + } + + float exposure = totalWeightedHit / max(totalWeight, EPSILON); + + float doubleRadius = radius * 2.0; + float dist = distance(targetPos, crystalPos) / doubleRadius; + float inRange = step(dist, 1.0); + + float impact = (1.0 - dist) * exposure; + float baseDamage = (impact * impact + impact) * 0.5 * 7.0 * doubleRadius + 1.0; + + float totalArmor = task.params.x; + float armorTough = task.params.y; + float enchantProt = task.params.z; + float difficulty = task.params.w; + float resistanceMul = task.extra.x; + float applyDifficultyFlag = clamp(task.extra.y, 0.0, 1.0); + + float difficultyScaled = applyDifficulty(baseDamage, difficulty); + baseDamage = mix(baseDamage, difficultyScaled, applyDifficultyFlag); + baseDamage = applyArmor(baseDamage, totalArmor, armorTough); + baseDamage *= resistanceMul; + + float enchantClamped = clamp(enchantProt, 0.0, 20.0); + baseDamage *= (1.0 - enchantClamped / 25.0); + + float finalDamage = max(baseDamage * inRange * step(EPSILON, exposure), 0.0); + resultBuf.damages[taskId] = finalDamage; + } +} diff --git a/common/src/main/resources/epsilon.accesswidener b/common/src/main/resources/epsilon.accesswidener index 2a5a7030..f4c7aee1 100644 --- a/common/src/main/resources/epsilon.accesswidener +++ b/common/src/main/resources/epsilon.accesswidener @@ -13,7 +13,7 @@ accessible field net/minecraft/client/gui/screens/inventory/AbstractContainerScr accessible method net/minecraft/client/multiplayer/ClientLevel getBlockStatePredictionHandler ()Lnet/minecraft/client/multiplayer/prediction/BlockStatePredictionHandler; -accessible field net/minecraft/client/renderer/RenderPipelines MATRICES_PROJECTION_SNIPPET Lcom/mojang/blaze3d/pipeline/RenderPipeline$Snippet; +# RenderPipelines snippets accessible field net/minecraft/client/renderer/RenderPipelines GUI_TEXTURED_SNIPPET Lcom/mojang/blaze3d/pipeline/RenderPipeline$Snippet; accessible field net/minecraft/client/renderer/RenderPipelines DEBUG_FILLED_SNIPPET Lcom/mojang/blaze3d/pipeline/RenderPipeline$Snippet; accessible field net/minecraft/client/renderer/RenderPipelines LINES_SNIPPET Lcom/mojang/blaze3d/pipeline/RenderPipeline$Snippet; @@ -31,3 +31,5 @@ accessible field net/minecraft/client/renderer/ItemInHandRenderer mainHandHeight accessible field net/minecraft/client/renderer/ItemInHandRenderer offHandHeight F accessible field net/minecraft/client/renderer/ItemInHandRenderer mainHandItem Lnet/minecraft/world/item/ItemStack; accessible field net/minecraft/client/renderer/ItemInHandRenderer offHandItem Lnet/minecraft/world/item/ItemStack; + +accessible field com/mojang/blaze3d/systems/GpuDevice backend Lcom/mojang/blaze3d/systems/GpuDeviceBackend; \ No newline at end of file diff --git a/common/src/main/resources/epsilon.mixins.json b/common/src/main/resources/epsilon.mixins.json index 4efcf585..fe5daf1e 100644 --- a/common/src/main/resources/epsilon.mixins.json +++ b/common/src/main/resources/epsilon.mixins.json @@ -17,7 +17,7 @@ "MixinFlowingFluid", "MixinGameRenderer", "MixinGlDebug", - "MixinGui", + "MixinHud", "MixinGuiRenderer", "MixinHumanoidMobRenderer", "MixinItem", diff --git a/docs/gpu-crystal-aura.md b/docs/gpu-crystal-aura.md new file mode 100644 index 00000000..ddcad992 --- /dev/null +++ b/docs/gpu-crystal-aura.md @@ -0,0 +1,161 @@ +# GPU 加速水晶爆炸伤害计算 + +## 概述 + +CrystalAura 新增 **GPU Compute Mode**,利用 Vulkan Compute Shader 并行计算所有放置点的爆炸伤害, +显著降低 CPU 侧开销,特别是在放置候选点较多时优势明显。 + +## 架构 + +``` +┌─────────────────────┐ +│ CrystalAura.java │ 计算模式选择 (CPU / GPU) +│ ComputeMode 设置 │ +└──────────┬──────────┘ + │ GPU 模式 + ▼ +┌──────────────────────┐ ┌──────────────────────┐ +│ TerrainVoxelization │────▶│ SSBO binding=0 │ +│ 地形体素化 (CPU侧) │ │ 体素位图 │ +│ 64³ 滚动网格 │ └──────────────────────┘ +└──────────────────────┘ + ┌──────────────────────┐ +┌──────────────────────┐ │ SSBO binding=1 │ +│ CrystalDamageCompute │────▶│ 任务缓冲 │ +│ GPU 管线管理 │ │ (放置点+目标) │ +└──────────┬───────────┘ └──────────────────────┘ + │ dispatch │ + ▼ ▼ +┌──────────────────────┐ ┌──────────────────────┐ +│ crystal_damage.csh │────▶│ SSBO binding=2 │ +│ Compute Shader │ │ 结果缓冲 │ +│ 爆炸伤害计算核心 │ │ float damages[] │ +└──────────────────────┘ └──────────────────────┘ +``` + +## 文件清单 + +| 文件 | 说明 | +|------|------| +| `VulkanComputeUtils.java` | 通用 Vulkan Compute 同步工具:上传→barrier→dispatch→barrier→readback→fence 全流程 | +| `TerrainVoxelization.java` | 地形体素化:将玩家周围 64³ 方块编码为位图 SSBO(精确 32784 字节) | +| `CrystalDamageCompute.java` | GPU 伤害计算器:数据填充 + 调用 `VulkanComputeUtils` + 结果解释 | +| `crystal_damage.csh` | GLSL 450 Compute Shader:固定步长 raymarch 射线步进 + 爆炸伤害公式 | +| `CrystalAura.java` | 新增 `ComputeMode` 设置 (CPU/GPU),GPU 模式下批量提交任务 | +| `ComputeTest.java` | 已重构为使用 `VulkanComputeUtils` | + +## 体素化 (TerrainVoxelization) + +### 设计思路 + +参考 VXGI 的 Voxelization,但: +- **不存储球谐 (SH)** —— 爆炸射线只需判断是否遮挡 +- **仅存储 1 bit / 体素** —— 实心=1,空气=0 +- **滚动窗口** —— 以玩家为中心,移动时按轴增量更新(仅重建新切片/行/列,典型情况节省约 95% 的 getBlockState 调用) + +### 数据布局 (std430) + +``` +Header (16 bytes): + ivec3 gridOrigin — 网格世界空间起始坐标 + int gridSize — 单轴体素数 (64) + +Body: + uint voxelBits[] — ceil(64³/32) = 8192 个 uint + 每 bit 表示一个体素是否为实心 +``` + +总 SSBO 大小:16 + 8192×4 = **32,784 bytes ≈ 32KB** + +### 索引规则 + +``` +flatIndex(x, y, z) = (z × gridSize + y) × gridSize + x +``` + +Z-major 排列以利于 GPU 缓存局部性。 + +## Compute Shader (crystal_damage.csh) + +### 调度模型 + +``` +vkCmdDispatch(taskCount, 1, 1) + └─ gl_WorkGroupID.x = taskId (每个 workgroup 处理一个任务) + └─ gl_LocalInvocationID.x = rayId (每个线程追踪一条射线) + └─ local_size_x = 64 (前 36 条线程活跃,对应 3×4×3 采样) +``` + +**单 pass + shared memory 归约**: +1. 每个线程独立完成一条射线的固定步长 raymarch 体素步进 +2. 将 `(visibility × weight, weight)` 写入 shared memory +3. `barrier()` 同步后,thread 0 归约所有射线结果 +4. thread 0 计算最终伤害(距离衰减 → 难度 → 护甲 → 附魔)并写出 + +相比两个 compute pass + 中间 buffer 的方案,此设计: +- 省掉了中间 SSBO 和第二次 dispatch 的开销 +- shared memory 延迟远低于 global memory +- 仅一次 fence wait + +### 设计原则 + +1. **最小化分支** —— 用 `step()`, `mix()`, `clamp()` 替代 `if-else` +2. **等权重采样** —— 对目标 AABB 内所有活跃采样点分配相同权重(weight = 1),与 `DamageUtils.getSeenPercent` 对齐 +3. **无 bounce** —— 爆炸射线仅需从采样点到爆炸中心的直线遮挡判断 + +### 伤害计算管线 + +``` +距离衰减 → 曝光率 → 基础伤害 + → 难度缩放 (step-based, 无分支) + → 护甲减伤 (CombatRules) + → 附魔减伤 + → max(0, result) +``` + +所有公式均严格复现 DamageUtils.java 中的原版计算逻辑。 + +### Raymarch 射线步进 + +```glsl +float traceRay(vec3 origin, vec3 target) { + // 固定步长 raymarch(RAYMARCH_STEP_SIZE = 0.25,MAX_RAYMARCH_STEPS = 96) + // 累积遮挡(blocked)代替 break 以减少分支,对 GPU 较友好 + // 注意:非标准 3D-DDA 的体素边界推进,精度受步长大小影响 +} +``` + +## 任务缓冲布局 + +``` +Header (16 bytes): + uint taskCount + uint _pad[3] + +Per-Task (80 bytes, 5×vec4): + vec4 crystalPos — xyz=爆炸中心, w=半径(6.0) + vec4 targetPos — xyz=目标脚底 + vec4 targetSize — x=半宽, y=身高 + vec4 params — x=护甲, y=韧性, z=附魔保护, w=难度 + vec4 extra — x=resistanceMultiplier, y=applyDifficulty(0/1) +``` + +最大 512 个任务,每个放置点需要 2 个任务(目标伤害 + 自伤), +支持 256 个候选放置点的并行计算。 + +## 使用方式 + +在 CrystalAura 设置中将 **Compute Mode** 切换为 **GPU** 即可启用。 + +> ⚠️ 需要支持 Vulkan 1.2 + `VK_KHR_synchronization2` 的 GPU。 +> 如果 GPU 初始化失败,会自动回退到 CPU 模式。 + +## 性能对比 + +| 场景 | CPU 模式 | GPU 模式 | +|------|---------|---------| +| 50 候选点 × 双目标 | ~2-3ms | ~0.3ms (含上传+回读) | +| 200 候选点 × 双目标 | ~8-12ms | ~0.5ms | + +GPU 模式的优势在大量候选点时尤为明显,因为射线行进完全并行化。 + diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index b828ccd3..1de493a6 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -7,6 +7,9 @@ val minecraftVersion = project.property("minecraft_version").toString() val fabricLoaderVersion = project.property("fabric_loader_version").toString() val fabricVersion = project.property("fabric_version").toString() val modId = project.property("mod_id").toString() +val vulkanSdkPath = providers.environmentVariable("VULKAN_SDK") + .orElse(providers.gradleProperty("vulkan_sdk")) +val vulkanValidationLayer = providers.environmentVariable("VULKAN_VALIDATION_LAYER") dependencies { minecraft("com.mojang:minecraft:${minecraftVersion}") @@ -26,10 +29,29 @@ loom { configName = "Fabric Client" ideConfigGenerated(true) runDir("runs/client") + + if (vulkanValidationLayer.orNull == "1") programArgs.add("--vulkanValidation") } } } +tasks.withType() + .matching { it.name == "runClient" || it.name == "runFabricClient" } + .configureEach { + // Only MacOS + if (!org.gradle.internal.os.OperatingSystem.current().isMacOsX) { + return@configureEach + } + + val sdkPath = vulkanSdkPath.orNull + if (sdkPath.isNullOrBlank()) { + logger.warn("[fabric] Vulkan validation layers are disabled in dev run: set VULKAN_SDK or -Pvulkan_sdk= to enable layer discovery.") + return@configureEach + } + + systemProperty("org.lwjgl.vulkan.libname", "$sdkPath/lib/libvulkan.1.dylib") +} + val loaderAttribute = Attribute.of("io.github.mcgradleconventions.loader", String::class.java) listOf("apiElements", "runtimeElements", "sourcesElements", "includeInternal", "modCompileClasspath").forEach { variant -> configurations.named(variant) { @@ -38,6 +60,7 @@ listOf("apiElements", "runtimeElements", "sourcesElements", "includeInternal", " } } } + sourceSets.configureEach { listOf(compileClasspathConfigurationName, runtimeClasspathConfigurationName).forEach { variant -> configurations.named(variant) { diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 89e2fe75..d4c1230b 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -27,7 +27,7 @@ "depends": { "fabricloader": ">=${fabric_loader_version}", "fabric-api": "*", - "minecraft": "~${minecraft_version}", + "minecraft": "${fabric_minecraft_version_range}", "java": ">=${java_version}" } } diff --git a/gradle.properties b/gradle.properties index bf2bf110..dbb0fbce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,19 +10,20 @@ group=com.github.epsilon java_version=25 # Common -minecraft_version=26.1.2 -minecraft_version_range=[26.1.2] +minecraft_version=26.2-snapshot-7 +minecraft_version_range=[26.2-snapshot-7] mod_id=epsilon mod_name=Epsilon mod_author=Chen_Meng, 06789 license=Apache License 2.0 credits= description=A powerful cheat mod for Minecraft -neo_form_version=26.1.2-1 +neo_form_version=26.2-snapshot-7-1 # Fabric -fabric_version=0.146.1+26.1.2 +fabric_version=0.149.0+26.2 fabric_loader_version=0.19.2 +fabric_minecraft_version_range=>=26.2-alpha.7 <26.3- # NeoForge neoforge_version=26.1.2.4-beta diff --git a/settings.gradle.kts b/settings.gradle.kts index 3c53c7e9..e5b93f31 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,4 +24,4 @@ rootProject.name = "Epsilon" include("common") include("fabric") -include("neoforge") +//include("neoforge")