Instance Refactor II

This commit is contained in:
PepperCode1 2023-04-05 18:03:25 -07:00
parent 227aef509b
commit 391adfef1a
70 changed files with 972 additions and 1020 deletions

View file

@ -3,17 +3,17 @@ package com.jozufozu.flywheel;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.slf4j.Logger;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.Loader;
import com.jozufozu.flywheel.backend.engine.batching.DrawBuffer;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.config.BackendArgument;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.handler.EntityWorldHandler;
import com.jozufozu.flywheel.handler.ForgeEvents;
import com.jozufozu.flywheel.impl.BackendManagerImpl;
import com.jozufozu.flywheel.impl.IdRegistryImpl;
import com.jozufozu.flywheel.impl.RegistryImpl;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.lib.backend.Backends;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.format.Formats;
@ -115,7 +115,7 @@ public class Flywheel {
VanillaInstances.init();
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManager::getBackendNameForCrashReport);
CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManagerImpl::getBackendNameForCrashReport);
// https://github.com/Jozufozu/Flywheel/issues/69
// Weird issue with accessor loading.
@ -129,7 +129,7 @@ public class Flywheel {
RegistryImpl.freezeAll();
IdRegistryImpl.freezeAll();
ArgumentTypes.register(rl("backend").toString(), BackendArgument.class, new EmptyArgumentSerializer<>(BackendArgument::getInstance));
ArgumentTypes.register(rl("backend").toString(), BackendArgument.class, new EmptyArgumentSerializer<>(() -> BackendArgument.INSTANCE));
}
public static ArtifactVersion getVersion() {

View file

@ -7,6 +7,7 @@ import com.jozufozu.flywheel.api.registry.IdRegistry;
import com.jozufozu.flywheel.impl.IdRegistryImpl;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.LevelAccessor;
public interface Backend {
static IdRegistry<Backend> REGISTRY = IdRegistryImpl.create();
@ -19,7 +20,7 @@ public interface Backend {
/**
* Create a new engine instance.
*/
Engine createEngine();
Engine createEngine(LevelAccessor level);
/**
* Get a fallback backend in case this backend is not supported.

View file

@ -13,13 +13,6 @@ public final class BackendManager {
return BackendManagerImpl.getBackend();
}
/**
* Get a string describing the current backend.
*/
public static String getBackendNameForCrashReport() {
return BackendManagerImpl.getBackendNameForCrashReport();
}
public static boolean isOn() {
return BackendManagerImpl.isOn();
}

View file

@ -2,11 +2,30 @@ package com.jozufozu.flywheel.api.backend;
import java.util.List;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.api.task.TaskExecutor;
public interface Engine extends RenderDispatcher, InstancerProvider {
void attachManagers(InstanceManager<?>... listener);
import net.minecraft.client.Camera;
import net.minecraft.core.Vec3i;
public interface Engine extends InstancerProvider {
void beginFrame(TaskExecutor executor, RenderContext context);
void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage);
/**
* Maintain the render origin to be within a certain distance from the camera in all directions,
* preventing floating point precision issues at high coordinates.
*
* @return {@code true} if the render origin changed, {@code false} otherwise.
*/
boolean updateRenderOrigin(Camera camera);
Vec3i renderOrigin();
void addDebugInfo(List<String> info);
void delete();
}

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.api.backend;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import net.minecraft.client.Camera;
import net.minecraft.core.Vec3i;
public interface RenderDispatcher {
void beginFrame(TaskExecutor executor, RenderContext context);
void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage);
/**
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions,
* preventing floating point precision issues at high coordinates.
*
* @return {@code true} if the origin coordinate was changed, {@code false} otherwise.
*/
boolean maintainOriginCoordinate(Camera camera);
Vec3i renderOrigin();
void delete();
}

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.api.instance;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer;
@ -34,4 +36,13 @@ public interface DynamicInstance extends Instance {
default boolean decreaseFramerateWithDistance() {
return true;
}
/**
* Check this instance against a frustum.<p>
* An implementor may choose to return a constant to skip the frustum check.
*
* @param frustum A frustum intersection tester for the current frame.
* @return {@code true} if this instance should be considered for updates.
*/
boolean isVisible(FrustumIntersection frustum);
}

View file

@ -0,0 +1,6 @@
package com.jozufozu.flywheel.api.instance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
public interface EffectInstance<T extends Effect> extends Instance {
}

View file

@ -2,5 +2,5 @@ package com.jozufozu.flywheel.api.instance;
import net.minecraft.world.entity.Entity;
public interface EntityInstance<E extends Entity> extends Instance {
public interface EntityInstance<T extends Entity> extends Instance {
}

View file

@ -1,7 +1,5 @@
package com.jozufozu.flywheel.api.instance;
import org.joml.FrustumIntersection;
/**
* A general interface providing information about any type of thing that could use Flywheel's instanced rendering.
*/
@ -33,15 +31,6 @@ public interface Instance {
*/
boolean shouldReset();
/**
* Check this instance against a frustum.<p>
* An implementor may choose to return a constant to skip the frustum check.
*
* @param frustum A frustum intersection tester for the current frame.
* @return {@code true} if this instance should be considered for updates.
*/
boolean checkFrustum(FrustumIntersection frustum);
/**
* Calculate the distance squared between this instance and the given <em>world</em> position.
*
@ -56,5 +45,4 @@ public interface Instance {
* Free any acquired resources.
*/
void delete();
}

View file

@ -10,7 +10,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
*/
public interface BlockEntityInstancingController<T extends BlockEntity> {
/**
* Given a block entity and an instancer manager, constructs an instance for the block entity.
* Given a block entity and context, constructs an instance for the block entity.
*
* @param ctx Context for creating an Instance.
* @param blockEntity The block entity to construct an instance for.

View file

@ -10,7 +10,7 @@ import net.minecraft.world.entity.Entity;
*/
public interface EntityInstancingController<T extends Entity> {
/**
* Given an entity and an instancer manager, constructs an instance for the entity.
* Given an entity and context, constructs an instance for the entity.
*
* @param ctx Context for creating an Instance.
* @param entity The entity to construct an instance for.

View file

@ -12,5 +12,4 @@ import net.minecraft.core.Vec3i;
* All models render as if this position is (0, 0, 0).
*/
public record InstanceContext(InstancerProvider instancerProvider, Vec3i renderOrigin) {
}

View file

@ -2,7 +2,7 @@ package com.jozufozu.flywheel.api.instance.controller;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.impl.instance.InstancingControllerRegistryImpl;
import com.jozufozu.flywheel.impl.InstancingControllerRegistryImpl;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;

View file

@ -2,9 +2,11 @@ package com.jozufozu.flywheel.api.instance.effect;
import java.util.Collection;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.EffectInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
// TODO: add level getter?
// TODO: return single instance instead of many?
public interface Effect {
Collection<Instance> createInstances(InstanceContext ctx);
Collection<EffectInstance<?>> createInstances(InstanceContext ctx);
}

View file

@ -10,5 +10,5 @@ public interface InstancerProvider {
*
* @return An instancer for the given struct type, model, and render stage.
*/
<D extends InstancedPart> Instancer<D> getInstancer(StructType<D> type, Model model, RenderStage stage);
<D extends InstancedPart> Instancer<D> instancer(StructType<D> type, Model model, RenderStage stage);
}

View file

@ -1,23 +1,5 @@
package com.jozufozu.flywheel.api.vertex;
import com.jozufozu.flywheel.extension.VertexFormatExtension;
import com.jozufozu.flywheel.impl.vertex.InferredVertexListProviderImpl;
import com.mojang.blaze3d.vertex.VertexFormat;
public interface VertexListProvider {
ReusableVertexList createVertexList();
static VertexListProvider get(VertexFormat format) {
VertexFormatExtension extension = (VertexFormatExtension) format;
VertexListProvider provider = extension.flywheel$getVertexListProvider();
if (provider == null) {
provider = new InferredVertexListProviderImpl(format);
extension.flywheel$setVertexListProvider(provider);
}
return provider;
}
static void set(VertexFormat format, VertexListProvider provider) {
((VertexFormatExtension) format).flywheel$setVertexListProvider(provider);
}
}

View file

@ -0,0 +1,17 @@
package com.jozufozu.flywheel.api.vertex;
import com.jozufozu.flywheel.impl.VertexListProviderRegistryImpl;
import com.mojang.blaze3d.vertex.VertexFormat;
public final class VertexListProviderRegistry {
public static VertexListProvider getProvider(VertexFormat format) {
return VertexListProviderRegistryImpl.getProvider(format);
}
public static void setProvider(VertexFormat format, VertexListProvider provider) {
VertexListProviderRegistryImpl.setProvider(format, provider);
}
private VertexListProviderRegistry() {
}
}

View file

@ -8,12 +8,9 @@ import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
public class BackendUtil {
public static final boolean DUMP_SHADER_SOURCE = System.getProperty("flw.dumpShaderSource") != null;
private static ParallelTaskExecutor executor;
/**
@ -30,7 +27,7 @@ public class BackendUtil {
}
@Contract("null -> false")
public static boolean canUseInstancing(@Nullable Level level) {
public static boolean canUseInstancing(@Nullable LevelAccessor level) {
return BackendManager.isOn() && isFlywheelLevel(level);
}
@ -38,11 +35,17 @@ public class BackendUtil {
* Used to avoid calling Flywheel functions on (fake) levels that don't specifically support it.
*/
public static boolean isFlywheelLevel(@Nullable LevelAccessor level) {
if (level == null) return false;
if (level == null) {
return false;
}
if (!level.isClientSide()) return false;
if (!level.isClientSide()) {
return false;
}
if (level instanceof FlywheelLevel && ((FlywheelLevel) level).supportsFlywheel()) return true;
if (level instanceof FlywheelLevel flywheelLevel && flywheelLevel.supportsFlywheel()) {
return true;
}
return level == Minecraft.getInstance().level;
}
@ -50,8 +53,4 @@ public class BackendUtil {
public static boolean isGameActive() {
return !(Minecraft.getInstance().level == null || Minecraft.getInstance().player == null);
}
public static void reloadWorldRenderers() {
Minecraft.getInstance().levelRenderer.allChanged();
}
}

View file

@ -2,9 +2,9 @@ package com.jozufozu.flywheel.backend;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.glsl.ShaderSources;
import com.jozufozu.flywheel.glsl.error.ErrorReporter;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
@ -39,8 +39,8 @@ public class Loader implements ResourceManagerReloadListener {
FlwCompiler.INSTANCE = new FlwCompiler(sources);
ClientLevel level = Minecraft.getInstance().level;
if (BackendUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.resetInstanceLevel(level);
if (level != null) {
InstancedRenderDispatcher.resetInstanceWorld(level);
}
}

View file

@ -10,7 +10,6 @@ import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.gl.GLSLVersion;
import com.jozufozu.flywheel.gl.shader.GlShader;
import com.jozufozu.flywheel.gl.shader.ShaderType;
@ -29,6 +28,8 @@ import net.minecraft.resources.ResourceLocation;
* and interprets/pretty prints any errors that occur.
*/
public class Compilation {
public static final boolean DUMP_SHADER_SOURCE = System.getProperty("flw.dumpShaderSource") != null;
private final List<SourceFile> files = new ArrayList<>();
private final List<ResourceLocation> componentNames = new ArrayList<>();
private final StringBuilder generatedSource;
@ -131,7 +132,7 @@ public class Compilation {
}
private static void dumpSource(String source, String fileName) {
if (!BackendUtil.DUMP_SHADER_SOURCE) {
if (!DUMP_SHADER_SOURCE) {
return;
}

View file

@ -11,7 +11,7 @@ import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
import com.jozufozu.flywheel.api.vertex.VertexListProvider;
import com.jozufozu.flywheel.api.vertex.VertexListProviderRegistry;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.mojang.blaze3d.vertex.VertexFormat;
@ -35,7 +35,7 @@ public class BatchedMeshPool {
*/
public BatchedMeshPool(VertexFormat vertexFormat) {
this.vertexFormat = vertexFormat;
vertexList = VertexListProvider.get(vertexFormat).createVertexList();
vertexList = VertexListProviderRegistry.getProvider(vertexFormat).createVertexList();
growthMargin = vertexFormat.getVertexSize() * 32;
}

View file

@ -10,7 +10,6 @@ import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.vertex.PoseStack;
@ -21,11 +20,11 @@ import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
public class BatchingEngine implements Engine {
protected final BatchingTransformManager transformManager = new BatchingTransformManager();
protected final BatchingDrawTracker drawTracker = new BatchingDrawTracker();
private final BatchingTransformManager transformManager = new BatchingTransformManager();
private final BatchingDrawTracker drawTracker = new BatchingDrawTracker();
@Override
public <D extends InstancedPart> Instancer<D> getInstancer(StructType<D> type, Model model, RenderStage stage) {
public <D extends InstancedPart> Instancer<D> instancer(StructType<D> type, Model model, RenderStage stage) {
return transformManager.getInstancer(type, model, stage);
}
@ -42,7 +41,7 @@ public class BatchingEngine implements Engine {
submitTasks(executor, stack.last(), context.level());
}
public void submitTasks(TaskExecutor executor, PoseStack.Pose matrices, ClientLevel level) {
private void submitTasks(TaskExecutor executor, PoseStack.Pose matrices, ClientLevel level) {
for (var transformSetEntry : transformManager.getTransformSetsView().entrySet()) {
var stage = transformSetEntry.getKey();
var transformSet = transformSetEntry.getValue();
@ -79,16 +78,10 @@ public class BatchingEngine implements Engine {
}
@Override
public boolean maintainOriginCoordinate(Camera camera) {
// do nothing
public boolean updateRenderOrigin(Camera camera) {
return false;
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
// noop
}
@Override
public Vec3i renderOrigin() {
return BlockPos.ZERO;

View file

@ -7,6 +7,7 @@ import org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.vertex.VertexListProvider;
import com.jozufozu.flywheel.api.vertex.VertexListProviderRegistry;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.renderer.RenderType;
@ -24,7 +25,7 @@ public class DrawBufferSet {
this.renderType = renderType;
format = renderType.format();
stride = format.getVertexSize();
provider = VertexListProvider.get(format);
provider = VertexListProviderRegistry.getProvider(format);
}
public DrawBuffer getBuffer(RenderStage stage) {

View file

@ -1,13 +1,10 @@
package com.jozufozu.flywheel.backend.engine.indirect;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
@ -15,40 +12,29 @@ import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.gl.GlStateTracker;
import com.jozufozu.flywheel.gl.GlTextureUnit;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public class IndirectEngine implements Engine {
private final int sqrMaxOriginDistance;
protected final IndirectDrawManager drawManager = new IndirectDrawManager();
private final IndirectDrawManager drawManager = new IndirectDrawManager();
/**
* The set of instance managers that are attached to this engine.
*/
private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet();
private BlockPos renderOrigin = BlockPos.ZERO;
protected final Context context;
protected final int sqrMaxOriginDistance;
protected BlockPos originCoordinate = BlockPos.ZERO;
public IndirectEngine(Context context, int sqrMaxOriginDistance) {
this.context = context;
this.sqrMaxOriginDistance = sqrMaxOriginDistance;
public IndirectEngine(int maxOriginDistance) {
this.sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance;
}
@Override
public <D extends InstancedPart> Instancer<D> getInstancer(StructType<D> type, Model model, RenderStage stage) {
public <D extends InstancedPart> Instancer<D> instancer(StructType<D> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}
@ -70,7 +56,7 @@ public class IndirectEngine implements Engine {
}
}
protected void setup() {
private void setup() {
GlTextureUnit.T2.makeActive();
Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer();
@ -82,36 +68,25 @@ public class IndirectEngine implements Engine {
}
@Override
public boolean maintainOriginCoordinate(Camera camera) {
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
double dx = renderOrigin.getX() - cameraPos.x;
double dy = renderOrigin.getY() - cameraPos.y;
double dz = renderOrigin.getZ() - cameraPos.z;
double distanceSqr = dx * dx + dy * dy + dz * dz;
double distanceSqr = Vec3.atLowerCornerOf(originCoordinate)
.subtract(cameraPos)
.lengthSqr();
if (distanceSqr > sqrMaxOriginDistance) {
shiftListeners(Mth.floor(cameraPos.x), Mth.floor(cameraPos.y), Mth.floor(cameraPos.z));
return true;
if (distanceSqr <= sqrMaxOriginDistance) {
return false;
}
return false;
}
private void shiftListeners(int cX, int cY, int cZ) {
originCoordinate = new BlockPos(cX, cY, cZ);
renderOrigin = new BlockPos(cameraPos);
drawManager.clearInstancers();
instanceManagers.forEach(InstanceManager::onOriginShift);
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
Collections.addAll(instanceManagers, listener);
return true;
}
@Override
public Vec3i renderOrigin() {
return originCoordinate;
return renderOrigin;
}
@Override
@ -122,6 +97,6 @@ public class IndirectEngine implements Engine {
@Override
public void addDebugInfo(List<String> info) {
info.add("GL46 Indirect");
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
}
}

View file

@ -1,8 +1,6 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.lwjgl.opengl.GL32;
@ -17,42 +15,33 @@ import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.compile.FlwCompiler;
import com.jozufozu.flywheel.backend.engine.UniformBuffer;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.gl.GlStateTracker;
import com.jozufozu.flywheel.gl.GlTextureUnit;
import com.jozufozu.flywheel.lib.material.MaterialIndices;
import com.jozufozu.flywheel.lib.pipeline.Pipelines;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec3;
public class InstancingEngine implements Engine {
private final Context context;
private final int sqrMaxOriginDistance;
protected final InstancingDrawManager drawManager = new InstancingDrawManager();
private final InstancingDrawManager drawManager = new InstancingDrawManager();
/**
* The set of instance managers that are attached to this engine.
*/
private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet();
private BlockPos renderOrigin = BlockPos.ZERO;
protected final Context context;
protected final int sqrMaxOriginDistance;
protected BlockPos originCoordinate = BlockPos.ZERO;
public InstancingEngine(Context context, int sqrMaxOriginDistance) {
public InstancingEngine(Context context, int maxOriginDistance) {
this.context = context;
this.sqrMaxOriginDistance = sqrMaxOriginDistance;
this.sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance;
}
@Override
public <D extends InstancedPart> Instancer<D> getInstancer(StructType<D> type, Model model, RenderStage stage) {
public <D extends InstancedPart> Instancer<D> instancer(StructType<D> type, Model model, RenderStage stage) {
return drawManager.getInstancer(type, model, stage);
}
@ -78,7 +67,7 @@ public class InstancingEngine implements Engine {
}
}
protected void setup() {
private void setup() {
GlTextureUnit.T2.makeActive();
Minecraft.getInstance().gameRenderer.lightTexture().turnOnLightLayer();
@ -89,7 +78,7 @@ public class InstancingEngine implements Engine {
RenderSystem.enableCull();
}
protected void render(InstancingDrawManager.DrawSet drawSet) {
private void render(InstancingDrawManager.DrawSet drawSet) {
for (var entry : drawSet) {
var shader = entry.getKey();
var drawCalls = entry.getValue();
@ -112,7 +101,7 @@ public class InstancingEngine implements Engine {
}
}
protected void setup(ShaderState desc) {
private void setup(ShaderState desc) {
var vertexType = desc.vertex();
var structType = desc.instance();
var material = desc.material();
@ -127,36 +116,25 @@ public class InstancingEngine implements Engine {
}
@Override
public boolean maintainOriginCoordinate(Camera camera) {
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
double dx = renderOrigin.getX() - cameraPos.x;
double dy = renderOrigin.getY() - cameraPos.y;
double dz = renderOrigin.getZ() - cameraPos.z;
double distanceSqr = dx * dx + dy * dy + dz * dz;
double distanceSqr = Vec3.atLowerCornerOf(originCoordinate)
.subtract(cameraPos)
.lengthSqr();
if (distanceSqr > sqrMaxOriginDistance) {
shiftListeners(Mth.floor(cameraPos.x), Mth.floor(cameraPos.y), Mth.floor(cameraPos.z));
return true;
if (distanceSqr <= sqrMaxOriginDistance) {
return false;
}
return false;
}
private void shiftListeners(int cX, int cY, int cZ) {
originCoordinate = new BlockPos(cX, cY, cZ);
renderOrigin = new BlockPos(cameraPos);
drawManager.clearInstancers();
instanceManagers.forEach(InstanceManager::onOriginShift);
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
Collections.addAll(instanceManagers, listener);
return true;
}
@Override
public Vec3i renderOrigin() {
return originCoordinate;
return renderOrigin;
}
@Override
@ -167,6 +145,6 @@ public class InstancingEngine implements Engine {
@Override
public void addDebugInfo(List<String> info) {
info.add("GL33 Instanced Arrays");
info.add("Origin: " + originCoordinate.getX() + ", " + originCoordinate.getY() + ", " + originCoordinate.getZ());
info.add("Origin: " + renderOrigin.getX() + ", " + renderOrigin.getY() + ", " + renderOrigin.getZ());
}
}

View file

@ -1,151 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.manager.EffectInstanceManager;
import com.jozufozu.flywheel.backend.instancing.manager.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.backend.task.ParallelTaskExecutor;
import com.jozufozu.flywheel.extension.ClientLevelExtension;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
/**
* A manager class for a single world where instancing is supported.
* <p>
* The instancer manager is shared between the different instance managers.
* </p>
*/
public class InstanceWorld implements AutoCloseable {
protected final Engine engine;
protected final InstanceManager<Entity> entities;
protected final InstanceManager<BlockEntity> blockEntities;
public final ParallelTaskExecutor taskExecutor;
private final InstanceManager<Effect> effects;
public static InstanceWorld create(LevelAccessor level) {
var engine = BackendManager.getBackend()
.createEngine();
var entities = new EntityInstanceManager(engine);
var blockEntities = new BlockEntityInstanceManager(engine);
var effects = new EffectInstanceManager(engine);
engine.attachManagers(entities, blockEntities, effects);
return new InstanceWorld(engine, entities, blockEntities, effects);
}
public InstanceWorld(Engine engine, InstanceManager<Entity> entities, InstanceManager<BlockEntity> blockEntities,
InstanceManager<Effect> effects) {
this.engine = engine;
this.entities = entities;
this.blockEntities = blockEntities;
this.effects = effects;
this.taskExecutor = BackendUtil.getTaskExecutor();
}
public InstanceManager<Entity> getEntities() {
return entities;
}
public InstanceManager<Effect> getEffects() {
return effects;
}
public InstanceManager<BlockEntity> getBlockEntities() {
return blockEntities;
}
/**
* Free all acquired resources and invalidate this instance world.
*/
public void delete() {
engine.delete();
entities.delete();
blockEntities.delete();
}
/**
* Get ready to render a frame.
* <p>
* Check and shift the origin coordinate.
* <br>
* Call {@link DynamicInstance#beginFrame()} on all instances in this world.
* </p>
*/
public void beginFrame(BeginFrameEvent event) {
RenderContext context = event.getContext();
boolean shifted = engine.maintainOriginCoordinate(context.camera());
taskExecutor.syncPoint();
if (!shifted) {
blockEntities.beginFrame(taskExecutor, context);
entities.beginFrame(taskExecutor, context);
effects.beginFrame(taskExecutor, context);
}
engine.beginFrame(taskExecutor, context);
}
/**
* Tick the renderers after the game has ticked:
* <p>
* Call {@link TickableInstance#tick()} on all instances in this world.
* </p>
*/
public void tick() {
Minecraft mc = Minecraft.getInstance();
if (mc.isPaused()) return;
Entity renderViewEntity = mc.cameraEntity != null ? mc.cameraEntity : mc.player;
if (renderViewEntity == null) return;
double x = renderViewEntity.getX();
double y = renderViewEntity.getY();
double z = renderViewEntity.getZ();
blockEntities.tick(taskExecutor, x, y, z);
entities.tick(taskExecutor, x, y, z);
effects.tick(taskExecutor, x, y, z);
}
/**
* Draw all instances for the given stage.
*/
public void renderStage(RenderContext context, RenderStage stage) {
taskExecutor.syncPoint();
engine.renderStage(taskExecutor, context, stage);
}
/**
* Instantiate all the necessary instances to render the given world.
*/
public void loadEntities(ClientLevel level) {
// Block entities are loaded while chunks are baked.
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(level)
.forEach(entities::add);
}
@Override
public void close() {
delete();
}
}

View file

@ -1,133 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.event.RenderStageEvent;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.WorldAttached;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Vec3i;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.event.TickEvent;
public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>(InstanceWorld::create);
/**
* Call this when you want to manually run {@link Instance#update()}.
* @param blockEntity The block entity whose instance you want to update.
*/
public static void enqueueUpdate(BlockEntity blockEntity) {
if (BackendManager.isOn() && blockEntity.hasLevel() && blockEntity.getLevel() instanceof ClientLevel) {
instanceWorlds.get(blockEntity.getLevel())
.getBlockEntities()
.queueUpdate(blockEntity);
}
}
/**
* Call this when you want to manually run {@link Instance#update()}.
* @param entity The entity whose instance you want to update.
*/
public static void enqueueUpdate(Entity entity) {
if (BackendManager.isOn()) {
instanceWorlds.get(entity.level)
.getEntities()
.queueUpdate(entity);
}
}
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
return getInstanceWorld(world).getBlockEntities();
}
public static InstanceManager<Entity> getEntities(LevelAccessor world) {
return getInstanceWorld(world).getEntities();
}
public static InstanceManager<Effect> getEffects(LevelAccessor world) {
return getInstanceWorld(world).getEffects();
}
/**
* Get or create the {@link InstanceWorld} for the given world.
* @throws NullPointerException if the backend is off
*/
public static InstanceWorld getInstanceWorld(LevelAccessor world) {
if (BackendManager.isOn()) {
return instanceWorlds.get(world);
} else {
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
}
}
public static void tick(TickEvent.ClientTickEvent event) {
if (!BackendUtil.isGameActive() || event.phase == TickEvent.Phase.START) {
return;
}
Minecraft mc = Minecraft.getInstance();
ClientLevel level = mc.level;
AnimationTickHolder.tick();
if (BackendManager.isOn()) {
instanceWorlds.get(level)
.tick();
}
}
public static void onBeginFrame(BeginFrameEvent event) {
if (BackendUtil.isGameActive() && BackendManager.isOn()) {
instanceWorlds.get(event.getContext().level())
.beginFrame(event);
}
}
public static void onRenderStage(RenderStageEvent event) {
ClientLevel level = event.getContext().level();
if (!BackendUtil.canUseInstancing(level)) return;
instanceWorlds.get(level).renderStage(event.getContext(), event.getStage());
}
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel level = event.getLevel();
if (BackendManager.isOn() && level != null) {
resetInstanceLevel(level);
}
}
public static void resetInstanceLevel(ClientLevel level) {
instanceWorlds.replace(level, InstanceWorld::delete)
.loadEntities(level);
}
public static void getDebugString(List<String> debug) {
if (BackendManager.isOn()) {
InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level);
debug.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
debug.add("B: " + instanceWorld.blockEntities.getInstanceCount() + ", E: " + instanceWorld.entities.getInstanceCount());
instanceWorld.engine.addDebugInfo(debug);
} else {
debug.add("Disabled");
}
}
public static Vec3i getOriginCoordinate(ClientLevel level) {
return instanceWorlds.get(level).engine.renderOrigin();
}
}

View file

@ -1,111 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.manager;
import java.util.ArrayList;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.backend.instancing.storage.AbstractStorage;
import com.jozufozu.flywheel.backend.instancing.storage.Storage;
public class EffectInstanceManager extends InstanceManager<Effect> {
private final EffectStorage<Effect> storage;
public EffectInstanceManager(Engine engine) {
storage = new EffectStorage<>(engine);
}
@Override
protected Storage<Effect> getStorage() {
return storage;
}
@Override
protected boolean canCreateInstance(Effect obj) {
return true;
}
private static class EffectStorage<T extends Effect> extends AbstractStorage<T> {
private final Multimap<T, Instance> instances;
public EffectStorage(Engine engine) {
super(engine);
this.instances = HashMultimap.create();
}
@Override
public Iterable<Instance> getAllInstances() {
return instances.values();
}
@Override
public int getInstanceCount() {
return instances.size();
}
@Override
public void add(T obj) {
var instances = this.instances.get(obj);
if (instances.isEmpty()) {
create(obj);
}
}
@Override
public void remove(T obj) {
var instances = this.instances.removeAll(obj);
if (instances.isEmpty()) {
return;
}
this.tickableInstances.removeAll(instances);
this.dynamicInstances.removeAll(instances);
for (Instance instance : instances) {
instance.delete();
}
}
@Override
public void update(T obj) {
var instances = this.instances.get(obj);
if (instances.isEmpty()) {
return;
}
instances.forEach(Instance::update);
}
@Override
public void recreateAll() {
tickableInstances.clear();
dynamicInstances.clear();
instances.values().forEach(Instance::delete);
var backup = new ArrayList<>(instances.keySet());
instances.clear();
backup.forEach(this::create);
}
@Override
public void invalidate() {
instances.values().forEach(Instance::delete);
instances.clear();
tickableInstances.clear();
dynamicInstances.clear();
}
private void create(T obj) {
var instances = obj.createInstances(new InstanceContext(engine, engine.renderOrigin()));
this.instances.putAll(obj, instances);
instances.forEach(this::setup);
}
}
}

View file

@ -17,9 +17,7 @@ import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
public enum BackendArgument implements ArgumentType<Backend> {
INSTANCE;
public class BackendArgument implements ArgumentType<Backend> {
private static final List<String> STRING_IDS = Backend.REGISTRY.getAllIds().stream().map(ResourceLocation::toString).toList();
private static final Dynamic2CommandExceptionType INVALID = new Dynamic2CommandExceptionType((found, constants) -> {
@ -27,9 +25,7 @@ public enum BackendArgument implements ArgumentType<Backend> {
return new TranslatableComponent("commands.forge.arguments.enum.invalid", constants, found);
});
public static BackendArgument getInstance() {
return INSTANCE;
}
public static final BackendArgument INSTANCE = new BackendArgument();
@Override
public Backend parse(StringReader reader) throws CommandSyntaxException {

View file

@ -3,10 +3,8 @@ package com.jozufozu.flywheel.config;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.api.backend.Backend;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.lib.uniform.FlwShaderUniforms;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@ -30,9 +28,9 @@ public class FlwCommands {
public static void registerClientCommands(RegisterClientCommandsEvent event) {
FlwConfig config = FlwConfig.get();
ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel");
LiteralArgumentBuilder<CommandSourceStack> command = Commands.literal("flywheel");
commandBuilder.addValue(config.client.backend, "backend", (builder, value) ->
addValue(command, config.client.backend, "backend", (builder, value) ->
builder
.executes(context -> {
LocalPlayer player = Minecraft.getInstance().player;
@ -68,12 +66,12 @@ public class FlwCommands {
Component message = backend.engineMessage();
player.displayClientMessage(message, false);
BackendUtil.reloadWorldRenderers();
Minecraft.getInstance().levelRenderer.allChanged();
}
return Command.SINGLE_SUCCESS;
})));
commandBuilder.addValue(config.client.limitUpdates, "limitUpdates", (builder, value) -> booleanValueCommand(builder, value,
addValue(command, config.client.limitUpdates, "limitUpdates", (builder, value) -> booleanValueCommand(builder, value,
(source, bool) -> {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return;
@ -88,12 +86,12 @@ public class FlwCommands {
Component text = boolToText(bool).append(new TextComponent(" update limiting.").withStyle(ChatFormatting.WHITE));
player.displayClientMessage(text, false);
BackendUtil.reloadWorldRenderers();
Minecraft.getInstance().levelRenderer.allChanged();
}
));
// TODO
commandBuilder.command.then(Commands.literal("debugNormals"))
command.then(Commands.literal("debugNormals"))
.executes(context -> {
LocalPlayer player = Minecraft.getInstance().player;
if (player == null) return 0;
@ -103,7 +101,7 @@ public class FlwCommands {
return Command.SINGLE_SUCCESS;
});
commandBuilder.command.then(Commands.literal("debugCrumbling")
command.then(Commands.literal("debugCrumbling")
.then(Commands.argument("pos", BlockPosArgument.blockPos())
.then(Commands.argument("stage", IntegerArgumentType.integer(0, 9))
.executes(context -> {
@ -122,7 +120,7 @@ public class FlwCommands {
return Command.SINGLE_SUCCESS;
}))));
commandBuilder.command.then(Commands.literal("debugFrustum")
command.then(Commands.literal("debugFrustum")
.then(Commands.literal("pause")
.executes(context -> {
FlwShaderUniforms.FRUSTUM_PAUSED = true;
@ -139,10 +137,16 @@ public class FlwCommands {
return 1;
})));
commandBuilder.build(event.getDispatcher());
event.getDispatcher().register(command);
}
public static void booleanValueCommand(LiteralArgumentBuilder<CommandSourceStack> builder, ConfigValue<Boolean> value, BiConsumer<CommandSourceStack, Boolean> displayAction, BiConsumer<CommandSourceStack, Boolean> setAction) {
private static <T extends ConfigValue<?>> void addValue(LiteralArgumentBuilder<CommandSourceStack> command, T value, String subcommand, BiConsumer<LiteralArgumentBuilder<CommandSourceStack>, T> consumer) {
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(subcommand);
consumer.accept(builder, value);
command.then(builder);
}
private static void booleanValueCommand(LiteralArgumentBuilder<CommandSourceStack> builder, ConfigValue<Boolean> value, BiConsumer<CommandSourceStack, Boolean> displayAction, BiConsumer<CommandSourceStack, Boolean> setAction) {
builder
.executes(context -> {
displayAction.accept(context.getSource(), value.get());
@ -165,22 +169,4 @@ public class FlwCommands {
public static MutableComponent boolToText(boolean b) {
return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED);
}
public static class ConfigCommandBuilder {
protected LiteralArgumentBuilder<CommandSourceStack> command;
public ConfigCommandBuilder(String baseLiteral) {
command = Commands.literal(baseLiteral);
}
public <T extends ConfigValue<?>> void addValue(T value, String subcommand, BiConsumer<LiteralArgumentBuilder<CommandSourceStack>, T> consumer) {
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(subcommand);
consumer.accept(builder, value);
command.then(builder);
}
public void build(CommandDispatcher<CommandSourceStack> dispatcher) {
dispatcher.register(command);
}
}
}

View file

@ -1,19 +1,34 @@
package com.jozufozu.flywheel.handler;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import net.minecraft.world.level.Level;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.EntityLeaveWorldEvent;
public class EntityWorldHandler {
public static void onEntityJoinWorld(EntityJoinWorldEvent event) {
if (event.getWorld().isClientSide && BackendManager.isOn()) InstancedRenderDispatcher.getEntities(event.getWorld())
.queueAdd(event.getEntity());
Level level = event.getWorld();
if (!level.isClientSide) {
return;
}
if (BackendUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getEntities(level)
.queueAdd(event.getEntity());
}
}
public static void onEntityLeaveWorld(EntityLeaveWorldEvent event) {
if (event.getWorld().isClientSide && BackendManager.isOn()) InstancedRenderDispatcher.getEntities(event.getWorld())
.remove(event.getEntity());
Level level = event.getWorld();
if (!level.isClientSide) {
return;
}
if (BackendUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getEntities(level)
.remove(event.getEntity());
}
}
}

View file

@ -4,7 +4,7 @@ import java.util.ArrayList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
import com.jozufozu.flywheel.util.StringUtil;
@ -16,14 +16,13 @@ import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.WorldEvent;
public class ForgeEvents {
public static void addToDebugScreen(RenderGameOverlayEvent.Text event) {
if (Minecraft.getInstance().options.renderDebug) {
ArrayList<String> debug = event.getRight();
debug.add("");
debug.add("Flywheel: " + Flywheel.getVersion());
InstancedRenderDispatcher.getDebugString(debug);
InstancedRenderDispatcher.addDebugInfo(debug);
debug.add("Memory Usage: CPU: " + StringUtil.formatBytes(FlwMemoryTracker.getCPUMemory()) + ", GPU: " + StringUtil.formatBytes(FlwMemoryTracker.getGPUMemory()));
}
@ -39,5 +38,4 @@ public class ForgeEvents {
.tick();
}
}
}

View file

@ -18,17 +18,6 @@ public final class BackendManagerImpl {
return backend;
}
public static String getBackendNameForCrashReport() {
if (backend == null) {
return "Uninitialized";
}
var backendId = Backend.REGISTRY.getId(backend);
if (backendId == null) {
return "Unregistered";
}
return backendId.toString();
}
public static boolean isOn() {
return backend != null && backend != Backends.OFF;
}
@ -58,6 +47,17 @@ public final class BackendManagerImpl {
return Backends.INDIRECT;
}
public static String getBackendNameForCrashReport() {
if (backend == null) {
return "Uninitialized";
}
var backendId = Backend.REGISTRY.getId(backend);
if (backendId == null) {
return "Unregistered";
}
return backendId.toString();
}
private BackendManagerImpl() {
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.impl.instance;
package com.jozufozu.flywheel.impl;
import org.jetbrains.annotations.Nullable;
@ -12,6 +12,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
//TODO: Add freezing
@SuppressWarnings("unchecked")
public final class InstancingControllerRegistryImpl {
@Nullable

View file

@ -0,0 +1,26 @@
package com.jozufozu.flywheel.impl;
import com.jozufozu.flywheel.api.vertex.VertexListProvider;
import com.jozufozu.flywheel.extension.VertexFormatExtension;
import com.jozufozu.flywheel.impl.vertex.InferredVertexListProviderImpl;
import com.mojang.blaze3d.vertex.VertexFormat;
// TODO: Add freezing
public final class VertexListProviderRegistryImpl {
public static VertexListProvider getProvider(VertexFormat format) {
VertexFormatExtension extension = (VertexFormatExtension) format;
VertexListProvider provider = extension.flywheel$getVertexListProvider();
if (provider == null) {
provider = new InferredVertexListProviderImpl(format);
extension.flywheel$setVertexListProvider(provider);
}
return provider;
}
public static void setProvider(VertexFormat format, VertexListProvider provider) {
((VertexFormatExtension) format).flywheel$setVertexListProvider(provider);
}
private VertexListProviderRegistryImpl() {
}
}

View file

@ -0,0 +1,135 @@
package com.jozufozu.flywheel.impl.instancing;
import java.util.List;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.impl.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.EffectInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.EntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
/**
* A manager class for a single world where instancing is supported.
*/
public class InstanceWorld {
private final Engine engine;
private final TaskExecutor taskExecutor;
private final InstanceManager<BlockEntity> blockEntities;
private final InstanceManager<Entity> entities;
private final InstanceManager<Effect> effects;
public InstanceWorld(LevelAccessor level) {
engine = BackendManager.getBackend().createEngine(level);
taskExecutor = BackendUtil.getTaskExecutor();
blockEntities = new BlockEntityInstanceManager(engine);
entities = new EntityInstanceManager(engine);
effects = new EffectInstanceManager(engine);
}
public Engine getEngine() {
return engine;
}
public InstanceManager<BlockEntity> getBlockEntities() {
return blockEntities;
}
public InstanceManager<Entity> getEntities() {
return entities;
}
public InstanceManager<Effect> getEffects() {
return effects;
}
/**
* Tick the instances after the game has ticked:
* <p>
* Call {@link TickableInstance#tick()} on all instances in this world.
* </p>
*/
public void tick(double cameraX, double cameraY, double cameraZ) {
blockEntities.tick(taskExecutor, cameraX, cameraY, cameraZ);
entities.tick(taskExecutor, cameraX, cameraY, cameraZ);
effects.tick(taskExecutor, cameraX, cameraY, cameraZ);
}
/**
* Get ready to render a frame.
* <p>
* Check and update the render origin.
* <br>
* Call {@link DynamicInstance#beginFrame()} on all instances in this world.
* </p>
*/
public void beginFrame(RenderContext context) {
boolean originChanged = engine.updateRenderOrigin(context.camera());
if (originChanged) {
blockEntities.recreateAll();
entities.recreateAll();
effects.recreateAll();
}
taskExecutor.syncPoint();
if (!originChanged) {
var cameraPos = context.camera()
.getPosition();
double cameraX = cameraPos.x;
double cameraY = cameraPos.y;
double cameraZ = cameraPos.z;
FrustumIntersection culler = context.culler();
blockEntities.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
entities.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
effects.beginFrame(taskExecutor, cameraX, cameraY, cameraZ, culler);
}
engine.beginFrame(taskExecutor, context);
}
/**
* Draw all instances for the given stage.
*/
public void renderStage(RenderContext context, RenderStage stage) {
taskExecutor.syncPoint();
engine.renderStage(taskExecutor, context, stage);
}
public void addDebugInfo(List<String> info) {
info.add("B: " + blockEntities.getInstanceCount()
+ ", E: " + entities.getInstanceCount()
+ ", F: " + effects.getInstanceCount());
info.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
engine.addDebugInfo(info);
}
/**
* Free all acquired resources and invalidate this instance world.
*/
public void delete() {
blockEntities.invalidate();
entities.invalidate();
effects.invalidate();
engine.delete();
}
}

View file

@ -0,0 +1,184 @@
package com.jozufozu.flywheel.impl.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.event.RenderStageEvent;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.extension.ClientLevelExtension;
import com.jozufozu.flywheel.impl.instancing.manager.InstanceManager;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.WorldAttached;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Vec3i;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.event.TickEvent;
public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> INSTANCE_WORLDS = new WorldAttached<>(InstanceWorld::new);
/**
* Call this when you want to run {@link Instance#update()}.
* @param blockEntity The block entity whose instance you want to update.
*/
public static void queueUpdate(BlockEntity blockEntity) {
if (!(blockEntity.getLevel() instanceof ClientLevel level)) {
return;
}
if (!BackendUtil.canUseInstancing(level)) {
return;
}
INSTANCE_WORLDS.get(level)
.getBlockEntities()
.queueUpdate(blockEntity);
}
/**
* Call this when you want to run {@link Instance#update()}.
* @param entity The entity whose instance you want to update.
*/
public static void queueUpdate(Entity entity) {
Level level = entity.level;
if (!BackendUtil.canUseInstancing(level)) {
return;
}
INSTANCE_WORLDS.get(level)
.getEntities()
.queueUpdate(entity);
}
/**
* Call this when you want to run {@link Instance#update()}.
* @param effect The effect whose instance you want to update.
*/
public static void queueUpdate(LevelAccessor level, Effect effect) {
if (!BackendUtil.canUseInstancing(level)) {
return;
}
INSTANCE_WORLDS.get(level)
.getEffects()
.queueUpdate(effect);
}
/**
* Get or create the {@link InstanceWorld} for the given world.
* @throws IllegalStateException if the backend is off
*/
private static InstanceWorld getInstanceWorld(LevelAccessor level) {
if (!BackendUtil.canUseInstancing(level)) {
throw new IllegalStateException("Cannot retrieve instance world when backend is off!");
}
return INSTANCE_WORLDS.get(level);
}
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor level) {
return getInstanceWorld(level).getBlockEntities();
}
public static InstanceManager<Entity> getEntities(LevelAccessor level) {
return getInstanceWorld(level).getEntities();
}
public static InstanceManager<Effect> getEffects(LevelAccessor level) {
return getInstanceWorld(level).getEffects();
}
public static Vec3i getRenderOrigin(LevelAccessor level) {
return getInstanceWorld(level).getEngine().renderOrigin();
}
public static void tick(TickEvent.ClientTickEvent event) {
if (!BackendUtil.isGameActive() || event.phase == TickEvent.Phase.START) {
return;
}
AnimationTickHolder.tick();
Minecraft mc = Minecraft.getInstance();
if (mc.isPaused()) {
return;
}
Entity cameraEntity = mc.getCameraEntity() == null ? mc.player : mc.getCameraEntity();
if (cameraEntity == null) {
return;
}
Level level = cameraEntity.level;
if (!BackendUtil.canUseInstancing(level)) {
return;
}
double cameraX = cameraEntity.getX();
double cameraY = cameraEntity.getEyeY();
double cameraZ = cameraEntity.getZ();
INSTANCE_WORLDS.get(level).tick(cameraX, cameraY, cameraZ);
}
public static void onBeginFrame(BeginFrameEvent event) {
if (!BackendUtil.isGameActive()) {
return;
}
ClientLevel level = event.getContext().level();
if (!BackendUtil.canUseInstancing(level)) {
return;
}
INSTANCE_WORLDS.get(level).beginFrame(event.getContext());
}
public static void onRenderStage(RenderStageEvent event) {
ClientLevel level = event.getContext().level();
if (!BackendUtil.canUseInstancing(level)) {
return;
}
INSTANCE_WORLDS.get(level).renderStage(event.getContext(), event.getStage());
}
public static void onReloadRenderers(ReloadRenderersEvent event) {
ClientLevel level = event.getLevel();
if (level == null) {
return;
}
resetInstanceWorld(level);
}
public static void resetInstanceWorld(ClientLevel level) {
INSTANCE_WORLDS.remove(level, InstanceWorld::delete);
if (!BackendUtil.canUseInstancing(level)) {
return;
}
InstanceWorld world = INSTANCE_WORLDS.get(level);
// Block entities are loaded while chunks are baked.
// Entities are loaded with the level, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(level)
.forEach(world.getEntities()::add);
}
public static void addDebugInfo(List<String> info) {
ClientLevel level = Minecraft.getInstance().level;
if (BackendUtil.canUseInstancing(level)) {
INSTANCE_WORLDS.get(level).addDebugInfo(info);
} else {
info.add("Disabled");
}
}
}

View file

@ -0,0 +1,77 @@
package com.jozufozu.flywheel.impl.instancing;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.controller.BlockEntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.EntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.InstancingControllerRegistry;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public final class InstancingControllerHelper {
@SuppressWarnings("unchecked")
@Nullable
public static <T extends BlockEntity> BlockEntityInstancingController<? super T> getController(T blockEntity) {
return InstancingControllerRegistry.getController((BlockEntityType<? super T>) blockEntity.getType());
}
@SuppressWarnings("unchecked")
@Nullable
public static <T extends Entity> EntityInstancingController<? super T> getController(T entity) {
return InstancingControllerRegistry.getController((EntityType<? super T>) entity.getType());
}
/**
* Checks if the given block entity can be instanced.
* @param type The block entity to check.
* @param <T> The block entity.
* @return {@code true} if the block entity can be instanced.
*/
public static <T extends BlockEntity> boolean canInstance(T blockEntity) {
return getController(blockEntity) != null;
}
/**
* Checks if the given entity can be instanced.
* @param type The entity to check.
* @param <T> The entity.
* @return {@code true} if the entity can be instanced.
*/
public static <T extends Entity> boolean canInstance(T entity) {
return getController(entity) != null;
}
/**
* Checks if the given block entity is instanced and should not be rendered normally.
* @param blockEntity The block entity to check.
* @param <T> The type of the block entity.
* @return {@code true} if the block entity is instanced and should not be rendered normally.
*/
public static <T extends BlockEntity> boolean shouldSkipRender(T blockEntity) {
BlockEntityInstancingController<? super T> controller = getController(blockEntity);
if (controller == null) {
return false;
}
return controller.shouldSkipRender(blockEntity);
}
/**
* Checks if the given entity is instanced and should not be rendered normally.
* @param entity The entity to check.
* @param <T> The type of the entity.
* @return {@code true} if the entity is instanced and should not be rendered normally.
*/
public static <T extends Entity> boolean shouldSkipRender(T entity) {
EntityInstancingController<? super T> controller = getController(entity);
if (controller == null) {
return false;
}
return controller.shouldSkipRender(entity);
}
private InstancingControllerHelper() {
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.instancing.manager;
package com.jozufozu.flywheel.impl.instancing.manager;
import java.util.List;
@ -8,11 +8,10 @@ import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.BlockEntityInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.controller.InstancingControllerRegistry;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.backend.instancing.storage.Storage;
import com.jozufozu.flywheel.lib.instance.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@ -46,7 +45,7 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return false;
}
if (!InstancingControllerHelper.canInstance(blockEntity.getType())) {
if (!InstancingControllerHelper.canInstance(blockEntity)) {
return false;
}
@ -81,17 +80,17 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
@Override
@Nullable
protected Instance createRaw(BlockEntity obj) {
var controller = InstancingControllerRegistry.getController(InstancingControllerHelper.getType(obj));
var controller = InstancingControllerHelper.getController(obj);
if (controller == null) {
return null;
}
var out = controller.createInstance(new InstanceContext(engine, engine.renderOrigin()), obj);
var instance = controller.createInstance(new InstanceContext(engine, engine.renderOrigin()), obj);
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), out);
posLookup.put(blockPos.asLong(), instance);
return out;
return instance;
}
@Override

View file

@ -0,0 +1,39 @@
package com.jozufozu.flywheel.impl.instancing.manager;
import java.util.Collection;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.impl.instancing.storage.One2ManyStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
public class EffectInstanceManager extends InstanceManager<Effect> {
private final EffectStorage storage;
public EffectInstanceManager(Engine engine) {
storage = new EffectStorage(engine);
}
@Override
protected Storage<Effect> getStorage() {
return storage;
}
@Override
protected boolean canCreateInstance(Effect obj) {
return true;
}
private static class EffectStorage extends One2ManyStorage<Effect> {
public EffectStorage(Engine engine) {
super(engine);
}
@Override
protected Collection<? extends Instance> createRaw(Effect obj) {
return obj.createInstances(new InstanceContext(engine, engine.renderOrigin()));
}
}
}

View file

@ -1,15 +1,14 @@
package com.jozufozu.flywheel.backend.instancing.manager;
package com.jozufozu.flywheel.impl.instancing.manager;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.controller.InstancingControllerRegistry;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.backend.instancing.storage.Storage;
import com.jozufozu.flywheel.lib.instance.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.storage.One2OneStorage;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
@ -32,7 +31,7 @@ public class EntityInstanceManager extends InstanceManager<Entity> {
return false;
}
if (!InstancingControllerHelper.canInstance(entity.getType())) {
if (!InstancingControllerHelper.canInstance(entity)) {
return false;
}
@ -49,10 +48,11 @@ public class EntityInstanceManager extends InstanceManager<Entity> {
@Override
@Nullable
protected Instance createRaw(Entity obj) {
var controller = InstancingControllerRegistry.getController(InstancingControllerHelper.getType(obj));
var controller = InstancingControllerHelper.getController(obj);
if (controller == null) {
return null;
}
return controller.createInstance(new InstanceContext(engine, engine.renderOrigin()), obj);
}
}

View file

@ -1,7 +1,5 @@
package com.jozufozu.flywheel.backend.instancing.manager;
package com.jozufozu.flywheel.impl.instancing.manager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -9,28 +7,26 @@ import java.util.function.Consumer;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.instancing.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.backend.instancing.ratelimit.DistanceUpdateLimiter;
import com.jozufozu.flywheel.backend.instancing.ratelimit.NonLimiter;
import com.jozufozu.flywheel.backend.instancing.storage.Storage;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.impl.instancing.ratelimit.BandedPrimeLimiter;
import com.jozufozu.flywheel.impl.instancing.ratelimit.DistanceUpdateLimiter;
import com.jozufozu.flywheel.impl.instancing.ratelimit.NonLimiter;
import com.jozufozu.flywheel.impl.instancing.storage.Storage;
public abstract class InstanceManager<T> {
private final Set<T> queuedAdditions = new HashSet<>(64);
private final Set<T> queuedUpdates = new HashSet<>(64);
protected DistanceUpdateLimiter frameLimiter;
protected DistanceUpdateLimiter tickLimiter;
protected DistanceUpdateLimiter frameLimiter;
public InstanceManager() {
frameLimiter = createUpdateLimiter();
tickLimiter = createUpdateLimiter();
frameLimiter = createUpdateLimiter();
}
protected abstract Storage<T> getStorage();
@ -60,22 +56,18 @@ public abstract class InstanceManager<T> {
* @return The object count.
*/
public int getInstanceCount() {
return getStorage().getInstanceCount();
return getStorage().getAllInstances().size();
}
public void add(T obj) {
if (!BackendManager.isOn()) return;
if (canCreateInstance(obj)) {
getStorage().add(obj);
}
}
public void queueAdd(T obj) {
if (!BackendManager.isOn()) {
if (!canCreateInstance(obj)) {
return;
}
getStorage().add(obj);
}
public void queueAdd(T obj) {
if (!canCreateInstance(obj)) {
return;
}
@ -85,38 +77,26 @@ public abstract class InstanceManager<T> {
}
}
public void queueAddAll(Collection<? extends T> objects) {
if (!BackendManager.isOn() || objects.isEmpty()) {
return;
}
synchronized (queuedAdditions) {
queuedAdditions.addAll(objects);
}
}
/**
* Update the instance associated with an object.
*
* <p>
* By default this is the only hook an IInstance has to change its internal state. This is the lowest frequency
* update hook IInstance gets. For more frequent updates, see {@link TickableInstance} and
* By default this is the only hook an {@link Instance} has to change its internal state. This is the lowest frequency
* update hook {@link Instance} gets. For more frequent updates, see {@link TickableInstance} and
* {@link DynamicInstance}.
* </p>
*
* @param obj the object to update.
*/
public void update(T obj) {
if (!BackendManager.isOn()) return;
if (canCreateInstance(obj)) {
getStorage().update(obj);
if (!canCreateInstance(obj)) {
return;
}
getStorage().update(obj);
}
public void queueUpdate(T obj) {
if (!BackendManager.isOn()) return;
if (!canCreateInstance(obj)) {
return;
}
@ -127,12 +107,10 @@ public abstract class InstanceManager<T> {
}
public void remove(T obj) {
if (!BackendManager.isOn()) return;
getStorage().remove(obj);
}
public void onOriginShift() {
public void recreateAll() {
getStorage().recreateAll();
}
@ -140,21 +118,15 @@ public abstract class InstanceManager<T> {
getStorage().invalidate();
}
public void delete() {
for (Instance instance : getStorage().getAllInstances()) {
instance.delete();
}
}
protected void processQueuedAdditions() {
if (queuedAdditions.isEmpty()) {
return;
}
ArrayList<T> queued;
List<T> queued;
synchronized (queuedAdditions) {
queued = new ArrayList<>(queuedAdditions);
queued = List.copyOf(queuedAdditions);
queuedAdditions.clear();
}
@ -164,14 +136,18 @@ public abstract class InstanceManager<T> {
}
protected void processQueuedUpdates() {
ArrayList<T> queued;
if (queuedUpdates.isEmpty()) {
return;
}
List<T> queued;
synchronized (queuedUpdates) {
queued = new ArrayList<>(queuedUpdates);
queued = List.copyOf(queuedUpdates);
queuedUpdates.clear();
}
if (queued.size() > 0) {
if (!queued.isEmpty()) {
queued.forEach(getStorage()::update);
}
}
@ -187,40 +163,34 @@ public abstract class InstanceManager<T> {
*/
public void tick(TaskExecutor executor, double cameraX, double cameraY, double cameraZ) {
tickLimiter.tick();
processQueuedAdditions();
processQueuedUpdates();
var instances = getStorage().getInstancesForTicking();
var instances = getStorage().getTickableInstances();
distributeWork(executor, instances, instance -> tickInstance(instance, cameraX, cameraY, cameraZ));
}
protected void tickInstance(TickableInstance instance, double cX, double cY, double cZ) {
if (!instance.decreaseTickRateWithDistance()) {
protected void tickInstance(TickableInstance instance, double cameraX, double cameraY, double cameraZ) {
if (!instance.decreaseTickRateWithDistance() || tickLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
instance.tick();
return;
}
var dsq = instance.distanceSquared(cX, cY, cZ);
if (!tickLimiter.shouldUpdate(dsq)) {
return;
}
instance.tick();
}
public void beginFrame(TaskExecutor executor, RenderContext context) {
public void beginFrame(TaskExecutor executor, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
frameLimiter.tick();
processQueuedAdditions();
processQueuedUpdates();
var cameraPos = context.camera()
.getPosition();
double cX = cameraPos.x;
double cY = cameraPos.y;
double cZ = cameraPos.z;
FrustumIntersection culler = context.culler();
var instances = getStorage().getDynamicInstances();
distributeWork(executor, instances, instance -> updateInstance(instance, cameraX, cameraY, cameraZ, frustum));
}
var instances = getStorage().getInstancesForUpdate();
distributeWork(executor, instances, instance -> updateInstance(instance, culler, cX, cY, cZ));
protected void updateInstance(DynamicInstance instance, double cameraX, double cameraY, double cameraZ, FrustumIntersection frustum) {
if (!instance.decreaseFramerateWithDistance() || frameLimiter.shouldUpdate(instance.distanceSquared(cameraX, cameraY, cameraZ))) {
if (instance.isVisible(frustum)) {
instance.beginFrame();
}
}
}
private static <I> void distributeWork(TaskExecutor executor, List<I> instances, Consumer<I> action) {
@ -239,19 +209,4 @@ public abstract class InstanceManager<T> {
}
}
}
protected void updateInstance(DynamicInstance instance, FrustumIntersection frustum, double cX, double cY, double cZ) {
if (!instance.decreaseFramerateWithDistance()) {
instance.beginFrame();
return;
}
if (!frameLimiter.shouldUpdate(instance.distanceSquared(cX, cY, cZ))) {
return;
}
if (instance.checkFrustum(frustum)) {
instance.beginFrame();
}
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.instancing.ratelimit;
package com.jozufozu.flywheel.impl.instancing.ratelimit;
import net.minecraft.util.Mth;

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.instancing.ratelimit;
package com.jozufozu.flywheel.impl.instancing.ratelimit;
/**
* Interface for rate-limiting updates based on an object's distance from the camera.

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.instancing.ratelimit;
package com.jozufozu.flywheel.impl.instancing.ratelimit;
public class NonLimiter implements DistanceUpdateLimiter {
@Override

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.instancing.storage;
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.ArrayList;
import java.util.List;
@ -18,12 +18,12 @@ public abstract class AbstractStorage<T> implements Storage<T> {
}
@Override
public List<TickableInstance> getInstancesForTicking() {
public List<TickableInstance> getTickableInstances() {
return tickableInstances;
}
@Override
public List<DynamicInstance> getInstancesForUpdate() {
public List<DynamicInstance> getDynamicInstances() {
return dynamicInstances;
}

View file

@ -0,0 +1,86 @@
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
public abstract class One2ManyStorage<T> extends AbstractStorage<T> {
private final Multimap<T, Instance> allInstances = HashMultimap.create();
public One2ManyStorage(Engine engine) {
super(engine);
}
@Override
public Collection<Instance> getAllInstances() {
return allInstances.values();
}
@Override
public void add(T obj) {
Collection<Instance> instances = allInstances.get(obj);
if (instances.isEmpty()) {
create(obj);
}
}
@Override
public void remove(T obj) {
Collection<Instance> instances = allInstances.removeAll(obj);
if (instances.isEmpty()) {
return;
}
tickableInstances.removeAll(instances);
dynamicInstances.removeAll(instances);
instances.forEach(Instance::delete);
}
@Override
public void update(T obj) {
Collection<Instance> instances = allInstances.get(obj);
if (instances.isEmpty()) {
return;
}
// TODO: shouldReset cannot be checked here because all instances are created at once
instances.forEach(Instance::update);
}
@Override
public void recreateAll() {
tickableInstances.clear();
dynamicInstances.clear();
allInstances.values().forEach(Instance::delete);
List<T> objects = List.copyOf(allInstances.keySet());
allInstances.clear();
objects.forEach(this::create);
}
@Override
public void invalidate() {
tickableInstances.clear();
dynamicInstances.clear();
allInstances.values().forEach(Instance::delete);
allInstances.clear();
}
private void create(T obj) {
Collection<? extends Instance> instances = createRaw(obj);
if (!instances.isEmpty()) {
instances.forEach(this::setup);
allInstances.putAll(obj, instances);
}
}
protected abstract Collection<? extends Instance> createRaw(T obj);
}

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.storage;
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@ -9,23 +10,17 @@ import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.instance.Instance;
public abstract class One2OneStorage<T> extends AbstractStorage<T> {
private final Map<T, Instance> instances;
private final Map<T, Instance> instances = new HashMap<>();
public One2OneStorage(Engine engine) {
super(engine);
this.instances = new HashMap<>();
}
@Override
public Iterable<Instance> getAllInstances() {
public Collection<Instance> getAllInstances() {
return instances.values();
}
@Override
public int getInstanceCount() {
return instances.size();
}
@Override
public void add(T obj) {
Instance instance = instances.get(obj);
@ -43,9 +38,9 @@ public abstract class One2OneStorage<T> extends AbstractStorage<T> {
return;
}
instance.delete();
dynamicInstances.remove(instance);
tickableInstances.remove(instance);
dynamicInstances.remove(instance);
instance.delete();
}
@Override
@ -69,8 +64,8 @@ public abstract class One2OneStorage<T> extends AbstractStorage<T> {
@Override
public void recreateAll() {
dynamicInstances.clear();
tickableInstances.clear();
dynamicInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.delete();
@ -86,10 +81,10 @@ public abstract class One2OneStorage<T> extends AbstractStorage<T> {
@Override
public void invalidate() {
instances.values().forEach(Instance::delete);
instances.clear();
tickableInstances.clear();
dynamicInstances.clear();
instances.values().forEach(Instance::delete);
instances.clear();
}
private void create(T obj) {

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.storage;
package com.jozufozu.flywheel.impl.instancing.storage;
import java.util.Collection;
import java.util.List;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
@ -7,13 +8,11 @@ import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
public interface Storage<T> {
Iterable<Instance> getAllInstances();
Collection<Instance> getAllInstances();
int getInstanceCount();
List<TickableInstance> getTickableInstances();
List<TickableInstance> getInstancesForTicking();
List<DynamicInstance> getInstancesForUpdate();
List<DynamicInstance> getDynamicInstances();
void add(T obj);

View file

@ -16,7 +16,7 @@ import net.minecraft.network.chat.TextComponent;
public class Backends {
public static final Backend OFF = SimpleBackend.builder()
.engineMessage(new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED))
.engineSupplier(() -> {
.engineFactory(level -> {
throw new IllegalStateException("Cannot create engine when backend is off.");
})
.fallback(() -> Backends.OFF)
@ -28,7 +28,7 @@ public class Backends {
*/
public static final Backend BATCHING = SimpleBackend.builder()
.engineMessage(new TextComponent("Using Batching Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(BatchingEngine::new)
.engineFactory(level -> new BatchingEngine())
.fallback(() -> Backends.OFF)
.supported(() -> !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("batching"));
@ -38,7 +38,7 @@ public class Backends {
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineMessage(new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(() -> new InstancingEngine(Contexts.WORLD, 100 * 100))
.engineFactory(level -> new InstancingEngine(Contexts.WORLD, 100))
.fallback(() -> Backends.BATCHING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance()
.instancedArraysSupported())
@ -50,7 +50,7 @@ public class Backends {
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineMessage(new TextComponent("Using Indirect Engine").withStyle(ChatFormatting.GREEN))
.engineSupplier(() -> new IndirectEngine(Contexts.WORLD, 100 * 100))
.engineFactory(level -> new IndirectEngine(100))
.fallback(() -> Backends.INSTANCING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance()
.supportsIndirect())

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.lib.backend;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@ -11,17 +12,18 @@ import com.jozufozu.flywheel.api.pipeline.Pipeline;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor;
public class SimpleBackend implements Backend {
private final Component engineMessage;
private final Supplier<Engine> engineSupplier;
private final Function<LevelAccessor, Engine> engineFactory;
private final Supplier<Backend> fallback;
private final BooleanSupplier isSupported;
private final Pipeline pipelineShader;
public SimpleBackend(Component engineMessage, Supplier<Engine> engineSupplier, Supplier<Backend> fallback, BooleanSupplier isSupported, @Nullable Pipeline pipelineShader) {
public SimpleBackend(Component engineMessage, Function<LevelAccessor, Engine> engineFactory, Supplier<Backend> fallback, BooleanSupplier isSupported, @Nullable Pipeline pipelineShader) {
this.engineMessage = engineMessage;
this.engineSupplier = engineSupplier;
this.engineFactory = engineFactory;
this.fallback = fallback;
this.isSupported = isSupported;
this.pipelineShader = pipelineShader;
@ -37,8 +39,8 @@ public class SimpleBackend implements Backend {
}
@Override
public Engine createEngine() {
return engineSupplier.get();
public Engine createEngine(LevelAccessor level) {
return engineFactory.apply(level);
}
@Override
@ -63,7 +65,7 @@ public class SimpleBackend implements Backend {
public static class Builder {
private Component engineMessage;
private Supplier<Engine> engineSupplier;
private Function<LevelAccessor, Engine> engineFactory;
private Supplier<Backend> fallback;
private BooleanSupplier isSupported;
private Pipeline pipelineShader;
@ -73,8 +75,8 @@ public class SimpleBackend implements Backend {
return this;
}
public Builder engineSupplier(Supplier<Engine> engineSupplier) {
this.engineSupplier = engineSupplier;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
this.engineFactory = engineFactory;
return this;
}
@ -94,7 +96,7 @@ public class SimpleBackend implements Backend {
}
public Backend register(ResourceLocation id) {
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineMessage, engineSupplier, fallback, isSupported, pipelineShader));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineMessage, engineFactory, fallback, isSupported, pipelineShader));
}
}
}

View file

@ -1,16 +1,12 @@
package com.jozufozu.flywheel.lib.instance;
import java.util.ArrayList;
import java.util.List;
import org.joml.FrustumIntersection;
import com.jozufozu.flywheel.api.instance.BlockEntityInstance;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.backend.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.lib.box.ImmutableBox;
import com.jozufozu.flywheel.lib.box.MutableBox;
@ -30,13 +26,12 @@ import net.minecraft.world.level.block.state.BlockState;
* </ul>
* See the interfaces' documentation for more information about each one.
*
* <br> Implementing one or more of these will give a {@link AbstractBlockEntityInstance} access
* <br> Implementing one or more of these will give an {@link AbstractBlockEntityInstance} access
* to more interesting and regular points within a tick or a frame.
*
* @param <T> The type of {@link BlockEntity} your class is an instance of.
*/
public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends AbstractInstance implements BlockEntityInstance<T> {
protected final T blockEntity;
protected final BlockPos pos;
protected final BlockPos instancePos;
@ -50,17 +45,6 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
this.instancePos = pos.subtract(renderOrigin);
}
@Override
public List<InstancedPart> getCrumblingParts() {
var out = new ArrayList<InstancedPart>();
addCrumblingParts(out);
return out;
}
public void addCrumblingParts(List<InstancedPart> data) {
}
/**
* Just before {@link #update()} would be called, {@code shouldReset()} is checked.
* If this function returns {@code true}, then this instance will be {@link #delete removed},
@ -69,10 +53,21 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
*
* @return {@code true} if this instance should be discarded and refreshed.
*/
@Override
public boolean shouldReset() {
return blockEntity.getBlockState() != blockState;
}
@Override
public double distanceSquared(double x, double y, double z) {
return pos.distToCenterSqr(x, y, z);
}
@Override
public ImmutableBox getVolume() {
return MutableBox.from(pos);
}
/**
* In order to accommodate for floating point precision errors at high coordinates,
* {@link BlockEntityInstanceManager}s are allowed to arbitrarily adjust the origin, and
@ -85,18 +80,7 @@ public abstract class AbstractBlockEntityInstance<T extends BlockEntity> extends
return pos.subtract(renderOrigin);
}
@Override
public ImmutableBox getVolume() {
return MutableBox.from(pos);
}
@Override
public boolean checkFrustum(FrustumIntersection frustum) {
public boolean isVisible(FrustumIntersection frustum) {
return frustum.testAab(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
}
@Override
public double distanceSquared(double x, double y, double z) {
return pos.distToCenterSqr(x, y, z);
}
}

View file

@ -6,19 +6,19 @@ import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.EntityInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.backend.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.impl.instancing.manager.BlockEntityInstanceManager;
import com.jozufozu.flywheel.lib.box.ImmutableBox;
import com.jozufozu.flywheel.lib.box.MutableBox;
import com.jozufozu.flywheel.lib.light.TickingLightListener;
import com.mojang.math.Vector3f;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
/**
* The layer between a {@link BlockEntity} and the Flywheel backend.
* The layer between an {@link Entity} and the Flywheel backend.
* *
* <br><br> There are a few additional features that overriding classes can opt in to:
* <ul>
@ -27,13 +27,12 @@ import net.minecraft.world.phys.Vec3;
* </ul>
* See the interfaces' documentation for more information about each one.
*
* <br> Implementing one or more of these will give a {@link AbstractEntityInstance} access
* <br> Implementing one or more of these will give an {@link AbstractEntityInstance} access
* to more interesting and regular points within a tick or a frame.
*
* @param <E> The type of {@link Entity} your class is an instance of.
*/
public abstract class AbstractEntityInstance<E extends Entity> extends AbstractInstance implements EntityInstance<E>, TickingLightListener {
protected final E entity;
protected final MutableBox bounds;
@ -44,7 +43,12 @@ public abstract class AbstractEntityInstance<E extends Entity> extends AbstractI
}
@Override
public MutableBox getVolume() {
public double distanceSquared(double x, double y, double z) {
return entity.distanceToSqr(x, y, z);
}
@Override
public ImmutableBox getVolume() {
return bounds;
}
@ -87,14 +91,8 @@ public abstract class AbstractEntityInstance<E extends Entity> extends AbstractI
return new Vector3f((float) (Mth.lerp(partialTicks, entity.xOld, pos.x) - renderOrigin.getX()), (float) (Mth.lerp(partialTicks, entity.yOld, pos.y) - renderOrigin.getY()), (float) (Mth.lerp(partialTicks, entity.zOld, pos.z) - renderOrigin.getZ()));
}
@Override
public boolean checkFrustum(FrustumIntersection frustum) {
public boolean isVisible(FrustumIntersection frustum) {
AABB aabb = entity.getBoundingBox();
return frustum.testAab((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ, (float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ);
}
@Override
public double distanceSquared(double x, double y, double z) {
return entity.distanceToSqr(x, y, z);
}
}

View file

@ -1,14 +1,13 @@
package com.jozufozu.flywheel.lib.instance;
import java.util.Arrays;
import java.util.stream.Stream;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instancer.FlatLit;
import com.jozufozu.flywheel.api.instancer.InstancerProvider;
import com.jozufozu.flywheel.lib.light.LightListener;
import com.jozufozu.flywheel.lib.light.LightUpdater;
import com.jozufozu.flywheel.lib.struct.FlatLit;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
@ -17,23 +16,22 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.LightLayer;
public abstract class AbstractInstance implements Instance, LightListener {
public final Level level;
public final Vec3i renderOrigin;
protected final InstancerProvider instancerProvider;
protected final Vec3i renderOrigin;
protected final Level level;
protected final InstancerProvider instancerManager;
protected boolean deleted = false;
public AbstractInstance(InstanceContext ctx, Level level) {
this.instancerManager = ctx.instancerProvider();
this.instancerProvider = ctx.instancerProvider();
this.renderOrigin = ctx.renderOrigin();
this.level = level;
}
@Override
public void init() {
LightUpdater.get(level).addListener(this);
updateLight();
LightUpdater.get(level)
.addListener(this);
}
@Override
@ -65,31 +63,31 @@ public abstract class AbstractInstance implements Instance, LightListener {
deleted = true;
}
@Override
public boolean isInvalid() {
return deleted;
}
@Override
public void onLightUpdate(LightLayer type, SectionPos pos) {
updateLight();
}
protected void relight(BlockPos pos, FlatLit<?>... models) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), models);
@Override
public boolean isInvalid() {
return deleted;
}
protected <L extends FlatLit<?>> void relight(BlockPos pos, Stream<L> models) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), models);
protected void relight(BlockPos pos, FlatLit<?>... parts) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), parts);
}
protected void relight(int block, int sky, FlatLit<?>... models) {
relight(block, sky, Arrays.stream(models));
protected void relight(int block, int sky, FlatLit<?>... parts) {
for (FlatLit<?> part : parts) {
part.setLight(block, sky);
}
}
protected <L extends FlatLit<?>> void relight(int block, int sky, Stream<L> models) {
models.forEach(model -> model.setBlockLight(block)
.setSkyLight(sky));
protected <L extends FlatLit<?>> void relight(BlockPos pos, Stream<L> parts) {
relight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos), parts);
}
protected <L extends FlatLit<?>> void relight(int block, int sky, Stream<L> parts) {
parts.forEach(model -> model.setLight(block, sky));
}
}

View file

@ -1,85 +0,0 @@
package com.jozufozu.flywheel.lib.instance;
import com.jozufozu.flywheel.api.instance.controller.BlockEntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.EntityInstancingController;
import com.jozufozu.flywheel.api.instance.controller.InstancingControllerRegistry;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
public final class InstancingControllerHelper {
/**
* Checks if the given block entity type can be instanced.
* @param type The block entity type to check.
* @param <T> The type of the block entity.
* @return {@code true} if the block entity type can be instanced.
*/
public static <T extends BlockEntity> boolean canInstance(BlockEntityType<? extends T> type) {
return InstancingControllerRegistry.getController(type) != null;
}
/**
* Checks if the given entity type can be instanced.
* @param type The entity type to check.
* @param <T> The type of the entity.
* @return {@code true} if the entity type can be instanced.
*/
public static <T extends Entity> boolean canInstance(EntityType<? extends T> type) {
return InstancingControllerRegistry.getController(type) != null;
}
/**
* Checks if the given block entity is instanced and should not be rendered normally.
* @param blockEntity The block entity to check.
* @param <T> The type of the block entity.
* @return {@code true} if the block entity is instanced and should not be rendered normally.
*/
public static <T extends BlockEntity> boolean shouldSkipRender(T blockEntity) {
BlockEntityInstancingController<? super T> controller = InstancingControllerRegistry.getController(getType(blockEntity));
if (controller == null) {
return false;
}
return controller.shouldSkipRender(blockEntity);
}
/**
* Checks if the given entity is instanced and should not be rendered normally.
* @param entity The entity to check.
* @param <T> The type of the entity.
* @return {@code true} if the entity is instanced and should not be rendered normally.
*/
public static <T extends Entity> boolean shouldSkipRender(T entity) {
EntityInstancingController<? super T> controller = InstancingControllerRegistry.getController(getType(entity));
if (controller == null) {
return false;
}
return controller.shouldSkipRender(entity);
}
/**
* Gets the type of the given block entity.
* @param blockEntity The block entity to get the type of.
* @param <T> The type of the block entity.
* @return The {@link BlockEntityType} associated with the given block entity.
*/
@SuppressWarnings("unchecked")
public static <T extends BlockEntity> BlockEntityType<? super T> getType(T blockEntity) {
return (BlockEntityType<? super T>) blockEntity.getType();
}
/**
* Gets the type of the given entity.
* @param entity The entity to get the type of.
* @param <T> The type of the entity.
* @return The {@link EntityType} associated with the given entity.
*/
@SuppressWarnings("unchecked")
public static <T extends Entity> EntityType<? super T> getType(T entity) {
return (EntityType<? super T>) entity.getType();
}
private InstancingControllerHelper() {
}
}

View file

@ -13,7 +13,7 @@ import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.vertex.ReusableVertexList;
import com.jozufozu.flywheel.api.vertex.VertexList;
import com.jozufozu.flywheel.api.vertex.VertexListProvider;
import com.jozufozu.flywheel.api.vertex.VertexListProviderRegistry;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.lib.format.Formats;
import com.jozufozu.flywheel.lib.material.Materials;
@ -62,7 +62,7 @@ public class ModelUtil {
long srcPtr = MemoryUtil.memAddress(src);
long dstPtr = dst.ptr();
ReusableVertexList srcList = VertexListProvider.get(srcFormat).createVertexList();
ReusableVertexList srcList = VertexListProviderRegistry.getProvider(srcFormat).createVertexList();
ReusableVertexList dstList = dstVertexType.createVertexList();
srcList.ptr(srcPtr);
dstList.ptr(dstPtr);

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.lib.struct;
import com.jozufozu.flywheel.api.instancer.FlatLit;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;

View file

@ -1,4 +1,6 @@
package com.jozufozu.flywheel.api.instancer;
package com.jozufozu.flywheel.lib.struct;
import com.jozufozu.flywheel.api.instancer.InstancedPart;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
@ -27,9 +29,13 @@ public interface FlatLit<D extends InstancedPart & FlatLit<D>> {
*/
D setSkyLight(int skyLight);
default D setLight(int blockLight, int skyLight) {
return setBlockLight(blockLight)
.setSkyLight(skyLight);
}
default D updateLight(BlockAndTintGetter level, BlockPos pos) {
return setBlockLight(level.getBrightness(LightLayer.BLOCK, pos))
.setSkyLight(level.getBrightness(LightLayer.SKY, pos));
return setLight(level.getBrightness(LightLayer.BLOCK, pos), level.getBrightness(LightLayer.SKY, pos));
}
int getPackedLight();

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.event.BeginFrameEvent;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.util.FlwUtil;
import com.jozufozu.flywheel.util.MatrixUtil;
import com.mojang.blaze3d.systems.RenderSystem;
@ -73,13 +73,13 @@ public class FlwShaderUniforms implements ShaderUniforms {
}
RenderContext context = event.getContext();
Vec3i originCoordinate = InstancedRenderDispatcher.getOriginCoordinate(context.level());
Vec3i renderOrigin = InstancedRenderDispatcher.getRenderOrigin(context.level());
Vec3 camera = context.camera()
.getPosition();
var camX = (float) (camera.x - originCoordinate.getX());
var camY = (float) (camera.y - originCoordinate.getY());
var camZ = (float) (camera.z - originCoordinate.getZ());
var camX = (float) (camera.x - renderOrigin.getX());
var camY = (float) (camera.y - renderOrigin.getY());
var camZ = (float) (camera.z - renderOrigin.getZ());
// don't want to mutate viewProjection
var vp = context.viewProjection()

View file

@ -9,9 +9,9 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.google.common.collect.Lists;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.extension.ClientLevelExtension;
import com.jozufozu.flywheel.lib.instance.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
@ -29,7 +29,7 @@ public abstract class ClientLevelMixin implements ClientLevelExtension {
@Inject(method = "entitiesForRendering", at = @At("RETURN"), cancellable = true)
private void flywheel$filterEntities(CallbackInfoReturnable<Iterable<Entity>> cir) {
if (BackendManager.isOn()) {
if (BackendUtil.canUseInstancing((ClientLevel) (Object) this)) {
Iterable<Entity> entities = cir.getReturnValue();
ArrayList<Entity> filtered = Lists.newArrayList(entities);

View file

@ -8,8 +8,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.lib.instance.InstancingControllerHelper;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.instancing.InstancingControllerHelper;
import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -19,7 +19,7 @@ public class ChunkRebuildHooksMixin {
@Inject(method = "handleBlockEntity", at = @At("HEAD"), cancellable = true)
private <E extends BlockEntity> void flywheel$addAndFilterBEs(ChunkRenderDispatcher.CompiledChunk compiledChunk, Set<BlockEntity> set, E be, CallbackInfo ci) {
if (BackendUtil.canUseInstancing(be.getLevel())) {
if (InstancingControllerHelper.canInstance(be.getType()))
if (InstancingControllerHelper.canInstance(be))
InstancedRenderDispatcher.getBlockEntities(be.getLevel()).queueAdd(be);
if (InstancingControllerHelper.shouldSkipRender(be))

View file

@ -7,8 +7,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -22,10 +22,10 @@ public class InstanceAddMixin {
@Inject(method = "setBlockEntity",
at = @At(value = "INVOKE_ASSIGN", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"))
private void flywheel$onBlockEntityAdded(BlockEntity be, CallbackInfo ci) {
if (level.isClientSide && BackendManager.isOn()) {
InstancedRenderDispatcher.getBlockEntities(this.level)
.add(be);
private void flywheel$onBlockEntityAdded(BlockEntity blockEntity, CallbackInfo ci) {
if (level.isClientSide && BackendUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getBlockEntities(level)
.add(blockEntity);
}
}
}

View file

@ -7,8 +7,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.level.Level;
@ -22,8 +22,8 @@ public class InstanceRemoveMixin {
@Inject(at = @At("TAIL"), method = "setRemoved")
private void flywheel$removeInstance(CallbackInfo ci) {
if (level instanceof ClientLevel && BackendManager.isOn()) {
InstancedRenderDispatcher.getBlockEntities(this.level)
if (level instanceof ClientLevel && BackendUtil.canUseInstancing(level)) {
InstancedRenderDispatcher.getBlockEntities(level)
.remove((BlockEntity) (Object) this);
}
}

View file

@ -6,8 +6,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.api.backend.BackendManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.BackendUtil;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
@ -25,7 +25,7 @@ public class InstanceUpdateMixin {
*/
@Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V")
private void flywheel$checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
if (!BackendManager.isOn()) {
if (!BackendUtil.canUseInstancing(level)) {
return;
}
BlockEntity blockEntity = level.getBlockEntity(pos);

View file

@ -78,14 +78,18 @@ public class WorldAttached<T> {
*/
@NotNull
public T replace(LevelAccessor world, Consumer<T> finalizer) {
T remove = attached.remove(world);
if (remove != null)
finalizer.accept(remove);
remove(world, finalizer);
return get(world);
}
public void remove(LevelAccessor world, Consumer<T> finalizer) {
T removed = attached.remove(world);
if (removed != null)
finalizer.accept(removed);
}
/**
* Deletes all entries after calling a function on them.
*

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.vanilla;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
@ -24,18 +23,22 @@ import net.minecraft.util.Mth;
import net.minecraft.world.level.block.entity.BellBlockEntity;
public class BellInstance extends AbstractBlockEntityInstance<BellBlockEntity> implements DynamicInstance {
private static final SimpleLazyModel MODEL = new SimpleLazyModel(BellInstance::createBellModel, Materials.BELL);
private final OrientedPart bell;
private OrientedPart bell;
private float lastRingTime = Float.NaN;
public BellInstance(InstanceContext ctx, BellBlockEntity blockEntity) {
super(ctx, blockEntity);
}
@Override
public void init() {
bell = createBellInstance().setPivot(0.5f, 0.75f, 0.5f)
.setPosition(getInstancePosition());
super.init();
}
@Override
@ -65,8 +68,8 @@ public class BellInstance extends AbstractBlockEntityInstance<BellBlockEntity> i
}
@Override
public void addCrumblingParts(List<InstancedPart> data) {
Collections.addAll(data, bell);
public List<InstancedPart> getCrumblingParts() {
return List.of(bell);
}
@Override
@ -75,7 +78,7 @@ public class BellInstance extends AbstractBlockEntityInstance<BellBlockEntity> i
}
private OrientedPart createBellInstance() {
return instancerManager.getInstancer(StructTypes.ORIENTED, MODEL, RenderStage.AFTER_BLOCK_ENTITIES)
return instancerProvider.instancer(StructTypes.ORIENTED, MODEL, RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}

View file

@ -1,12 +1,9 @@
package com.jozufozu.flywheel.vanilla;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
@ -36,24 +33,25 @@ import net.minecraft.world.level.block.entity.LidBlockEntity;
import net.minecraft.world.level.block.state.properties.ChestType;
public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends AbstractBlockEntityInstance<T> implements DynamicInstance {
private static final BiFunction<ChestType, TextureAtlasSprite, SimpleLazyModel> LID = Util.memoize((type, mat) -> new SimpleLazyModel(() -> createLidModel(type, mat), Materials.CHEST));
private static final BiFunction<ChestType, TextureAtlasSprite, SimpleLazyModel> BASE = Util.memoize((type, mat) -> new SimpleLazyModel(() -> createBaseModel(type, mat), Materials.CHEST));
private final OrientedPart body;
private final TransformedPart lid;
private OrientedPart body;
private TransformedPart lid;
private final Float2FloatFunction lidProgress;
private final TextureAtlasSprite sprite;
@NotNull
private final ChestType chestType;
private final Quaternion baseRotation;
private Float2FloatFunction lidProgress;
private TextureAtlasSprite sprite;
private ChestType chestType;
private Quaternion baseRotation;
private float lastProgress = Float.NaN;
public ChestInstance(InstanceContext ctx, T blockEntity) {
super(ctx, blockEntity);
}
@Override
public void init() {
Block block = blockState.getBlock();
chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE;
@ -64,7 +62,6 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
lid = lidInstance();
if (block instanceof AbstractChestBlock<?> chestBlock) {
float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot();
baseRotation = Vector3f.YP.rotationDegrees(-horizontalAngle);
@ -78,6 +75,8 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
baseRotation = Quaternion.ONE;
lidProgress = $ -> 0f;
}
super.init();
}
@Override
@ -112,8 +111,8 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
}
@Override
public void addCrumblingParts(List<InstancedPart> data) {
Collections.addAll(data, body, lid);
public List<InstancedPart> getCrumblingParts() {
return List.of(body, lid);
}
@Override
@ -123,12 +122,12 @@ public class ChestInstance<T extends BlockEntity & LidBlockEntity> extends Abstr
}
private OrientedPart baseInstance() {
return instancerManager.getInstancer(StructTypes.ORIENTED, BASE.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
return instancerProvider.instancer(StructTypes.ORIENTED, BASE.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedPart lidInstance() {
return instancerManager.getInstancer(StructTypes.TRANSFORMED, LID.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
return instancerProvider.instancer(StructTypes.TRANSFORMED, LID.apply(chestType, sprite), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}

View file

@ -26,22 +26,26 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class MinecartInstance<T extends AbstractMinecart> extends AbstractEntityInstance<T> implements DynamicInstance, TickableInstance {
private static final SimpleLazyModel MODEL = new SimpleLazyModel(MinecartInstance::getBodyModel, Materials.MINECART);
private final PoseStack stack = new PoseStack();
private final TransformedPart body;
private TransformedPart body;
private TransformedPart contents;
private BlockState blockState;
private boolean active;
public MinecartInstance(InstanceContext ctx, T entity) {
super(ctx, entity);
}
@Override
public void init() {
body = getBody();
blockState = entity.getDisplayBlockState();
contents = getContents();
super.init();
}
@Override
@ -167,12 +171,12 @@ public class MinecartInstance<T extends AbstractMinecart> extends AbstractEntity
return null;
}
return instancerManager.getInstancer(StructTypes.TRANSFORMED, Models.block(blockState), RenderStage.AFTER_ENTITIES)
return instancerProvider.instancer(StructTypes.TRANSFORMED, Models.block(blockState), RenderStage.AFTER_ENTITIES)
.createInstance();
}
private TransformedPart getBody() {
return instancerManager.getInstancer(StructTypes.TRANSFORMED, MODEL, RenderStage.AFTER_ENTITIES)
return instancerProvider.instancer(StructTypes.TRANSFORMED, MODEL, RenderStage.AFTER_ENTITIES)
.createInstance();
}

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.vanilla;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@ -33,17 +32,20 @@ public class ShulkerBoxInstance extends AbstractBlockEntityInstance<ShulkerBoxBl
private static final Function<TextureAtlasSprite, SimpleLazyModel> BASE = Util.memoize(it -> new SimpleLazyModel(() -> makeBaseModel(it), Materials.SHULKER));
private static final Function<TextureAtlasSprite, SimpleLazyModel> LID = Util.memoize(it -> new SimpleLazyModel(() -> makeLidModel(it), Materials.SHULKER));
private final TextureAtlasSprite texture;
private TextureAtlasSprite texture;
private final TransformedPart base;
private final TransformedPart lid;
private TransformedPart base;
private TransformedPart lid;
private final PoseStack stack = new PoseStack();
private float lastProgress = Float.NaN;
public ShulkerBoxInstance(InstanceContext ctx, ShulkerBoxBlockEntity blockEntity) {
super(ctx, blockEntity);
}
@Override
public void init() {
DyeColor color = blockEntity.getColor();
if (color == null) {
texture = Sheets.DEFAULT_SHULKER_TEXTURE_LOCATION.sprite();
@ -67,6 +69,8 @@ public class ShulkerBoxInstance extends AbstractBlockEntityInstance<ShulkerBoxBl
tstack.translateY(0.25);
lid = makeLidInstance().setTransform(stack);
super.init();
}
@Override
@ -91,8 +95,8 @@ public class ShulkerBoxInstance extends AbstractBlockEntityInstance<ShulkerBoxBl
}
@Override
public void addCrumblingParts(List<InstancedPart> data) {
Collections.addAll(data, base, lid);
public List<InstancedPart> getCrumblingParts() {
return List.of(base, lid);
}
@Override
@ -107,12 +111,12 @@ public class ShulkerBoxInstance extends AbstractBlockEntityInstance<ShulkerBoxBl
}
private TransformedPart makeBaseInstance() {
return instancerManager.getInstancer(StructTypes.TRANSFORMED, BASE.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
return instancerProvider.instancer(StructTypes.TRANSFORMED, BASE.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}
private TransformedPart makeLidInstance() {
return instancerManager.getInstancer(StructTypes.TRANSFORMED, LID.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
return instancerProvider.instancer(StructTypes.TRANSFORMED, LID.apply(texture), RenderStage.AFTER_BLOCK_ENTITIES)
.createInstance();
}

View file

@ -11,11 +11,11 @@ import org.joml.Vector3f;
import com.jozufozu.flywheel.api.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.EffectInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.api.instance.controller.InstanceContext;
import com.jozufozu.flywheel.api.instance.effect.Effect;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.impl.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.lib.box.ImmutableBox;
import com.jozufozu.flywheel.lib.box.MutableBox;
import com.jozufozu.flywheel.lib.instance.AbstractInstance;
@ -110,7 +110,7 @@ public class ExampleEffect implements Effect {
}
@Override
public Collection<Instance> createInstances(InstanceContext ctx) {
public Collection<EffectInstance<?>> createInstances(InstanceContext ctx) {
effects.clear();
boids.clear();
for (int i = 0; i < INSTANCE_COUNT; i++) {
@ -238,10 +238,10 @@ public class ExampleEffect implements Effect {
}
}
public class BoidInstance extends AbstractInstance implements DynamicInstance, TickableInstance {
public class BoidInstance extends AbstractInstance implements EffectInstance<ExampleEffect>, DynamicInstance, TickableInstance {
private final Boid self;
TransformedPart instance;
private TransformedPart instance;
public BoidInstance(InstanceContext ctx, Level level, Boid self) {
super(ctx, level);
@ -250,11 +250,13 @@ public class ExampleEffect implements Effect {
@Override
public void init() {
instance = instancerManager.getInstancer(StructTypes.TRANSFORMED, Models.block(Blocks.SHROOMLIGHT.defaultBlockState()), RenderStage.AFTER_PARTICLES)
instance = instancerProvider.instancer(StructTypes.TRANSFORMED, Models.block(Blocks.SHROOMLIGHT.defaultBlockState()), RenderStage.AFTER_PARTICLES)
.createInstance();
instance.setBlockLight(15)
.setSkyLight(15);
super.init();
}
@Override
@ -297,7 +299,7 @@ public class ExampleEffect implements Effect {
}
@Override
public boolean checkFrustum(FrustumIntersection frustum) {
public boolean isVisible(FrustumIntersection frustum) {
return true;
}