diff --git a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala index 2a524bea1b..199a4f5883 100644 --- a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala +++ b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala @@ -16,9 +16,8 @@ import net.minecraft.tileentity.TileEntity import org.lwjgl.opengl.GL11 object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEntity, Int] { - val renderer = - if (Settings.get.fontRenderer == "texture") new font.StaticFontRenderer() - else new font.DynamicFontRenderer() + + final val renderer = new font.TextureFontRenderer() private val cache = com.google.common.cache.CacheBuilder.newBuilder(). expireAfterAccess(2, TimeUnit.SECONDS). @@ -26,54 +25,13 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti asInstanceOf[CacheBuilder[TextBufferRenderData, Int]]. build[TextBufferRenderData, Int]() - // To allow access in cache entry init. - private var currentBuffer: TextBufferRenderData = _ - // ----------------------------------------------------------------------- // // Rendering // ----------------------------------------------------------------------- // def render(buffer: TextBufferRenderData) { - currentBuffer = buffer - compileOrDraw(cache.get(currentBuffer, this)) - } - - private def compileOrDraw(list: Int) = { - if (currentBuffer.dirty) { - RenderState.checkError(getClass.getName + ".compileOrDraw: entering (aka: wasntme)") - - for (line <- currentBuffer.data.buffer) { - renderer.generateChars(line) - } - - val doCompile = !RenderState.compilingDisplayList - if (doCompile) { - currentBuffer.dirty = false - GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE) - - RenderState.checkError(getClass.getName + ".compileOrDraw: glNewList") - } - - renderer.drawBuffer(currentBuffer.data, currentBuffer.viewport._1, currentBuffer.viewport._2) - - RenderState.checkError(getClass.getName + ".compileOrDraw: drawString") - - if (doCompile) { - GL11.glEndList() - - RenderState.checkError(getClass.getName + ".compileOrDraw: glEndList") - - } - - RenderState.checkError(getClass.getName + ".compileOrDraw: leaving") - - true - } - else { - GL11.glCallList(list) - - RenderState.checkError(getClass.getName + ".compileOrDraw: glCallList") - } + var reRender = cache.getIfPresent(buffer) == null + renderer.drawBuffer(buffer.data, buffer.viewport._1, buffer.viewport._2, cache.get(buffer, this), reRender) } // ----------------------------------------------------------------------- // @@ -82,19 +40,19 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti def call = { RenderState.checkError(getClass.getName + ".call: entering (aka: wasntme)") - - val list = GLAllocation.generateDisplayLists(1) - currentBuffer.dirty = true // Force compilation. + + var texID = font.TextureFontRenderer.createTexture RenderState.checkError(getClass.getName + ".call: leaving") - - list + + texID } + def onRemoval(e: RemovalNotification[TileEntity, Int]) { RenderState.checkError(getClass.getName + ".onRemoval: entering (aka: wasntme)") - GLAllocation.deleteDisplayLists(e.getValue) + GL11.glDeleteTextures(e.getValue) RenderState.checkError(getClass.getName + ".onRemoval: leaving") } diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala deleted file mode 100644 index 0d3df8a697..0000000000 --- a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala +++ /dev/null @@ -1,163 +0,0 @@ -package li.cil.oc.client.renderer.font - -import li.cil.oc.Settings -import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture -import li.cil.oc.util.FontUtils -import li.cil.oc.util.RenderState -import net.minecraft.client.Minecraft -import net.minecraft.client.resources.IReloadableResourceManager -import net.minecraft.client.resources.IResourceManager -import net.minecraft.client.resources.IResourceManagerReloadListener -import org.lwjgl.BufferUtils -import org.lwjgl.opengl._ - -import scala.collection.mutable - -/** - * Font renderer that dynamically generates lookup textures by rendering a font - * to it. It's pretty broken right now, and font rendering looks crappy as hell. - */ -class DynamicFontRenderer extends TextureFontRenderer with IResourceManagerReloadListener { - private val glyphProvider: IGlyphProvider = Settings.get.fontRenderer match { - case _ => new FontParserHex() - } - - private val textures = mutable.ArrayBuffer.empty[CharTexture] - - private val charMap = mutable.Map.empty[Char, DynamicFontRenderer.CharIcon] - - private var activeTexture: CharTexture = _ - - initialize() - - Minecraft.getMinecraft.getResourceManager match { - case reloadable: IReloadableResourceManager => reloadable.registerReloadListener(this) - case _ => - } - - def initialize() { - for (texture <- textures) { - texture.delete() - } - textures.clear() - charMap.clear() - textures += new DynamicFontRenderer.CharTexture(this) - activeTexture = textures.head - generateChars(basicChars.toCharArray) - } - - def onResourceManagerReload(manager: IResourceManager) { - glyphProvider.initialize() - initialize() - } - - override protected def charWidth = glyphProvider.getGlyphWidth - - override protected def charHeight = glyphProvider.getGlyphHeight - - override protected def textureCount = textures.length - - override protected def bindTexture(index: Int) { - activeTexture = textures(index) - activeTexture.bind() - RenderState.checkError(getClass.getName + ".bindTexture") - } - - override protected def generateChar(char: Char) { - charMap.getOrElseUpdate(char, createCharIcon(char)) - } - - override protected def drawChar(tx: Float, ty: Float, char: Char) { - charMap.get(char) match { - case Some(icon) if icon.texture == activeTexture => icon.draw(tx, ty) - case _ => - } - } - - private def createCharIcon(char: Char): DynamicFontRenderer.CharIcon = { - if (FontUtils.wcwidth(char) < 1 || glyphProvider.getGlyph(char) == null) { - if (char == '?') null - else charMap.getOrElseUpdate('?', createCharIcon('?')) - } - else { - if (textures.last.isFull(char)) { - textures += new DynamicFontRenderer.CharTexture(this) - textures.last.bind() - } - textures.last.add(char) - } - } -} - -object DynamicFontRenderer { - private val size = 256 - - class CharTexture(val owner: DynamicFontRenderer) { - private val id = GL11.glGenTextures() - GL11.glBindTexture(GL11.GL_TEXTURE_2D, id) - if (Settings.get.textLinearFiltering) { - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR) - } else { - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) - } - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) - GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(size * size * 4)) - - RenderState.checkError(getClass.getName + ".: create texture") - - // Some padding to avoid bleeding. - private val cellWidth = owner.charWidth + 2 - private val cellHeight = owner.charHeight + 2 - private val cols = size / cellWidth - private val rows = size / cellHeight - private val uStep = cellWidth / size.toDouble - private val vStep = cellHeight / size.toDouble - private val pad = 1.0 / size - private val capacity = cols * rows - - private var chars = 0 - - def delete() { - GL11.glDeleteTextures(id) - } - - def bind() { - GL11.glBindTexture(GL11.GL_TEXTURE_2D, id) - } - - def isFull(char: Char) = chars + FontUtils.wcwidth(char) > capacity - - def add(char: Char) = { - val glyphWidth = FontUtils.wcwidth(char) - val w = owner.charWidth * glyphWidth - val h = owner.charHeight - // Force line break if we have a char that's wider than what space remains in this row. - if (chars % cols + glyphWidth > cols) { - chars += 1 - } - val x = chars % cols - val y = chars / cols - - GL11.glBindTexture(GL11.GL_TEXTURE_2D, id) - GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, owner.glyphProvider.getGlyph(char)) - - chars += glyphWidth - - new CharIcon(this, w, h, pad + x * uStep, pad + y * vStep, (x + glyphWidth) * uStep - pad, (y + 1) * vStep - pad) - } - } - - class CharIcon(val texture: CharTexture, val w: Int, val h: Int, val u1: Double, val v1: Double, val u2: Double, val v2: Double) { - def draw(tx: Float, ty: Float) { - GL11.glTexCoord2d(u1, v2) - GL11.glVertex2f(tx, ty + h) - GL11.glTexCoord2d(u2, v2) - GL11.glVertex2f(tx + w, ty + h) - GL11.glTexCoord2d(u2, v1) - GL11.glVertex2f(tx + w, ty) - GL11.glTexCoord2d(u1, v1) - GL11.glVertex2f(tx, ty) - } - } - -} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/font/FontParserHex.java b/src/main/scala/li/cil/oc/client/renderer/font/FontParserHex.java index 78eee08440..0c28dc5472 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/FontParserHex.java +++ b/src/main/scala/li/cil/oc/client/renderer/font/FontParserHex.java @@ -70,6 +70,7 @@ public ByteBuffer getGlyph(int charCode) { if (charCode < 0 || charCode >= glyphs.length || glyphs[charCode] == null || glyphs[charCode].length == 0) return null; final byte[] glyph = glyphs[charCode]; + final ByteBuffer buffer = BufferUtils.createByteBuffer(glyph.length * getGlyphWidth() * 4); for (byte aGlyph : glyph) { int c = ((int) aGlyph) & 0xFF; @@ -84,6 +85,38 @@ public ByteBuffer getGlyph(int charCode) { buffer.rewind(); return buffer; } + + @Override + public ByteBuffer getGlyph(int charCode, int color, int bg, ByteBuffer buffer, int stride) { + if (charCode < 0 || charCode >= glyphs.length || glyphs[charCode] == null || glyphs[charCode].length == 0) + return null; + + final byte[] cdata = {(byte) (((color & 0xFF0000) >> 16)), (byte) (((color & 0x00FF00) >> 8)), (byte) (((color & 0x0000FF) >> 0)), (byte) 255}; + final byte[] bdata = {(byte) (((bg & 0xFF0000) >> 16)), (byte) (((bg & 0x00FF00) >> 8)), (byte) (((bg & 0x0000FF) >> 0)), (byte) 255}; + final byte[] glyph = glyphs[charCode]; + boolean first = true; + int width = FontUtils.wcwidth(charCode); + for (int i = 0; i < glyph.length; i+=width) { + if (first) { + first = false; + } else { + buffer.position(buffer.position() + stride * 4); + } + + for (int k = 0; k < width; k++) { + byte aGlyph = glyph[i + k]; + int c = ((int) aGlyph) & 0xFF; + // Grab all bits by grabbing the leftmost one then shifting. + for (int j = 0; j < 8; j++) { + final boolean isBitSet = (c & 0x80) > 0; + if (isBitSet) buffer.put(cdata); + else buffer.put(bdata); + c <<= 1; + } + } + } + return null; + } @Override public int getGlyphWidth() { diff --git a/src/main/scala/li/cil/oc/client/renderer/font/IGlyphProvider.java b/src/main/scala/li/cil/oc/client/renderer/font/IGlyphProvider.java index 230b2bab45..a4d0d2598f 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/IGlyphProvider.java +++ b/src/main/scala/li/cil/oc/client/renderer/font/IGlyphProvider.java @@ -2,6 +2,8 @@ import java.nio.ByteBuffer; +import li.cil.oc.util.PackedColor; + /** * Common interface for classes providing glyph data in a format that can be * rendered using the {@link li.cil.oc.client.renderer.font.DynamicFontRenderer}. @@ -33,6 +35,8 @@ public interface IGlyphProvider { * @see FontParserHex#getGlyph(int) See the hexfont parser for a reference implementation. */ ByteBuffer getGlyph(int charCode); + + public ByteBuffer getGlyph(int charCode, int color, int bg, ByteBuffer buffer, int stride); /** * Get the single-width glyph width for this provider, in pixels. diff --git a/src/main/scala/li/cil/oc/client/renderer/font/StaticFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/StaticFontRenderer.scala deleted file mode 100644 index 3a1944b031..0000000000 --- a/src/main/scala/li/cil/oc/client/renderer/font/StaticFontRenderer.scala +++ /dev/null @@ -1,73 +0,0 @@ -package li.cil.oc.client.renderer.font - -import com.google.common.base.Charsets -import li.cil.oc.OpenComputers -import li.cil.oc.Settings -import li.cil.oc.client.Textures -import net.minecraft.client.Minecraft -import net.minecraft.util.ResourceLocation -import org.lwjgl.opengl.GL11 - -import scala.io.Source - -/** - * Font renderer using a user specified texture file, meaning the list of - * supported characters is fixed. But at least this one works. - */ -class StaticFontRenderer extends TextureFontRenderer { - protected val (chars, charWidth, charHeight) = try { - val lines = Source.fromInputStream(Minecraft.getMinecraft.getResourceManager.getResource(new ResourceLocation(Settings.resourceDomain, "textures/font/chars.txt")).getInputStream)(Charsets.UTF_8).getLines() - val chars = lines.next() - val (w, h) = if (lines.hasNext) { - val size = lines.next().split(" ", 2) - (size(0).toInt, size(1).toInt) - } else (10, 18) - (chars, w, h) - } - catch { - case t: Throwable => - OpenComputers.log.warn("Failed reading font metadata, using defaults.", t) - (basicChars, 10, 18) - } - - private val cols = 256 / charWidth - private val uStep = charWidth / 256.0 - private val uSize = uStep - private val vStep = (charHeight + 1) / 256.0 - private val vSize = charHeight / 256.0 - private val s = Settings.get.fontCharScale - private val dw = charWidth * s - charWidth - private val dh = charHeight * s - charHeight - - override protected def textureCount = 1 - - override protected def bindTexture(index: Int) { - if (Settings.get.textAntiAlias) { - Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.fontAntiAliased) - } - else { - Minecraft.getMinecraft.getTextureManager.bindTexture(Textures.fontAliased) - } - } - - override protected def drawChar(tx: Float, ty: Float, char: Char) { - val index = 1 + (chars.indexOf(char) match { - case -1 => chars.indexOf('?') - case i => i - }) - val x = (index - 1) % cols - val y = (index - 1) / cols - val u = x * uStep - val v = y * vStep - GL11.glTexCoord2d(u, v + vSize) - GL11.glVertex3d(tx - dw, ty + charHeight * s, 0) - GL11.glTexCoord2d(u + uSize, v + vSize) - GL11.glVertex3d(tx + charWidth * s, ty + charHeight * s, 0) - GL11.glTexCoord2d(u + uSize, v) - GL11.glVertex3d(tx + charWidth * s, ty - dh, 0) - GL11.glTexCoord2d(u, v) - GL11.glVertex3d(tx - dw, ty - dh, 0) - } - - override protected def generateChar(char: Char) {} -} diff --git a/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala index 97c290a2a7..b555088793 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala @@ -1,108 +1,178 @@ package li.cil.oc.client.renderer.font import li.cil.oc.Settings +import li.cil.oc.OpenComputers import li.cil.oc.util.PackedColor import li.cil.oc.util.RenderState import li.cil.oc.util.TextBuffer +import li.cil.oc.util.FontUtils +import org.lwjgl.BufferUtils import org.lwjgl.opengl.GL11 -/** - * Base class for texture based font rendering. - * - * Provides common logic for the static one (using an existing texture) and the - * dynamic one (generating textures on the fly from a font). - */ -abstract class TextureFontRenderer { - protected final val basicChars = """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■""" - - def charRenderWidth = charWidth / 2 - - def charRenderHeight = charHeight / 2 - - /** - * If drawString() is called inside display lists this should be called - * beforehand, outside the display list, to ensure no characters have to - * be generated inside the draw call. - */ - def generateChars(chars: Array[Char]) { - for (char <- chars) { - generateChar(char) +object TextureFontRenderer { + private val maxCharsRes = math.max(Settings.screenResolutionsByTier.last._1, Settings.screenResolutionsByTier.last._2) + + private val _glyphProvider: IGlyphProvider = Settings.get.fontRenderer match { + case _ => new FontParserHex() + } + + _glyphProvider.initialize() + + //val texMaxPxWidth = maxCharsRes * _glyphProvider.getGlyphWidth + //val texMaxPxHeight = maxCharsRes * _glyphProvider.getGlyphHeight + + + def createTexture(): Int = { + var texID = GL11.glGenTextures() + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texID) + + if (Settings.get.textLinearFiltering) { + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR) + } else { + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) } + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) + + + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, _glyphProvider.getGlyphWidth, _glyphProvider.getGlyphHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(_glyphProvider.getGlyphWidth * _glyphProvider.getGlyphHeight * 4)) + + texID } +} + +class TextureFontRenderer { + protected final val basicChars = """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ """ + + protected final val glyphProvider = TextureFontRenderer._glyphProvider + final val charRenderWidth = glyphProvider.getGlyphWidth + final val charRenderHeight = glyphProvider.getGlyphHeight + + private final val staticbuff = BufferUtils.createByteBuffer(TextureFontRenderer.maxCharsRes * charRenderWidth * 4 * charRenderHeight); + - def drawBuffer(buffer: TextBuffer, viewportWidth: Int, viewportHeight: Int) { + def drawBuffer(buffer: TextBuffer, viewportWidth: Int, viewportHeight: Int, texID: Int, forceRefresh: Boolean) { val format = buffer.format GL11.glPushMatrix() GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) - GL11.glScalef(0.5f, 0.5f, 1) + //GlStateManager.scale(0.5f, 0.5f, 1) GL11.glDepthMask(false) - GL11.glDisable(GL11.GL_TEXTURE_2D) + RenderState.makeItBlend() + GL11.glEnable(GL11.GL_TEXTURE_2D) + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, texID) RenderState.checkError(getClass.getName + ".drawBuffer: configure state") - - // Background first. We try to merge adjacent backgrounds of the same - // color to reduce the number of quads we have to draw. - GL11.glBegin(GL11.GL_QUADS) - for (y <- 0 until (viewportHeight min buffer.height)) { - val color = buffer.color(y) - var cbg = 0x000000 - var x = 0 - var width = 0 - for (col <- color.map(PackedColor.unpackBackground(_, format)) if x + width < viewportWidth) { - if (col != cbg) { - drawQuad(cbg, x, y, width) - cbg = col - x += width - width = 0 - } - width = width + 1 - } - drawQuad(cbg, x, y, width) + + // Create texture if it does not exist + var texCharsWidth = viewportWidth + var texCharsHeight = viewportHeight max buffer.height + var texPxWidth = viewportWidth * charRenderWidth + var texPxHeight = texCharsHeight * charRenderHeight + + + var currentPxWidth = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH) + var currentPxHeight = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT) + if (texPxWidth != currentPxWidth || texPxHeight != currentPxHeight) { + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, texPxWidth, texPxHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(texPxWidth * texPxHeight * 4)) } - GL11.glEnd() - - RenderState.checkError(getClass.getName + ".drawBuffer: background") - - GL11.glEnable(GL11.GL_TEXTURE_2D) + if (Settings.get.textLinearFiltering) { GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR) } - - // Foreground second. We only have to flush when the color changes, so - // unless every char has a different color this should be quite efficient. + for (y <- 0 until (viewportHeight min buffer.height)) { val line = buffer.buffer(y) val color = buffer.color(y) - val ty = y * charHeight - for (i <- 0 until textureCount) { - bindTexture(i) - GL11.glBegin(GL11.GL_QUADS) - var cfg = -1 - var tx = 0f - for (n <- 0 until viewportWidth) { - val ch = line(n) - val col = PackedColor.unpackForeground(color(n), format) - // Check if color changed. - if (col != cfg) { - cfg = col - GL11.glColor3ub( - ((cfg & 0xFF0000) >> 16).toByte, - ((cfg & 0x00FF00) >> 8).toByte, - ((cfg & 0x0000FF) >> 0).toByte) - } - // Don't render whitespace. - if (ch != ' ') { - drawChar(tx, ty, ch) + val dirty = buffer.dirty(y) + + var ty = y * charRenderHeight + var tx = 0 + + var backlogData = new Array[Int](viewportWidth) + var backlogPx = -1 + var backlogSize = 0 + var backlogWidth = 0 + + var flush = () => { + var pxStrideOffset = 0 + + for (backlogIdx <- 0 until backlogSize) { + val lineIdx = backlogData(backlogIdx) + val char = line(lineIdx); + val charWidth = FontUtils.wcwidth(char) * charRenderWidth + + val col = PackedColor.unpackForeground(color(lineIdx), format) + val back = PackedColor.unpackBackground(color(lineIdx), format) + + staticbuff.position(pxStrideOffset * 4) + glyphProvider.getGlyph(char, col, back, staticbuff, backlogWidth - charWidth) + + pxStrideOffset += charWidth + } + + staticbuff.position(0) + GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, backlogPx, ty, backlogWidth, charRenderHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, staticbuff) + + backlogPx = -1 + backlogSize = 0 + backlogWidth = 0 + } + + var n = 0 + while (n < viewportWidth) { + val nChar = line(n) + val nCharWidth = FontUtils.wcwidth(nChar) + val nCharWidthPx = nCharWidth * charRenderWidth; + + if (dirty(n) || forceRefresh) { + dirty(n) = false + + backlogData(backlogSize) = n + backlogSize += 1 + backlogWidth += nCharWidthPx + + if (backlogPx == -1) { + backlogPx = tx } - tx += charWidth + + } else if (backlogSize != 0) { + // this one is not dirty, but the last one was + flush() } - GL11.glEnd() + tx += nCharWidthPx; + + // OC represents wide chars as a wide char followed by a space + // The wide char will render over into that space so we can skip the next char + n += nCharWidth; } + if (backlogSize != 0) { + flush() + } } + + GL11.glBegin(GL11.GL_QUADS) + var tx = 0 + var ty = 0 + var h = (viewportHeight min buffer.height) * charRenderHeight + var w = viewportWidth * charRenderWidth + var u1 = 0 + var u2 = w.floatValue() / texPxWidth + var v1 = 0 + var v2 = h.floatValue() / texPxHeight + GL11.glTexCoord2d(u1, v2) + GL11.glVertex2f(tx, ty + h) + GL11.glTexCoord2d(u2, v2) + GL11.glVertex2f(tx + w, ty + h) + GL11.glTexCoord2d(u2, v1) + GL11.glVertex2f(tx + w, ty) + GL11.glTexCoord2d(u1, v1) + GL11.glVertex2f(tx, ty) + GL11.glEnd() RenderState.checkError(getClass.getName + ".drawBuffer: foreground") @@ -111,55 +181,4 @@ abstract class TextureFontRenderer { RenderState.checkError(getClass.getName + ".drawBuffer: leaving") } - - def drawString(s: String, x: Int, y: Int): Unit = { - GL11.glPushMatrix() - GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) - - GL11.glTranslatef(x, y, 0) - GL11.glScalef(0.5f, 0.5f, 1) - GL11.glDepthMask(false) - - for (i <- 0 until textureCount) { - bindTexture(i) - GL11.glBegin(GL11.GL_QUADS) - var tx = 0f - for (n <- 0 until s.length) { - val ch = s.charAt(n) - // Don't render whitespace. - if (ch != ' ') { - drawChar(tx, 0, ch) - } - tx += charWidth - } - GL11.glEnd() - } - - GL11.glPopAttrib() - GL11.glPopMatrix() - } - - protected def charWidth: Int - - protected def charHeight: Int - - protected def textureCount: Int - - protected def bindTexture(index: Int): Unit - - protected def generateChar(char: Char): Unit - - protected def drawChar(tx: Float, ty: Float, char: Char): Unit - - private def drawQuad(color: Int, x: Int, y: Int, width: Int) = if (color != 0 && width > 0) { - val x0 = x * charWidth - val x1 = (x + width) * charWidth - val y0 = y * charHeight - val y1 = (y + 1) * charHeight - GL11.glColor3ub(((color >> 16) & 0xFF).toByte, ((color >> 8) & 0xFF).toByte, (color & 0xFF).toByte) - GL11.glVertex3d(x0, y1, 0) - GL11.glVertex3d(x1, y1, 0) - GL11.glVertex3d(x1, y0, 0) - GL11.glVertex3d(x0, y0, 0) - } } diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala index 5356e55e40..25ad39bf60 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala @@ -7,8 +7,6 @@ import org.lwjgl.opengl.GL11 private[markdown] class CodeSegment(val parent: Segment, val text: String) extends BasicTextSegment { override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { - TextBufferRenderCache.renderer.generateChars(text.toCharArray) - var currentX = x + indent var currentY = y var chars = text @@ -17,7 +15,7 @@ private[markdown] class CodeSegment(val parent: Segment, val text: String) exten while (chars.length > 0) { val part = chars.take(numChars) GL11.glColor4f(0.75f, 0.8f, 1, 1) - TextBufferRenderCache.renderer.drawString(part, currentX, currentY) + // TODO TextBufferRenderCache.renderer.drawString(part, currentX, currentY) currentX = x + wrapIndent currentY += lineHeight(renderer) chars = chars.drop(numChars).dropWhile(_.isWhitespace) diff --git a/src/main/scala/li/cil/oc/util/TextBuffer.scala b/src/main/scala/li/cil/oc/util/TextBuffer.scala index 31503ae80c..0991762a99 100644 --- a/src/main/scala/li/cil/oc/util/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/util/TextBuffer.scala @@ -65,6 +65,8 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col var color = Array.fill(height, width)(packed) var buffer = Array.fill(height, width)(' ') + + var dirty = Array.fill(height, width)(true) /** The current buffer size in columns by rows. */ def size = (width, height) @@ -88,6 +90,7 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col }) buffer = newBuffer color = newColor + dirty = Array.fill(h, w)(true) width = w height = h true @@ -111,9 +114,10 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col for (y <- row until math.min(row + s.length, height)) if (y >= 0) { val line = buffer(y) val lineColor = color(y) + val lineDirty = dirty(y) val c = s(y - row) changed = changed || (line(col) != c) || (lineColor(col) != packed) - setChar(line, lineColor, col, c) + setChar(line, lineColor, lineDirty, col, c) } changed } @@ -124,11 +128,12 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col var changed = false val line = buffer(row) val lineColor = color(row) + val lineDirty = dirty(row) var bx = math.max(col, 0) for (x <- bx until math.min(col + s.length, width) if bx < line.length) { val c = s(x - col) changed = changed || (line(bx) != c) || (lineColor(bx) != packed) - setChar(line, lineColor, bx, c) + setChar(line, lineColor, lineDirty, bx, c) bx += math.max(1, FontUtils.wcwidth(c)) } changed @@ -144,10 +149,11 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col for (y <- math.max(row, 0) until math.min(row + h, height)) { val line = buffer(y) val lineColor = color(y) + val lineDirty = dirty(y) var bx = math.max(col, 0) for (x <- bx until math.min(col + w, width) if bx < line.length) { changed = changed || (line(bx) != c) || (lineColor(bx) != packed) - setChar(line, lineColor, bx, c) + setChar(line, lineColor, lineDirty, bx, c) bx += math.max(1, FontUtils.wcwidth(c)) } } @@ -178,6 +184,7 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col for (ny <- dy0 to dy1 by sy) { val nl = buffer(ny) val nc = color(ny) + val nd = dirty(ny) ny - ty match { case oy if oy >= 0 && oy < height => val ol = buffer(oy) @@ -187,9 +194,11 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col changed = changed || (nl(nx) != ol(ox)) || (nc(nx) != oc(ox)) nl(nx) = ol(ox) nc(nx) = oc(ox) + nd(nx) = true for (offset <- 1 until FontUtils.wcwidth(nl(nx))) { nl(nx + offset) = ' ' nc(nx + offset) = oc(nx) + nd(nx + offset) = true } case _ => /* Got no source column. */ } @@ -197,6 +206,7 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col // don't change their colors if (left_edge >= 0 && FontUtils.wcwidth(nl(left_edge)) > 1) { nl(left_edge) = ' ' + nd(left_edge) = true changed = true } case _ => /* Got no source row. */ @@ -205,20 +215,23 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col changed } - private def setChar(line: Array[Char], lineColor: Array[Short], x: Int, c: Char) { + private def setChar(line: Array[Char], lineColor: Array[Short], lineDirty: Array[Boolean], x: Int, c: Char) { if (FontUtils.wcwidth(c) > 1 && x >= line.length - 1) { // Don't allow setting wide chars in right-most col. return } line(x) = c lineColor(x) = packed + lineDirty(x) = true for (x1 <- x + 1 until x + FontUtils.wcwidth(c)) { line(x1) = ' ' lineColor(x1) = packed + lineDirty(x1) = true } if (x > 0 && FontUtils.wcwidth(line(x - 1)) > 1) { // remove previous wide char (but don't change its color) line(x - 1) = ' ' + lineDirty(x - 1) = true } }