mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-09 07:15:11 -04:00
gpu video ram with allocate, free, bitblts
writing text and color data to a gpu page is free and server side only bitblt to screen cause an update and has more budget and power cost
This commit is contained in:
parent
9d38ecb51d
commit
e70856bf9f
@ -244,6 +244,16 @@ opencomputers {
|
||||
1024
|
||||
]
|
||||
|
||||
# Video ram can be allocated on a gpu. The amount of vram you can allocate
|
||||
# is equal to the width*height of the max resolution of the gpu multiplied
|
||||
# by the "vramSize" for that tier. For example, a T2 gpu can have 80*25*2 of
|
||||
# text buffer space allocated
|
||||
vramSizes: [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
|
||||
# This setting allows you to fine-tune how RAM sizes are scaled internally
|
||||
# on 64 Bit machines (i.e. when the Minecraft server runs in a 64 Bit VM).
|
||||
# Why is this even necessary? Because objects consume more memory in a 64
|
||||
|
@ -93,6 +93,12 @@ class Settings(val config: Config) {
|
||||
OpenComputers.log.warn("Bad number of RAM sizes, ignoring.")
|
||||
Array(192, 256, 384, 512, 768, 1024)
|
||||
}
|
||||
val vramSizes = Array(config.getIntList("computer.lua.vramSizes"): _*) match {
|
||||
case Array(tier1, tier2, tier3) => Array(tier1: Int, tier2: Int, tier3: Int)
|
||||
case _ =>
|
||||
OpenComputers.log.warn("Bad number of VRAM sizes, ignoring.")
|
||||
Array(1, 2, 3)
|
||||
}
|
||||
val ramScaleFor64Bit = config.getDouble("computer.lua.ramScaleFor64Bit") max 1
|
||||
val maxTotalRam = config.getInt("computer.lua.maxTotalRam") max 0
|
||||
|
||||
|
@ -13,6 +13,7 @@ import li.cil.oc.api.event.NetworkActivityEvent
|
||||
import li.cil.oc.client.renderer.PetRenderer
|
||||
import li.cil.oc.common.Loot
|
||||
import li.cil.oc.common.PacketType
|
||||
import li.cil.oc.common.component
|
||||
import li.cil.oc.common.container
|
||||
import li.cil.oc.common.nanomachines.ControllerImpl
|
||||
import li.cil.oc.common.tileentity._
|
||||
@ -620,6 +621,9 @@ object PacketHandler extends CommonPacketHandler {
|
||||
case PacketType.TextBufferMultiViewportResolutionChange => onTextBufferMultiViewportResolutionChange(p, buffer)
|
||||
case PacketType.TextBufferMultiMaxResolutionChange => onTextBufferMultiMaxResolutionChange(p, buffer)
|
||||
case PacketType.TextBufferMultiSet => onTextBufferMultiSet(p, buffer)
|
||||
case PacketType.TextBufferRamInit => onTextBufferRamInit(p, buffer)
|
||||
case PacketType.TextBufferBitBlt => onTextBufferBitBlt(p, buffer)
|
||||
case PacketType.TextBufferRamDestroy => onTextBufferRamDestroy(p, buffer)
|
||||
case PacketType.TextBufferMultiRawSetText => onTextBufferMultiRawSetText(p, buffer)
|
||||
case PacketType.TextBufferMultiRawSetBackground => onTextBufferMultiRawSetBackground(p, buffer)
|
||||
case PacketType.TextBufferMultiRawSetForeground => onTextBufferMultiRawSetForeground(p, buffer)
|
||||
@ -700,6 +704,41 @@ object PacketHandler extends CommonPacketHandler {
|
||||
buffer.set(col, row, s, vertical)
|
||||
}
|
||||
|
||||
def onTextBufferRamInit(p: PacketParser, buffer: api.internal.TextBuffer): Unit = {
|
||||
val id = p.readInt()
|
||||
val nbt = p.readNBT()
|
||||
|
||||
buffer match {
|
||||
case screen: component.traits.VideoRamAware => screen.loadBuffer(id, nbt)
|
||||
case _ => // ignore
|
||||
}
|
||||
}
|
||||
|
||||
def onTextBufferBitBlt(p: PacketParser, buffer: api.internal.TextBuffer): Unit = {
|
||||
val col = p.readInt()
|
||||
val row = p.readInt()
|
||||
val w = p.readInt()
|
||||
val h = p.readInt()
|
||||
val id = p.readInt()
|
||||
val fromCol = p.readInt()
|
||||
val fromRow = p.readInt()
|
||||
|
||||
component.GpuTextBuffer.bitblt(buffer, col, row, w, h, id, fromCol, fromRow)
|
||||
}
|
||||
|
||||
def onTextBufferRamDestroy(p: PacketParser, buffer: api.internal.TextBuffer): Unit = {
|
||||
val length = p.readInt()
|
||||
val ids = new Array[Int](length)
|
||||
for (i <- 0 until length) {
|
||||
ids(i) = p.readInt()
|
||||
}
|
||||
|
||||
buffer match {
|
||||
case screen: component.traits.VideoRamAware => screen.removeBuffers(ids)
|
||||
case _ => // ignore, not compatible with bitblts
|
||||
}
|
||||
}
|
||||
|
||||
def onTextBufferMultiRawSetText(p: PacketParser, buffer: api.internal.TextBuffer) {
|
||||
val col = p.readInt()
|
||||
val row = p.readInt()
|
||||
|
@ -51,6 +51,9 @@ object PacketType extends Enumeration {
|
||||
SwitchActivity,
|
||||
TextBufferInit, // Goes both ways.
|
||||
TextBufferMulti,
|
||||
TextBufferRamInit,
|
||||
TextBufferBitBlt,
|
||||
TextBufferRamDestroy,
|
||||
TextBufferMultiColorChange,
|
||||
TextBufferMultiCopy,
|
||||
TextBufferMultiDepthChange,
|
||||
|
154
src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala
Normal file
154
src/main/scala/li/cil/oc/common/component/GpuTextBuffer.scala
Normal file
@ -0,0 +1,154 @@
|
||||
package li.cil.oc.common.component
|
||||
|
||||
import li.cil.oc.api.network.{ManagedEnvironment, Message, Node}
|
||||
import net.minecraft.entity.player.EntityPlayer
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
import li.cil.oc.api.internal.TextBuffer.ColorDepth
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.common.component.traits.TextBufferProxy
|
||||
import li.cil.oc.util.PackedColor
|
||||
|
||||
class GpuTextBuffer(val id: Int, val data: li.cil.oc.util.TextBuffer) extends ManagedEnvironment with traits.TextBufferProxy {
|
||||
override def getMaximumWidth: Int = data.width
|
||||
override def getMaximumHeight: Int = data.height
|
||||
override def getViewportWidth: Int = data.height
|
||||
override def getViewportHeight: Int = data.width
|
||||
|
||||
var dirty: Boolean = false
|
||||
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = dirty = true
|
||||
override def onBufferColorChange(): Unit = dirty = true
|
||||
override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = dirty = true
|
||||
override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = dirty = true
|
||||
override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = dirty = true
|
||||
override def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = dirty = false
|
||||
|
||||
override def setEnergyCostPerTick(value: Double): Unit = {}
|
||||
override def getEnergyCostPerTick: Double = 0
|
||||
override def setPowerState(value: Boolean): Unit = {}
|
||||
override def getPowerState: Boolean = false
|
||||
override def setMaximumResolution(width: Int, height: Int): Unit = {}
|
||||
override def setAspectRatio(width: Double, height: Double): Unit = {}
|
||||
override def getAspectRatio: Double = 1
|
||||
override def setResolution(width: Int, height: Int): Boolean = false
|
||||
override def setViewport(width: Int, height: Int): Boolean = false
|
||||
override def setMaximumColorDepth(depth: ColorDepth): Unit = {}
|
||||
override def getMaximumColorDepth: ColorDepth = data.format.depth
|
||||
override def renderText: Boolean = false
|
||||
override def renderWidth: Int = 0
|
||||
override def renderHeight: Int = 0
|
||||
override def setRenderingEnabled(enabled: Boolean): Unit = {}
|
||||
override def isRenderingEnabled: Boolean = false
|
||||
override def keyDown(character: Char, code: Int, player: EntityPlayer): Unit = {}
|
||||
override def keyUp(character: Char, code: Int, player: EntityPlayer): Unit = {}
|
||||
override def clipboard(value: String, player: EntityPlayer): Unit = {}
|
||||
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
|
||||
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
|
||||
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
|
||||
override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer): Unit = {}
|
||||
override def canUpdate: Boolean = false
|
||||
override def update(): Unit = {}
|
||||
override def node: li.cil.oc.api.network.Node = null
|
||||
override def onConnect(node: Node): Unit = {}
|
||||
override def onDisconnect(node: Node): Unit = {}
|
||||
override def onMessage(message: Message): Unit = {}
|
||||
override def load(nbt: NBTTagCompound): Unit = {}
|
||||
override def save(nbt: NBTTagCompound): Unit = {}
|
||||
}
|
||||
|
||||
object GpuTextBuffer {
|
||||
def wrap(id: Int, data: li.cil.oc.util.TextBuffer): GpuTextBuffer = new GpuTextBuffer(id, data)
|
||||
|
||||
def bitblt(dst: api.internal.TextBuffer, col: Int, row: Int, w: Int, h: Int, srcId: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
dst match {
|
||||
case screen: traits.TextBufferProxy => screen.getBuffer(srcId) match {
|
||||
case Some(buffer: GpuTextBuffer) => {
|
||||
bitblt(dst, col, row, w, h, buffer, fromCol, fromRow)
|
||||
}
|
||||
case _ => // ignore - got a bitblt for a missing buffer
|
||||
}
|
||||
case _ => // ignore - weird packet handler called this, should only happen for screens that know about thsi
|
||||
}
|
||||
}
|
||||
|
||||
def bitblt(dst: api.internal.TextBuffer, col: Int, row: Int, w: Int, h: Int, src: api.internal.TextBuffer, fromCol: Int, fromRow: Int): Unit = {
|
||||
val x = col - 1
|
||||
val y = row - 1
|
||||
val fx = fromCol - 1
|
||||
val fy = fromRow - 1
|
||||
var adjustedDstX = x
|
||||
var adjustedDstY = y
|
||||
var adjustedWidth = w
|
||||
var adjustedHeight = h
|
||||
var adjustedSourceX = fx
|
||||
var adjustedSourceY = fy
|
||||
|
||||
if (x < 0) {
|
||||
adjustedWidth += x
|
||||
adjustedSourceX -= x
|
||||
adjustedDstX = 0
|
||||
}
|
||||
|
||||
if (y < 0) {
|
||||
adjustedHeight += y
|
||||
adjustedSourceY -= y
|
||||
adjustedDstY = 0
|
||||
}
|
||||
|
||||
if (adjustedSourceX < 0) {
|
||||
adjustedWidth += adjustedSourceX
|
||||
adjustedDstX -= adjustedSourceX
|
||||
adjustedSourceX = 0
|
||||
}
|
||||
|
||||
if (adjustedSourceY < 0) {
|
||||
adjustedHeight += adjustedSourceY
|
||||
adjustedDstY -= adjustedSourceY
|
||||
adjustedSourceY = 0
|
||||
}
|
||||
|
||||
adjustedWidth -= ((adjustedDstX + adjustedWidth) - dst.getWidth) max 0
|
||||
adjustedWidth -= ((adjustedSourceX + adjustedWidth) - src.getWidth) max 0
|
||||
|
||||
adjustedHeight -= ((adjustedDstY + adjustedHeight) - dst.getHeight) max 0
|
||||
adjustedHeight -= ((adjustedSourceY + adjustedHeight) - src.getHeight) max 0
|
||||
|
||||
// anything left?
|
||||
if (adjustedWidth <= 0 || adjustedHeight <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
dst match {
|
||||
case dstRam: GpuTextBuffer => src match {
|
||||
case srcRam: GpuTextBuffer => write_vram_to_vram(dstRam, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcRam, adjustedSourceX, adjustedSourceY)
|
||||
case srcScreen: traits.TextBufferProxy => write_screen_to_vram(dstRam, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcScreen, adjustedSourceX, adjustedSourceY)
|
||||
case _ => throw new UnsupportedOperationException("Source buffer does not support bitblt operations")
|
||||
}
|
||||
case dstScreen: traits.TextBufferProxy => src match {
|
||||
case srcRam: GpuTextBuffer => write_vram_to_screen(dstScreen, adjustedDstX, adjustedDstY, adjustedWidth, adjustedHeight, srcRam, adjustedSourceX, adjustedSourceY)
|
||||
case _: traits.TextBufferProxy => throw new UnsupportedOperationException("Screen to screen bitblt not supported")
|
||||
case _ => throw new UnsupportedOperationException("Source buffer does not support bitblt operations")
|
||||
}
|
||||
case _ => throw new UnsupportedOperationException("Destination buffer does not support bitblt operations")
|
||||
}
|
||||
}
|
||||
|
||||
def write_vram_to_vram(dstRam: GpuTextBuffer, x: Int, y: Int, w: Int, h: Int, srcRam: GpuTextBuffer, fx: Int, fy: Int): Boolean = {
|
||||
dstRam.data.rawcopy(x + 1, y + 1, w, h, srcRam.data, fx + 1, fx + 1)
|
||||
}
|
||||
|
||||
def write_vram_to_screen(dstScreen: traits.TextBufferProxy, x: Int, y: Int, w: Int, h: Int, srcRam: GpuTextBuffer, fx: Int, fy: Int): Boolean = {
|
||||
if (dstScreen.data.rawcopy(x + 1, y + 1, w, h, srcRam.data, fx + 1, fy + 1)) {
|
||||
// rawcopy returns true only if data was modified
|
||||
dstScreen.addBuffer(srcRam)
|
||||
dstScreen.onBufferBitBlt(x + 1, y + 1, w, h, srcRam.id, fx + 1, fy + 1)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
def write_screen_to_vram(dstRam: GpuTextBuffer, x: Int, y: Int, w: Int, h: Int, srcScreen: traits.TextBufferProxy, fx: Int, fy: Int): Boolean = {
|
||||
val format: PackedColor.ColorFormat = PackedColor.Depth.format(srcScreen.getColorDepth)
|
||||
val tempGpu = GpuTextBuffer.wrap(id = -1, new li.cil.oc.util.TextBuffer(w, h, format))
|
||||
tempGpu.data.rawcopy(col = 1, row = 1, w, h, srcScreen.data, fx + 1, fy + 1)
|
||||
write_vram_to_vram(dstRam, x, y, w, h, tempGpu, fx = 0, fy = 0)
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import li.cil.oc.client.renderer.font.TextBufferRenderData
|
||||
import li.cil.oc.client.{ComponentTracker => ClientComponentTracker}
|
||||
import li.cil.oc.client.{PacketSender => ClientPacketSender}
|
||||
import li.cil.oc.common._
|
||||
import li.cil.oc.common.component.traits.TextBufferProxy
|
||||
import li.cil.oc.server.component.Keyboard
|
||||
import li.cil.oc.server.{ComponentTracker => ServerComponentTracker}
|
||||
import li.cil.oc.server.{PacketSender => ServerPacketSender}
|
||||
@ -34,7 +35,6 @@ import net.minecraft.entity.player.EntityPlayer
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
import net.minecraftforge.event.world.ChunkEvent
|
||||
import net.minecraftforge.event.world.WorldEvent
|
||||
import tconstruct.client.tabs.InventoryTabVanilla
|
||||
|
||||
import scala.collection.convert.WrapAsJava._
|
||||
import scala.collection.convert.WrapAsScala._
|
||||
@ -311,67 +311,28 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi
|
||||
override def onBufferColorChange(): Unit =
|
||||
proxy.onBufferColorChange()
|
||||
|
||||
class ViewportBox(val x1: Int, val y1: Int, val x2: Int, val y2: Int) {
|
||||
def width: Int = (x2 - x1 + 1) max 0
|
||||
def height: Int = (y2 - y1 + 1) max 0
|
||||
def isVisible: Boolean = width > 0 && height > 0
|
||||
def isEmpty: Boolean = !isVisible
|
||||
}
|
||||
|
||||
// return box within viewport
|
||||
private def truncateToViewport(x1: Int, y1: Int, x2: Int, y2: Int): ViewportBox = {
|
||||
val x: Int = (x1 min x2) max 0
|
||||
val y: Int = (y1 min y2) max 0
|
||||
val lastX: Int = ((x1 max x2) max 0) min (viewport._1 - 1)
|
||||
val lastY: Int = ((y1 max y2) max 0) min (viewport._2 - 1)
|
||||
new ViewportBox(x, y, lastX, lastY)
|
||||
}
|
||||
|
||||
override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {
|
||||
// only notify about viewport changes
|
||||
val box = truncateToViewport(col + tx, row + ty, col + tx + w - 1, row + ty + h - 1)
|
||||
if (box.isVisible) {
|
||||
proxy.onBufferCopy(col, row, box.width, box.height, tx, ty)
|
||||
}
|
||||
proxy.onBufferCopy(col, row, w, h, tx, ty)
|
||||
}
|
||||
|
||||
override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = {
|
||||
val box = truncateToViewport(col, row, col + w - 1, row + h - 1)
|
||||
if (box.isVisible) {
|
||||
proxy.onBufferFill(col, row, box.width, box.height, c)
|
||||
}
|
||||
}
|
||||
|
||||
private def truncateToViewport(col: Int, row: Int, s: String, vertical: Boolean): String = {
|
||||
var start: Int = 0
|
||||
var last: Int = -1
|
||||
if (vertical) {
|
||||
val box = truncateToViewport(col, row, col, row + s.length - 1)
|
||||
if (box.isVisible) {
|
||||
start = box.y1 - row
|
||||
last = box.y2 - row
|
||||
}
|
||||
} else {
|
||||
val box = truncateToViewport(col, row, col + s.length - 1, row)
|
||||
if (box.isVisible) {
|
||||
start = box.x1 - col
|
||||
last = box.x2 - col
|
||||
}
|
||||
}
|
||||
|
||||
if (last >= start && start >= 0 && last < s.length) {
|
||||
s.substring(start, last + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
proxy.onBufferFill(col, row, w, h, c)
|
||||
}
|
||||
|
||||
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = {
|
||||
// only notify about viewport changes
|
||||
val truncatedString = truncateToViewport(col, row, s, vertical)
|
||||
if (!truncatedString.isEmpty) {
|
||||
proxy.onBufferSet(col, row, truncatedString, vertical)
|
||||
}
|
||||
proxy.onBufferSet(col, row, s, vertical)
|
||||
}
|
||||
|
||||
override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
proxy.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow)
|
||||
}
|
||||
|
||||
override def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = {
|
||||
proxy.onBufferRamInit(id, ram)
|
||||
}
|
||||
|
||||
override def onBufferRamDestroy(ids: Array[Int]): Unit = {
|
||||
proxy.onBufferRamDestroy(ids)
|
||||
}
|
||||
|
||||
override def rawSetText(col: Int, row: Int, text: Array[Array[Char]]): Unit = {
|
||||
@ -595,6 +556,18 @@ object TextBuffer {
|
||||
owner.relativeLitArea = -1
|
||||
}
|
||||
|
||||
def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
owner.relativeLitArea = -1
|
||||
}
|
||||
|
||||
def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = {
|
||||
owner.relativeLitArea = -1
|
||||
}
|
||||
|
||||
def onBufferRamDestroy(ids: Array[Int]): Unit = {
|
||||
owner.relativeLitArea = -1
|
||||
}
|
||||
|
||||
def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
|
||||
owner.relativeLitArea = -1
|
||||
}
|
||||
@ -678,6 +651,19 @@ object TextBuffer {
|
||||
dirty = true
|
||||
}
|
||||
|
||||
override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
super.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow)
|
||||
dirty = true
|
||||
}
|
||||
|
||||
override def onBufferRamInit(id: Int, buffer: TextBufferProxy): Unit = {
|
||||
super.onBufferRamInit(id, buffer)
|
||||
}
|
||||
|
||||
override def onBufferRamDestroy(ids: Array[Int]): Unit = {
|
||||
super.onBufferRamDestroy(ids)
|
||||
}
|
||||
|
||||
override def keyDown(character: Char, code: Int, player: EntityPlayer) {
|
||||
debug(s"{type = keyDown, char = $character, code = $code}")
|
||||
ClientPacketSender.sendKeyDown(nodeAddress, character, code)
|
||||
@ -780,6 +766,26 @@ object TextBuffer {
|
||||
owner.synchronized(ServerPacketSender.appendTextBufferSet(owner.pendingCommands, col, row, s, vertical))
|
||||
}
|
||||
|
||||
override def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
super.onBufferBitBlt(col, row, w, h, id, fromCol, fromRow)
|
||||
owner.host.markChanged()
|
||||
owner.synchronized(ServerPacketSender.appendTextBufferBitBlt(owner.pendingCommands, col, row, w, h, id, fromCol, fromRow))
|
||||
}
|
||||
|
||||
override def onBufferRamInit(id: Int, buffer: TextBufferProxy): Unit = {
|
||||
super.onBufferRamInit(id, buffer)
|
||||
owner.host.markChanged()
|
||||
val nbt = new NBTTagCompound()
|
||||
buffer.data.save(nbt)
|
||||
owner.synchronized(ServerPacketSender.appendTextBufferRamInit(owner.pendingCommands, id, nbt))
|
||||
}
|
||||
|
||||
override def onBufferRamDestroy(ids: Array[Int]): Unit = {
|
||||
super.onBufferRamDestroy(ids)
|
||||
owner.host.markChanged()
|
||||
owner.synchronized(ServerPacketSender.appendTextBufferRamDestroy(owner.pendingCommands, ids))
|
||||
}
|
||||
|
||||
override def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
|
||||
super.onBufferRawSetText(col, row, text)
|
||||
owner.host.markChanged()
|
||||
|
@ -5,7 +5,7 @@ import li.cil.oc.api
|
||||
import li.cil.oc.api.internal.TextBuffer
|
||||
import li.cil.oc.util.PackedColor
|
||||
|
||||
trait TextBufferProxy extends api.internal.TextBuffer {
|
||||
trait TextBufferProxy extends api.internal.TextBuffer with VideoRamAware {
|
||||
def data: util.TextBuffer
|
||||
|
||||
override def getWidth: Int = data.width
|
||||
|
@ -0,0 +1,72 @@
|
||||
package li.cil.oc.common.component.traits
|
||||
|
||||
import li.cil.oc.common.component
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
|
||||
trait VideoRamAware {
|
||||
private val internalBuffers = new scala.collection.mutable.HashMap[Int, component.GpuTextBuffer]
|
||||
val RESERVED_SCREEN_INDEX: Int = 0
|
||||
|
||||
def onBufferBitBlt(col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {}
|
||||
def onBufferRamInit(id: Int, ram: TextBufferProxy): Unit = {}
|
||||
def onBufferRamDestroy(ids: Array[Int]): Unit = {}
|
||||
|
||||
def bufferIndexes(): Array[Int] = internalBuffers.collect {
|
||||
case (index: Int, _: Any) => index
|
||||
}.toArray
|
||||
|
||||
def addBuffer(buffer: component.GpuTextBuffer): Boolean = {
|
||||
val preexists = internalBuffers.contains(buffer.id)
|
||||
if (!preexists) {
|
||||
internalBuffers += buffer.id -> buffer
|
||||
}
|
||||
if (!preexists || buffer.dirty) {
|
||||
buffer.onBufferRamInit(buffer.id, buffer)
|
||||
onBufferRamInit(buffer.id, buffer)
|
||||
}
|
||||
preexists
|
||||
}
|
||||
|
||||
def removeBuffers(ids: Array[Int]): Boolean = {
|
||||
var allRemoved: Boolean = true
|
||||
if (ids.nonEmpty) {
|
||||
onBufferRamDestroy(ids)
|
||||
for (id <- ids) {
|
||||
if (internalBuffers.remove(id).isEmpty)
|
||||
allRemoved = false
|
||||
}
|
||||
}
|
||||
allRemoved
|
||||
}
|
||||
|
||||
def removeAllBuffers(): Boolean = removeBuffers(bufferIndexes())
|
||||
|
||||
def loadBuffer(id: Int, nbt: NBTTagCompound): Unit = {
|
||||
val src = new li.cil.oc.util.TextBuffer(width = 1, height = 1, li.cil.oc.util.PackedColor.SingleBitFormat)
|
||||
src.load(nbt)
|
||||
addBuffer(component.GpuTextBuffer.wrap(id, src))
|
||||
}
|
||||
|
||||
def getBuffer(id: Int): Option[component.GpuTextBuffer] = {
|
||||
if (internalBuffers.contains(id))
|
||||
Option(internalBuffers(id))
|
||||
else
|
||||
None
|
||||
}
|
||||
|
||||
def nextAvailableBufferIndex: Int = {
|
||||
var index = RESERVED_SCREEN_INDEX + 1
|
||||
while (internalBuffers.contains(index)) {
|
||||
index += 1;
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
def calculateUsedMemory(): Int = {
|
||||
var sum: Int = 0
|
||||
for ((_, buffer: component.GpuTextBuffer) <- internalBuffers) {
|
||||
sum += buffer.data.width * buffer.data.height
|
||||
}
|
||||
sum
|
||||
}
|
||||
}
|
@ -665,6 +665,33 @@ object PacketSender {
|
||||
pb.writeBoolean(vertical)
|
||||
}
|
||||
|
||||
def appendTextBufferBitBlt(pb: PacketBuilder, col: Int, row: Int, w: Int, h: Int, id: Int, fromCol: Int, fromRow: Int): Unit = {
|
||||
pb.writePacketType(PacketType.TextBufferBitBlt)
|
||||
|
||||
pb.writeInt(col)
|
||||
pb.writeInt(row)
|
||||
pb.writeInt(w)
|
||||
pb.writeInt(h)
|
||||
pb.writeInt(id)
|
||||
pb.writeInt(fromCol)
|
||||
pb.writeInt(fromRow)
|
||||
}
|
||||
|
||||
def appendTextBufferRamInit(pb: PacketBuilder, id: Int, nbt: NBTTagCompound): Unit = {
|
||||
pb.writePacketType(PacketType.TextBufferRamInit)
|
||||
|
||||
pb.writeInt(id)
|
||||
pb.writeNBT(nbt)
|
||||
}
|
||||
|
||||
def appendTextBufferRamDestroy(pb: PacketBuilder, ids: Array[Int]): Unit = {
|
||||
pb.writePacketType(PacketType.TextBufferRamDestroy)
|
||||
pb.writeInt(ids.length)
|
||||
for (idx <- ids) {
|
||||
pb.writeInt(idx)
|
||||
}
|
||||
}
|
||||
|
||||
def appendTextBufferRawSetText(pb: PacketBuilder, col: Int, row: Int, text: Array[Array[Char]]) {
|
||||
pb.writePacketType(PacketType.TextBufferMultiRawSetText)
|
||||
|
||||
|
@ -2,10 +2,7 @@ package li.cil.oc.server.component
|
||||
|
||||
import java.util
|
||||
|
||||
import li.cil.oc.Constants
|
||||
import li.cil.oc.Localization
|
||||
import li.cil.oc.Settings
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.{Constants, Localization, Settings, api}
|
||||
import li.cil.oc.api.Network
|
||||
import li.cil.oc.api.driver.DeviceInfo
|
||||
import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute
|
||||
@ -16,7 +13,9 @@ import li.cil.oc.api.machine.Context
|
||||
import li.cil.oc.api.network._
|
||||
import li.cil.oc.api.prefab
|
||||
import li.cil.oc.util.PackedColor
|
||||
import net.minecraft.nbt.NBTTagCompound
|
||||
import net.minecraft.nbt.{NBTTagCompound, NBTTagList}
|
||||
import li.cil.oc.common.component
|
||||
import li.cil.oc.common.component.GpuTextBuffer
|
||||
|
||||
import scala.collection.convert.WrapAsJava._
|
||||
import scala.util.matching.Regex
|
||||
@ -33,7 +32,7 @@ import scala.util.matching.Regex
|
||||
// saved, but before the computer was saved, leading to mismatching states in
|
||||
// the save file - a Bad Thing (TM).
|
||||
|
||||
class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceInfo {
|
||||
class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceInfo with component.traits.VideoRamAware {
|
||||
override val node = Network.newNode(this, Visibility.Neighbors).
|
||||
withComponent("gpu").
|
||||
withConnector().
|
||||
@ -47,17 +46,32 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
|
||||
private var screenInstance: Option[api.internal.TextBuffer] = None
|
||||
|
||||
private def screen(f: (api.internal.TextBuffer) => Array[AnyRef]) = screenInstance match {
|
||||
case Some(screen) => screen.synchronized(f(screen))
|
||||
case _ => Array(Unit, "no screen")
|
||||
private var bufferIndex: Int = RESERVED_SCREEN_INDEX // screen is index zero
|
||||
|
||||
private def screen(index: Int, f: (api.internal.TextBuffer) => Array[AnyRef]): Array[AnyRef] = {
|
||||
if (index == RESERVED_SCREEN_INDEX) {
|
||||
screenInstance match {
|
||||
case Some(screen) => screen.synchronized(f(screen))
|
||||
case _ => Array(Unit, "no screen")
|
||||
}
|
||||
} else {
|
||||
getBuffer(index) match {
|
||||
case Some(buffer: api.internal.TextBuffer) => f(buffer)
|
||||
case _ => Array(Unit, "invalid buffer index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def screen(f: (api.internal.TextBuffer) => Array[AnyRef]): Array[AnyRef] = screen(bufferIndex, f)
|
||||
|
||||
final val setBackgroundCosts = Array(1.0 / 32, 1.0 / 64, 1.0 / 128)
|
||||
final val setForegroundCosts = Array(1.0 / 32, 1.0 / 64, 1.0 / 128)
|
||||
final val setPaletteColorCosts = Array(1.0 / 2, 1.0 / 8, 1.0 / 16)
|
||||
final val setCosts = Array(1.0 / 64, 1.0 / 128, 1.0 / 256)
|
||||
final val copyCosts = Array(1.0 / 16, 1.0 / 32, 1.0 / 64)
|
||||
final val fillCosts = Array(1.0 / 32, 1.0 / 64, 1.0 / 128)
|
||||
final val bitbltCosts = Array(2.0, 1.0, 1.0 / 2.0)
|
||||
final val totalVRAM: Int = (maxResolution._1 * maxResolution._2) * Settings.get.vramSizes(0 max tier min Settings.get.vramSizes.length)
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
@ -81,30 +95,134 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def getViewportOverlapSize(s: api.internal.TextBuffer, x1: Int, y1: Int, x2: Int, y2: Int): Int = {
|
||||
val width = s.getViewportWidth
|
||||
val height = s.getViewportHeight
|
||||
val left = math.min(x1, x2);
|
||||
val right = math.max(x1, x2);
|
||||
val top = math.min(y1, y2);
|
||||
val bottom = math.max(y1, y2);
|
||||
if (right < 0 || left >= width || top >= height || bottom < 0)
|
||||
return 0
|
||||
val box_left = math.max(0, left)
|
||||
val box_right = math.min(width - 1, right)
|
||||
val box_top = math.max(0, top)
|
||||
val box_bottom = math.min(height - 1, bottom)
|
||||
(box_right - box_left + 1) * (box_bottom - box_top + 1)
|
||||
private def consumeViewportPower(buffer: api.internal.TextBuffer, context: Context, budgetCost: Double, units: Int, factor: Double): Boolean = {
|
||||
buffer match {
|
||||
case _: component.GpuTextBuffer => true
|
||||
case _ =>
|
||||
context.consumeCallBudget(budgetCost)
|
||||
consumePower(units, factor)
|
||||
}
|
||||
}
|
||||
|
||||
private def consumeViewportPower(overlap: Int, context: Context, budgetCost: Double, callFactor: Double): Boolean = {
|
||||
if (overlap == 0) true
|
||||
else {
|
||||
context.consumeCallBudget(budgetCost)
|
||||
consumePower(overlap, callFactor)
|
||||
@Callback(direct = true, doc = """function(): number -- returns the index of the currently selected buffer. 0 is reserved for the screen. Can return 0 even when there is no screen""")
|
||||
def getBuffer(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
result(bufferIndex)
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(index: number): number -- Sets the active buffer to `index`. 1 is the first vram buffer and 0 is reserved for the screen. returns nil for invalid index (0 is always valid)""")
|
||||
def setBuffer(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val previousIndex: Int = bufferIndex
|
||||
val newIndex: Int = args.checkInteger(0)
|
||||
if (newIndex != RESERVED_SCREEN_INDEX && getBuffer(newIndex).isEmpty) {
|
||||
result(Unit, "invalid buffer index")
|
||||
} else {
|
||||
bufferIndex = newIndex
|
||||
if (bufferIndex == RESERVED_SCREEN_INDEX) {
|
||||
screen(s => result(true))
|
||||
}
|
||||
result(previousIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(): number -- Returns an array of indexes of the allocated buffers""")
|
||||
def buffers(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
result(bufferIndexes())
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function([width: number, height: number]): number -- allocates a new buffer with dimensions width*height (defaults to max resolution) and appends it to the buffer list. Returns the index of the new buffer and returns nil with an error message on failure. A buffer can be allocated even when there is no screen bound to this gpu. Index 0 is always reserved for the screen and thus the lowest index of an allocated buffer is always 1.""")
|
||||
def allocateBuffer(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val width: Int = args.optInteger(0, maxResolution._1)
|
||||
val height: Int = args.optInteger(1, maxResolution._2)
|
||||
val size: Int = width * height
|
||||
if (width <= 0 || height <= 0) {
|
||||
result(Unit, "invalid page dimensions: must be greater than zero")
|
||||
}
|
||||
else if (size > (totalVRAM - calculateUsedMemory)) {
|
||||
result(Unit, "not enough video memory")
|
||||
} else {
|
||||
val format: PackedColor.ColorFormat = PackedColor.Depth.format(Settings.screenDepthsByTier(tier))
|
||||
val buffer = new li.cil.oc.util.TextBuffer(width, height, format)
|
||||
val page = component.GpuTextBuffer.wrap(nextAvailableBufferIndex, buffer)
|
||||
addBuffer(page)
|
||||
result(page.id)
|
||||
}
|
||||
}
|
||||
|
||||
// this event occurs when the gpu is told a page was removed - we need to notify the screen of this
|
||||
// we do this because the VideoRamAware trait only notifies itself, it doesn't assume there is a screen
|
||||
override def onBufferRamDestroy(ids: Array[Int]): Unit = {
|
||||
// first protect our buffer index - it needs to fall back to the screen if its buffer was removed
|
||||
if (ids.contains(bufferIndex)) {
|
||||
bufferIndex = RESERVED_SCREEN_INDEX
|
||||
}
|
||||
if (ids.nonEmpty) {
|
||||
screen(RESERVED_SCREEN_INDEX, s => s match {
|
||||
case oc: component.traits.VideoRamAware => result(oc.removeBuffers(ids))
|
||||
case _ => result(true)// addon mod screen type that is not video ram aware
|
||||
})
|
||||
} else result(true)
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(index: number): boolean -- Closes buffer at `index`. Returns true if a buffer closed. If the current buffer is closed, index moves to 0""")
|
||||
def freeBuffer(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val index: Int = args.checkInteger(0)
|
||||
if (removeBuffers(Array(index))) result(true)
|
||||
else result(Unit, "no buffer at index")
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(): number -- Closes all buffers and returns true on success. If the active buffer is closed, index moves to 0""")
|
||||
def freeAllBuffers(context: Context, args: Arguments): Array[AnyRef] = result(removeAllBuffers())
|
||||
|
||||
@Callback(direct = true, doc = """function(): number -- returns the total memory size of the gpu vram. This does not include the screen.""")
|
||||
def totalMemory(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
result(totalVRAM)
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(): number -- returns the total free memory not allocated to buffers. This does not include the screen.""")
|
||||
def freeMemory(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
result(totalVRAM - calculateUsedMemory)
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function(index: number): number, number -- returns the buffer size at index. Returns the screen resolution for index 0. returns nil for invalid indexes""")
|
||||
def getBufferSize(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val idx = args.checkInteger(0)
|
||||
screen(idx, s => result(s.getWidth, s.getHeight))
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function([dst: number, col: number, row: number, width: number, height: number, src: number, fromCol: number, fromRow: number]):boolean -- bitblt from buffer to screen. All parameters are optional. Writes to `dst` page in rectangle `x, y, width, height`, defaults to the bound screen and its viewport. Reads data from `src` page at `fx, fy`, default is the active page from position 1, 1""")
|
||||
def bitblt(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val dstIdx = args.optInteger(0, RESERVED_SCREEN_INDEX)
|
||||
screen(dstIdx, dst => {
|
||||
val col = args.optInteger(1, 1)
|
||||
val row = args.optInteger(2, 1)
|
||||
val w = args.optInteger(3, dst.getWidth)
|
||||
val h = args.optInteger(4, dst.getHeight)
|
||||
val srcIdx = args.optInteger(5, bufferIndex)
|
||||
screen(srcIdx, src => {
|
||||
val fromCol = args.optInteger(6, 1)
|
||||
val fromRow = args.optInteger(7, 1)
|
||||
// if src is vram and dirty, bltbit cost is large
|
||||
val dirtyPage = src match {
|
||||
case vram: GpuTextBuffer => vram.dirty
|
||||
case _ => false
|
||||
}
|
||||
|
||||
if (consumeViewportPower(dst, context, if (dirtyPage) bitbltCosts(tier) else setCosts(tier), w * h, Settings.get.gpuCopyCost)) {
|
||||
if (dstIdx == srcIdx) {
|
||||
val tx = col - fromCol
|
||||
val ty = row - fromRow
|
||||
dst.copy(col, row, w, h, tx, ty)
|
||||
result(true)
|
||||
} else {
|
||||
// at least one of the two buffers is a gpu buffer
|
||||
component.GpuTextBuffer.bitblt(dst, col, row, w, h, src, fromRow, fromCol)
|
||||
result(true)
|
||||
}
|
||||
} else result(Unit, "not enough energy")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@Callback(doc = """function(address:string[, reset:boolean=true]):boolean -- Binds the GPU to the screen with the specified address and resets screen settings if `reset` is true.""")
|
||||
def bind(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
val address = args.checkString(0)
|
||||
@ -123,6 +241,10 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
s.setColorDepth(api.internal.TextBuffer.ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal)))
|
||||
s.setForegroundColor(0xFFFFFF)
|
||||
s.setBackgroundColor(0x000000)
|
||||
s match {
|
||||
case oc: component.traits.VideoRamAware => oc.removeAllBuffers()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
else context.pause(0) // To discourage outputting "in realtime" to multiple screens using one GPU.
|
||||
result(true)
|
||||
@ -132,7 +254,13 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function():string -- Get the address of the screen the GPU is currently bound to.""")
|
||||
def getScreen(context: Context, args: Arguments): Array[AnyRef] = screen(s => result(s.node.address))
|
||||
def getScreen(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
if (bufferIndex == RESERVED_SCREEN_INDEX) {
|
||||
screen(s => result(s.node.address))
|
||||
} else {
|
||||
result(Unit, "the current text buffer is video ram")
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(direct = true, doc = """function():number, boolean -- Get the current background color and whether it's from the palette or not.""")
|
||||
def getBackground(context: Context, args: Arguments): Array[AnyRef] =
|
||||
@ -307,8 +435,7 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
screen(s => {
|
||||
val x2 = if (vertical) x else x + value.length - 1
|
||||
val y2 = if (!vertical) y else y + value.length - 1
|
||||
val overlap: Int = getViewportOverlapSize(s, x, y, x2, y2)
|
||||
if (consumeViewportPower(overlap, context, setCosts(tier), Settings.get.gpuSetCost)) {
|
||||
if (consumeViewportPower(s, context, setCosts(tier), value.length, Settings.get.gpuSetCost)) {
|
||||
s.set(x, y, value, vertical)
|
||||
result(true)
|
||||
}
|
||||
@ -325,8 +452,7 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
val tx = args.checkInteger(4)
|
||||
val ty = args.checkInteger(5)
|
||||
screen(s => {
|
||||
val overlap: Int = getViewportOverlapSize(s, x + tx, y + ty, x + tx + w - 1, y + ty + h - 1)
|
||||
if (consumeViewportPower(overlap, context, copyCosts(tier), Settings.get.gpuCopyCost)) {
|
||||
if (consumeViewportPower(s, context, copyCosts(tier), w * h, Settings.get.gpuCopyCost)) {
|
||||
s.copy(x, y, w, h, tx, ty)
|
||||
result(true)
|
||||
}
|
||||
@ -336,7 +462,6 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
|
||||
@Callback(direct = true, doc = """function(x:number, y:number, width:number, height:number, char:string):boolean -- Fills a portion of the screen at the specified position with the specified size with the specified character.""")
|
||||
def fill(context: Context, args: Arguments): Array[AnyRef] = {
|
||||
context.consumeCallBudget(fillCosts(tier))
|
||||
val x = args.checkInteger(0) - 1
|
||||
val y = args.checkInteger(1) - 1
|
||||
val w = math.max(0, args.checkInteger(2))
|
||||
@ -345,8 +470,7 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
if (value.length == 1) screen(s => {
|
||||
val c = value.charAt(0)
|
||||
val cost = if (c == ' ') Settings.get.gpuClearCost else Settings.get.gpuFillCost
|
||||
val overlap: Int = getViewportOverlapSize(s, x, y, x + w - 1, y + h - 1)
|
||||
if (consumeViewportPower(overlap, context, fillCosts(tier), cost)) {
|
||||
if (consumeViewportPower(s, context, fillCosts(tier), w * h, cost)) {
|
||||
s.fill(x, y, w, h, value.charAt(0))
|
||||
result(true)
|
||||
}
|
||||
@ -363,6 +487,13 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
|
||||
override def onMessage(message: Message) {
|
||||
super.onMessage(message)
|
||||
if (node.isNeighborOf(message.source)) {
|
||||
if (message.name == "computer.stopped" || message.name == "computer.started") {
|
||||
bufferIndex = RESERVED_SCREEN_INDEX
|
||||
removeAllBuffers()
|
||||
}
|
||||
}
|
||||
|
||||
if (message.name == "computer.stopped" && node.isNeighborOf(message.source)) {
|
||||
screen(s => {
|
||||
val (gmw, gmh) = maxResolution
|
||||
@ -426,23 +557,69 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private val SCREEN_KEY: String = "screen"
|
||||
private val BUFFER_INDEX_KEY: String = "bufferIndex"
|
||||
private val VIDEO_RAM_KEY: String = "videoRam"
|
||||
private final val NBT_PAGES: String = "pages"
|
||||
private final val NBT_PAGE_IDX: String = "page_idx"
|
||||
private final val NBT_PAGE_DATA: String = "page_data"
|
||||
private val COMPOUND_ID = (new NBTTagCompound).getId
|
||||
|
||||
override def load(nbt: NBTTagCompound) {
|
||||
super.load(nbt)
|
||||
|
||||
if (nbt.hasKey("screen")) {
|
||||
nbt.getString("screen") match {
|
||||
if (nbt.hasKey(SCREEN_KEY)) {
|
||||
nbt.getString(SCREEN_KEY) match {
|
||||
case screen: String if !screen.isEmpty => screenAddress = Some(screen)
|
||||
case _ => screenAddress = None
|
||||
}
|
||||
screenInstance = None
|
||||
}
|
||||
|
||||
if (nbt.hasKey(BUFFER_INDEX_KEY)) {
|
||||
bufferIndex = nbt.getInteger(BUFFER_INDEX_KEY)
|
||||
}
|
||||
|
||||
removeAllBuffers() // JUST in case
|
||||
if (nbt.hasKey(VIDEO_RAM_KEY)) {
|
||||
val videoRamNbt = nbt.getCompoundTag(VIDEO_RAM_KEY)
|
||||
val nbtPages = videoRamNbt.getTagList(NBT_PAGES, COMPOUND_ID)
|
||||
for (i <- 0 until nbtPages.tagCount) {
|
||||
val nbtPage = nbtPages.getCompoundTagAt(i)
|
||||
val idx: Int = nbtPage.getInteger(NBT_PAGE_IDX)
|
||||
val data = nbtPage.getCompoundTag(NBT_PAGE_DATA)
|
||||
loadBuffer(idx, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound) {
|
||||
super.save(nbt)
|
||||
|
||||
if (screenAddress.isDefined) {
|
||||
nbt.setString("screen", screenAddress.get)
|
||||
nbt.setString(SCREEN_KEY, screenAddress.get)
|
||||
}
|
||||
|
||||
nbt.setInteger(BUFFER_INDEX_KEY, bufferIndex)
|
||||
|
||||
val videoRamNbt = new NBTTagCompound
|
||||
val nbtPages = new NBTTagList
|
||||
|
||||
val indexes = bufferIndexes()
|
||||
for (idx: Int <- indexes) {
|
||||
getBuffer(idx) match {
|
||||
case Some(page) => {
|
||||
val nbtPage = new NBTTagCompound
|
||||
nbtPage.setInteger(NBT_PAGE_IDX, idx)
|
||||
val data = new NBTTagCompound
|
||||
page.data.save(data)
|
||||
nbtPage.setTag(NBT_PAGE_DATA, data)
|
||||
nbtPages.appendTag(nbtPage)
|
||||
}
|
||||
case _ => // ignore
|
||||
}
|
||||
}
|
||||
videoRamNbt.setTag(NBT_PAGES, nbtPages)
|
||||
nbt.setTag(VIDEO_RAM_KEY, videoRamNbt)
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +205,28 @@ class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.Col
|
||||
changed
|
||||
}
|
||||
|
||||
// copy a portion of another buffer into this buffer
|
||||
def rawcopy(col: Int, row: Int, w: Int, h: Int, src: TextBuffer, fromCol: Int, fromRow: Int): Boolean = {
|
||||
var changed: Boolean = false
|
||||
val col_index = col - 1
|
||||
val row_index = row - 1
|
||||
for (yOffset <- 0 until h) {
|
||||
val dstCharLine = buffer(row_index + yOffset)
|
||||
val dstColorLine = color(row_index + yOffset)
|
||||
for (xOffset <- 0 until w) {
|
||||
val srcChar = src.buffer(fromRow + yOffset - 1)(fromCol + xOffset - 1)
|
||||
val srcColor = src.color(fromRow + yOffset - 1)(fromCol + xOffset - 1)
|
||||
if (srcChar != dstCharLine(col_index + xOffset) || srcColor != dstColorLine(col_index + xOffset)) {
|
||||
changed = true
|
||||
dstCharLine(col_index + xOffset) = srcChar
|
||||
dstColorLine(col_index + xOffset) = srcColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed
|
||||
}
|
||||
|
||||
private def setChar(line: Array[Char], lineColor: Array[Short], x: Int, c: Char) {
|
||||
if (FontUtils.wcwidth(c) > 1 && x >= line.length - 1) {
|
||||
// Don't allow setting wide chars in right-most col.
|
||||
|
Loading…
x
Reference in New Issue
Block a user