From 4157109b2e8596d189b4e54ce2a2d1a3db55bd46 Mon Sep 17 00:00:00 2001 From: evg-zhabotinsky Date: Tue, 10 Jun 2014 23:52:11 +0400 Subject: [PATCH 1/4] Made dataBuffer be common for all holograms. --- .../tileentity/HologramRenderer.scala | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala index 62d56f46c..65b959441 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala @@ -15,15 +15,15 @@ import org.lwjgl.BufferUtils import li.cil.oc.Settings import java.nio.IntBuffer -object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, IntBuffer)] with RemovalListener[TileEntity, (Int, IntBuffer)] with ITickHandler { +object HologramRenderer extends TileEntitySpecialRenderer with Callable[Int] with RemovalListener[TileEntity, Int] with ITickHandler { private val random = new Random() /** We cache the VBOs for the projectors we render for performance. */ private val cache = com.google.common.cache.CacheBuilder.newBuilder(). expireAfterAccess(10, TimeUnit.SECONDS). removalListener(this). - asInstanceOf[CacheBuilder[Hologram, (Int, IntBuffer)]]. - build[Hologram, (Int, IntBuffer)]() + asInstanceOf[CacheBuilder[Hologram, Int]]. + build[Hologram, Int]() /** * Common for all holograms. Holds the vertex positions, texture @@ -33,9 +33,20 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, In * same dimensions (in voxels). If we ever need holograms of different * sizes we could probably just fake that by making the outer layers * immutable (i.e. always empty). + * + * NOTE: It already takes up 47.25 MiB of video memory and increasing + * hologram size to, for example, 64*64*64 will result in 168 MiB. */ private var commonBuffer = 0 + /** + * Also common for all holograms. Temporary buffer used to upload + * hologram data to GPU. First half stores colors for each vertex + * (0xAABBGGRR Int, alpha is used for alignment only) and second + * half stores (Int) indices of vertices that should be drawn. + */ + private var dataBuffer: IntBuffer = null + /** Used to pass the current screen along to call(). */ private var hologram: Hologram = null @@ -92,13 +103,13 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, In // When we don't do this the hologram will look different from different // angles (because some faces will shine through sometimes and sometimes // they won't), so a more... consistent look is desirable. - val (glBuffer, dataBuffer) = cache.get(hologram, this) + val glBuffer = cache.get(hologram, this) GL11.glColorMask(false, false, false, false) GL11.glDepthMask(true) - draw(glBuffer, dataBuffer) + draw(glBuffer) GL11.glColorMask(true, true, true, true) GL11.glDepthFunc(GL11.GL_EQUAL) - draw(glBuffer, dataBuffer) + draw(glBuffer) GL11.glPopMatrix() GL11.glPopAttrib() @@ -107,15 +118,17 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, In RenderState.checkError(getClass.getName + ".renderTileEntityAt: leaving") } - def draw(glBuffer: Int, dataBuffer: IntBuffer) { + def draw(glBuffer: Int) { initialize() - validate(glBuffer, dataBuffer) + validate(glBuffer) publish(glBuffer) } private def initialize() { // First run only, create structure information. if (commonBuffer == 0) { + dataBuffer = BufferUtils.createIntBuffer(hologram.width * hologram.width * hologram.height * 6 * 4 * 2) + commonBuffer = GL15.glGenBuffers() val data = BufferUtils.createFloatBuffer(hologram.width * hologram.width * hologram.height * 24 * (2 + 3 + 3)) @@ -188,7 +201,7 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, In } } - private def validate(glBuffer: Int, dataBuffer: IntBuffer) { + private def validate(glBuffer: Int) { // Refresh indexes when the hologram's data changed. if (hologram.dirty) { def value(hx: Int, hy: Int, hz: Int) = if (hx >= 0 && hy >= 0 && hz >= 0 && hx < hologram.width && hy < hologram.height && hz < hologram.width) hologram.getColor(hx, hy, hz) else 0 @@ -294,16 +307,15 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[(Int, In def call = { val glBuffer = GL15.glGenBuffers() - val dataBuffer = BufferUtils.createIntBuffer(hologram.width * hologram.width * hologram.height * 6 * 4 * 2) // Force re-indexing. hologram.dirty = true - (glBuffer, dataBuffer) + glBuffer } - def onRemoval(e: RemovalNotification[TileEntity, (Int, IntBuffer)]) { - val (glBuffer, dataBuffer) = e.getValue + def onRemoval(e: RemovalNotification[TileEntity, Int]) { + val glBuffer = e.getValue GL15.glDeleteBuffers(glBuffer) dataBuffer.clear() } From ed2707097ccc9e2c5e541074daec2c74ba73c4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 10 Jun 2014 23:56:08 +0200 Subject: [PATCH 2/4] Reduced hologram memory usage by only pushing indexes of visible faces. Clearing buffer when hologram isn't visible at all. --- .../tileentity/HologramRenderer.scala | 23 ++++++++++++++----- .../renderer/tileentity/ScreenRenderer.scala | 4 ++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala index 65b959441..9b9feba7d 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala @@ -20,7 +20,7 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[Int] wit /** We cache the VBOs for the projectors we render for performance. */ private val cache = com.google.common.cache.CacheBuilder.newBuilder(). - expireAfterAccess(10, TimeUnit.SECONDS). + expireAfterAccess(5, TimeUnit.SECONDS). removalListener(this). asInstanceOf[CacheBuilder[Hologram, Int]]. build[Hologram, Int]() @@ -275,12 +275,23 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[Int] wit } } - // Important! OpenGL will start reading from the current buffer position. - dataBuffer.rewind() - - // This buffer can be updated quite frequently, so dynamic seems sensible. GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, glBuffer) - GL15.glBufferData(GL15.GL_ARRAY_BUFFER, dataBuffer, GL15.GL_DYNAMIC_DRAW) + if (hologram.visibleQuads > 0) { + // Flip the buffer to only fill in as much data as necessary. + dataBuffer.flip() + + // This buffer can be updated quite frequently, so dynamic seems sensible. + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, dataBuffer, GL15.GL_DYNAMIC_DRAW) + } + else { + // Empty hologram. + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, 0L, GL15.GL_DYNAMIC_DRAW) + } + + println("HOLOGRAM SIZE: " + GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE) / 1024.0 / 1024.0) + + // Reset for the next operation. + dataBuffer.clear() hologram.dirty = false } 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 2f6429c1e..38cb2905b 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 @@ -62,7 +62,7 @@ object ScreenRenderer extends TileEntitySpecialRenderer { } if (screen.buffer.isRenderingEnabled) { - compileOrDraw() + draw() } GL11.glPopMatrix() @@ -119,7 +119,7 @@ object ScreenRenderer extends TileEntitySpecialRenderer { } } - private def compileOrDraw() { + private def draw() { val sx = screen.width val sy = screen.height val tw = sx * 16f From faade8b79f950fa5bcdd929ef41d499af6741879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 11 Jun 2014 13:47:05 +0200 Subject: [PATCH 3/4] Removed debug println in hologram renderer (oops). Fixed screen clicking via arrows a bit. Caching shells for reduced memory consumption for nested os.executes. --- .../opencomputers/loot/BetterShell/besh.lua | 41 ++++--------------- .../opencomputers/loot/OpenOS/bin/sh.lua | 4 +- .../opencomputers/loot/OpenOS/lib/shell.lua | 21 +++++++++- .../assets/opencomputers/lua/kernel.lua | 10 +++++ .../tileentity/HologramRenderer.scala | 2 - .../scala/li/cil/oc/common/block/Screen.scala | 4 +- .../li/cil/oc/common/tileentity/Screen.scala | 36 ++++++++-------- 7 files changed, 58 insertions(+), 60 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua b/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua index ae0bbda1c..370d7ed5b 100644 --- a/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua +++ b/src/main/resources/assets/opencomputers/loot/BetterShell/besh.lua @@ -360,7 +360,7 @@ end ------------------------------------------------------------------------------- -local function execute(command, env, ...) +local function execute(env, command, ...) checkArg(1, command, "string") local commands, reason = parseCommands(command) if not commands then @@ -479,39 +479,12 @@ local args, options = shell.parse(...) local history = {} if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then - -- interactive shell. - while true do - if not term.isAvailable() then -- don't clear unless we lost the term - while not term.isAvailable() do - event.pull("term_available") - end - term.clear() - end - while term.isAvailable() do - local foreground = component.gpu.setForeground(0xFF0000) - term.write(expand(os.getenv("PS1") or "$ ")) - component.gpu.setForeground(foreground) - local command = term.read(history) - if not command then - term.write("exit\n") - return -- eof - end - while #history > 10 do - table.remove(history, 1) - end - command = text.trim(command) - if command == "exit" then - return - elseif command ~= "" then - local result, reason = execute(command) - if not result then - io.stderr:write((tostring(reason) or "unknown error").. "\n") - elseif term.getCursor() > 1 then - term.write("\n") - end - end - end - end + -- interactive shell. use original shell for input but register self as + -- global SHELL for command execution. + local oldShell = os.getenv("SHELL") + os.setenv("SHELL", process.running()) + os.execute("/bin/sh") + os.setenv("SHELL", oldShell) else -- execute command. local result = table.pack(execute(...)) diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua index 47ec37ce5..74b46f19d 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua @@ -41,7 +41,7 @@ local function evaluate(value) return result end -local function execute(command, ...) +local function execute(env, command, ...) local parts, reason = text.tokenize(command) if not parts then return false, reason @@ -119,7 +119,7 @@ if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then if command == "exit" then return elseif command ~= "" then - local result, reason = execute(command) + local result, reason = os.execute(command) if term.getCursor() > 1 then term.write("\n") end diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/shell.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/shell.lua index ff97529f6..64270ab76 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/shell.lua @@ -5,6 +5,23 @@ local unicode = require("unicode") local shell = {} local aliases = {} +-- Cache loaded shells for command execution. This puts the requirement on +-- shells that they do not keep a global state, since they may be called +-- multiple times, but reduces memory usage a lot. +local shells = setmetatable({}, {__mode="v"}) + +local function getShell() + local shellName = shell.resolve(os.getenv("SHELL"), "lua") + if shells[shellName] then + return shells[shellName] + end + local sh, reason = loadfile(shellName, "t", env) + if sh then + shells[shellName] = sh + end + return sh, reason +end + local function findFile(name, ext) checkArg(1, name, "string") local function findIn(dir) @@ -129,11 +146,11 @@ function shell.resolve(path, ext) end function shell.execute(command, env, ...) - local sh, reason = loadfile(shell.resolve(os.getenv("SHELL"), "lua"), "t", env) + local sh, reason = getShell() if not sh then return false, reason end - local result = table.pack(pcall(sh, command, ...)) + local result = table.pack(pcall(sh, env, command, ...)) if not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then if result[2].code then return true diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index 146578dd6..350c86217 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -630,10 +630,20 @@ local function main() -- After memory footprint to avoid init.lua bumping the baseline. local co, args = bootstrap() + local forceGC = 10 while true do deadline = computer.realTime() + system.timeout() hitDeadline = false + + -- NOTE: since this is run in an executor thread and we enforce timeouts + -- in user-defined garbage collector callbacks this should be safe. + forceGC = forceGC - 1 + if forceGC < 1 then + collectgarbage("collect") + forceGC = 10 + end + debug.sethook(co, checkDeadline, "", hookInterval) local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n))) if not result[1] then diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala index 9b9feba7d..632615192 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/HologramRenderer.scala @@ -288,8 +288,6 @@ object HologramRenderer extends TileEntitySpecialRenderer with Callable[Int] wit GL15.glBufferData(GL15.GL_ARRAY_BUFFER, 0L, GL15.GL_DYNAMIC_DRAW) } - println("HOLOGRAM SIZE: " + GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE) / 1024.0 / 1024.0) - // Reset for the next operation. dataBuffer.clear() diff --git a/src/main/scala/li/cil/oc/common/block/Screen.scala b/src/main/scala/li/cil/oc/common/block/Screen.scala index a7b2d88b3..98386a80f 100644 --- a/src/main/scala/li/cil/oc/common/block/Screen.scala +++ b/src/main/scala/li/cil/oc/common/block/Screen.scala @@ -329,7 +329,7 @@ abstract class Screen(val parent: SimpleDelegator) extends RedstoneAware with Si } override def collide(world: World, x: Int, y: Int, z: Int, entity: Entity) = - if (!world.isRemote) (entity, world.getBlockTileEntity(x, y, z)) match { + if (world.isRemote) (entity, world.getBlockTileEntity(x, y, z)) match { case (arrow: EntityArrow, screen: tileentity.Screen) if screen.tier > 0 => val hitX = math.max(0, math.min(1, arrow.posX - x)) val hitY = math.max(0, math.min(1, arrow.posY - y)) @@ -350,7 +350,7 @@ abstract class Screen(val parent: SimpleDelegator) extends RedstoneAware with Si else ForgeDirection.SOUTH } if (side == screen.facing) { - screen.shot(arrow, hitX, hitY, hitZ) + screen.shot(arrow) } case _ => } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala index ab4aa094b..54974ac79 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala @@ -4,6 +4,7 @@ import cpw.mods.fml.relauncher.{Side, SideOnly} import li.cil.oc.api.network._ import li.cil.oc.Settings import li.cil.oc.util.Color +import net.minecraft.client.Minecraft import net.minecraft.entity.Entity import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.projectile.EntityArrow @@ -134,19 +135,8 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with } } - def shot(arrow: EntityArrow, hitX: Double, hitY: Double, hitZ: Double) { - // This is nasty, but I see no other way: arrows can trigger two collisions, - // once on their own when hitting a block, a second time via their entity's - // common collision checker. The second one (collision checker) has the - // better coordinates (arrow moved back out of the block it collided with), - // so use that when possible, otherwise resolve in next update. - if (!arrows.add(arrow)) { - arrows.remove(arrow) - arrow.shootingEntity match { - case player: EntityPlayer => click(player, hitX, hitY, hitZ) - case _ => - } - } + def shot(arrow: EntityArrow) { + arrows.add(arrow) } // ----------------------------------------------------------------------- // @@ -219,12 +209,22 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with } if (arrows.size > 0) { for (arrow <- arrows) { - val hitX = math.max(0, math.min(1, arrow.posX - x)) - val hitY = math.max(0, math.min(1, arrow.posY - y)) - val hitZ = math.max(0, math.min(1, arrow.posZ - z)) - shot(arrow, hitX, hitY, hitZ) + val hitX = arrow.posX - x + val hitY = arrow.posY - y + val hitZ = arrow.posZ - z + val hitXInner = math.abs(hitX - 0.5) < 0.45 + val hitYInner = math.abs(hitY - 0.5) < 0.45 + val hitZInner = math.abs(hitZ - 0.5) < 0.45 + if (hitXInner && hitYInner && !hitZInner || + hitXInner && !hitYInner && hitZInner || + !hitXInner && hitYInner && hitZInner) { + arrow.shootingEntity match { + case player: EntityPlayer if player == Minecraft.getMinecraft.thePlayer => click(player, hitX, hitY, hitZ) + case _ => + } + } } - assert(arrows.isEmpty) + arrows.clear() } } From 41b4321b9981ba887411c528dcc1fe15da940cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 11 Jun 2014 14:04:37 +0200 Subject: [PATCH 4/4] Yet more OpenGL error checks. --- .../renderer/tileentity/ScreenRenderer.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 38cb2905b..23770d8f5 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 @@ -46,6 +46,8 @@ object ScreenRenderer extends TileEntitySpecialRenderer { return } + RenderState.checkError(getClass.getName + ".renderTileEntityAt: checks") + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) RenderState.disableLighting() @@ -55,12 +57,18 @@ object ScreenRenderer extends TileEntitySpecialRenderer { GL11.glTranslated(x + 0.5, y + 0.5, z + 0.5) + RenderState.checkError(getClass.getName + ".renderTileEntityAt: setup") + drawOverlay() + RenderState.checkError(getClass.getName + ".renderTileEntityAt: overlay") + if (distance > fadeDistanceSq) { RenderState.setBlendAlpha(math.max(0, 1 - ((distance - fadeDistanceSq) * fadeRatio).toFloat)) } + RenderState.checkError(getClass.getName + ".renderTileEntityAt: fade") + if (screen.buffer.isRenderingEnabled) { draw() } @@ -120,6 +128,8 @@ object ScreenRenderer extends TileEntitySpecialRenderer { } private def draw() { + RenderState.checkError(getClass.getName + ".draw: entering (aka: wasntme)") + val sx = screen.width val sy = screen.height val tw = sx * 16f @@ -157,8 +167,12 @@ object ScreenRenderer extends TileEntitySpecialRenderer { // Slightly offset the text so it doesn't clip into the screen. GL11.glTranslatef(0, 0, 0.01f) + RenderState.checkError(getClass.getName + ".draw: setup") + // Render the actual text. screen.buffer.renderText() + + RenderState.checkError(getClass.getName + ".draw: text") } private def playerDistanceSq() = {