mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-15 14:45:00 +01:00
Merge remote-tracking branch 'origin/1.18/dev' into 1.18/fabric/dev
Conflicts: .github/ISSUE_TEMPLATE/bug_report.yml build.gradle src/main/java/com/jozufozu/flywheel/Flywheel.java src/main/java/com/jozufozu/flywheel/config/FlwCommands.java src/main/java/com/jozufozu/flywheel/config/FlwConfig.java
This commit is contained in:
commit
e0aa5dd7ce
22 changed files with 460 additions and 153 deletions
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -81,9 +81,9 @@ body:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Fabric API Version
|
label: Loader Version
|
||||||
description: The version of Fabric API you were using when the bug occured
|
description: The version of Forge or Fabric you were using when the bug occured
|
||||||
placeholder: 0.44.0+1.18
|
placeholder: Forge 39.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
0.6.2:
|
||||||
|
Update to 1.18.2
|
||||||
|
|
||||||
0.6.1:
|
0.6.1:
|
||||||
Fixes
|
Fixes
|
||||||
- Fix crash when loading block entities for Flywheel to render, most common when exploring the end.
|
- Fix crash when loading block entities for Flywheel to render, most common when exploring the end.
|
||||||
|
|
|
@ -2,7 +2,7 @@ org.gradle.jvmargs = -Xmx3G
|
||||||
org.gradle.daemon = false
|
org.gradle.daemon = false
|
||||||
|
|
||||||
# mod version info
|
# mod version info
|
||||||
mod_version = 0.6.2
|
mod_version = 0.6.3
|
||||||
mc_update_version = 1.18
|
mc_update_version = 1.18
|
||||||
minecraft_version = 1.18.2
|
minecraft_version = 1.18.2
|
||||||
loader_version = 0.13.3
|
loader_version = 0.13.3
|
||||||
|
|
|
@ -6,8 +6,9 @@ import org.slf4j.Logger;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.FlywheelWorld;
|
import com.jozufozu.flywheel.api.FlywheelWorld;
|
||||||
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
||||||
|
import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine;
|
||||||
import com.jozufozu.flywheel.config.FlwConfig;
|
import com.jozufozu.flywheel.config.FlwConfig;
|
||||||
import com.jozufozu.flywheel.config.FlwEngine;
|
import com.jozufozu.flywheel.config.BackendType;
|
||||||
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
|
|
||||||
|
@ -19,12 +20,30 @@ import net.minecraft.world.level.LevelAccessor;
|
||||||
public class Backend {
|
public class Backend {
|
||||||
public static final Logger LOGGER = LogUtils.getLogger();
|
public static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
|
||||||
private static FlwEngine engine;
|
private static BackendType backendType;
|
||||||
|
|
||||||
|
private static ParallelTaskEngine taskEngine;
|
||||||
|
|
||||||
private static final Loader loader = new Loader();
|
private static final Loader loader = new Loader();
|
||||||
|
|
||||||
public static FlwEngine getEngine() {
|
/**
|
||||||
return engine;
|
* Get the current Flywheel backend type.
|
||||||
|
*/
|
||||||
|
public static BackendType getBackendType() {
|
||||||
|
return backendType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a thread pool for running Flywheel related work in parallel.
|
||||||
|
* @return A global Flywheel thread pool.
|
||||||
|
*/
|
||||||
|
public static ParallelTaskEngine getTaskEngine() {
|
||||||
|
if (taskEngine == null) {
|
||||||
|
taskEngine = new ParallelTaskEngine("Flywheel");
|
||||||
|
taskEngine.startWorkers();
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +51,7 @@ public class Backend {
|
||||||
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
|
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
|
||||||
*/
|
*/
|
||||||
public static String getBackendDescriptor() {
|
public static String getBackendDescriptor() {
|
||||||
return engine == null ? "Uninitialized" : engine.getProperName();
|
return backendType == null ? "Uninitialized" : backendType.getProperName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -41,11 +60,11 @@ public class Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void refresh() {
|
public static void refresh() {
|
||||||
engine = chooseEngine();
|
backendType = chooseEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOn() {
|
public static boolean isOn() {
|
||||||
return engine != FlwEngine.OFF;
|
return backendType != BackendType.OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean canUseInstancing(@Nullable Level world) {
|
public static boolean canUseInstancing(@Nullable Level world) {
|
||||||
|
@ -73,9 +92,9 @@ public class Backend {
|
||||||
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
|
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FlwEngine chooseEngine() {
|
private static BackendType chooseEngine() {
|
||||||
FlwEngine preferredChoice = FlwConfig.get()
|
BackendType preferredChoice = FlwConfig.get()
|
||||||
.getEngine();
|
.getBackendType();
|
||||||
|
|
||||||
boolean usingShaders = IrisShaderHandler.isShaderPackInUse();
|
boolean usingShaders = IrisShaderHandler.isShaderPackInUse();
|
||||||
boolean canUseEngine = switch (preferredChoice) {
|
boolean canUseEngine = switch (preferredChoice) {
|
||||||
|
@ -84,7 +103,7 @@ public class Backend {
|
||||||
case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported();
|
case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported();
|
||||||
};
|
};
|
||||||
|
|
||||||
return canUseEngine ? preferredChoice : FlwEngine.OFF;
|
return canUseEngine ? preferredChoice : BackendType.OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine;
|
||||||
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
|
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
|
||||||
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
|
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
|
||||||
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
|
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
|
||||||
import com.jozufozu.flywheel.config.FlwEngine;
|
|
||||||
import com.jozufozu.flywheel.core.Contexts;
|
import com.jozufozu.flywheel.core.Contexts;
|
||||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||||
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
import com.jozufozu.flywheel.event.BeginFrameEvent;
|
||||||
|
@ -17,7 +16,6 @@ import com.jozufozu.flywheel.util.ClientLevelExtension;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.multiplayer.ClientLevel;
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.level.Level;
|
|
||||||
import net.minecraft.world.level.LevelAccessor;
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
@ -32,35 +30,37 @@ public class InstanceWorld {
|
||||||
protected final InstanceManager<Entity> entityInstanceManager;
|
protected final InstanceManager<Entity> entityInstanceManager;
|
||||||
protected final InstanceManager<BlockEntity> blockEntityInstanceManager;
|
protected final InstanceManager<BlockEntity> blockEntityInstanceManager;
|
||||||
|
|
||||||
protected final ParallelTaskEngine taskEngine;
|
public final ParallelTaskEngine taskEngine;
|
||||||
|
|
||||||
public InstanceWorld(LevelAccessor levelAccessor) {
|
public static InstanceWorld create(LevelAccessor level) {
|
||||||
Level world = (Level) levelAccessor;
|
return switch (Backend.getBackendType()) {
|
||||||
|
|
||||||
this.taskEngine = new ParallelTaskEngine("Flywheel " + world.dimension().location());
|
|
||||||
this.taskEngine.startWorkers();
|
|
||||||
|
|
||||||
FlwEngine engine = Backend.getEngine();
|
|
||||||
|
|
||||||
switch (engine) {
|
|
||||||
case INSTANCING -> {
|
case INSTANCING -> {
|
||||||
InstancingEngine<WorldProgram> manager = InstancingEngine.builder(Contexts.WORLD)
|
InstancingEngine<WorldProgram> manager = InstancingEngine.builder(Contexts.WORLD)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
entityInstanceManager = new EntityInstanceManager(manager);
|
var entityInstanceManager = new EntityInstanceManager(manager);
|
||||||
blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
||||||
|
|
||||||
manager.addListener(entityInstanceManager);
|
manager.addListener(entityInstanceManager);
|
||||||
manager.addListener(blockEntityInstanceManager);
|
manager.addListener(blockEntityInstanceManager);
|
||||||
this.engine = manager;
|
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
|
||||||
}
|
}
|
||||||
case BATCHING -> {
|
case BATCHING -> {
|
||||||
this.engine = new BatchingEngine();
|
var manager = new BatchingEngine();
|
||||||
entityInstanceManager = new EntityInstanceManager(this.engine);
|
var entityInstanceManager = new EntityInstanceManager(manager);
|
||||||
blockEntityInstanceManager = new BlockEntityInstanceManager(this.engine);
|
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
||||||
|
|
||||||
|
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
|
||||||
}
|
}
|
||||||
default -> throw new IllegalArgumentException("Unknown engine type");
|
default -> throw new IllegalArgumentException("Unknown engine type");
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstanceWorld(Engine engine, InstanceManager<Entity> entityInstanceManager, InstanceManager<BlockEntity> blockEntityInstanceManager) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.entityInstanceManager = entityInstanceManager;
|
||||||
|
this.blockEntityInstanceManager = blockEntityInstanceManager;
|
||||||
|
this.taskEngine = Backend.getTaskEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public InstanceManager<Entity> getEntityInstanceManager() {
|
public InstanceManager<Entity> getEntityInstanceManager() {
|
||||||
|
@ -75,7 +75,6 @@ public class InstanceWorld {
|
||||||
* Free all acquired resources and invalidate this instance world.
|
* Free all acquired resources and invalidate this instance world.
|
||||||
*/
|
*/
|
||||||
public void delete() {
|
public void delete() {
|
||||||
taskEngine.stopWorkers();
|
|
||||||
engine.delete();
|
engine.delete();
|
||||||
entityInstanceManager.detachLightListeners();
|
entityInstanceManager.detachLightListeners();
|
||||||
blockEntityInstanceManager.detachLightListeners();
|
blockEntityInstanceManager.detachLightListeners();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
public class InstancedRenderDispatcher {
|
public class InstancedRenderDispatcher {
|
||||||
|
|
||||||
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>(InstanceWorld::new);
|
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>(InstanceWorld::create);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this when you want to manually run {@link AbstractInstance#update()}.
|
* Call this when you want to manually run {@link AbstractInstance#update()}.
|
||||||
|
@ -47,18 +47,20 @@ public class InstancedRenderDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
|
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
|
||||||
if (Backend.isOn()) {
|
return getInstanceWorld(world).getBlockEntityInstanceManager();
|
||||||
return instanceWorlds.get(world)
|
|
||||||
.getBlockEntityInstanceManager();
|
|
||||||
} else {
|
|
||||||
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InstanceManager<Entity> getEntities(LevelAccessor world) {
|
public static InstanceManager<Entity> getEntities(LevelAccessor world) {
|
||||||
|
return getInstanceWorld(world).getEntityInstanceManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create the {@link InstanceWorld} for the given world.
|
||||||
|
* @throws NullPointerException if the backend is off
|
||||||
|
*/
|
||||||
|
public static InstanceWorld getInstanceWorld(LevelAccessor world) {
|
||||||
if (Backend.isOn()) {
|
if (Backend.isOn()) {
|
||||||
return instanceWorlds.get(world)
|
return instanceWorlds.get(world);
|
||||||
.getEntityInstanceManager();
|
|
||||||
} else {
|
} else {
|
||||||
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
|
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
@ -20,9 +23,15 @@ import net.minecraft.util.Mth;
|
||||||
public class ParallelTaskEngine implements TaskEngine {
|
public class ParallelTaskEngine implements TaskEngine {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger("BatchExecutor");
|
private static final Logger LOGGER = LoggerFactory.getLogger("BatchExecutor");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to false, the engine will shut down.
|
||||||
|
*/
|
||||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
private final WaitGroup wg = new WaitGroup();
|
private final WaitGroup wg = new WaitGroup();
|
||||||
|
|
||||||
|
private final Deque<Runnable> syncTasks = new ConcurrentLinkedDeque<>();
|
||||||
private final Deque<Runnable> jobQueue = new ConcurrentLinkedDeque<>();
|
private final Deque<Runnable> jobQueue = new ConcurrentLinkedDeque<>();
|
||||||
private final List<Thread> threads = new ArrayList<>();
|
private final List<Thread> threads = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -30,13 +39,15 @@ public class ParallelTaskEngine implements TaskEngine {
|
||||||
|
|
||||||
private final int threadCount;
|
private final int threadCount;
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public ParallelTaskEngine(String name) {
|
public ParallelTaskEngine(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
threadCount = getOptimalThreadCount();
|
threadCount = getOptimalThreadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WorkGroupBuilder group(String name) {
|
||||||
|
return new WorkGroupBuilder(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawns a number of work-stealing threads to process results in the build queue. If the builder is already
|
* Spawns a number of work-stealing threads to process results in the build queue. If the builder is already
|
||||||
* running, this method does nothing and exits.
|
* running, this method does nothing and exits.
|
||||||
|
@ -117,6 +128,10 @@ public class ParallelTaskEngine implements TaskEngine {
|
||||||
this.wg.await();
|
this.wg.await();
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while ((job = this.syncTasks.pollLast()) != null) {
|
||||||
|
job.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -157,6 +172,7 @@ public class ParallelTaskEngine implements TaskEngine {
|
||||||
private static int getMaxThreadCount() {
|
private static int getMaxThreadCount() {
|
||||||
return Runtime.getRuntime().availableProcessors();
|
return Runtime.getRuntime().availableProcessors();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WorkerRunnable implements Runnable {
|
private class WorkerRunnable implements Runnable {
|
||||||
|
|
||||||
private final AtomicBoolean running = ParallelTaskEngine.this.running;
|
private final AtomicBoolean running = ParallelTaskEngine.this.running;
|
||||||
|
@ -176,4 +192,87 @@ public class ParallelTaskEngine implements TaskEngine {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class WorkGroupBuilder {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Runnable finalizer;
|
||||||
|
|
||||||
|
Stream<Runnable> tasks;
|
||||||
|
|
||||||
|
public WorkGroupBuilder(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> WorkGroupBuilder addTasks(Stream<T> iterable, Consumer<T> consumer) {
|
||||||
|
return addTasks(iterable.map(it -> () -> consumer.accept(it)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkGroupBuilder addTasks(Stream<Runnable> tasks) {
|
||||||
|
if (this.tasks == null) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
} else {
|
||||||
|
this.tasks = Stream.concat(this.tasks, tasks);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkGroupBuilder onComplete(Runnable runnable) {
|
||||||
|
this.finalizer = runnable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submit() {
|
||||||
|
if (this.tasks == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkGroup workGroup = new WorkGroup(name, finalizer);
|
||||||
|
|
||||||
|
tasks.map(task -> new WorkGroupTask(workGroup, task)).forEach(ParallelTaskEngine.this::submit);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class WorkGroupTask implements Runnable {
|
||||||
|
|
||||||
|
private final WorkGroup parent;
|
||||||
|
private final Runnable wrapped;
|
||||||
|
|
||||||
|
public WorkGroupTask(WorkGroup parent, Runnable wrapped) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
|
||||||
|
this.parent.running.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
this.wrapped.run();
|
||||||
|
|
||||||
|
this.parent.oneDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class WorkGroup {
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
final Runnable finalizer;
|
||||||
|
|
||||||
|
final AtomicInteger running = new AtomicInteger(0);
|
||||||
|
|
||||||
|
public WorkGroup(String name, @Nullable Runnable finalizer) {
|
||||||
|
this.name = name;
|
||||||
|
this.finalizer = finalizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void oneDown() {
|
||||||
|
if (running.decrementAndGet() == 0) {
|
||||||
|
if (finalizer != null) {
|
||||||
|
ParallelTaskEngine.this.syncTasks.add(finalizer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import com.jozufozu.flywheel.core.model.Model;
|
||||||
|
|
||||||
public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
||||||
|
|
||||||
private final ModelAllocator modelAllocator;
|
|
||||||
private final BufferLayout instanceFormat;
|
private final BufferLayout instanceFormat;
|
||||||
private final Instanced<D> instancedType;
|
private final Instanced<D> instancedType;
|
||||||
|
|
||||||
|
@ -32,9 +31,8 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
||||||
|
|
||||||
protected boolean anyToUpdate;
|
protected boolean anyToUpdate;
|
||||||
|
|
||||||
public GPUInstancer(Instanced<D> type, Model model, ModelAllocator modelAllocator) {
|
public GPUInstancer(Instanced<D> type, Model model) {
|
||||||
super(type::create, model);
|
super(type::create, model);
|
||||||
this.modelAllocator = modelAllocator;
|
|
||||||
this.instanceFormat = type.getLayout();
|
this.instanceFormat = type.getLayout();
|
||||||
instancedType = type;
|
instancedType = type;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +61,7 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
||||||
return deleted || model == null;
|
return deleted || model == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init(ModelAllocator modelAllocator) {
|
||||||
if (isInitialized()) return;
|
if (isInitialized()) return;
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import com.jozufozu.flywheel.api.InstanceData;
|
||||||
import com.jozufozu.flywheel.api.Instancer;
|
import com.jozufozu.flywheel.api.Instancer;
|
||||||
import com.jozufozu.flywheel.api.Material;
|
import com.jozufozu.flywheel.api.Material;
|
||||||
import com.jozufozu.flywheel.api.struct.Instanced;
|
import com.jozufozu.flywheel.api.struct.Instanced;
|
||||||
import com.jozufozu.flywheel.backend.model.ModelAllocator;
|
|
||||||
import com.jozufozu.flywheel.core.model.Model;
|
import com.jozufozu.flywheel.core.model.Model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,14 +19,12 @@ import com.jozufozu.flywheel.core.model.Model;
|
||||||
*/
|
*/
|
||||||
public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
||||||
|
|
||||||
protected final ModelAllocator allocator;
|
|
||||||
protected final Map<Object, GPUInstancer<D>> models = new HashMap<>();
|
protected final Map<Object, GPUInstancer<D>> models = new HashMap<>();
|
||||||
protected final Instanced<D> type;
|
protected final Instanced<D> type;
|
||||||
protected final List<GPUInstancer<D>> uninitialized = new ArrayList<>();
|
protected final List<GPUInstancer<D>> uninitialized = new ArrayList<>();
|
||||||
|
|
||||||
public InstancedMaterial(Instanced<D> type, ModelAllocator allocator) {
|
public InstancedMaterial(Instanced<D> type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.allocator = allocator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +37,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
||||||
@Override
|
@Override
|
||||||
public Instancer<D> model(Object key, Supplier<Model> modelSupplier) {
|
public Instancer<D> model(Object key, Supplier<Model> modelSupplier) {
|
||||||
return models.computeIfAbsent(key, $ -> {
|
return models.computeIfAbsent(key, $ -> {
|
||||||
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator);
|
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get());
|
||||||
uninitialized.add(instancer);
|
uninitialized.add(instancer);
|
||||||
return instancer;
|
return instancer;
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,27 +32,21 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
||||||
protected final RenderType type;
|
protected final RenderType type;
|
||||||
|
|
||||||
private final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
|
private final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
|
||||||
private final ModelAllocator allocator;
|
|
||||||
|
|
||||||
|
private ModelAllocator allocator;
|
||||||
private int vertexCount;
|
private int vertexCount;
|
||||||
private int instanceCount;
|
private int instanceCount;
|
||||||
|
|
||||||
public InstancedMaterialGroup(InstancingEngine<P> owner, RenderType type) {
|
public InstancedMaterialGroup(InstancingEngine<P> owner, RenderType type) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
if (GlCompat.getInstance()
|
|
||||||
.onAMDWindows()) {
|
|
||||||
this.allocator = FallbackAllocator.INSTANCE;
|
|
||||||
} else {
|
|
||||||
this.allocator = new ModelPool(Formats.POS_TEX_NORMAL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public <D extends InstanceData> InstancedMaterial<D> material(StructType<D> type) {
|
public <D extends InstanceData> InstancedMaterial<D> material(StructType<D> type) {
|
||||||
if (type instanceof Instanced<D> instanced) {
|
if (type instanceof Instanced<D> instanced) {
|
||||||
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, t -> new InstancedMaterial<>(t, allocator));
|
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, InstancedMaterial::new);
|
||||||
} else {
|
} else {
|
||||||
throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing.");
|
throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing.");
|
||||||
}
|
}
|
||||||
|
@ -82,18 +76,7 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
|
protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
|
||||||
// initialize all uninitialized instancers...
|
initializeInstancers();
|
||||||
for (InstancedMaterial<?> material : materials.values()) {
|
|
||||||
for (GPUInstancer<?> instancer : material.uninitialized) {
|
|
||||||
instancer.init();
|
|
||||||
}
|
|
||||||
material.uninitialized.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocator instanceof ModelPool pool) {
|
|
||||||
// ...and then flush the model arena in case anything was marked for upload
|
|
||||||
pool.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexCount = 0;
|
vertexCount = 0;
|
||||||
instanceCount = 0;
|
instanceCount = 0;
|
||||||
|
@ -119,7 +102,24 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setup(P program) {
|
private void initializeInstancers() {
|
||||||
|
ModelAllocator allocator = getModelAllocator();
|
||||||
|
|
||||||
|
// initialize all uninitialized instancers...
|
||||||
|
for (InstancedMaterial<?> material : materials.values()) {
|
||||||
|
for (GPUInstancer<?> instancer : material.uninitialized) {
|
||||||
|
instancer.init(allocator);
|
||||||
|
}
|
||||||
|
material.uninitialized.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocator instanceof ModelPool pool) {
|
||||||
|
// ...and then flush the model arena in case anything was marked for upload
|
||||||
|
pool.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setup(P program) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,4 +133,20 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
||||||
|
|
||||||
materials.clear();
|
materials.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ModelAllocator getModelAllocator() {
|
||||||
|
if (allocator == null) {
|
||||||
|
allocator = createAllocator();
|
||||||
|
}
|
||||||
|
return this.allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelAllocator createAllocator() {
|
||||||
|
if (GlCompat.getInstance()
|
||||||
|
.onAMDWindows()) {
|
||||||
|
return FallbackAllocator.INSTANCE;
|
||||||
|
} else {
|
||||||
|
return new ModelPool(Formats.POS_TEX_NORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/main/java/com/jozufozu/flywheel/config/BackendType.java
Normal file
55
src/main/java/com/jozufozu/flywheel/config/BackendType.java
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package com.jozufozu.flywheel.config;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public enum BackendType {
|
||||||
|
OFF("Off"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a thread pool to buffer instances in parallel on the CPU.
|
||||||
|
*/
|
||||||
|
BATCHING("Parallel Batching"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use GPU instancing to render everything.
|
||||||
|
*/
|
||||||
|
INSTANCING("GL33 Instanced Arrays"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final Map<String, BackendType> lookup;
|
||||||
|
|
||||||
|
static {
|
||||||
|
lookup = new HashMap<>();
|
||||||
|
for (BackendType value : values()) {
|
||||||
|
lookup.put(value.name().toLowerCase(Locale.ROOT), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String properName;
|
||||||
|
|
||||||
|
BackendType(String properName) {
|
||||||
|
this.properName = properName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProperName() {
|
||||||
|
return properName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShortName() {
|
||||||
|
return name().toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static BackendType byName(String name) {
|
||||||
|
return lookup.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<String> validNames() {
|
||||||
|
return lookup.keySet();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.jozufozu.flywheel.config;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.mojang.brigadier.arguments.ArgumentType;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
|
|
||||||
|
import net.minecraft.commands.SharedSuggestionProvider;
|
||||||
|
import net.minecraft.network.chat.TranslatableComponent;
|
||||||
|
|
||||||
|
public enum BackendTypeArgument implements ArgumentType<BackendType> {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private static final Dynamic2CommandExceptionType INVALID = new Dynamic2CommandExceptionType((found, constants) -> {
|
||||||
|
// TODO: don't steal lang
|
||||||
|
return new TranslatableComponent("commands.forge.arguments.enum.invalid", constants, found);
|
||||||
|
});
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BackendType parse(StringReader reader) throws CommandSyntaxException {
|
||||||
|
String string = reader.readUnquotedString();
|
||||||
|
|
||||||
|
BackendType engine = BackendType.byName(string);
|
||||||
|
|
||||||
|
if (engine == null) {
|
||||||
|
throw INVALID.createWithContext(reader, string, BackendType.validNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
|
||||||
|
return SharedSuggestionProvider.suggest(BackendType.validNames(), builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getExamples() {
|
||||||
|
return BackendType.validNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BackendTypeArgument getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,8 +22,8 @@ public final class FlwCommands {
|
||||||
public static void init(FlwConfig config) {
|
public static void init(FlwConfig config) {
|
||||||
ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel");
|
ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel");
|
||||||
|
|
||||||
commandBuilder.addOption(config.engine, "backend", (builder, option) -> enumOptionCommand(builder, config, option,
|
commandBuilder.addOption(config.backend, (builder, option) -> enumOptionCommand(builder, config, option,
|
||||||
FlwEngine::getShortName,
|
BackendType::getShortName,
|
||||||
(source, value) -> {
|
(source, value) -> {
|
||||||
source.sendFeedback(getEngineMessage(value));
|
source.sendFeedback(getEngineMessage(value));
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@ public final class FlwCommands {
|
||||||
return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED);
|
return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Component getEngineMessage(@NotNull FlwEngine type) {
|
public static Component getEngineMessage(@NotNull BackendType type) {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED);
|
case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED);
|
||||||
case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN);
|
case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN);
|
||||||
|
|
|
@ -34,9 +34,9 @@ public class FlwConfig {
|
||||||
protected final Object2ObjectLinkedOpenHashMap<String, Option<?>> optionMap = new Object2ObjectLinkedOpenHashMap<>();
|
protected final Object2ObjectLinkedOpenHashMap<String, Option<?>> optionMap = new Object2ObjectLinkedOpenHashMap<>();
|
||||||
protected final Map<String, Option<?>> optionMapView = Collections.unmodifiableMap(optionMap);
|
protected final Map<String, Option<?>> optionMapView = Collections.unmodifiableMap(optionMap);
|
||||||
|
|
||||||
/** Enable or disable the entire engine */
|
/** Select the backend to use. */
|
||||||
public final EnumOption<FlwEngine> engine = addOption(new EnumOption<>("engine", FlwEngine.INSTANCING));
|
public final EnumOption<BackendType> backend = addOption(new EnumOption<>("engine", BackendType.INSTANCING));
|
||||||
/** Enable or disable a debug overlay that colors pixels by their normal */
|
/** Enable or disable a debug overlay that colors pixels by their normal. */
|
||||||
public final BooleanOption debugNormals = addOption(new BooleanOption("debugNormals", false));
|
public final BooleanOption debugNormals = addOption(new BooleanOption("debugNormals", false));
|
||||||
/** Enable or disable instance update limiting with distance. */
|
/** Enable or disable instance update limiting with distance. */
|
||||||
public final BooleanOption limitUpdates = addOption(new BooleanOption("limitUpdates", true));
|
public final BooleanOption limitUpdates = addOption(new BooleanOption("limitUpdates", true));
|
||||||
|
@ -54,8 +54,8 @@ public class FlwConfig {
|
||||||
FlwCommands.init(INSTANCE);
|
FlwCommands.init(INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FlwEngine getEngine() {
|
public BackendType getBackendType() {
|
||||||
return engine.get();
|
return backend.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean debugNormals() {
|
public boolean debugNormals() {
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package com.jozufozu.flywheel.config;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public enum FlwEngine {
|
|
||||||
OFF("off", "Off"),
|
|
||||||
BATCHING("batching", "Parallel Batching"),
|
|
||||||
INSTANCING("instancing", "GL33 Instanced Arrays"),
|
|
||||||
;
|
|
||||||
|
|
||||||
private static final Map<String, FlwEngine> lookup;
|
|
||||||
|
|
||||||
static {
|
|
||||||
lookup = new HashMap<>();
|
|
||||||
for (FlwEngine value : values()) {
|
|
||||||
lookup.put(value.shortName, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String shortName;
|
|
||||||
private final String properName;
|
|
||||||
|
|
||||||
FlwEngine(String shortName, String properName) {
|
|
||||||
this.shortName = shortName;
|
|
||||||
this.properName = properName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShortName() {
|
|
||||||
return shortName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProperName() {
|
|
||||||
return properName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static FlwEngine byName(String name) {
|
|
||||||
return lookup.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Collection<String> validNames() {
|
|
||||||
return lookup.keySet();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -54,7 +54,7 @@ public class CrumblingGroup<P extends CrumblingProgram> extends InstancedMateria
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setup(P p) {
|
protected void setup(P p) {
|
||||||
p.setAtlasSize(width, height);
|
p.setAtlasSize(width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.util.box.ImmutableBox;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.LightLayer;
|
||||||
|
|
||||||
|
public class DummyLightUpdater extends LightUpdater {
|
||||||
|
public static final DummyLightUpdater INSTANCE = new DummyLightUpdater();
|
||||||
|
|
||||||
|
private DummyLightUpdater() {
|
||||||
|
super(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(LightListener listener) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(LightListener listener) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightUpdate(LightLayer type, long sectionPos) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLightPacket(int chunkX, int chunkZ) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<ImmutableBox> getAllBoxes() {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,15 +114,8 @@ public class GPULightVolume extends LightVolume {
|
||||||
if (lightData == null) return;
|
if (lightData == null) return;
|
||||||
|
|
||||||
if (box.contains(newSampleVolume)) {
|
if (box.contains(newSampleVolume)) {
|
||||||
if (newSampleVolume.intersects(sampleVolume)) {
|
sampleVolume.assign(newSampleVolume);
|
||||||
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
|
initialize(world);
|
||||||
sampleVolume.assign(newSampleVolume);
|
|
||||||
|
|
||||||
copyLight(world, newArea);
|
|
||||||
} else {
|
|
||||||
sampleVolume.assign(newSampleVolume);
|
|
||||||
initialize(world);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
super.move(world, newSampleVolume);
|
super.move(world, newSampleVolume);
|
||||||
}
|
}
|
||||||
|
|
32
src/main/java/com/jozufozu/flywheel/light/LightUpdated.java
Normal file
32
src/main/java/com/jozufozu/flywheel/light/LightUpdated.java
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for custom/fake levels to indicate that LightUpdater should bother interacting with it.<p>
|
||||||
|
*
|
||||||
|
* Implement this if your custom level has light updates at all. If so, be sure to call
|
||||||
|
* {@link com.jozufozu.flywheel.util.WorldAttached#invalidateWorld} when your level in unloaded.
|
||||||
|
*/
|
||||||
|
public interface LightUpdated extends LevelAccessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if this level is passing light updates into LightUpdater.
|
||||||
|
*/
|
||||||
|
default boolean receivesLightUpdates() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean receivesLightUpdates(LevelAccessor level) {
|
||||||
|
// The client level is guaranteed to receive updates.
|
||||||
|
if (Minecraft.getInstance().level == level) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Custom/fake levels need to indicate that LightUpdater has meaning.
|
||||||
|
if (level instanceof LightUpdated c) {
|
||||||
|
return c.receivesLightUpdates();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,42 @@
|
||||||
package com.jozufozu.flywheel.light;
|
package com.jozufozu.flywheel.light;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.Queue;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.jozufozu.flywheel.backend.Backend;
|
||||||
|
import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine;
|
||||||
import com.jozufozu.flywheel.util.WeakHashSet;
|
import com.jozufozu.flywheel.util.WeakHashSet;
|
||||||
|
import com.jozufozu.flywheel.util.WorldAttached;
|
||||||
import com.jozufozu.flywheel.util.box.GridAlignedBB;
|
import com.jozufozu.flywheel.util.box.GridAlignedBB;
|
||||||
import com.jozufozu.flywheel.util.box.ImmutableBox;
|
import com.jozufozu.flywheel.util.box.ImmutableBox;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.SectionPos;
|
import net.minecraft.core.SectionPos;
|
||||||
import net.minecraft.world.level.BlockAndTintGetter;
|
import net.minecraft.world.level.LevelAccessor;
|
||||||
import net.minecraft.world.level.LightLayer;
|
import net.minecraft.world.level.LightLayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
|
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
|
||||||
|
*
|
||||||
|
* @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
|
||||||
|
* {@link LightUpdated} for LightUpdater to work with them.
|
||||||
*/
|
*/
|
||||||
public class LightUpdater {
|
public class LightUpdater {
|
||||||
|
|
||||||
private static final Map<BlockAndTintGetter, LightUpdater> light = new HashMap<>();
|
private static final WorldAttached<LightUpdater> LEVELS = new WorldAttached<>(LightUpdater::new);
|
||||||
public static LightUpdater get(BlockAndTintGetter world) {
|
private final ParallelTaskEngine taskEngine;
|
||||||
return light.computeIfAbsent(world, LightUpdater::new);
|
|
||||||
|
public static LightUpdater get(LevelAccessor level) {
|
||||||
|
if (LightUpdated.receivesLightUpdates(level)) {
|
||||||
|
// The level is valid, add it to the map.
|
||||||
|
return LEVELS.get(level);
|
||||||
|
} else {
|
||||||
|
// Fake light updater for a fake level.
|
||||||
|
return DummyLightUpdater.INSTANCE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final LightProvider provider;
|
private final LightProvider provider;
|
||||||
|
@ -31,7 +45,8 @@ public class LightUpdater {
|
||||||
private final WeakContainmentMultiMap<LightListener> sections = new WeakContainmentMultiMap<>();
|
private final WeakContainmentMultiMap<LightListener> sections = new WeakContainmentMultiMap<>();
|
||||||
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
|
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
|
||||||
|
|
||||||
public LightUpdater(BlockAndTintGetter world) {
|
public LightUpdater(LevelAccessor world) {
|
||||||
|
taskEngine = Backend.getTaskEngine();
|
||||||
provider = new BasicProvider(world);
|
provider = new BasicProvider(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,13 +55,31 @@ public class LightUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick() {
|
public void tick() {
|
||||||
for (MovingListener listener : movingListeners) {
|
tickSerial();
|
||||||
if (listener.update(provider)) {
|
//tickParallel();
|
||||||
addListener(listener);
|
}
|
||||||
|
|
||||||
|
private void tickSerial() {
|
||||||
|
for (MovingListener movingListener : movingListeners) {
|
||||||
|
if (movingListener.update(provider)) {
|
||||||
|
addListener(movingListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void tickParallel() {
|
||||||
|
Queue<LightListener> listeners = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
taskEngine.group("LightUpdater")
|
||||||
|
.addTasks(movingListeners.stream(), listener -> {
|
||||||
|
if (listener.update(provider)) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onComplete(() -> listeners.forEach(this::addListener))
|
||||||
|
.submit();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener.
|
* Add a listener.
|
||||||
*
|
*
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class WorldAttached<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void invalidateWorld(LevelAccessor world) {
|
public static void invalidateWorld(LevelAccessor world) {
|
||||||
Iterator<WeakReference<Map<LevelAccessor, ?>>> i = allMaps.iterator();
|
var i = allMaps.iterator();
|
||||||
while (i.hasNext()) {
|
while (i.hasNext()) {
|
||||||
Map<LevelAccessor, ?> map = i.next()
|
Map<LevelAccessor, ?> map = i.next()
|
||||||
.get();
|
.get();
|
||||||
|
|
|
@ -42,6 +42,15 @@ public interface ImmutableBox {
|
||||||
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
|
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean sameAs(ImmutableBox other, int margin) {
|
||||||
|
return getMinX() == other.getMinX() - margin &&
|
||||||
|
getMinY() == other.getMinY() - margin &&
|
||||||
|
getMinZ() == other.getMinZ() - margin &&
|
||||||
|
getMaxX() == other.getMaxX() + margin &&
|
||||||
|
getMaxY() == other.getMaxY() + margin &&
|
||||||
|
getMaxZ() == other.getMaxZ() + margin;
|
||||||
|
}
|
||||||
|
|
||||||
default boolean sameAs(AABB other) {
|
default boolean sameAs(AABB other) {
|
||||||
return getMinX() == Math.floor(other.minX)
|
return getMinX() == Math.floor(other.minX)
|
||||||
&& getMinY() == Math.floor(other.minY)
|
&& getMinY() == Math.floor(other.minY)
|
||||||
|
|
Loading…
Reference in a new issue