working on color screens

This commit is contained in:
Florian Nücke 2013-11-10 16:54:14 +01:00
parent 39b2c97010
commit 4ee76b146d
7 changed files with 309 additions and 116 deletions

View File

@ -1,91 +0,0 @@
package li.cil.oc.client.gui
import li.cil.oc.Config
import net.minecraft.client.renderer.GLAllocation
import net.minecraft.client.renderer.Tessellator
import net.minecraft.client.renderer.texture.TextureManager
import net.minecraft.util.ResourceLocation
import org.lwjgl.opengl.GL11
import scala.io.Source
object MonospaceFontRenderer {
private val font = new ResourceLocation(Config.resourceDomain, "textures/font/chars.png")
private val chars = Source.fromInputStream(MonospaceFontRenderer.getClass.getResourceAsStream("/assets/" + Config.resourceDomain + "/textures/font/chars.txt")).mkString
private var instance: Option[Renderer] = None
def init(textureManager: TextureManager) =
instance = instance.orElse(Some(new Renderer(textureManager)))
val (fontWidth, fontHeight) = (5, 9)
def drawString(value: Array[Char], x: Int, y: Int) = instance match {
case None => // Do nothing, not initialized.
case Some(renderer) => renderer.drawString(value, x, y)
}
private class Renderer(private val textureManager: TextureManager) {
/** Display lists, one per char (renders quad with char's uv coords). */
private val charLists = GLAllocation.generateDisplayLists(256)
/** Buffer filled with char display lists to efficiently draw strings. */
private val listBuffer = GLAllocation.createDirectIntBuffer(512)
// Set up the display lists.
{
val (charWidth, charHeight) = (MonospaceFontRenderer.fontWidth * 2, MonospaceFontRenderer.fontHeight * 2)
val cols = 256 / charWidth
val uStep = charWidth / 256.0
val vStep = charHeight / 256.0
val t = Tessellator.instance
// Now create lists for all printable chars.
for (index <- 1 until 0xFF) {
val x = (index - 1) % cols
val y = (index - 1) / cols
val u = x * uStep
val v = y * vStep
GL11.glNewList(charLists + index, GL11.GL_COMPILE)
t.startDrawingQuads()
t.addVertexWithUV(0, charHeight, 0, u, v + vStep)
t.addVertexWithUV(charWidth, charHeight, 0, u + uStep, v + vStep)
t.addVertexWithUV(charWidth, 0, 0, u + uStep, v)
t.addVertexWithUV(0, 0, 0, u, v)
t.draw()
GL11.glTranslatef(charWidth, 0, 0)
GL11.glEndList()
}
// Special case for whitespace: just translate, don't render.
GL11.glNewList(charLists + ' ', GL11.GL_COMPILE)
GL11.glTranslatef(charWidth, 0, 0)
GL11.glEndList()
}
def drawString(value: Array[Char], x: Int, y: Int) = {
setTexture(textureManager, MonospaceFontRenderer.font)
GL11.glPushMatrix()
GL11.glTranslatef(x, y, 0)
GL11.glScalef(0.5f, 0.5f, 1)
GL11.glColor4f(1, 1, 1, 1)
for (c <- value) {
val index = 1 + chars.indexOf(c) match {
case -1 => chars.indexOf('?')
case i => i
}
listBuffer.put(charLists + index)
if (listBuffer.remaining == 0)
flush()
}
flush()
GL11.glPopMatrix()
}
private def flush() = {
listBuffer.flip()
GL11.glCallLists(listBuffer)
listBuffer.clear()
}
}
private def setTexture(tm: TextureManager, resource: ResourceLocation) = tm.bindTexture(resource)
}

View File

@ -2,6 +2,7 @@ package li.cil.oc.client.gui
import li.cil.oc.Config
import li.cil.oc.client.PacketSender
import li.cil.oc.client.renderer.MonospaceFontRenderer
import li.cil.oc.common.tileentity
import net.minecraft.client.gui.{GuiScreen => MCGuiScreen}
import net.minecraft.client.renderer.GLAllocation
@ -11,6 +12,7 @@ import net.minecraft.util.ResourceLocation
import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11
import scala.collection.mutable
import li.cil.oc.util.PackedColor
/**
* This GUI shows the buffer of a single screen.
@ -47,11 +49,11 @@ class Screen(tileEntity: tileentity.Screen) extends MCGuiScreen {
// Re-build display lists.
Screen.compileBackground(innerWidth, innerHeight)
Screen.compileText(scale, screen.instance.lines)
Screen.compileText(scale, screen.instance.lines, screen.instance.colors, screen.instance.depth)
}
/** Must be called whenever the buffer of the underlying screen changes. */
def updateText() = Screen.compileText(scale, screen.instance.lines)
def updateText() = Screen.compileText(scale, screen.instance.lines, screen.instance.colors, screen.instance.depth)
override def handleKeyboardInput() {
super.handleKeyboardInput()
@ -182,14 +184,14 @@ object Screen {
GL11.glEndList()
}
private[gui] def compileText(scale: Double, lines: Array[Array[Char]]) =
private[gui] def compileText(scale: Double, lines: Array[Array[Char]], colors:Array[Array[Int]], depth: PackedColor.Depth.Value) =
if (textureManager.isDefined) {
GL11.glNewList(displayLists.get + 1, GL11.GL_COMPILE)
GL11.glTranslatef(margin + innerMargin, margin + innerMargin, 0)
GL11.glScaled(scale, scale, 1)
lines.zipWithIndex.foreach {
case (line, i) => MonospaceFontRenderer.drawString(line, 0, i * MonospaceFontRenderer.fontHeight)
lines.zip(colors).zipWithIndex.foreach {
case ((line, color), i) => MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, depth)
}
GL11.glEndList()

View File

@ -0,0 +1,137 @@
package li.cil.oc.client.renderer
import li.cil.oc.util.{RenderState, PackedColor}
import li.cil.oc.{OpenComputers, Config}
import net.minecraft.client.renderer.GLAllocation
import net.minecraft.client.renderer.Tessellator
import net.minecraft.client.renderer.texture.TextureManager
import net.minecraft.util.ResourceLocation
import org.lwjgl.opengl.GL11
import scala.io.Source
object MonospaceFontRenderer {
private val font = new ResourceLocation(Config.resourceDomain, "textures/font/chars.png")
private val chars = Source.fromInputStream(MonospaceFontRenderer.getClass.getResourceAsStream("/assets/" + Config.resourceDomain + "/textures/font/chars.txt")).mkString
private var instance: Option[Renderer] = None
def init(textureManager: TextureManager) = this.synchronized(
instance = instance.orElse(Some(new Renderer(textureManager))))
val (fontWidth, fontHeight) = (5, 9)
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Int], depth: PackedColor.Depth.Value) = instance match {
case None => OpenComputers.log.warning("Trying to render string with uninitialized MonospaceFontRenderer.")
case Some(renderer) => renderer.drawString(x, y, value, color, depth)
}
private class Renderer(private val textureManager: TextureManager) {
/** Display lists, one per char (renders quad with char's uv coords). */
private val charLists = GLAllocation.generateDisplayLists(256)
/** Buffer filled with char display lists to efficiently draw strings. */
private val listBuffer = GLAllocation.createDirectIntBuffer(512)
private val (charWidth, charHeight) = (MonospaceFontRenderer.fontWidth * 2, MonospaceFontRenderer.fontHeight * 2)
private val cols = 256 / charWidth
private val uStep = charWidth / 256.0
private val vStep = charHeight / 256.0
// Set up the display lists.
{
val t = Tessellator.instance
// Now create lists for all printable chars.
for (index <- 1 until 0xFF) {
val x = (index - 1) % cols
val y = (index - 1) / cols
val u = x * uStep
val v = y * vStep
GL11.glNewList(charLists + index, GL11.GL_COMPILE)
t.startDrawingQuads()
t.addVertexWithUV(0, charHeight, 0, u, v + vStep)
t.addVertexWithUV(charWidth, charHeight, 0, u + uStep, v + vStep)
t.addVertexWithUV(charWidth, 0, 0, u + uStep, v)
t.addVertexWithUV(0, 0, 0, u, v)
t.draw()
GL11.glTranslatef(charWidth, 0, 0)
GL11.glEndList()
}
// Special case for whitespace: just translate, don't render.
GL11.glNewList(charLists + ' ', GL11.GL_COMPILE)
GL11.glTranslatef(charWidth, 0, 0)
GL11.glEndList()
}
def drawString(x: Int, y: Int, value: Array[Char], color: Array[Int], depth: PackedColor.Depth.Value) = {
if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.")
textureManager.bindTexture(MonospaceFontRenderer.font)
GL11.glPushMatrix()
GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT)
GL11.glTranslatef(x, y, 0)
GL11.glScalef(0.5f, 0.5f, 1)
GL11.glDepthMask(false)
RenderState.makeItBlend()
// 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 width = 0
for (col <- color.map(PackedColor.unpackBackground(_, depth))) {
if (col != cbg) {
draw(cbg, width)
cbg = col
width = 0
}
width = width + 1
}
draw(cbg, width)
// Foreground second. We only have to flush when the color changes, so
// unless every char has a different color this should be quite efficient.
var cfg = 0x000000
GL11.glColor3f(0, 0, 0)
for ((ch, col) <- value.zip(color.map(PackedColor.unpackForeground(_, depth)))) {
val index = 1 + chars.indexOf(ch) match {
case -1 => chars.indexOf('?')
case i => i
}
if (col != cfg) {
// Color changed, force flush and adjust colors.
flush()
cfg = col
GL11.glColor3ub(
(cfg & 0xFF0000 >> 16).toByte,
(cfg & 0x00FF00 >> 8).toByte,
(cfg & 0x0000FF).toByte)
}
listBuffer.put(charLists + index)
if (listBuffer.remaining == 0)
flush()
}
flush()
GL11.glPopAttrib()
GL11.glPopMatrix()
}
private def draw(color: Int, width: Int) = if (color != 0 && width > 0) {
val t = Tessellator.instance
t.startDrawingQuads()
t.setColorOpaque_I(color)
t.addVertexWithUV(0, charHeight, 0, 0, vStep)
t.addVertexWithUV(charWidth, charHeight, 0, width * uStep, vStep)
t.addVertexWithUV(charWidth, 0, 0, width * uStep, 0)
t.addVertexWithUV(0, 0, 0, 0, 0)
t.draw()
}
private def flush() = {
listBuffer.flip()
GL11.glCallLists(listBuffer)
listBuffer.clear()
}
}
}

View File

@ -5,7 +5,7 @@ import cpw.mods.fml.common.{TickType, ITickHandler}
import java.util
import java.util.concurrent.{TimeUnit, Callable}
import li.cil.oc.Config
import li.cil.oc.client.gui.MonospaceFontRenderer
import li.cil.oc.client.renderer.MonospaceFontRenderer
import li.cil.oc.common.tileentity.Screen
import li.cil.oc.util.RenderState
import net.minecraft.client.Minecraft
@ -130,8 +130,8 @@ object ScreenRenderer extends TileEntitySpecialRenderer with Callable[Int] with
// Slightly offset the text so it doesn't clip into the screen.
GL11.glTranslatef(0, 0, 0.01f)
for ((line, i) <- screen.instance.lines.zipWithIndex) {
MonospaceFontRenderer.drawString(line, 0, i * MonospaceFontRenderer.fontHeight)
for (((line, color), i) <- screen.instance.lines.zip(screen.instance.colors).zipWithIndex) {
MonospaceFontRenderer.drawString(0, i * MonospaceFontRenderer.fontHeight, line, color, screen.instance.depth)
}
GL11.glEndList()

View File

@ -3,12 +3,12 @@ package li.cil.oc.common.component
import li.cil.oc.api.Persistable
import li.cil.oc.api.network.Visibility
import li.cil.oc.common.{tileentity, component}
import li.cil.oc.util.TextBuffer
import li.cil.oc.util.{PackedColor, TextBuffer}
import li.cil.oc.{Config, util, api}
import net.minecraft.nbt.NBTTagCompound
class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) extends Persistable {
private val buffer = new TextBuffer(maxResolution)
private val buffer = new TextBuffer(maxResolution, PackedColor.Depth.OneBit)
// ----------------------------------------------------------------------- //
@ -16,6 +16,10 @@ class Screen(val owner: Screen.Environment, val maxResolution: (Int, Int)) exten
def lines = buffer.buffer
def colors = buffer.color
def depth = buffer.depth
def resolution = buffer.size
def resolution_=(value: (Int, Int)) = {

View File

@ -0,0 +1,100 @@
package li.cil.oc.util
object PackedColor {
object Depth extends Enumeration {
val OneBit, EightBit, SixteenBit = Value
}
private val rMask32 = 0xFF0000
private val gMask32 = 0x00FF00
private val bMask32 = 0x0000FF
private val rShift32 = 16
private val gShift32 = 8
private val bShift32 = 0
// 7 6 5 4 3 2 1 0
// r r r g g g b b : 3.3.2
private val rMask8 = Integer.parseInt("11100000", 2)
private val gMask8 = Integer.parseInt("00011100", 2)
private val bMask8 = Integer.parseInt("00000011", 2)
private val rShift8 = 5
private val gShift8 = 2
private val bShift8 = 0
private val rScale8 = 255.0 / 0x7
private val gScale8 = 255.0 / 0x7
private val bScale8 = 255.0 / 0x3
// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
// r r r r r g g g g g g b b b b b : 5.6.5
private val rMask16 = Integer.parseInt("1111100000000000", 2)
private val gMask16 = Integer.parseInt("0000011111100000", 2)
private val bMask16 = Integer.parseInt("0000000000011111", 2)
private val rShift16 = 11
private val gShift16 = 5
private val bShift16 = 0
private val rScale16 = 255.0 / 0x1F
private val gScale16 = 255.0 / 0x3F
private val bScale16 = 255.0 / 0x1F
private def extractFrom1Bit(c: Short) = if (c == 0) 0x000000 else 0xFFFFFF
private def compressTo1Bit(c: Int) = (if (c == 0) 0 else 1).toShort
private def extractFrom8Bit(c: Short) = {
val r = ((((c & rMask8) >>> rShift8) * rScale8).toInt << rShift32) & rMask32
val g = ((((c & gMask8) >>> gShift8) * gScale8).toInt << gShift32) & gMask32
val b = ((((c & bMask8) >>> bShift8) * bScale8).toInt << bShift32) & bMask32
r | g | b
}
private def compressTo8Bit(c: Int) = {
val r = ((((c & rMask32) >>> rShift32) / rScale8).toInt << rShift8) & rMask8
val g = ((((c & gMask32) >>> gShift32) / gScale8).toInt << gShift8) & gMask8
val b = ((((c & bMask32) >>> bShift32) / bScale8).toInt << bShift8) & bMask8
(r | g | b).toShort
}
private def extractFrom16Bit(c: Short) = {
val r = ((((c & rMask16) >>> rShift16) * rScale16).toInt << rShift32) & rMask32
val g = ((((c & gMask16) >>> gShift16) * gScale16).toInt << gShift32) & gMask32
val b = ((((c & bMask16) >>> bShift16) * bScale16).toInt << bShift32) & bMask32
r | g | b
}
private def compressTo16Bit(c: Int) = {
val r = ((((c & rMask32) >>> rShift32) / rScale16).toInt << rShift16) & rMask16
val g = ((((c & gMask32) >>> gShift32) / gScale16).toInt << gShift16) & gMask16
val b = ((((c & bMask32) >>> bShift32) / bScale16).toInt << bShift16) & bMask16
(r | g | b).toShort
}
// Colors are packed: 0xFFFFBBBB (F = foreground, B = background)
private val fgShift = 16
private val bgShift = 0
def pack(foreground: Int, background: Int, depth: Depth.Value) =
depth match {
case Depth.OneBit => (compressTo1Bit(foreground) << fgShift) | (compressTo1Bit(background) << bgShift)
case Depth.EightBit => (compressTo8Bit(foreground) << fgShift) | (compressTo8Bit(background) << bgShift)
case Depth.SixteenBit => (compressTo16Bit(foreground) << fgShift) | (compressTo16Bit(background) << bgShift)
}
def unpackForeground(color: Int, depth: Depth.Value) = {
val c = (color >>> fgShift).toShort
depth match {
case Depth.OneBit => extractFrom1Bit(c)
case Depth.EightBit => extractFrom8Bit(c)
case Depth.SixteenBit => extractFrom16Bit(c)
}
}
def unpackBackground(color: Int, depth: Depth.Value) = {
val c = (color >>> bgShift).toShort
depth match {
case Depth.OneBit => extractFrom1Bit(c)
case Depth.EightBit => extractFrom8Bit(c)
case Depth.SixteenBit => extractFrom16Bit(c)
}
}
}

View File

@ -1,8 +1,6 @@
package li.cil.oc.util
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.nbt.NBTTagList
import net.minecraft.nbt.NBTTagString
import net.minecraft.nbt.{NBTTagIntArray, NBTTagCompound, NBTTagList, NBTTagString}
/**
* This stores chars in a 2D-Array and provides some manipulation functions.
@ -12,8 +10,30 @@ import net.minecraft.nbt.NBTTagString
* relatively fast updates, given a smart algorithm (using copy()/fill()
* instead of set()ing everything).
*/
class TextBuffer(var width: Int, var height: Int) {
def this(size: (Int, Int)) = this(size._1, size._2)
class TextBuffer(var width: Int, var height: Int, val depth: PackedColor.Depth.Value) {
def this(size: (Int, Int), depth: PackedColor.Depth.Value) = this(size._1, size._2, depth)
private var foreground_ = 0xFFFFFF
private var background_ = 0x000000
private var packed = PackedColor.pack(foreground_, background_, depth)
def foreground = foreground_
def foreground_=(value: Int) = {
foreground_ = value
packed = PackedColor.pack(foreground_, background_, depth)
}
def background = background_
def background_=(value: Int) = {
background_ = value
packed = PackedColor.pack(foreground_, background_, depth)
}
var color = Array.fill(height, width)(packed)
var buffer = Array.fill(height, width)(' ')
@ -32,10 +52,13 @@ class TextBuffer(var width: Int, var height: Int) {
val (w, h) = (iw max 1, ih max 1)
if (width != w || height != h) {
val newBuffer = Array.fill(h, w)(' ')
(0 until (h min height)) foreach {
y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width)
}
val newColor = Array.fill(h, w)(packed)
(0 until (h min height)).foreach(y => {
Array.copy(buffer(y), 0, newBuffer(y), 0, w min width)
Array.copy(color(y), 0, newColor(y), 0, w min width)
})
buffer = newBuffer
color = newColor
width = w
height = h
true
@ -52,10 +75,12 @@ class TextBuffer(var width: Int, var height: Int) {
else {
var changed = false
val line = buffer(row)
val lineColor = color(row)
for (x <- col until ((col + s.length) min width)) if (x >= 0) {
val c = s(x - col)
changed = changed || (line(x) != c)
changed = changed || (line(x) != c) || (lineColor(x) != packed)
line(x) = c
lineColor(x) = packed
}
changed
}
@ -68,9 +93,11 @@ class TextBuffer(var width: Int, var height: Int) {
var changed = false
for (y <- (row max 0) until ((row + h) min height)) {
val line = buffer(y)
val lineColor = color(y)
for (x <- (col max 0) until ((col + w) min width)) {
changed = changed || (line(x) != c)
changed = changed || (line(x) != c) || (lineColor(x) != packed)
line(x) = c
lineColor(x) = packed
}
}
changed
@ -97,13 +124,16 @@ class TextBuffer(var width: Int, var height: Int) {
var changed = false
for (ny <- dy0 to dy1 by sy) {
val nl = buffer(ny)
val nc = color(ny)
ny - ty match {
case oy if oy >= 0 && oy < height =>
val ol = buffer(oy)
val oc = color(oy)
for (nx <- dx0 to dx1 by sx) nx - tx match {
case ox if ox >= 0 && ox < width => {
changed = changed || (nl(nx) != ol(ox))
changed = changed || (nl(nx) != ol(ox)) || (nc(nx) != oc(ox))
nl(nx) = ol(ox)
nc(nx) = oc(ox)
}
case _ => /* Got no source column. */
}
@ -118,19 +148,30 @@ class TextBuffer(var width: Int, var height: Int) {
val h = nbt.getInteger("height")
size = (w, h)
val b = nbt.getTagList("buffer")
for (i <- 0 until (h min b.tagCount())) {
set(0, i, b.tagAt(i).asInstanceOf[NBTTagString].data)
for (i <- 0 until (h min b.tagCount)) {
val line = b.tagAt(i).asInstanceOf[NBTTagString].data
set(0, i, line)
}
val c = nbt.getTagList("color")
for (i <- 0 until (h min c.tagCount)) {
val line = c.tagAt(i).asInstanceOf[NBTTagIntArray].intArray
Array.copy(line, 0, color(i), 0, line.length min width)
}
}
def writeToNBT(nbt: NBTTagCompound): Unit = {
nbt.setInteger("width", width)
nbt.setInteger("height", height)
val b = new NBTTagList
val b = new NBTTagList()
for (i <- 0 until height) {
b.appendTag(new NBTTagString("", String.valueOf(buffer(i))))
b.appendTag(new NBTTagString(null, String.valueOf(buffer(i))))
}
nbt.setTag("buffer", b)
val c = new NBTTagList()
for (i <- 0 until height) {
c.appendTag(new NBTTagIntArray(null, color(i)))
}
nbt.setTag("color", c)
}
override def toString = {