Separate LightVolume and GPULightVolume

- LightVolumes now can act as a light cache with configurable size
 - More GridAlignedBB changes
 - Remove ILightUpdateListeners
 - Simplify pulley rendering using LightVolume
This commit is contained in:
Jozufozu 2021-09-08 15:48:49 -07:00
parent 1f7af0d8b2
commit 7e65eaa00d
11 changed files with 568 additions and 315 deletions

View file

@ -6,11 +6,10 @@ import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.core.materials.IFlatLight;
import com.jozufozu.flywheel.light.GridAlignedBB;
import com.jozufozu.flywheel.light.ILightUpdateListener;
import com.jozufozu.flywheel.light.LightProvider;
import com.jozufozu.flywheel.light.ListenerStatus;
import com.jozufozu.flywheel.light.ReadOnlyBox;
import com.jozufozu.flywheel.light.ImmutableBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.LightType;
@ -74,7 +73,7 @@ public abstract class AbstractInstance implements IInstance, ILightUpdateListene
}
@Override
public void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed) {
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed) {
updateLight();
}

View file

@ -278,6 +278,8 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
LightUpdater.get(instance.world)
.removeListener(instance);
}
@Nullable
@ -286,7 +288,8 @@ public abstract class InstanceManager<T> implements MaterialManagerImpl.OriginSh
if (renderer != null) {
renderer.updateLight();
LightUpdater.get(renderer.world).addListener(renderer);
LightUpdater.get(renderer.world)
.addListener(renderer);
instances.put(obj, renderer);
if (renderer instanceof IDynamicInstance) dynamicInstances.put(obj, (IDynamicInstance) renderer);

View file

@ -9,6 +9,7 @@ import com.jozufozu.flywheel.core.Materials;
import com.jozufozu.flywheel.core.materials.ModelData;
import com.jozufozu.flywheel.core.materials.OrientedData;
import com.jozufozu.flywheel.light.GridAlignedBB;
import com.jozufozu.flywheel.light.ImmutableBox;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntity;
@ -84,7 +85,7 @@ public abstract class TileEntityInstance<T extends TileEntity> extends AbstractI
}
@Override
public GridAlignedBB getVolume() {
public ImmutableBox getVolume() {
return GridAlignedBB.from(pos);
}
}

View file

@ -48,7 +48,7 @@ public class ForgeEvents {
}
@SubscribeEvent
public static void rwle(TickEvent.ClientTickEvent e) {
public static void tickLight(TickEvent.ClientTickEvent e) {
if (e.phase == TickEvent.Phase.END && Backend.isGameActive())
LightUpdater.get(Minecraft.getInstance().level).tick();
}

View file

@ -0,0 +1,160 @@
package com.jozufozu.flywheel.light;
import static org.lwjgl.opengl.GL20.GL_LINEAR;
import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT;
import static org.lwjgl.opengl.GL20.GL_TEXTURE0;
import static org.lwjgl.opengl.GL20.GL_TEXTURE4;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT;
import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS;
import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL20.glActiveTexture;
import static org.lwjgl.opengl.GL20.glPixelStorei;
import static org.lwjgl.opengl.GL20.glTexImage3D;
import static org.lwjgl.opengl.GL20.glTexParameteri;
import static org.lwjgl.opengl.GL20.glTexSubImage3D;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlTexture;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat;
import net.minecraft.world.LightType;
public class GPULightVolume extends LightVolume {
protected final GridAlignedBB sampleVolume = new GridAlignedBB();
private final GlTexture glTexture;
private final RGPixelFormat pixelFormat;
protected boolean bufferDirty;
public GPULightVolume(ImmutableBox sampleVolume) {
super(sampleVolume);
this.sampleVolume.assign(sampleVolume);
pixelFormat = Backend.getInstance().compat.pixelFormat;
glTexture = new GlTexture(GL_TEXTURE_3D);
// allocate space for the texture
glActiveTexture(GL_TEXTURE4);
glTexture.bind();
int sizeX = box.sizeX();
int sizeY = box.sizeY();
int sizeZ = box.sizeZ();
glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0);
glTexture.unbind();
glActiveTexture(GL_TEXTURE0);
}
@Override
protected void setBox(ImmutableBox box) {
this.box.assign(box);
this.box.nextPowerOf2Centered();
// called during super ctor
if (sampleVolume != null) this.sampleVolume.assign(box);
}
public void bind() {
// just in case something goes wrong, or we accidentally call this before this volume is properly disposed of.
if (lightData == null) return;
GlTextureUnit.T4.makeActive();
glTexture.bind();
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
uploadTexture();
}
private void uploadTexture() {
if (bufferDirty) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
int sizeX = box.sizeX();
int sizeY = box.sizeY();
int sizeZ = box.sizeZ();
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
bufferDirty = false;
}
}
public void unbind() {
glTexture.unbind();
}
@Override
public void delete() {
super.delete();
glTexture.delete();
}
public void move(LightProvider world, ImmutableBox newSampleVolume) {
if (lightData == null) return;
if (box.contains(newSampleVolume)) {
if (newSampleVolume.intersects(sampleVolume)) {
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
sampleVolume.assign(newSampleVolume);
copyLight(world, newArea);
} else {
sampleVolume.assign(newSampleVolume);
initialize(world);
}
} else {
super.move(world, newSampleVolume);
}
}
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) {
super.onLightUpdate(world, type, changedVolume);
bufferDirty = true;
}
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
super.onLightPacket(world, chunkX, chunkZ);
bufferDirty = true;
}
/**
* Completely (re)populate this volume with block and sky lighting data.
* This is expensive and should be avoided.
*/
public void initialize(LightProvider world) {
super.initialize(world);
bufferDirty = true;
}
@Override
protected int getStride() {
return Backend.getInstance().compat.pixelFormat.byteCount();
}
@Override
public ImmutableBox getVolume() {
return sampleVolume;
}
}

View file

@ -8,7 +8,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.util.math.vector.Vector3i;
public class GridAlignedBB implements ReadOnlyBox {
public class GridAlignedBB implements ImmutableBox {
private int minX;
private int minY;
private int minZ;
@ -16,6 +16,10 @@ public class GridAlignedBB implements ReadOnlyBox {
private int maxY;
private int maxZ;
public GridAlignedBB() {
}
public GridAlignedBB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
this.minX = minX;
this.minY = minY;
@ -58,19 +62,19 @@ public class GridAlignedBB implements ReadOnlyBox {
}
public void fixMinMax() {
int minX = Math.min(this.getMinX(), this.getMaxX());
int minY = Math.min(this.getMinY(), this.getMaxY());
int minZ = Math.min(this.getMinZ(), this.getMaxZ());
int maxX = Math.max(this.getMinX(), this.getMaxX());
int maxY = Math.max(this.getMinY(), this.getMaxY());
int maxZ = Math.max(this.getMinZ(), this.getMaxZ());
int minX = Math.min(this.minX, this.maxX);
int minY = Math.min(this.minY, this.maxY);
int minZ = Math.min(this.minZ, this.maxZ);
int maxX = Math.max(this.minX, this.maxX);
int maxY = Math.max(this.minY, this.maxY);
int maxZ = Math.max(this.minZ, this.maxZ);
this.setMinX(minX);
this.setMinY(minY);
this.setMinZ(minZ);
this.setMaxX(maxX);
this.setMaxY(maxY);
this.setMaxZ(maxZ);
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
}
public void translate(Vector3i by) {
@ -78,12 +82,12 @@ public class GridAlignedBB implements ReadOnlyBox {
}
public void translate(int x, int y, int z) {
setMinX(getMinX() + x);
setMaxX(getMaxX() + x);
setMinY(getMinY() + y);
setMaxY(getMaxY() + y);
setMinZ(getMinZ() + z);
setMaxZ(getMaxZ() + z);
minX = minX + x;
maxX = maxX + x;
minY = minY + y;
maxY = maxY + y;
minZ = minZ + z;
maxZ = maxZ + z;
}
public void mirrorAbout(Direction.Axis axis) {
@ -93,15 +97,15 @@ public class GridAlignedBB implements ReadOnlyBox {
int flipY = axisVec.getY() - 1;
int flipZ = axisVec.getZ() - 1;
int maxX = this.getMaxX() * flipX;
int maxY = this.getMaxY() * flipY;
int maxZ = this.getMaxZ() * flipZ;
this.setMaxX(this.getMinX() * flipX);
this.setMaxY(this.getMinY() * flipY);
this.setMaxZ(this.getMinZ() * flipZ);
this.setMinX(maxX);
this.setMinY(maxY);
this.setMinZ(maxZ);
int maxX = this.maxX * flipX;
int maxY = this.maxY * flipY;
int maxZ = this.maxZ * flipZ;
this.maxX = this.minX * flipX;
this.maxY = this.minY * flipY;
this.maxZ = this.minZ * flipZ;
this.minX = maxX;
this.minY = maxY;
this.minZ = maxZ;
}
/**
@ -120,12 +124,12 @@ public class GridAlignedBB implements ReadOnlyBox {
int diffY = newSizeY - sizeY;
int diffZ = newSizeZ - sizeZ;
setMinX(getMinX() - diffX / 2); // floor division for the minimums
setMinY(getMinY() - diffY / 2);
setMinZ(getMinZ() - diffZ / 2);
setMaxX(getMaxX() + (diffX + 1) / 2); // ceiling divison for the maximums
setMaxY(getMaxY() + (diffY + 1) / 2);
setMaxZ(getMaxZ() + (diffZ + 1) / 2);
minX = minX - diffX / 2; // floor division for the minimums
minY = minY - diffY / 2;
minZ = minZ - diffZ / 2;
maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums
maxY = maxY + (diffY + 1) / 2;
maxZ = maxZ + (diffZ + 1) / 2;
}
/**
@ -136,9 +140,9 @@ public class GridAlignedBB implements ReadOnlyBox {
int sizeY = RenderUtil.nextPowerOf2(sizeY());
int sizeZ = RenderUtil.nextPowerOf2(sizeZ());
this.setMaxX(this.getMinX() + sizeX);
this.setMaxY(this.getMinY() + sizeY);
this.setMaxZ(this.getMinZ() + sizeZ);
maxX = minX + sizeX;
maxY = minY + sizeY;
maxZ = minZ + sizeZ;
}
public void grow(int s) {
@ -146,57 +150,57 @@ public class GridAlignedBB implements ReadOnlyBox {
}
public void grow(int x, int y, int z) {
setMinX(getMinX() - x);
setMinY(getMinY() - y);
setMinZ(getMinZ() - z);
setMaxX(getMaxX() + x);
setMaxY(getMaxY() + y);
setMaxZ(getMaxZ() + z);
minX = minX - x;
minY = minY - y;
minZ = minZ - z;
maxX = maxX + x;
maxY = maxY + y;
maxZ = maxZ + z;
}
public void intersectAssign(ReadOnlyBox other) {
this.setMinX(Math.max(this.getMinX(), other.getMinX()));
this.setMinY(Math.max(this.getMinY(), other.getMinY()));
this.setMinZ(Math.max(this.getMinZ(), other.getMinZ()));
this.setMaxX(Math.min(this.getMaxX(), other.getMaxX()));
this.setMaxY(Math.min(this.getMaxY(), other.getMaxY()));
this.setMaxZ(Math.min(this.getMaxZ(), other.getMaxZ()));
public void intersectAssign(ImmutableBox other) {
minX = Math.max(this.minX, other.getMinX());
minY = Math.max(this.minY, other.getMinY());
minZ = Math.max(this.minZ, other.getMinZ());
maxX = Math.min(this.maxX, other.getMaxX());
maxY = Math.min(this.maxY, other.getMaxY());
maxZ = Math.min(this.maxZ, other.getMaxZ());
}
public void unionAssign(ReadOnlyBox other) {
this.setMinX(Math.min(this.getMinX(), other.getMinX()));
this.setMinY(Math.min(this.getMinY(), other.getMinY()));
this.setMinZ(Math.min(this.getMinZ(), other.getMinZ()));
this.setMaxX(Math.max(this.getMaxX(), other.getMaxX()));
this.setMaxY(Math.max(this.getMaxY(), other.getMaxY()));
this.setMaxZ(Math.max(this.getMaxZ(), other.getMaxZ()));
public void unionAssign(ImmutableBox other) {
minX = Math.min(this.minX, other.getMinX());
minY = Math.min(this.minY, other.getMinY());
minZ = Math.min(this.minZ, other.getMinZ());
maxX = Math.max(this.maxX, other.getMaxX());
maxY = Math.max(this.maxY, other.getMaxY());
maxZ = Math.max(this.maxZ, other.getMaxZ());
}
public void unionAssign(AxisAlignedBB other) {
this.setMinX(Math.min(this.getMinX(), (int) Math.floor(other.minX)));
this.setMinY(Math.min(this.getMinY(), (int) Math.floor(other.minY)));
this.setMinZ(Math.min(this.getMinZ(), (int) Math.floor(other.minZ)));
this.setMaxX(Math.max(this.getMaxX(), (int) Math.ceil(other.maxX)));
this.setMaxY(Math.max(this.getMaxY(), (int) Math.ceil(other.maxY)));
this.setMaxZ(Math.max(this.getMaxZ(), (int) Math.ceil(other.maxZ)));
minX = Math.min(this.minX, (int) Math.floor(other.minX));
minY = Math.min(this.minY, (int) Math.floor(other.minY));
minZ = Math.min(this.minZ, (int) Math.floor(other.minZ));
maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX));
maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY));
maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ));
}
public void assign(AxisAlignedBB other) {
this.setMinX((int) Math.floor(other.minX));
this.setMinY((int) Math.floor(other.minY));
this.setMinZ((int) Math.floor(other.minZ));
this.setMaxX((int) Math.ceil(other.maxX));
this.setMaxY((int) Math.ceil(other.maxY));
this.setMaxZ((int) Math.ceil(other.maxZ));
minX = (int) Math.floor(other.minX);
minY = (int) Math.floor(other.minY);
minZ = (int) Math.floor(other.minZ);
maxX = (int) Math.ceil(other.maxX);
maxY = (int) Math.ceil(other.maxY);
maxZ = (int) Math.ceil(other.maxZ);
}
public void assign(ReadOnlyBox other) {
this.setMinX(other.getMinX());
this.setMinY(other.getMinY());
this.setMinZ(other.getMinZ());
this.setMaxX(other.getMaxX());
this.setMaxY(other.getMaxY());
this.setMaxZ(other.getMaxZ());
public void assign(ImmutableBox other) {
minX = other.getMinX();
minY = other.getMinY();
minZ = other.getMinZ();
maxX = other.getMaxX();
maxY = other.getMaxY();
maxZ = other.getMaxZ();
}
@Override
@ -204,7 +208,7 @@ public class GridAlignedBB implements ReadOnlyBox {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReadOnlyBox that = (ReadOnlyBox) o;
ImmutableBox that = (ImmutableBox) o;
return this.sameAs(that);
}
@ -279,4 +283,132 @@ public class GridAlignedBB implements ReadOnlyBox {
this.maxZ = maxZ;
return this;
}
public GridAlignedBB assign(BlockPos start, BlockPos end) {
minX = start.getX();
minY = start.getY();
minZ = start.getZ();
maxX = end.getX() + 1;
maxY = end.getY() + 1;
maxZ = end.getZ() + 1;
return this;
}
public GridAlignedBB setMax(Vector3i v) {
return setMax(v.getX(), v.getY(), v.getZ());
}
public GridAlignedBB setMin(Vector3i v) {
return setMin(v.getX(), v.getY(), v.getZ());
}
public GridAlignedBB setMax(int x, int y, int z) {
maxX = x;
maxY = y;
maxZ = z;
return this;
}
public GridAlignedBB setMin(int x, int y, int z) {
minX = x;
minY = y;
minZ = z;
return this;
}
@Override
public int sizeX() {
return maxX - minX;
}
@Override
public int sizeY() {
return maxY - minY;
}
@Override
public int sizeZ() {
return maxZ - minZ;
}
@Override
public boolean empty() {
// if any dimension has side length 0 this box contains no volume
return minX == maxX || minY == maxY || minZ == maxZ;
}
@Override
public boolean sameAs(ImmutableBox other) {
return minX == other.getMinX() && minY == other.getMinY() && minZ == other.getMinZ() && maxX == other.getMaxX() && maxY == other.getMaxY() && maxZ == other.getMaxZ();
}
@Override
public boolean sameAs(AxisAlignedBB other) {
return minX == Math.floor(other.minX)
&& minY == Math.floor(other.minY)
&& minZ == Math.floor(other.minZ)
&& maxX == Math.ceil(other.maxX)
&& maxY == Math.ceil(other.maxY)
&& maxZ == Math.ceil(other.maxZ);
}
@Override
public GridAlignedBB intersect(ImmutableBox other) {
int minX = Math.max(this.minX, other.getMinX());
int minY = Math.max(this.minY, other.getMinY());
int minZ = Math.max(this.minZ, other.getMinZ());
int maxX = Math.min(this.maxX, other.getMaxX());
int maxY = Math.min(this.maxY, other.getMaxY());
int maxZ = Math.min(this.maxZ, other.getMaxZ());
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public ImmutableBox union(ImmutableBox other) {
int minX = Math.min(this.minX, other.getMinX());
int minY = Math.min(this.minY, other.getMinY());
int minZ = Math.min(this.minZ, other.getMinZ());
int maxX = Math.max(this.maxX, other.getMaxX());
int maxY = Math.max(this.maxY, other.getMaxY());
int maxZ = Math.max(this.maxZ, other.getMaxZ());
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public boolean contains(ImmutableBox other) {
return other.getMinX() >= this.minX && other.getMaxX() <= this.maxX && other.getMinY() >= this.minY && other.getMaxY() <= this.maxY && other.getMinZ() >= this.minZ && other.getMaxZ() <= this.maxZ;
}
@Override
public boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ;
}
@Override
public void forEachContained(ICoordinateConsumer func) {
if (empty()) return;
for (int x = minX; x < maxX; x++) {
for (int y = Math.max(minY, 0); y < Math.min(maxY, 255); y++) { // clamp to world height limits
for (int z = minZ; z < maxZ; z++) {
func.consume(x, y, z);
}
}
}
}
@Override
public AxisAlignedBB toAABB() {
return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public GridAlignedBB copy() {
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
@Override
public String toString() {
return "(" + minX + ", " + minY + ", " + minZ + ")->(" + maxX + ", " + maxY + ", " + maxZ + ')';
}
}

View file

@ -4,14 +4,14 @@ import net.minecraft.world.LightType;
public interface ILightUpdateListener {
ReadOnlyBox getVolume();
ImmutableBox getVolume();
ListenerStatus status();
/**
* Called when a light updates in a chunk the implementor cares about.
*/
void onLightUpdate(LightProvider world, LightType type, ReadOnlyBox changed);
void onLightUpdate(LightProvider world, LightType type, ImmutableBox changed);
/**
* Called when the server sends light data to the client.

View file

@ -4,7 +4,7 @@ import static com.jozufozu.flywheel.util.RenderUtil.isPowerOf2;
import net.minecraft.util.math.AxisAlignedBB;
public interface ReadOnlyBox {
public interface ImmutableBox {
int getMinX();
int getMinY();
@ -38,7 +38,7 @@ public interface ReadOnlyBox {
return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ();
}
default boolean sameAs(ReadOnlyBox other) {
default boolean sameAs(ImmutableBox other) {
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
}
@ -56,7 +56,7 @@ public interface ReadOnlyBox {
return isPowerOf2(volume());
}
default GridAlignedBB intersect(ReadOnlyBox other) {
default GridAlignedBB intersect(ImmutableBox other) {
int minX = Math.max(this.getMinX(), other.getMinX());
int minY = Math.max(this.getMinY(), other.getMinY());
int minZ = Math.max(this.getMinZ(), other.getMinZ());
@ -66,7 +66,7 @@ public interface ReadOnlyBox {
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
default ReadOnlyBox union(ReadOnlyBox other) {
default ImmutableBox union(ImmutableBox other) {
int minX = Math.min(this.getMinX(), other.getMinX());
int minY = Math.min(this.getMinY(), other.getMinY());
int minZ = Math.min(this.getMinZ(), other.getMinZ());
@ -77,12 +77,26 @@ public interface ReadOnlyBox {
}
default boolean intersects(ReadOnlyBox other) {
default boolean intersects(ImmutableBox other) {
return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
}
default boolean contains(ReadOnlyBox other) {
return other.getMinX() >= this.getMinX() && other.getMaxX() <= this.getMaxX() && other.getMinY() >= this.getMinY() && other.getMaxY() <= this.getMaxY() && other.getMinZ() >= this.getMinZ() && other.getMaxZ() <= this.getMaxZ();
default boolean contains(int x, int y, int z) {
return x >= getMinX()
&& x <= getMaxX()
&& y >= getMinY()
&& y <= getMaxY()
&& z >= getMinZ()
&& z <= getMaxZ();
}
default boolean contains(ImmutableBox other) {
return other.getMinX() >= this.getMinX()
&& other.getMaxX() <= this.getMaxX()
&& other.getMinY() >= this.getMinY()
&& other.getMaxY() <= this.getMaxY()
&& other.getMinZ() >= this.getMinZ()
&& other.getMaxZ() <= this.getMaxZ();
}
default boolean isContainedBy(GridAlignedBB other) {

View file

@ -14,7 +14,7 @@ import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.LightType;
/**
* Keeps track of what chunks/sections each listener is in so we can update exactly what needs to be updated.
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
*/
public class LightUpdater {
@ -33,6 +33,10 @@ public class LightUpdater {
provider = BasicProvider.get(world);
}
public LightProvider getProvider() {
return provider;
}
public void tick() {
for (IMovingListener listener : movingListeners) {
if (listener.update(provider)) {
@ -50,7 +54,7 @@ public class LightUpdater {
if (listener instanceof IMovingListener)
movingListeners.add(((IMovingListener) listener));
ReadOnlyBox box = listener.getVolume();
ImmutableBox box = listener.getVolume();
LongSet sections = this.sections.getAndResetContainment(listener);
LongSet chunks = this.chunks.getAndResetContainment(listener);
@ -76,6 +80,11 @@ public class LightUpdater {
}
}
public void removeListener(ILightUpdateListener listener) {
this.sections.remove(listener);
this.chunks.remove(listener);
}
/**
* Dispatch light updates to all registered {@link ILightUpdateListener}s.
* @param type The type of light that changed.
@ -88,7 +97,7 @@ public class LightUpdater {
set.removeIf(l -> l.status().shouldRemove());
ReadOnlyBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
ImmutableBox chunkBox = GridAlignedBB.from(SectionPos.of(sectionPos));
for (ILightUpdateListener listener : set) {
listener.onLightUpdate(provider, type, chunkBox);
@ -122,7 +131,7 @@ public class LightUpdater {
return sectionPos & 0xFFFFFFFFFFF_00000L;
}
public Stream<ReadOnlyBox> getAllBoxes() {
public Stream<ImmutableBox> getAllBoxes() {
return chunks.stream().map(ILightUpdateListener::getVolume);
}

View file

@ -1,165 +1,89 @@
package com.jozufozu.flywheel.light;
import static org.lwjgl.opengl.GL20.GL_LINEAR;
import static org.lwjgl.opengl.GL20.GL_MIRRORED_REPEAT;
import static org.lwjgl.opengl.GL20.GL_TEXTURE0;
import static org.lwjgl.opengl.GL20.GL_TEXTURE4;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_3D;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_R;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL20.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL20.GL_UNPACK_ALIGNMENT;
import static org.lwjgl.opengl.GL20.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL20.GL_UNPACK_ROW_LENGTH;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_IMAGES;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_PIXELS;
import static org.lwjgl.opengl.GL20.GL_UNPACK_SKIP_ROWS;
import static org.lwjgl.opengl.GL20.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL20.glActiveTexture;
import static org.lwjgl.opengl.GL20.glPixelStorei;
import static org.lwjgl.opengl.GL20.glTexImage3D;
import static org.lwjgl.opengl.GL20.glTexParameteri;
import static org.lwjgl.opengl.GL20.glTexSubImage3D;
import java.nio.ByteBuffer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlTexture;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.gl.versioned.RGPixelFormat;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.LightType;
public class LightVolume {
public class LightVolume implements ImmutableBox, ILightUpdateListener {
private GridAlignedBB sampleVolume;
private GridAlignedBB textureVolume;
private ByteBuffer lightData;
protected final GridAlignedBB box = new GridAlignedBB();
protected ByteBuffer lightData;
private boolean bufferDirty;
private boolean removed;
public LightVolume(ImmutableBox sampleVolume) {
this.setBox(sampleVolume);
private final GlTexture glTexture;
private final RGPixelFormat pixelFormat;
public LightVolume(GridAlignedBB sampleVolume) {
setSampleVolume(sampleVolume);
pixelFormat = Backend.getInstance().compat.pixelFormat;
this.glTexture = new GlTexture(GL_TEXTURE_3D);
this.lightData = MemoryUtil.memAlloc(this.textureVolume.volume() * pixelFormat.byteCount());
// allocate space for the texture
glActiveTexture(GL_TEXTURE4);
glTexture.bind();
int sizeX = textureVolume.sizeX();
int sizeY = textureVolume.sizeY();
int sizeZ = textureVolume.sizeZ();
glTexImage3D(GL_TEXTURE_3D, 0, pixelFormat.internalFormat(), sizeX, sizeY, sizeZ, 0, pixelFormat.format(), GL_UNSIGNED_BYTE, 0);
glTexture.unbind();
glActiveTexture(GL_TEXTURE0);
this.lightData = MemoryUtil.memAlloc(this.box.volume() * getStride());
}
private void setSampleVolume(GridAlignedBB sampleVolume) {
this.sampleVolume = sampleVolume;
this.textureVolume = sampleVolume.copy();
this.textureVolume.nextPowerOf2Centered();
protected void setBox(ImmutableBox box) {
this.box.assign(box);
}
public ReadOnlyBox getTextureVolume() {
return textureVolume;
}
public ReadOnlyBox getSampleVolume() {
return sampleVolume;
}
public int getMinX() {
return textureVolume.getMinX();
}
public int getMinY() {
return textureVolume.getMinY();
}
public int getMinZ() {
return textureVolume.getMinZ();
}
public int getMaxX() {
return textureVolume.getMaxX();
}
public int getMaxY() {
return textureVolume.getMaxY();
}
public int getMaxZ() {
return textureVolume.getMaxZ();
}
public int getSizeX() {
return textureVolume.sizeX();
}
public int getSizeY() {
return textureVolume.sizeY();
}
public int getSizeZ() {
return textureVolume.sizeZ();
}
public void move(IBlockDisplayReader world, GridAlignedBB newSampleVolume) {
if (removed) return;
if (textureVolume.contains(newSampleVolume)) {
BasicProvider basicProvider = BasicProvider.get(world);
if (newSampleVolume.intersects(sampleVolume)) {
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
sampleVolume = newSampleVolume;
copyLight(basicProvider, newArea);
} else {
sampleVolume = newSampleVolume;
initialize(world);
}
public short getPackedLight(int x, int y, int z) {
if (box.contains(x, y, z)) {
return lightData.getShort(worldPosToBufferIndex(x, y, z));
} else {
setSampleVolume(newSampleVolume);
int volume = textureVolume.volume();
if (volume * 2 > lightData.capacity()) {
lightData = MemoryUtil.memRealloc(lightData, volume * 2);
}
initialize(world);
return 0;
}
}
public void notifyLightUpdate(LightProvider world, LightType type, ReadOnlyBox changedVolume) {
if (removed) return;
if (!changedVolume.intersects(sampleVolume)) return;
changedVolume = changedVolume.intersect(sampleVolume); // compute the region contained by us that has dirty lighting data.
if (type == LightType.BLOCK) copyBlock(world, changedVolume);
else if (type == LightType.SKY) copySky(world, changedVolume);
public int getMinX() {
return box.getMinX();
}
public void notifyLightPacket(LightProvider world, int chunkX, int chunkZ) {
if (removed) return;
public int getMinY() {
return box.getMinY();
}
public int getMinZ() {
return box.getMinZ();
}
public int getMaxX() {
return box.getMaxX();
}
public int getMaxY() {
return box.getMaxY();
}
public int getMaxZ() {
return box.getMaxZ();
}
public void move(LightProvider world, ImmutableBox newSampleVolume) {
if (lightData == null) return;
setBox(newSampleVolume);
int volume = box.volume();
if (volume * 2 > lightData.capacity()) {
lightData = MemoryUtil.memRealloc(lightData, volume * 2);
}
initialize(world);
}
@Override
public void onLightUpdate(LightProvider world, LightType type, ImmutableBox changedVolume) {
if (lightData == null) return;
GridAlignedBB vol = changedVolume.copy();
if (!vol.intersects(getVolume())) return;
vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
if (type == LightType.BLOCK) copyBlock(world, vol);
else if (type == LightType.SKY) copySky(world, vol);
}
@Override
public void onLightPacket(LightProvider world, int chunkX, int chunkZ) {
if (lightData == null) return;
GridAlignedBB changedVolume = GridAlignedBB.from(chunkX, chunkZ);
if (!changedVolume.intersects(sampleVolume)) return;
changedVolume.intersectAssign(sampleVolume); // compute the region contained by us that has dirty lighting data.
if (!changedVolume.intersects(getVolume())) return;
changedVolume.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
copyLight(world, changedVolume);
}
@ -168,25 +92,20 @@ public class LightVolume {
* Completely (re)populate this volume with block and sky lighting data.
* This is expensive and should be avoided.
*/
public void initialize(IBlockDisplayReader world) {
if (removed) return;
public void initialize(LightProvider world) {
if (lightData == null) return;
BlockPos.Mutable pos = new BlockPos.Mutable();
ImmutableBox box = getVolume();
int shiftX = box.getMinX();
int shiftY = box.getMinY();
int shiftZ = box.getMinZ();
int shiftX = textureVolume.getMinX();
int shiftY = textureVolume.getMinY();
int shiftZ = textureVolume.getMinZ();
sampleVolume.forEachContained((x, y, z) -> {
pos.set(x, y, z);
int blockLight = world.getBrightness(LightType.BLOCK, pos);
int skyLight = world.getBrightness(LightType.SKY, pos);
box.forEachContained((x, y, z) -> {
int blockLight = world.getLight(LightType.BLOCK, x, y, z);
int skyLight = world.getLight(LightType.SKY, x, y, z);
writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight);
});
bufferDirty = true;
}
/**
@ -194,18 +113,16 @@ public class LightVolume {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copyBlock(LightProvider world, ReadOnlyBox worldVolume) {
int xShift = textureVolume.getMinX();
int yShift = textureVolume.getMinY();
int zShift = textureVolume.getMinZ();
public void copyBlock(LightProvider world, ImmutableBox worldVolume) {
int xShift = box.getMinX();
int yShift = box.getMinY();
int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> {
int light = world.getLight(LightType.BLOCK, x, y, z);
writeBlock(x - xShift, y - yShift, z - zShift, light);
});
bufferDirty = true;
}
/**
@ -213,18 +130,16 @@ public class LightVolume {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copySky(LightProvider world, ReadOnlyBox worldVolume) {
int xShift = textureVolume.getMinX();
int yShift = textureVolume.getMinY();
int zShift = textureVolume.getMinZ();
public void copySky(LightProvider world, ImmutableBox worldVolume) {
int xShift = box.getMinX();
int yShift = box.getMinY();
int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> {
int light = world.getLight(LightType.SKY, x, y, z);
writeSky(x - xShift, y - yShift, z - zShift, light);
});
bufferDirty = true;
}
/**
@ -232,12 +147,12 @@ public class LightVolume {
*
* @param worldVolume the region in the world to copy data from.
*/
public void copyLight(LightProvider world, ReadOnlyBox worldVolume) {
public void copyLight(LightProvider world, ImmutableBox worldVolume) {
BlockPos.Mutable pos = new BlockPos.Mutable();
int xShift = textureVolume.getMinX();
int yShift = textureVolume.getMinY();
int zShift = textureVolume.getMinZ();
int xShift = box.getMinX();
int yShift = box.getMinY();
int zShift = box.getMinZ();
worldVolume.forEachContained((x, y, z) -> {
pos.set(x, y, z);
@ -247,77 +162,80 @@ public class LightVolume {
writeLight(x - xShift, y - yShift, z - zShift, block, sky);
});
bufferDirty = true;
}
public void bind() {
// just in case something goes wrong or we accidentally call this before this volume is properly disposed of.
if (lightData == null || removed) return;
GlTextureUnit.T4.makeActive();
glTexture.bind();
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
uploadTexture();
}
private void uploadTexture() {
if (bufferDirty) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
int sizeX = textureVolume.sizeX();
int sizeY = textureVolume.sizeY();
int sizeZ = textureVolume.sizeZ();
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, pixelFormat.format(), GL_UNSIGNED_BYTE, lightData);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
bufferDirty = false;
}
}
public void unbind() {
glTexture.unbind();
}
public void delete() {
removed = true;
glTexture.delete();
MemoryUtil.memFree(lightData);
lightData = null;
}
private void writeLight(int x, int y, int z, int block, int sky) {
protected void writeLight(int x, int y, int z, int block, int sky) {
byte b = (byte) ((block & 0xF) << 4);
byte s = (byte) ((sky & 0xF) << 4);
int i = posToIndex(x, y, z);
int i = boxPosToBufferIndex(x, y, z);
lightData.put(i, b);
lightData.put(i + 1, s);
}
private void writeBlock(int x, int y, int z, int block) {
protected void writeBlock(int x, int y, int z, int block) {
byte b = (byte) ((block & 0xF) << 4);
lightData.put(posToIndex(x, y, z), b);
lightData.put(boxPosToBufferIndex(x, y, z), b);
}
private void writeSky(int x, int y, int z, int sky) {
protected void writeSky(int x, int y, int z, int sky) {
byte b = (byte) ((sky & 0xF) << 4);
lightData.put(posToIndex(x, y, z) + 1, b);
lightData.put(boxPosToBufferIndex(x, y, z) + 1, b);
}
private int posToIndex(int x, int y, int z) {
return (x + textureVolume.sizeX() * (y + z * textureVolume.sizeY())) * pixelFormat.byteCount();
protected int worldPosToBufferIndex(int x, int y, int z) {
x -= box.getMinX();
y -= box.getMinY();
z -= box.getMinZ();
return boxPosToBufferIndex(x, y, z);
}
protected int boxPosToBufferIndex(int x, int y, int z) {
return (x + box.sizeX() * (y + z * box.sizeY())) * getStride();
}
/**
* @return The stride of the texels, in bytes.
*/
protected int getStride() {
return 2;
}
@Override
public ImmutableBox getVolume() {
return box;
}
@Override
public ListenerStatus status() {
return ListenerStatus.OKAY;
}
public static int unpackBlock(short packed) {
return (packed >> 4) & 0xF;
}
public static int unpackSky(short packed) {
return (packed >> 12) & 0xF;
}
public static byte packLight(byte block, byte sky) {
return (byte) (block | (sky << 4));
}
public static int unpackBlock(byte packed) {
return packed & 0xF;
}
public static int unpackSky(byte packed) {
return (packed >> 4) & 0xF;
}
}

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.light;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.WeakHashMap;
@ -57,6 +56,24 @@ public class WeakContainmentMultiMap<T> extends AbstractCollection<T> {
forward.computeIfAbsent(sectionPos, $ -> new WeakHashSet<>()).add(listener);
}
@Override
public boolean remove(Object o) {
LongSet containmentSet = reverse.remove(o);
if (containmentSet != null) {
containmentSet.forEach((LongConsumer) l -> {
WeakHashSet<T> listeners = forward.get(l);
if (listeners != null) listeners.remove(o);
});
containmentSet.clear();
return true;
}
return false;
}
@Override
public Iterator<T> iterator() {
return reverse.keySet().iterator();