From 15d34a86601098574fd069bf09b889cb3b28b941 Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 15 May 2020 21:01:41 -0700 Subject: [PATCH] GPU speed up: Writing to the gpu buffer outside the viewport is free. no budget cost, no power cost, (also, writes are already direct calls) We've discussed a large variety of options for the gpu I've reviewed our options and suggestions. Ultimately - users want faster graphics. Most of the ideas are relating to what api is meaningful to the user. The core issue we have in making graphics faster is an increase load on the server. For example Tier 3 GPU and Tier 3 Screen has a max resolution of 160x50 If you set the viewport (via gpu.setViewport) to 160x25 the bottom half of the buffer will no longer be shown. All gpu.set, gpu.copy, and gpu.fill calls into that space have no cost Overlaps are calculated for partial cost. Half in and half out will have half the power cost. closes #779 --- .../oc/server/component/GraphicsCard.scala | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala index b25316e19..edc7ce2dd 100644 --- a/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala +++ b/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala @@ -71,16 +71,40 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI DeviceAttribute.Clock -> clockInfo ) - def capacityInfo = (maxResolution._1 * maxResolution._2).toString + def capacityInfo: String = (maxResolution._1 * maxResolution._2).toString - def widthInfo = Array("1", "4", "8").apply(maxDepth.ordinal()) + def widthInfo: String = Array("1", "4", "8").apply(maxDepth.ordinal()) - def clockInfo = ((2000 / setBackgroundCosts(tier)).toInt / 100).toString + "/" + ((2000 / setForegroundCosts(tier)).toInt / 100).toString + "/" + ((2000 / setPaletteColorCosts(tier)).toInt / 100).toString + "/" + ((2000 / setCosts(tier)).toInt / 100).toString + "/" + ((2000 / copyCosts(tier)).toInt / 100).toString + "/" + ((2000 / fillCosts(tier)).toInt / 100).toString + def clockInfo: String = ((2000 / setBackgroundCosts(tier)).toInt / 100).toString + "/" + ((2000 / setForegroundCosts(tier)).toInt / 100).toString + "/" + ((2000 / setPaletteColorCosts(tier)).toInt / 100).toString + "/" + ((2000 / setCosts(tier)).toInt / 100).toString + "/" + ((2000 / copyCosts(tier)).toInt / 100).toString + "/" + ((2000 / fillCosts(tier)).toInt / 100).toString override def getDeviceInfo: util.Map[String, String] = deviceInfo // ----------------------------------------------------------------------- // + 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(overlap: Int, context: Context, budgetCost: Double, callFactor: Double): Boolean = { + if (overlap == 0) true + else { + context.consumeCallBudget(budgetCost) + consumePower(overlap, callFactor) + } + } + @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) @@ -275,14 +299,16 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI @Callback(direct = true, doc = """function(x:number, y:number, value:string[, vertical:boolean]):boolean -- Plots a string value to the screen at the specified position. Optionally writes the string vertically.""") def set(context: Context, args: Arguments): Array[AnyRef] = { - context.consumeCallBudget(setCosts(tier)) val x = args.checkInteger(0) - 1 val y = args.checkInteger(1) - 1 val value = args.checkString(2) val vertical = args.optBoolean(3, false) screen(s => { - if (consumePower(value.length, Settings.get.gpuSetCost)) { + 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)) { s.set(x, y, value, vertical) result(true) } @@ -292,7 +318,6 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI @Callback(direct = true, doc = """function(x:number, y:number, width:number, height:number, tx:number, ty:number):boolean -- Copies a portion of the screen from the specified location with the specified size by the specified translation.""") def copy(context: Context, args: Arguments): Array[AnyRef] = { - context.consumeCallBudget(copyCosts(tier)) val x = args.checkInteger(0) - 1 val y = args.checkInteger(1) - 1 val w = math.max(0, args.checkInteger(2)) @@ -300,7 +325,8 @@ class GraphicsCard(val tier: Int) extends prefab.ManagedEnvironment with DeviceI val tx = args.checkInteger(4) val ty = args.checkInteger(5) screen(s => { - if (consumePower(w * h, Settings.get.gpuCopyCost)) { + 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)) { s.copy(x, y, w, h, tx, ty) result(true) } @@ -319,7 +345,8 @@ 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 - if (consumePower(w * h, cost)) { + val overlap: Int = getViewportOverlapSize(s, x, y, x + w - 1, y + h - 1) + if (consumeViewportPower(overlap, context, fillCosts(tier), cost)) { s.fill(x, y, w, h, value.charAt(0)) result(true) }