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