gpu rendered light wip

This commit is contained in:
minjaesong
2019-01-29 02:21:22 +09:00
parent 465ed0d7a4
commit cd1ad9277a
9 changed files with 440 additions and 176 deletions

100
assets/raytracelight.frag Normal file
View File

@@ -0,0 +1,100 @@
#version 120
#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_color;
varying vec2 v_texCoords;
// all 3 must have the same dimension!
// the divisor of 2 input and an output must be the same. I.e. either divide all by 4, or not.
uniform sampler2D shades;
uniform sampler2D lights;
uniform sampler2D u_texture;
uniform vec2 outSize;
uniform float multiplier = 4.0; // if divided by four, put 4.0 in there
#define TRAVERSE_SIZE 128 // should be good for screen size up to 1920 for tile size of 16
vec4 sampleFrom(sampler2D from, vec2 which) {
return texture2D(from, which / outSize);
}
int traceRayCount(vec2 delta) {
vec2 absDelta = abs(delta);
int arraySize = int(max(absDelta.x, absDelta.y));
return arraySize + 1;
}
vec2[TRAVERSE_SIZE] traceRay(int arraySize, vec2 from, vec2 to) {
vec2 delta = to - from;
vec2[TRAVERSE_SIZE] returnArray;
int arri = 0;
// if the line is not vertical...
if (delta.x != 0) {
float deltaError = abs(delta.y / delta.x);
float error = 0.0;
float traceY = from.y;
for (float traceX = from.x; traceX <= to.x; traceX++) {
// plot(traceX, traceY)
returnArray[arri] = vec2(traceX, traceY);
arri = arri + 1;
error = error + deltaError;
if (error >= 0.5) {
traceY = traceY + sign(delta.y);
error = error - 1.0;
}
}
}
else {
for (float traceY = from.y; traceY <= to.y; traceY++) {
returnArray[arri] = vec2(from.x, traceY);
}
}
return returnArray;
}
void main() {
vec4 outColor = vec4(0.0,0.0,0.0,0.0);
// 1. pick a light source
for (int y = 0; y < int(outSize.y); y++) {
for (int x = 0; x < int(outSize.x); x++) {
vec2 from = vec2(x, y);
vec2 to = gl_FragCoord.xy;
vec2 delta = to - from;
int traceCount = traceRayCount(delta);
vec4 light = sampleFrom(lights, from);
// 2. get a trace path
vec2[TRAVERSE_SIZE] returnArray = traceRay(traceCount, from, to);
// 2.1 get angular darkening coefficient
vec2 unitVec = delta / max(delta.x, delta.y);
float angularDimming = sqrt(unitVec.x * unitVec.x + unitVec.y * unitVec.y);
//float angularDimming = 1.0; // TODO depends on the angle of (lightPos, gl_FragCoord.x)
// 3. traverse the light path to dim the "light"
// var "light" will be attenuated after this loop
for (int i = 0; i < traceCount; i++) {
vec4 shade = sampleFrom(shades, returnArray[i]) * angularDimming;
light = light - shade;
}
// 4. mix the incoming light into the light buffer.
outColor = max(outColor, light);
}
}
gl_FragColor = outColor * multiplier;
gl_FragColor = vec4(0,1,0,1);
gl_FragColor = vec4(texture2D(lights, v_texCoords)) * multiplier;
}

View File

@@ -745,4 +745,30 @@ public class AppLoader implements ApplicationListener {
return s; return s;
} }
public static void measureDebugTime(String name, kotlin.jvm.functions.Function0<kotlin.Unit> block) {
if (IS_DEVELOPMENT_BUILD) {
//debugTimers.put(name, kotlin.system.TimingKt.measureNanoTime(block));
long start = System.nanoTime();
block.invoke();
debugTimers.put(name, System.nanoTime() - start);
}
}
public static void setDebugTime(String name, long value) {
if (IS_DEVELOPMENT_BUILD) {
debugTimers.put(name, value);
}
}
public static void addDebugTime(String target, String... targets) {
if (IS_DEVELOPMENT_BUILD) {
long l = 0L;
for (String s : targets) {
l += ((long) debugTimers.get(s));
}
debugTimers.put(target, l);
}
}
} }

View File

@@ -24,6 +24,7 @@ object DefaultConfig {
jsonObject.addProperty("notificationshowuptime", 6500) jsonObject.addProperty("notificationshowuptime", 6500)
jsonObject.addProperty("multithread", true) // experimental! jsonObject.addProperty("multithread", true) // experimental!
jsonObject.addProperty("multithreadedlight", false) // experimental! jsonObject.addProperty("multithreadedlight", false) // experimental!
jsonObject.addProperty("gpulightcalc", true) // experimental!

View File

@@ -529,7 +529,7 @@ class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
while (updateDeltaCounter >= updateRate) { while (updateDeltaCounter >= updateRate) {
//updateGame(delta) //updateGame(delta)
AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) }
updateDeltaCounter -= updateRate updateDeltaCounter -= updateRate
updateTries++ updateTries++
@@ -544,7 +544,7 @@ class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
/** RENDER CODE GOES HERE */ /** RENDER CODE GOES HERE */
//renderGame(batch) //renderGame(batch)
AppLoader.debugTimers["Ingame.render"] = measureNanoTime { renderGame(batch) } AppLoader.measureDebugTime("Ingame.render") { renderGame(batch) }
} }
protected fun updateGame(delta: Float) { protected fun updateGame(delta: Float) {

View File

@@ -12,7 +12,6 @@ import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.graphics.glutils.ShapeRenderer
import com.badlogic.gdx.math.Matrix4 import com.badlogic.gdx.math.Matrix4
import net.torvald.terrarum.gamecontroller.KeyToggler import net.torvald.terrarum.gamecontroller.KeyToggler
import kotlin.system.measureNanoTime
/** /**
* Must be called by the App Loader * Must be called by the App Loader
@@ -52,7 +51,7 @@ object PostProcessor {
AppLoader.debugTimers["Renderer.PostProcessor"] = measureNanoTime { AppLoader.measureDebugTime("Renderer.PostProcessor") {
gdxClearAndSetBlend(.094f, .094f, .094f, 0f) gdxClearAndSetBlend(.094f, .094f, .094f, 0f)

View File

@@ -392,8 +392,8 @@ object Terrarum : Screen {
} }
override fun render(delta: Float) { override fun render(delta: Float) {
AppLoader.debugTimers["GDX.rawDelta"] = Gdx.graphics.rawDeltaTime.times(1000_000_000f).toLong() AppLoader.setDebugTime("GDX.rawDelta", Gdx.graphics.rawDeltaTime.times(1000_000_000f).toLong())
AppLoader.debugTimers["GDX.smtDelta"] = Gdx.graphics.deltaTime.times(1000_000_000f).toLong() AppLoader.setDebugTime("GDX.smtDelta", Gdx.graphics.deltaTime.times(1000_000_000f).toLong())
AppLoader.getINSTANCE().screen.render(delta) AppLoader.getINSTANCE().screen.render(delta)
} }

View File

@@ -212,7 +212,7 @@ class TitleScreen(val batch: SpriteBatch) : Screen {
updateAkku -= delta updateAkku -= delta
i += 1 i += 1
} }
AppLoader.debugTimers["Ingame.updateCounter"] = i AppLoader.setDebugTime("Ingame.updateCounter", i)
// render? just do it anyway // render? just do it anyway

View File

@@ -36,7 +36,6 @@ import net.torvald.terrarum.worlddrawer.LightmapRenderer
import net.torvald.terrarum.worlddrawer.WorldCamera import net.torvald.terrarum.worlddrawer.WorldCamera
import java.util.* import java.util.*
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.system.measureNanoTime
/** /**
@@ -387,7 +386,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
LightmapRenderer.fireRecalculateEvent() LightmapRenderer.fireRecalculateEvent()
AppLoader.debugTimers["Ingame.updateCounter"] = 0 AppLoader.setDebugTime("Ingame.updateCounter", 0)
@@ -457,18 +456,19 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) {
var i = 0L var i = 0L
while (updateAkku >= delta) { while (updateAkku >= delta) {
AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) }
updateAkku -= delta updateAkku -= delta
i += 1 i += 1
} }
AppLoader.debugTimers["Ingame.updateCounter"] = i AppLoader.setDebugTime("Ingame.updateCounter", i)
/** RENDER CODE GOES HERE */ /** RENDER CODE GOES HERE */
AppLoader.debugTimers["Ingame.render"] = measureNanoTime { renderGame() } AppLoader.measureDebugTime("Ingame.render") { renderGame() }
AppLoader.debugTimers["Ingame.render-Light"] = AppLoader.setDebugTime("Ingame.render-Light",
(AppLoader.debugTimers["Ingame.render"] as Long) - ((AppLoader.debugTimers["Renderer.LightTotal"] as? Long) ?: 0) (AppLoader.debugTimers["Ingame.render"] as Long) - ((AppLoader.debugTimers["Renderer.LightTotal"] as? Long) ?: 0)
)
} }

View File

@@ -1,11 +1,10 @@
package net.torvald.terrarum.worlddrawer package net.torvald.terrarum.worlddrawer
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.*
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.glutils.FrameBuffer
import com.badlogic.gdx.graphics.glutils.ShaderProgram
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.* import net.torvald.terrarum.*
import net.torvald.terrarum.AppLoader.printdbg import net.torvald.terrarum.AppLoader.printdbg
@@ -16,10 +15,11 @@ import net.torvald.terrarum.concurrent.ParallelUtils.sliceEvenly
import net.torvald.terrarum.concurrent.ThreadParallel import net.torvald.terrarum.concurrent.ThreadParallel
import net.torvald.terrarum.gameactors.ActorWBMovable import net.torvald.terrarum.gameactors.ActorWBMovable
import net.torvald.terrarum.gameactors.Luminous import net.torvald.terrarum.gameactors.Luminous
import net.torvald.terrarum.gameworld.BlockAddress
import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.gameworld.GameWorld
import net.torvald.terrarum.modulebasegame.IngameRenderer import net.torvald.terrarum.modulebasegame.IngameRenderer
import net.torvald.terrarum.realestate.LandUtil
import java.util.concurrent.atomic.AtomicReferenceArray import java.util.concurrent.atomic.AtomicReferenceArray
import kotlin.system.measureNanoTime
/** /**
* Sub-portion of IngameRenderer. You are not supposed to directly deal with this. * Sub-portion of IngameRenderer. You are not supposed to directly deal with this.
@@ -37,6 +37,8 @@ import kotlin.system.measureNanoTime
object LightmapRenderer { object LightmapRenderer {
private var world: GameWorld = GameWorld.makeNullWorld() private var world: GameWorld = GameWorld.makeNullWorld()
private lateinit var lightCalcShader: ShaderProgram
private val SHADER_LIGHTING = AppLoader.getConfigBoolean("gpulightcalc")
/** do not call this yourself! Let your game renderer handle this! */ /** do not call this yourself! Let your game renderer handle this! */
fun setWorld(world: GameWorld) { fun setWorld(world: GameWorld) {
@@ -70,10 +72,6 @@ object LightmapRenderer {
val overscan_open: Int = 32 val overscan_open: Int = 32
val overscan_opaque: Int = 8 val overscan_opaque: Int = 8
init {
printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque")
}
// TODO resize(int, int) -aware // TODO resize(int, int) -aware
val LIGHTMAP_WIDTH = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(Terrarum.WIDTH) val LIGHTMAP_WIDTH = (Terrarum.ingame?.ZOOM_MINIMUM ?: 1f).inv().times(Terrarum.WIDTH)
@@ -86,8 +84,70 @@ object LightmapRenderer {
*/ */
// it utilises alpha channel to determine brightness of "glow" sprites (so that alpha channel works like UV light) // it utilises alpha channel to determine brightness of "glow" sprites (so that alpha channel works like UV light)
//private val lightmap: Array<Array<Color>> = Array(LIGHTMAP_HEIGHT) { Array(LIGHTMAP_WIDTH, { Color(0f,0f,0f,0f) }) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4. //private val lightmap: Array<Array<Color>> = Array(LIGHTMAP_HEIGHT) { Array(LIGHTMAP_WIDTH, { Color(0f,0f,0f,0f) }) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4.
private var lightmap: Array<Color> = Array(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT) { Color(0f,0f,0f,0f) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4. private val lightmap: Array<Color> = Array(LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT) { Color(0f,0f,0f,0f) } // Can't use framebuffer/pixmap -- this is a fvec4 array, whereas they are ivec4.
private val lanternMap = HashMap<Point2i, Color>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4) private val lanternMap = HashMap<BlockAddress, Color>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
private lateinit var texturedLightMap: FrameBuffer
private lateinit var texturedLightMapOutput: Pixmap
private lateinit var texturedLightSources: Texture
private lateinit var texturedShadeSources: Texture
private lateinit var texturedLightSourcePixmap: Pixmap // used to turn tiles into texture
private lateinit var texturedShadeSourcePixmap: Pixmap // used to turn tiles into texture
private lateinit var texturedLightQuad: Mesh
private lateinit var texturedLightCamera: OrthographicCamera
private lateinit var texturedLightBatch: SpriteBatch
private const val LIGHTMAP_UNIT = 1
private const val SHADEMAP_UNIT = 0
init {
printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque")
if (SHADER_LIGHTING) {
lightCalcShader = AppLoader.loadShader("assets/4096.vert", "assets/raytracelight.frag")
lightCalcShader.setUniformf("outSize", LIGHTMAP_WIDTH.toFloat(), LIGHTMAP_HEIGHT.toFloat())
lightCalcShader.setUniformi("shades", SHADEMAP_UNIT)
lightCalcShader.setUniformi("lights", LIGHTMAP_UNIT)
lightCalcShader.setUniformi("u_texture", 0)
texturedLightMap = FrameBuffer(Pixmap.Format.RGBA8888, LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, false)
//texturedLightMap = Texture(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
//texturedLightMap.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
texturedLightMapOutput = Pixmap(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
texturedLightSources = Texture(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
texturedLightSources.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
texturedShadeSources = Texture(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
texturedShadeSources.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
texturedLightSourcePixmap = Pixmap(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
texturedLightSourcePixmap.blending = Pixmap.Blending.None
texturedShadeSourcePixmap = Pixmap(LIGHTMAP_WIDTH, LIGHTMAP_HEIGHT, Pixmap.Format.RGBA8888)
texturedShadeSourcePixmap.blending = Pixmap.Blending.None
texturedLightQuad = Mesh(
true, 4, 6,
VertexAttribute.Position(),
VertexAttribute.ColorUnpacked(),
VertexAttribute.TexCoords(0)
)
texturedLightQuad.setVertices(floatArrayOf(
0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f, 1f,
LIGHTMAP_WIDTH.toFloat(), 0f, 0f, 1f, 1f, 1f, 1f, 1f, 1f,
LIGHTMAP_WIDTH.toFloat(), LIGHTMAP_HEIGHT.toFloat(), 0f, 1f, 1f, 1f, 1f, 1f, 0f,
0f, LIGHTMAP_HEIGHT.toFloat(), 0f, 1f, 1f, 1f, 1f, 0f, 0f))
texturedLightQuad.setIndices(shortArrayOf(0, 1, 2, 2, 3, 0))
texturedLightCamera = OrthographicCamera(LIGHTMAP_WIDTH.toFloat(), LIGHTMAP_HEIGHT.toFloat())
texturedLightCamera.setToOrtho(true)
texturedLightCamera.update()
texturedLightBatch = SpriteBatch(8, lightCalcShader)
}
}
private val AIR = Block.AIR private val AIR = Block.AIR
@@ -202,133 +262,190 @@ object LightmapRenderer {
//println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y") //println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y")
/** AppLoader.measureDebugTime("Renderer.Lanterns") {
* Updating order:
* ,--------. ,--+-----. ,-----+--. ,--------. -
* |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
* | ,-----+ | | 2 | | 2 | | +-----. | - depending on the noop_mask
* | |1 | | |1 | | 1| | | 1| |
* | | 2 | | `-----+ +-----' | | 2 | |
* | | 3| |↗ | | ↖| |3 | |
* `--+-----' `--------' `--------' `-----+--'
* round: 1 2 3 4
* for all lightmap[y][x], run in this order: 2-3-4-1-2
* If you run only 4 sets, orthogonal/diagonal artefacts are bound to occur,
* it seems 5-pass is mandatory
*/
AppLoader.debugTimers["Renderer.Lanterns"] = measureNanoTime {
buildLanternmap() buildLanternmap()
} // usually takes 3000 ns } // usually takes 3000 ns
if (!SHADER_LIGHTING) {
// wipe out lightmap /**
AppLoader.debugTimers["Renderer.Light0"] = measureNanoTime { * Updating order:
//for (ky in 0 until lightmap.size) for (kx in 0 until lightmap[0].size) lightmap[ky][kx] = colourNull * ,--------. ,--+-----. ,-----+--. ,--------. -
for (k in 0 until lightmap.size) lightmap[k] = colourNull * |↘ | | | 3| |3 | | | ↙| ↕︎ overscan_open / overscan_opaque
// when disabled, light will "decay out" instead of "instantly out", which can have a cool effect * | ,-----+ | | 2 | | 2 | | +-----. | - depending on the noop_mask
// but the performance boost is measly 0.1 ms on 6700K * | |1 | | |1 | | 1| | | 1| |
} * | | 2 | | `-----+ +-----' | | 2 | |
// O((5*9)n) == O(n) where n is a size of the map. * | | 3| |↗ | | ↖| |3 | |
// Because of inevitable overlaps on the area, it only works with MAX blend * `--+-----' `--------' `--------' `-----+--'
* round: 1 2 3 4
* for all lightmap[y][x], run in this order: 2-3-4-1-2
* If you run only 4 sets, orthogonal/diagonal artefacts are bound to occur,
* it seems 5-pass is mandatory
*/
// each usually takes 8 000 000..12 000 000 miliseconds total when not threaded // wipe out lightmap
AppLoader.measureDebugTime("Renderer.Light0") {
//for (ky in 0 until lightmap.size) for (kx in 0 until lightmap[0].size) lightmap[ky][kx] = colourNull
for (k in 0 until lightmap.size) lightmap[k] = colourNull
// when disabled, light will "decay out" instead of "instantly out", which can have a cool effect
// but the performance boost is measly 0.1 ms on 6700K
}
// O((5*9)n) == O(n) where n is a size of the map.
// Because of inevitable overlaps on the area, it only works with MAX blend
if (!AppLoader.getConfigBoolean("multithreadedlight")) {
//val workMap = Array(lightmap.size) { colourNull }
// The skipping is dependent on how you get ambient light, // each usually takes 8 000 000..12 000 000 miliseconds total when not threaded
// in this case we have 'spillage' due to the fact calculate() samples 3x3 area.
// FIXME theoretically skipping shouldn't work (light can be anywhere on the screen, not just centre if (!AppLoader.getConfigBoolean("multithreadedlight")) {
// but how does it actually work ?!?!?!!?!?!?!? //val workMap = Array(lightmap.size) { colourNull }
// because things are filled in subsequent frames ?
// because of not wiping out prev map ! (if pass=1 also calculates ambience, was disabled to not have to wipe out)
// Round 2 // The skipping is dependent on how you get ambient light,
AppLoader.debugTimers["Renderer.Light1"] = measureNanoTime { // in this case we have 'spillage' due to the fact calculate() samples 3x3 area.
for (y in for_y_end + overscan_open downTo for_y_start) {
for (x in for_x_start - overscan_open..for_x_end) { // FIXME theoretically skipping shouldn't work (light can be anywhere on the screen, not just centre
setLightOf(lightmap, x, y, calculate(x, y)) // but how does it actually work ?!?!?!!?!?!?!?
// because things are filled in subsequent frames ?
// because of not wiping out prev map ! (if pass=1 also calculates ambience, was disabled to not have to wipe out)
// Round 2
AppLoader.measureDebugTime("Renderer.Light1") {
for (y in for_y_end + overscan_open downTo for_y_start) {
for (x in for_x_start - overscan_open..for_x_end) {
setLightOf(lightmap, x, y, calculate(x, y))
}
} }
} }
}
// Round 3 // Round 3
AppLoader.debugTimers["Renderer.Light2"] = measureNanoTime { AppLoader.measureDebugTime("Renderer.Light2") {
for (y in for_y_end + overscan_open downTo for_y_start) { for (y in for_y_end + overscan_open downTo for_y_start) {
for (x in for_x_end + overscan_open downTo for_x_start) { for (x in for_x_end + overscan_open downTo for_x_start) {
setLightOf(lightmap, x, y, calculate(x, y)) setLightOf(lightmap, x, y, calculate(x, y))
}
} }
} }
}
// Round 4 // Round 4
AppLoader.debugTimers["Renderer.Light3"] = measureNanoTime { AppLoader.measureDebugTime("Renderer.Light3") {
for (y in for_y_start - overscan_open..for_y_end) { for (y in for_y_start - overscan_open..for_y_end) {
for (x in for_x_end + overscan_open downTo for_x_start) { for (x in for_x_end + overscan_open downTo for_x_start) {
setLightOf(lightmap, x, y, calculate(x, y)) setLightOf(lightmap, x, y, calculate(x, y))
}
} }
} }
}
// Round 1 // Round 1
AppLoader.debugTimers["Renderer.Light4"] = measureNanoTime { AppLoader.measureDebugTime("Renderer.Light4") {
for (y in for_y_start - overscan_open..for_y_end) { for (y in for_y_start - overscan_open..for_y_end) {
for (x in for_x_start - overscan_open..for_x_end) { for (x in for_x_start - overscan_open..for_x_end) {
setLightOf(lightmap, x, y, calculate(x, y)) setLightOf(lightmap, x, y, calculate(x, y))
}
} }
} }
AppLoader.addDebugTime("Renderer.LightTotal",
"Renderer.Light1",
"Renderer.Light2",
"Renderer.Light3",
"Renderer.Light4",
"Renderer.Light0"
)
} }
else if (world.worldIndex != -1) { // to avoid updating on the null world
val buf = AtomicReferenceArray<Color>(lightmap.size)
AppLoader.debugTimers["Renderer.LightTotal"] = AppLoader.measureDebugTime("Renderer.LightPrlPre") {
(AppLoader.debugTimers["Renderer.Light1"]!! as Long) + // update the content of buf using maxBlend -- it's not meant for overwrite
(AppLoader.debugTimers["Renderer.Light2"]!! as Long) +
(AppLoader.debugTimers["Renderer.Light3"]!! as Long) +
(AppLoader.debugTimers["Renderer.Light4"]!! as Long) +
(AppLoader.debugTimers["Renderer.Light0"]!! as Long)
}
else if (world.worldIndex != -1) { // to avoid updating on the null world
val buf = AtomicReferenceArray<Color>(lightmap.size)
AppLoader.debugTimers["Renderer.LightPrlPre"] = measureNanoTime { updateMessages.forEachIndexed { index, msg ->
// update the content of buf using maxBlend -- it's not meant for overwrite ThreadParallel.map(index, "Light") {
// for the message slices...
msg.forEach { m ->
// update the content of buf using maxBlend -- it's not meant for overwrite
buf.getAndUpdate(m.y * LIGHTMAP_WIDTH + m.x) { oldCol ->
val ux = m.x + for_x_start - overscan_open
val uy = m.y + for_y_start - overscan_open
updateMessages.forEachIndexed { index, msg -> (oldCol ?: colourNull) maxBlend calculate(ux, uy)
ThreadParallel.map(index, "Light") { }
// for the message slices...
msg.forEach { m ->
// update the content of buf using maxBlend -- it's not meant for overwrite
buf.getAndUpdate(m.y * LIGHTMAP_WIDTH + m.x) { oldCol ->
val ux = m.x + for_x_start - overscan_open
val uy = m.y + for_y_start - overscan_open
(oldCol ?: colourNull) maxBlend calculate(ux, uy)
} }
} }
} }
} }
}
AppLoader.debugTimers["Renderer.LightPrlRun"] = measureNanoTime { AppLoader.measureDebugTime("Renderer.LightPrlRun") {
ThreadParallel.startAllWaitForDie() ThreadParallel.startAllWaitForDie()
}
AppLoader.debugTimers["Renderer.LightPrlPost"] = measureNanoTime {
// copy to lightmap
for (k in 0 until lightmap.size) {
lightmap[k] = buf.getPlain(k) ?: colourNull
} }
}
AppLoader.debugTimers["Renderer.LightTotal"] = AppLoader.measureDebugTime("Renderer.LightPrlPost") {
(AppLoader.debugTimers["Renderer.LightPrlPre"]!! as Long) + // copy to lightmap
(AppLoader.debugTimers["Renderer.LightPrlRun"]!! as Long) + for (k in 0 until lightmap.size) {
(AppLoader.debugTimers["Renderer.LightPrlPost"]!! as Long) lightmap[k] = buf.getPlain(k) ?: colourNull
}
}
AppLoader.addDebugTime("Renderer.LightTotal",
"Renderer.LightPrlPre",
"Renderer.LightPrlRun",
"Renderer.LightPrlPost"
)
}
}
else {
AppLoader.measureDebugTime("Renderer.LightGPU") {
// prepare necessary textures (lightmap, shademap) for the input.
for (ty in 0 until LIGHTMAP_HEIGHT) {
for (tx in 0 until LIGHTMAP_WIDTH) {
val wx = tx + for_x_start - overscan_open
val wy = ty + for_y_start - overscan_open
// Several variables will be altered by this. See its documentation.
getLightsAndShades(wx, wy)
texturedLightSourcePixmap.drawPixel(tx, ty, lightLevelThis.toIntBits())
texturedShadeSourcePixmap.drawPixel(tx, ty, thisTileOpacity.toIntBits())
if (wy in for_y_start..for_y_end && wx in for_x_start..for_x_end) {
texturedLightSourcePixmap.drawPixel(tx, ty, 0x00FFFFFF)
}
else {
texturedLightSourcePixmap.drawPixel(tx, ty, 0xFF000000.toInt())
}
}
}
texturedLightSources.dispose()
texturedLightSources = Texture(texturedLightSourcePixmap)
texturedShadeSources.dispose()
texturedShadeSources = Texture(texturedShadeSourcePixmap)
texturedLightMap.inAction(texturedLightCamera, null) {
gdxClearAndSetBlend(0f,0f,0f,0f)
texturedLightSources.bind(LIGHTMAP_UNIT)
texturedShadeSources.bind(SHADEMAP_UNIT)
lightCalcShader.begin()
lightCalcShader.setUniformMatrix("u_projTrans", texturedLightCamera.combined)
texturedLightQuad.render(lightCalcShader, GL20.GL_TRIANGLES)
lightCalcShader.end()
}
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
}
} }
} }
// TODO re-init at every resize // TODO re-init at every resize
private lateinit var updateMessages: List<Array<ThreadedLightmapUpdateMessage>> private lateinit var updateMessages: List<Array<ThreadedLightmapUpdateMessage>>
@@ -392,10 +509,11 @@ object LightmapRenderer {
val normalisedColor = it.color.cpy().mul(DIV_FLOAT) val normalisedColor = it.color.cpy().mul(DIV_FLOAT)
lanternMap[Point2i(x, y)] = normalisedColor lanternMap[LandUtil.getBlockAddr(world, x, y)] = normalisedColor
//lanternMap[Point2i(x, y)] = normalisedColor
// Q&D fix for Roundworld anomaly // Q&D fix for Roundworld anomaly
lanternMap[Point2i(x + world.width, y)] = normalisedColor //lanternMap[Point2i(x + world.width, y)] = normalisedColor
lanternMap[Point2i(x - world.width, y)] = normalisedColor //lanternMap[Point2i(x - world.width, y)] = normalisedColor
} }
} }
} }
@@ -417,25 +535,18 @@ object LightmapRenderer {
private val thisTileOpacity2 = Color(0) // thisTileOpacity * sqrt(2) private val thisTileOpacity2 = Color(0) // thisTileOpacity * sqrt(2)
private val sunLight = Color(0) private val sunLight = Color(0)
/** /**
* Calculates the light simulation, using main lightmap as one of the input. * This function will alter following variables:
* - lightLevelThis
* - thisTerrain
* - thisFluid
* - thisWall
* - thisTileLuminosity
* - thisTileOpacity
* - thisTileOpacity2
* - sunlight
*/ */
private fun calculate(x: Int, y: Int): Color { private fun getLightsAndShades(x: Int, y: Int) {
// TODO is JEP 338 released yet?
// TODO if we only use limited set of operations (max, mul, sub) then int-ify should be possible.
// 0xiiii_ffff, 65536 for 1.0
// Tested it, no perf gain :(
// O(9n) == O(n) where n is a size of the map
// TODO devise multithreading on this
//ambientAccumulator.set(0f,0f,0f,0f)
// this six fetch tasks take 2 ms ?!
lightLevelThis.set(colourNull) lightLevelThis.set(colourNull)
thisTerrain = world.getTileFromTerrain(x, y) ?: Block.STONE thisTerrain = world.getTileFromTerrain(x, y) ?: Block.STONE
thisFluid = world.getFluid(x, y) thisFluid = world.getFluid(x, y)
@@ -464,7 +575,26 @@ object LightmapRenderer {
} }
// blend lantern // blend lantern
lightLevelThis.maxAndAssign(thisTileLuminosity).maxAndAssign(lanternMap[Point2i(x, y)] ?: colourNull) lightLevelThis.maxAndAssign(thisTileLuminosity).maxAndAssign(lanternMap[LandUtil.getBlockAddr(world, x, y)] ?: colourNull)
}
/**
* Calculates the light simulation, using main lightmap as one of the input.
*/
private fun calculate(x: Int, y: Int): Color {
// TODO is JEP 338 released yet?
// TODO if we only use limited set of operations (max, mul, sub) then int-ify should be possible.
// 0xiiii_ffff, 65536 for 1.0
// Tested it, no perf gain :(
// O(9n) == O(n) where n is a size of the map
// TODO devise multithreading on this
getLightsAndShades(x, y)
// calculate ambient // calculate ambient
/* + * + 0 4 1 /* + * + 0 4 1
@@ -519,49 +649,57 @@ object LightmapRenderer {
internal fun draw(batch: SpriteBatch) { internal fun draw(batch: SpriteBatch) {
val this_x_start = for_x_start// + overscan_open // when shader is not used: 0.5 ms on 6700K
val this_x_end = for_x_end// + overscan_open AppLoader.measureDebugTime("Renderer.LightToScreen") {
val this_y_start = for_y_start// + overscan_open
val this_y_end = for_y_end// + overscan_open
// wipe out beforehand. You DO need this val this_x_start = for_x_start// + overscan_open
lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker) val this_x_end = for_x_end// + overscan_open
lightBuffer.setColor(colourNull) val this_y_start = for_y_start// + overscan_open
lightBuffer.fill() val this_y_end = for_y_end// + overscan_open
// wipe out beforehand. You DO need this
if (!SHADER_LIGHTING) {
lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker)
lightBuffer.setColor(colourNull)
lightBuffer.fill()
// write to colour buffer // write to colour buffer
for (y in this_y_start..this_y_end) { for (y in this_y_start..this_y_end) {
//println("y: $y, this_y_start: $this_y_start") //println("y: $y, this_y_start: $this_y_start")
if (y == this_y_start && this_y_start == 0) { if (y == this_y_start && this_y_start == 0) {
//throw Error("Fuck hits again...") //throw Error("Fuck hits again...")
}
for (x in this_x_start..this_x_end) {
val color = (getLightForOpaque(x, y) ?: Color(0f, 0f, 0f, 0f)).normaliseToHDR()
lightBuffer.setColor(color)
//lightBuffer.drawPixel(x - this_x_start, y - this_y_start)
lightBuffer.drawPixel(x - this_x_start, lightBuffer.height - 1 - y + this_y_start) // flip Y
}
}
// draw to the batch
_lightBufferAsTex.dispose()
_lightBufferAsTex = Texture(lightBuffer)
_lightBufferAsTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
// we might not need shader here...
//batch.draw(lightBufferAsTex, 0f, 0f, lightBufferAsTex.width.toFloat(), lightBufferAsTex.height.toFloat())
batch.draw(_lightBufferAsTex, 0f, 0f, _lightBufferAsTex.width * DRAW_TILE_SIZE, _lightBufferAsTex.height * DRAW_TILE_SIZE)
} }
else {
for (x in this_x_start..this_x_end) { Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
batch.draw(texturedLightMap.colorBufferTexture, -overscan_open * DRAW_TILE_SIZE, -overscan_open * DRAW_TILE_SIZE, texturedLightMap.width * DRAW_TILE_SIZE, texturedLightMap.height * DRAW_TILE_SIZE)
val color = (getLightForOpaque(x, y) ?: Color(0f,0f,0f,0f)).normaliseToHDR()
lightBuffer.setColor(color)
//lightBuffer.drawPixel(x - this_x_start, y - this_y_start)
lightBuffer.drawPixel(x - this_x_start, lightBuffer.height - 1 - y + this_y_start) // flip Y
} }
} }
// draw to the batch
_lightBufferAsTex.dispose()
_lightBufferAsTex = Texture(lightBuffer)
_lightBufferAsTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest)
Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0) // so that batch that comes next will bind any tex to it
// we might not need shader here...
//batch.draw(lightBufferAsTex, 0f, 0f, lightBufferAsTex.width.toFloat(), lightBufferAsTex.height.toFloat())
batch.draw(_lightBufferAsTex, 0f, 0f, _lightBufferAsTex.width * DRAW_TILE_SIZE, _lightBufferAsTex.height * DRAW_TILE_SIZE)
} }
fun dispose() { fun dispose() {
@@ -769,6 +907,7 @@ object LightmapRenderer {
// make sure the BlocksDrawer is resized first! // make sure the BlocksDrawer is resized first!
// copied from BlocksDrawer, duh! // copied from BlocksDrawer, duh!
// FIXME 'lightBuffer' is not zoomable in this way
val tilesInHorizontal = (screenW.toFloat() / TILE_SIZE).ceilInt() + 1 val tilesInHorizontal = (screenW.toFloat() / TILE_SIZE).ceilInt() + 1
val tilesInVertical = (screenH.toFloat() / TILE_SIZE).ceilInt() + 1 val tilesInVertical = (screenH.toFloat() / TILE_SIZE).ceilInt() + 1
@@ -781,7 +920,6 @@ object LightmapRenderer {
lightBuffer = Pixmap(tilesInHorizontal, tilesInVertical, Pixmap.Format.RGBA8888) lightBuffer = Pixmap(tilesInHorizontal, tilesInVertical, Pixmap.Format.RGBA8888)
printdbg(this, "Resize event") printdbg(this, "Resize event")
} }