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
|
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
|
# 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).
|
# 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
|
# 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.")
|
OpenComputers.log.warn("Bad number of RAM sizes, ignoring.")
|
||||||
Array(192, 256, 384, 512, 768, 1024)
|
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 ramScaleFor64Bit = config.getDouble("computer.lua.ramScaleFor64Bit") max 1
|
||||||
val maxTotalRam = config.getInt("computer.lua.maxTotalRam") max 0
|
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.client.renderer.PetRenderer
|
||||||
import li.cil.oc.common.Loot
|
import li.cil.oc.common.Loot
|
||||||
import li.cil.oc.common.PacketType
|
import li.cil.oc.common.PacketType
|
||||||
|
import li.cil.oc.common.component
|
||||||
import li.cil.oc.common.container
|
import li.cil.oc.common.container
|
||||||
import li.cil.oc.common.nanomachines.ControllerImpl
|
import li.cil.oc.common.nanomachines.ControllerImpl
|
||||||
import li.cil.oc.common.tileentity._
|
import li.cil.oc.common.tileentity._
|
||||||
@ -620,6 +621,9 @@ object PacketHandler extends CommonPacketHandler {
|
|||||||
case PacketType.TextBufferMultiViewportResolutionChange => onTextBufferMultiViewportResolutionChange(p, buffer)
|
case PacketType.TextBufferMultiViewportResolutionChange => onTextBufferMultiViewportResolutionChange(p, buffer)
|
||||||
case PacketType.TextBufferMultiMaxResolutionChange => onTextBufferMultiMaxResolutionChange(p, buffer)
|
case PacketType.TextBufferMultiMaxResolutionChange => onTextBufferMultiMaxResolutionChange(p, buffer)
|
||||||
case PacketType.TextBufferMultiSet => onTextBufferMultiSet(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.TextBufferMultiRawSetText => onTextBufferMultiRawSetText(p, buffer)
|
||||||
case PacketType.TextBufferMultiRawSetBackground => onTextBufferMultiRawSetBackground(p, buffer)
|
case PacketType.TextBufferMultiRawSetBackground => onTextBufferMultiRawSetBackground(p, buffer)
|
||||||
case PacketType.TextBufferMultiRawSetForeground => onTextBufferMultiRawSetForeground(p, buffer)
|
case PacketType.TextBufferMultiRawSetForeground => onTextBufferMultiRawSetForeground(p, buffer)
|
||||||
@ -700,6 +704,41 @@ object PacketHandler extends CommonPacketHandler {
|
|||||||
buffer.set(col, row, s, vertical)
|
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) {
|
def onTextBufferMultiRawSetText(p: PacketParser, buffer: api.internal.TextBuffer) {
|
||||||
val col = p.readInt()
|
val col = p.readInt()
|
||||||
val row = p.readInt()
|
val row = p.readInt()
|
||||||
|
@ -51,6 +51,9 @@ object PacketType extends Enumeration {
|
|||||||
SwitchActivity,
|
SwitchActivity,
|
||||||
TextBufferInit, // Goes both ways.
|
TextBufferInit, // Goes both ways.
|
||||||
TextBufferMulti,
|
TextBufferMulti,
|
||||||
|
TextBufferRamInit,
|
||||||
|
TextBufferBitBlt,
|
||||||
|
TextBufferRamDestroy,
|
||||||
TextBufferMultiColorChange,
|
TextBufferMultiColorChange,
|
||||||
TextBufferMultiCopy,
|
TextBufferMultiCopy,
|
||||||
TextBufferMultiDepthChange,
|
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.{ComponentTracker => ClientComponentTracker}
|
||||||
import li.cil.oc.client.{PacketSender => ClientPacketSender}
|
import li.cil.oc.client.{PacketSender => ClientPacketSender}
|
||||||
import li.cil.oc.common._
|
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.component.Keyboard
|
||||||
import li.cil.oc.server.{ComponentTracker => ServerComponentTracker}
|
import li.cil.oc.server.{ComponentTracker => ServerComponentTracker}
|
||||||
import li.cil.oc.server.{PacketSender => ServerPacketSender}
|
import li.cil.oc.server.{PacketSender => ServerPacketSender}
|
||||||
@ -34,7 +35,6 @@ import net.minecraft.entity.player.EntityPlayer
|
|||||||
import net.minecraft.nbt.NBTTagCompound
|
import net.minecraft.nbt.NBTTagCompound
|
||||||
import net.minecraftforge.event.world.ChunkEvent
|
import net.minecraftforge.event.world.ChunkEvent
|
||||||
import net.minecraftforge.event.world.WorldEvent
|
import net.minecraftforge.event.world.WorldEvent
|
||||||
import tconstruct.client.tabs.InventoryTabVanilla
|
|
||||||
|
|
||||||
import scala.collection.convert.WrapAsJava._
|
import scala.collection.convert.WrapAsJava._
|
||||||
import scala.collection.convert.WrapAsScala._
|
import scala.collection.convert.WrapAsScala._
|
||||||
@ -311,67 +311,28 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi
|
|||||||
override def onBufferColorChange(): Unit =
|
override def onBufferColorChange(): Unit =
|
||||||
proxy.onBufferColorChange()
|
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 = {
|
override def onBufferCopy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {
|
||||||
// only notify about viewport changes
|
proxy.onBufferCopy(col, row, w, h, tx, ty)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onBufferFill(col: Int, row: Int, w: Int, h: Int, c: Char): Unit = {
|
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)
|
proxy.onBufferFill(col, row, w, h, c)
|
||||||
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 {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = {
|
override def onBufferSet(col: Int, row: Int, s: String, vertical: Boolean): Unit = {
|
||||||
// only notify about viewport changes
|
proxy.onBufferSet(col, row, s, vertical)
|
||||||
val truncatedString = truncateToViewport(col, row, s, vertical)
|
}
|
||||||
if (!truncatedString.isEmpty) {
|
|
||||||
proxy.onBufferSet(col, row, truncatedString, 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 = {
|
override def rawSetText(col: Int, row: Int, text: Array[Array[Char]]): Unit = {
|
||||||
@ -595,6 +556,18 @@ object TextBuffer {
|
|||||||
owner.relativeLitArea = -1
|
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]]) {
|
def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
|
||||||
owner.relativeLitArea = -1
|
owner.relativeLitArea = -1
|
||||||
}
|
}
|
||||||
@ -678,6 +651,19 @@ object TextBuffer {
|
|||||||
dirty = true
|
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) {
|
override def keyDown(character: Char, code: Int, player: EntityPlayer) {
|
||||||
debug(s"{type = keyDown, char = $character, code = $code}")
|
debug(s"{type = keyDown, char = $character, code = $code}")
|
||||||
ClientPacketSender.sendKeyDown(nodeAddress, character, code)
|
ClientPacketSender.sendKeyDown(nodeAddress, character, code)
|
||||||
@ -780,6 +766,26 @@ object TextBuffer {
|
|||||||
owner.synchronized(ServerPacketSender.appendTextBufferSet(owner.pendingCommands, col, row, s, vertical))
|
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]]) {
|
override def onBufferRawSetText(col: Int, row: Int, text: Array[Array[Char]]) {
|
||||||
super.onBufferRawSetText(col, row, text)
|
super.onBufferRawSetText(col, row, text)
|
||||||
owner.host.markChanged()
|
owner.host.markChanged()
|
||||||
|
@ -5,7 +5,7 @@ import li.cil.oc.api
|
|||||||
import li.cil.oc.api.internal.TextBuffer
|
import li.cil.oc.api.internal.TextBuffer
|
||||||
import li.cil.oc.util.PackedColor
|
import li.cil.oc.util.PackedColor
|
||||||
|
|
||||||
trait TextBufferProxy extends api.internal.TextBuffer {
|
trait TextBufferProxy extends api.internal.TextBuffer with VideoRamAware {
|
||||||
def data: util.TextBuffer
|
def data: util.TextBuffer
|
||||||
|
|
||||||
override def getWidth: Int = data.width
|
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)
|
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]]) {
|
def appendTextBufferRawSetText(pb: PacketBuilder, col: Int, row: Int, text: Array[Array[Char]]) {
|
||||||
pb.writePacketType(PacketType.TextBufferMultiRawSetText)
|
pb.writePacketType(PacketType.TextBufferMultiRawSetText)
|
||||||
|
|
||||||
|
@ -2,10 +2,7 @@ package li.cil.oc.server.component
|
|||||||
|
|
||||||
import java.util
|
import java.util
|
||||||
|
|
||||||
import li.cil.oc.Constants
|
import li.cil.oc.{Constants, Localization, Settings, api}
|
||||||
import li.cil.oc.Localization
|
|
||||||
import li.cil.oc.Settings
|
|
||||||
import li.cil.oc.api
|
|
||||||
import li.cil.oc.api.Network
|
import li.cil.oc.api.Network
|
||||||
import li.cil.oc.api.driver.DeviceInfo
|
import li.cil.oc.api.driver.DeviceInfo
|
||||||
import li.cil.oc.api.driver.DeviceInfo.DeviceAttribute
|
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.network._
|
||||||
import li.cil.oc.api.prefab
|
import li.cil.oc.api.prefab
|
||||||
import li.cil.oc.util.PackedColor
|
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.collection.convert.WrapAsJava._
|
||||||
import scala.util.matching.Regex
|
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
|
// saved, but before the computer was saved, leading to mismatching states in
|
||||||
// the save file - a Bad Thing (TM).
|
// 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).
|
override val node = Network.newNode(this, Visibility.Neighbors).
|
||||||
withComponent("gpu").
|
withComponent("gpu").
|
||||||
withConnector().
|
withConnector().
|
||||||
@ -47,17 +46,32 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
|||||||
|
|
||||||
private var screenInstance: Option[api.internal.TextBuffer] = None
|
private var screenInstance: Option[api.internal.TextBuffer] = None
|
||||||
|
|
||||||
private def screen(f: (api.internal.TextBuffer) => Array[AnyRef]) = screenInstance match {
|
private var bufferIndex: Int = RESERVED_SCREEN_INDEX // screen is index zero
|
||||||
case Some(screen) => screen.synchronized(f(screen))
|
|
||||||
case _ => Array(Unit, "no screen")
|
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 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 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 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 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 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 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 = {
|
private def consumeViewportPower(buffer: api.internal.TextBuffer, context: Context, budgetCost: Double, units: Int, factor: Double): Boolean = {
|
||||||
val width = s.getViewportWidth
|
buffer match {
|
||||||
val height = s.getViewportHeight
|
case _: component.GpuTextBuffer => true
|
||||||
val left = math.min(x1, x2);
|
case _ =>
|
||||||
val right = math.max(x1, x2);
|
context.consumeCallBudget(budgetCost)
|
||||||
val top = math.min(y1, y2);
|
consumePower(units, factor)
|
||||||
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(overlap: Int, context: Context, budgetCost: Double, callFactor: Double): Boolean = {
|
@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""")
|
||||||
if (overlap == 0) true
|
def getBuffer(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
else {
|
result(bufferIndex)
|
||||||
context.consumeCallBudget(budgetCost)
|
}
|
||||||
consumePower(overlap, callFactor)
|
|
||||||
|
@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.""")
|
@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] = {
|
def bind(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
val address = args.checkString(0)
|
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.setColorDepth(api.internal.TextBuffer.ColorDepth.values.apply(math.min(maxDepth.ordinal, s.getMaximumColorDepth.ordinal)))
|
||||||
s.setForegroundColor(0xFFFFFF)
|
s.setForegroundColor(0xFFFFFF)
|
||||||
s.setBackgroundColor(0x000000)
|
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.
|
else context.pause(0) // To discourage outputting "in realtime" to multiple screens using one GPU.
|
||||||
result(true)
|
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.""")
|
@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.""")
|
@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] =
|
def getBackground(context: Context, args: Arguments): Array[AnyRef] =
|
||||||
@ -307,8 +435,7 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
|||||||
screen(s => {
|
screen(s => {
|
||||||
val x2 = if (vertical) x else x + value.length - 1
|
val x2 = if (vertical) x else x + value.length - 1
|
||||||
val y2 = if (!vertical) y else y + value.length - 1
|
val y2 = if (!vertical) y else y + value.length - 1
|
||||||
val overlap: Int = getViewportOverlapSize(s, x, y, x2, y2)
|
if (consumeViewportPower(s, context, setCosts(tier), value.length, Settings.get.gpuSetCost)) {
|
||||||
if (consumeViewportPower(overlap, context, setCosts(tier), Settings.get.gpuSetCost)) {
|
|
||||||
s.set(x, y, value, vertical)
|
s.set(x, y, value, vertical)
|
||||||
result(true)
|
result(true)
|
||||||
}
|
}
|
||||||
@ -325,8 +452,7 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
|||||||
val tx = args.checkInteger(4)
|
val tx = args.checkInteger(4)
|
||||||
val ty = args.checkInteger(5)
|
val ty = args.checkInteger(5)
|
||||||
screen(s => {
|
screen(s => {
|
||||||
val overlap: Int = getViewportOverlapSize(s, x + tx, y + ty, x + tx + w - 1, y + ty + h - 1)
|
if (consumeViewportPower(s, context, copyCosts(tier), w * h, Settings.get.gpuCopyCost)) {
|
||||||
if (consumeViewportPower(overlap, context, copyCosts(tier), Settings.get.gpuCopyCost)) {
|
|
||||||
s.copy(x, y, w, h, tx, ty)
|
s.copy(x, y, w, h, tx, ty)
|
||||||
result(true)
|
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.""")
|
@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] = {
|
def fill(context: Context, args: Arguments): Array[AnyRef] = {
|
||||||
context.consumeCallBudget(fillCosts(tier))
|
|
||||||
val x = args.checkInteger(0) - 1
|
val x = args.checkInteger(0) - 1
|
||||||
val y = args.checkInteger(1) - 1
|
val y = args.checkInteger(1) - 1
|
||||||
val w = math.max(0, args.checkInteger(2))
|
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 => {
|
if (value.length == 1) screen(s => {
|
||||||
val c = value.charAt(0)
|
val c = value.charAt(0)
|
||||||
val cost = if (c == ' ') Settings.get.gpuClearCost else Settings.get.gpuFillCost
|
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(s, context, fillCosts(tier), w * h, cost)) {
|
||||||
if (consumeViewportPower(overlap, context, fillCosts(tier), cost)) {
|
|
||||||
s.fill(x, y, w, h, value.charAt(0))
|
s.fill(x, y, w, h, value.charAt(0))
|
||||||
result(true)
|
result(true)
|
||||||
}
|
}
|
||||||
@ -363,6 +487,13 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI
|
|||||||
|
|
||||||
override def onMessage(message: Message) {
|
override def onMessage(message: Message) {
|
||||||
super.onMessage(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)) {
|
if (message.name == "computer.stopped" && node.isNeighborOf(message.source)) {
|
||||||
screen(s => {
|
screen(s => {
|
||||||
val (gmw, gmh) = maxResolution
|
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) {
|
override def load(nbt: NBTTagCompound) {
|
||||||
super.load(nbt)
|
super.load(nbt)
|
||||||
|
|
||||||
if (nbt.hasKey("screen")) {
|
if (nbt.hasKey(SCREEN_KEY)) {
|
||||||
nbt.getString("screen") match {
|
nbt.getString(SCREEN_KEY) match {
|
||||||
case screen: String if !screen.isEmpty => screenAddress = Some(screen)
|
case screen: String if !screen.isEmpty => screenAddress = Some(screen)
|
||||||
case _ => screenAddress = None
|
case _ => screenAddress = None
|
||||||
}
|
}
|
||||||
screenInstance = 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) {
|
override def save(nbt: NBTTagCompound) {
|
||||||
super.save(nbt)
|
super.save(nbt)
|
||||||
|
|
||||||
if (screenAddress.isDefined) {
|
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
|
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) {
|
private def setChar(line: Array[Char], lineColor: Array[Short], x: Int, c: Char) {
|
||||||
if (FontUtils.wcwidth(c) > 1 && x >= line.length - 1) {
|
if (FontUtils.wcwidth(c) > 1 && x >= line.length - 1) {
|
||||||
// Don't allow setting wide chars in right-most col.
|
// Don't allow setting wide chars in right-most col.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user