gpu rendered light wip

This commit is contained in:
minjaesong
2019-01-29 02:21:22 +09:00
parent 45fa56cb23
commit b842780761
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;
}
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("multithread", true) // 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) {
//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) {

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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)
)
}

View File

@@ -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")
}