diff --git a/src/main/resources/assets/opencomputers/textures/font/chars.txt b/src/main/resources/assets/opencomputers/textures/font/chars.txt index a5b66a8ba..f76fba393 100644 --- a/src/main/resources/assets/opencomputers/textures/font/chars.txt +++ b/src/main/resources/assets/opencomputers/textures/font/chars.txt @@ -1,2 +1,2 @@ ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ -5 9 \ No newline at end of file +10 18 \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/gui/Robot.scala b/src/main/scala/li/cil/oc/client/gui/Robot.scala index 5b4e6e0fb..0a8903904 100644 --- a/src/main/scala/li/cil/oc/client/gui/Robot.scala +++ b/src/main/scala/li/cil/oc/client/gui/Robot.scala @@ -3,7 +3,7 @@ package li.cil.oc.client.gui import java.util import li.cil.oc.api import li.cil.oc.Settings -import li.cil.oc.client.renderer.MonospaceFontRenderer +import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.gui.BufferRenderer import li.cil.oc.client.{PacketSender => ClientPacketSender, Textures} import li.cil.oc.common.container @@ -250,8 +250,8 @@ class Robot(playerInventory: InventoryPlayer, val robot: tileentity.Robot) exten } override protected def changeSize(w: Double, h: Double, recompile: Boolean) = { - val bw = w * MonospaceFontRenderer.fontWidth - val bh = h * MonospaceFontRenderer.fontHeight + val bw = w * TextBufferRenderCache.renderer.charRenderWidth + val bh = h * TextBufferRenderCache.renderer.charRenderHeight val scaleX = math.min(bufferWidth / (bw + bufferMargin * 2.0), 1) val scaleY = math.min(bufferHeight / (bh + bufferMargin * 2.0), 1) math.min(scaleX, scaleY) diff --git a/src/main/scala/li/cil/oc/client/gui/Screen.scala b/src/main/scala/li/cil/oc/client/gui/Screen.scala index 3966640c8..bb5321fab 100644 --- a/src/main/scala/li/cil/oc/client/gui/Screen.scala +++ b/src/main/scala/li/cil/oc/client/gui/Screen.scala @@ -1,7 +1,7 @@ package li.cil.oc.client.gui import li.cil.oc.api -import li.cil.oc.client.renderer.MonospaceFontRenderer +import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.gui.BufferRenderer import li.cil.oc.util.RenderState import org.lwjgl.input.Mouse @@ -27,8 +27,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha if (Mouse.hasWheel && Mouse.getEventDWheel != 0) { val mouseX = Mouse.getEventX * width / mc.displayWidth val mouseY = height - Mouse.getEventY * height / mc.displayHeight - 1 - val bx = (mouseX - x - bufferMargin) / MonospaceFontRenderer.fontWidth + 1 - val by = (mouseY - y - bufferMargin) / MonospaceFontRenderer.fontHeight + 1 + val bx = (mouseX - x - bufferMargin) / TextBufferRenderCache.renderer.charRenderWidth + 1 + val by = (mouseY - y - bufferMargin) / TextBufferRenderCache.renderer.charRenderHeight + 1 val bw = buffer.getWidth val bh = buffer.getHeight if (bx > 0 && by > 0 && bx <= bw && by <= bh) { @@ -60,8 +60,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha super.mouseMovedOrUp(mouseX, mouseY, button) if (button >= 0) { if (didDrag) { - val bx = ((mouseX - x - bufferMargin) / scale / MonospaceFontRenderer.fontWidth).toInt + 1 - val by = ((mouseY - y - bufferMargin) / scale / MonospaceFontRenderer.fontHeight).toInt + 1 + val bx = ((mouseX - x - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderWidth).toInt + 1 + val by = ((mouseY - y - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderHeight).toInt + 1 val bw = buffer.getWidth val bh = buffer.getHeight if (bx > 0 && by > 0 && bx <= bw && by <= bh) { @@ -78,8 +78,8 @@ class Screen(val buffer: api.component.TextBuffer, val hasMouse: Boolean, val ha } private def clickOrDrag(mouseX: Int, mouseY: Int, button: Int) { - val bx = ((mouseX - x - bufferMargin) / scale / MonospaceFontRenderer.fontWidth).toInt + 1 - val by = ((mouseY - y - bufferMargin) / scale / MonospaceFontRenderer.fontHeight).toInt + 1 + val bx = ((mouseX - x - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderWidth).toInt + 1 + val by = ((mouseY - y - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderHeight).toInt + 1 val bw = buffer.getWidth val bh = buffer.getHeight if (bx > 0 && by > 0 && bx <= bw && by <= bh) { diff --git a/src/main/scala/li/cil/oc/client/gui/TextBuffer.scala b/src/main/scala/li/cil/oc/client/gui/TextBuffer.scala index dee14279b..5b109871b 100644 --- a/src/main/scala/li/cil/oc/client/gui/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/client/gui/TextBuffer.scala @@ -1,7 +1,6 @@ package li.cil.oc.client.gui import li.cil.oc.client.{Textures, KeyBindings} -import li.cil.oc.client.renderer.MonospaceFontRenderer import li.cil.oc.client.renderer.gui.BufferRenderer import li.cil.oc.util.RenderState import li.cil.oc.util.mods.NEI @@ -35,7 +34,6 @@ trait TextBuffer extends GuiScreen { override def initGui() = { super.initGui() - MonospaceFontRenderer.init(Minecraft.getMinecraft.renderEngine) BufferRenderer.init(Minecraft.getMinecraft.renderEngine) Keyboard.enableRepeatEvents(true) } diff --git a/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala deleted file mode 100644 index b8b7f40e3..000000000 --- a/src/main/scala/li/cil/oc/client/renderer/MonospaceFontRenderer.scala +++ /dev/null @@ -1,156 +0,0 @@ -package li.cil.oc.client.renderer - -import java.util.logging.Level -import li.cil.oc.client.Textures -import li.cil.oc.util.{RenderState, PackedColor} -import li.cil.oc.{OpenComputers, Settings} -import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.texture.TextureManager -import net.minecraft.util.ResourceLocation -import org.lwjgl.opengl.GL11 -import scala.io.Source - -// IMPORTANT: we must not use the tessellator here. Doing so can cause -// crashes on certain graphics cards with certain drivers (reported for -// ATI/AMD and Intel chip sets). These crashes have been reported to -// happen I have no idea why, and can only guess that it's related to -// using the VBO/ARB the tessellator uses inside a display list (since -// this stuff is eventually only rendered via display lists). - -object MonospaceFontRenderer { - val (chars, fontWidth, fontHeight) = try { - val lines = Source.fromInputStream(Minecraft.getMinecraft.getResourceManager.getResource(new ResourceLocation(Settings.resourceDomain, "textures/font/chars.txt")).getInputStream)("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 (5, 9) - (chars, w, h) - } - catch { - case t: Throwable => - OpenComputers.log.log(Level.WARNING, "Failed reading font metadata, using defaults.", t) - ( """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■""", 5, 9) - } - - private var instance: Option[Renderer] = None - - def init(textureManager: TextureManager) = this.synchronized( - instance = instance.orElse(Some(new Renderer(textureManager)))) - - def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = this.synchronized(instance match { - case None => OpenComputers.log.warning("Trying to render string with uninitialized MonospaceFontRenderer.") - case Some(renderer) => renderer.drawString(x, y, value, color, format) - }) - - private class Renderer(private val textureManager: TextureManager) { - private val (charWidth, charHeight) = (MonospaceFontRenderer.fontWidth * 2, MonospaceFontRenderer.fontHeight * 2) - 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 - - def drawString(x: Int, y: Int, value: Array[Char], color: Array[Short], format: PackedColor.ColorFormat) = { - if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.") - - RenderState.checkError(getClass.getName + ".drawString: entering (aka: wasntme)") - - if (Settings.get.textAntiAlias) - textureManager.bindTexture(Textures.fontAntiAliased) - else - textureManager.bindTexture(Textures.fontAliased) - GL11.glPushMatrix() - GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) - - GL11.glTranslatef(x, y, 0) - GL11.glScalef(0.5f, 0.5f, 1) - GL11.glDepthMask(false) - GL11.glDisable(GL11.GL_TEXTURE_2D) - - RenderState.checkError(getClass.getName + ".drawString: configure state") - - GL11.glBegin(GL11.GL_QUADS) - // Background first. We try to merge adjacent backgrounds of the same - // color to reduce the number of quads we have to draw. - var cbg = 0x000000 - var offset = 0 - var width = 0 - for (col <- color.map(PackedColor.unpackBackground(_, format))) { - if (col != cbg) { - draw(cbg, offset, width) - cbg = col - offset += width - width = 0 - } - width = width + 1 - } - draw(cbg, offset, width) - GL11.glEnd() - - RenderState.checkError(getClass.getName + ".drawString: 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. - GL11.glBegin(GL11.GL_QUADS) - var cfg = -1 - var posX = 0.0 - for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, format)))) { - val index = 1 + (chars.indexOf(ch) match { - case -1 => chars.indexOf('?') - case i => i - }) - if (col != cfg) { - // Color changed. - cfg = col - GL11.glColor3ub( - ((cfg & 0xFF0000) >> 16).toByte, - ((cfg & 0x00FF00) >> 8).toByte, - ((cfg & 0x0000FF) >> 0).toByte) - } - if (ch != ' ') { - // Don't render whitespace. - 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(posX - dw, charHeight * s, 0) - GL11.glTexCoord2d(u + uSize, v + vSize) - GL11.glVertex3d(posX + charWidth * s, charHeight * s, 0) - GL11.glTexCoord2d(u + uSize, v) - GL11.glVertex3d(posX + charWidth * s, -dh, 0) - GL11.glTexCoord2d(u, v) - GL11.glVertex3d(posX - dw, -dh, 0) - } - posX += charWidth - } - GL11.glEnd() - - RenderState.checkError(getClass.getName + ".drawString: foreground") - - GL11.glPopAttrib() - GL11.glPopMatrix() - - RenderState.checkError(getClass.getName + ".drawString: leaving") - } - - private def draw(color: Int, offset: Int, width: Int) = if (color != 0 && width > 0) { - GL11.glColor3ub(((color >> 16) & 0xFF).toByte, ((color >> 8) & 0xFF).toByte, (color & 0xFF).toByte) - GL11.glVertex3d(charWidth * offset, charHeight, 0) - GL11.glVertex3d(charWidth * (offset + width), charHeight, 0) - GL11.glVertex3d(charWidth * (offset + width), 0, 0) - GL11.glVertex3d(charWidth * offset, 0, 0) - } - } - -} \ No newline at end of file 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 a6faf9e20..81a9b02cb 100644 --- a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala +++ b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala @@ -1,18 +1,23 @@ package li.cil.oc.client.renderer -import com.google.common.cache.{RemovalListener, RemovalNotification, CacheBuilder} -import cpw.mods.fml.common.{ITickHandler, TickType} import java.util import java.util.concurrent.{Callable, TimeUnit} + +import com.google.common.cache.{CacheBuilder, RemovalListener, RemovalNotification} +import cpw.mods.fml.common.{ITickHandler, TickType} +import li.cil.oc.client.renderer.font.StaticFontRenderer import li.cil.oc.common.component.TextBuffer import li.cil.oc.util.RenderState import net.minecraft.client.renderer.GLAllocation import net.minecraft.tileentity.TileEntity import org.lwjgl.opengl.GL11 -import net.minecraft.client.Minecraft object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEntity, Int] with ITickHandler { - val cache = com.google.common.cache.CacheBuilder.newBuilder(). + val renderer = + new StaticFontRenderer() +// new DynamicFontRenderer("Terminal") + + private val cache = com.google.common.cache.CacheBuilder.newBuilder(). expireAfterAccess(2, TimeUnit.SECONDS). removalListener(this). asInstanceOf[CacheBuilder[TextBuffer, Int]]. @@ -26,7 +31,6 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti // ----------------------------------------------------------------------- // def render(buffer: TextBuffer) { - MonospaceFontRenderer.init(Minecraft.getMinecraft.getTextureManager) currentBuffer = buffer compileOrDraw(cache.get(currentBuffer, this)) } @@ -35,6 +39,10 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti if (currentBuffer.proxy.dirty) { RenderState.checkError(getClass.getName + ".compileOrDraw: entering (aka: wasntme)") + for (line <- currentBuffer.data.buffer) { + renderer.generateChars(line) + } + val doCompile = !RenderState.compilingDisplayList if (doCompile) { currentBuffer.proxy.dirty = false @@ -43,9 +51,7 @@ object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEnti RenderState.checkError(getClass.getName + ".compileOrDraw: glNewList") } - for (((line, color), i) <- currentBuffer.data.buffer.zip(currentBuffer.data.color).zipWithIndex) { - MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, currentBuffer.data.format) - } + renderer.drawBuffer(currentBuffer.data) RenderState.checkError(getClass.getName + ".compileOrDraw: drawString") diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala new file mode 100644 index 000000000..7ad53f7ba --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/DynamicCharRenderer.scala @@ -0,0 +1,125 @@ +package li.cil.oc.client.renderer.font + +import java.awt.Font +import java.awt.font.FontRenderContext +import java.awt.geom.{PathIterator, Point2D} + +import org.lwjgl.opengl.GL11 +import org.lwjgl.util.glu.{GLU, GLUtessellatorCallbackAdapter} + +class DynamicCharRenderer(val font: Font) { + private val context = new FontRenderContext(font.getTransform, true, true) + private val callback = new DynamicCharRenderer.Callback() + private val maxCharBounds = font.getMaxCharBounds(context) + + def charWidth = maxCharBounds.getWidth.toFloat + + def charHeight = maxCharBounds.getHeight.toFloat + + def drawChar(char: Char) { + GL11.glPushMatrix() + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + GL11.glTranslated(-maxCharBounds.getX, -maxCharBounds.getY, 0) + GL11.glDisable(GL11.GL_TEXTURE_2D) + GL11.glColor4f(1, 1, 1, 1) + + val vector = font.createGlyphVector(context, Array(char)) + + val tess = GLU.gluNewTess() + tess.gluTessCallback(GLU.GLU_TESS_BEGIN, callback) + tess.gluTessCallback(GLU.GLU_TESS_END, callback) + tess.gluTessCallback(GLU.GLU_TESS_VERTEX, callback) + tess.gluTessCallback(GLU.GLU_TESS_EDGE_FLAG, callback) + tess.gluTessNormal(0, 0, -1) + + for (i <- 0 until vector.getNumGlyphs) { + val outline = vector.getGlyphOutline(i).getPathIterator(null) + tess.gluTessBeginPolygon(null) + val current = new Point2D.Double(0, 0) + val coords = new Array[Double](6) + while (!outline.isDone) { + if (outline.getWindingRule == PathIterator.WIND_EVEN_ODD) + tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_ODD) + else + tess.gluTessProperty(GLU.GLU_TESS_WINDING_RULE, GLU.GLU_TESS_WINDING_NONZERO) + outline.currentSegment(coords) match { + case PathIterator.SEG_MOVETO => + tess.gluTessBeginContour() + current.setLocation(coords(0), coords(1)) + case PathIterator.SEG_LINETO => + val buffer = Array[Float](coords(0).toFloat, coords(1).toFloat) + tess.gluTessVertex(coords, 0, buffer) + current.setLocation(coords(0), coords(1)) + case PathIterator.SEG_QUADTO => + // From SEG_QUADTO: + // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 + // with 0 <= t <= 1 + // B(n,m) = mth coefficient of nth degree Bernstein polynomial + // = C(n,m) * t^(m) * (1 - t)^(n-m) + // C(n,m) = Combinations of n things, taken m at a time + // = n! / (m! * (n-m)!) + // So: + // P(t) = B(2,0)*CP + B(2,1)*P1 + B(2,2)*P2 + // = C(2,0) * t^(0) * (1 - t)^(2-0) * CP + + // C(2,1) * t^(1) * (1 - t)^(2-1) * P1 + + // C(2,2) * t^(2) * (1 - t)^(2-2) * P2 + // = 2! / (0! * (2-0)!) * (1 - t)^2 * CP + + // 2! / (1! * (2-1)!) * t * (1 - t) * P1 + + // 2! / (2! * (2-2)!) * t^2 * P2 + // = (1 - t)^2 * CP + + // 2 * t * (1 - t) * P1 + + // t^2 * P2 + // = (1 - 2*t + t^2) * CP + + // 2 * (t - t^2) * P1 + + // t^2 * P2 + val interpolated = new Array[Double](3) + def p(t: Double) = { + val tSquared = t * t + val fc = 1 - 2 * t + tSquared + val f1 = 2 * (t - tSquared) + val f2 = tSquared + interpolated(0) = fc * current.x + f1 * coords(0) + f2 * coords(2) + interpolated(1) = fc * current.y + f1 * coords(1) + f2 * coords(3) + val buffer = Array[Float](interpolated(0).toFloat, interpolated(1).toFloat) + tess.gluTessVertex(interpolated, 0, buffer) + } + for (t <- 0.0 until 1.0 by 0.25) { + p(t) + } + current.setLocation(coords(2), coords(3)) + case PathIterator.SEG_CUBICTO => + // Not supported. + case PathIterator.SEG_CLOSE => + tess.gluTessEndContour() + case _ => // Wuh? + } + outline.next() + } + tess.gluTessEndPolygon() + } + + tess.gluDeleteTess() + + GL11.glPopAttrib() + GL11.glPopMatrix() + } +} + +object DynamicCharRenderer { + + private class Callback extends GLUtessellatorCallbackAdapter { + override def begin(mode: Int) = GL11.glBegin(mode) + + override def end() = GL11.glEnd() + + override def vertex(coords: scala.Any) { + val point = coords.asInstanceOf[Array[Float]] + GL11.glVertex2f(point(0), point(1)) + } + + override def edgeFlag(boundaryEdge: Boolean) { + GL11.glEdgeFlag(boundaryEdge) + } + } + +} \ No newline at end of file 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 new file mode 100644 index 000000000..b4c064d42 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala @@ -0,0 +1,166 @@ +package li.cil.oc.client.renderer.font + +import java.awt.Font +import java.io.InputStream + +import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture +import li.cil.oc.util.RenderState +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(val font: Font) extends TextureFontRenderer { + def this(name: String) = this(new Font(name, Font.PLAIN, 11)) + + def this(stream: InputStream) = this(Font.createFont(Font.TRUETYPE_FONT, stream).deriveFont(11f)) + + private val charRenderer = new DynamicCharRenderer(font) + + private val textures = mutable.ArrayBuffer(new DynamicFontRenderer.CharTexture(this)) + + private val charMap = mutable.Map.empty[Char, DynamicFontRenderer.CharIcon] + + private val fbo = GL30.glGenFramebuffers() + + private val rbo = GL30.glGenRenderbuffers() + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo) + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rbo) + + GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL11.GL_RGBA8, charWidth, charHeight) + GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_RENDERBUFFER, rbo) + + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, 0) + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0) + + var activeTexture: CharTexture = textures(0) + + generateChars(basicChars.toCharArray) + + RenderState.checkError(getClass.getName + ".: glGenFramebuffers") + + override protected def charWidth = charRenderer.charWidth.toInt + + override protected def charHeight = charRenderer.charHeight.toInt + + 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) { + val icon = charMap(char) + if (icon.texture == activeTexture) { + GL11.glTexCoord2f(icon.u1, icon.v2) + GL11.glVertex2f(tx, ty + charHeight) + GL11.glTexCoord2f(icon.u2, icon.v2) + GL11.glVertex2f(tx + charWidth, ty + charHeight) + GL11.glTexCoord2f(icon.u2, icon.v1) + GL11.glVertex2f(tx + charWidth, ty) + GL11.glTexCoord2f(icon.u1, icon.v1) + GL11.glVertex2f(tx, ty) + } + } + + private def createCharIcon(char: Char): DynamicFontRenderer.CharIcon = { + if (!font.canDisplay(char)) { + if (char == '?') null + else charMap.getOrElseUpdate('?', createCharIcon('?')) + } + else { + if (textures.last.isFull) { + 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) + 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.toFloat + private val vStep = cellHeight / size.toFloat + private val pad = 1f / size + private val capacity = cols * rows + + private var chars = 0 + + def bind() { + GL11.glBindTexture(GL11.GL_TEXTURE_2D, id) + } + + def isFull = chars >= capacity + + def add(char: Char) = { + val x = chars % cols + val y = chars / cols + + GL11.glDisable(GL11.GL_DEPTH_TEST) + GL11.glDepthMask(false) + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, owner.fbo) + GL11.glClearColor(0, 0, 0, 0) + GL20.glDrawBuffers(GL30.GL_COLOR_ATTACHMENT0) + GL11.glClear(GL11.GL_COLOR_BUFFER_BIT) + + GL11.glViewport(0, 0, owner.charWidth, owner.charHeight) + + GL11.glMatrixMode(GL11.GL_PROJECTION) + GL11.glPushMatrix() + GL11.glLoadIdentity() + + GL11.glOrtho(0, owner.charWidth, owner.charHeight, 0, 0, 1) + + GL11.glMatrixMode(GL11.GL_MODELVIEW) + GL11.glPushMatrix() + GL11.glLoadIdentity() + GL11.glTranslatef(0, 0, -0.5f) + + owner.charRenderer.drawChar(char) + + GL43.glCopyImageSubData(owner.rbo, GL30.GL_RENDERBUFFER, 0, 0, 0, 0, id, GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, 0, owner.charWidth, owner.charHeight, 1) + + GL11.glMatrixMode(GL11.GL_PROJECTION) + GL11.glPopMatrix() + GL11.glMatrixMode(GL11.GL_MODELVIEW) + GL11.glPopMatrix() + + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0) + + chars += 1 + + new CharIcon(this, pad + x * uStep, pad + y * vStep, (x + 1) * uStep - 2 * pad, (y + 1) * vStep - 2 * pad) + } + } + + class CharIcon(val texture: CharTexture, val u1: Float, val v1: Float, val u2: Float, val v2: Float) + +} \ No newline at end of file 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 new file mode 100644 index 000000000..5859babb6 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/StaticFontRenderer.scala @@ -0,0 +1,71 @@ +package li.cil.oc.client.renderer.font + +import scala.io.Source +import net.minecraft.client.Minecraft +import net.minecraft.util.ResourceLocation +import li.cil.oc.{OpenComputers, Settings} +import java.util.logging.Level +import li.cil.oc.client.Textures +import org.lwjgl.opengl.GL11 + +/** + * 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)("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.log(Level.WARNING, "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 new file mode 100644 index 000000000..5f7d10aa9 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala @@ -0,0 +1,136 @@ +package li.cil.oc.client.renderer.font + +import li.cil.oc.Settings +import li.cil.oc.util.{TextBuffer, RenderState, PackedColor} +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) + } + } + + def drawBuffer(buffer: TextBuffer) { + val format = buffer.format + + GL11.glPushMatrix() + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + + GL11.glScalef(0.5f, 0.5f, 1) + + GL11.glDepthMask(false) + GL11.glDisable(GL11.GL_TEXTURE_2D) + + 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 buffer.height) { + val color = buffer.color(y) + var cbg = 0x000000 + var x = 0 + var width = 0 + for (col <- color.map(PackedColor.unpackBackground(_, format))) { + if (col != cbg) { + drawQuad(cbg, x, y, width) + cbg = col + x += width + width = 0 + } + width = width + 1 + } + drawQuad(cbg, x, y, width) + } + 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 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 line.length) { + 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) + } + tx += charWidth + } + GL11.glEnd() + } + } + + RenderState.checkError(getClass.getName + ".drawBuffer: foreground") + + GL11.glPopAttrib() + GL11.glPopMatrix() + + RenderState.checkError(getClass.getName + ".drawBuffer: leaving") + } + + protected def charWidth: Int + + protected def charHeight: Int + + protected def textureCount: Int + + protected def bindTexture(index: Int) + + protected def generateChar(char: Char) + + protected def drawChar(tx: Float, ty: Float, char: Char) + + 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/tileentity/ScreenRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala index 23770d8f5..69b76c034 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/ScreenRenderer.scala @@ -7,8 +7,8 @@ import li.cil.oc.common.tileentity.Screen import li.cil.oc.util.RenderState import li.cil.oc.util.mods.BuildCraft import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer import net.minecraft.tileentity.TileEntity import net.minecraftforge.common.ForgeDirection import org.lwjgl.opengl.GL11 diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala index 50f0414ef..6ab2d51aa 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -7,7 +7,7 @@ import li.cil.oc.api.component.TextBuffer.ColorDepth import li.cil.oc.api.driver.Container import li.cil.oc.api.network._ import li.cil.oc.client.{PacketSender => ClientPacketSender, ComponentTracker => ClientComponentTracker} -import li.cil.oc.client.renderer.{MonospaceFontRenderer, TextBufferRenderCache} +import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.common.tileentity import li.cil.oc.server.{PacketSender => ServerPacketSender, ComponentTracker => ServerComponentTracker} import li.cil.oc.server.component.Keyboard @@ -293,10 +293,10 @@ class TextBuffer(val owner: Container) extends ManagedComponent with api.compone override def renderText() = relativeLitArea != 0 && proxy.render() @SideOnly(Side.CLIENT) - override def renderWidth = MonospaceFontRenderer.fontWidth * data.width + override def renderWidth = TextBufferRenderCache.renderer.charRenderWidth * data.width @SideOnly(Side.CLIENT) - override def renderHeight = MonospaceFontRenderer.fontHeight * data.height + override def renderHeight = TextBufferRenderCache.renderer.charRenderHeight * data.height @SideOnly(Side.CLIENT) override def setRenderingEnabled(enabled: Boolean) = isRendering = enabled