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.Vector4f; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL33; 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.stbtt_BakeFontBitmap; import static org.lwjgl.stb.STBTruetype.stbtt_GetBakedQuad; /** * 支持 ASCII + 中文的 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 asciiCharData; private STBTTBakedChar.Buffer chineseCharData; private int asciiTextureId; private int chineseTextureId; private boolean initialized = false; // 中文字符起始编码(选择一个不冲突的范围) private static final int CHINESE_FIRST_CHAR = 0x4E00; // CJK Unified Ideographs 常用汉字起始范围 private static final int CHINESE_CHAR_COUNT = 20000; public TextRenderer(int bitmapWidth, int bitmapHeight, int firstChar, int charCount) { this.bitmapWidth = bitmapWidth; this.bitmapHeight = bitmapHeight; this.firstChar = firstChar; this.charCount = charCount; } /** * 初始化字体渲染器 */ public void initialize(ByteBuffer fontData, float fontHeight) { if (initialized) { logger.warn("TextRenderer already initialized"); return; } if (fontData == null || fontData.capacity() == 0 || fontHeight <= 0) { logger.error("Invalid font data or font height"); return; } ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader"); if (shader == null) { logger.error("TextShader not found"); return; } shader.use(); try { asciiCharData = STBTTBakedChar.malloc(charCount); ByteBuffer asciiBitmap = ByteBuffer.allocateDirect(bitmapWidth * bitmapHeight); int asciiRes = stbtt_BakeFontBitmap(fontData, fontHeight, asciiBitmap, bitmapWidth, bitmapHeight, firstChar, asciiCharData); if (asciiRes <= 0) { logger.error("ASCII font bake failed, result: {}", asciiRes); cleanup(); return; } asciiTextureId = createTextureFromBitmap(bitmapWidth, bitmapHeight, asciiBitmap); if (asciiTextureId == 0) { logger.error("Failed to create ASCII texture"); cleanup(); return; } // 烘焙中文 - 使用更大的纹理和正确的字符范围 int chineseTexSize = 4096; // 中文字符需要更大的纹理 // 分配足够的空间来存储 CHINESE_CHAR_COUNT 个字符的数据 chineseCharData = STBTTBakedChar.malloc(CHINESE_CHAR_COUNT); ByteBuffer chineseBitmap = ByteBuffer.allocateDirect(chineseTexSize * chineseTexSize); // 关键:烘焙从 CHINESE_FIRST_CHAR 开始的 CHINESE_CHAR_COUNT 个连续字符 int chineseRes = stbtt_BakeFontBitmap(fontData, fontHeight, chineseBitmap, chineseTexSize, chineseTexSize, CHINESE_FIRST_CHAR, chineseCharData); if (chineseRes <= 0) { logger.error("Chinese font bake failed, result: {}", chineseRes); cleanup(); return; } chineseTextureId = createTextureFromBitmap(chineseTexSize, chineseTexSize, chineseBitmap); if (chineseTextureId == 0) { logger.error("Failed to create Chinese texture"); cleanup(); return; } initialized = true; logger.debug("TextRenderer initialized, ASCII tex={}, Chinese tex={}", asciiTextureId, chineseTextureId); } catch (Exception e) { logger.error("Exception during TextRenderer init: {}", e.getMessage(), e); cleanup(); } finally { shader.stop(); } } /** * 渲染文字 */ public void renderText(String text, float x, float y, Vector4f color) { renderText(text, x, y, color, 1.0f); } /** * 获取一行文字的宽度(单位:像素) */ public float getTextWidth(String text) { return getTextWidth(text, 1.0f); } /** * 获取一行文字的宽度(带缩放) */ public float getTextWidth(String text, float scale) { if (!initialized || text == null || text.isEmpty()) return 0f; float width = 0f; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if (c >= firstChar && c < firstChar + charCount) { STBTTBakedChar bakedChar = asciiCharData.get(c - firstChar); width += bakedChar.xadvance() * scale; } else { // 修复中文索引逻辑:检查字符是否在烘焙的连续范围内 if (c >= CHINESE_FIRST_CHAR && c < CHINESE_FIRST_CHAR + CHINESE_CHAR_COUNT) { int idx = c - CHINESE_FIRST_CHAR; // 关键:使用 Unicode 差值作为索引 STBTTBakedChar bakedChar = chineseCharData.get(idx); width += bakedChar.xadvance() * scale; } else { // 对于未找到的字符,使用空格宽度 width += 0.5f * scale; // 估计值 } } } return width; } public void renderText(String text, float x, float y, Vector4f color, float scale) { if (!initialized || text == null || text.isEmpty()) return; if (scale <= 0f) scale = 1.0f; RenderSystem.assertOnRenderThread(); RenderSystem.pushState(); try { ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader"); if (shader == null) { logger.error("TextShader not found"); return; } 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(); try (MemoryStack stack = MemoryStack.stackPush()) { STBTTAlignedQuad q = STBTTAlignedQuad.malloc(stack); float[] xpos = {x}; float[] ypos = {y}; Tesselator t = Tesselator.getInstance(); BufferBuilder builder = t.getBuilder(); // 按字符类型分组渲染以减少纹理切换 int currentTexture = -1; boolean batchStarted = false; builder.setColor(color); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); int targetTexture; STBTTBakedChar.Buffer charBuffer; int texWidth, texHeight; if (c >= firstChar && c < firstChar + charCount) { targetTexture = asciiTextureId; charBuffer = asciiCharData; texWidth = bitmapWidth; texHeight = bitmapHeight; stbtt_GetBakedQuad(charBuffer, texWidth, texHeight, c - firstChar, xpos, ypos, q, true); } else { // 修复中文索引逻辑:检查字符是否在烘焙的连续范围内 if (c >= CHINESE_FIRST_CHAR && c < CHINESE_FIRST_CHAR + CHINESE_CHAR_COUNT) { targetTexture = chineseTextureId; charBuffer = chineseCharData; texWidth = 4096; texHeight = 4096; // 关键修复:索引是字符的 Unicode 减去起始 Unicode int idx = c - CHINESE_FIRST_CHAR; stbtt_GetBakedQuad(charBuffer, texWidth, texHeight, idx, xpos, ypos, q, true); } else { continue; // 跳过不支持的字符 } } // 如果纹理改变,结束当前批次 if (targetTexture != currentTexture) { if (batchStarted) { t.end(); batchStarted = false; } RenderSystem.bindTexture(targetTexture); currentTexture = targetTexture; } // 开始新批次(如果需要) if (!batchStarted) { builder.begin(RenderSystem.DRAW_TRIANGLES, (text.length() - i) * 6); batchStarted = true; } // 应用缩放并计算顶点 float sx0 = x + (q.x0() - x) * scale; float sx1 = x + (q.x1() - x) * scale; float sy0 = y + (q.y0() - y) * scale; float sy1 = y + (q.y1() - y) * scale; builder.vertex(sx0, sy0, q.s0(), q.t0()); builder.vertex(sx1, sy0, q.s1(), q.t0()); builder.vertex(sx0, sy1, q.s0(), q.t1()); builder.vertex(sx1, sy0, q.s1(), q.t0()); builder.vertex(sx1, sy1, q.s1(), q.t1()); builder.vertex(sx0, sy1, q.s0(), q.t1()); } // 结束最后一个批次 if (batchStarted) { t.end(); } } } catch (Exception e) { logger.error("Error rendering text: {}", e.getMessage(), e); } finally { RenderSystem.popState(); } } private int createTextureFromBitmap(int width, int height, ByteBuffer pixels) { RenderSystem.assertOnRenderThread(); try { int textureId = RenderSystem.genTextures(); RenderSystem.bindTexture(textureId); RenderSystem.pixelStore(GL11.GL_UNPACK_ALIGNMENT, 1); RenderSystem.texImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL11.GL_RED, 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); // 设置纹理swizzle以便单通道纹理在着色器中显示为白色 RenderSystem.texParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_RED); RenderSystem.texParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_G, GL11.GL_RED); RenderSystem.texParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_B, GL11.GL_RED); RenderSystem.texParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_A, GL11.GL_RED); RenderSystem.pixelStore(GL11.GL_UNPACK_ALIGNMENT, 4); RenderSystem.bindTexture(0); return textureId; } catch (Exception e) { logger.error("Failed to create texture from bitmap: {}", e.getMessage(), e); return 0; } } public void cleanup() { RenderSystem.assertOnRenderThread(); if (asciiTextureId != 0) { RenderSystem.deleteTextures(asciiTextureId); asciiTextureId = 0; } if (chineseTextureId != 0) { RenderSystem.deleteTextures(chineseTextureId); chineseTextureId = 0; } if (asciiCharData != null) { asciiCharData.free(); asciiCharData = null; } if (chineseCharData != null) { chineseCharData.free(); chineseCharData = null; } initialized = false; logger.debug("TextRenderer cleaned up"); } public boolean isInitialized() { return initialized; } public int getAsciiTextureId() { return asciiTextureId; } public int getChineseTextureId() { return chineseTextureId; } }