更新渲染方式

This commit is contained in:
Color_yr committed 2026-07-11 18:15:46 +08:00
1 parent a7777d627e
commit f607dcde63
52 files changed
+2094 -5521

No files matched your search

@@ -31,10 +31,8 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
/** 当前帧的 PoseStack,由 GuiShow mixin 注入 */
public static PoseStack context;
/** 由 HUD 渲染 mixin 调用,缓存当前帧的 PoseStack */
public static void update(PoseStack matrices) {
context = matrices;
}
@@ -113,9 +113,10 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size() || line >= entries.size()) {
if (line >= texts.size()) {
return;
}
if (line >= entries.size()) return;
PoseStack matrices = AllMusicClient.context;
if (matrices == null) return;
Font font = Minecraft.getInstance().font;
@@ -153,11 +154,12 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
int texOffset = (int) (maxOffset * state);
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
enableScissor(drawX, drawY, maxWidth, entry.height);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, matrices, entry, drawX - texOffset, drawY, finalColor);
drawString(font, matrices, entry, drawX - (int) texOffset, drawY, finalColor);
disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
@@ -48,8 +48,7 @@ public class PicRender extends PictureFrameBuffer {
public void draw(boolean rotate, int size, float x, float y, int ang, HudPosType dir, float alpha) {
Point2f point = AllMusicHud.getPos(size, size, x, y, dir);
RenderSystem.setShaderTexture(0, rotate ? rotateTexture.getId() : sourceTexture.getId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.bindTexture(rotate ? rotateTexture.getId() : sourceTexture.getId());
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
@@ -18,6 +18,7 @@ import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.resources.metadata.texture.TextureMetadataSection;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.io.IOException;
@@ -42,8 +43,7 @@ public class TexRender extends TextureRender {
@Override
public void drawPic(float x, float y, float alpha) {
RenderSystem.setShaderTexture(0, sourceTexture.getId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.bindTexture(sourceTexture.getId());
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
@@ -64,7 +64,7 @@ public class TexRender extends TextureRender {
float v1 = 1;
BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder();
bufferBuilder.begin(7, DefaultVertexFormat.POSITION_COLOR_TEX);
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormat.POSITION_COLOR_TEX);
bufferBuilder.vertex(matrix, x0, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v0).endVertex();
@@ -78,8 +78,7 @@ public class TexRender extends TextureRender {
@Override
public void drawPic(float x, float y, float width, float alpha) {
RenderSystem.setShaderTexture(0, sourceTexture.getId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.bindTexture(sourceTexture.getId());
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
@@ -117,8 +116,7 @@ public class TexRender extends TextureRender {
public void drawPic(float x, float y, float width, float height, HudPosType dir, float alpha) {
Point2f point = AllMusicHud.getPos(width, height, x, y, dir);
RenderSystem.setShaderTexture(0, sourceTexture.getId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.bindTexture(sourceTexture.getId());
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
@@ -23,7 +23,7 @@
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"fabric": "*",
"minecraft": ">=1.16"
},
@@ -26,10 +26,10 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
public static final ResourceLocation ID = new ResourceLocation("allmusic", "channel");
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics graphics;
public static GuiGraphics context;
public static void update(GuiGraphics graphics) {
AllMusicClient.graphics = graphics;
public static void update(GuiGraphics context) {
AllMusicClient.context = context;
AllMusicCore.hudUpdate();
}
@@ -72,7 +72,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -4,301 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static final RenderBuffers renderBuffers = new RenderBuffers();
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final RenderTarget target;
private Matrix4f matrix4f;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
public CoreRenderTarget() {
target = new TextureTarget(800, 200, false, false);
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = (int) (width * window.getGuiScale());
nowHeight = (int) (height * window.getGuiScale());
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight, false);
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
} else if (matrix4f == null) {
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.clear(false);
target.bindWrite(true);
RenderSystem.backupProjectionMatrix();
if (matrix4f != null) {
RenderSystem.setProjectionMatrix(matrix4f, VertexSorting.ORTHOGRAPHIC_Z);
}
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
RenderSystem.restoreProjectionMatrix();
RenderTarget main = Minecraft.getInstance().getMainRenderTarget();
main.bindWrite(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
if (font.width(component) == 0) {
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int width = font.drawInBatch(component, 0, y, color, shadow, new Matrix4f(), renderBuffers.bufferSource(), Font.DisplayMode.NORMAL, 0, 15728880);
RenderSystem.disableDepthTest();
renderBuffers.bufferSource().endBatch();
RenderSystem.enableDepthTest();
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
RenderSystem.setShaderTexture(0, target.getColorTextureId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
float w = (width / 2);
float h = (height / 2);
Matrix4f matrix = new Matrix4f().translation(x + w, y + h, 0);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder();
bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR_TEX);
bufferBuilder.vertex(matrix, x0, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v0).endVertex();
bufferBuilder.vertex(matrix, x0, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v0).endVertex();
BufferUploader.drawWithShader(bufferBuilder.end());
RenderSystem.disableBlend();
RenderSystem.depthMask(true);
target.unbindRead();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -306,19 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
target.unbindRead();
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
}
@@ -23,10 +23,9 @@
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"fabric-api": "*",
"minecraft": ">=1.20.1",
"java": ">=17"
"minecraft": ">=1.20.1"
},
"suggests": {
}
@@ -27,7 +27,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("allmusic", "channel");
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
private static GuiGraphics context;
public static GuiGraphics context;
public static void update(GuiGraphics draw) {
context = draw;
@@ -50,12 +50,6 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
return Minecraft.getInstance().font.lineHeight;
}
public void drawText(String item, int x, int y, int color, boolean shadow) {
var hud = Minecraft.getInstance().font;
Component component = MiniMessage.parse(item);
context.drawString(hud, component, x, y, color, shadow);
}
public void sendMessage(String data) {
data = "[AllMusic Client]" + data;
LOGGER.warn(data);
@@ -79,7 +73,7 @@ public class AllMusicClient implements ClientModInitializer, AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -4,213 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static final RenderBuffers renderBuffers = new RenderBuffers(Runtime.getRuntime().availableProcessors());
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final RenderTarget target;
private Matrix4f matrix4f;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
public CoreRenderTarget() {
target = new TextureTarget(800, 200, false, false);
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = (int) (width * window.getGuiScale());
nowHeight = (int) (height * window.getGuiScale());
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight, false);
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
} else if (matrix4f == null) {
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.clear(false);
target.bindWrite(true);
RenderSystem.backupProjectionMatrix();
if (matrix4f != null) {
RenderSystem.setProjectionMatrix(matrix4f, VertexSorting.ORTHOGRAPHIC_Z);
}
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
RenderSystem.restoreProjectionMatrix();
RenderTarget main = Minecraft.getInstance().getMainRenderTarget();
main.bindWrite(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
if (font.width(component) == 0) {
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int width = font.drawInBatch(component, 0, y, color, shadow, new Matrix4f(), renderBuffers.bufferSource(), Font.DisplayMode.NORMAL, 0, 15728880);
RenderSystem.disableDepthTest();
renderBuffers.bufferSource().endBatch();
RenderSystem.enableDepthTest();
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
RenderSystem.setShaderTexture(0, target.getColorTextureId());
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
float w = (width / 2);
float h = (height / 2);
Matrix4f matrix = new Matrix4f().translation(x + w, y + h, 0);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
BufferBuilder bufferBuilder = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
BufferUploader.drawWithShader(bufferBuilder.buildOrThrow());
RenderSystem.disableBlend();
RenderSystem.depthMask(true);
target.unbindRead();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -218,19 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
target.unbindRead();
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
}
@@ -26,9 +26,8 @@
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -4,218 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
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 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.GuiGraphics;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.gui.render.state.GlyphRenderState;
import net.minecraft.client.gui.render.state.GuiElementRenderState;
import net.minecraft.client.gui.render.state.GuiRenderState;
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.network.chat.Component;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.drawString(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -223,280 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
private final CachedOrthoProjectionMatrixBuffer itemsProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("items", -1000.0F, 1000.0F, true);
private @Nullable ScreenRectangle previousScissorArea = null;
private @Nullable RenderPipeline previousPipeline = null;
private @Nullable TextureSetup previousTextureSetup = null;
private @Nullable BufferBuilder bufferBuilder = null;
public GuiRenderer(GuiRenderState state) {
this.renderState = state;
}
public void render(GpuBufferSlice bufferSlice, CoreRenderTarget target) {
this.renderState.forEachText(state -> {
var matrix3x2fc = state.pose;
var screenrectangle = state.scissor;
state.ensurePrepared().visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(TextRenderable.Styled styled) {
this.accept(styled);
}
@Override
public void acceptEffect(TextRenderable textRenderable) {
this.accept(textRenderable);
}
private void accept(TextRenderable textRenderable) {
renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2fc, textRenderable, screenrectangle));
}
});
});
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.bufferBuilder = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
var map = new Object2IntOpenHashMap<VertexFormat>();
for (var draw : this.meshesToDraw) {
var state = draw.mesh.drawState();
var vertexformat = state.format();
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize());
}
for (var entry : map.object2IntEntrySet()) {
var vertexformat = entry.getKey();
int i = entry.getIntValue();
var mappableringbuffer = this.vertexBuffers.get(vertexformat);
if (mappableringbuffer == null || mappableringbuffer.size() < i) {
if (mappableringbuffer != null) {
mappableringbuffer.close();
}
this.vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "GUI vertex buffer for " + vertexformat, 34, i));
}
}
var commandencoder = RenderSystem.getDevice().createCommandEncoder();
map = new Object2IntOpenHashMap<>();
for (MeshToDraw draw : this.meshesToDraw) {
var meshdata = draw.mesh;
var state = meshdata.drawState();
var vertexformat = state.format();
var mappableringbuffer = this.vertexBuffers.get(vertexformat);
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
ByteBuffer bytebuffer = meshdata.vertexBuffer();
int i = bytebuffer.remaining();
int j = map.getInt(vertexformat);
try (GpuBuffer.MappedView view = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) {
MemoryUtil.memCopy(bytebuffer, view.data());
}
map.put(vertexformat, j + i);
this.draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(),
state.indexCount(), draw.pipeline, draw.textureSetup, draw.scissorArea));
draw.close();
}
this.draw(bufferSlice, target);
for (var mappableringbuffer : this.vertexBuffers.values()) {
mappableringbuffer.rotate();
}
this.draws.clear();
this.meshesToDraw.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
}
private void draw(GpuBufferSlice bufferSlice, CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var window = minecraft.getWindow();
RenderSystem.setProjectionMatrix(
this.guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(), (float) target.target.height / window.getGuiScale()),
ProjectionType.ORTHOGRAPHIC
);
var rendertarget = target.target;
int i = 0;
for (var item : this.draws) {
if (item.indexCount > i) {
i = item.indexCount;
}
}
var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var gpubuffer = buffer.getBuffer(i);
var indexType = buffer.type();
var gpubufferslice = 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());
if (!this.meshesToDraw.isEmpty()) {
this.executeDrawRange(() -> "GUI before blur", rendertarget, bufferSlice, gpubufferslice, gpubuffer,
indexType, Math.min(this.meshesToDraw.size(), this.draws.size()));
}
}
}
private void executeDrawRange(Supplier<String> name, RenderTarget target, GpuBufferSlice bufferSlice, GpuBufferSlice bufferSlice1,
GpuBuffer buffer, VertexFormat.IndexType indexType, int count) {
try (var renderpass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(),
target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderpass);
renderpass.setUniform("Fog", bufferSlice);
renderpass.setUniform("DynamicTransforms", bufferSlice1);
for (int i = 0; i < count; i++) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderpass, buffer, indexType);
}
}
}
private void addElementToMesh(GuiElementRenderState state) {
var pipeline = state.pipeline();
var texturesetup = state.textureSetup();
var screenrectangle = state.scissorArea();
if (pipeline != this.previousPipeline
|| this.scissorChanged(screenrectangle, this.previousScissorArea)
|| !texturesetup.equals(this.previousTextureSetup)) {
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
this.bufferBuilder = new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
this.previousPipeline = pipeline;
this.previousTextureSetup = texturesetup;
this.previousScissorArea = screenrectangle;
}
state.buildVertices(this.bufferBuilder);
}
private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle rectangle) {
var meshdata = builder.build();
if (meshdata != null) {
this.meshesToDraw.add(new MeshToDraw(meshdata, pipeline, textureSetup, rectangle));
}
}
private void executeDraw(Draw draw, RenderPass pass, GpuBuffer buffer, VertexFormat.IndexType index) {
var renderpipeline = draw.pipeline();
pass.setPipeline(renderpipeline);
pass.setVertexBuffer(0, draw.vertexBuffer);
var screenrectangle = draw.scissorArea();
if (screenrectangle != null) {
Window window = Minecraft.getInstance().getWindow();
int i = window.getHeight();
int j = window.getGuiScale();
double d0 = screenrectangle.left() * j;
double d1 = i - screenrectangle.bottom() * j;
double d2 = screenrectangle.width() * j;
double d3 = screenrectangle.height() * j;
pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3));
} else {
pass.disableScissor();
}
if (draw.textureSetup.texure0() != null) {
pass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0());
}
if (draw.textureSetup.texure1() != null) {
pass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1());
}
if (draw.textureSetup.texure2() != null) {
pass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2());
}
pass.setIndexBuffer(buffer, index);
pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
}
private boolean scissorChanged(@Nullable ScreenRectangle rectangle, @Nullable ScreenRectangle rectangle1) {
if (rectangle == rectangle1) {
return false;
} else {
return rectangle != null ? !rectangle.equals(rectangle1) : true;
}
}
@Override
public void close() {
this.byteBufferBuilder.close();
this.guiProjectionMatrixBuffer.close();
this.itemsProjectionMatrixBuffer.close();
for (MappableRingBuffer mappableringbuffer : this.vertexBuffers.values()) {
mappableringbuffer.close();
}
}
record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
}
record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) implements AutoCloseable {
@Override
public void close() {
this.mesh.close();
}
}
}
}
}
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.18.4",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.11",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -170,7 +170,9 @@ public class CoreRenderTarget extends TextFrameBuffer {
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -28,7 +28,6 @@
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.3",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -63,7 +63,6 @@ public class CoreRenderTarget extends TextFrameBuffer {
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
@@ -171,7 +170,9 @@ public class CoreRenderTarget extends TextFrameBuffer {
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -28,7 +28,6 @@
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.4",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -4,212 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
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 it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.gui.render.state.GlyphEffectRenderState;
import net.minecraft.client.gui.render.state.GlyphRenderState;
import net.minecraft.client.gui.render.state.GuiElementRenderState;
import net.minecraft.client.gui.render.state.GuiRenderState;
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.network.chat.Component;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState);
color = color | 0xFF000000;
graphics.drawString(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -217,274 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
private ScreenRectangle previousScissorArea;
private RenderPipeline previousPipeline;
private TextureSetup previousTextureSetup;
private BufferBuilder bufferBuilder;
public GuiRenderer(GuiRenderState state) {
renderState = state;
}
public void render(GpuBufferSlice gpu, CoreRenderTarget target) {
renderState.forEachText(state -> {
var matrix3x2f = state.pose;
var screenrectangle = state.scissor;
state.ensurePrepared().visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(BakedGlyph.GlyphInstance glyph) {
if (glyph.glyph().textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenrectangle));
}
}
@Override
public void acceptEffect(BakedGlyph glyph, BakedGlyph.Effect effect) {
if (glyph.textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphEffectRenderState(matrix3x2f, glyph, effect, screenrectangle));
}
}
});
});
renderState.sortElements(ELEMENT_SORT_COMPARATOR);
previousScissorArea = null;
previousPipeline = null;
previousTextureSetup = null;
bufferBuilder = null;
renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
recordDraws();
if (!draws.isEmpty()) {
Minecraft minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
RenderSystem.setProjectionMatrix(
guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(),
(float) target.target.height / window.getGuiScale()),
ProjectionType.ORTHOGRAPHIC
);
var rendertarget = target.target;
int i = 0;
for (Draw guirenderer$draw : draws) {
if (guirenderer$draw.indexCount > i) {
i = guirenderer$draw.indexCount;
}
}
var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var gpubuffer = buffer.getBuffer(i);
var index = buffer.type();
var gpubufferslice = 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(), 0.0F);
if (!meshesToDraw.isEmpty()) {
executeDrawRange(() -> "before blur", rendertarget, gpu, gpubufferslice, gpubuffer,
index, Math.min(meshesToDraw.size(), draws.size()), target);
}
}
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.rotate();
}
draws.clear();
meshesToDraw.clear();
renderState.reset();
}
private void executeDrawRange(Supplier<String> name, RenderTarget target, GpuBufferSlice buffer1, GpuBufferSlice buffer2,
GpuBuffer buffer3, VertexFormat.IndexType index, int count, CoreRenderTarget target1) {
try (RenderPass renderpass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(),
target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderpass);
renderpass.setUniform("Fog", buffer1);
renderpass.setUniform("DynamicTransforms", buffer2);
for (int i = 0; i < count; i++) {
var item = draws.get(i);
executeDraw(item, renderpass, target1, buffer3, index);
}
}
}
private void addElementToMesh(GuiElementRenderState state, int range) {
var pipeline = state.pipeline();
var setup = state.textureSetup();
var screen = state.scissorArea();
if (pipeline != previousPipeline || scissorChanged(screen, previousScissorArea) || !setup.equals(previousTextureSetup)) {
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
bufferBuilder = new BufferBuilder(byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
previousPipeline = pipeline;
previousTextureSetup = setup;
previousScissorArea = screen;
}
state.buildVertices(bufferBuilder, 0.0F + range * 0.01F);
}
private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup texture, ScreenRectangle rectangle) {
var meshdata = builder.buildOrThrow();
meshesToDraw.add(new MeshToDraw(meshdata, pipeline, texture, rectangle));
}
private void recordDraws() {
var map = new Object2IntOpenHashMap<VertexFormat>();
for (MeshToDraw item : meshesToDraw) {
var state = item.mesh.drawState();
var vertexformat = state.format();
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize());
}
for (var entry : map.object2IntEntrySet()) {
var vertexformat = entry.getKey();
int i = entry.getIntValue();
var buffer = vertexBuffers.get(vertexformat);
if (buffer == null || buffer.size() < i) {
if (buffer != null) {
buffer.close();
}
vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "vertex buffer for " + vertexformat, 34, i));
}
}
var commandencoder = RenderSystem.getDevice().createCommandEncoder();
map = new Object2IntOpenHashMap<>();
for (MeshToDraw item : meshesToDraw) {
var meshdata = item.mesh;
var state = meshdata.drawState();
var vertexformat = state.format();
var mappableringbuffer = vertexBuffers.get(vertexformat);
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
ByteBuffer bytebuffer = meshdata.vertexBuffer();
int i = bytebuffer.remaining();
int j = map.getInt(vertexformat);
try (var item1 = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) {
MemoryUtil.memCopy(bytebuffer, item1.data());
}
map.put(vertexformat, j + i);
draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(),
state.indexCount(), item.pipeline, item.textureSetup, item.scissorArea));
item.close();
}
}
private void executeDraw(Draw draw, RenderPass pass, CoreRenderTarget target, GpuBuffer buffer, VertexFormat.IndexType index) {
var renderpipeline = draw.pipeline();
pass.setPipeline(renderpipeline);
pass.setVertexBuffer(0, draw.vertexBuffer);
var area = draw.scissorArea();
if (area != null) {
Window window = Minecraft.getInstance().getWindow();
int i = target.target.height;
int j = window.getGuiScale();
double d0 = area.left() * j;
double d1 = i - area.bottom() * j;
double d2 = area.width() * j;
double d3 = area.height() * j;
pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3));
} else {
pass.disableScissor();
}
if (draw.textureSetup.texure0() != null) {
pass.bindSampler("Sampler0", draw.textureSetup.texure0());
}
if (draw.textureSetup.texure1() != null) {
pass.bindSampler("Sampler1", draw.textureSetup.texure1());
}
if (draw.textureSetup.texure2() != null) {
pass.bindSampler("Sampler2", draw.textureSetup.texure2());
}
pass.setIndexBuffer(buffer, index);
pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
}
private boolean scissorChanged(ScreenRectangle screen1, ScreenRectangle screen2) {
if (screen1 == screen2) {
return false;
} else {
return screen1 == null || !screen1.equals(screen2);
}
}
@Override
public void close() {
byteBufferBuilder.close();
guiProjectionMatrixBuffer.close();
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.close();
}
}
record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, ScreenRectangle scissorArea) {
}
record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
ScreenRectangle scissorArea) implements AutoCloseable {
@Override
public void close() {
mesh.close();
}
}
}
}
}
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.16.14",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.6",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -4,212 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
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 it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.gui.render.state.GlyphEffectRenderState;
import net.minecraft.client.gui.render.state.GlyphRenderState;
import net.minecraft.client.gui.render.state.GuiElementRenderState;
import net.minecraft.client.gui.render.state.GuiRenderState;
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.network.chat.Component;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState);
color = color | 0xFF000000;
graphics.drawString(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.submitGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -217,274 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
private ScreenRectangle previousScissorArea;
private RenderPipeline previousPipeline;
private TextureSetup previousTextureSetup;
private BufferBuilder bufferBuilder;
public GuiRenderer(GuiRenderState state) {
renderState = state;
}
public void render(GpuBufferSlice gpu, CoreRenderTarget target) {
renderState.forEachText(state -> {
var matrix3x2f = state.pose;
var screenrectangle = state.scissor;
state.ensurePrepared().visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(BakedGlyph.GlyphInstance glyph) {
if (glyph.glyph().textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenrectangle));
}
}
@Override
public void acceptEffect(BakedGlyph glyph, BakedGlyph.Effect effect) {
if (glyph.textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphEffectRenderState(matrix3x2f, glyph, effect, screenrectangle));
}
}
});
});
renderState.sortElements(ELEMENT_SORT_COMPARATOR);
previousScissorArea = null;
previousPipeline = null;
previousTextureSetup = null;
bufferBuilder = null;
renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
recordDraws();
if (!draws.isEmpty()) {
Minecraft minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
RenderSystem.setProjectionMatrix(
guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(),
(float) target.target.height / window.getGuiScale()),
ProjectionType.ORTHOGRAPHIC
);
var rendertarget = target.target;
int i = 0;
for (Draw guirenderer$draw : draws) {
if (guirenderer$draw.indexCount > i) {
i = guirenderer$draw.indexCount;
}
}
var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var gpubuffer = buffer.getBuffer(i);
var index = buffer.type();
var gpubufferslice = 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(), 0.0F);
if (!meshesToDraw.isEmpty()) {
executeDrawRange(() -> "before blur", rendertarget, gpu, gpubufferslice, gpubuffer,
index, Math.min(meshesToDraw.size(), draws.size()), target);
}
}
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.rotate();
}
draws.clear();
meshesToDraw.clear();
renderState.reset();
}
private void executeDrawRange(Supplier<String> name, RenderTarget target, GpuBufferSlice buffer1, GpuBufferSlice buffer2,
GpuBuffer buffer3, VertexFormat.IndexType index, int count, CoreRenderTarget target1) {
try (RenderPass renderpass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(),
target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderpass);
renderpass.setUniform("Fog", buffer1);
renderpass.setUniform("DynamicTransforms", buffer2);
for (int i = 0; i < count; i++) {
var item = draws.get(i);
executeDraw(item, renderpass, target1, buffer3, index);
}
}
}
private void addElementToMesh(GuiElementRenderState state, int range) {
var pipeline = state.pipeline();
var setup = state.textureSetup();
var screen = state.scissorArea();
if (pipeline != previousPipeline || scissorChanged(screen, previousScissorArea) || !setup.equals(previousTextureSetup)) {
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
bufferBuilder = new BufferBuilder(byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
previousPipeline = pipeline;
previousTextureSetup = setup;
previousScissorArea = screen;
}
state.buildVertices(bufferBuilder, 0.0F + range * 0.01F);
}
private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup texture, ScreenRectangle rectangle) {
var meshdata = builder.buildOrThrow();
meshesToDraw.add(new MeshToDraw(meshdata, pipeline, texture, rectangle));
}
private void recordDraws() {
var map = new Object2IntOpenHashMap<VertexFormat>();
for (MeshToDraw item : meshesToDraw) {
var state = item.mesh.drawState();
var vertexformat = state.format();
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize());
}
for (var entry : map.object2IntEntrySet()) {
var vertexformat = entry.getKey();
int i = entry.getIntValue();
var buffer = vertexBuffers.get(vertexformat);
if (buffer == null || buffer.size() < i) {
if (buffer != null) {
buffer.close();
}
vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "vertex buffer for " + vertexformat, 34, i));
}
}
var commandencoder = RenderSystem.getDevice().createCommandEncoder();
map = new Object2IntOpenHashMap<>();
for (MeshToDraw item : meshesToDraw) {
var meshdata = item.mesh;
var state = meshdata.drawState();
var vertexformat = state.format();
var mappableringbuffer = vertexBuffers.get(vertexformat);
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
ByteBuffer bytebuffer = meshdata.vertexBuffer();
int i = bytebuffer.remaining();
int j = map.getInt(vertexformat);
try (var item1 = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) {
MemoryUtil.memCopy(bytebuffer, item1.data());
}
map.put(vertexformat, j + i);
draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(),
state.indexCount(), item.pipeline, item.textureSetup, item.scissorArea));
item.close();
}
}
private void executeDraw(Draw draw, RenderPass pass, CoreRenderTarget target, GpuBuffer buffer, VertexFormat.IndexType index) {
var renderpipeline = draw.pipeline();
pass.setPipeline(renderpipeline);
pass.setVertexBuffer(0, draw.vertexBuffer);
var area = draw.scissorArea();
if (area != null) {
Window window = Minecraft.getInstance().getWindow();
int i = target.target.height;
int j = window.getGuiScale();
double d0 = area.left() * j;
double d1 = i - area.bottom() * j;
double d2 = area.width() * j;
double d3 = area.height() * j;
pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3));
} else {
pass.disableScissor();
}
if (draw.textureSetup.texure0() != null) {
pass.bindSampler("Sampler0", draw.textureSetup.texure0());
}
if (draw.textureSetup.texure1() != null) {
pass.bindSampler("Sampler1", draw.textureSetup.texure1());
}
if (draw.textureSetup.texure2() != null) {
pass.bindSampler("Sampler2", draw.textureSetup.texure2());
}
pass.setIndexBuffer(buffer, index);
pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
}
private boolean scissorChanged(ScreenRectangle screen1, ScreenRectangle screen2) {
if (screen1 == screen2) {
return false;
} else {
return screen1 == null || !screen1.equals(screen2);
}
}
@Override
public void close() {
byteBufferBuilder.close();
guiProjectionMatrixBuffer.close();
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.close();
}
}
record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, ScreenRectangle scissorArea) {
}
record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
ScreenRectangle scissorArea) implements AutoCloseable {
@Override
public void close() {
mesh.close();
}
}
}
}
}
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.16.14",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.6",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -3,221 +3,160 @@ package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.others.ModernUI;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
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 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.GuiGraphicsExtractor;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.client.renderer.state.WindowRenderState;
import net.minecraft.client.renderer.state.gui.GlyphRenderState;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.profiling.Profiler;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.text(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.text(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -225,285 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right));
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey)).thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final Projection guiProjection = new Projection();
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
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 GuiRenderer(GuiRenderState renderState) {
this.renderState = renderState;
}
public void render(GpuBufferSlice fogBuffer, CoreRenderTarget target) {
var profiler = Profiler.get();
if (this.renderState.panoramaRenderState != null) {
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
}
profiler.push("allmusic text prepare");
this.renderState.forEachText((text) -> {
var pose = text.pose;
var scissor = text.scissor;
Font.PreparedText preparedText = text.ensurePrepared();
if (AllMusicClient.modui) {
ModernUI.text(preparedText, renderState, pose, scissor);
} else {
preparedText.visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(TextRenderable.Styled glyph) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, glyph, scissor));
}
@Override
public void acceptEffect(TextRenderable glyph) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, glyph, scissor));
}
});
}
});
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.bufferBuilder = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
this.ensureVertexBufferSizes();
var commandEncoder = RenderSystem.getDevice().createCommandEncoder();
var offsets = new Object2IntOpenHashMap<VertexFormat>();
for (var meshToDraw : this.meshesToDraw) {
var mesh = meshToDraw.mesh;
var drawState = mesh.drawState();
var format = drawState.format();
var vertexBuffer = this.vertexBuffers.get(format);
if (!offsets.containsKey(format)) {
offsets.put(format, 0);
}
var meshVertexBuffer = mesh.vertexBuffer();
int meshBufferSize = meshVertexBuffer.remaining();
int offset = offsets.getInt(format);
try (var mappedView = commandEncoder.mapBuffer(vertexBuffer.currentBuffer().slice(offset, meshBufferSize), false, true)) {
MemoryUtil.memCopy(meshVertexBuffer, mappedView.data());
}
offsets.put(format, offset + meshBufferSize);
this.draws.add(new Draw(vertexBuffer.currentBuffer(), offset / format.getVertexSize(),
drawState.mode(), drawState.indexCount(), meshToDraw.pipeline, meshToDraw.textureSetup, meshToDraw.scissorArea));
meshToDraw.close();
}
profiler.popPush("allmusic text draw");
this.draw(fogBuffer, target);
profiler.popPush("allmusic text vertexBufferRotate");
for (MappableRingBuffer buffer : this.vertexBuffers.values()) {
buffer.rotate();
}
profiler.pop();
this.draws.clear();
this.meshesToDraw.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
}
private void draw(GpuBufferSlice fogBuffer, CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var windowState = minecraft.gameRenderer.getGameRenderState().windowRenderState;
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
(float) target.target.height / (float) windowState.guiScale, true);
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
var mainRenderTarget = target.target;
int maxIndexCount = 0;
for (var draw : this.draws) {
if (draw.indexCount > maxIndexCount) {
maxIndexCount = draw.indexCount;
}
}
var autoIndices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var indexBuffer = autoIndices.getBuffer(maxIndexCount);
var indexType = autoIndices.type();
var 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());
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, fogBuffer, dynamicTransforms,
indexBuffer, indexType, Math.min(this.meshesToDraw.size(), this.draws.size()));
}
}
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice fogBuffer,
GpuBufferSlice dynamicTransforms, GpuBuffer indexBuffer, VertexFormat.IndexType indexType, int endIndex) {
try (var renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(label, mainRenderTarget.getColorTextureView(),
OptionalInt.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("Fog", fogBuffer);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
for (int i = 0; i < endIndex; ++i) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderPass, indexBuffer, indexType);
}
}
}
private void addElementToMesh(GuiElementRenderState elementState) {
var pipeline = elementState.pipeline();
var textureSetup = elementState.textureSetup();
var 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 = new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
this.previousPipeline = pipeline;
this.previousTextureSetup = textureSetup;
this.previousScissorArea = scissorArea;
}
elementState.buildVertices(this.bufferBuilder);
}
private void recordMesh(BufferBuilder bufferBuilder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
var mesh = bufferBuilder.build();
if (mesh != null) {
this.meshesToDraw.add(new MeshToDraw(mesh, pipeline, textureSetup, scissorArea));
}
}
private void ensureVertexBufferSizes() {
var requiredSizes = new Object2IntOpenHashMap<VertexFormat>();
for (var meshToDraw : this.meshesToDraw) {
var drawState = meshToDraw.mesh.drawState();
var format = drawState.format();
if (!requiredSizes.containsKey(format)) {
requiredSizes.put(format, 0);
}
requiredSizes.put(format, requiredSizes.getInt(format) + drawState.vertexCount() * format.getVertexSize());
}
for (var entry : requiredSizes.object2IntEntrySet()) {
var vertexFormat = entry.getKey();
int requiredSize = entry.getIntValue();
var 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));
}
}
}
private void executeDraw(Draw draw, RenderPass renderPass, GpuBuffer indexBuffer, VertexFormat.IndexType indexType) {
var pipeline = draw.pipeline();
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, draw.vertexBuffer);
var 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 boolean scissorChanged(@Nullable ScreenRectangle newScissor, @Nullable ScreenRectangle oldScissor) {
if (newScissor == oldScissor) {
return false;
} else if (newScissor != null) {
return !newScissor.equals(oldScissor);
} else {
return true;
}
}
private void enableScissor(ScreenRectangle rectangle, RenderPass renderPass) {
WindowRenderState windowState = Minecraft.getInstance().gameRenderer.getGameRenderState().windowRenderState;
int windowHeight = windowState.height;
int guiScale = windowState.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));
}
public void close() {
this.byteBufferBuilder.close();
this.guiProjectionMatrixBuffer.close();
for (MappableRingBuffer buffer : this.vertexBuffers.values()) {
buffer.close();
}
this.cubeMap.close();
}
private record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
}
private record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) implements AutoCloseable {
public void close() {
this.mesh.close();
}
}
}
}
}
@@ -32,9 +32,8 @@
}
],
"depends": {
"fabricloader": ">=0.18.3",
"fabricloader": ">=0.19.3",
"minecraft": ">=26.1",
"java": ">=25",
"fabric-api": "*"
},
"suggests": {
@@ -4,207 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.GpuFormat;
import com.mojang.blaze3d.ProjectionType;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.FilterMode;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.state.gui.*;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.profiling.Profiler;
import org.joml.*;
import org.jspecify.annotations.Nullable;
import java.lang.Math;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false, GpuFormat.RGBA8_UNORM);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), new Vector4f(0));
entries.clear();
}
@Override
public void unUse() {
renderer.render(this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.text(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.guiRenderState.addGuiElement(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.scissorStack.peek()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.text(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -212,200 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right));
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR;
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final StagedVertexBuffer vertexBuffer = new StagedVertexBuffer(() -> "GUI Vertex Buffer", 786432);
private final Projection guiProjection = new Projection();
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
private final CubeMap cubeMap = new CubeMap(Identifier.withDefaultNamespace("textures/gui/title/background/panorama"));
private @Nullable ScreenRectangle previousScissorArea = null;
private @Nullable RenderPipeline previousPipeline = null;
private @Nullable TextureSetup previousTextureSetup = null;
private StagedVertexBuffer.@Nullable Draw previousDraw;
public GuiRenderer(final GuiRenderState renderState) {
this.renderState = renderState;
}
public void render(CoreRenderTarget target) {
var profiler = Profiler.get();
if (this.renderState.panoramaRenderState != null) {
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
}
profiler.push("prepare");
this.prepare();
profiler.popPush("upload");
this.vertexBuffer.upload();
profiler.popPush("draw");
this.draw(target);
profiler.popPush("endFrame");
this.vertexBuffer.endDraw();
this.vertexBuffer.endFrame();
this.draws.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
profiler.pop();
}
private void prepare() {
this.prepareText();
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.addElementsToMeshes();
}
private void addElementsToMeshes() {
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.previousDraw = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
}
private void draw(CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var windowState = minecraft.gameRenderer.gameRenderState().windowRenderState;
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
(float) target.target.height / (float) windowState.guiScale, true);
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
var mainRenderTarget = target.target;
var dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform((new Matrix4f()).setTranslation(0.0F, 0.0F, -11000.0F));
if (!this.draws.isEmpty()) {
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, dynamicTransforms, this.draws.size());
}
}
}
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice dynamicTransforms, int endIndex) {
try (var renderPass = RenderSystem.getDevice().createCommandEncoder()
.createRenderPass(label, mainRenderTarget.getColorTextureView(),
Optional.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
for (int i = 0; i < endIndex; ++i) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderPass);
}
}
}
private void addElementToMesh(final GuiElementRenderState elementState) {
var pipeline = elementState.pipeline();
var textureSetup = elementState.textureSetup();
var scissorArea = elementState.scissorArea();
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.vertexBuffer.getVertexBuilder(Objects.requireNonNull(this.previousDraw)));
}
private void prepareText() {
this.renderState.forEachText((text) -> {
var pose = text.pose;
var scissor = text.scissor;
text.ensurePrepared().visit(new Font.GlyphVisitor() {
public void acceptRenderable(final TextRenderable renderable) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, renderable, scissor));
}
});
});
}
private void executeDraw(final Draw draw, final RenderPass renderPass) {
var executeInfo = this.vertexBuffer.getExecuteInfo(draw.draw);
if (executeInfo != null) {
var pipeline = draw.pipeline();
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, executeInfo.vertexBuffer().slice());
var 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(executeInfo.indexBuffer(), executeInfo.indexType());
renderPass.drawIndexed(executeInfo.indexCount(), 1, executeInfo.firstIndex(), executeInfo.baseVertex(), 0);
}
}
private boolean scissorChanged(final @Nullable ScreenRectangle newScissor, final @Nullable ScreenRectangle oldScissor) {
if (newScissor == oldScissor) {
return false;
} else if (newScissor != null) {
return !newScissor.equals(oldScissor);
} else {
return true;
}
}
private void enableScissor(final ScreenRectangle rectangle, final RenderPass renderPass) {
var window = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState;
int guiScale = window.guiScale;
double left = (rectangle.left() * guiScale);
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 close() {
this.vertexBuffer.close();
this.guiProjectionMatrixBuffer.close();
this.cubeMap.close();
}
static {
ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
}
@Environment(EnvType.CLIENT)
private record Draw(StagedVertexBuffer.Draw draw, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) {
}
}
}
}
@@ -34,7 +34,6 @@
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=26.2",
"java": ">=25",
"fabric-api": "*"
},
"suggests": {
@@ -130,7 +130,7 @@ public class AllMusicClient implements AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -6,288 +6,162 @@ import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.shader.Framebuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final Framebuffer target;
private static class TextEntry {
final String text;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
public CoreRenderTarget() {
target = new Framebuffer(800, 200, false);
target.setFramebufferColor(0.0f, 0.0f, 0.0f, 0.0f);
TextEntry(String text, int y, int color, boolean shadow, int width, int height) {
this.text = text;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
nowWidth = (width * scaledresolution.getScaledWidth());
nowHeight = (height * scaledresolution.getScaledHeight());
if (nowWidth > target.framebufferWidth || nowHeight > target.framebufferHeight) {
target.createFramebuffer(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.framebufferClear();
target.bindFramebuffer(true);
// GlStateManager.ortho(0.0F, target.framebufferWidth, target.framebufferHeight, 0.0F, 1000.0F, 3000.0D);
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
Minecraft minecraft = Minecraft.getMinecraft();
// GlStateManager.ortho(0.0D, minecraft.displayWidth, minecraft.displayHeight, 0.0D, 1000.0D, 3000.0D);
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Minecraft minecraft = Minecraft.getMinecraft();
ScaledResolution scaledresolution = new ScaledResolution(Minecraft.getMinecraft());
FontRenderer font = minecraft.fontRenderer;
int width;
if (shadow) {
width = font.drawStringWithShadow(text, 0, 0, color);
} else {
width = font.drawString(text, 0, 0, color);
}
TextItem item = new TextItem(width, font.FONT_HEIGHT + (shadow ? 1 : 0), y, (float) scaledresolution.getScaledWidth());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
GlStateManager.bindTexture(target.framebufferTexture);
GlStateManager.depthMask(false);
GlStateManager.enableBlend();
float w = (width / 2);
float h = (height / 2);
GL11.glPushMatrix();
GL11.glTranslatef(x + w, y + h, 0.0f);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.framebufferWidth;
float v0 = 1 - (texY * scale / target.framebufferHeight);
float u1 = (texX + width) * scale / target.framebufferWidth;
float v1 = 1 - ((texY + height) * scale / target.framebufferHeight);
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder bufferbuilder = tessellator.getBuffer();
bufferbuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);
bufferbuilder.pos(x0, y1, z).tex(u0, v1).color(1.0f, 1.0f, 1.0f, alpha).endVertex();
bufferbuilder.pos(x1, y1, z).tex(u1, v1).color(1.0f, 1.0f, 1.0f, alpha).endVertex();
bufferbuilder.pos(x1, y0, z).tex(u1, v0).color(1.0f, 1.0f, 1.0f, alpha).endVertex();
bufferbuilder.pos(x0, y0, z).tex(u0, v0).color(1.0f, 1.0f, 1.0f, alpha).endVertex();
tessellator.draw();
GlStateManager.disableBlend();
GlStateManager.depthMask(true);
GL11.glPopMatrix();
target.unbindFramebufferTexture();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
color = (color & 0x00FFFFFF) | 0xFF000000;
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
int width = font.getStringWidth(text);
if (width == 0) {
return;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
int height = font.FONT_HEIGHT + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(text, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
GlStateManager.pushMatrix();
GlStateManager.translate(drawX, drawY, 0);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
enableScissor(drawX, drawY, maxWidth, entry.height);
drawString(font, entry, -scrollOffset, 0, finalColor);
if (scrollOffset > 0) {
drawString(font, entry, -scrollOffset + entry.width, 0, finalColor);
}
disableScissor();
} else {
drawString(font, entry, 0, 0, finalColor);
}
GlStateManager.popMatrix();
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
TextEntry entry = entries.get(line);
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
GlStateManager.pushMatrix();
GlStateManager.translate(x, y + entry.y, 0);
drawString(font, entry, 0, 0, applyAlpha(entry.color, alpha));
GlStateManager.popMatrix();
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
GlStateManager.pushMatrix();
GlStateManager.translate(drawX, drawY, 0);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
enableScissor(drawX, drawY, maxWidth, entry.height);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, entry, -(int) texOffset, 0, finalColor);
disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, entry, 0, 0, finalColor);
disableScissor();
}
GlStateManager.popMatrix();
}
}
@Override
@@ -295,17 +169,41 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
private static void drawString(FontRenderer font, TextEntry entry, int x, int y, int color) {
if (entry.shadow) {
font.drawStringWithShadow(entry.text, x, y, color);
} else {
font.drawString(entry.text, x, y, color);
}
}
private static void enableScissor(int x, int y, int width, int height) {
Minecraft mc = Minecraft.getMinecraft();
int scale = mc.gameSettings.guiScale;
if (scale == 0) {
scale = Math.max(1, Math.min(mc.displayWidth, mc.displayHeight) / 320);
}
int sx = x * scale;
int sy = mc.displayHeight - (y + height) * scale;
int sw = width * scale;
int sh = height * scale;
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(sx, sy, sw, sh);
}
private static void disableScissor() {
GL11.glDisable(GL11.GL_SCISSOR_TEST);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -5,6 +5,7 @@ import com.coloryr.allmusic.client.core.AllMusicCore;
import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
@@ -40,6 +41,12 @@ public class AllMusicClient implements AllMusicBridge {
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
private static final ResourceLocation channel = new ResourceLocation("allmusic", "channel");
public static PoseStack context;
public static void update(PoseStack matrices) {
context = matrices;
}
public AllMusicClient() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
@@ -112,6 +119,7 @@ public class AllMusicClient implements AllMusicBridge {
@SubscribeEvent
public void onRenderOverlay(RenderGameOverlayEvent.Pre e) {
if (e.getType() == RenderGameOverlayEvent.ElementType.PORTAL) {
update(e.getMatrixStack());
AllMusicCore.hudUpdate();
}
}
@@ -139,7 +147,7 @@ public class AllMusicClient implements AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -4,321 +4,200 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.math.Matrix4f;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.network.chat.Component;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static final RenderBuffers renderBuffers = new RenderBuffers();
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final RenderTarget target;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
public CoreRenderTarget() {
target = new RenderTarget(800, 200, false, false);
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = (int) (width * window.getGuiScale());
nowHeight = (int) (height * window.getGuiScale());
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight, false);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.clear(false);
target.bindWrite(true);
Window window = Minecraft.getInstance().getWindow();
RenderSystem.ortho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
Window window = Minecraft.getInstance().getWindow();
RenderSystem.ortho(0.0F, (double) window.getWidth() / window.getGuiScale(), (double) window.getHeight() / window.getGuiScale(), 0.0F, 1000.0F, 3000.0F);
RenderTarget main = Minecraft.getInstance().getMainRenderTarget();
main.bindWrite(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
Font font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
if (font.width(component) == 0) {
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
// 使用 drawInBatch 将文字渲染到 CoreRenderTarget 自己的 buffer,
// 而不是 Font 内部的 buffer。这样 endBatch() 才能正确 flush 到当前 RenderTarget(FBO)。
int width = font.drawInBatch(component, 0, y, color, shadow,
new Matrix4f(), renderBuffers.bufferSource(),
false, 0, 15728880);
RenderSystem.disableDepthTest();
renderBuffers.bufferSource().endBatch();
RenderSystem.enableDepthTest();
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
RenderSystem.bindTexture(target.getColorTextureId());
RenderSystem.enableTexture();
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
float w = (width / 2);
float h = (height / 2);
Matrix4f matrix = Matrix4f.createTranslateMatrix(x + w, y + h, 0);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
// 注意:1.16.5 的 FBO 纹理不需要 Y 轴翻转,纹理原点在左上角
float u0 = texX * scale / target.width;
float v0 = texY * scale / target.height;
float u1 = (texX + width) * scale / target.width;
float v1 = (texY + height) * scale / target.height;
BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder();
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormat.POSITION_COLOR_TEX);
bufferBuilder.vertex(matrix, x0, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v0).endVertex();
bufferBuilder.vertex(matrix, x0, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v0).endVertex();
bufferBuilder.end();
BufferUploader.end(bufferBuilder);
RenderSystem.disableBlend();
RenderSystem.depthMask(true);
target.unbindRead();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
PoseStack matrices = AllMusicClient.context;
if (matrices == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
enableScissor(drawX, drawY, maxWidth, entry.height);
drawString(font, matrices, entry, drawX - scrollOffset, drawY, finalColor);
if (scrollOffset > 0) {
drawString(font, matrices, entry, drawX - scrollOffset + entry.width, drawY, finalColor);
}
disableScissor();
} else {
drawString(font, matrices, entry, drawX, drawY, finalColor);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
PoseStack matrices = AllMusicClient.context;
if (matrices == null) return;
Font font = Minecraft.getInstance().font;
TextEntry entry = entries.get(line);
drawString(font, matrices, entry,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha));
}
@Override
public Point2f getLine(int line) {
if (line >= texts.size()) {
if (line >= texts.size() || line >= entries.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
if (texts.isEmpty()) {
return;
}
PoseStack matrices = AllMusicClient.context;
if (matrices == null) return;
Font font = Minecraft.getInstance().font;
target.unbindRead();
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
enableScissor(drawX, drawY, maxWidth, entry.height);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, matrices, entry, drawX - (int) texOffset, drawY, finalColor);
disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, matrices, entry, drawX, drawY, finalColor);
disableScissor();
}
}
}
private static void drawString(Font font, PoseStack matrices, TextEntry entry,
int x, int y, int color) {
if (entry.shadow) {
font.drawShadow(matrices, entry.component, x, y, color);
} else {
font.draw(matrices, entry.component, x, (float) y, color);
}
}
private static void enableScissor(int x, int y, int width, int height) {
Window window = Minecraft.getInstance().getWindow();
double scale = window.getGuiScale();
int sx = (int) (x * scale);
int sy = (int) (window.getHeight() - (y + height) * scale);
int sw = (int) (width * scale);
int sh = (int) (height * scale);
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(sx, sy, sw, sh);
}
private static void disableScissor() {
GL11.glDisable(GL11.GL_SCISSOR_TEST);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -6,6 +6,7 @@ import com.coloryr.allmusic.client.core.render.PictureFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.core.render.TextureRender;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
@@ -37,6 +38,8 @@ public class AllMusicClient implements AllMusicBridge {
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
private static final ResourceLocation channel = new ResourceLocation("allmusic", "channel");
public static GuiGraphics context;
public AllMusicClient() {
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
@@ -124,6 +127,7 @@ public class AllMusicClient implements AllMusicBridge {
@SubscribeEvent
public void onRenderOverlay(RenderGuiOverlayEvent.Pre e) {
if (e.getOverlay().id() == VanillaGuiOverlay.PORTAL.id()) {
context = e.getGuiGraphics();
AllMusicCore.hudUpdate();
}
}
@@ -135,7 +139,7 @@ public class AllMusicClient implements AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -4,300 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static final RenderBuffers renderBuffers = new RenderBuffers();
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final RenderTarget target;
private Matrix4f matrix4f;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
public CoreRenderTarget() {
target = new TextureTarget(800, 200, false, false);
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = (int) (width * window.getGuiScale());
nowHeight = (int) (height * window.getGuiScale());
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight, false);
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
} else if (matrix4f == null) {
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.clear(false);
target.bindWrite(true);
RenderSystem.backupProjectionMatrix();
if (matrix4f != null) {
RenderSystem.setProjectionMatrix(matrix4f, VertexSorting.ORTHOGRAPHIC_Z);
}
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
RenderSystem.restoreProjectionMatrix();
RenderTarget main = Minecraft.getInstance().getMainRenderTarget();
main.bindWrite(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
if (font.width(component) == 0) {
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int width = font.drawInBatch(component, 0, y, color, shadow, new Matrix4f(), renderBuffers.bufferSource(), Font.DisplayMode.NORMAL, 0, 15728880);
RenderSystem.disableDepthTest();
renderBuffers.bufferSource().endBatch();
RenderSystem.enableDepthTest();
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
RenderSystem.setShaderTexture(0, target.getColorTextureId());
RenderSystem.setShader(GameRenderer::getPositionColorTexShader);
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
float w = (width / 2);
float h = (height / 2);
Matrix4f matrix = new Matrix4f().translation(x + w, y + h, 0);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder();
bufferBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR_TEX);
bufferBuilder.vertex(matrix, x0, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y1, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v1).endVertex();
bufferBuilder.vertex(matrix, x1, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u1, v0).endVertex();
bufferBuilder.vertex(matrix, x0, y0, z).color(1.0f, 1.0f, 1.0f, alpha).uv(u0, v0).endVertex();
BufferUploader.drawWithShader(bufferBuilder.end());
RenderSystem.disableBlend();
RenderSystem.depthMask(true);
target.unbindRead();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -305,19 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
target.unbindRead();
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
}
@@ -142,7 +142,7 @@ public class AllMusic implements AllMusicBridge {
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -6,280 +6,161 @@ import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.shader.Framebuffer;
import org.lwjgl.opengl.GL11;
public class CoreRenderTarget extends TextFrameBuffer {
private final Framebuffer target;
import java.util.ArrayList;
import java.util.List;
public CoreRenderTarget() {
target = new Framebuffer(800, 200, false);
target.setFramebufferColor(0.0f, 0.0f, 0.0f, 0.0f);
public class CoreRenderTarget extends TextFrameBuffer {
private static class TextEntry {
final String text;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
TextEntry(String text, int y, int color, boolean shadow, int width, int height) {
this.text = text;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Minecraft mc = Minecraft.getMinecraft();
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
nowWidth = (width * scaledresolution.getScaledWidth());
nowHeight = (height * scaledresolution.getScaledHeight());
if (nowWidth > target.framebufferWidth || nowHeight > target.framebufferHeight) {
target.createFramebuffer(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.framebufferClear();
target.bindFramebuffer(true);
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
Minecraft minecraft = Minecraft.getMinecraft();
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Minecraft minecraft = Minecraft.getMinecraft();
ScaledResolution scaledresolution = new ScaledResolution(minecraft, minecraft.displayWidth, minecraft.displayHeight);
FontRenderer font = minecraft.fontRenderer;
int width;
if (shadow) {
width = font.drawStringWithShadow(text, 0, 0, color);
} else {
width = font.drawString(text, 0, 0, color);
}
TextItem item = new TextItem(width, font.FONT_HEIGHT + (shadow ? 1 : 0), y, (float) scaledresolution.getScaledWidth());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, target.framebufferTexture);
GL11.glDepthMask(false);
GL11.glEnable(GL11.GL_BLEND);
float w = (width / 2);
float h = (height / 2);
GL11.glPushMatrix();
GL11.glTranslatef(x + w, y + h, 0.0f);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.framebufferWidth;
float v0 = 1 - (texY * scale / target.framebufferHeight);
float u1 = (texX + width) * scale / target.framebufferWidth;
float v1 = 1 - ((texY + height) * scale / target.framebufferHeight);
Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.setColorRGBA_F(1.0f, 1.0f, 1.0f, alpha);
tessellator.addVertexWithUV(x0, y1, z, u0, v1);
tessellator.addVertexWithUV(x1, y1, z, u1, v1);
tessellator.addVertexWithUV(x1, y0, z, u1, v0);
tessellator.addVertexWithUV(x0, y0, z, u0, v0);
tessellator.draw();
GL11.glDisable(GL11.GL_BLEND);
GL11.glDepthMask(true);
GL11.glPopMatrix();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
color = (color & 0x00FFFFFF) | 0xFF000000;
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
int width = font.getStringWidth(text);
if (width == 0) {
return;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
int height = font.FONT_HEIGHT + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(text, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
GL11.glPushMatrix();
GL11.glTranslatef(drawX, drawY, 0);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
enableScissor(drawX, drawY, maxWidth, entry.height);
drawString(font, entry, -scrollOffset, 0, finalColor);
if (scrollOffset > 0) {
drawString(font, entry, -scrollOffset + entry.width, 0, finalColor);
}
disableScissor();
} else {
drawString(font, entry, 0, 0, finalColor);
}
GL11.glPopMatrix();
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
TextEntry entry = entries.get(line);
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
GL11.glPushMatrix();
GL11.glTranslatef((float) x, (float) (y + entry.y), 0);
drawString(font, entry, 0, 0, applyAlpha(entry.color, alpha));
GL11.glPopMatrix();
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
FontRenderer font = Minecraft.getMinecraft().fontRenderer;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
GL11.glPushMatrix();
GL11.glTranslatef(drawX, drawY, 0);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
enableScissor(drawX, drawY, maxWidth, entry.height);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, entry, -(int) texOffset, 0, finalColor);
disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
enableScissor(drawX, drawY, revealWidth, entry.height);
drawString(font, entry, 0, 0, finalColor);
disableScissor();
}
GL11.glPopMatrix();
}
}
@Override
@@ -287,17 +168,42 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (TextItem item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
private static void drawString(FontRenderer font, TextEntry entry, int x, int y, int color) {
if (entry.shadow) {
font.drawStringWithShadow(entry.text, x, y, color);
} else {
font.drawString(entry.text, x, y, color);
}
}
private static void enableScissor(int x, int y, int width, int height) {
Minecraft mc = Minecraft.getMinecraft();
int scale = mc.gameSettings.guiScale;
if (scale == 0) {
int k = Math.min(mc.displayWidth, mc.displayHeight);
scale = Math.max(1, k / 320);
}
int sx = x * scale;
int sy = mc.displayHeight - (y + height) * scale;
int sw = width * scale;
int sh = height * scale;
GL11.glEnable(GL11.GL_SCISSOR_TEST);
GL11.glScissor(sx, sy, sw, sh);
}
private static void disableScissor() {
GL11.glDisable(GL11.GL_SCISSOR_TEST);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -8,6 +8,7 @@ import com.coloryr.allmusic.client.core.render.TextureRender;
import com.coloryr.allmusic.comm.AllMusicInit;
import com.coloryr.allmusic.comm.MusicCodec;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
@@ -37,6 +38,8 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
public static final String MODID = "allmusic_client";
public static final Logger LOGGER = LogManager.getLogger("AllMusic Client");
public static GuiGraphics context;
@SubscribeEvent
public static void setup(final FMLClientSetupEvent event) {
AllMusicClient client = new AllMusicClient();
@@ -98,7 +101,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
@Override
public TextFrameBuffer makeTextRender(String name) {
return new CoreRenderTarget();
return new CoreRenderTarget(name);
}
@Override
@@ -169,6 +172,7 @@ public class AllMusicClient implements IPayloadHandler<MusicCodec>, AllMusicBrid
@SubscribeEvent
public static void onRenderOverlay(RenderGuiLayerEvent.Post e) {
if (e.getName().equals(VanillaGuiLayers.CAMERA_OVERLAYS)) {
context= e.getGuiGraphics();
AllMusicCore.hudUpdate();
}
}
@@ -4,299 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderBuffers;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private static final RenderBuffers renderBuffers = new RenderBuffers(Runtime.getRuntime().availableProcessors());
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final RenderTarget target;
private Matrix4f matrix4f;
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
public CoreRenderTarget() {
target = new TextureTarget(800, 200, false, false);
target.setClearColor(0.0f, 0.0f, 0.0f, 0.0f);
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = (int) (width * window.getGuiScale());
nowHeight = (int) (height * window.getGuiScale());
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight, false);
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
} else if (matrix4f == null) {
matrix4f = new Matrix4f().setOrtho(0.0F, (float) (target.width / window.getGuiScale()),
(float) (target.height / window.getGuiScale()), 0.0F, 1000.0F, 21000.0F);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
target.clear(false);
target.bindWrite(true);
RenderSystem.backupProjectionMatrix();
if (matrix4f != null) {
RenderSystem.setProjectionMatrix(matrix4f, VertexSorting.ORTHOGRAPHIC_Z);
}
entries.clear();
}
@Override
public void unUse() {
isDraw = false;
RenderSystem.restoreProjectionMatrix();
RenderTarget main = Minecraft.getInstance().getMainRenderTarget();
main.bindWrite(true);
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
if (font.width(component) == 0) {
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int width = font.drawInBatch(component, 0, y, color, shadow, new Matrix4f(), renderBuffers.bufferSource(), Font.DisplayMode.NORMAL, 0, 15728880);
RenderSystem.disableDepthTest();
renderBuffers.bufferSource().endBatch();
RenderSystem.enableDepthTest();
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
RenderSystem.setShaderTexture(0, target.getColorTextureId());
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.depthMask(false);
RenderSystem.enableBlend();
RenderSystem.depthFunc(GL30.GL_ALWAYS);
float w = (width / 2);
float h = (height / 2);
Matrix4f matrix = new Matrix4f().translation(x + w, y + h, 0);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
float z = 0;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
BufferBuilder bufferBuilder = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
bufferBuilder.addVertex(matrix, x0, y1, z).setUv(u0, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y1, z).setUv(u1, v1).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x1, y0, z).setUv(u1, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
bufferBuilder.addVertex(matrix, x0, y0, z).setUv(u0, v0).setColor(1.0f, 1.0f, 1.0f, alpha);
BufferUploader.drawWithShader(bufferBuilder.buildOrThrow());
RenderSystem.disableBlend();
RenderSystem.depthMask(true);
target.unbindRead();
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, (int) (texWidth * percent), texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
int texOffset = (int) (maxOffset * percent);
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
}
/**
* 正向百分比显示(从左开始显示,右边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示前面40%的内容
*/
public void drawByForwardPercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移为0(靠左显示)
int screenOffset = 0;
// 贴图偏移为0(从左边开始)
int texOffset = 0;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
}
/**
* 反向百分比显示(从右开始显示,左边空白)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),表示显示的内容比例,例如0.4表示显示后面40%的内容
*/
public void drawByReversePercent(float alpha, int startX, int startY,
int texX, int texY,
int texWidth, int texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.min(1.0f, Math.max(0.0f, percent));
// 如果要显示的内容为0,直接返回
if (percent <= 0) {
return;
}
// 计算要显示的内容宽度
int contentWidth = (int) (texWidth * percent);
// 实际渲染宽度
int renderWidth = Math.min(contentWidth, maxWidth);
// 屏幕偏移(靠右显示)
int screenOffset = maxWidth - renderWidth;
// 贴图偏移(显示后面部分)
int texOffset = texWidth - contentWidth;
// 渲染
draw(alpha, startX + screenOffset, startY,
renderWidth, texHeight,
texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -304,19 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
target.unbindRead();
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
}
@@ -4,219 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
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 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.GuiGraphics;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.gui.render.state.GlyphRenderState;
import net.minecraft.client.gui.render.state.GuiElementRenderState;
import net.minecraft.client.gui.render.state.GuiRenderState;
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.network.chat.Component;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.drawString(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -224,280 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
private final CachedOrthoProjectionMatrixBuffer itemsProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("items", -1000.0F, 1000.0F, true);
private @Nullable ScreenRectangle previousScissorArea = null;
private @Nullable RenderPipeline previousPipeline = null;
private @Nullable TextureSetup previousTextureSetup = null;
private @Nullable BufferBuilder bufferBuilder = null;
public GuiRenderer(GuiRenderState state) {
this.renderState = state;
}
public void render(GpuBufferSlice bufferSlice, CoreRenderTarget target) {
this.renderState.forEachText(state -> {
var matrix3x2fc = state.pose;
var screenrectangle = state.scissor;
state.ensurePrepared().visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(TextRenderable.Styled styled) {
this.accept(styled);
}
@Override
public void acceptEffect(TextRenderable textRenderable) {
this.accept(textRenderable);
}
private void accept(TextRenderable textRenderable) {
renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2fc, textRenderable, screenrectangle));
}
});
});
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.bufferBuilder = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
var map = new Object2IntOpenHashMap<VertexFormat>();
for (var draw : this.meshesToDraw) {
var state = draw.mesh.drawState();
var vertexformat = state.format();
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize());
}
for (var entry : map.object2IntEntrySet()) {
var vertexformat = entry.getKey();
int i = entry.getIntValue();
var mappableringbuffer = this.vertexBuffers.get(vertexformat);
if (mappableringbuffer == null || mappableringbuffer.size() < i) {
if (mappableringbuffer != null) {
mappableringbuffer.close();
}
this.vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "GUI vertex buffer for " + vertexformat, 34, i));
}
}
var commandencoder = RenderSystem.getDevice().createCommandEncoder();
map = new Object2IntOpenHashMap<>();
for (MeshToDraw draw : this.meshesToDraw) {
var meshdata = draw.mesh;
var state = meshdata.drawState();
var vertexformat = state.format();
var mappableringbuffer = this.vertexBuffers.get(vertexformat);
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
ByteBuffer bytebuffer = meshdata.vertexBuffer();
int i = bytebuffer.remaining();
int j = map.getInt(vertexformat);
try (GpuBuffer.MappedView view = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) {
MemoryUtil.memCopy(bytebuffer, view.data());
}
map.put(vertexformat, j + i);
this.draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(),
state.indexCount(), draw.pipeline, draw.textureSetup, draw.scissorArea));
draw.close();
}
this.draw(bufferSlice, target);
for (var mappableringbuffer : this.vertexBuffers.values()) {
mappableringbuffer.rotate();
}
this.draws.clear();
this.meshesToDraw.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
}
private void draw(GpuBufferSlice bufferSlice, CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var window = minecraft.getWindow();
RenderSystem.setProjectionMatrix(
this.guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(), (float) target.target.height / window.getGuiScale()),
ProjectionType.ORTHOGRAPHIC
);
var rendertarget = target.target;
int i = 0;
for (var item : this.draws) {
if (item.indexCount > i) {
i = item.indexCount;
}
}
var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var gpubuffer = buffer.getBuffer(i);
var indexType = buffer.type();
var gpubufferslice = 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());
if (!this.meshesToDraw.isEmpty()) {
this.executeDrawRange(() -> "GUI before blur", rendertarget, bufferSlice, gpubufferslice, gpubuffer,
indexType, Math.min(this.meshesToDraw.size(), this.draws.size()));
}
}
}
private void executeDrawRange(Supplier<String> name, RenderTarget target, GpuBufferSlice bufferSlice, GpuBufferSlice bufferSlice1,
GpuBuffer buffer, VertexFormat.IndexType indexType, int count) {
try (var renderpass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(),
target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderpass);
renderpass.setUniform("Fog", bufferSlice);
renderpass.setUniform("DynamicTransforms", bufferSlice1);
for (int i = 0; i < count; i++) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderpass, buffer, indexType);
}
}
}
private void addElementToMesh(GuiElementRenderState state) {
var pipeline = state.pipeline();
var texturesetup = state.textureSetup();
var screenrectangle = state.scissorArea();
if (pipeline != this.previousPipeline
|| this.scissorChanged(screenrectangle, this.previousScissorArea)
|| !texturesetup.equals(this.previousTextureSetup)) {
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
this.bufferBuilder = new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
this.previousPipeline = pipeline;
this.previousTextureSetup = texturesetup;
this.previousScissorArea = screenrectangle;
}
state.buildVertices(this.bufferBuilder);
}
private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle rectangle) {
var meshdata = builder.build();
if (meshdata != null) {
this.meshesToDraw.add(new MeshToDraw(meshdata, pipeline, textureSetup, rectangle));
}
}
private void executeDraw(Draw draw, RenderPass pass, GpuBuffer buffer, VertexFormat.IndexType index) {
var renderpipeline = draw.pipeline();
pass.setPipeline(renderpipeline);
pass.setVertexBuffer(0, draw.vertexBuffer);
var screenrectangle = draw.scissorArea();
if (screenrectangle != null) {
Window window = Minecraft.getInstance().getWindow();
int i = window.getHeight();
int j = window.getGuiScale();
double d0 = screenrectangle.left() * j;
double d1 = i - screenrectangle.bottom() * j;
double d2 = screenrectangle.width() * j;
double d3 = screenrectangle.height() * j;
pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3));
} else {
pass.disableScissor();
}
if (draw.textureSetup.texure0() != null) {
pass.bindTexture("Sampler0", draw.textureSetup.texure0(), draw.textureSetup.sampler0());
}
if (draw.textureSetup.texure1() != null) {
pass.bindTexture("Sampler1", draw.textureSetup.texure1(), draw.textureSetup.sampler1());
}
if (draw.textureSetup.texure2() != null) {
pass.bindTexture("Sampler2", draw.textureSetup.texure2(), draw.textureSetup.sampler2());
}
pass.setIndexBuffer(buffer, index);
pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
}
private boolean scissorChanged(@Nullable ScreenRectangle rectangle, @Nullable ScreenRectangle rectangle1) {
if (rectangle == rectangle1) {
return false;
} else {
return rectangle != null ? !rectangle.equals(rectangle1) : true;
}
}
@Override
public void close() {
this.byteBufferBuilder.close();
this.guiProjectionMatrixBuffer.close();
this.itemsProjectionMatrixBuffer.close();
for (MappableRingBuffer mappableringbuffer : this.vertexBuffers.values()) {
mappableringbuffer.close();
}
}
record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
}
record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) implements AutoCloseable {
@Override
public void close() {
this.mesh.close();
}
}
}
}
}
@@ -170,7 +170,9 @@ public class CoreRenderTarget extends TextFrameBuffer {
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -63,7 +63,6 @@ public class CoreRenderTarget extends TextFrameBuffer {
Component component = MiniMessage.parse(text);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
@@ -171,7 +170,9 @@ public class CoreRenderTarget extends TextFrameBuffer {
}
private static int applyAlpha(int color, float alpha) {
int a = Math.clamp((int) (alpha * 255), 0, 255);
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
}
@@ -4,212 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
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 it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.gui.render.state.GlyphEffectRenderState;
import net.minecraft.client.gui.render.state.GlyphRenderState;
import net.minecraft.client.gui.render.state.GuiElementRenderState;
import net.minecraft.client.gui.render.state.GuiRenderState;
import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.network.chat.Component;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
public void unUse() {
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
Window window = Minecraft.getInstance().getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
GuiGraphics graphics = new GuiGraphics(Minecraft.getInstance(), renderState);
color = color | 0xFF000000;
graphics.drawString(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView()), matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.drawString(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.drawString(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphics gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.drawString(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -217,274 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom)
.thenComparing(ScreenRectangle::left)
.thenComparing(ScreenRectangle::right)
);
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
private ScreenRectangle previousScissorArea;
private RenderPipeline previousPipeline;
private TextureSetup previousTextureSetup;
private BufferBuilder bufferBuilder;
public GuiRenderer(GuiRenderState state) {
renderState = state;
}
public void render(GpuBufferSlice gpu, CoreRenderTarget target) {
renderState.forEachText(state -> {
var matrix3x2f = state.pose;
var screenrectangle = state.scissor;
state.ensurePrepared().visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(BakedGlyph.GlyphInstance glyph) {
if (glyph.glyph().textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenrectangle));
}
}
@Override
public void acceptEffect(BakedGlyph glyph, BakedGlyph.Effect effect) {
if (glyph.textureView() != null) {
renderState.submitGlyphToCurrentLayer(new GlyphEffectRenderState(matrix3x2f, glyph, effect, screenrectangle));
}
}
});
});
renderState.sortElements(ELEMENT_SORT_COMPARATOR);
previousScissorArea = null;
previousPipeline = null;
previousTextureSetup = null;
bufferBuilder = null;
renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
recordDraws();
if (!draws.isEmpty()) {
Minecraft minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
RenderSystem.setProjectionMatrix(
guiProjectionMatrixBuffer.getBuffer((float) target.target.width / window.getGuiScale(),
(float) target.target.height / window.getGuiScale()),
ProjectionType.ORTHOGRAPHIC
);
var rendertarget = target.target;
int i = 0;
for (Draw guirenderer$draw : draws) {
if (guirenderer$draw.indexCount > i) {
i = guirenderer$draw.indexCount;
}
}
var buffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var gpubuffer = buffer.getBuffer(i);
var index = buffer.type();
var gpubufferslice = 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(), 0.0F);
if (!meshesToDraw.isEmpty()) {
executeDrawRange(() -> "before blur", rendertarget, gpu, gpubufferslice, gpubuffer,
index, Math.min(meshesToDraw.size(), draws.size()), target);
}
}
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.rotate();
}
draws.clear();
meshesToDraw.clear();
renderState.reset();
}
private void executeDrawRange(Supplier<String> name, RenderTarget target, GpuBufferSlice buffer1, GpuBufferSlice buffer2,
GpuBuffer buffer3, VertexFormat.IndexType index, int count, CoreRenderTarget target1) {
try (RenderPass renderpass = RenderSystem.getDevice()
.createCommandEncoder()
.createRenderPass(name, target.getColorTextureView(), OptionalInt.empty(),
target.useDepth ? target.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderpass);
renderpass.setUniform("Fog", buffer1);
renderpass.setUniform("DynamicTransforms", buffer2);
for (int i = 0; i < count; i++) {
var item = draws.get(i);
executeDraw(item, renderpass, target1, buffer3, index);
}
}
}
private void addElementToMesh(GuiElementRenderState state, int range) {
var pipeline = state.pipeline();
var setup = state.textureSetup();
var screen = state.scissorArea();
if (pipeline != previousPipeline || scissorChanged(screen, previousScissorArea) || !setup.equals(previousTextureSetup)) {
if (bufferBuilder != null) {
recordMesh(bufferBuilder, previousPipeline, previousTextureSetup, previousScissorArea);
}
bufferBuilder = new BufferBuilder(byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
previousPipeline = pipeline;
previousTextureSetup = setup;
previousScissorArea = screen;
}
state.buildVertices(bufferBuilder, 0.0F + range * 0.01F);
}
private void recordMesh(BufferBuilder builder, RenderPipeline pipeline, TextureSetup texture, ScreenRectangle rectangle) {
var meshdata = builder.buildOrThrow();
meshesToDraw.add(new MeshToDraw(meshdata, pipeline, texture, rectangle));
}
private void recordDraws() {
var map = new Object2IntOpenHashMap<VertexFormat>();
for (MeshToDraw item : meshesToDraw) {
var state = item.mesh.drawState();
var vertexformat = state.format();
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
map.put(vertexformat, map.getInt(vertexformat) + state.vertexCount() * vertexformat.getVertexSize());
}
for (var entry : map.object2IntEntrySet()) {
var vertexformat = entry.getKey();
int i = entry.getIntValue();
var buffer = vertexBuffers.get(vertexformat);
if (buffer == null || buffer.size() < i) {
if (buffer != null) {
buffer.close();
}
vertexBuffers.put(vertexformat, new MappableRingBuffer(() -> "vertex buffer for " + vertexformat, 34, i));
}
}
var commandencoder = RenderSystem.getDevice().createCommandEncoder();
map = new Object2IntOpenHashMap<>();
for (MeshToDraw item : meshesToDraw) {
var meshdata = item.mesh;
var state = meshdata.drawState();
var vertexformat = state.format();
var mappableringbuffer = vertexBuffers.get(vertexformat);
if (!map.containsKey(vertexformat)) {
map.put(vertexformat, 0);
}
ByteBuffer bytebuffer = meshdata.vertexBuffer();
int i = bytebuffer.remaining();
int j = map.getInt(vertexformat);
try (var item1 = commandencoder.mapBuffer(mappableringbuffer.currentBuffer().slice(j, i), false, true)) {
MemoryUtil.memCopy(bytebuffer, item1.data());
}
map.put(vertexformat, j + i);
draws.add(new Draw(mappableringbuffer.currentBuffer(), j / vertexformat.getVertexSize(), state.mode(),
state.indexCount(), item.pipeline, item.textureSetup, item.scissorArea));
item.close();
}
}
private void executeDraw(Draw draw, RenderPass pass, CoreRenderTarget target, GpuBuffer buffer, VertexFormat.IndexType index) {
var renderpipeline = draw.pipeline();
pass.setPipeline(renderpipeline);
pass.setVertexBuffer(0, draw.vertexBuffer);
var area = draw.scissorArea();
if (area != null) {
Window window = Minecraft.getInstance().getWindow();
int i = target.target.height;
int j = window.getGuiScale();
double d0 = area.left() * j;
double d1 = i - area.bottom() * j;
double d2 = area.width() * j;
double d3 = area.height() * j;
pass.enableScissor((int) d0, (int) d1, Math.max(0, (int) d2), Math.max(0, (int) d3));
} else {
pass.disableScissor();
}
if (draw.textureSetup.texure0() != null) {
pass.bindSampler("Sampler0", draw.textureSetup.texure0());
}
if (draw.textureSetup.texure1() != null) {
pass.bindSampler("Sampler1", draw.textureSetup.texure1());
}
if (draw.textureSetup.texure2() != null) {
pass.bindSampler("Sampler2", draw.textureSetup.texure2());
}
pass.setIndexBuffer(buffer, index);
pass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
}
private boolean scissorChanged(ScreenRectangle screen1, ScreenRectangle screen2) {
if (screen1 == screen2) {
return false;
} else {
return screen1 == null || !screen1.equals(screen2);
}
}
@Override
public void close() {
byteBufferBuilder.close();
guiProjectionMatrixBuffer.close();
for (MappableRingBuffer mappableringbuffer : vertexBuffers.values()) {
mappableringbuffer.close();
}
}
record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, ScreenRectangle scissorArea) {
}
record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
ScreenRectangle scissorArea) implements AutoCloseable {
@Override
public void close() {
mesh.close();
}
}
}
}
}
@@ -3,82 +3,53 @@ package com.coloryr.allmusic.client;
import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.client.others.ModernUI;
import com.coloryr.allmusic.codec.HudPosType;
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.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
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 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.GuiGraphicsExtractor;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.fog.FogRenderer;
import net.minecraft.client.renderer.state.WindowRenderState;
import net.minecraft.client.renderer.state.gui.GlyphRenderState;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.profiling.Profiler;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final FogRenderer fogRenderer = new FogRenderer();
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), 0);
entries.clear();
}
@Override
@@ -88,133 +59,104 @@ public class CoreRenderTarget extends TextFrameBuffer {
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.text(font, component, 0, y, color, shadow);
renderer.render(fogRenderer.getBuffer(FogRenderer.FogMode.NONE), this);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.text(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -222,285 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right));
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey)).thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final List<MeshToDraw> meshesToDraw = new ArrayList<>();
private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
private final Projection guiProjection = new Projection();
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
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 GuiRenderer(GuiRenderState renderState) {
this.renderState = renderState;
}
public void render(GpuBufferSlice fogBuffer, CoreRenderTarget target) {
var profiler = Profiler.get();
if (this.renderState.panoramaRenderState != null) {
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
}
profiler.push("allmusic text prepare");
this.renderState.forEachText((text) -> {
var pose = text.pose;
var scissor = text.scissor;
Font.PreparedText preparedText = text.ensurePrepared();
if (AllMusicClient.modui) {
ModernUI.text(preparedText, renderState, pose, scissor);
} else {
preparedText.visit(new Font.GlyphVisitor() {
@Override
public void acceptGlyph(TextRenderable.Styled glyph) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, glyph, scissor));
}
@Override
public void acceptEffect(TextRenderable glyph) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, glyph, scissor));
}
});
}
});
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.bufferBuilder = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
if (this.bufferBuilder != null) {
this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
}
this.ensureVertexBufferSizes();
var commandEncoder = RenderSystem.getDevice().createCommandEncoder();
var offsets = new Object2IntOpenHashMap<VertexFormat>();
for (var meshToDraw : this.meshesToDraw) {
var mesh = meshToDraw.mesh;
var drawState = mesh.drawState();
var format = drawState.format();
var vertexBuffer = this.vertexBuffers.get(format);
if (!offsets.containsKey(format)) {
offsets.put(format, 0);
}
var meshVertexBuffer = mesh.vertexBuffer();
int meshBufferSize = meshVertexBuffer.remaining();
int offset = offsets.getInt(format);
try (var mappedView = commandEncoder.mapBuffer(vertexBuffer.currentBuffer().slice(offset, meshBufferSize), false, true)) {
MemoryUtil.memCopy(meshVertexBuffer, mappedView.data());
}
offsets.put(format, offset + meshBufferSize);
this.draws.add(new Draw(vertexBuffer.currentBuffer(), offset / format.getVertexSize(),
drawState.mode(), drawState.indexCount(), meshToDraw.pipeline, meshToDraw.textureSetup, meshToDraw.scissorArea));
meshToDraw.close();
}
profiler.popPush("allmusic text draw");
this.draw(fogBuffer, target);
profiler.popPush("allmusic text vertexBufferRotate");
for (MappableRingBuffer buffer : this.vertexBuffers.values()) {
buffer.rotate();
}
profiler.pop();
this.draws.clear();
this.meshesToDraw.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
}
private void draw(GpuBufferSlice fogBuffer, CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var windowState = minecraft.gameRenderer.getGameRenderState().windowRenderState;
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
(float) target.target.height / (float) windowState.guiScale, true);
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
var mainRenderTarget = target.target;
int maxIndexCount = 0;
for (var draw : this.draws) {
if (draw.indexCount > maxIndexCount) {
maxIndexCount = draw.indexCount;
}
}
var autoIndices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
var indexBuffer = autoIndices.getBuffer(maxIndexCount);
var indexType = autoIndices.type();
var 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());
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, fogBuffer, dynamicTransforms,
indexBuffer, indexType, Math.min(this.meshesToDraw.size(), this.draws.size()));
}
}
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice fogBuffer,
GpuBufferSlice dynamicTransforms, GpuBuffer indexBuffer, VertexFormat.IndexType indexType, int endIndex) {
try (var renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(label, mainRenderTarget.getColorTextureView(),
OptionalInt.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("Fog", fogBuffer);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
for (int i = 0; i < endIndex; ++i) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderPass, indexBuffer, indexType);
}
}
}
private void addElementToMesh(GuiElementRenderState elementState) {
var pipeline = elementState.pipeline();
var textureSetup = elementState.textureSetup();
var 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 = new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
this.previousPipeline = pipeline;
this.previousTextureSetup = textureSetup;
this.previousScissorArea = scissorArea;
}
elementState.buildVertices(this.bufferBuilder);
}
private void recordMesh(BufferBuilder bufferBuilder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
var mesh = bufferBuilder.build();
if (mesh != null) {
this.meshesToDraw.add(new MeshToDraw(mesh, pipeline, textureSetup, scissorArea));
}
}
private void ensureVertexBufferSizes() {
var requiredSizes = new Object2IntOpenHashMap<VertexFormat>();
for (var meshToDraw : this.meshesToDraw) {
var drawState = meshToDraw.mesh.drawState();
var format = drawState.format();
if (!requiredSizes.containsKey(format)) {
requiredSizes.put(format, 0);
}
requiredSizes.put(format, requiredSizes.getInt(format) + drawState.vertexCount() * format.getVertexSize());
}
for (var entry : requiredSizes.object2IntEntrySet()) {
var vertexFormat = entry.getKey();
int requiredSize = entry.getIntValue();
var 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));
}
}
}
private void executeDraw(Draw draw, RenderPass renderPass, GpuBuffer indexBuffer, VertexFormat.IndexType indexType) {
var pipeline = draw.pipeline();
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, draw.vertexBuffer);
var 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 boolean scissorChanged(@Nullable ScreenRectangle newScissor, @Nullable ScreenRectangle oldScissor) {
if (newScissor == oldScissor) {
return false;
} else if (newScissor != null) {
return !newScissor.equals(oldScissor);
} else {
return true;
}
}
private void enableScissor(ScreenRectangle rectangle, RenderPass renderPass) {
WindowRenderState windowState = Minecraft.getInstance().gameRenderer.getGameRenderState().windowRenderState;
int windowHeight = windowState.height;
int guiScale = windowState.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));
}
public void close() {
this.byteBufferBuilder.close();
this.guiProjectionMatrixBuffer.close();
for (MappableRingBuffer buffer : this.vertexBuffers.values()) {
buffer.close();
}
this.cubeMap.close();
}
private record Draw(GpuBuffer vertexBuffer, int baseVertex, VertexFormat.Mode mode, int indexCount,
RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
}
private record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) implements AutoCloseable {
public void close() {
this.mesh.close();
}
}
}
}
}
@@ -4,208 +4,159 @@ import com.coloryr.allmusic.client.core.AllMusicHud;
import com.coloryr.allmusic.client.core.Point2f;
import com.coloryr.allmusic.client.core.render.TextFrameBuffer;
import com.coloryr.allmusic.codec.HudPosType;
import com.mojang.blaze3d.GpuFormat;
import com.mojang.blaze3d.ProjectionType;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.FilterMode;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.font.TextRenderable;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.render.TextureSetup;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.state.gui.GlyphRenderState;
import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
import net.minecraft.client.renderer.state.gui.GuiRenderState;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.util.profiling.Profiler;
import org.joml.Matrix3x2f;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.jspecify.annotations.Nullable;
import java.util.*;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
public class CoreRenderTarget extends TextFrameBuffer {
private final RenderTarget target;
private final String name;
private static class TextEntry {
final Component component;
final int y;
final int color;
final boolean shadow;
final int width;
final int height;
private final GuiRenderer renderer;
private final GuiRenderState renderState = new GuiRenderState();
TextEntry(Component component, int y, int color, boolean shadow, int width, int height) {
this.component = component;
this.y = y;
this.color = color;
this.shadow = shadow;
this.width = width;
this.height = height;
}
}
private final List<TextEntry> entries = new ArrayList<>();
private final boolean isState;
public CoreRenderTarget(String name) {
this.name = name;
target = new TextureTarget(name, 800, 200, false, GpuFormat.RGBA8_UNORM);
renderer = new GuiRenderer(renderState);
isState = name.equals("state");
}
@Override
public void resize(int width, int height) {
Window window = Minecraft.getInstance().getWindow();
nowWidth = width * window.getGuiScale();
nowHeight = height * window.getGuiScale();
if (nowWidth > target.width || nowHeight > target.height) {
target.resize(nowWidth, nowHeight);
}
nowWidth = width;
nowHeight = height;
}
@Override
public void use() {
isDraw = true;
clear();
RenderSystem.getDevice().createCommandEncoder().clearColorTexture(target.getColorTexture(), new Vector4f(0));
entries.clear();
}
@Override
public void unUse() {
renderer.render(this);
isDraw = false;
}
@Override
public void drawText(String text, int y, int color, boolean shadow) {
var minecraft = Minecraft.getInstance();
Window window = minecraft.getWindow();
var font = Minecraft.getInstance().font;
color = (color & 0x00FFFFFF) | 0xFF000000;
Component component = MiniMessage.parse(text);
int width = font.width(component.getVisualOrderText());
Font font = Minecraft.getInstance().font;
int width = font.width(component);
if (width == 0) {
return;
}
int i = (int) minecraft.mouseHandler.getScaledXPos(window);
int j = (int) minecraft.mouseHandler.getScaledYPos(window);
GuiGraphicsExtractor graphics = new GuiGraphicsExtractor(Minecraft.getInstance(), renderState, i, j);
color = color | 0xFF000000;
graphics.text(font, component, 0, y, color, shadow);
TextItem item = new TextItem(width, font.lineHeight + (shadow ? 1 : 0), y, (float) window.getGuiScale());
texts.add(item);
}
/**
* 渲染贴图的一部分到屏幕指定位置
*
* @param alpha 透明度
* @param x 屏幕X坐标(左上角)
* @param y 屏幕Y坐标(左上角)
* @param width 需要渲染的宽度
* @param height 需要渲染的高度
* @param texX 贴图左上角X坐标
* @param texY 贴图左上角Y坐标
* @param scale 贴图缩放
*/
private void draw(float alpha, float x, float y, float width, float height, float texX, float texY, float scale) {
float w = width / 2;
float h = height / 2;
Matrix3x2f matrix = new Matrix3x2f().translation(x + w, y + h);
float x0 = -w;
float x1 = w;
float y0 = -h;
float y1 = h;
// 计算贴图区域UV
float u0 = texX * scale / target.width;
float v0 = 1 - (texY * scale / target.height);
float u1 = (texX + width) * scale / target.width;
float v1 = 1 - ((texY + height) * scale / target.height);
int color = 0xFFFFFF00 + (int) (255 * alpha);
AllMusicClient.context.submitGuiElementRenderState(new FloatRenderState(RenderPipelines.GUI_TEXTURED,
TextureSetup.singleTexture(target.getColorTextureView(), RenderSystem.getSamplerCache().getRepeat(FilterMode.NEAREST)),
matrix, x0, y0, x1, y1, u0, u1, v0, v1, color, AllMusicClient.context.peekScissorStack()));
}
public void drawLoop(float alpha, float x, float y,
float texX, float texY,
float textWidth, float textHeight,
int maxWidth, float offsetX, float scale) {
// 如果宽度不大于最大宽度,直接全部渲染
if (maxWidth == -1 || textWidth <= maxWidth) {
draw(alpha, x, y, textWidth, textHeight, texX, texY, scale);
return;
int height = font.lineHeight + (shadow ? 1 : 0);
if (isState) {
y = 0;
}
if (textWidth - offsetX < maxWidth) {
float nowWith = textWidth - offsetX;
draw(alpha, x, y, nowWith, textHeight, offsetX, texY, scale);
draw(alpha, x + nowWith, y, maxWidth - nowWith, textHeight, 0, texY, scale);
} else {
draw(alpha, x, y, maxWidth, textHeight, offsetX, texY, scale);
}
}
/**
* 居中百分比显示(根据百分比选择显示贴图的中间部分)
*
* @param alpha 透明度
* @param startX 起始X坐标(屏幕左上角)
* @param startY 起始Y坐标(屏幕左上角)
* @param texX 贴图左上角X
* @param texY 贴图左上角Y
* @param texWidth 贴图总宽度
* @param texHeight 贴图高度
* @param maxWidth 最大渲染宽度
* @param percent 百分比(0.0-1.0),0%显示左边,100%显示右边
*/
public void drawByPercent(float alpha, float startX, float startY,
float texX, float texY,
float texWidth, float texHeight,
int maxWidth, float percent, float scale) {
// 限制百分比范围
percent = Math.clamp(percent, 0.0f, 1.0f);
// 如果贴图宽度小于等于最大宽度,直接全部显示
if (texWidth <= maxWidth) {
draw(alpha, startX, startY, texWidth * percent, texHeight, texX, texY, scale);
return;
}
// 计算贴图的起始位置(根据百分比)
float maxOffset = texWidth - maxWidth;
float texOffset = maxOffset * percent;
// 渲染
draw(alpha, startX, startY, maxWidth, texHeight, texX + texOffset, texY, scale);
entries.add(new TextEntry(component, y, color, shadow, width, height));
texts.add(new TextItem(width, height, y, 1.0f));
}
@Override
public void draw(float alpha, int x, int y, int maxWidth, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
if (texts.isEmpty()) {
return;
}
drawLoop(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, getOffset(item) % item.textWidth, item.scale);
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
int scrollOffset = (int) (offsetX % entry.width);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - scrollOffset, drawY, finalColor, entry.shadow);
if (scrollOffset > 0) {
gui.text(font, entry.component, drawX - scrollOffset + entry.width, drawY, finalColor, entry.shadow);
}
gui.disableScissor();
} else {
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
}
}
}
@Override
public void drawLine(float x, float y, float alpha, int line) {
if (texts.isEmpty()) {
return;
}
if (line >= texts.size()) {
return;
}
TextItem item = texts.get(line);
draw(alpha, x, y, item.textWidth, item.textHeight, 0, item.y, item.scale);
if (line >= entries.size()) return;
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
TextEntry entry = entries.get(line);
gui.text(Minecraft.getInstance().font, entry.component,
(int) x, (int) (y + entry.y),
applyAlpha(entry.color, alpha), entry.shadow);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
if (texts.isEmpty()) {
return;
}
GuiGraphicsExtractor gui = AllMusicClient.context;
if (gui == null) return;
Font font = Minecraft.getInstance().font;
for (TextEntry entry : entries) {
int displayWidth = maxWidth != -1 ? Math.min(entry.width, maxWidth) : entry.width;
Point2f point = AllMusicHud.getPos(displayWidth, entry.height, x, y, dir);
int drawX = (int) point.x;
int drawY = (int) (point.y + entry.y);
int finalColor = applyAlpha(entry.color, alpha);
if (maxWidth != -1 && entry.width > maxWidth) {
float maxOffset = entry.width - maxWidth;
float texOffset = maxOffset * state;
int revealWidth = (int) (maxWidth * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + maxWidth, drawY + entry.height);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX - (int) texOffset, drawY, finalColor, entry.shadow);
gui.disableScissor();
} else {
int revealWidth = (int) (entry.width * state);
gui.enableScissor(drawX, Math.max(0, drawY), drawX + revealWidth, drawY + entry.height);
gui.text(font, entry.component, drawX, drawY, finalColor, entry.shadow);
gui.disableScissor();
}
}
}
@Override
@@ -213,199 +164,15 @@ public class CoreRenderTarget extends TextFrameBuffer {
if (line >= texts.size()) {
return new Point2f(0, 0);
}
TextItem item = texts.get(line);
return new Point2f(item.textWidth, item.textHeight);
if (line >= entries.size()) return new Point2f(0, 0);
TextEntry entry = entries.get(line);
return new Point2f(entry.width, entry.height);
}
@Override
public void drawWithState(float alpha, int x, int y, int maxWidth, float state, HudPosType dir) {
for (var item : texts) {
Point2f point = AllMusicHud.getPos(Math.min(maxWidth, item.textWidth), item.textHeight, x, y, dir);
drawByPercent(alpha, point.x, point.y + item.y, 0, item.y, item.textWidth, item.textHeight, maxWidth, state, item.scale);
}
private static int applyAlpha(int color, float alpha) {
int a = (int) (alpha * 255);
if (a < 0) a = 0;
if (a > 255) a = 255;
return (color & 0x00FFFFFF) | (a << 24);
}
public static class GuiRenderer implements AutoCloseable {
private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(ScreenRectangle::top)
.thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right));
private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR;
private final GuiRenderState renderState;
private final List<Draw> draws = new ArrayList<>();
private final StagedVertexBuffer vertexBuffer = new StagedVertexBuffer(() -> "GUI Vertex Buffer", 786432);
private final Projection guiProjection = new Projection();
private final ProjectionMatrixBuffer guiProjectionMatrixBuffer = new ProjectionMatrixBuffer("gui");
private final CubeMap cubeMap = new CubeMap(Identifier.withDefaultNamespace("textures/gui/title/background/panorama"));
private @Nullable ScreenRectangle previousScissorArea = null;
private @Nullable RenderPipeline previousPipeline = null;
private @Nullable TextureSetup previousTextureSetup = null;
private StagedVertexBuffer.@Nullable Draw previousDraw;
public GuiRenderer(final GuiRenderState renderState) {
this.renderState = renderState;
}
public void render(CoreRenderTarget target) {
var profiler = Profiler.get();
if (this.renderState.panoramaRenderState != null) {
this.cubeMap.render(10.0F, this.renderState.panoramaRenderState.spin());
}
profiler.push("prepare");
this.prepare();
profiler.popPush("upload");
this.vertexBuffer.upload();
profiler.popPush("draw");
this.draw(target);
profiler.popPush("endFrame");
this.vertexBuffer.endDraw();
this.vertexBuffer.endFrame();
this.draws.clear();
this.renderState.reset();
if (SharedConstants.DEBUG_SHUFFLE_UI_RENDERING_ORDER) {
RenderPipeline.updateSortKeySeed();
TextureSetup.updateSortKeySeed();
}
profiler.pop();
}
private void prepare() {
this.prepareText();
this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
this.addElementsToMeshes();
}
private void addElementsToMeshes() {
this.previousScissorArea = null;
this.previousPipeline = null;
this.previousTextureSetup = null;
this.previousDraw = null;
this.renderState.forEachElement(this::addElementToMesh, GuiRenderState.TraverseRange.BEFORE_BLUR);
}
private void draw(CoreRenderTarget target) {
if (!this.draws.isEmpty()) {
var minecraft = Minecraft.getInstance();
var windowState = minecraft.gameRenderer.gameRenderState().windowRenderState;
this.guiProjection.setupOrtho(1000.0F, 11000.0F, (float) target.target.width / (float) windowState.guiScale,
(float) target.target.height / (float) windowState.guiScale, true);
RenderSystem.setProjectionMatrix(this.guiProjectionMatrixBuffer.getBuffer(this.guiProjection), ProjectionType.ORTHOGRAPHIC);
var mainRenderTarget = target.target;
var dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform((new Matrix4f()).setTranslation(0.0F, 0.0F, -11000.0F));
if (!this.draws.isEmpty()) {
this.executeDrawRange(() -> "GUI before blur", mainRenderTarget, dynamicTransforms, this.draws.size());
}
}
}
private void executeDrawRange(Supplier<String> label, RenderTarget mainRenderTarget, GpuBufferSlice dynamicTransforms, int endIndex) {
try (var renderPass = RenderSystem.getDevice().createCommandEncoder()
.createRenderPass(label, mainRenderTarget.getColorTextureView(),
Optional.empty(), mainRenderTarget.useDepth ? mainRenderTarget.getDepthTextureView() : null, OptionalDouble.empty())) {
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
for (int i = 0; i < endIndex; ++i) {
Draw draw = this.draws.get(i);
this.executeDraw(draw, renderPass);
}
}
}
private void addElementToMesh(final GuiElementRenderState elementState) {
var pipeline = elementState.pipeline();
var textureSetup = elementState.textureSetup();
var scissorArea = elementState.scissorArea();
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.vertexBuffer.getVertexBuilder(Objects.requireNonNull(this.previousDraw)));
}
private void prepareText() {
this.renderState.forEachText((text) -> {
var pose = text.pose;
var scissor = text.scissor;
text.ensurePrepared().visit(new Font.GlyphVisitor() {
public void acceptRenderable(final TextRenderable renderable) {
renderState.addGlyphToCurrentLayer(new GlyphRenderState(pose, renderable, scissor));
}
});
});
}
private void executeDraw(final Draw draw, final RenderPass renderPass) {
var executeInfo = this.vertexBuffer.getExecuteInfo(draw.draw);
if (executeInfo != null) {
var pipeline = draw.pipeline();
renderPass.setPipeline(pipeline);
renderPass.setVertexBuffer(0, executeInfo.vertexBuffer().slice());
var 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(executeInfo.indexBuffer(), executeInfo.indexType());
renderPass.drawIndexed(executeInfo.indexCount(), 1, executeInfo.firstIndex(), executeInfo.baseVertex(), 0);
}
}
private boolean scissorChanged(final @Nullable ScreenRectangle newScissor, final @Nullable ScreenRectangle oldScissor) {
if (newScissor == oldScissor) {
return false;
} else if (newScissor != null) {
return !newScissor.equals(oldScissor);
} else {
return true;
}
}
private void enableScissor(final ScreenRectangle rectangle, final RenderPass renderPass) {
var window = Minecraft.getInstance().gameRenderer.gameRenderState().windowRenderState;
int guiScale = window.guiScale;
double left = (rectangle.left() * guiScale);
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 close() {
this.vertexBuffer.close();
this.guiProjectionMatrixBuffer.close();
this.cubeMap.close();
}
static {
ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
}
private record Draw(StagedVertexBuffer.Draw draw, RenderPipeline pipeline, TextureSetup textureSetup,
@Nullable ScreenRectangle scissorArea) {
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
plugins {
id("fabric-loom") version Versions.fabricLoom
}
// 直接引用 client 和 server 模块的源码目录,无需手动复制
val clientSourceDir = project(":client:fabric_1_20_1").projectDir.resolve("src/main/java")
val serverSourceDir = project(":server:fabric_1_20_1").projectDir.resolve("src/main/java")
val clientResDir = project(":client:fabric_1_20_1").projectDir.resolve("src/main/resources")
val serverResDir = project(":server:fabric_1_20_1").projectDir.resolve("src/main/resources")
sourceSets {
main {
java {
setSrcDirs(listOf(clientSourceDir, serverSourceDir))
}
resources {
setSrcDirs(listOf(
file("src/main/resources"), // onejar 自己的合并资源(fabric.mod.json)
clientResDir,
serverResDir
))
}
}
}
dependencies {
minecraft("com.mojang:minecraft:1.20.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.16.10")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.92.3+1.20.1")
modImplementation(include("net.kyori:adventure-platform-fabric:5.9.0")!!)
}
tasks {
processResources {
// onejar 自己的 fabric.mod.json 优先(排在 srcDirs 第一位),
// client/server 的 fabric.mod.json 作为重复项被排除
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
filesMatching("fabric.mod.json") {
expand(
"version" to project.version
)
}
}
remapJar {
inputFile.set(shadowJar.get().archiveFile)
archiveFileName.set("[fabric-1.20.1]AllMusic-${project.version}.jar")
destinationDirectory.set(file("${parent!!.projectDir}/../build"))
}
build {
dependsOn(remapJar)
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,35 @@
{
"schemaVersion": 1,
"id": "allmusic_client",
"version": "${version}",
"name": "AllMusic Client",
"description": "AllMusic Client Mod",
"authors": [
"Color_yr"
],
"contact": {
"homepage": "https://github.com/Coloryr/AllMusic",
"sources": "https://github.com/Coloryr/AllMusic"
},
"license": "GPL-3.0",
"icon": "assets/allmusic/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.coloryr.allmusic.server.AllMusicServer"
],
"client": [
"com.coloryr.allmusic.client.AllMusicClient"
]
},
"mixins": [
"allmusic_server.mixins.json",
"allmusic_client.mixins.json"
],
"depends": {
"fabricloader": ">=0.19.3",
"fabric": "*",
"minecraft": ">=1.16"
},
"suggests": {}
}
@@ -29,7 +29,7 @@
"adventure-platform-fabric.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"fabric": "*",
"minecraft": ">=1.16"
},
@@ -23,10 +23,9 @@
"allmusic_server.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"fabric-api": "*",
"minecraft": "~1.20",
"java": ">=17"
"minecraft": "~1.20"
},
"suggests": {
}
@@ -26,9 +26,8 @@
"allmusic_server.mixins.json"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.18.4",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.11",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.16.14",
"fabricloader": ">=0.19.3",
"minecraft": ">=1.21.6",
"java": ">=21",
"fabric-api": "*"
},
"suggests": {
@@ -29,9 +29,8 @@
}
],
"depends": {
"fabricloader": ">=0.18.3",
"fabricloader": ">=0.19.3",
"minecraft": ">=26.1",
"java": ">=25",
"fabric-api": "*"
},
"suggests": {
@@ -31,7 +31,6 @@
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": ">=26.2",
"java": ">=25",
"fabric-api": "*"
},
"suggests": {
+1
View File
@@ -58,6 +58,7 @@ include(":server:velocity")
include(":onejar")
include(":onejar:fabric_1_16_5")
include(":onejar:fabric_1_20_1")
pluginManagement {
repositories {