| package li.cil.oc.client.renderer |
| |
| 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.GLAllocation |
| import net.minecraft.client.renderer.texture.TextureManager |
| import net.minecraft.util.ResourceLocation |
| import org.lwjgl.opengl.GL11 |
| import scala.io.Source |
| |
| 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.warning("Failed reading font metdata, using defaults.") |
| ("""☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !\"#$%&'()*+,-./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) { |
| /** Display lists, one per char (renders quad with char's uv coords). */ |
| private val charLists = GLAllocation.generateDisplayLists(256) |
| RenderState.checkError("MonospaceFontRenderer.charLists") |
| |
| /** Buffer filled with char display lists to efficiently draw strings. */ |
| private val listBuffer = GLAllocation.createDirectIntBuffer(512) |
| RenderState.checkError("MonospaceFontRenderer.listBuffer") |
| |
| 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 |
| |
| // Set up the display lists. |
| { |
| val s = Settings.get.fontCharScale |
| val dw = charWidth * s - charWidth |
| val dh = charHeight * s - charHeight |
| // 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) |
| GL11.glBegin(GL11.GL_QUADS) |
| GL11.glTexCoord2d(u, v + vSize) |
| GL11.glVertex3d(-dw, charHeight * s, 0) |
| GL11.glTexCoord2d(u + uSize, v + vSize) |
| GL11.glVertex3d(charWidth * s, charHeight * s, 0) |
| GL11.glTexCoord2d(u + uSize, v) |
| GL11.glVertex3d(charWidth * s, -dh, 0) |
| GL11.glTexCoord2d(u, v) |
| GL11.glVertex3d(-dw, -dh, 0) |
| GL11.glEnd() |
| 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[Short], format: PackedColor.ColorFormat) = { |
| if (color.length != value.length) throw new IllegalArgumentException("Color count must match char count.") |
| |
| if (Settings.get.textAntiAlias) |
| textureManager.bindTexture(Textures.fontAntiAliased) |
| else |
| textureManager.bindTexture(Textures.fontAliased) |
| GL11.glPushMatrix() |
| GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_TEXTURE_BIT) |
| GL11.glTranslatef(x, y, 0) |
| GL11.glScalef(0.5f, 0.5f, 1) |
| GL11.glDepthMask(false) |
| GL11.glEnable(GL11.GL_TEXTURE_2D) |
| |
| // 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) |
| |
| 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. |
| var cfg = -1 |
| 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, force flush and adjust colors. |
| flush() |
| cfg = col |
| GL11.glColor3ub( |
| ((cfg & 0xFF0000) >> 16).toByte, |
| ((cfg & 0x00FF00) >> 8).toByte, |
| ((cfg & 0x0000FF) >> 0).toByte) |
| } |
| listBuffer.put(charLists + index) |
| if (listBuffer.remaining == 0) |
| flush() |
| } |
| flush() |
| |
| GL11.glPopAttrib() |
| GL11.glPopMatrix() |
| } |
| |
| private val bgu1 = 254.0 / 256.0 |
| private val bgu2 = 255.0 / 256.0 |
| private val bgv1 = 255.0 / 256.0 |
| private val bgv2 = 256.0 / 256.0 |
| |
| private def draw(color: Int, offset: Int, width: Int) = if (color != 0 && width > 0) { |
| // 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). |
| GL11.glBegin(GL11.GL_QUADS) |
| GL11.glColor3ub(((color >> 16) & 0xFF).toByte, ((color >> 8) & 0xFF).toByte, (color & 0xFF).toByte) |
| GL11.glTexCoord2d(bgu1, bgv2) |
| GL11.glVertex3d(charWidth * offset, charHeight, 0) |
| GL11.glTexCoord2d(bgu2, bgv2) |
| GL11.glVertex3d(charWidth * (offset + width), charHeight, 0) |
| GL11.glTexCoord2d(bgu2, bgv1) |
| GL11.glVertex3d(charWidth * (offset + width), 0, 0) |
| GL11.glTexCoord2d(bgu1, bgv1) |
| GL11.glVertex3d(charWidth * offset, 0, 0) |
| GL11.glEnd() |
| } |
| |
| private def flush() = if (listBuffer.position > 0) { |
| listBuffer.flip() |
| GL11.glCallLists(listBuffer) |
| listBuffer.clear() |
| } |
| } |
| |
| } |