We need to go deeper

- Compose transformations of embeddings
- Make EmbeddedEnvironment abstract and move light stuffs into
  TopLevelEmbeddedEnvironment
- Add NestedEmbeddedEnvironment that only handles matrices, and tracks
  a parent EmbeddedEnvironment
- AbstractEmbeddedEnvironment handles most of the deletion/update logic,
  and also creates further NestedEmbeddedEnvironments
- EnvironmentStorage now only tracks environments and does not creat
  them.
This commit is contained in:
Jozufozu 2024-03-24 15:04:51 -05:00
parent 277597d6bf
commit ff7130c71c
9 changed files with 328 additions and 193 deletions

View File

@ -14,8 +14,8 @@ import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.backend.engine.embed.TopLevelEmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.lib.task.Flag;
@ -30,7 +30,7 @@ import net.minecraft.world.phys.Vec3;
public class EngineImpl implements Engine {
private final int sqrMaxOriginDistance;
private final DrawManager<? extends AbstractInstancer<?>> drawManager;
private final EnvironmentStorage environmentStorage = new EnvironmentStorage(this);
private final EnvironmentStorage environmentStorage = new EnvironmentStorage();
private final Flag flushFlag = new NamedFlag("flushed");
private BlockPos renderOrigin = BlockPos.ZERO;
@ -110,12 +110,8 @@ public class EngineImpl implements Engine {
flushFlag.raise();
}
public VisualEmbedding createEmbedding(RenderStage renderStage) {
return environmentStorage.create(renderStage);
}
public void freeEmbedding(EmbeddedEnvironment embeddedEnvironment) {
environmentStorage.enqueueDeletion(embeddedEnvironment);
public EnvironmentStorage environmentStorage() {
return environmentStorage;
}
private class VisualizationContextImpl implements VisualizationContext {
@ -139,7 +135,9 @@ public class EngineImpl implements Engine {
@Override
public VisualEmbedding createEmbedding() {
return EngineImpl.this.createEmbedding(stage);
var out = new TopLevelEmbeddedEnvironment(EngineImpl.this, stage);
environmentStorage.track(out);
return out;
}
}
}

View File

@ -3,49 +3,38 @@ package com.jozufozu.flywheel.backend.engine;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage {
protected final ReferenceSet<EmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
private final Queue<EmbeddedEnvironment> forDeletion = new ConcurrentLinkedQueue<>();
private final EngineImpl engine;
protected final ReferenceSet<AbstractEmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
private final Queue<AbstractEmbeddedEnvironment> forDeletion = new ConcurrentLinkedQueue<>();
public EnvironmentStorage(EngineImpl engine) {
this.engine = engine;
public void track(AbstractEmbeddedEnvironment environment) {
environments.add(environment);
}
public VisualEmbedding create(RenderStage stage) {
var out = new EmbeddedEnvironment(engine, stage);
environments.add(out);
return out;
}
public void enqueueDeletion(EmbeddedEnvironment environment) {
public void enqueueDeletion(AbstractEmbeddedEnvironment environment) {
environments.remove(environment);
forDeletion.add(environment);
}
public void flush() {
EmbeddedEnvironment env;
AbstractEmbeddedEnvironment env;
while ((env = forDeletion.poll()) != null) {
env.actuallyDelete();
}
environments.forEach(EmbeddedEnvironment::flush);
environments.forEach(AbstractEmbeddedEnvironment::flush);
}
public void delete() {
environments.forEach(EmbeddedEnvironment::actuallyDelete);
environments.forEach(AbstractEmbeddedEnvironment::actuallyDelete);
environments.clear();
}
}

View File

@ -0,0 +1,125 @@
package com.jozufozu.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.util.AtomicReferenceCounted;
import net.minecraft.core.Vec3i;
public abstract class AbstractEmbeddedEnvironment extends AtomicReferenceCounted implements Environment, VisualEmbedding {
protected final Matrix4f pose = new Matrix4f();
protected final Matrix3f normal = new Matrix3f();
private final Matrix4f poseComposed = new Matrix4f();
private final Matrix3f normalComposed = new Matrix3f();
private final InstancerProvider instancerProvider;
private final EngineImpl engine;
private final RenderStage renderStage;
public AbstractEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
this.engine = engine;
this.renderStage = renderStage;
instancerProvider = new InstancerProvider() {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
// Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(AbstractEmbeddedEnvironment.this, type, model, renderStage);
}
};
// Acquire the reference owned by the visual that created this.
acquire();
}
@Override
public void transforms(Matrix4fc pose, Matrix3fc normal) {
this.pose.set(pose);
this.normal.set(normal);
}
public void flush() {
poseComposed.identity();
normalComposed.identity();
composeMatrices(poseComposed, normalComposed);
}
@Override
public void setupDraw(GlProgram program) {
setupLight(program);
program.setMat4("_flw_modelMatrix", poseComposed);
program.setMat3("_flw_normalMatrix", normalComposed);
}
@Override
public void setupCull(GlProgram program) {
program.setBool("_flw_useModelMatrix", true);
program.setMat4("_flw_modelMatrix", poseComposed);
}
@Override
public ContextShader contextShader() {
return ContextShader.EMBEDDED;
}
@Override
public InstancerProvider instancerProvider() {
return instancerProvider;
}
@Override
public Vec3i renderOrigin() {
return Vec3i.ZERO;
}
@Override
public VisualEmbedding createEmbedding() {
var out = new NestedEmbeddedEnvironment(this, engine, renderStage);
engine.environmentStorage()
.track(out);
return out;
}
/**
* Called by visuals
*/
@Override
public void delete() {
// Release the reference owned by the visual that created this.
// Note that visuals don't explicitly call acquire, instead the
// storage acquired a reference when this was constructed.
release();
}
/**
* Called when referenceCount goes to 0
*/
@Override
public void _delete() {
engine.environmentStorage().enqueueDeletion(this);
}
public abstract void setupLight(GlProgram program);
public abstract void composeMatrices(Matrix4f pose, Matrix3f normal);
/**
* Called in EnvironmentStorage#flush
*/
public abstract void actuallyDelete();
}

View File

@ -1,156 +0,0 @@
package com.jozufozu.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.util.AtomicReferenceCounted;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.BlockAndTintGetter;
public class EmbeddedEnvironment extends AtomicReferenceCounted implements Environment, VisualEmbedding {
private final Matrix4f pose = new Matrix4f();
private final Matrix3f normal = new Matrix3f();
private final EmbeddedLightVolume lightVolume = new EmbeddedLightVolume();
private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture();
private final InstancerProvider instancerProvider;
private final EngineImpl engine;
private final RenderStage renderStage;
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
this.engine = engine;
this.renderStage = renderStage;
instancerProvider = new InstancerProvider() {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
// Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(EmbeddedEnvironment.this, type, model, renderStage);
}
};
// Acquire the reference owned by the visual that created this.
acquire();
}
public void flush() {
if (lightVolume.empty()) {
return;
}
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
lightTexture.ensureCapacity(lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
}
@Override
public void transforms(Matrix4fc pose, Matrix3fc normal) {
this.pose.set(pose);
this.normal.set(normal);
}
@Override
public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
lightVolume.collect(level, minX, minY, minZ, sizeX, sizeY, sizeZ);
}
@Override
public void invalidateLight() {
lightVolume.clear();
}
@Override
public ContextShader contextShader() {
return ContextShader.EMBEDDED;
}
@Override
public void setupDraw(GlProgram drawProgram) {
if (!lightVolume.empty()) {
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
float oneOverSizeX = 1f / (float) lightTexture.sizeX;
float oneOverSizeY = 1f / (float) lightTexture.sizeY;
float oneOverSizeZ = 1f / (float) lightTexture.sizeZ;
drawProgram.setVec3("_flw_oneOverLightBoxSize", oneOverSizeX, oneOverSizeY, oneOverSizeZ);
drawProgram.setVec3("_flw_lightVolumeMin", lightVolume.x(), lightVolume.y(), lightVolume.z());
drawProgram.setBool("_flw_useLightVolume", true);
} else {
drawProgram.setBool("_flw_useLightVolume", false);
}
drawProgram.setMat4("_flw_model", pose);
drawProgram.setMat3("_flw_normal", normal);
}
@Override
public void setupCull(GlProgram cullProgram) {
cullProgram.setBool("_flw_useEmbeddedModel", true);
cullProgram.setMat4("_flw_embeddedModel", pose);
}
@Override
public InstancerProvider instancerProvider() {
return instancerProvider;
}
@Override
public Vec3i renderOrigin() {
return Vec3i.ZERO;
}
@Override
public VisualEmbedding createEmbedding() {
return engine.createEmbedding(renderStage);
}
/**
* Called by visuals
*/
@Override
public void delete() {
// Release the reference owned by the visual that created this.
// Note that visuals don't explicitly call acquire, instead the
// storage acquired a reference when this was constructed.
release();
}
/**
* Called when referenceCount goes to 0
*/
@Override
public void _delete() {
engine.freeEmbedding(this);
}
/**
* Called in EnvironmentStorage#flush
*/
public void actuallyDelete() {
// We could technically free the light volume right away in _delete, but
// the control flow here is so convoluted that it's probably best to do
// everything in one place.
lightVolume.delete();
lightTexture.delete();
}
}

View File

@ -0,0 +1,45 @@
package com.jozufozu.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.world.level.BlockAndTintGetter;
public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
private final AbstractEmbeddedEnvironment parent;
public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl engine, RenderStage renderStage) {
super(engine, renderStage);
this.parent = parent;
parent.acquire();
}
@Override
public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
}
@Override
public void invalidateLight() {
}
@Override
public void setupLight(GlProgram program) {
parent.setupLight(program);
}
@Override
public void composeMatrices(Matrix4f pose, Matrix3f normal) {
parent.composeMatrices(pose, normal);
pose.mul(this.pose);
normal.mul(this.normal);
}
@Override
public void actuallyDelete() {
parent.release();
}
}

View File

@ -0,0 +1,81 @@
package com.jozufozu.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.world.level.BlockAndTintGetter;
public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
private final EmbeddedLightVolume lightVolume = new EmbeddedLightVolume();
private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture();
public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
super(engine, renderStage);
}
@Override
public void flush() {
super.flush();
if (lightVolume.empty()) {
return;
}
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
lightTexture.ensureCapacity(lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
}
@Override
public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
lightVolume.collect(level, minX, minY, minZ, sizeX, sizeY, sizeZ);
}
@Override
public void invalidateLight() {
lightVolume.clear();
}
@Override
public void setupLight(GlProgram program) {
if (!lightVolume.empty()) {
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
float oneOverSizeX = 1f / (float) lightTexture.sizeX;
float oneOverSizeY = 1f / (float) lightTexture.sizeY;
float oneOverSizeZ = 1f / (float) lightTexture.sizeZ;
program.setVec3("_flw_oneOverLightBoxSize", oneOverSizeX, oneOverSizeY, oneOverSizeZ);
program.setVec3("_flw_lightVolumeMin", lightVolume.x(), lightVolume.y(), lightVolume.z());
program.setBool("_flw_useLightVolume", true);
} else {
program.setBool("_flw_useLightVolume", false);
}
}
@Override
public void composeMatrices(Matrix4f pose, Matrix3f normal) {
pose.set(this.pose);
normal.set(this.normal);
}
@Override
public void actuallyDelete() {
// We could technically free the light volume right away in _delete, but
// the control flow here is so convoluted that it's probably best to do
// everything in one place.
lightVolume.delete();
lightTexture.delete();
}
}

View File

@ -0,0 +1,53 @@
package com.jozufozu.flywheel.lib.util;
import java.util.Collection;
import java.util.function.LongConsumer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
public class SectionUtil {
public static void containingAll(Collection<BlockPos> blocks, LongConsumer consumer) {
if (blocks.isEmpty()) {
return;
}
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (BlockPos pos : blocks) {
minX = Math.min(minX, pos.getX());
minY = Math.min(minY, pos.getY());
minZ = Math.min(minZ, pos.getZ());
maxX = Math.max(maxX, pos.getX());
maxY = Math.max(maxY, pos.getY());
maxZ = Math.max(maxZ, pos.getZ());
}
betweenClosedBlocks(minX, minY, minZ, maxX, maxY, maxZ, consumer);
}
public static void betweenClosedBox(int x, int y, int z, int sizeX, int sizeY, int sizeZ, LongConsumer consumer) {
betweenClosedBlocks(x, y, z, x + sizeX, y + sizeY, z + sizeZ, consumer);
}
public static void betweenClosedBlocks(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, LongConsumer consumer) {
int minSectionX = SectionPos.blockToSectionCoord(minX);
int minSectionY = SectionPos.blockToSectionCoord(minY);
int minSectionZ = SectionPos.blockToSectionCoord(minZ);
int maxSectionX = SectionPos.blockToSectionCoord(maxX);
int maxSectionY = SectionPos.blockToSectionCoord(maxY);
int maxSectionZ = SectionPos.blockToSectionCoord(maxZ);
for (int sectionX = minSectionX; sectionX <= maxSectionX; sectionX++) {
for (int sectionY = minSectionY; sectionY <= maxSectionY; sectionY++) {
for (int sectionZ = minSectionZ; sectionZ <= maxSectionZ; sectionZ++) {
consumer.accept(SectionPos.asLong(sectionX, sectionY, sectionZ));
}
}
}
}
}

View File

@ -69,8 +69,8 @@ vec2 getCrumblingTexCoord() {
#ifdef _FLW_EMBEDDED
uniform vec3 _flw_oneOverLightBoxSize;
uniform vec3 _flw_lightVolumeMin;
uniform mat4 _flw_model;
uniform mat3 _flw_normal;
uniform mat4 _flw_modelMatrix;
uniform mat3 _flw_normalMatrix;
out vec3 _flw_lightVolumeCoord;
#endif
@ -87,8 +87,8 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) {
#endif
#ifdef _FLW_EMBEDDED
flw_vertexPos = _flw_model * flw_vertexPos;
flw_vertexNormal = _flw_normal * flw_vertexNormal;
flw_vertexPos = _flw_modelMatrix * flw_vertexPos;
flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal;
_flw_lightVolumeCoord = (flw_vertexPos.xyz - _flw_lightVolumeMin) * _flw_oneOverLightBoxSize;
#endif

View File

@ -17,8 +17,8 @@ layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer
ModelDescriptor _flw_models[];
};
uniform mat4 _flw_embeddedModel;
uniform bool _flw_useEmbeddedModel = false;
uniform mat4 _flw_modelMatrix;
uniform bool _flw_useModelMatrix = false;
// Disgustingly vectorized sphere frustum intersection taking advantage of ahead of time packing.
// Only uses 6 fmas and some boolean ops.
@ -44,8 +44,8 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) {
flw_transformBoundingSphere(instance, center, radius);
if (_flw_useEmbeddedModel) {
transformBoundingSphere(_flw_embeddedModel, center, radius);
if (_flw_useModelMatrix) {
transformBoundingSphere(_flw_modelMatrix, center, radius);
}
return _flw_testSphere(center, radius);