mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 20:31:51 +09:00
gpu rendered light wip
This commit is contained in:
100
assets/raytracelight.frag
Normal file
100
assets/raytracelight.frag
Normal 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;
|
||||
|
||||
}
|
||||
@@ -745,4 +745,30 @@ public class AppLoader implements ApplicationListener {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<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 lanternMap = HashMap<Point2i, Color>((Terrarum.ingame?.ACTORCONTAINER_INITIAL_SIZE ?: 2) * 4)
|
||||
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<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
|
||||
|
||||
@@ -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<Color>(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<Color>(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<Array<ThreadedLightmapUpdateMessage>>
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user