diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 071a5f818c..49f0df4478 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -34,6 +34,12 @@ opencomputers { # Wikipedia.) textLinearFiltering: false + # Which text renderer to use for screens. Available options include: + # - auto: Let the mod decide on its own. Preferred. + # - vbo: New, VBO-based renderer. + # - displaylist: Old, display list-based renderer. + textRenderingEngine: "auto" + # If you prefer the text on the screens to be aliased (you know, *not* # anti-aliased / smoothed) turn this option off. textAntiAlias: true diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 39c6a2c762..f125d64985 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -33,9 +33,10 @@ class Settings(val config: Config) { val maxScreenTextRenderDistance = config.getDouble("client.maxScreenTextRenderDistance") val textLinearFiltering = config.getBoolean("client.textLinearFiltering") val textAntiAlias = config.getBoolean("client.textAntiAlias") + val textRenderingEngine = config.getString("client.textRenderingEngine") val robotLabels = config.getBoolean("client.robotLabels") - val soundVolume = config.getDouble("client.soundVolume").toFloat max 0 min 2 - val fontCharScale = config.getDouble("client.fontCharScale") max 0.5 min 2 + val soundVolume = config.getDouble("client.soundVolume").toFloat max 0.0f min 2.0f + val fontCharScale = config.getDouble("client.fontCharScale").toFloat max 0.5f min 2.0f val hologramFadeStartDistance = config.getDouble("client.hologramFadeStartDistance") max 0 val hologramRenderDistance = config.getDouble("client.hologramRenderDistance") max 0 val hologramFlickerFrequency = config.getDouble("client.hologramFlickerFrequency") max 0 diff --git a/src/main/scala/li/cil/oc/client/Proxy.scala b/src/main/scala/li/cil/oc/client/Proxy.scala index c8a912dbeb..7d88aa5a1b 100644 --- a/src/main/scala/li/cil/oc/client/Proxy.scala +++ b/src/main/scala/li/cil/oc/client/Proxy.scala @@ -6,11 +6,11 @@ import li.cil.oc.client import li.cil.oc.client.renderer.HighlightRenderer import li.cil.oc.client.renderer.MFUTargetRenderer import li.cil.oc.client.renderer.PetRenderer -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.WirelessNetworkDebugRenderer import li.cil.oc.client.renderer.block.ModelInitialization import li.cil.oc.client.renderer.block.NetSplitterModel import li.cil.oc.client.renderer.entity.DroneRenderer +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache import li.cil.oc.client.renderer.tileentity._ import li.cil.oc.common.component.TextBuffer import li.cil.oc.common.entity.Drone diff --git a/src/main/scala/li/cil/oc/client/gui/Drone.scala b/src/main/scala/li/cil/oc/client/gui/Drone.scala index e144077cc9..796731769f 100644 --- a/src/main/scala/li/cil/oc/client/gui/Drone.scala +++ b/src/main/scala/li/cil/oc/client/gui/Drone.scala @@ -3,8 +3,8 @@ package li.cil.oc.client.gui import li.cil.oc.Localization import li.cil.oc.client.Textures import li.cil.oc.client.gui.widget.ProgressBar -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.font.TextBufferRenderData +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.common.container import li.cil.oc.common.entity 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 a2ad0ec5dd..f028b990d9 100644 --- a/src/main/scala/li/cil/oc/client/gui/Robot.scala +++ b/src/main/scala/li/cil/oc/client/gui/Robot.scala @@ -6,8 +6,8 @@ import li.cil.oc.api import li.cil.oc.api.internal.TextBuffer import li.cil.oc.client.Textures import li.cil.oc.client.gui.widget.ProgressBar -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.gui.BufferRenderer +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.common.container import li.cil.oc.common.tileentity @@ -56,9 +56,9 @@ class Robot(playerInventory: InventoryPlayer, val robot: tileentity.Robot) exten private val maxBufferWidth = 240.0 private val maxBufferHeight = 140.0 - private def bufferRenderWidth = math.min(maxBufferWidth, TextBufferRenderCache.renderer.charRenderWidth * Settings.screenResolutionsByTier(0)._1) + private def bufferRenderWidth = math.min(maxBufferWidth, TextBufferRenderCache.fontTextureProvider.getCharWidth / 2 * Settings.screenResolutionsByTier(0)._1) - private def bufferRenderHeight = math.min(maxBufferHeight, TextBufferRenderCache.renderer.charRenderHeight * Settings.screenResolutionsByTier(0)._2) + private def bufferRenderHeight = math.min(maxBufferHeight, TextBufferRenderCache.fontTextureProvider.getCharHeight / 2 * Settings.screenResolutionsByTier(0)._2) override protected def bufferX: Int = (8 + (maxBufferWidth - bufferRenderWidth) / 2).toInt @@ -245,8 +245,8 @@ class Robot(playerInventory: InventoryPlayer, val robot: tileentity.Robot) exten } override protected def changeSize(w: Double, h: Double, recompile: Boolean): Double = { - val bw = w * TextBufferRenderCache.renderer.charRenderWidth - val bh = h * TextBufferRenderCache.renderer.charRenderHeight + val bw = w * TextBufferRenderCache.fontTextureProvider.getCharWidth / 2 + val bh = h * TextBufferRenderCache.fontTextureProvider.getCharHeight / 2 val scaleX = math.min(bufferRenderWidth / bw, 1) val scaleY = math.min(bufferRenderHeight / bh, 1) if (recompile) { 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 104b2518a5..5a7e2e6fec 100644 --- a/src/main/scala/li/cil/oc/client/gui/Screen.scala +++ b/src/main/scala/li/cil/oc/client/gui/Screen.scala @@ -1,8 +1,8 @@ package li.cil.oc.client.gui import li.cil.oc.api -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.gui.BufferRenderer +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache import li.cil.oc.util.RenderState import net.minecraft.client.renderer.GlStateManager import org.lwjgl.input.Mouse @@ -82,8 +82,8 @@ class Screen(val buffer: api.internal.TextBuffer, val hasMouse: Boolean, val has } private def toBufferCoordinates(mouseX: Int, mouseY: Int): Option[(Double, Double)] = { - val bx = (mouseX - x - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderWidth - val by = (mouseY - y - bufferMargin) / scale / TextBufferRenderCache.renderer.charRenderHeight + val bx = (mouseX - x - bufferMargin) / scale / (TextBufferRenderCache.fontTextureProvider.getCharWidth / 2) + val by = (mouseY - y - bufferMargin) / scale / (TextBufferRenderCache.fontTextureProvider.getCharHeight / 2) val bw = buffer.getViewportWidth val bh = buffer.getViewportHeight if (bx >= 0 && by >= 0 && bx < bw && by < bh) Some((bx, by)) diff --git a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala deleted file mode 100644 index e025e22283..0000000000 --- a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala +++ /dev/null @@ -1,119 +0,0 @@ -package li.cil.oc.client.renderer - -import java.util.concurrent.Callable -import java.util.concurrent.TimeUnit - -import com.google.common.cache.CacheBuilder -import com.google.common.cache.RemovalListener -import com.google.common.cache.RemovalNotification -import li.cil.oc.Settings -import li.cil.oc.client.renderer.font.TextBufferRenderData -import li.cil.oc.util.RenderState -import net.minecraft.client.renderer.GLAllocation -import net.minecraft.client.renderer.GlStateManager -import net.minecraft.tileentity.TileEntity -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent -import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent -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() - - private val cache = com.google.common.cache.CacheBuilder.newBuilder(). - expireAfterAccess(2, TimeUnit.SECONDS). - removalListener(this). - 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) - GlStateManager.enableTexture2D() - GlStateManager.depthMask(true) - GlStateManager.color(1, 1, 1, 1) - - // Because display lists and the GlStateManager don't like each other, apparently. - GL11.glEnable(GL11.GL_TEXTURE_2D) - RenderState.bindTexture(0) - GL11.glDepthMask(true) - GL11.glColor4f(1, 1, 1, 1) - - RenderState.disableBlend() - - RenderState.checkError(getClass.getName + ".compileOrDraw: glCallList") - } - } - - // ----------------------------------------------------------------------- // - // Cache - // ----------------------------------------------------------------------- // - - def call = { - RenderState.checkError(getClass.getName + ".call: entering (aka: wasntme)") - - val list = GLAllocation.generateDisplayLists(1) - currentBuffer.dirty = true // Force compilation. - - RenderState.checkError(getClass.getName + ".call: leaving") - - list - } - - def onRemoval(e: RemovalNotification[TileEntity, Int]) { - RenderState.checkError(getClass.getName + ".onRemoval: entering (aka: wasntme)") - - GLAllocation.deleteDisplayLists(e.getValue) - - RenderState.checkError(getClass.getName + ".onRemoval: leaving") - } - - // ----------------------------------------------------------------------- // - // ITickHandler - // ----------------------------------------------------------------------- // - - @SubscribeEvent - def onTick(e: ClientTickEvent) = cache.cleanUp() -} diff --git a/src/main/scala/li/cil/oc/client/renderer/font/CouldNotFitTextureException.java b/src/main/scala/li/cil/oc/client/renderer/font/CouldNotFitTextureException.java new file mode 100644 index 0000000000..d681f12f9a --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/CouldNotFitTextureException.java @@ -0,0 +1,11 @@ +package li.cil.oc.client.renderer.font; + +public class CouldNotFitTextureException extends RuntimeException { + public CouldNotFitTextureException() { + super(); + } + + public CouldNotFitTextureException(String s) { + super(s); + } +} 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..b9136c19e0 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 @@ -4,6 +4,7 @@ import li.cil.oc.Settings; import li.cil.oc.util.FontUtils; import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResource; import net.minecraft.util.ResourceLocation; import org.lwjgl.BufferUtils; @@ -14,9 +15,6 @@ import java.nio.ByteBuffer; public class FontParserHex implements IGlyphProvider { - private static final byte[] OPAQUE = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; - private static final byte[] TRANSPARENT = {0, 0, 0, 0}; - private final byte[][] glyphs = new byte[FontUtils.codepoint_limit()][]; @Override @@ -24,60 +22,56 @@ public void initialize() { for (int i = 0; i < glyphs.length; ++i) { glyphs[i] = null; } - try { - final InputStream font = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation(Settings.resourceDomain(), "font.hex")).getInputStream(); - try { - OpenComputers.log().info("Initializing unicode glyph provider."); - final BufferedReader input = new BufferedReader(new InputStreamReader(font)); - String line; - int glyphCount = 0; - while ((line = input.readLine()) != null) { - final String[] info = line.split(":"); - final int charCode = Integer.parseInt(info[0], 16); - if (charCode < 0 || charCode >= glyphs.length) continue; // Out of bounds. - final int expectedWidth = FontUtils.wcwidth(charCode); - if (expectedWidth < 1) continue; // Skip control characters. - // Two chars representing one byte represent one row of eight pixels. - final byte[] glyph = new byte[info[1].length() >> 1]; - final int glyphWidth = glyph.length / getGlyphHeight(); - if (expectedWidth == glyphWidth) { - for (int i = 0; i < glyph.length; i++) { - glyph[i] = (byte) Integer.parseInt(info[1].substring(i * 2, i * 2 + 2), 16); - } - if (glyphs[charCode] == null) { - glyphCount++; - } - glyphs[charCode] = glyph; - } else if (Settings.get().logHexFontErrors()) { - OpenComputers.log().warn(String.format("Size of glyph for code point U+%04X (%s) in font (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth)); + ResourceLocation fontLocation = new ResourceLocation(Settings.resourceDomain(), "font.hex"); + try (IResource fontResource = Minecraft.getMinecraft().getResourceManager().getResource(fontLocation); InputStream font = fontResource.getInputStream()){ + OpenComputers.log().info("Initializing unicode glyph provider."); + final BufferedReader input = new BufferedReader(new InputStreamReader(font)); + String line; + int glyphCount = 0; + while ((line = input.readLine()) != null) { + final String[] info = line.split(":"); + final int charCode = Integer.parseInt(info[0], 16); + if (charCode < 0 || charCode >= glyphs.length) continue; // Out of bounds. + final int expectedWidth = FontUtils.wcwidth(charCode); + if (expectedWidth < 1) continue; // Skip control characters. + // Two chars representing one byte represent one row of eight pixels. + final byte[] glyph = new byte[info[1].length() >> 1]; + final int glyphWidth = glyph.length / getGlyphHeight(); + if (expectedWidth == glyphWidth) { + for (int i = 0; i < glyph.length; i++) { + glyph[i] = (byte) Integer.parseInt(info[1].substring(i * 2, i * 2 + 2), 16); } - } - OpenComputers.log().info("Loaded " + glyphCount + " glyphs."); - } finally { - try { - font.close(); - } catch (IOException ex) { - OpenComputers.log().warn("Error parsing font.", ex); + if (glyphs[charCode] == null) { + glyphCount++; + } + glyphs[charCode] = glyph; + } else if (Settings.get().logHexFontErrors()) { + OpenComputers.log().warn(String.format("Size of glyph for code point U+%04X (%s) in font (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth)); } } + OpenComputers.log().info("Loaded " + glyphCount + " glyphs."); } catch (IOException ex) { OpenComputers.log().warn("Failed loading glyphs.", ex); } } + @Override + public boolean containsGlyph(int charCode) { + return charCode >= 0 && charCode < glyphs.length && glyphs[charCode] != null && glyphs[charCode].length != 0; + } + @Override public ByteBuffer getGlyph(int charCode) { - if (charCode < 0 || charCode >= glyphs.length || glyphs[charCode] == null || glyphs[charCode].length == 0) + if (!containsGlyph(charCode)) return null; final byte[] glyph = glyphs[charCode]; - final ByteBuffer buffer = BufferUtils.createByteBuffer(glyph.length * getGlyphWidth() * 4); + final ByteBuffer buffer = BufferUtils.createByteBuffer(glyph.length * getGlyphWidth()); for (byte aGlyph : glyph) { 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(OPAQUE); - else buffer.put(TRANSPARENT); + final boolean isBitSet = (c & 0x80) != 0; + buffer.put(isBitSet ? (byte)255 : 0); c <<= 1; } } diff --git a/src/main/scala/li/cil/oc/client/renderer/font/FontTextureProvider.java b/src/main/scala/li/cil/oc/client/renderer/font/FontTextureProvider.java new file mode 100644 index 0000000000..5078d41852 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/FontTextureProvider.java @@ -0,0 +1,17 @@ +package li.cil.oc.client.renderer.font; + +public interface FontTextureProvider { + interface Receiver { + void draw(float x1, float x2, float y1, float y2, float u1, float u2, float v1, float v2); + } + + int getCharWidth(); + int getCharHeight(); + + boolean isDynamic(); + int getTextureCount(); + void begin(int texture); + void loadCodePoint(int codePoint); + void drawCodePoint(int codePoint, float tx, float ty, Receiver receiver); + void end(int texture); +} 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..c94bbf1eca 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 @@ -4,7 +4,7 @@ /** * Common interface for classes providing glyph data in a format that can be - * rendered using the {@link li.cil.oc.client.renderer.font.DynamicFontRenderer}. + * rendered using the {@link li.cil.oc.client.renderer.font.FontTextureProvider}. */ public interface IGlyphProvider { /** @@ -14,6 +14,8 @@ public interface IGlyphProvider { */ void initialize(); + boolean containsGlyph(int charCode); + /** * Get a byte array of RGBA data describing the specified char. *

diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/MultiDynamicGlyphFontTextureProvider.scala similarity index 51% rename from src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala rename to src/main/scala/li/cil/oc/client/renderer/font/MultiDynamicGlyphFontTextureProvider.scala index 1e178b7052..1757e9fa84 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/MultiDynamicGlyphFontTextureProvider.scala @@ -1,7 +1,11 @@ package li.cil.oc.client.renderer.font +import java.nio.ByteBuffer + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import li.cil.oc.Settings -import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture +import li.cil.oc.client.renderer.font.MultiDynamicGlyphFontTextureProvider.{CharIcon, CharTexture} +import li.cil.oc.client.renderer.font.FontTextureProvider.Receiver import li.cil.oc.util.FontUtils import li.cil.oc.util.RenderState import net.minecraft.client.Minecraft @@ -14,18 +18,10 @@ 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() - } - +class MultiDynamicGlyphFontTextureProvider(private val glyphProvider: IGlyphProvider) extends FontTextureProvider with IResourceManagerReloadListener { private val textures = mutable.ArrayBuffer.empty[CharTexture] - private val charMap = mutable.Map.empty[Char, DynamicFontRenderer.CharIcon] + private val charMap = new Int2ObjectOpenHashMap[MultiDynamicGlyphFontTextureProvider.CharIcon] private var activeTexture: CharTexture = _ @@ -40,11 +36,13 @@ class DynamicFontRenderer extends TextureFontRenderer with IResourceManagerReloa for (texture <- textures) { texture.delete() } + textures.clear() charMap.clear() - textures += new DynamicFontRenderer.CharTexture(this) + textures += new MultiDynamicGlyphFontTextureProvider.CharTexture(this) activeTexture = textures.head - generateChars(basicChars.toCharArray) + + StaticTextureFontTextureProvider.basicChars.foreach(c => getCodePoint(c)) } def onResourceManagerReload(manager: IResourceManager) { @@ -52,57 +50,79 @@ class DynamicFontRenderer extends TextureFontRenderer with IResourceManagerReloa initialize() } - override protected def charWidth = glyphProvider.getGlyphWidth + private def charWidth = glyphProvider.getGlyphWidth + + private def charHeight = glyphProvider.getGlyphHeight - override protected def charHeight = glyphProvider.getGlyphHeight + override def getCharWidth: Int = charWidth - override protected def textureCount = textures.length + override def getCharHeight: Int = charHeight - override protected def bindTexture(index: Int) { - activeTexture = textures(index) + override def getTextureCount: Int = textures.size + + override def begin(tex: Int): Unit = { + activeTexture = textures(tex) activeTexture.bind() - RenderState.checkError(getClass.getName + ".bindTexture") + RenderState.checkError(getClass.getName + ".begin") } - override protected def generateChar(char: Char) { - charMap.getOrElseUpdate(char, createCharIcon(char)) + override def end(tex: Int): Unit = { + } - override protected def drawChar(tx: Float, ty: Float, char: Char) { + private def getCodePoint(char: Int): CharIcon = { charMap.get(char) match { - case Some(icon) if icon.texture == activeTexture => icon.draw(tx, ty) - case _ => + case icon: CharIcon => icon + case null => + val result = createCharIcon(char) + charMap.put(char, result) + result } } - private def createCharIcon(char: Char): DynamicFontRenderer.CharIcon = { + override def drawCodePoint(char: Int, tx: Float, ty: Float, receiver: Receiver) { + getCodePoint(char) match { + case icon: CharIcon => + if (icon.texture == activeTexture) { + receiver.draw(tx, tx + icon.w, ty, ty + icon.h, icon.u1, icon.u2, icon.v1, icon.v2) + } + case null => + } + } + + private def createCharIcon(char: Int): MultiDynamicGlyphFontTextureProvider.CharIcon = { if (FontUtils.wcwidth(char) < 1 || glyphProvider.getGlyph(char) == null) { if (char == '?') null - else charMap.getOrElseUpdate('?', createCharIcon('?')) + else getCodePoint('?') } else { if (textures.last.isFull(char)) { - textures += new DynamicFontRenderer.CharTexture(this) + textures += new MultiDynamicGlyphFontTextureProvider.CharTexture(this) textures.last.bind() } textures.last.add(char) } } + + override def isDynamic: Boolean = true + + override def loadCodePoint(codePoint: Int): Unit = getCodePoint(codePoint) } -object DynamicFontRenderer { - private val size = 256 +object MultiDynamicGlyphFontTextureProvider { + private val size = 512 - class CharTexture(val owner: DynamicFontRenderer) { + class CharTexture(val owner: MultiDynamicGlyphFontTextureProvider) { private val id = GlStateManager.generateTexture() RenderState.bindTexture(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_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)) + + GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_INTENSITY8, size, size, 0, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null.asInstanceOf[ByteBuffer]) RenderState.bindTexture(0) RenderState.checkError(getClass.getName + ".: create texture") @@ -112,9 +132,9 @@ object DynamicFontRenderer { 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 uStep = cellWidth / size.toFloat + private val vStep = cellHeight / size.toFloat + private val pad = 1.0f / size private val capacity = cols * rows private var chars = 0 @@ -127,9 +147,9 @@ object DynamicFontRenderer { RenderState.bindTexture(id) } - def isFull(char: Char) = chars + FontUtils.wcwidth(char) > capacity + def isFull(char: Int): Boolean = chars + FontUtils.wcwidth(char) > capacity - def add(char: Char) = { + def add(char: Int): CharIcon = { val glyphWidth = FontUtils.wcwidth(char) val w = owner.charWidth * glyphWidth val h = owner.charHeight @@ -141,7 +161,7 @@ object DynamicFontRenderer { val y = chars / cols RenderState.bindTexture(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)) + GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, w, h, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, owner.glyphProvider.getGlyph(char)) chars += glyphWidth @@ -149,17 +169,8 @@ object DynamicFontRenderer { } } - 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) - } + class CharIcon(val texture: CharTexture, val w: Int, val h: Int, 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/SingleGlyphFontTextureProvider.scala b/src/main/scala/li/cil/oc/client/renderer/font/SingleGlyphFontTextureProvider.scala new file mode 100644 index 0000000000..66041a7357 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/SingleGlyphFontTextureProvider.scala @@ -0,0 +1,180 @@ +package li.cil.oc.client.renderer.font + +import java.nio.ByteBuffer + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import li.cil.oc.Settings +import li.cil.oc.client.renderer.font.FontTextureProvider.Receiver +import li.cil.oc.client.renderer.font.SingleGlyphFontTextureProvider.{CharIcon, CharTexture} +import li.cil.oc.util.{FontUtils, RenderState} +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.resources.{IReloadableResourceManager, IResourceManager, IResourceManagerReloadListener} +import net.minecraft.util.math.MathHelper +import org.lwjgl.BufferUtils +import org.lwjgl.opengl.GL11 + +class SingleGlyphFontTextureProvider(private val glyphProvider: IGlyphProvider, private val dynamic: Boolean) extends FontTextureProvider with IResourceManagerReloadListener { + private var texture: CharTexture = _ + + private val charMap = new Int2ObjectOpenHashMap[CharIcon] + + private var activeTexture: CharTexture = _ + + initialize() + + Minecraft.getMinecraft.getResourceManager match { + case reloadable: IReloadableResourceManager => reloadable.registerReloadListener(this) + case _ => + } + + def initialize() { + if (texture != null) { + texture.delete() + } + + // estimate texture size required + // be lazy about it, because most GPUs are way overkill for this... but it is 16MB + val maxTextureSize = Minecraft.getGLMaximumTextureSize + val worstCaseSideSize = 4096 // hardcoded for now... TODO + + if (maxTextureSize < worstCaseSideSize) { + throw new CouldNotFitTextureException() + } + + charMap.clear() + texture = new CharTexture(worstCaseSideSize, worstCaseSideSize, this) + + if (dynamic) { + // preload only basic chars + StaticTextureFontTextureProvider.basicChars.foreach(c => getCodePoint(c)) + } else { + for (i <- 0 until FontUtils.codepoint_limit) { + getCodePoint(i) + } + } + } + + def onResourceManagerReload(manager: IResourceManager) { + glyphProvider.initialize() + initialize() + } + + private def charWidth = glyphProvider.getGlyphWidth + + private def charHeight = glyphProvider.getGlyphHeight + + override def getCharWidth: Int = charWidth + + override def getCharHeight: Int = charHeight + + override def getTextureCount: Int = 1 + + override def begin(v: Int): Unit = { + texture.bind() + } + + override def end(v: Int): Unit = { + + } + + private def getCodePoint(char: Int): CharIcon = { + charMap.get(char) match { + case icon: CharIcon => icon + case null => + val result = createCharIcon(char) + charMap.put(char, result) + result + } + } + + override def drawCodePoint(char: Int, tx: Float, ty: Float, receiver: Receiver) { + getCodePoint(char) match { + case icon: CharIcon => + receiver.draw(tx, tx + icon.w, ty, ty + icon.h, icon.u1, icon.u2, icon.v1, icon.v2) + case null => + } + } + + private def createCharIcon(char: Int): CharIcon = { + if (FontUtils.wcwidth(char) < 1 || glyphProvider.getGlyph(char) == null) { + if (char == '?') null + else getCodePoint('?') + } + else { + if (texture.isFull(char)) { + throw new CouldNotFitTextureException() + } + + texture.add(char) + } + } + + override def isDynamic: Boolean = dynamic + + override def loadCodePoint(codePoint: Int): Unit = if (dynamic) getCodePoint(codePoint) +} + +object SingleGlyphFontTextureProvider { + class CharTexture(val xSize: Int, val ySize: Int, val owner: SingleGlyphFontTextureProvider) { + private val id = GlStateManager.generateTexture() + RenderState.bindTexture(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_INTENSITY8, xSize, ySize, 0, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null.asInstanceOf[ByteBuffer]) + RenderState.bindTexture(0) + + 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 = xSize / cellWidth + private val rows = ySize / cellHeight + private val uStep = cellWidth / xSize.toFloat + private val vStep = cellHeight / ySize.toFloat + private val padU = 1.0f / xSize + private val padV = 1.0f / ySize + private val capacity = cols * rows + + private var chars = 0 + + def delete() { + GlStateManager.deleteTexture(id) + } + + def bind() { + RenderState.bindTexture(id) + } + + def isFull(char: Int): Boolean = chars + FontUtils.wcwidth(char) > capacity + + def add(char: Int): CharIcon = { + 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 + + RenderState.bindTexture(id) + GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 1 + x * cellWidth, 1 + y * cellHeight, w, h, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, owner.glyphProvider.getGlyph(char)) + + chars += glyphWidth + + new CharIcon(w, h, padU + x * uStep, padV + y * vStep, (x + glyphWidth) * uStep - padU, (y + 1) * vStep - padV) + } + } + + class CharIcon(val w: Int, val h: Int, 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 deleted file mode 100644 index ff3a55f63d..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) { - Textures.bind(Textures.Font.AntiAliased) - } - else { - Textures.bind(Textures.Font.Aliased) - } - } - - 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/StaticTextureFontTextureProvider.scala b/src/main/scala/li/cil/oc/client/renderer/font/StaticTextureFontTextureProvider.scala new file mode 100644 index 0000000000..3c3d7ce820 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/font/StaticTextureFontTextureProvider.scala @@ -0,0 +1,88 @@ +package li.cil.oc.client.renderer.font + +import com.google.common.base.Charsets +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap +import li.cil.oc.{OpenComputers, Settings} +import li.cil.oc.client.Textures +import li.cil.oc.client.renderer.font.FontTextureProvider.Receiver +import net.minecraft.client.Minecraft +import net.minecraft.util.ResourceLocation + +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 StaticTextureFontTextureProvider extends FontTextureProvider { + 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) + (StaticTextureFontTextureProvider.basicChars, 10, 18) + } + + private val cols = 256.0f / charWidth + private val uStep = charWidth / 256.0f + private val uSize = uStep + private val vStep = (charHeight + 1) / 256.0f + private val vSize = charHeight / 256.0f + private val s = Settings.get.fontCharScale + private val dw = charWidth * s - charWidth + private val dh = charHeight * s - charHeight + + override def getCharWidth: Int = charWidth + + override def getCharHeight: Int = charHeight + + private val charsMap = new Int2IntOpenHashMap() + charsMap.defaultReturnValue(chars.indexOf('?')) + + for (idx <- 0 until chars.length) { + charsMap.put(chars.codePointAt(idx), idx) + } + + override def getTextureCount: Int = 1 + + override def begin(texture: Int) { + if (Settings.get.textAntiAlias) { + Textures.bind(Textures.Font.AntiAliased) + } + else { + Textures.bind(Textures.Font.Aliased) + } + } + + override def end(texture: Int) { + + } + + override def drawCodePoint(char: Int, tx: Float, ty: Float, receiver: Receiver) { + val index = charsMap.get(char) + if (index >= 0) { + val x = index % cols + val y = index / cols + val u = x * uStep + val v = y * vStep + receiver.draw(tx - dw, tx + charWidth * s, ty - dh, ty + charHeight * s, u, u + uSize, v, v + vSize) + } + } + + override def isDynamic: Boolean = false + + override def loadCodePoint(codePoint: Int): Unit = { + + } +} + +object StaticTextureFontTextureProvider { + final val basicChars = """☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■""" +} \ No newline at end of file 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 deleted file mode 100644 index d164830208..0000000000 --- a/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala +++ /dev/null @@ -1,176 +0,0 @@ -package li.cil.oc.client.renderer.font - -import li.cil.oc.Settings -import li.cil.oc.util.PackedColor -import li.cil.oc.util.RenderState -import li.cil.oc.util.TextBuffer -import net.minecraft.client.renderer.GlStateManager -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]) { - GlStateManager.enableTexture2D() - for (char <- chars) { - generateChar(char) - } - } - - def drawBuffer(buffer: TextBuffer, viewportWidth: Int, viewportHeight: Int) { - val format = buffer.format - - GlStateManager.pushMatrix() - RenderState.pushAttrib() - - GlStateManager.scale(0.5f, 0.5f, 1) - - GL11.glDepthMask(false) - RenderState.makeItBlend() - 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 (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) - } - 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.glColor3f( - ((cfg & 0xFF0000) >> 16) / 255f, - ((cfg & 0x00FF00) >> 8) / 255f, - ((cfg & 0x0000FF) >> 0) / 255f) - } - // Don't render whitespace. - if (ch != ' ') { - drawChar(tx, ty, ch) - } - tx += charWidth - } - GL11.glEnd() - } - } - - RenderState.checkError(getClass.getName + ".drawBuffer: foreground") - - GlStateManager.bindTexture(0) - GL11.glDepthMask(true) - GL11.glColor3f(1, 1, 1) - RenderState.disableBlend() - RenderState.popAttrib() - GlStateManager.popMatrix() - - RenderState.checkError(getClass.getName + ".drawBuffer: leaving") - } - - def drawString(s: String, x: Int, y: Int): Unit = { - GlStateManager.pushMatrix() - RenderState.pushAttrib() - - GlStateManager.translate(x, y, 0) - GlStateManager.scale(0.5f, 0.5f, 1) - GlStateManager.depthMask(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() - } - - RenderState.popAttrib() - GlStateManager.popMatrix() - GlStateManager.color(1, 1, 1) - } - - 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 - GlStateManager.color( - ((color >> 16) & 0xFF) / 255f, - ((color >> 8) & 0xFF) / 255f, - (color & 0xFF) / 255f) - 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 692019483f..67d2d1fdb0 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 @@ -1,14 +1,12 @@ package li.cil.oc.client.renderer.markdown.segment -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.markdown.MarkupFormat +import li.cil.oc.client.renderer.textbuffer.{TextBufferRenderCache, TextBufferRendererDisplayList} import net.minecraft.client.gui.FontRenderer import net.minecraft.client.renderer.GlStateManager 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) GlStateManager.color(0.75f, 0.8f, 1, 1) - TextBufferRenderCache.renderer.drawString(part, currentX, currentY) + TextBufferRendererDisplayList.drawString(TextBufferRenderCache.fontTextureProvider, part, currentX, currentY) currentX = x + wrapIndent currentY += lineHeight(renderer) chars = chars.drop(numChars).dropWhile(_.isWhitespace) @@ -29,7 +27,7 @@ private[markdown] class CodeSegment(val parent: Segment, val text: String) exten override protected def ignoreLeadingWhitespace: Boolean = false - override protected def stringWidth(s: String, renderer: FontRenderer): Int = s.length * TextBufferRenderCache.renderer.charRenderWidth + override protected def stringWidth(s: String, renderer: FontRenderer): Int = s.length * TextBufferRenderCache.fontTextureProvider.getCharWidth / 2 override def toString(format: MarkupFormat.Value): String = format match { case MarkupFormat.Markdown => s"`$text`" diff --git a/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderCache.scala b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderCache.scala new file mode 100644 index 0000000000..2b4f0df8d8 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderCache.scala @@ -0,0 +1,104 @@ +package li.cil.oc.client.renderer.textbuffer + +import java.util.concurrent.{Callable, TimeUnit} + +import com.google.common.cache.{CacheBuilder, RemovalListener, RemovalNotification} +import li.cil.oc.{OpenComputers, Settings} +import li.cil.oc.client.renderer.font.{CouldNotFitTextureException, FontParserHex, FontTextureProvider, MultiDynamicGlyphFontTextureProvider, SingleGlyphFontTextureProvider, StaticTextureFontTextureProvider, TextBufferRenderData} +import li.cil.oc.util.RenderState +import net.minecraft.client.Minecraft +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent + +object TextBufferRenderCache extends Callable[TextBufferRenderer] with RemovalListener[TextBufferRenderData, TextBufferRenderer] { + private val cache = com.google.common.cache.CacheBuilder.newBuilder(). + expireAfterAccess(2, TimeUnit.SECONDS). + removalListener(this). + asInstanceOf[CacheBuilder[TextBufferRenderData, TextBufferRenderer]]. + build[TextBufferRenderData, TextBufferRenderer]() + + val fontTextureProvider: FontTextureProvider = { + if (Settings.get.fontRenderer == "texture") { + new StaticTextureFontTextureProvider() + } + else { + val glyphProvider = new FontParserHex() + try { + new SingleGlyphFontTextureProvider(glyphProvider, false) + } catch { + case _: CouldNotFitTextureException => { + OpenComputers.log.warn("Could not fit font into maximum texture; using slow fallback renderer.") + new MultiDynamicGlyphFontTextureProvider(glyphProvider) + } + } + } + } + + private val profiler = Minecraft.getMinecraft.mcProfiler + + // To allow access in cache entry init. + private var currentBuffer: TextBufferRenderData = _ + + private val renderingEngine: String = Settings.get.textRenderingEngine match { + case "vbo" => + if (TextBufferRendererVBO.isSupported(fontTextureProvider)) { + "vbo" + } else { + OpenComputers.log.error("Could not initialize VBO-based renderer! Using fallback.") + "displaylist" + } + case "displaylist" => "displaylist" + case _ => + if (TextBufferRendererVBO.isSupported(fontTextureProvider)) { + "vbo" + } else { + "displaylist" + } + } + + OpenComputers.log.info("Using text rendering engine '" + renderingEngine + "'.") + + // ----------------------------------------------------------------------- // + // Rendering + // ----------------------------------------------------------------------- // + + def render(buffer: TextBufferRenderData) { + currentBuffer = buffer + cache.get(currentBuffer, this).render(profiler, fontTextureProvider, buffer) + } + + // ----------------------------------------------------------------------- // + // Cache + // ----------------------------------------------------------------------- // + + def call = { + RenderState.checkError(getClass.getName + ".call: entering (aka: wasntme)") + + val renderer = renderingEngine match { + case "vbo" => new TextBufferRendererVBO() + case "displaylist" => new TextBufferRendererDisplayList() + case other => throw new RuntimeException("Unknown renderingEngine: " + other) + } + + currentBuffer.dirty = true // Force compilation. + + RenderState.checkError(getClass.getName + ".call: leaving") + + renderer.asInstanceOf[TextBufferRenderer] + } + + def onRemoval(e: RemovalNotification[TextBufferRenderData, TextBufferRenderer]) { + RenderState.checkError(getClass.getName + ".onRemoval: entering (aka: wasntme)") + + e.getValue.destroy() + + RenderState.checkError(getClass.getName + ".onRemoval: leaving") + } + + // ----------------------------------------------------------------------- // + // ITickHandler + // ----------------------------------------------------------------------- // + + @SubscribeEvent + def onTick(e: ClientTickEvent) = cache.cleanUp() +} diff --git a/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderer.java b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderer.java new file mode 100644 index 0000000000..80c7c0782a --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRenderer.java @@ -0,0 +1,10 @@ +package li.cil.oc.client.renderer.textbuffer; + +import li.cil.oc.client.renderer.font.FontTextureProvider; +import li.cil.oc.client.renderer.font.TextBufferRenderData; +import net.minecraft.profiler.Profiler; + +public interface TextBufferRenderer { + boolean render(Profiler profiler, FontTextureProvider fontTextureProvider, TextBufferRenderData data); + boolean destroy(); +} diff --git a/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererDisplayList.scala b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererDisplayList.scala new file mode 100644 index 0000000000..f42af57402 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererDisplayList.scala @@ -0,0 +1,239 @@ +package li.cil.oc.client.renderer.textbuffer +import li.cil.oc.Settings +import li.cil.oc.client.renderer.font.FontTextureProvider.Receiver +import li.cil.oc.client.renderer.font.{FontTextureProvider, TextBufferRenderData} +import li.cil.oc.util.{PackedColor, RenderState, TextBuffer} +import net.minecraft.client.renderer.{GLAllocation, GlStateManager} +import net.minecraft.profiler.Profiler +import org.lwjgl.opengl.GL11 + +class TextBufferRendererDisplayList extends TextBufferRenderer { + private val list = GLAllocation.generateDisplayLists(1) + + override def render(profiler: Profiler, fontTextureProvider: FontTextureProvider, currentBuffer: TextBufferRenderData): Boolean = { + if (currentBuffer.dirty) { + RenderState.checkError(getClass.getName + ".render: entering (aka: wasntme)") + + profiler.startSection("compile_list") + + val doCompile = !RenderState.compilingDisplayList + if (doCompile) { + if (fontTextureProvider.isDynamic) { + for (y <- 0 until (currentBuffer.viewport._2 min currentBuffer.data.height)) { + val line = currentBuffer.data.buffer(y) + for (n <- 0 until currentBuffer.viewport._1) { + fontTextureProvider.loadCodePoint(line(n)) + } + } + } + + currentBuffer.dirty = false + GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE) + + RenderState.checkError(getClass.getName + ".render: glNewList") + } + + drawBuffer(currentBuffer.data, fontTextureProvider, currentBuffer.viewport._1, currentBuffer.viewport._2) + + RenderState.checkError(getClass.getName + ".render: drawString") + + if (doCompile) { + GL11.glEndList() + + RenderState.checkError(getClass.getName + ".render: glEndList") + } + + RenderState.checkError(getClass.getName + ".render: leaving") + + profiler.endSection() + } + else { + profiler.startSection("execute_list") + + GL11.glCallList(list) + + RenderState.checkError(getClass.getName + ".render: glCallList") + + profiler.endSection() + } + + RenderState.disableBlend() + GlStateManager.enableTexture2D() + GlStateManager.depthMask(true) + GlStateManager.color(1, 1, 1, 1) + + // Because display lists and the GlStateManager don't like each other, apparently. + GL11.glEnable(GL11.GL_TEXTURE_2D) + RenderState.bindTexture(0) + GL11.glDepthMask(true) + GL11.glColor4f(1, 1, 1, 1) + + true + } + + override def destroy(): Boolean = { + GLAllocation.deleteDisplayLists(list) + + true + } + + private def drawBuffer(buffer: TextBuffer, fontTextureProvider: FontTextureProvider, viewportWidth: Int, viewportHeight: Int) { + val format = buffer.format + val charWidth = fontTextureProvider.getCharWidth + val charHeight = fontTextureProvider.getCharHeight + + GlStateManager.pushMatrix() + RenderState.pushAttrib() + + GlStateManager.scale(0.5f, 0.5f, 1) + + GL11.glDepthMask(false) + GL11.glDisable(GL11.GL_BLEND) + GL11.glDisable(GL11.GL_ALPHA_TEST) + 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 (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(charWidth, charHeight, cbg, x, y, width) + cbg = col + x += width + width = 0 + } + width = width + 1 + } + drawQuad(charWidth, charHeight, cbg, x, y, width) + } + GL11.glEnd() + + RenderState.checkError(getClass.getName + ".drawBuffer: background") + + GL11.glEnable(GL11.GL_TEXTURE_2D) + GL11.glEnable(GL11.GL_ALPHA_TEST) + GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.5f) + GlStateManager.alphaFunc(GL11.GL_GEQUAL, 0.5f) + + 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 (i <- 0 until fontTextureProvider.getTextureCount) { + fontTextureProvider.begin(i) + GL11.glBegin(GL11.GL_QUADS) + var cfg = -1 + for (y <- 0 until (viewportHeight min buffer.height)) { + val line = buffer.buffer(y) + val color = buffer.color(y) + var tx = 0f + val ty = y * charHeight + for (n <- 0 until viewportWidth) { + val ch = line(n) + val col = PackedColor.unpackForeground(color(n), format) + // Don't render whitespace. + if (ch != ' ') { + // Check if color changed. + if (col != cfg) { + cfg = col + GL11.glColor4f( + ((cfg & 0xFF0000) >> 16) / 255.0f, + ((cfg & 0x00FF00) >> 8) / 255.0f, + ((cfg & 0x0000FF) >> 0) / 255.0f, + 1.0f) + } + fontTextureProvider.drawCodePoint(ch, tx, ty, TextBufferRendererDisplayList.receiver) + } + tx += charWidth + } + } + GL11.glEnd() + fontTextureProvider.end(i) + } + + RenderState.checkError(getClass.getName + ".drawBuffer: foreground") + + GlStateManager.bindTexture(0) + GL11.glDepthMask(true) + GL11.glColor4f(1, 1, 1, 1) + GL11.glDisable(GL11.GL_ALPHA_TEST) + GlStateManager.disableAlpha() + GlStateManager.disableBlend() + RenderState.popAttrib() + GlStateManager.popMatrix() + + RenderState.checkError(getClass.getName + ".drawBuffer: leaving") + } + + private def drawQuad(charWidth: Int, charHeight: Int, color: Int, x: Int, y: Int, width: Int): Unit = if (color != 0 && width > 0) { + val x0 = x * charWidth + val x1 = (x + width) * charWidth + val y0 = y * charHeight + val y1 = (y + 1) * charHeight + GL11.glColor4f( + ((color >> 16) & 0xFF) / 255f, + ((color >> 8) & 0xFF) / 255f, + (color & 0xFF) / 255f, + 1.0f) + GL11.glVertex3f(x0, y1, 0) + GL11.glVertex3f(x1, y1, 0) + GL11.glVertex3f(x1, y0, 0) + GL11.glVertex3f(x0, y0, 0) + } +} + +object TextBufferRendererDisplayList { + private val receiver = new Receiver { + override def draw(x1: Float, x2: Float, y1: Float, y2: Float, u1: Float, u2: Float, v1: Float, v2: Float): Unit = { + GL11.glTexCoord2f(u1, v2) + GL11.glVertex2f(x1, y2) + GL11.glTexCoord2f(u2, v2) + GL11.glVertex2f(x2, y2) + GL11.glTexCoord2f(u2, v1) + GL11.glVertex2f(x2, y1) + GL11.glTexCoord2f(u1, v1) + GL11.glVertex2f(x1, y1) + } + } + + def drawString(fontTextureProvider: FontTextureProvider, s: String, x: Int, y: Int): Unit = { + GlStateManager.pushMatrix() + RenderState.pushAttrib() + + GlStateManager.translate(x, y, 0) + GlStateManager.scale(0.5f, 0.5f, 1) + GlStateManager.depthMask(false) + GlStateManager.enableTexture2D() + + for (i <- 0 until fontTextureProvider.getTextureCount) { + fontTextureProvider.begin(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 != ' ') { + fontTextureProvider.drawCodePoint(ch, tx, 0, receiver) + } + tx += fontTextureProvider.getCharWidth + } + GL11.glEnd() + fontTextureProvider.end(i) + } + + RenderState.popAttrib() + GlStateManager.popMatrix() + + GL11.glColor4f(1f, 1f, 1f, 1f) + GlStateManager.color(1, 1, 1) + } +} \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererVBO.scala b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererVBO.scala new file mode 100644 index 0000000000..a0e4b6d811 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/textbuffer/TextBufferRendererVBO.scala @@ -0,0 +1,269 @@ +package li.cil.oc.client.renderer.textbuffer + +import java.nio.{ByteBuffer, IntBuffer} + +import li.cil.oc.client.renderer.font.FontTextureProvider.Receiver +import li.cil.oc.client.renderer.font.{FontTextureProvider, TextBufferRenderData} +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache.fontTextureProvider +import li.cil.oc.util.{PackedColor, RenderState, TextBuffer} +import net.minecraft.client.Minecraft +import net.minecraft.client.renderer.{GLAllocation, GlStateManager} +import net.minecraft.profiler.Profiler +import net.minecraft.util.math.MathHelper +import org.lwjgl.opengl.{GL11, GL15, GLContext} + +class TextBufferRendererVBO extends TextBufferRenderer { + private val bgBufferVbo = GL15.glGenBuffers() + private val fgBufferVbo = GL15.glGenBuffers() + private var bgBufferElems = 0 + private var fgBufferElems = 0 + + override def render(profiler: Profiler, fontTextureProvider: FontTextureProvider, currentBuffer: TextBufferRenderData): Boolean = { + RenderState.checkError(getClass.getName + ".render: entering (aka: wasntme)") + + if (currentBuffer.dirty) { + val vboBuffer = TextBufferRendererVBO.getVboBuffer() + + profiler.startSection("vbo_build") + vboBuffer.drawBuffer(currentBuffer.data, fontTextureProvider, currentBuffer.viewport._1, currentBuffer.viewport._2) + profiler.endStartSection("vbo_upload") + vboBuffer.uploadBuffer(bgBufferVbo, fgBufferVbo) + RenderState.checkError(getClass.getName + ".render: uploading vbo)") + profiler.endStartSection("gl_draw_bg") + + bgBufferElems = vboBuffer.bgBufferElements() + fgBufferElems = vboBuffer.fgBufferElements() + + currentBuffer.dirty = false + } else { + profiler.startSection("gl_draw_bg") + } + + GlStateManager.pushMatrix() + RenderState.pushAttrib() + + GlStateManager.scale(0.5f, 0.5f, 1) + GlStateManager.color(1, 1, 1, 1) + + GlStateManager.depthMask(false) + GlStateManager.disableBlend() + GlStateManager.disableTexture2D() + GlStateManager.disableAlpha() + + RenderState.checkError(getClass.getName + ".render: configure state") + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bgBufferVbo) + GlStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY) + GlStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY) + + GlStateManager.glVertexPointer(2, GL11.GL_INT, 12, 4) + GlStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 12, 0) + + GL11.glDrawArrays(GL11.GL_QUADS, 0, bgBufferElems) + RenderState.checkError(getClass.getName + ".render: drawing bg arrays") + + profiler.endStartSection("gl_draw_fg") + + fontTextureProvider.begin(0) + + GlStateManager.enableAlpha() + GlStateManager.alphaFunc(GL11.GL_GEQUAL, 0.5f) + GlStateManager.enableTexture2D() + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, fgBufferVbo) + GlStateManager.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY) + + GlStateManager.glVertexPointer(2, GL11.GL_FLOAT, 20, 4) + GlStateManager.glTexCoordPointer(2, GL11.GL_FLOAT, 20, 12) + GlStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 20, 0) + + GL11.glDrawArrays(GL11.GL_QUADS, 0, fgBufferElems) + + fontTextureProvider.end(0) + + RenderState.checkError(getClass.getName + ".render: drawing fg arrays") + + GlStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY) + GlStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY) + GlStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY) + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0) + + RenderState.popAttrib() + GlStateManager.popMatrix() + + profiler.endSection() + + RenderState.checkError(getClass.getName + ".render: leaving") + + true + } + + override def destroy(): Boolean = { + GL15.glDeleteBuffers(bgBufferVbo) + GL15.glDeleteBuffers(fgBufferVbo) + + true + } +} + +object TextBufferRendererVBO { + private val vboBuffers = new ThreadLocal[VBOBuffer] { + override def initialValue(): VBOBuffer = new VBOBuffer() + } + + def getVboBuffer(): VBOBuffer = vboBuffers.get() + + def isSupported(fontTextureProvider: FontTextureProvider): Boolean = GLContext.getCapabilities.OpenGL15 && fontTextureProvider.getTextureCount == 1 +} + +class VBOBuffer extends Receiver { + val BG_ENTRY_SIZE: Int = 4 * 12 + val FG_ENTRY_SIZE: Int = 4 * 20 + + var bgBufferByte: ByteBuffer = GLAllocation.createDirectByteBuffer(MathHelper.smallestEncompassingPowerOfTwo(80 * 25 * BG_ENTRY_SIZE * 4)) + var fgBufferByte: ByteBuffer = GLAllocation.createDirectByteBuffer(MathHelper.smallestEncompassingPowerOfTwo(80 * 25 * FG_ENTRY_SIZE * 4)) + var bgBuffer: IntBuffer = bgBufferByte.asIntBuffer() + var fgBuffer: IntBuffer = fgBufferByte.asIntBuffer() + + private var bgBufferCount = 0 + private var fgBufferCount = 0 + private var currentColorFg = -1 + + def bgBufferElements(): Int = bgBufferCount * 4 + + def fgBufferElements(): Int = fgBufferCount * 4 + + def uploadBuffer(bgBufferVbo: Int, fgBufferVbo: Int) = { + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bgBufferVbo) + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bgBufferByte, GL15.GL_STATIC_DRAW) + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, fgBufferVbo) + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, fgBufferByte, GL15.GL_STATIC_DRAW) + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0) + } + + def drawBuffer(buffer: TextBuffer, fontTextureProvider: FontTextureProvider, viewportWidth: Int, viewportHeight: Int) { + val format = buffer.format + val charWidth = fontTextureProvider.getCharWidth + val charHeight = fontTextureProvider.getCharHeight + + bgBufferByte.clear() + fgBufferByte.clear() + bgBuffer.clear() + fgBuffer.clear() + + // Background first. We try to merge adjacent backgrounds of the same + // color to reduce the number of quads we have to draw. + 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) { + drawBgQuad(charWidth, charHeight, cbg, x, y, width) + cbg = col + x += width + width = 0 + } + width = width + 1 + } + drawBgQuad(charWidth, charHeight, cbg, x, y, 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. + for (y <- 0 until (viewportHeight min buffer.height)) { + val line = buffer.buffer(y) + val color = buffer.color(y) + val ty = y * charHeight + for (_ <- 0 until fontTextureProvider.getTextureCount) { + var tx = 0f + for (n <- 0 until viewportWidth) { + val ch = line(n) + // Don't render whitespace. + if (ch != ' ') { + currentColorFg = PackedColor.unpackForeground(color(n), format) | 0xFF000000 + fontTextureProvider.drawCodePoint(ch, tx, ty, this) + } + tx += charWidth + } + } + } + + bgBufferCount = bgBuffer.position() / (BG_ENTRY_SIZE / 4) + fgBufferCount = fgBuffer.position() / (FG_ENTRY_SIZE / 4) + + bgBufferByte.rewind() + fgBufferByte.rewind() + bgBuffer.rewind() + fgBuffer.rewind() + + bgBufferByte.limit(bgBufferCount * BG_ENTRY_SIZE) + fgBufferByte.limit(fgBufferCount * FG_ENTRY_SIZE) + } + + override def draw(x1: Float, x2: Float, y1: Float, y2: Float, u1: Float, u2: Float, v1: Float, v2: Float): Unit = { + growFgBuffer(FG_ENTRY_SIZE) + + val xf1 = java.lang.Float.floatToRawIntBits(x1) + val xf2 = java.lang.Float.floatToRawIntBits(x2) + val yf1 = java.lang.Float.floatToRawIntBits(y1) + val yf2 = java.lang.Float.floatToRawIntBits(y2) + val uf1 = java.lang.Float.floatToRawIntBits(u1) + val uf2 = java.lang.Float.floatToRawIntBits(u2) + val vf1 = java.lang.Float.floatToRawIntBits(v1) + val vf2 = java.lang.Float.floatToRawIntBits(v2) + val colorSwapped = currentColorFg & 0xFF00FF00 | ((currentColorFg & 0xFF0000) >> 16) | ((currentColorFg & 0xFF) << 16) + + fgBuffer.put(colorSwapped) + fgBuffer.put(xf1).put(yf2).put(uf1).put(vf2) + fgBuffer.put(colorSwapped) + fgBuffer.put(xf2).put(yf2).put(uf2).put(vf2) + fgBuffer.put(colorSwapped) + fgBuffer.put(xf2).put(yf1).put(uf2).put(vf1) + fgBuffer.put(colorSwapped) + fgBuffer.put(xf1).put(yf1).put(uf1).put(vf1) + } + + private def drawBgQuad(charWidth: Int, charHeight: Int, color: Int, x: Int, y: Int, width: Int): Unit = if (color != 0 && width > 0) { + val x0 = x * charWidth + val x1 = (x + width) * charWidth + val y0 = y * charHeight + val y1 = (y + 1) * charHeight + + growBgBuffer(BG_ENTRY_SIZE) + + val colorSwapped = color & 0xFF00FF00 | ((color & 0xFF0000) >> 16) | ((color & 0xFF) << 16) + + bgBuffer.put(colorSwapped) + bgBuffer.put(x0).put(y1) + bgBuffer.put(colorSwapped) + bgBuffer.put(x1).put(y1) + bgBuffer.put(colorSwapped) + bgBuffer.put(x1).put(y0) + bgBuffer.put(colorSwapped) + bgBuffer.put(x0).put(y0) + } + + def growBgBuffer(size: Int): Unit = { + while (bgBufferByte.remaining() < size) { + val oldBgBufferByte = bgBufferByte + oldBgBufferByte.position(0) + bgBufferByte = GLAllocation.createDirectByteBuffer(oldBgBufferByte.capacity() * 2) + bgBufferByte.put(oldBgBufferByte) + bgBuffer = bgBufferByte.asIntBuffer() + bgBuffer.position(bgBufferByte.position() >> 2) + } + } + + def growFgBuffer(size: Int): Unit = { + while (fgBufferByte.remaining() < size) { + val oldFgBufferByte = fgBufferByte + oldFgBufferByte.position(0) + fgBufferByte = GLAllocation.createDirectByteBuffer(oldFgBufferByte.capacity() * 2) + fgBufferByte.put(oldFgBufferByte) + fgBuffer = fgBufferByte.asIntBuffer() + fgBuffer.position(fgBufferByte.position() >> 2) + } + } +} \ No newline at end of file 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 7300228f9d..82e7a6ed18 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 @@ -88,7 +88,10 @@ object ScreenRenderer extends TileEntitySpecialRenderer[Screen] { RenderState.checkError(getClass.getName + ".render: fade") if (screen.buffer.isRenderingEnabled) { + val profiler = Minecraft.getMinecraft.mcProfiler + profiler.startSection("opencomputers:screen_buffer") draw() + profiler.endSection() } RenderState.disableBlend() 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 5bbf1512ad..2594af18cf 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -15,8 +15,8 @@ import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.network._ import li.cil.oc.api.prefab import li.cil.oc.api.prefab.AbstractManagedEnvironment -import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.font.TextBufferRenderData +import li.cil.oc.client.renderer.textbuffer.TextBufferRenderCache import li.cil.oc.client.{ComponentTracker => ClientComponentTracker} import li.cil.oc.client.{PacketSender => ClientPacketSender} import li.cil.oc.common._ @@ -452,10 +452,10 @@ class TextBuffer(val host: EnvironmentHost) extends AbstractManagedEnvironment w override def renderText() = relativeLitArea != 0 && proxy.render() @SideOnly(Side.CLIENT) - override def renderWidth = TextBufferRenderCache.renderer.charRenderWidth * getViewportWidth + override def renderWidth = TextBufferRenderCache.fontTextureProvider.getCharWidth / 2 * getViewportWidth @SideOnly(Side.CLIENT) - override def renderHeight = TextBufferRenderCache.renderer.charRenderHeight * getViewportHeight + override def renderHeight = TextBufferRenderCache.fontTextureProvider.getCharHeight / 2 * getViewportHeight @SideOnly(Side.CLIENT) override def setRenderingEnabled(enabled: Boolean) = isRendering = enabled diff --git a/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala b/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala index ae3e6c75a9..ede87453b4 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/traits/BundledRedstoneAware.scala @@ -15,6 +15,7 @@ import java.util import li.cil.oc.integration.charset.{CapabilitiesCharset, ModCharset} import net.minecraftforge.common.capabilities.Capability +@Optional.Interface(iface = "mrtjp.projectred.api.IBundledTile", modid = "projectred-core") trait BundledRedstoneAware extends RedstoneAware with IBundledTile { protected[tileentity] val _bundledInput: Array[Array[Int]] = Array.fill(6)(Array.fill(16)(-1)) diff --git a/src/main/scala/li/cil/oc/util/PackedColor.scala b/src/main/scala/li/cil/oc/util/PackedColor.scala index 7714986153..b5dc6834fe 100644 --- a/src/main/scala/li/cil/oc/util/PackedColor.scala +++ b/src/main/scala/li/cil/oc/util/PackedColor.scala @@ -32,6 +32,24 @@ object PackedColor { (r, g, b) } + private val colorCube685 = new Array[Int](240) + + { + val reds = 6 + val greens = 8 + val blues = 5 + + for (index <- colorCube685.indices) { + val idxB = index % blues + val idxG = (index / blues) % greens + val idxR = (index / blues / greens) % reds + val r = (idxR * 0xFF / (reds - 1.0) + 0.5).toInt + val g = (idxG * 0xFF / (greens - 1.0) + 0.5).toInt + val b = (idxB * 0xFF / (blues - 1.0) + 0.5).toInt + colorCube685(index) = (r << rShift32) | (g << gShift32) | (b << bShift32) + } + } + trait ColorFormat extends Persistable { def depth: api.internal.TextBuffer.ColorDepth @@ -132,13 +150,7 @@ object PackedColor { if (isFromPalette(value)) super.inflate(value) else { val index = value - palette.length - val idxB = index % blues - val idxG = (index / blues) % greens - val idxR = (index / blues / greens) % reds - val r = (idxR * 0xFF / (reds - 1.0) + 0.5).toInt - val g = (idxG * 0xFF / (greens - 1.0) + 0.5).toInt - val b = (idxB * 0xFF / (blues - 1.0) + 0.5).toInt - (r << rShift32) | (g << gShift32) | (b << bShift32) + if (index >= 0 && index < colorCube685.length) colorCube685(index) else 0 } override def deflate(value: Color) = {