Freestyle instances

- Add Effect instancing
   - Allows mods to create visual effects without (block) entities
   - One effect object maps to many AbstractInstances
   - Effect objects are expected to exist in isolation
 - Refactor InstanceManager to be composable about how its instances are stored
   - EffectInstanceManager uses this to provide a one to many topology
   - This is in need of more iteration, and more aspects of InstanceManager should be made composable in the future
This commit is contained in:
Jozufozu 2022-07-17 16:01:30 -05:00
parent 5657e8669e
commit 1fe8297e72
22 changed files with 683 additions and 242 deletions

View file

@ -28,6 +28,7 @@ import com.jozufozu.flywheel.event.ForgeEvents;
import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor; import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
import com.jozufozu.flywheel.vanilla.VanillaInstances; import com.jozufozu.flywheel.vanilla.VanillaInstances;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import net.minecraft.commands.synchronization.ArgumentTypes; import net.minecraft.commands.synchronization.ArgumentTypes;
@ -108,6 +109,8 @@ public class Flywheel {
modEventBus.addListener(StitchedSprite::onTextureStitchPre); modEventBus.addListener(StitchedSprite::onTextureStitchPre);
modEventBus.addListener(StitchedSprite::onTextureStitchPost); modEventBus.addListener(StitchedSprite::onTextureStitchPost);
// forgeEventBus.addListener(ExampleEffect::spawn);
LayoutShaders.init(); LayoutShaders.init();
InstanceShaders.init(); InstanceShaders.init();
Contexts.init(); Contexts.init();

View file

@ -5,5 +5,7 @@ import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.api.InstancerManager;
public interface Engine extends RenderDispatcher, InstancerManager { public interface Engine extends RenderDispatcher, InstancerManager {
void attachManagers(InstanceManager<?>... listener);
void addDebugInfo(List<String> info); void addDebugInfo(List<String> info);
} }

View file

@ -1,15 +1,9 @@
package com.jozufozu.flywheel.backend.instancing; package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance; import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance; import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
@ -20,37 +14,38 @@ import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.light.LightUpdater; import com.jozufozu.flywheel.light.LightUpdater;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Camera; import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
public abstract class InstanceManager<T> { public abstract class InstanceManager<T> {
public final InstancerManager instancerManager;
private final Set<T> queuedAdditions; private final Set<T> queuedAdditions;
private final Set<T> queuedUpdates; private final Set<T> queuedUpdates;
protected final Map<T, AbstractInstance> instances;
protected final Object2ObjectOpenHashMap<T, TickableInstance> tickableInstances;
protected final Object2ObjectOpenHashMap<T, DynamicInstance> dynamicInstances;
protected DistanceUpdateLimiter frame; protected DistanceUpdateLimiter frame;
protected DistanceUpdateLimiter tick; protected DistanceUpdateLimiter tick;
public InstanceManager(InstancerManager instancerManager) { public InstanceManager() {
this.instancerManager = instancerManager;
this.queuedUpdates = new HashSet<>(64); this.queuedUpdates = new HashSet<>(64);
this.queuedAdditions = new HashSet<>(64); this.queuedAdditions = new HashSet<>(64);
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
frame = createUpdateLimiter(); frame = createUpdateLimiter();
tick = createUpdateLimiter(); tick = createUpdateLimiter();
} }
public abstract Storage<T> getStorage();
/**
* Is the given object currently capable of being instanced?
*
* <p>
* This won't be the case for TEs or entities that are outside of loaded chunks.
* </p>
*
* @return true if the object is currently capable of being instanced.
*/
protected abstract boolean canCreateInstance(T obj);
protected DistanceUpdateLimiter createUpdateLimiter() { protected DistanceUpdateLimiter createUpdateLimiter() {
if (FlwConfig.get().limitUpdates()) { if (FlwConfig.get().limitUpdates()) {
return new BandedPrimeLimiter(); return new BandedPrimeLimiter();
@ -65,30 +60,9 @@ public abstract class InstanceManager<T> {
* @return The object count. * @return The object count.
*/ */
public int getObjectCount() { public int getObjectCount() {
return instances.size(); return getStorage().getObjectCount();
} }
/**
* Is the given object capable of being instanced at all?
*
* @return false if on object cannot be instanced.
*/
protected abstract boolean canInstance(T obj);
/**
* Is the given object currently capable of being instanced?
*
* <p>
* This won't be the case for TEs or entities that are outside of loaded chunks.
* </p>
*
* @return true if the object is currently capable of being instanced.
*/
protected abstract boolean canCreateInstance(T obj);
@Nullable
protected abstract AbstractInstance createRaw(T obj);
/** /**
* Ticks the InstanceManager. * Ticks the InstanceManager.
* *
@ -107,14 +81,14 @@ public abstract class InstanceManager<T> {
int cY = (int) cameraY; int cY = (int) cameraY;
int cZ = (int) cameraZ; int cZ = (int) cameraZ;
ArrayList<TickableInstance> instances = new ArrayList<>(tickableInstances.values()); var instances = getStorage().getInstancesForTicking();
int incr = 500; int incr = 500;
int size = instances.size(); int size = instances.size();
int start = 0; int start = 0;
while (start < size) { while (start < size) {
int end = Math.min(start + incr, size); int end = Math.min(start + incr, size);
List<TickableInstance> sub = instances.subList(start, end); var sub = instances.subList(start, end);
taskEngine.submit(() -> { taskEngine.submit(() -> {
for (TickableInstance instance : sub) { for (TickableInstance instance : sub) {
tickInstance(cX, cY, cZ, instance); tickInstance(cX, cY, cZ, instance);
@ -154,14 +128,14 @@ public abstract class InstanceManager<T> {
int cY = (int) camera.getPosition().y; int cY = (int) camera.getPosition().y;
int cZ = (int) camera.getPosition().z; int cZ = (int) camera.getPosition().z;
ArrayList<DynamicInstance> instances = new ArrayList<>(dynamicInstances.values()); var instances = getStorage().getInstancesForUpdate();
int incr = 500; int incr = 500;
int size = instances.size(); int size = instances.size();
int start = 0; int start = 0;
while (start < size) { while (start < size) {
int end = Math.min(start + incr, size); int end = Math.min(start + incr, size);
List<DynamicInstance> sub = instances.subList(start, end); var sub = instances.subList(start, end);
taskEngine.submit(() -> { taskEngine.submit(() -> {
for (DynamicInstance dyn : sub) { for (DynamicInstance dyn : sub) {
updateInstance(dyn, lookX, lookY, lookZ, cX, cY, cZ); updateInstance(dyn, lookX, lookY, lookZ, cX, cY, cZ);
@ -197,13 +171,19 @@ public abstract class InstanceManager<T> {
public void add(T obj) { public void add(T obj) {
if (!Backend.isOn()) return; if (!Backend.isOn()) return;
if (canInstance(obj)) { if (canCreateInstance(obj)) {
addInternal(obj); getStorage().add(obj);
} }
} }
public void queueAdd(T obj) { public void queueAdd(T obj) {
if (!Backend.isOn()) return; if (!Backend.isOn()) {
return;
}
if (!canCreateInstance(obj)) {
return;
}
synchronized (queuedAdditions) { synchronized (queuedAdditions) {
queuedAdditions.add(obj); queuedAdditions.add(obj);
@ -212,6 +192,11 @@ public abstract class InstanceManager<T> {
public void queueUpdate(T obj) { public void queueUpdate(T obj) {
if (!Backend.isOn()) return; if (!Backend.isOn()) return;
if (!canCreateInstance(obj)) {
return;
}
synchronized (queuedUpdates) { synchronized (queuedUpdates) {
queuedUpdates.add(obj); queuedUpdates.add(obj);
} }
@ -231,45 +216,21 @@ public abstract class InstanceManager<T> {
public void update(T obj) { public void update(T obj) {
if (!Backend.isOn()) return; if (!Backend.isOn()) return;
if (canInstance(obj)) { if (canCreateInstance(obj)) {
AbstractInstance instance = getInstance(obj); getStorage().update(obj);
if (instance != null) {
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
removeInternal(obj, instance);
createInternal(obj);
} else {
instance.update();
}
}
} }
} }
public void remove(T obj) { public void remove(T obj) {
if (!Backend.isOn()) return; if (!Backend.isOn()) return;
if (canInstance(obj)) { if (canCreateInstance(obj)) {
AbstractInstance instance = getInstance(obj); getStorage().remove(obj);
if (instance != null) removeInternal(obj, instance);
} }
} }
public void invalidate() { public void invalidate() {
instances.values().forEach(AbstractInstance::remove); getStorage().invalidate();
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@Nullable
protected <I extends T> AbstractInstance getInstance(I obj) {
if (!Backend.isOn()) return null;
return instances.get(obj);
} }
protected void processQueuedAdditions() { protected void processQueuedAdditions() {
@ -285,7 +246,7 @@ public abstract class InstanceManager<T> {
} }
if (!queued.isEmpty()) { if (!queued.isEmpty()) {
queued.forEach(this::addInternal); queued.forEach(getStorage()::add);
} }
} }
@ -298,75 +259,16 @@ public abstract class InstanceManager<T> {
} }
if (queued.size() > 0) { if (queued.size() > 0) {
queued.forEach(this::update); queued.forEach(getStorage()::update);
}
}
protected void addInternal(T obj) {
if (!Backend.isOn()) return;
AbstractInstance instance = instances.get(obj);
if (instance == null && canCreateInstance(obj)) {
createInternal(obj);
}
}
protected void removeInternal(T obj, AbstractInstance instance) {
instance.remove();
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
LightUpdater.get(instance.level)
.removeListener(instance);
}
@Nullable
protected AbstractInstance createInternal(T obj) {
AbstractInstance renderer = createRaw(obj);
if (renderer != null) {
setup(obj, renderer);
instances.put(obj, renderer);
}
return renderer;
}
private void setup(T obj, AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.put(obj, r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.put(obj, r);
r.beginFrame();
} }
} }
public void onOriginShift() { public void onOriginShift() {
dynamicInstances.clear(); getStorage().recreateAll();
tickableInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.remove();
AbstractInstance out = createRaw(obj);
if (out != null) {
setup(obj, out);
}
return out;
});
} }
public void detachLightListeners() { public void delete() {
for (AbstractInstance value : instances.values()) { for (AbstractInstance value : getStorage().allInstances()) {
LightUpdater.get(value.level).removeListener(value); LightUpdater.get(value.level).removeListener(value);
} }
} }

View file

@ -5,13 +5,15 @@ import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; 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.effect.Effect;
import com.jozufozu.flywheel.backend.instancing.effect.EffectInstanceManager;
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.core.Contexts; import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent; import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.util.ClientLevelExtension; import com.jozufozu.flywheel.util.ClientLevelExtension;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import net.minecraft.client.Camera; import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -29,47 +31,47 @@ import net.minecraft.world.level.block.entity.BlockEntity;
*/ */
public class InstanceWorld { public class InstanceWorld {
protected final Engine engine; protected final Engine engine;
protected final InstanceManager<Entity> entityInstanceManager; protected final InstanceManager<Entity> entities;
protected final InstanceManager<BlockEntity> blockEntityInstanceManager; protected final InstanceManager<BlockEntity> blockEntities;
public final ParallelTaskEngine taskEngine; public final ParallelTaskEngine taskEngine;
private final InstanceManager<Effect> effects;
public static InstanceWorld create(LevelAccessor level) { public static InstanceWorld create(LevelAccessor level) {
return switch (Backend.getBackendType()) { var engine = switch (Backend.getBackendType()) {
case INSTANCING -> { case INSTANCING -> new InstancingEngine<>(Contexts.WORLD);
InstancingEngine<WorldProgram> engine = new InstancingEngine<>(Contexts.WORLD); case BATCHING -> new BatchingEngine();
case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off.");
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
engine.attachManager(entityInstanceManager);
engine.attachManager(blockEntityInstanceManager);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
case BATCHING -> {
var engine = new BatchingEngine();
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
default -> throw new IllegalArgumentException("Unknown engine type");
}; };
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> entityInstanceManager, InstanceManager<BlockEntity> blockEntityInstanceManager) { public InstanceWorld(Engine engine, InstanceManager<Entity> entities, InstanceManager<BlockEntity> blockEntities,
InstanceManager<Effect> effects) {
this.engine = engine; this.engine = engine;
this.entityInstanceManager = entityInstanceManager; this.entities = entities;
this.blockEntityInstanceManager = blockEntityInstanceManager; this.blockEntities = blockEntities;
this.effects = effects;
this.taskEngine = Backend.getTaskEngine(); this.taskEngine = Backend.getTaskEngine();
} }
public InstanceManager<Entity> getEntityInstanceManager() { public InstanceManager<Entity> getEntities() {
return entityInstanceManager; return entities;
} }
public InstanceManager<BlockEntity> getBlockEntityInstanceManager() { public InstanceManager<Effect> getEffects() {
return blockEntityInstanceManager; return effects;
}
public InstanceManager<BlockEntity> getBlockEntities() {
return blockEntities;
} }
/** /**
@ -77,8 +79,8 @@ public class InstanceWorld {
*/ */
public void delete() { public void delete() {
engine.delete(); engine.delete();
entityInstanceManager.detachLightListeners(); entities.delete();
blockEntityInstanceManager.detachLightListeners(); blockEntities.delete();
} }
/** /**
@ -96,8 +98,9 @@ public class InstanceWorld {
taskEngine.syncPoint(); taskEngine.syncPoint();
if (!shifted) { if (!shifted) {
blockEntityInstanceManager.beginFrame(taskEngine, camera); blockEntities.beginFrame(taskEngine, camera);
entityInstanceManager.beginFrame(taskEngine, camera); entities.beginFrame(taskEngine, camera);
effects.beginFrame(taskEngine, camera);
} }
engine.beginFrame(taskEngine, camera); engine.beginFrame(taskEngine, camera);
@ -115,8 +118,13 @@ public class InstanceWorld {
if (renderViewEntity == null) return; if (renderViewEntity == null) return;
blockEntityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ()); double x = renderViewEntity.getX();
entityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ()); double y = renderViewEntity.getY();
double z = renderViewEntity.getZ();
blockEntities.tick(taskEngine, x, y, z);
entities.tick(taskEngine, x, y, z);
effects.tick(taskEngine, x, y, z);
} }
/** /**
@ -148,7 +156,7 @@ public class InstanceWorld {
// Block entities are loaded while chunks are baked. // Block entities are loaded while chunks are baked.
// Entities are loaded with the world, so when chunks are reloaded they need to be re-added. // Entities are loaded with the world, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(world) ClientLevelExtension.getAllLoadedEntities(world)
.forEach(entityInstanceManager::add); .forEach(entities::add);
} }
} }

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.instancing;
import java.util.List; import java.util.List;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.config.FlwCommands; import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
@ -10,6 +11,7 @@ import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.util.AnimationTickHolder; import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.WorldAttached; import com.jozufozu.flywheel.util.WorldAttached;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
@ -30,7 +32,7 @@ public class InstancedRenderDispatcher {
public static void enqueueUpdate(BlockEntity blockEntity) { public static void enqueueUpdate(BlockEntity blockEntity) {
if (Backend.isOn() && blockEntity.hasLevel() && blockEntity.getLevel() instanceof ClientLevel) { if (Backend.isOn() && blockEntity.hasLevel() && blockEntity.getLevel() instanceof ClientLevel) {
instanceWorlds.get(blockEntity.getLevel()) instanceWorlds.get(blockEntity.getLevel())
.getBlockEntityInstanceManager() .getBlockEntities()
.queueUpdate(blockEntity); .queueUpdate(blockEntity);
} }
} }
@ -42,17 +44,21 @@ public class InstancedRenderDispatcher {
public static void enqueueUpdate(Entity entity) { public static void enqueueUpdate(Entity entity) {
if (Backend.isOn()) { if (Backend.isOn()) {
instanceWorlds.get(entity.level) instanceWorlds.get(entity.level)
.getEntityInstanceManager() .getEntities()
.queueUpdate(entity); .queueUpdate(entity);
} }
} }
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) { public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
return getInstanceWorld(world).getBlockEntityInstanceManager(); return getInstanceWorld(world).getBlockEntities();
} }
public static InstanceManager<Entity> getEntities(LevelAccessor world) { public static InstanceManager<Entity> getEntities(LevelAccessor world) {
return getInstanceWorld(world).getEntityInstanceManager(); return getInstanceWorld(world).getEntities();
}
public static InstanceManager<Effect> getEffects(LevelAccessor world) {
return getInstanceWorld(world).getEffects();
} }
/** /**
@ -119,7 +125,7 @@ public class InstancedRenderDispatcher {
InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level); InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level);
debug.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString()); debug.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
debug.add("B: " + instanceWorld.blockEntityInstanceManager.getObjectCount() + ", E: " + instanceWorld.entityInstanceManager.getObjectCount()); debug.add("B: " + instanceWorld.blockEntities.getObjectCount() + ", E: " + instanceWorld.entities.getObjectCount());
instanceWorld.engine.addDebugInfo(debug); instanceWorld.engine.addDebugInfo(debug);
} else { } else {
debug.add("Disabled"); debug.add("Disabled");

View file

@ -0,0 +1,148 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.light.LightUpdater;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
public abstract class One2OneStorage<T> implements Storage<T> {
private final Map<T, AbstractInstance> instances;
private final Object2ObjectOpenHashMap<T, TickableInstance> tickableInstances;
private final Object2ObjectOpenHashMap<T, DynamicInstance> dynamicInstances;
protected final InstancerManager instancerManager;
public One2OneStorage(InstancerManager instancerManager) {
this.instancerManager = instancerManager;
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
}
@Override
public int getObjectCount() {
return instances.size();
}
@Override
public Iterable<AbstractInstance> allInstances() {
return instances.values();
}
@Override
public List<TickableInstance> getInstancesForTicking() {
return new ArrayList<>(tickableInstances.values());
}
@Override
public List<DynamicInstance> getInstancesForUpdate() {
return new ArrayList<>(dynamicInstances.values());
}
@Override
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@Override
public void add(T obj) {
AbstractInstance instance = instances.get(obj);
if (instance == null) {
create(obj);
}
}
@Override
public void remove(T obj) {
var instance = instances.remove(obj);
if (instance == null) {
return;
}
instance.remove();
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
LightUpdater.get(instance.level)
.removeListener(instance);
}
@Override
public void update(T obj) {
AbstractInstance instance = instances.get(obj);
if (instance == null) {
return;
}
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
remove(obj);
create(obj);
} else {
instance.update();
}
}
@Override
public void recreateAll() {
dynamicInstances.clear();
tickableInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.remove();
AbstractInstance out = createRaw(obj);
if (out != null) {
setup(obj, out);
}
return out;
});
}
private void create(T obj) {
AbstractInstance renderer = createRaw(obj);
if (renderer != null) {
setup(obj, renderer);
instances.put(obj, renderer);
}
}
@Nullable
protected abstract AbstractInstance createRaw(T obj);
private void setup(T obj, AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.put(obj, r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.put(obj, r);
r.beginFrame();
}
}
}

View file

@ -0,0 +1,26 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
public interface Storage<T> {
int getObjectCount();
Iterable<AbstractInstance> allInstances();
List<TickableInstance> getInstancesForTicking();
List<DynamicInstance> getInstancesForUpdate();
void invalidate();
void add(T obj);
void remove(T obj);
void update(T obj);
void recreateAll();
}

View file

@ -8,9 +8,7 @@ import java.util.Map;
import com.jozufozu.flywheel.api.InstancedPart; import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.OptifineHandler; import com.jozufozu.flywheel.backend.OptifineHandler;
import com.jozufozu.flywheel.backend.instancing.BatchDrawingTracker; import com.jozufozu.flywheel.backend.instancing.*;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.platform.Lighting;
@ -112,6 +110,11 @@ public class BatchingEngine implements Engine {
submitTasks(stack, taskEngine); submitTasks(stack, taskEngine);
} }
@Override
public void attachManagers(InstanceManager<?>... listener) {
// noop
}
@Override @Override
public void addDebugInfo(List<String> info) { public void addDebugInfo(List<String> info) {
info.add("Batching"); info.add("Batching");

View file

@ -5,8 +5,10 @@ import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.One2OneStorage;
import com.jozufozu.flywheel.backend.instancing.Storage;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@ -17,51 +19,43 @@ import net.minecraft.world.level.block.entity.BlockEntity;
public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> { public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
private final Long2ObjectMap<BlockEntityInstance<?>> posLookup = new Long2ObjectOpenHashMap<>(); private final BlockEntityStorage storage;
public BlockEntityInstanceManager(InstancerManager instancerManager) { public BlockEntityInstanceManager(InstancerManager instancerManager) {
super(instancerManager); storage = new BlockEntityStorage(instancerManager);
}
@Override
public Storage<BlockEntity> getStorage() {
return storage;
} }
public void getCrumblingInstances(long pos, List<BlockEntityInstance<?>> data) { public void getCrumblingInstances(long pos, List<BlockEntityInstance<?>> data) {
BlockEntityInstance<?> instance = posLookup.get(pos); BlockEntityInstance<?> instance = storage.posLookup.get(pos);
if (instance != null) { if (instance != null) {
data.add(instance); data.add(instance);
} }
} }
@Override @Override
protected boolean canInstance(BlockEntity obj) { protected boolean canCreateInstance(BlockEntity blockEntity) {
return obj != null && InstancedRenderRegistry.canInstance(obj.getType()); if (blockEntity.isRemoved()) {
} return false;
@Override
protected AbstractInstance createRaw(BlockEntity obj) {
var instance = InstancedRenderRegistry.createInstance(instancerManager, obj);
if (instance != null) {
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), instance);
} }
return instance; if (!InstancedRenderRegistry.canInstance(blockEntity.getType())) {
} return false;
}
@Override
protected void removeInternal(BlockEntity obj, AbstractInstance instance) {
super.removeInternal(obj, instance);
posLookup.remove(obj.getBlockPos().asLong());
}
@Override
protected boolean canCreateInstance(BlockEntity blockEntity) {
if (blockEntity.isRemoved()) return false;
Level world = blockEntity.getLevel(); Level world = blockEntity.getLevel();
if (world == null) return false; if (world == null) {
return false;
}
if (world.isEmptyBlock(blockEntity.getBlockPos())) return false; if (world.isEmptyBlock(blockEntity.getBlockPos())) {
return false;
}
if (Backend.isFlywheelWorld(world)) { if (Backend.isFlywheelWorld(world)) {
BlockPos pos = blockEntity.getBlockPos(); BlockPos pos = blockEntity.getBlockPos();
@ -73,4 +67,32 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return false; return false;
} }
public static class BlockEntityStorage extends One2OneStorage<BlockEntity> {
final Long2ObjectMap<BlockEntityInstance<?>> posLookup = new Long2ObjectOpenHashMap<>();
public BlockEntityStorage(InstancerManager manager) {
super(manager);
}
@Override
protected AbstractInstance createRaw(BlockEntity obj) {
var instance = InstancedRenderRegistry.createInstance(instancerManager, obj);
if (instance != null) {
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), instance);
}
return instance;
}
@Override
public void remove(BlockEntity obj) {
super.remove(obj);
posLookup.remove(obj.getBlockPos().asLong());
}
}
} }

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.backend.instancing.effect;
import java.util.Collection;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
public interface Effect {
Collection<? extends AbstractInstance> createInstances(InstancerManager instancerManager);
}

View file

@ -0,0 +1,155 @@
package com.jozufozu.flywheel.backend.instancing.effect;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.Storage;
import com.jozufozu.flywheel.light.LightUpdater;
public class EffectInstanceManager extends InstanceManager<Effect> {
private final EffectStorage<Effect> storage;
public EffectInstanceManager(InstancerManager instancerManager) {
storage = new EffectStorage<>(instancerManager);
}
@Override
public Storage<Effect> getStorage() {
return storage;
}
@Override
protected boolean canCreateInstance(Effect obj) {
return true;
}
public static class EffectStorage<T extends Effect> implements Storage<T> {
private final Multimap<T, AbstractInstance> instances;
private final Set<DynamicInstance> dynamicInstances;
private final Set<TickableInstance> tickableInstances;
private final InstancerManager manager;
public EffectStorage(InstancerManager manager) {
this.instances = HashMultimap.create();
this.dynamicInstances = new HashSet<>();
this.tickableInstances = new HashSet<>();
this.manager = manager;
}
@Override
public int getObjectCount() {
return instances.size();
}
@Override
public Iterable<AbstractInstance> allInstances() {
return instances.values();
}
@Override
public List<TickableInstance> getInstancesForTicking() {
return new ArrayList<>(tickableInstances);
}
@Override
public List<DynamicInstance> getInstancesForUpdate() {
return new ArrayList<>(dynamicInstances);
}
@Override
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.clear();
tickableInstances.clear();
dynamicInstances.clear();
}
@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 (AbstractInstance instance : instances) {
LightUpdater.get(instance.level)
.removeListener(instance);
}
}
@Override
public void update(T obj) {
var instances = this.instances.get(obj);
if (instances.isEmpty()) {
return;
}
instances.forEach(AbstractInstance::update);
}
@Override
public void recreateAll() {
this.dynamicInstances.clear();
this.tickableInstances.clear();
this.instances.asMap()
.forEach((obj, instances) -> {
instances.forEach(AbstractInstance::remove);
instances.clear();
var newInstances = obj.createInstances(manager);
newInstances.forEach(this::setup);
instances.addAll(newInstances);
});
}
private void create(T obj) {
var instances = obj.createInstances(manager);
this.instances.putAll(obj, instances);
instances.forEach(this::setup);
}
private void setup(AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.add(r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.add(r);
r.beginFrame();
}
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.effect;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -1,10 +1,13 @@
package com.jozufozu.flywheel.backend.instancing.entity; package com.jozufozu.flywheel.backend.instancing.entity;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager; import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance; import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.One2OneStorage;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
@ -13,23 +16,31 @@ import net.minecraft.world.level.Level;
public class EntityInstanceManager extends InstanceManager<Entity> { public class EntityInstanceManager extends InstanceManager<Entity> {
private final One2OneStorage<Entity> storage;
public EntityInstanceManager(InstancerManager instancerManager) { public EntityInstanceManager(InstancerManager instancerManager) {
super(instancerManager); storage = new One2OneStorage<>(instancerManager) {
@Override
protected @Nullable AbstractInstance createRaw(Entity obj) {
return InstancedRenderRegistry.createInstance(this.instancerManager, obj);
}
};
} }
@Override @Override
protected boolean canInstance(Entity obj) { public One2OneStorage<Entity> getStorage() {
return obj != null && InstancedRenderRegistry.canInstance(obj.getType()); return storage;
}
@Override
protected AbstractInstance createRaw(Entity obj) {
return InstancedRenderRegistry.createInstance(instancerManager, obj);
} }
@Override @Override
protected boolean canCreateInstance(Entity entity) { protected boolean canCreateInstance(Entity entity) {
if (!entity.isAlive()) return false; if (!entity.isAlive()) {
return false;
}
if (!InstancedRenderRegistry.canInstance(entity.getType())) {
return false;
}
Level world = entity.level; Level world = entity.level;

View file

@ -19,8 +19,8 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlVertexArray; import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.TaskEngine; import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance; import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance;
@ -202,8 +202,9 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
return originCoordinate; return originCoordinate;
} }
public void attachManager(InstanceManager<?> listener) { @Override
instanceManagers.add(listener); public void attachManagers(InstanceManager<?>... listener) {
instanceManagers.addAll(List.of(listener));
} }
@Override @Override
@ -350,7 +351,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
} }
if (!(InstancedRenderDispatcher.getInstanceWorld(level) if (!(InstancedRenderDispatcher.getInstanceWorld(level)
.getBlockEntityInstanceManager() instanceof BlockEntityInstanceManager beim)) { .getBlockEntities() instanceof BlockEntityInstanceManager beim)) {
return Int2ObjectMaps.emptyMap(); return Int2ObjectMaps.emptyMap();
} }

View file

@ -144,7 +144,7 @@ public class CrumblingRenderer {
private State() { private State() {
instancerManager = new CrumblingEngine(); instancerManager = new CrumblingEngine();
instanceManager = new CrumblingInstanceManager(instancerManager); instanceManager = new CrumblingInstanceManager(instancerManager);
instancerManager.attachManager(instanceManager); instancerManager.attachManagers(instanceManager);
} }
private void kill() { private void kill() {

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.core.source.parse.AbstractShaderElement;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderField extends AbstractShaderElement { public class ShaderField extends AbstractShaderElement {
public static final Pattern PATTERN = Pattern.compile("layout\\s+\\(location\\s+=\\s+(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public final Span location; public final Span location;
public final @Nullable Decoration decoration; public final @Nullable Decoration decoration;

View file

@ -12,6 +12,7 @@ import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
@Mixin(LevelRenderer.class) @Mixin(LevelRenderer.class)
@ -24,9 +25,16 @@ public class LevelRendererInstanceUpdateMixin {
*/ */
@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") @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 checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) { private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
if (Backend.isOn()) { if (!Backend.isOn()) {
InstancedRenderDispatcher.getBlockEntities(level) return;
.update(level.getBlockEntity(pos));
} }
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity == null) {
return;
}
InstancedRenderDispatcher.getBlockEntities(level)
.update(blockEntity);
} }
} }

View file

@ -0,0 +1,117 @@
package com.jozufozu.flywheel.vanilla.effect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.core.Models;
import com.jozufozu.flywheel.core.structs.StructTypes;
import com.jozufozu.flywheel.core.structs.model.TransformedPart;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.fml.LogicalSide;
public class ExampleEffect implements Effect {
private static final int INSTANCE_COUNT = 50;
private final Level level;
private final Vec3 targetPoint;
private final BlockPos blockPos;
private final ImmutableBox volume;
private final List<Instance> effects;
public ExampleEffect(Level level, Vec3 targetPoint) {
this.level = level;
this.targetPoint = targetPoint;
this.blockPos = new BlockPos(targetPoint);
this.effects = new ArrayList<>();
this.volume = GridAlignedBB.from(this.blockPos);
}
public static void spawn(TickEvent.PlayerTickEvent event) {
if (event.side == LogicalSide.SERVER || event.phase == TickEvent.Phase.START) {
return;
}
Player player = event.player;
Level level = player.level;
if (level.random.nextFloat() > 0.01) {
return;
}
var effects = InstancedRenderDispatcher.getEffects(level);
effects.add(new ExampleEffect(level, player.position()));
}
@Override
public Collection<? extends AbstractInstance> createInstances(InstancerManager instancerManager) {
effects.clear();
for (int i = 0; i < INSTANCE_COUNT; i++) {
effects.add(new Instance(instancerManager, level));
}
return effects;
}
public class Instance extends AbstractInstance implements DynamicInstance {
TransformedPart firefly;
public Instance(InstancerManager instancerManager, Level level) {
super(instancerManager, level);
}
@Override
public void init() {
firefly = instancerManager.factory(StructTypes.TRANSFORMED)
.model(Models.block(Blocks.SHROOMLIGHT.defaultBlockState()))
.createInstance();
firefly.setBlockLight(15)
.setSkyLight(15);
}
@Override
public BlockPos getWorldPosition() {
return blockPos;
}
@Override
public void remove() {
firefly.delete();
}
@Override
public ImmutableBox getVolume() {
return volume;
}
@Override
public void beginFrame() {
var x = level.random.nextFloat() * 3 - 1.5;
var y = level.random.nextFloat() * 3 - 1.5;
var z = level.random.nextFloat() * 3 - 1.5;
firefly.loadIdentity()
.translate(instancerManager.getOriginCoordinate())
.translate(targetPoint)
.translate(x, y, z)
.scale(1 / 16f);
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.vanilla.effect;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.vanilla;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -2,11 +2,11 @@
#use "flywheel:util/light.glsl" #use "flywheel:util/light.glsl"
#use "flywheel:util/quaternion.glsl" #use "flywheel:util/quaternion.glsl"
layout (location = 0) in vec2 oriented_light; layout(location = 0) in vec2 oriented_light;
layout (location = 1) in vec4 oriented_color; layout(location = 1) in vec4 oriented_color;
layout (location = 2) in vec3 oriented_pos; layout(location = 2) in vec3 oriented_pos;
layout (location = 3) in vec3 oriented_pivot; layout(location = 3) in vec3 oriented_pivot;
layout (location = 4) in vec4 oriented_rotation; layout(location = 4) in vec4 oriented_rotation;
void flw_instanceVertex() { void flw_instanceVertex() {
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0); flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0);

View file

@ -1,10 +1,10 @@
#use "flywheel:api/vertex.glsl" #use "flywheel:api/vertex.glsl"
#use "flywheel:util/light.glsl" #use "flywheel:util/light.glsl"
layout (location = 0) in vec2 transformed_light; layout(location = 0) in vec2 transformed_light;
layout (location = 1) in vec4 transformed_color; layout(location = 1) in vec4 transformed_color;
layout (location = 2) in mat4 transformed_pose; layout(location = 2) in mat4 transformed_pose;
layout (location = 6) in mat3 transformed_normal; layout(location = 6) in mat3 transformed_normal;
void flw_instanceVertex() { void flw_instanceVertex() {
flw_vertexPos = transformed_pose * flw_vertexPos; flw_vertexPos = transformed_pose * flw_vertexPos;