mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-13 13:45:01 +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
|
||||
- type: input
|
||||
attributes:
|
||||
label: Fabric API Version
|
||||
description: The version of Fabric API you were using when the bug occured
|
||||
placeholder: 0.44.0+1.18
|
||||
label: Loader Version
|
||||
description: The version of Forge or Fabric you were using when the bug occured
|
||||
placeholder: Forge 39.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
0.6.2:
|
||||
Update to 1.18.2
|
||||
|
||||
0.6.1:
|
||||
Fixes
|
||||
- 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
|
||||
|
||||
# mod version info
|
||||
mod_version = 0.6.2
|
||||
mod_version = 0.6.3
|
||||
mc_update_version = 1.18
|
||||
minecraft_version = 1.18.2
|
||||
loader_version = 0.13.3
|
||||
|
|
|
@ -6,8 +6,9 @@ import org.slf4j.Logger;
|
|||
|
||||
import com.jozufozu.flywheel.api.FlywheelWorld;
|
||||
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.FlwEngine;
|
||||
import com.jozufozu.flywheel.config.BackendType;
|
||||
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
|
@ -19,12 +20,30 @@ import net.minecraft.world.level.LevelAccessor;
|
|||
public class Backend {
|
||||
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();
|
||||
|
||||
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.
|
||||
*/
|
||||
public static String getBackendDescriptor() {
|
||||
return engine == null ? "Uninitialized" : engine.getProperName();
|
||||
return backendType == null ? "Uninitialized" : backendType.getProperName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -41,11 +60,11 @@ public class Backend {
|
|||
}
|
||||
|
||||
public static void refresh() {
|
||||
engine = chooseEngine();
|
||||
backendType = chooseEngine();
|
||||
}
|
||||
|
||||
public static boolean isOn() {
|
||||
return engine != FlwEngine.OFF;
|
||||
return backendType != BackendType.OFF;
|
||||
}
|
||||
|
||||
public static boolean canUseInstancing(@Nullable Level world) {
|
||||
|
@ -73,9 +92,9 @@ public class Backend {
|
|||
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
|
||||
}
|
||||
|
||||
private static FlwEngine chooseEngine() {
|
||||
FlwEngine preferredChoice = FlwConfig.get()
|
||||
.getEngine();
|
||||
private static BackendType chooseEngine() {
|
||||
BackendType preferredChoice = FlwConfig.get()
|
||||
.getBackendType();
|
||||
|
||||
boolean usingShaders = IrisShaderHandler.isShaderPackInUse();
|
||||
boolean canUseEngine = switch (preferredChoice) {
|
||||
|
@ -84,7 +103,7 @@ public class Backend {
|
|||
case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported();
|
||||
};
|
||||
|
||||
return canUseEngine ? preferredChoice : FlwEngine.OFF;
|
||||
return canUseEngine ? preferredChoice : BackendType.OFF;
|
||||
}
|
||||
|
||||
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.entity.EntityInstanceManager;
|
||||
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.shader.WorldProgram;
|
||||
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.multiplayer.ClientLevel;
|
||||
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;
|
||||
|
||||
|
@ -32,35 +30,37 @@ public class InstanceWorld {
|
|||
protected final InstanceManager<Entity> entityInstanceManager;
|
||||
protected final InstanceManager<BlockEntity> blockEntityInstanceManager;
|
||||
|
||||
protected final ParallelTaskEngine taskEngine;
|
||||
public final ParallelTaskEngine taskEngine;
|
||||
|
||||
public InstanceWorld(LevelAccessor levelAccessor) {
|
||||
Level world = (Level) levelAccessor;
|
||||
|
||||
this.taskEngine = new ParallelTaskEngine("Flywheel " + world.dimension().location());
|
||||
this.taskEngine.startWorkers();
|
||||
|
||||
FlwEngine engine = Backend.getEngine();
|
||||
|
||||
switch (engine) {
|
||||
public static InstanceWorld create(LevelAccessor level) {
|
||||
return switch (Backend.getBackendType()) {
|
||||
case INSTANCING -> {
|
||||
InstancingEngine<WorldProgram> manager = InstancingEngine.builder(Contexts.WORLD)
|
||||
.build();
|
||||
|
||||
entityInstanceManager = new EntityInstanceManager(manager);
|
||||
blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
||||
var entityInstanceManager = new EntityInstanceManager(manager);
|
||||
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
||||
|
||||
manager.addListener(entityInstanceManager);
|
||||
manager.addListener(blockEntityInstanceManager);
|
||||
this.engine = manager;
|
||||
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
|
||||
}
|
||||
case BATCHING -> {
|
||||
this.engine = new BatchingEngine();
|
||||
entityInstanceManager = new EntityInstanceManager(this.engine);
|
||||
blockEntityInstanceManager = new BlockEntityInstanceManager(this.engine);
|
||||
var manager = new BatchingEngine();
|
||||
var entityInstanceManager = new EntityInstanceManager(manager);
|
||||
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
|
||||
|
||||
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
|
||||
}
|
||||
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() {
|
||||
|
@ -75,7 +75,6 @@ public class InstanceWorld {
|
|||
* Free all acquired resources and invalidate this instance world.
|
||||
*/
|
||||
public void delete() {
|
||||
taskEngine.stopWorkers();
|
||||
engine.delete();
|
||||
entityInstanceManager.detachLightListeners();
|
||||
blockEntityInstanceManager.detachLightListeners();
|
||||
|
|
|
@ -20,7 +20,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
|||
|
||||
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()}.
|
||||
|
@ -47,18 +47,20 @@ public class InstancedRenderDispatcher {
|
|||
}
|
||||
|
||||
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
|
||||
if (Backend.isOn()) {
|
||||
return instanceWorlds.get(world)
|
||||
.getBlockEntityInstanceManager();
|
||||
} else {
|
||||
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
|
||||
}
|
||||
return getInstanceWorld(world).getBlockEntityInstanceManager();
|
||||
}
|
||||
|
||||
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()) {
|
||||
return instanceWorlds.get(world)
|
||||
.getEntityInstanceManager();
|
||||
return instanceWorlds.get(world);
|
||||
} else {
|
||||
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.concurrent.ConcurrentLinkedDeque;
|
||||
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.Nullable;
|
||||
|
@ -20,9 +23,15 @@ import net.minecraft.util.Mth;
|
|||
public class ParallelTaskEngine implements TaskEngine {
|
||||
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 WaitGroup wg = new WaitGroup();
|
||||
|
||||
private final Deque<Runnable> syncTasks = new ConcurrentLinkedDeque<>();
|
||||
private final Deque<Runnable> jobQueue = new ConcurrentLinkedDeque<>();
|
||||
private final List<Thread> threads = new ArrayList<>();
|
||||
|
||||
|
@ -30,13 +39,15 @@ public class ParallelTaskEngine implements TaskEngine {
|
|||
|
||||
private final int threadCount;
|
||||
|
||||
private final String name;
|
||||
|
||||
public ParallelTaskEngine(String name) {
|
||||
this.name = name;
|
||||
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
|
||||
* running, this method does nothing and exits.
|
||||
|
@ -117,6 +128,10 @@ public class ParallelTaskEngine implements TaskEngine {
|
|||
this.wg.await();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
while ((job = this.syncTasks.pollLast()) != null) {
|
||||
job.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -157,6 +172,7 @@ public class ParallelTaskEngine implements TaskEngine {
|
|||
private static int getMaxThreadCount() {
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
|
||||
private class WorkerRunnable implements Runnable {
|
||||
|
||||
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> {
|
||||
|
||||
private final ModelAllocator modelAllocator;
|
||||
private final BufferLayout instanceFormat;
|
||||
private final Instanced<D> instancedType;
|
||||
|
||||
|
@ -32,9 +31,8 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
|||
|
||||
protected boolean anyToUpdate;
|
||||
|
||||
public GPUInstancer(Instanced<D> type, Model model, ModelAllocator modelAllocator) {
|
||||
public GPUInstancer(Instanced<D> type, Model model) {
|
||||
super(type::create, model);
|
||||
this.modelAllocator = modelAllocator;
|
||||
this.instanceFormat = type.getLayout();
|
||||
instancedType = type;
|
||||
}
|
||||
|
@ -63,7 +61,7 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
|||
return deleted || model == null;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
public void init(ModelAllocator modelAllocator) {
|
||||
if (isInitialized()) return;
|
||||
|
||||
initialized = true;
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.jozufozu.flywheel.api.InstanceData;
|
|||
import com.jozufozu.flywheel.api.Instancer;
|
||||
import com.jozufozu.flywheel.api.Material;
|
||||
import com.jozufozu.flywheel.api.struct.Instanced;
|
||||
import com.jozufozu.flywheel.backend.model.ModelAllocator;
|
||||
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> {
|
||||
|
||||
protected final ModelAllocator allocator;
|
||||
protected final Map<Object, GPUInstancer<D>> models = new HashMap<>();
|
||||
protected final Instanced<D> type;
|
||||
protected final List<GPUInstancer<D>> uninitialized = new ArrayList<>();
|
||||
|
||||
public InstancedMaterial(Instanced<D> type, ModelAllocator allocator) {
|
||||
public InstancedMaterial(Instanced<D> type) {
|
||||
this.type = type;
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +37,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
|||
@Override
|
||||
public Instancer<D> model(Object key, Supplier<Model> modelSupplier) {
|
||||
return models.computeIfAbsent(key, $ -> {
|
||||
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator);
|
||||
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get());
|
||||
uninitialized.add(instancer);
|
||||
return instancer;
|
||||
});
|
||||
|
|
|
@ -32,27 +32,21 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
|||
protected final RenderType type;
|
||||
|
||||
private final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
|
||||
private final ModelAllocator allocator;
|
||||
|
||||
private ModelAllocator allocator;
|
||||
private int vertexCount;
|
||||
private int instanceCount;
|
||||
|
||||
public InstancedMaterialGroup(InstancingEngine<P> owner, RenderType type) {
|
||||
this.owner = owner;
|
||||
this.type = type;
|
||||
if (GlCompat.getInstance()
|
||||
.onAMDWindows()) {
|
||||
this.allocator = FallbackAllocator.INSTANCE;
|
||||
} else {
|
||||
this.allocator = new ModelPool(Formats.POS_TEX_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <D extends InstanceData> InstancedMaterial<D> material(StructType<D> type) {
|
||||
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 {
|
||||
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) {
|
||||
// initialize all uninitialized instancers...
|
||||
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();
|
||||
}
|
||||
initializeInstancers();
|
||||
|
||||
vertexCount = 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();
|
||||
}
|
||||
|
||||
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) {
|
||||
ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel");
|
||||
|
||||
commandBuilder.addOption(config.engine, "backend", (builder, option) -> enumOptionCommand(builder, config, option,
|
||||
FlwEngine::getShortName,
|
||||
commandBuilder.addOption(config.backend, (builder, option) -> enumOptionCommand(builder, config, option,
|
||||
BackendType::getShortName,
|
||||
(source, 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);
|
||||
}
|
||||
|
||||
public static Component getEngineMessage(@NotNull FlwEngine type) {
|
||||
public static Component getEngineMessage(@NotNull BackendType type) {
|
||||
return switch (type) {
|
||||
case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED);
|
||||
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 Map<String, Option<?>> optionMapView = Collections.unmodifiableMap(optionMap);
|
||||
|
||||
/** Enable or disable the entire engine */
|
||||
public final EnumOption<FlwEngine> engine = addOption(new EnumOption<>("engine", FlwEngine.INSTANCING));
|
||||
/** Enable or disable a debug overlay that colors pixels by their normal */
|
||||
/** Select the backend to use. */
|
||||
public final EnumOption<BackendType> backend = addOption(new EnumOption<>("engine", BackendType.INSTANCING));
|
||||
/** Enable or disable a debug overlay that colors pixels by their normal. */
|
||||
public final BooleanOption debugNormals = addOption(new BooleanOption("debugNormals", false));
|
||||
/** Enable or disable instance update limiting with distance. */
|
||||
public final BooleanOption limitUpdates = addOption(new BooleanOption("limitUpdates", true));
|
||||
|
@ -54,8 +54,8 @@ public class FlwConfig {
|
|||
FlwCommands.init(INSTANCE);
|
||||
}
|
||||
|
||||
public FlwEngine getEngine() {
|
||||
return engine.get();
|
||||
public BackendType getBackendType() {
|
||||
return backend.get();
|
||||
}
|
||||
|
||||
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
|
||||
public void setup(P p) {
|
||||
protected void setup(P p) {
|
||||
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 (box.contains(newSampleVolume)) {
|
||||
if (newSampleVolume.intersects(sampleVolume)) {
|
||||
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
|
||||
sampleVolume.assign(newSampleVolume);
|
||||
|
||||
copyLight(world, newArea);
|
||||
} else {
|
||||
sampleVolume.assign(newSampleVolume);
|
||||
initialize(world);
|
||||
}
|
||||
sampleVolume.assign(newSampleVolume);
|
||||
initialize(world);
|
||||
} else {
|
||||
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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
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.WorldAttached;
|
||||
import com.jozufozu.flywheel.util.box.GridAlignedBB;
|
||||
import com.jozufozu.flywheel.util.box.ImmutableBox;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
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.
|
||||
*
|
||||
* @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
|
||||
* {@link LightUpdated} for LightUpdater to work with them.
|
||||
*/
|
||||
public class LightUpdater {
|
||||
|
||||
private static final Map<BlockAndTintGetter, LightUpdater> light = new HashMap<>();
|
||||
public static LightUpdater get(BlockAndTintGetter world) {
|
||||
return light.computeIfAbsent(world, LightUpdater::new);
|
||||
private static final WorldAttached<LightUpdater> LEVELS = new WorldAttached<>(LightUpdater::new);
|
||||
private final ParallelTaskEngine taskEngine;
|
||||
|
||||
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;
|
||||
|
@ -31,7 +45,8 @@ public class LightUpdater {
|
|||
private final WeakContainmentMultiMap<LightListener> sections = new WeakContainmentMultiMap<>();
|
||||
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
|
||||
|
||||
public LightUpdater(BlockAndTintGetter world) {
|
||||
public LightUpdater(LevelAccessor world) {
|
||||
taskEngine = Backend.getTaskEngine();
|
||||
provider = new BasicProvider(world);
|
||||
}
|
||||
|
||||
|
@ -40,13 +55,31 @@ public class LightUpdater {
|
|||
}
|
||||
|
||||
public void tick() {
|
||||
for (MovingListener listener : movingListeners) {
|
||||
if (listener.update(provider)) {
|
||||
addListener(listener);
|
||||
tickSerial();
|
||||
//tickParallel();
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -28,7 +28,7 @@ public class WorldAttached<T> {
|
|||
}
|
||||
|
||||
public static void invalidateWorld(LevelAccessor world) {
|
||||
Iterator<WeakReference<Map<LevelAccessor, ?>>> i = allMaps.iterator();
|
||||
var i = allMaps.iterator();
|
||||
while (i.hasNext()) {
|
||||
Map<LevelAccessor, ?> map = i.next()
|
||||
.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();
|
||||
}
|
||||
|
||||
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) {
|
||||
return getMinX() == Math.floor(other.minX)
|
||||
&& getMinY() == Math.floor(other.minY)
|
||||
|
|
Loading…
Reference in a new issue