diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index b0f705e5c..b5d0a5fbe 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -99,19 +99,19 @@ opencomputers { # Defaults to white, feel free to make it some other color, tho! monochromeColor: 0xFFFFFF - # Used to suppress log spam for OpenGL errors on derpy drivers. I'm - # quite certain the code in the font render is valid, display list - # compatible OpenGL, but it seems to cause 'invalid operation' errors - # when executed as a display list. I'd be happy to be proven wrong, - # since it'd restore some of my trust into AMD drivers... - logOpenGLErrors: false - - # Whether to use the old, font-texture based font renderer that was used - # in OC versions prior to 1.3.2. This will allow overriding the font - # texture as before. Keep in mind that this renderer is slightly less - # efficient than the new one, and more importantly, can only render - # code page 437 (as opposed to... a *lot* of unicode). - useOldTextureFontRenderer: false + # Which font renderer to use. Defaults to `unifont` if invalid. + # Possible values: + # - unifont: the (since 1.3.2) default font renderer. Based on the + # Unifont and capable of rendering many unicode glyphs. + # The used font data can be swapped out using resource packs, + # but is harder to work with, since it involves binary data. + # - texture: the old, font-texture based font renderer that was used + # in OC versions prior to 1.3.2. This will allow overriding + # the font texture as before. Keep in mind that this renderer + # is slightly less efficient than the new one, and more + # importantly, can only render code page 437 (as opposed to... + # a *lot* of unicode). + fontRenderer: "unifont" } # Computer related settings, concerns server performance and security. @@ -223,58 +223,6 @@ opencomputers { # performance on your server, increase this number a little (it should # never exceed 50, a single tick, though) to reduce CPU load even more. executionDelay: 12 - - # Debugging related settings. You usually don't want to touch these - # unless asked to do so by a developer. - debug { - # Forces the use of the LuaJ fallback instead of the native libraries. - # Use this if you have concerns using native libraries or experience - # issues with the native library. - forceLuaJ: false - - # This setting is meant for debugging errors that occur in Lua callbacks. - # Per default, if an error occurs and it has a message set, only the - # message is pushed back to Lua, and that's it. If you encounter weird - # errors or are developing an addon you'll want the stacktrace for those - # errors. Enabling this setting will log them to the game log. This is - # disabled per default to avoid spamming the log with inconsequentual - # exceptions such as IllegalArgumentExceptions and the like. - logCallbackErrors: false - - # Disable user data support. This means any otherwise supported - # userdata (implementing the Value interface) will not be pushed - # to the Lua state. - disableUserdata: false - - # Disable computer state persistence. This means that computers will - # automatically be rebooted when loaded after being unloaded, instead - # of resuming with their exection (it also means the state is not even - # saved). Only relevant when using the native library. - disablePersistence: false - - # Disable memory limit enforcement. This means Lua states can - # theoretically use as much memory as they want. Only relevant when - # using the native library. - disableMemoryLimit: false - - # Force the buffered file system to be case insensitive. This makes it - # impossible to have multiple files whose names only differ in their - # capitalization, which is commonly the case on Windows, for example. - # This only takes effect when bufferChanges is set to true. - forceCaseInsensitiveFS: false - - # Logs the full error when a native library fails to load. This is - # disabled by default to avoid spamming the log, since libraries are - # iterated until one works, so it's very likely for some to fail. Use - # this in case all libraries fail to load even though you'd expect one - # to work. - logFullNativeLibLoadErrors: false - - # Force loading one specific library, to avoid trying to load any - # others. Use this if you get warnings in the log or are told to do - # so for debugging purposes ;-) - forceNativeLibWithName: "" - } } # Robot related settings, what they may do and general balancing. @@ -802,6 +750,7 @@ opencomputers { threads: 4 } + # Switch and access point network message forwarding logic related stuff. switch { # The delay a switch has by default between relaying packets (in ticks). # WARNING: lowering this value will result in higher maximum CPU load, @@ -883,14 +832,6 @@ opencomputers { # joins a server / the first map in single player is opened). updateCheck: true - # On some platforms the native library can crash the game, so there are - # a few checks in place to avoid trying to load it in those cases. This - # is Windows XP and Windows Server 2003, right. If you think it might - # work nonetheless (newer builds of Server2k3 e.g.) you might want to - # try setting this to `true`. Use this at your own risk. If the game - # crashes as a result of setting this to `true` DO NOT REPORT IT. - alwaysTryNative: false - # The probability (or rather, weighted chance) that a program disk is # spawned as loot in a treasure chest. For reference, iron ingots have # a value of 10, gold ingots a value of 5 and and diamonds a value of 3. @@ -923,11 +864,6 @@ opencomputers { # this chance of breaking in the process. disassemblerBreakChance: 0.05 - # This is meant for debugging errors. Enabling this has a high impact - # on computers' save and load performance, so you should not enable - # this unless you're asked to. - verbosePersistenceErrors: false - # Whether to not show your special thinger (if you have one you know it). hideOwnSpecial: false @@ -936,4 +872,80 @@ opencomputers { # of said upgrade is enabled or not). allowItemStackInspection: false } + + # Settings that are intended for debugging issues, not for normal use. + # You usually don't want to touch these unless asked to do so by a developer. + debug { + # Forces the use of the LuaJ fallback instead of the native libraries. + # Use this if you have concerns using native libraries or experience + # issues with the native library. + forceLuaJ: false + + # This setting is meant for debugging errors that occur in Lua callbacks. + # Per default, if an error occurs and it has a message set, only the + # message is pushed back to Lua, and that's it. If you encounter weird + # errors or are developing an addon you'll want the stacktrace for those + # errors. Enabling this setting will log them to the game log. This is + # disabled per default to avoid spamming the log with inconsequentual + # exceptions such as IllegalArgumentExceptions and the like. + logCallbackErrors: false + + # Disable user data support. This means any otherwise supported + # userdata (implementing the Value interface) will not be pushed + # to the Lua state. + disableUserdata: false + + # Disable computer state persistence. This means that computers will + # automatically be rebooted when loaded after being unloaded, instead + # of resuming with their exection (it also means the state is not even + # saved). Only relevant when using the native library. + disablePersistence: false + + # Disable memory limit enforcement. This means Lua states can + # theoretically use as much memory as they want. Only relevant when + # using the native library. + disableMemoryLimit: false + + # Force the buffered file system to be case insensitive. This makes it + # impossible to have multiple files whose names only differ in their + # capitalization, which is commonly the case on Windows, for example. + # This only takes effect when bufferChanges is set to true. + forceCaseInsensitiveFS: false + + # Logs the full error when a native library fails to load. This is + # disabled by default to avoid spamming the log, since libraries are + # iterated until one works, so it's very likely for some to fail. Use + # this in case all libraries fail to load even though you'd expect one + # to work. + logFullNativeLibLoadErrors: false + + # Force loading one specific library, to avoid trying to load any + # others. Use this if you get warnings in the log or are told to do + # so for debugging purposes ;-) + forceNativeLibWithName: "" + + # Used to suppress log spam for OpenGL errors on derpy drivers. I'm + # quite certain the code in the font render is valid, display list + # compatible OpenGL, but it seems to cause 'invalid operation' errors + # when executed as a display list. I'd be happy to be proven wrong, + # since it'd restore some of my trust into AMD drivers... + logOpenGLErrors: false + + # On some platforms the native library can crash the game, so there are + # a few checks in place to avoid trying to load it in those cases. This + # is Windows XP and Windows Server 2003, right. If you think it might + # work nonetheless (newer builds of Server2k3 e.g.) you might want to + # try setting this to `true`. Use this at your own risk. If the game + # crashes as a result of setting this to `true` DO NOT REPORT IT. + alwaysTryNative: false + + # This is meant for debugging errors. Enabling this has a high impact + # on computers' save and load performance, so you should not enable + # this unless you're asked to. + verbosePersistenceErrors: false + + # Logs information about malformed glyphs (i.e. glyphs that deviate in + # width from what wcwidth says). + logUnifontErrors: false + } } \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index afc6ce8e1..73de7fa35 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -46,8 +46,7 @@ class Settings(config: Config) { Array(3.0, 4.0) } val monochromeColor = Integer.decode(config.getString("client.monochromeColor")) - val logOpenGLErrors = config.getBoolean("client.logOpenGLErrors") - val useOldTextureFontRenderer = config.getBoolean("client.useOldTextureFontRenderer") + val fontRenderer = config.getString("client.fontRenderer") // ----------------------------------------------------------------------- // // computer @@ -76,18 +75,6 @@ class Settings(config: Config) { val eraseTmpOnReboot = config.getBoolean("computer.eraseTmpOnReboot") val executionDelay = config.getInt("computer.executionDelay") max 0 - // ----------------------------------------------------------------------- // - // computer.debug - - val logLuaCallbackErrors = config.getBoolean("computer.debug.logCallbackErrors") - val forceLuaJ = config.getBoolean("computer.debug.forceLuaJ") - val allowUserdata = !config.getBoolean("computer.debug.disableUserdata") - val allowPersistence = !config.getBoolean("computer.debug.disablePersistence") - val limitMemory = !config.getBoolean("computer.debug.disableMemoryLimit") - val forceCaseInsensitive = config.getBoolean("computer.debug.forceCaseInsensitiveFS") - val logFullLibLoadErrors = config.getBoolean("computer.debug.logFullNativeLibLoadErrors") - val forceNativeLib = config.getString("computer.debug.forceNativeLibWithName") - // ----------------------------------------------------------------------- // // robot @@ -232,14 +219,27 @@ class Settings(config: Config) { Array(2, 4, 8) } val updateCheck = config.getBoolean("misc.updateCheck") - val alwaysTryNative = config.getBoolean("misc.alwaysTryNative") val lootProbability = config.getInt("misc.lootProbability") - val debugPersistence = config.getBoolean("misc.verbosePersistenceErrors") val geolyzerRange = config.getInt("misc.geolyzerRange") val geolyzerNoise = config.getDouble("misc.geolyzerNoise").toFloat max 0 val disassembleAllTheThings = config.getBoolean("misc.disassembleAllTheThings") val disassemblerBreakChance = config.getDouble("misc.disassemblerBreakChance") max 0 min 1 val hideOwnPet = config.getBoolean("misc.hideOwnSpecial") + + // ----------------------------------------------------------------------- // + // debug + val logLuaCallbackErrors = config.getBoolean("debug.logCallbackErrors") + val forceLuaJ = config.getBoolean("debug.forceLuaJ") + val allowUserdata = !config.getBoolean("debug.disableUserdata") + val allowPersistence = !config.getBoolean("debug.disablePersistence") + val limitMemory = !config.getBoolean("debug.disableMemoryLimit") + val forceCaseInsensitive = config.getBoolean("debug.forceCaseInsensitiveFS") + val logFullLibLoadErrors = config.getBoolean("debug.logFullNativeLibLoadErrors") + val forceNativeLib = config.getString("debug.forceNativeLibWithName") + val logOpenGLErrors = config.getBoolean("debug.logOpenGLErrors") + val logUnifontErrors = config.getBoolean("debug.logUnifontErrors") + val alwaysTryNative = config.getBoolean("debug.alwaysTryNative") + val debugPersistence = config.getBoolean("debug.verbosePersistenceErrors") } object Settings { @@ -337,6 +337,14 @@ object Settings { // reduced as discussed in #447. VersionRange.createFromVersionSpec("[1.3.0,1.3.3)") -> Array( "power.cost.chunkloaderCost" + ), + // Upgrading to version 1.3.4+, computer.debug category was moved to top + // level debug category for more flexibility and some other settings merged + // into that new category. + VersionRange.createFromVersionSpec("1.3.3") -> Array( + "computer.debug", + "misc.alwaysTryNative", + "misc.verbosePersistenceErrors" ) ) @@ -355,7 +363,12 @@ object Settings { for (path <- paths) { val fullPath = prefix + path OpenComputers.log.info(s"Updating setting '$fullPath'. ") - patched = patched.withValue(fullPath, defaults.getValue(fullPath)) + if (defaults.hasPath(fullPath)) { + patched = patched.withValue(fullPath, defaults.getValue(fullPath)) + } + else { + patched = patched.withoutPath(fullPath) + } } } } diff --git a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala index 2cfc4343d..3685009be 100644 --- a/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala +++ b/src/main/scala/li/cil/oc/client/renderer/TextBufferRenderCache.scala @@ -14,7 +14,7 @@ import org.lwjgl.opengl.GL11 object TextBufferRenderCache extends Callable[Int] with RemovalListener[TileEntity, Int] with ITickHandler { val renderer = - if (Settings.get.useOldTextureFontRenderer) new font.StaticFontRenderer() + if (Settings.get.fontRenderer == "texture") new font.StaticFontRenderer() else new font.DynamicFontRenderer() private val cache = com.google.common.cache.CacheBuilder.newBuilder(). diff --git a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala index c8b51de7f..e360c76f9 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/DynamicFontRenderer.scala @@ -1,5 +1,6 @@ package li.cil.oc.client.renderer.font +import li.cil.oc.Settings import li.cil.oc.client.renderer.font.DynamicFontRenderer.CharTexture import li.cil.oc.util.{FontUtil, RenderState} import net.minecraft.client.Minecraft @@ -14,7 +15,9 @@ import scala.collection.mutable * to it. It's pretty broken right now, and font rendering looks crappy as hell. */ class DynamicFontRenderer extends TextureFontRenderer with ResourceManagerReloadListener { - private val glyphProvider: IGlyphProvider = new FontParserUnifont() + private val glyphProvider: IGlyphProvider = Settings.get.fontRenderer match { + case _ => new FontParserUnifont() + } private val textures = mutable.ArrayBuffer.empty[CharTexture] diff --git a/src/main/scala/li/cil/oc/client/renderer/font/FontParserUnifont.java b/src/main/scala/li/cil/oc/client/renderer/font/FontParserUnifont.java index 675bec217..8a4defc99 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/FontParserUnifont.java +++ b/src/main/scala/li/cil/oc/client/renderer/font/FontParserUnifont.java @@ -15,10 +15,10 @@ import java.nio.ByteBuffer; import java.util.logging.Level; public class FontParserUnifont implements IGlyphProvider { - private static final byte[] b_set = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; - private static final byte[] b_unset = {0, 0, 0, 0}; + 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[65536][]; + private final byte[][] glyphs = new byte[0x10000][]; @Override public void initialize() { @@ -31,19 +31,28 @@ public class FontParserUnifont implements IGlyphProvider { return; } try { - OpenComputers.log().info("Initialized Unifont glyph provider."); + OpenComputers.log().info("Initializing Unifont glyph provider."); final BufferedReader input = new BufferedReader(new InputStreamReader(font)); String line; int glyphCount = 0; while ((line = input.readLine()) != null) { - glyphCount++; final String[] info = line.split(":"); final int charCode = Integer.parseInt(info[0], 16); + final int expectedWidth = FontUtil.wcwidth(charCode); + if (expectedWidth < 1) continue; // Skip control characters. final byte[] glyph = new byte[info[1].length() >> 1]; - for (int i = 0; i < glyph.length; i++) { - glyph[i] = (byte) Integer.parseInt(info[1].substring(i * 2, i * 2 + 2), 16); + 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().logUnifontErrors()) { + OpenComputers.log().warning(String.format("Size of glyph for code point U+%04X (%s) in Unifont (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth)); } - glyphs[charCode] = glyph; } OpenComputers.log().info("Loaded " + glyphCount + " glyphs."); } catch (IOException ex) { @@ -58,23 +67,15 @@ public class FontParserUnifont implements IGlyphProvider { @Override public ByteBuffer getGlyph(int charCode) { - if (charCode >= 65536 || glyphs[charCode] == null || glyphs[charCode].length == 0) + if (charCode < 0 || charCode >= glyphs.length || glyphs[charCode] == null || glyphs[charCode].length == 0) return null; final byte[] glyph = glyphs[charCode]; - final int expectedWidth = FontUtil.wcwidth(charCode); - final int glyphWidth = glyph.length / 16; - if (glyphWidth != expectedWidth) { - if (OpenComputers.log().isLoggable(Level.FINEST)) { - OpenComputers.log().finest(String.format("Size of glyph for code point U+%04X (%s) in Unifont (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth)); - } - return null; - } - final ByteBuffer buffer = BufferUtils.createByteBuffer(glyphWidth * getGlyphWidth() * 16 * 4); + final ByteBuffer buffer = BufferUtils.createByteBuffer(glyph.length * getGlyphWidth() * 4); for (byte aGlyph : glyph) { int c = ((int) aGlyph) & 0xFF; for (int j = 0; j < 8; j++) { - if ((c & 128) > 0) buffer.put(b_set); - else buffer.put(b_unset); + if ((c & 128) > 0) buffer.put(OPAQUE); + else buffer.put(TRANSPARENT); c <<= 1; } } 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 160fd7109..fabd3c45a 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 @@ -2,12 +2,51 @@ package li.cil.oc.client.renderer.font; import java.nio.ByteBuffer; +/** + * Common interface for classes providing glyph data in a format that can be + * rendered using the {@link li.cil.oc.client.renderer.font.DynamicFontRenderer}. + */ public interface IGlyphProvider { + /** + * Called when the resource manager is reloaded. + *
+ * This should usually also be called from the implementation's constructor. + */ public void initialize(); + /** + * Get a byte array of RGBA data describing the specified char. + * + * This is only called once for each char per resource reload cycle (i.e. + * it may called multiple times, but only if {@link #initialize()} was + * called in-between). This means implementations may be relatively + * inefficient (be reasonable) in generating the RGBA data. + * + * The returned buffer is expected to be of a format so that it can be + * directly passed on toglTexSubImage2D
, meaning a byte array
+ * with 4 byte per pixel, row by row.
+ *
+ * Important: remember to rewind the buffer, if necessary.
+ *
+ * @param charCode the char to get the render glyph data for.
+ * @return the RGBA byte array representing the char.
+ * @see li.cil.oc.client.renderer.font.FontParserUnifont#getGlyph(int) See the Unifont parser for a reference implementation.
+ */
public ByteBuffer getGlyph(int charCode);
+ /**
+ * Get the single-width glyph width for this provider, in pixels.
+ *
+ * Each glyph provided is expected to have the same width multiplier; i.e.
+ * a glyphs actual width (in pixels) is expected to be this value times
+ * {@link li.cil.oc.util.FontUtil#wcwidth(int)} (for a specific char).
+ */
public int getGlyphWidth();
+ /**
+ * Get the glyph height for this provider, in pixels.
+ *
+ * Each glyph provided is expected to have the same height.
+ */
public int getGlyphHeight();
}