mirror of
https://github.com/Coloryr/AllMusic.git
synced 2026-11-08 18:54:44 +00:00
更新渲染方式
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
+118
-264
@@ -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
|
||||
|
||||
+118
-176
@@ -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": {
|
||||
|
||||
+114
-438
@@ -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": {
|
||||
|
||||
+3
-1
@@ -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": {
|
||||
|
||||
+3
-2
@@ -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": {
|
||||
|
||||
+114
-426
@@ -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": {
|
||||
|
||||
+114
-426
@@ -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": {
|
||||
|
||||
+114
-445
@@ -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": {
|
||||
|
||||
+114
-347
@@ -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
|
||||
|
||||
+148
-250
@@ -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
|
||||
|
||||
+137
-258
@@ -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
|
||||
|
||||
+118
-263
@@ -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
|
||||
|
||||
+150
-244
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+118
-262
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
-439
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+114
-426
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+115
-443
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
-347
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -58,6 +58,7 @@ include(":server:velocity")
|
||||
include(":onejar")
|
||||
|
||||
include(":onejar:fabric_1_16_5")
|
||||
include(":onejar:fabric_1_20_1")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
|
||||
Reference in new issue
Block a user