237 lines
8.0 KiB
Java
237 lines
8.0 KiB
Java
|
|
package com.chuangzhou.vivid2D.render;
|
|||
|
|
|
|||
|
|
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
|
|||
|
|
import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder;
|
|||
|
|
import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator;
|
|||
|
|
import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement;
|
|||
|
|
import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram;
|
|||
|
|
import org.joml.Vector2f;
|
|||
|
|
import org.joml.Vector4f;
|
|||
|
|
import org.lwjgl.opengl.*;
|
|||
|
|
import org.lwjgl.stb.STBTTAlignedQuad;
|
|||
|
|
import org.lwjgl.stb.STBTTBakedChar;
|
|||
|
|
import org.lwjgl.system.MemoryStack;
|
|||
|
|
import org.slf4j.Logger;
|
|||
|
|
import org.slf4j.LoggerFactory;
|
|||
|
|
|
|||
|
|
import java.nio.ByteBuffer;
|
|||
|
|
|
|||
|
|
import static org.lwjgl.stb.STBTruetype.*;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* OpenGL 文字渲染器实例类
|
|||
|
|
* 支持多字体、多实例管理,每个实例维护独立的字符数据与纹理
|
|||
|
|
*
|
|||
|
|
* @author tzdwindows 7
|
|||
|
|
*/
|
|||
|
|
public final class TextRenderer {
|
|||
|
|
private static final Logger logger = LoggerFactory.getLogger(TextRenderer.class);
|
|||
|
|
private final int bitmapWidth;
|
|||
|
|
private final int bitmapHeight;
|
|||
|
|
private final int firstChar;
|
|||
|
|
private final int charCount;
|
|||
|
|
|
|||
|
|
private STBTTBakedChar.Buffer charData;
|
|||
|
|
private int fontTextureId;
|
|||
|
|
private boolean initialized = false;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造函数
|
|||
|
|
*
|
|||
|
|
* @param bitmapWidth 字符纹理宽度
|
|||
|
|
* @param bitmapHeight 字符纹理高度
|
|||
|
|
* @param firstChar 字符起始码
|
|||
|
|
* @param charCount 字符数量
|
|||
|
|
*/
|
|||
|
|
public TextRenderer(int bitmapWidth, int bitmapHeight, int firstChar, int charCount) {
|
|||
|
|
this.bitmapWidth = bitmapWidth;
|
|||
|
|
this.bitmapHeight = bitmapHeight;
|
|||
|
|
this.firstChar = firstChar;
|
|||
|
|
this.charCount = charCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化字体渲染器
|
|||
|
|
*
|
|||
|
|
* @param fontData TTF 字体文件内容
|
|||
|
|
* @param fontHeight 字体像素高度
|
|||
|
|
*/
|
|||
|
|
public void initialize(ByteBuffer fontData, float fontHeight) {
|
|||
|
|
if (initialized) return;
|
|||
|
|
ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader");
|
|||
|
|
shader.use();
|
|||
|
|
// 验证输入参数
|
|||
|
|
if (fontData == null || fontData.capacity() == 0) {
|
|||
|
|
logger.error("Invalid font data provided to TextRenderer");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (fontHeight <= 0) {
|
|||
|
|
logger.error("Invalid font height: {}", fontHeight);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (bitmapWidth <= 0 || bitmapHeight <= 0) {
|
|||
|
|
logger.error("Invalid bitmap dimensions: {}x{}", bitmapWidth, bitmapHeight);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
charData = STBTTBakedChar.malloc(charCount);
|
|||
|
|
|
|||
|
|
// 分配位图内存
|
|||
|
|
int bitmapSize = bitmapWidth * bitmapHeight;
|
|||
|
|
if (bitmapSize <= 0) {
|
|||
|
|
logger.error("Invalid bitmap size: {}", bitmapSize);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ByteBuffer bitmap = ByteBuffer.allocateDirect(bitmapSize);
|
|||
|
|
|
|||
|
|
// 烘焙字体位图
|
|||
|
|
int result = stbtt_BakeFontBitmap(fontData, fontHeight, bitmap, bitmapWidth, bitmapHeight, firstChar, charData);
|
|||
|
|
if (result <= 0) {
|
|||
|
|
logger.error("stbtt_BakeFontBitmap failed with result: {}", result);
|
|||
|
|
charData.free();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建纹理
|
|||
|
|
fontTextureId = createTextureFromBitmap(bitmapWidth, bitmapHeight, bitmap);
|
|||
|
|
|
|||
|
|
if (fontTextureId == 0) {
|
|||
|
|
logger.error("Failed to create font texture");
|
|||
|
|
charData.free();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
initialized = true;
|
|||
|
|
logger.debug("TextRenderer initialized successfully with texture ID: {}", fontTextureId);
|
|||
|
|
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
logger.error("Exception during TextRenderer initialization: {}", e.getMessage());
|
|||
|
|
if (charData != null) {
|
|||
|
|
charData.free();
|
|||
|
|
charData = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
shader.stop();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private int createTextureFromBitmap(int width, int height, ByteBuffer pixels) {
|
|||
|
|
RenderSystem.assertOnRenderThread();
|
|||
|
|
|
|||
|
|
int textureId = RenderSystem.genTextures();
|
|||
|
|
RenderSystem.bindTexture(textureId);
|
|||
|
|
|
|||
|
|
// 使用更兼容的纹理格式
|
|||
|
|
RenderSystem.texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_ALPHA,
|
|||
|
|
width, height, 0, GL11.GL_ALPHA, GL11.GL_UNSIGNED_BYTE, pixels);
|
|||
|
|
|
|||
|
|
RenderSystem.setTextureMinFilter(GL11.GL_LINEAR);
|
|||
|
|
RenderSystem.setTextureMagFilter(GL11.GL_LINEAR);
|
|||
|
|
RenderSystem.setTextureWrapS(GL12.GL_CLAMP_TO_EDGE);
|
|||
|
|
RenderSystem.setTextureWrapT(GL12.GL_CLAMP_TO_EDGE);
|
|||
|
|
|
|||
|
|
RenderSystem.bindTexture(0);
|
|||
|
|
return textureId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 渲染文字(使用 RenderSystem 封装,不使用 glBegin/glEnd)
|
|||
|
|
*
|
|||
|
|
* @param text 要显示的文字
|
|||
|
|
* @param x 世界坐标 X
|
|||
|
|
* @param y 世界坐标 Y
|
|||
|
|
* @param color 文字颜色
|
|||
|
|
*/
|
|||
|
|
public void renderText(String text, float x, float y, Vector4f color) {
|
|||
|
|
if (!initialized || text == null || text.isEmpty()) return;
|
|||
|
|
|
|||
|
|
RenderSystem.assertOnRenderThread();
|
|||
|
|
|
|||
|
|
// 保存当前状态
|
|||
|
|
RenderSystem.pushState();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 检查文本着色器是否存在,如果不存在则创建默认的
|
|||
|
|
ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader");
|
|||
|
|
|
|||
|
|
shader.use();
|
|||
|
|
ShaderManagement.setUniformVec4(shader, "uColor", color);
|
|||
|
|
ShaderManagement.setUniformInt(shader, "uTexture", 0);
|
|||
|
|
|
|||
|
|
RenderSystem.enableBlend();
|
|||
|
|
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
|||
|
|
RenderSystem.disableDepthTest();
|
|||
|
|
|
|||
|
|
RenderSystem.activeTexture(GL13.GL_TEXTURE0);
|
|||
|
|
RenderSystem.bindTexture(fontTextureId);
|
|||
|
|
|
|||
|
|
Vector2f offset = ModelRender.getCameraOffset();
|
|||
|
|
float px = x + offset.x;
|
|||
|
|
float py = y + offset.y;
|
|||
|
|
|
|||
|
|
try (MemoryStack stack = MemoryStack.stackPush()) {
|
|||
|
|
STBTTAlignedQuad q = STBTTAlignedQuad.mallocStack(stack);
|
|||
|
|
float[] xpos = {px};
|
|||
|
|
float[] ypos = {py};
|
|||
|
|
|
|||
|
|
Tesselator tesselator = Tesselator.getInstance();
|
|||
|
|
BufferBuilder builder = tesselator.getBuilder();
|
|||
|
|
|
|||
|
|
// 计算估计的顶点数量:每个字符6个顶点(2个三角形)
|
|||
|
|
int estimatedVertexCount = text.length() * 6;
|
|||
|
|
|
|||
|
|
// 修复:begin方法需要2个参数
|
|||
|
|
builder.begin(RenderSystem.DRAW_TRIANGLES, estimatedVertexCount);
|
|||
|
|
|
|||
|
|
for (int i = 0; i < text.length(); i++) {
|
|||
|
|
char c = text.charAt(i);
|
|||
|
|
if (c < firstChar || c >= firstChar + charCount) continue;
|
|||
|
|
|
|||
|
|
stbtt_GetBakedQuad(charData, bitmapWidth, bitmapHeight, c - firstChar, xpos, ypos, q, true);
|
|||
|
|
|
|||
|
|
// 使用两个三角形组成一个四边形
|
|||
|
|
// 第一个三角形
|
|||
|
|
builder.vertex(q.x0(), q.y0(), q.s0(), q.t0());
|
|||
|
|
builder.vertex(q.x1(), q.y0(), q.s1(), q.t0());
|
|||
|
|
builder.vertex(q.x0(), q.y1(), q.s0(), q.t1());
|
|||
|
|
|
|||
|
|
// 第二个三角形
|
|||
|
|
builder.vertex(q.x1(), q.y0(), q.s1(), q.t0());
|
|||
|
|
builder.vertex(q.x1(), q.y1(), q.s1(), q.t1());
|
|||
|
|
builder.vertex(q.x0(), q.y1(), q.s0(), q.t1());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tesselator.end();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RenderSystem.checkGLError("renderText");
|
|||
|
|
|
|||
|
|
} finally {
|
|||
|
|
// 恢复之前的状态
|
|||
|
|
RenderSystem.popState();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理字体资源
|
|||
|
|
*/
|
|||
|
|
public void cleanup() {
|
|||
|
|
if (fontTextureId != 0) {
|
|||
|
|
RenderSystem.deleteTextures(fontTextureId);
|
|||
|
|
fontTextureId = 0;
|
|||
|
|
}
|
|||
|
|
if (charData != null) {
|
|||
|
|
charData.free();
|
|||
|
|
charData = null;
|
|||
|
|
}
|
|||
|
|
initialized = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public boolean isInitialized() {
|
|||
|
|
return initialized;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int getFontTextureId() {
|
|||
|
|
return fontTextureId;
|
|||
|
|
}
|
|||
|
|
}
|