Reworked font rendering system to be a little more generic.

Added font renderer i've been tinkering with that generates character textures on the fly from an actual font, in the hopes of supporting arbitrary unicode one day. It's terribly broken, still, though.
This commit is contained in:
Florian Nücke 2014-06-18 13:10:30 +02:00
parent cbfe1dd25a
commit cfd8b5db16
12 changed files with 527 additions and 181 deletions

View File

@ -1,2 +1,2 @@
☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
5 9
10 18

View File

@ -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)

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -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")

View File

@ -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)
}
}
}

View File

@ -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 + ".<init>: 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 + ".<init>: 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)
}

View File

@ -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) {}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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