diff --git a/assets/raytracelight.frag b/assets/raytracelight.frag new file mode 100644 index 000000000..c36343fe9 --- /dev/null +++ b/assets/raytracelight.frag @@ -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; + +} diff --git a/src/net/torvald/terrarum/AppLoader.java b/src/net/torvald/terrarum/AppLoader.java index b39e4b8b4..9648466d2 100644 --- a/src/net/torvald/terrarum/AppLoader.java +++ b/src/net/torvald/terrarum/AppLoader.java @@ -745,4 +745,30 @@ public class AppLoader implements ApplicationListener { return s; } + + public static void measureDebugTime(String name, kotlin.jvm.functions.Function0 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); + } + } } \ No newline at end of file diff --git a/src/net/torvald/terrarum/DefaultConfig.kt b/src/net/torvald/terrarum/DefaultConfig.kt index ec094afa0..f2c9d9967 100644 --- a/src/net/torvald/terrarum/DefaultConfig.kt +++ b/src/net/torvald/terrarum/DefaultConfig.kt @@ -24,6 +24,7 @@ object DefaultConfig { jsonObject.addProperty("notificationshowuptime", 6500) jsonObject.addProperty("multithread", true) // experimental! jsonObject.addProperty("multithreadedlight", false) // experimental! + jsonObject.addProperty("gpulightcalc", true) // experimental! diff --git a/src/net/torvald/terrarum/FuckingWorldRenderer.kt.unused b/src/net/torvald/terrarum/FuckingWorldRenderer.kt.unused index 37f9e8be9..8ffcd7d16 100644 --- a/src/net/torvald/terrarum/FuckingWorldRenderer.kt.unused +++ b/src/net/torvald/terrarum/FuckingWorldRenderer.kt.unused @@ -529,7 +529,7 @@ class Ingame(batch: SpriteBatch) : IngameInstance(batch) { while (updateDeltaCounter >= updateRate) { //updateGame(delta) - AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } + AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) } updateDeltaCounter -= updateRate updateTries++ @@ -544,7 +544,7 @@ class Ingame(batch: SpriteBatch) : IngameInstance(batch) { /** RENDER CODE GOES HERE */ //renderGame(batch) - AppLoader.debugTimers["Ingame.render"] = measureNanoTime { renderGame(batch) } + AppLoader.measureDebugTime("Ingame.render") { renderGame(batch) } } protected fun updateGame(delta: Float) { diff --git a/src/net/torvald/terrarum/PostProcessor.kt b/src/net/torvald/terrarum/PostProcessor.kt index fd1dc0867..cc7ce5c96 100644 --- a/src/net/torvald/terrarum/PostProcessor.kt +++ b/src/net/torvald/terrarum/PostProcessor.kt @@ -12,7 +12,6 @@ import com.badlogic.gdx.graphics.glutils.ShaderProgram import com.badlogic.gdx.graphics.glutils.ShapeRenderer import com.badlogic.gdx.math.Matrix4 import net.torvald.terrarum.gamecontroller.KeyToggler -import kotlin.system.measureNanoTime /** * 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) diff --git a/src/net/torvald/terrarum/Terrarum.kt b/src/net/torvald/terrarum/Terrarum.kt index d033ce2de..039bbb376 100644 --- a/src/net/torvald/terrarum/Terrarum.kt +++ b/src/net/torvald/terrarum/Terrarum.kt @@ -392,8 +392,8 @@ object Terrarum : Screen { } override fun render(delta: Float) { - AppLoader.debugTimers["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.rawDelta", Gdx.graphics.rawDeltaTime.times(1000_000_000f).toLong()) + AppLoader.setDebugTime("GDX.smtDelta", Gdx.graphics.deltaTime.times(1000_000_000f).toLong()) AppLoader.getINSTANCE().screen.render(delta) } diff --git a/src/net/torvald/terrarum/TitleScreen.kt b/src/net/torvald/terrarum/TitleScreen.kt index ce64112fc..66e7ce97b 100644 --- a/src/net/torvald/terrarum/TitleScreen.kt +++ b/src/net/torvald/terrarum/TitleScreen.kt @@ -212,7 +212,7 @@ class TitleScreen(val batch: SpriteBatch) : Screen { updateAkku -= delta i += 1 } - AppLoader.debugTimers["Ingame.updateCounter"] = i + AppLoader.setDebugTime("Ingame.updateCounter", i) // render? just do it anyway diff --git a/src/net/torvald/terrarum/modulebasegame/Ingame.kt b/src/net/torvald/terrarum/modulebasegame/Ingame.kt index 115afb5a1..4ff0316d0 100644 --- a/src/net/torvald/terrarum/modulebasegame/Ingame.kt +++ b/src/net/torvald/terrarum/modulebasegame/Ingame.kt @@ -36,7 +36,6 @@ import net.torvald.terrarum.worlddrawer.LightmapRenderer import net.torvald.terrarum.worlddrawer.WorldCamera import java.util.* import java.util.concurrent.locks.ReentrantLock -import kotlin.system.measureNanoTime /** @@ -387,7 +386,7 @@ open class Ingame(batch: SpriteBatch) : IngameInstance(batch) { 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 while (updateAkku >= delta) { - AppLoader.debugTimers["Ingame.update"] = measureNanoTime { updateGame(delta) } + AppLoader.measureDebugTime("Ingame.update") { updateGame(delta) } updateAkku -= delta i += 1 } - AppLoader.debugTimers["Ingame.updateCounter"] = i + AppLoader.setDebugTime("Ingame.updateCounter", i) /** RENDER CODE GOES HERE */ - AppLoader.debugTimers["Ingame.render"] = measureNanoTime { renderGame() } - AppLoader.debugTimers["Ingame.render-Light"] = + AppLoader.measureDebugTime("Ingame.render") { renderGame() } + AppLoader.setDebugTime("Ingame.render-Light", (AppLoader.debugTimers["Ingame.render"] as Long) - ((AppLoader.debugTimers["Renderer.LightTotal"] as? Long) ?: 0) + ) } diff --git a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt b/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt index 601d19da1..6d7c55f2c 100644 --- a/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt +++ b/src/net/torvald/terrarum/worlddrawer/LightmapRendererNew.kt @@ -1,11 +1,10 @@ package net.torvald.terrarum.worlddrawer import com.badlogic.gdx.Gdx -import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.graphics.GL20 -import com.badlogic.gdx.graphics.Pixmap -import com.badlogic.gdx.graphics.Texture +import com.badlogic.gdx.graphics.* 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 net.torvald.terrarum.* 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.gameactors.ActorWBMovable import net.torvald.terrarum.gameactors.Luminous +import net.torvald.terrarum.gameworld.BlockAddress import net.torvald.terrarum.gameworld.GameWorld import net.torvald.terrarum.modulebasegame.IngameRenderer +import net.torvald.terrarum.realestate.LandUtil import java.util.concurrent.atomic.AtomicReferenceArray -import kotlin.system.measureNanoTime /** * Sub-portion of IngameRenderer. You are not supposed to directly deal with this. @@ -37,6 +37,8 @@ import kotlin.system.measureNanoTime object LightmapRenderer { 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! */ fun setWorld(world: GameWorld) { @@ -70,10 +72,6 @@ object LightmapRenderer { val overscan_open: Int = 32 val overscan_opaque: Int = 8 - init { - printdbg(this, "Overscan open: $overscan_open; opaque: $overscan_opaque") - } - // TODO resize(int, int) -aware 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) //private val lightmap: Array> = 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 = 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((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4) + private val lightmap: Array = 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((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 @@ -202,133 +262,190 @@ object LightmapRenderer { //println("$for_x_start..$for_x_end, $for_x\t$for_y_start..$for_y_end, $for_y") - /** - * 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 { + AppLoader.measureDebugTime("Renderer.Lanterns") { buildLanternmap() } // usually takes 3000 ns - - // wipe out lightmap - AppLoader.debugTimers["Renderer.Light0"] = measureNanoTime { - //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 (!SHADER_LIGHTING) { + /** + * 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 + */ - // 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, - // in this case we have 'spillage' due to the fact calculate() samples 3x3 area. + // each usually takes 8 000 000..12 000 000 miliseconds total when not threaded - // FIXME theoretically skipping shouldn't work (light can be anywhere on the screen, not just centre - // 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) + if (!AppLoader.getConfigBoolean("multithreadedlight")) { + //val workMap = Array(lightmap.size) { colourNull } - // Round 2 - AppLoader.debugTimers["Renderer.Light1"] = measureNanoTime { - 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)) + // The skipping is dependent on how you get ambient light, + // 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 + // 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 - AppLoader.debugTimers["Renderer.Light2"] = measureNanoTime { - for (y in for_y_end + overscan_open downTo for_y_start) { - for (x in for_x_end + overscan_open downTo for_x_start) { - setLightOf(lightmap, x, y, calculate(x, y)) + // Round 3 + AppLoader.measureDebugTime("Renderer.Light2") { + for (y in for_y_end + overscan_open downTo for_y_start) { + for (x in for_x_end + overscan_open downTo for_x_start) { + setLightOf(lightmap, x, y, calculate(x, y)) + } } } - } - // Round 4 - AppLoader.debugTimers["Renderer.Light3"] = measureNanoTime { - for (y in for_y_start - overscan_open..for_y_end) { - for (x in for_x_end + overscan_open downTo for_x_start) { - setLightOf(lightmap, x, y, calculate(x, y)) + // Round 4 + AppLoader.measureDebugTime("Renderer.Light3") { + for (y in for_y_start - overscan_open..for_y_end) { + for (x in for_x_end + overscan_open downTo for_x_start) { + setLightOf(lightmap, x, y, calculate(x, y)) + } } } - } - // Round 1 - AppLoader.debugTimers["Renderer.Light4"] = measureNanoTime { - for (y in for_y_start - overscan_open..for_y_end) { - for (x in for_x_start - overscan_open..for_x_end) { - setLightOf(lightmap, x, y, calculate(x, y)) + // Round 1 + AppLoader.measureDebugTime("Renderer.Light4") { + for (y in for_y_start - overscan_open..for_y_end) { + for (x in for_x_start - overscan_open..for_x_end) { + 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(lightmap.size) - AppLoader.debugTimers["Renderer.LightTotal"] = - (AppLoader.debugTimers["Renderer.Light1"]!! as Long) + - (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(lightmap.size) + AppLoader.measureDebugTime("Renderer.LightPrlPre") { + // update the content of buf using maxBlend -- it's not meant for overwrite - AppLoader.debugTimers["Renderer.LightPrlPre"] = measureNanoTime { - // update the content of buf using maxBlend -- it's not meant for overwrite + updateMessages.forEachIndexed { index, msg -> + 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 -> - 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) + (oldCol ?: colourNull) maxBlend calculate(ux, uy) + } } } } } - } - AppLoader.debugTimers["Renderer.LightPrlRun"] = measureNanoTime { - ThreadParallel.startAllWaitForDie() - } - - AppLoader.debugTimers["Renderer.LightPrlPost"] = measureNanoTime { - // copy to lightmap - for (k in 0 until lightmap.size) { - lightmap[k] = buf.getPlain(k) ?: colourNull + AppLoader.measureDebugTime("Renderer.LightPrlRun") { + ThreadParallel.startAllWaitForDie() } - } - AppLoader.debugTimers["Renderer.LightTotal"] = - (AppLoader.debugTimers["Renderer.LightPrlPre"]!! as Long) + - (AppLoader.debugTimers["Renderer.LightPrlRun"]!! as Long) + - (AppLoader.debugTimers["Renderer.LightPrlPost"]!! as Long) + AppLoader.measureDebugTime("Renderer.LightPrlPost") { + // copy to lightmap + for (k in 0 until lightmap.size) { + 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 private lateinit var updateMessages: List> @@ -392,10 +509,11 @@ object LightmapRenderer { 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 - 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 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 { - - // 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 ?! + private fun getLightsAndShades(x: Int, y: Int) { lightLevelThis.set(colourNull) thisTerrain = world.getTileFromTerrain(x, y) ?: Block.STONE thisFluid = world.getFluid(x, y) @@ -464,7 +575,26 @@ object LightmapRenderer { } // 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 /* + * + 0 4 1 @@ -519,49 +649,57 @@ object LightmapRenderer { internal fun draw(batch: SpriteBatch) { - val this_x_start = for_x_start// + overscan_open - val this_x_end = for_x_end// + overscan_open - val this_y_start = for_y_start// + overscan_open - val this_y_end = for_y_end// + overscan_open + // when shader is not used: 0.5 ms on 6700K + AppLoader.measureDebugTime("Renderer.LightToScreen") { - // wipe out beforehand. You DO need this - lightBuffer.blending = Pixmap.Blending.None // gonna overwrite (remove this line causes the world to go bit darker) - lightBuffer.setColor(colourNull) - lightBuffer.fill() + val this_x_start = for_x_start// + overscan_open + val this_x_end = for_x_end// + overscan_open + 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 + 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 - for (y in this_y_start..this_y_end) { - //println("y: $y, this_y_start: $this_y_start") - if (y == this_y_start && this_y_start == 0) { - //throw Error("Fuck hits again...") + // write to colour buffer + for (y in this_y_start..this_y_end) { + //println("y: $y, this_y_start: $this_y_start") + if (y == this_y_start && this_y_start == 0) { + //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) } - - 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 + else { + 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) } } - - - // 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() { @@ -769,6 +907,7 @@ object LightmapRenderer { // make sure the BlocksDrawer is resized first! // copied from BlocksDrawer, duh! + // FIXME 'lightBuffer' is not zoomable in this way val tilesInHorizontal = (screenW.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) - printdbg(this, "Resize event") }