mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-11-14 22:43:56 +01:00
Merge branch '1.20/dev' into feat/multi-loader-1.21
# Conflicts: # common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java # forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java # gradle.properties
This commit is contained in:
commit
e7e3579315
@ -7,4 +7,8 @@ public interface InstanceHandle {
|
||||
void setChanged();
|
||||
|
||||
void setDeleted();
|
||||
|
||||
void setVisible(boolean visible);
|
||||
|
||||
boolean isVisible();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package dev.engine_room.flywheel.backend;
|
||||
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
|
||||
|
||||
public interface BackendConfig {
|
||||
BackendConfig INSTANCE = FlwBackendXplat.INSTANCE.getConfig();
|
||||
BackendConfig INSTANCE = FlwBackend.config();
|
||||
|
||||
/**
|
||||
* How smooth/accurate our flw_light impl is.
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend;
|
||||
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -7,11 +8,18 @@ import dev.engine_room.flywheel.api.Flywheel;
|
||||
|
||||
public final class FlwBackend {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(Flywheel.ID + "/backend");
|
||||
@UnknownNullability
|
||||
private static BackendConfig config;
|
||||
|
||||
private FlwBackend() {
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
public static BackendConfig config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public static void init(BackendConfig config) {
|
||||
FlwBackend.config = config;
|
||||
Backends.init();
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,4 @@ public interface FlwBackendXplat {
|
||||
FlwBackendXplat INSTANCE = DependencyInjection.load(FlwBackendXplat.class, "dev.engine_room.flywheel.backend.FlwBackendXplatImpl");
|
||||
|
||||
int getLightEmission(BlockState state, BlockGetter level, BlockPos pos);
|
||||
|
||||
BackendConfig getConfig();
|
||||
}
|
||||
|
@ -30,6 +30,9 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
||||
private static final ResourceLocation CULL_SHADER_MAIN = Flywheel.rl("internal/indirect/cull.glsl");
|
||||
private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl");
|
||||
private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl");
|
||||
private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl");
|
||||
private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl");
|
||||
public static final List<ResourceLocation> UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND);
|
||||
|
||||
private static final Compile<InstanceType<?>> CULL = new Compile<>();
|
||||
private static final Compile<ResourceLocation> UTIL = new Compile<>();
|
||||
@ -42,14 +45,12 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
||||
|
||||
private final Map<PipelineProgramKey, GlProgram> pipeline;
|
||||
private final Map<InstanceType<?>, GlProgram> culling;
|
||||
private final GlProgram apply;
|
||||
private final GlProgram scatter;
|
||||
private final Map<ResourceLocation, GlProgram> utils;
|
||||
|
||||
private IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling, GlProgram apply, GlProgram scatter) {
|
||||
private IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling, Map<ResourceLocation, GlProgram> utils) {
|
||||
this.pipeline = pipeline;
|
||||
this.culling = culling;
|
||||
this.apply = apply;
|
||||
this.scatter = scatter;
|
||||
this.utils = utils;
|
||||
}
|
||||
|
||||
private static List<String> getExtensions(GlslVersion glslVersion) {
|
||||
@ -94,10 +95,10 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
||||
try {
|
||||
var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys);
|
||||
var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys());
|
||||
var utils = utilCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN));
|
||||
var utils = utilCompiler.compileAndReportErrors(UTIL_SHADERS);
|
||||
|
||||
if (pipelineResult != null && cullingResult != null && utils != null) {
|
||||
newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN));
|
||||
newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
FlwPrograms.LOGGER.error("Failed to compile indirect programs", t);
|
||||
@ -177,11 +178,19 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
||||
}
|
||||
|
||||
public GlProgram getApplyProgram() {
|
||||
return apply;
|
||||
return utils.get(APPLY_SHADER_MAIN);
|
||||
}
|
||||
|
||||
public GlProgram getScatterProgram() {
|
||||
return scatter;
|
||||
return utils.get(SCATTER_SHADER_MAIN);
|
||||
}
|
||||
|
||||
public GlProgram getDownsampleFirstProgram() {
|
||||
return utils.get(DOWNSAMPLE_FIRST);
|
||||
}
|
||||
|
||||
public GlProgram getDownsampleSecondProgram() {
|
||||
return utils.get(DOWNSAMPLE_SECOND);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -190,6 +199,7 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
||||
.forEach(GlProgram::delete);
|
||||
culling.values()
|
||||
.forEach(GlProgram::delete);
|
||||
apply.delete();
|
||||
utils.values()
|
||||
.forEach(GlProgram::delete);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,9 @@ public class BufferTextureInstanceComponent extends InstanceAssemblerComponent {
|
||||
|
||||
fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
|
||||
|
||||
builder._addRaw("uniform usamplerBuffer _flw_instances;");
|
||||
builder.uniform()
|
||||
.type("usamplerBuffer")
|
||||
.name("_flw_instances");
|
||||
builder.blankLine();
|
||||
builder.function()
|
||||
.signature(FnSignature.create()
|
||||
|
@ -34,7 +34,7 @@ public class InstanceStructComponent implements SourceComponent {
|
||||
var builder = new GlslBuilder();
|
||||
|
||||
var instance = builder.struct();
|
||||
instance.setName(STRUCT_NAME);
|
||||
instance.name(STRUCT_NAME);
|
||||
for (var element : layout.elements()) {
|
||||
instance.addField(LayoutInterpreter.typeName(element.type()), element.name());
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class SsboInstanceComponent extends InstanceAssemblerComponent {
|
||||
|
||||
fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
|
||||
|
||||
builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
|
||||
builder._raw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
|
||||
+ " uint _flw_instances[];\n"
|
||||
+ "};");
|
||||
builder.blankLine();
|
||||
|
@ -1,23 +1,17 @@
|
||||
package dev.engine_room.flywheel.backend.engine;
|
||||
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
public class Arena {
|
||||
private final long elementSizeBytes;
|
||||
|
||||
private MemoryBlock memoryBlock;
|
||||
|
||||
// Monotonic index, generally represents the size of the arena.
|
||||
private int top = 0;
|
||||
public abstract class AbstractArena {
|
||||
protected final long elementSizeBytes;
|
||||
// List of free indices.
|
||||
private final IntList freeStack = new IntArrayList();
|
||||
// Monotonic index, generally represents the size of the arena.
|
||||
private int top = 0;
|
||||
|
||||
public Arena(long elementSizeBytes, int initialCapacity) {
|
||||
public AbstractArena(long elementSizeBytes) {
|
||||
this.elementSizeBytes = elementSizeBytes;
|
||||
|
||||
memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity);
|
||||
}
|
||||
|
||||
public int alloc() {
|
||||
@ -27,8 +21,8 @@ public class Arena {
|
||||
}
|
||||
|
||||
// Make sure there's room to increment top.
|
||||
if (top * elementSizeBytes >= memoryBlock.size()) {
|
||||
memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2);
|
||||
if (top * elementSizeBytes >= byteCapacity()) {
|
||||
grow();
|
||||
}
|
||||
|
||||
// Return the top index and increment.
|
||||
@ -40,19 +34,15 @@ public class Arena {
|
||||
freeStack.add(i);
|
||||
}
|
||||
|
||||
public long indexToPointer(int i) {
|
||||
return memoryBlock.ptr() + i * elementSizeBytes;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
memoryBlock.free();
|
||||
public long byteOffsetOf(int i) {
|
||||
return i * elementSizeBytes;
|
||||
}
|
||||
|
||||
public int capacity() {
|
||||
return top;
|
||||
}
|
||||
|
||||
public long byteCapacity() {
|
||||
return memoryBlock.size();
|
||||
}
|
||||
public abstract long byteCapacity();
|
||||
|
||||
protected abstract void grow();
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package dev.engine_room.flywheel.backend.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -13,26 +14,32 @@ import dev.engine_room.flywheel.backend.util.AtomicBitSet;
|
||||
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
|
||||
public final InstanceType<I> type;
|
||||
public final Environment environment;
|
||||
private final Supplier<AbstractInstancer<I>> recreate;
|
||||
|
||||
// Lock for all instances, only needs to be used in methods that may run on the TaskExecutor.
|
||||
protected final Object lock = new Object();
|
||||
protected final ArrayList<I> instances = new ArrayList<>();
|
||||
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>();
|
||||
protected final ArrayList<InstanceHandleImpl<I>> handles = new ArrayList<>();
|
||||
|
||||
protected final AtomicBitSet changed = new AtomicBitSet();
|
||||
protected final AtomicBitSet deleted = new AtomicBitSet();
|
||||
|
||||
protected AbstractInstancer(InstanceType<I> type, Environment environment) {
|
||||
this.type = type;
|
||||
this.environment = environment;
|
||||
protected AbstractInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
|
||||
this.type = key.type();
|
||||
this.environment = key.environment();
|
||||
this.recreate = recreate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public I createInstance() {
|
||||
synchronized (lock) {
|
||||
var i = instances.size();
|
||||
var handle = new InstanceHandleImpl(this, i);
|
||||
var handle = new InstanceHandleImpl<I>();
|
||||
handle.instancer = this;
|
||||
handle.recreate = recreate;
|
||||
handle.index = i;
|
||||
I instance = type.create(handle);
|
||||
handle.instance = instance;
|
||||
|
||||
addLocked(instance, handle);
|
||||
return instance;
|
||||
@ -47,12 +54,15 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
||||
|
||||
var instanceHandle = instance.handle();
|
||||
|
||||
if (!(instanceHandle instanceof InstanceHandleImpl handle)) {
|
||||
if (!(instanceHandle instanceof InstanceHandleImpl<?>)) {
|
||||
// UB: do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle.instancer == this) {
|
||||
// Should InstanceType have an isInstance method?
|
||||
var handle = (InstanceHandleImpl<I>) instanceHandle;
|
||||
|
||||
if (handle.instancer == this && handle.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -65,19 +75,23 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
||||
// is filtering deleted instances later, so is safe.
|
||||
handle.setDeleted();
|
||||
|
||||
// Only lock now that we'll be mutating our state.
|
||||
synchronized (lock) {
|
||||
// Add the instance to this instancer.
|
||||
handle.instancer = this;
|
||||
handle.index = instances.size();
|
||||
addLocked(instance, handle);
|
||||
// Add the instance to this instancer.
|
||||
handle.instancer = this;
|
||||
handle.recreate = recreate;
|
||||
|
||||
if (handle.visible) {
|
||||
// Only lock now that we'll be mutating our state.
|
||||
synchronized (lock) {
|
||||
handle.index = instances.size();
|
||||
addLocked(instance, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls must be synchronized on {@link #lock}.
|
||||
*/
|
||||
private void addLocked(I instance, InstanceHandleImpl handle) {
|
||||
private void addLocked(I instance, InstanceHandleImpl<I> handle) {
|
||||
instances.add(instance);
|
||||
handles.add(handle);
|
||||
changed.set(handle.index);
|
||||
@ -122,7 +136,7 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
||||
|
||||
if (writePos < newSize) {
|
||||
// Since we'll be shifting everything into this space we can consider it all changed.
|
||||
changed.set(writePos, newSize);
|
||||
setRangeChanged(writePos, newSize);
|
||||
}
|
||||
|
||||
// We definitely shouldn't consider the deleted instances as changed though,
|
||||
@ -155,11 +169,15 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
|
||||
.clear();
|
||||
}
|
||||
|
||||
protected void setRangeChanged(int start, int end) {
|
||||
changed.set(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all instances without freeing resources.
|
||||
*/
|
||||
public void clear() {
|
||||
for (InstanceHandleImpl handle : handles) {
|
||||
for (InstanceHandleImpl<I> handle : handles) {
|
||||
// Only clear instances that belong to this instancer.
|
||||
// If one of these handles was stolen by another instancer,
|
||||
// clearing it here would cause significant visual artifacts and instance leaks.
|
||||
|
@ -0,0 +1,30 @@
|
||||
package dev.engine_room.flywheel.backend.engine;
|
||||
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
public class CpuArena extends AbstractArena {
|
||||
|
||||
private MemoryBlock memoryBlock;
|
||||
|
||||
public CpuArena(long elementSizeBytes, int initialCapacity) {
|
||||
super(elementSizeBytes);
|
||||
|
||||
memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity);
|
||||
}
|
||||
|
||||
public long indexToPointer(int i) {
|
||||
return memoryBlock.ptr() + i * elementSizeBytes;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
memoryBlock.free();
|
||||
}
|
||||
|
||||
public long byteCapacity() {
|
||||
return memoryBlock.size();
|
||||
}
|
||||
|
||||
protected void grow() {
|
||||
memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2);
|
||||
}
|
||||
}
|
@ -8,16 +8,16 @@ import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.api.backend.Engine;
|
||||
import dev.engine_room.flywheel.api.instance.Instance;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.api.instance.Instancer;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualType;
|
||||
import dev.engine_room.flywheel.backend.FlwBackend;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.Environment;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
@ -36,9 +36,13 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
|
||||
*/
|
||||
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public <I extends Instance> AbstractInstancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
|
||||
return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
|
||||
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit);
|
||||
public <I extends Instance> AbstractInstancer<I> getInstancer(InstancerKey<I> key) {
|
||||
return (AbstractInstancer<I>) instancers.computeIfAbsent(key, this::createAndDeferInit);
|
||||
}
|
||||
|
||||
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
|
||||
@ -94,8 +98,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) {
|
||||
Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> byType = new HashMap<>();
|
||||
protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) {
|
||||
Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> byType = new HashMap<>();
|
||||
for (Engine.CrumblingBlock block : crumblingBlocks) {
|
||||
int progress = block.progress();
|
||||
|
||||
@ -107,7 +111,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
|
||||
// Filter out instances that weren't created by this engine.
|
||||
// If all is well, we probably shouldn't take the `continue`
|
||||
// branches but better to do checked casts.
|
||||
if (!(instance.handle() instanceof InstanceHandleImpl impl)) {
|
||||
if (!(instance.handle() instanceof InstanceHandleImpl<?> impl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,22 @@
|
||||
package dev.engine_room.flywheel.backend.engine;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.Instance;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceHandle;
|
||||
|
||||
public class InstanceHandleImpl implements InstanceHandle {
|
||||
public AbstractInstancer<?> instancer;
|
||||
public class InstanceHandleImpl<I extends Instance> implements InstanceHandle {
|
||||
@UnknownNullability
|
||||
public AbstractInstancer<I> instancer;
|
||||
@UnknownNullability
|
||||
public I instance;
|
||||
@UnknownNullability
|
||||
public Supplier<AbstractInstancer<I>> recreate;
|
||||
public boolean visible = true;
|
||||
public int index;
|
||||
|
||||
public InstanceHandleImpl(AbstractInstancer<?> instancer, int index) {
|
||||
this.instancer = instancer;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChanged() {
|
||||
instancer.notifyDirty(index);
|
||||
@ -23,6 +29,27 @@ public class InstanceHandleImpl implements InstanceHandle {
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
if (this.visible == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = visible;
|
||||
|
||||
if (visible) {
|
||||
recreate.get().stealInstance(instance);
|
||||
} else {
|
||||
instancer.notifyRemoval(index);
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
index = -1;
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class LightStorage {
|
||||
|
||||
private final LevelAccessor level;
|
||||
|
||||
private final Arena arena;
|
||||
private final CpuArena arena;
|
||||
private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap();
|
||||
{
|
||||
section2ArenaIndex.defaultReturnValue(INVALID_SECTION);
|
||||
@ -62,7 +62,7 @@ public class LightStorage {
|
||||
public LightStorage(LevelAccessor level) {
|
||||
this.level = level;
|
||||
|
||||
arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS);
|
||||
arena = new CpuArena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.engine.embed;
|
||||
|
||||
import dev.engine_room.flywheel.backend.engine.Arena;
|
||||
import dev.engine_room.flywheel.backend.engine.CpuArena;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSet;
|
||||
|
||||
@ -13,7 +13,7 @@ public class EnvironmentStorage {
|
||||
|
||||
// Note than the arena starts indexing at zero, but we reserve zero for the identity matrix.
|
||||
// Any time an ID from the arena is written we want to add one to it.
|
||||
public final Arena arena = new Arena(MATRIX_SIZE_BYTES, 32);
|
||||
public final CpuArena arena = new CpuArena(MATRIX_SIZE_BYTES, 32);
|
||||
|
||||
{
|
||||
// Reserve the identity matrix. Burns a few bytes but oh well.
|
||||
|
@ -1,11 +1,12 @@
|
||||
package dev.engine_room.flywheel.backend.engine.indirect;
|
||||
|
||||
public final class BufferBindings {
|
||||
public static final int INSTANCE = 0;
|
||||
public static final int TARGET = 1;
|
||||
public static final int MODEL_INDEX = 2;
|
||||
public static final int PAGE_FRAME_DESCRIPTOR = 0;
|
||||
public static final int INSTANCE = 1;
|
||||
public static final int DRAW_INSTANCE_INDEX = 2;
|
||||
public static final int MODEL = 3;
|
||||
public static final int DRAW = 4;
|
||||
|
||||
public static final int LIGHT_LUT = 5;
|
||||
public static final int LIGHT_SECTION = 6;
|
||||
public static final int MATRICES = 7;
|
||||
|
@ -0,0 +1,127 @@
|
||||
package dev.engine_room.flywheel.backend.engine.indirect;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL46;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
|
||||
import dev.engine_room.flywheel.backend.gl.GlTextureUnit;
|
||||
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
|
||||
import dev.engine_room.flywheel.lib.math.MoreMath;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
public class DepthPyramid {
|
||||
private final GlProgram downsampleFirstProgram;
|
||||
private final GlProgram downsampleSecondProgram;
|
||||
|
||||
public int pyramidTextureId = -1;
|
||||
|
||||
private int lastWidth = -1;
|
||||
private int lastHeight = -1;
|
||||
|
||||
public DepthPyramid(GlProgram downsampleFirstProgram, GlProgram downsampleSecondProgram) {
|
||||
this.downsampleFirstProgram = downsampleFirstProgram;
|
||||
this.downsampleSecondProgram = downsampleSecondProgram;
|
||||
}
|
||||
|
||||
public void generate() {
|
||||
var mainRenderTarget = Minecraft.getInstance()
|
||||
.getMainRenderTarget();
|
||||
|
||||
int width = mip0Size(mainRenderTarget.width);
|
||||
int height = mip0Size(mainRenderTarget.height);
|
||||
|
||||
int mipLevels = getImageMipLevels(width, height);
|
||||
|
||||
createPyramidMips(mipLevels, width, height);
|
||||
|
||||
int depthBufferId = mainRenderTarget.getDepthTextureId();
|
||||
|
||||
GL46.glMemoryBarrier(GL46.GL_FRAMEBUFFER_BARRIER_BIT);
|
||||
|
||||
GlTextureUnit.T0.makeActive();
|
||||
GlStateManager._bindTexture(depthBufferId);
|
||||
|
||||
downsampleFirstProgram.bind();
|
||||
downsampleFirstProgram.setUInt("max_mip_level", mipLevels);
|
||||
|
||||
for (int i = 0; i < Math.min(6, mipLevels); i++) {
|
||||
GL46.glBindImageTexture(i + 1, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F);
|
||||
}
|
||||
|
||||
GL46.glDispatchCompute(MoreMath.ceilingDiv(width << 1, 64), MoreMath.ceilingDiv(height << 1, 64), 1);
|
||||
|
||||
if (mipLevels < 7) {
|
||||
GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GL46.glMemoryBarrier(GL46.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
downsampleSecondProgram.bind();
|
||||
downsampleSecondProgram.setUInt("max_mip_level", mipLevels);
|
||||
|
||||
// Note: mip_6 in the shader is actually mip level 5 in the texture
|
||||
GL46.glBindImageTexture(0, pyramidTextureId, 5, false, 0, GL32.GL_READ_ONLY, GL32.GL_R32F);
|
||||
for (int i = 6; i < Math.min(12, mipLevels); i++) {
|
||||
GL46.glBindImageTexture(i - 5, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F);
|
||||
}
|
||||
|
||||
GL46.glDispatchCompute(1, 1, 1);
|
||||
|
||||
GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT);
|
||||
}
|
||||
|
||||
public void bindForCull() {
|
||||
GlTextureUnit.T0.makeActive();
|
||||
GlStateManager._bindTexture(pyramidTextureId);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (pyramidTextureId != -1) {
|
||||
GL32.glDeleteTextures(pyramidTextureId);
|
||||
pyramidTextureId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void createPyramidMips(int mipLevels, int width, int height) {
|
||||
if (lastWidth == width && lastHeight == height) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
|
||||
delete();
|
||||
|
||||
pyramidTextureId = GL46.glCreateTextures(GL46.GL_TEXTURE_2D);
|
||||
GL46.glTextureStorage2D(pyramidTextureId, mipLevels, GL32.GL_R32F, width, height);
|
||||
|
||||
GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
|
||||
GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
|
||||
GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_COMPARE_MODE, GL32.GL_NONE);
|
||||
GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE);
|
||||
GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
public static int mipSize(int mip0Size, int level) {
|
||||
return Math.max(1, mip0Size >> level);
|
||||
}
|
||||
|
||||
public static int mip0Size(int screenSize) {
|
||||
return Integer.highestOneBit(screenSize);
|
||||
}
|
||||
|
||||
public static int getImageMipLevels(int width, int height) {
|
||||
int result = 1;
|
||||
|
||||
while (width > 2 && height > 2) {
|
||||
result++;
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -30,18 +30,19 @@ public class IndirectBuffers {
|
||||
private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE;
|
||||
|
||||
// Offsets to the vbos
|
||||
private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET;
|
||||
private static final long TARGET_HANDLE_OFFSET = INT_SIZE;
|
||||
private static final long MODEL_INDEX_HANDLE_OFFSET = INT_SIZE * 2;
|
||||
private static final long MODEL_HANDLE_OFFSET = INT_SIZE * 3;
|
||||
private static final long DRAW_HANDLE_OFFSET = INT_SIZE * 4;
|
||||
private static final long PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * INT_SIZE;
|
||||
private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.INSTANCE * INT_SIZE;
|
||||
private static final long DRAW_INSTANCE_INDEX_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * INT_SIZE;
|
||||
private static final long MODEL_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.MODEL * INT_SIZE;
|
||||
private static final long DRAW_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW * INT_SIZE;
|
||||
|
||||
// Offsets to the sizes
|
||||
private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET;
|
||||
private static final long TARGET_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE;
|
||||
private static final long MODEL_INDEX_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2;
|
||||
private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 3;
|
||||
private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 4;
|
||||
private static final long PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * PTR_SIZE;
|
||||
private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.INSTANCE * PTR_SIZE;
|
||||
private static final long DRAW_INSTANCE_INDEX_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * PTR_SIZE;
|
||||
private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.MODEL * PTR_SIZE;
|
||||
private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW * PTR_SIZE;
|
||||
|
||||
|
||||
private static final float INSTANCE_GROWTH_FACTOR = 1.25f;
|
||||
private static final float MODEL_GROWTH_FACTOR = 2f;
|
||||
@ -60,73 +61,71 @@ public class IndirectBuffers {
|
||||
* one for the instance buffer, target buffer, model index buffer, model buffer, and draw buffer.
|
||||
*/
|
||||
private final MemoryBlock multiBindBlock;
|
||||
private final long instanceStride;
|
||||
public final ResizableStorageArray instance;
|
||||
public final ResizableStorageArray target;
|
||||
public final ResizableStorageArray modelIndex;
|
||||
|
||||
public final ObjectStorage objectStorage;
|
||||
public final ResizableStorageArray drawInstanceIndex;
|
||||
public final ResizableStorageArray model;
|
||||
public final ResizableStorageArray draw;
|
||||
|
||||
IndirectBuffers(long instanceStride) {
|
||||
this.instanceStride = instanceStride;
|
||||
this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
|
||||
|
||||
instance = new ResizableStorageArray(instanceStride, INSTANCE_GROWTH_FACTOR);
|
||||
target = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
|
||||
modelIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
|
||||
objectStorage = new ObjectStorage(instanceStride);
|
||||
drawInstanceIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
|
||||
model = new ResizableStorageArray(MODEL_STRIDE, MODEL_GROWTH_FACTOR);
|
||||
draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, DRAW_GROWTH_FACTOR);
|
||||
}
|
||||
|
||||
void updateCounts(int instanceCount, int modelCount, int drawCount) {
|
||||
instance.ensureCapacity(instanceCount);
|
||||
target.ensureCapacity(instanceCount);
|
||||
modelIndex.ensureCapacity(instanceCount);
|
||||
drawInstanceIndex.ensureCapacity(instanceCount);
|
||||
model.ensureCapacity(modelCount);
|
||||
draw.ensureCapacity(drawCount);
|
||||
|
||||
final long ptr = multiBindBlock.ptr();
|
||||
MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, instance.handle());
|
||||
MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle());
|
||||
MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, modelIndex.handle());
|
||||
|
||||
MemoryUtil.memPutInt(ptr + PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET, objectStorage.frameDescriptorBuffer.handle());
|
||||
MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, objectStorage.objectBuffer.handle());
|
||||
MemoryUtil.memPutInt(ptr + DRAW_INSTANCE_INDEX_HANDLE_OFFSET, drawInstanceIndex.handle());
|
||||
MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle());
|
||||
MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle());
|
||||
|
||||
MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, instanceStride * instanceCount);
|
||||
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount);
|
||||
MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount);
|
||||
MemoryUtil.memPutAddress(ptr + PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET, objectStorage.frameDescriptorBuffer.capacity());
|
||||
MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, objectStorage.objectBuffer.capacity());
|
||||
MemoryUtil.memPutAddress(ptr + DRAW_INSTANCE_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount);
|
||||
MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount);
|
||||
MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount);
|
||||
}
|
||||
|
||||
public void bindForCompute() {
|
||||
multiBind();
|
||||
public void bindForCull() {
|
||||
multiBind(0, 4);
|
||||
}
|
||||
|
||||
public void bindForApply() {
|
||||
multiBind(3, 2);
|
||||
}
|
||||
|
||||
public void bindForDraw() {
|
||||
multiBind();
|
||||
multiBind(1, 4);
|
||||
GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle());
|
||||
}
|
||||
|
||||
private void multiBind() {
|
||||
final long ptr = multiBindBlock.ptr();
|
||||
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind all buffers except the draw command buffer.
|
||||
*/
|
||||
public void bindForCrumbling() {
|
||||
multiBind(3, 3);
|
||||
}
|
||||
|
||||
private void multiBind(int base, int count) {
|
||||
final long ptr = multiBindBlock.ptr();
|
||||
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
|
||||
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, base, count, ptr + base * INT_SIZE, ptr + OFFSET_OFFSET + base * PTR_SIZE, ptr + SIZE_OFFSET + base * PTR_SIZE);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
multiBindBlock.free();
|
||||
|
||||
instance.delete();
|
||||
target.delete();
|
||||
modelIndex.delete();
|
||||
objectStorage.delete();
|
||||
drawInstanceIndex.delete();
|
||||
model.delete();
|
||||
draw.delete();
|
||||
}
|
||||
|
@ -74,8 +74,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
continue;
|
||||
}
|
||||
|
||||
instancer.modelIndex = modelIndex;
|
||||
instancer.baseInstance = instanceCountThisFrame;
|
||||
instancer.postUpdate(modelIndex, instanceCountThisFrame);
|
||||
instanceCountThisFrame += instanceCount;
|
||||
|
||||
modelIndex++;
|
||||
@ -96,6 +95,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
// Upload only instances that have changed.
|
||||
uploadInstances(stagingBuffer);
|
||||
|
||||
buffers.objectStorage.uploadDescriptors(stagingBuffer);
|
||||
|
||||
// We need to upload the models every frame to reset the instance count.
|
||||
uploadModels(stagingBuffer);
|
||||
|
||||
@ -117,8 +118,8 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
Uniforms.bindAll();
|
||||
cullProgram.bind();
|
||||
|
||||
buffers.bindForCompute();
|
||||
glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1);
|
||||
buffers.bindForCull();
|
||||
glDispatchCompute(buffers.objectStorage.capacity(), 1, 1);
|
||||
}
|
||||
|
||||
public void dispatchApply() {
|
||||
@ -126,7 +127,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
return;
|
||||
}
|
||||
|
||||
buffers.bindForCompute();
|
||||
buffers.bindForApply();
|
||||
glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1);
|
||||
}
|
||||
|
||||
@ -171,7 +172,9 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
}
|
||||
|
||||
public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) {
|
||||
instancer.modelIndex = instancers.size();
|
||||
instancer.mapping = buffers.objectStorage.createMapping();
|
||||
instancer.postUpdate(instancers.size(), -1);
|
||||
|
||||
instancers.add(instancer);
|
||||
|
||||
List<Model.ConfiguredMesh> meshes = key.model()
|
||||
@ -242,12 +245,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||
|
||||
private void uploadInstances(StagingBuffer stagingBuffer) {
|
||||
for (var instancer : instancers) {
|
||||
instancer.uploadInstances(stagingBuffer, buffers.instance.handle());
|
||||
}
|
||||
|
||||
for (var instancer : instancers) {
|
||||
instancer.uploadModelIndices(stagingBuffer, buffers.modelIndex.handle());
|
||||
instancer.resetChanged();
|
||||
instancer.uploadInstances(stagingBuffer, buffers.objectStorage.objectBuffer.handle());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,9 +72,9 @@ public class IndirectDraw {
|
||||
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader
|
||||
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
|
||||
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
|
||||
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance); // baseInstance
|
||||
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance()); // baseInstance
|
||||
|
||||
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
|
||||
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex
|
||||
|
||||
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
|
||||
|
||||
@ -89,9 +89,9 @@ public class IndirectDraw {
|
||||
MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance
|
||||
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
|
||||
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
|
||||
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance + instanceIndex); // baseInstance
|
||||
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance() + instanceIndex); // baseInstance
|
||||
|
||||
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
|
||||
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex
|
||||
|
||||
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
|
||||
|
||||
|
@ -46,6 +46,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
private final LightBuffers lightBuffers;
|
||||
private final MatrixBuffer matrixBuffer;
|
||||
|
||||
private final DepthPyramid depthPyramid;
|
||||
|
||||
private boolean needsBarrier = false;
|
||||
|
||||
public IndirectDrawManager(IndirectPrograms programs) {
|
||||
@ -58,11 +60,13 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
meshPool.bind(vertexArray);
|
||||
lightBuffers = new LightBuffers();
|
||||
matrixBuffer = new MatrixBuffer();
|
||||
|
||||
depthPyramid = new DepthPyramid(programs.getDownsampleFirstProgram(), programs.getDownsampleSecondProgram());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) {
|
||||
return new IndirectInstancer<>(key.type(), key.environment(), key.model());
|
||||
return new IndirectInstancer<>(key, () -> getInstancer(key));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -136,6 +140,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
|
||||
stagingBuffer.flush();
|
||||
|
||||
depthPyramid.generate();
|
||||
|
||||
// We could probably save some driver calls here when there are
|
||||
// actually zero instances, but that feels like a very rare case
|
||||
|
||||
@ -143,6 +149,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
|
||||
matrixBuffer.bind();
|
||||
|
||||
depthPyramid.bindForCull();
|
||||
|
||||
for (var group : cullingGroups.values()) {
|
||||
group.dispatchCull();
|
||||
}
|
||||
@ -174,6 +182,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
crumblingDrawBuffer.delete();
|
||||
|
||||
programs.release();
|
||||
|
||||
depthPyramid.delete();
|
||||
}
|
||||
|
||||
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {
|
||||
@ -209,8 +219,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
IndirectInstancer<?> instancer = instanceHandlePair.first();
|
||||
int instanceIndex = instanceHandlePair.second().index;
|
||||
IndirectInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
int instanceIndex = instanceHandlePair.getSecond().index;
|
||||
|
||||
for (IndirectDraw draw : instancer.draws()) {
|
||||
// Transform the material to be suited for crumbling.
|
||||
|
@ -2,16 +2,17 @@ package dev.engine_room.flywheel.backend.engine.indirect;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.UnknownNullability;
|
||||
import org.joml.Vector4fc;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.Instance;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceWriter;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.Environment;
|
||||
import dev.engine_room.flywheel.backend.engine.InstancerKey;
|
||||
import dev.engine_room.flywheel.backend.util.AtomicBitSet;
|
||||
import dev.engine_room.flywheel.lib.math.MoreMath;
|
||||
|
||||
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
|
||||
@ -20,18 +21,34 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||
private final List<IndirectDraw> associatedDraws = new ArrayList<>();
|
||||
private final Vector4fc boundingSphere;
|
||||
|
||||
public int modelIndex = -1;
|
||||
public int baseInstance = -1;
|
||||
private int lastModelIndex = -1;
|
||||
private int lastBaseInstance = -1;
|
||||
private int lastInstanceCount = -1;
|
||||
private final AtomicBitSet changedPages = new AtomicBitSet();
|
||||
|
||||
public IndirectInstancer(InstanceType<I> type, Environment environment, Model model) {
|
||||
super(type, environment);
|
||||
public ObjectStorage.@UnknownNullability Mapping mapping;
|
||||
|
||||
private int modelIndex = -1;
|
||||
private int baseInstance = -1;
|
||||
|
||||
public IndirectInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
|
||||
super(key, recreate);
|
||||
instanceStride = MoreMath.align4(type.layout()
|
||||
.byteSize());
|
||||
writer = this.type.writer();
|
||||
boundingSphere = model.boundingSphere();
|
||||
boundingSphere = key.model().boundingSphere();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDirty(int index) {
|
||||
if (index < 0 || index >= instanceCount()) {
|
||||
return;
|
||||
}
|
||||
changedPages.set(ObjectStorage.objectIndex2PageIndex(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRangeChanged(int start, int end) {
|
||||
super.setRangeChanged(start, end);
|
||||
|
||||
changedPages.set(ObjectStorage.objectIndex2PageIndex(start), ObjectStorage.objectIndex2PageIndex(end) + 1);
|
||||
}
|
||||
|
||||
public void addDraw(IndirectDraw draw) {
|
||||
@ -46,6 +63,12 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||
removeDeletedInstances();
|
||||
}
|
||||
|
||||
public void postUpdate(int modelIndex, int baseInstance) {
|
||||
this.modelIndex = modelIndex;
|
||||
this.baseInstance = baseInstance;
|
||||
mapping.update(modelIndex, instanceCount());
|
||||
}
|
||||
|
||||
public void writeModel(long ptr) {
|
||||
MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
|
||||
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
|
||||
@ -57,71 +80,55 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||
}
|
||||
|
||||
public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) {
|
||||
long baseByte = baseInstance * instanceStride;
|
||||
|
||||
if (baseInstance != lastBaseInstance) {
|
||||
uploadAllInstances(stagingBuffer, baseByte, instanceVbo);
|
||||
} else {
|
||||
uploadChangedInstances(stagingBuffer, baseByte, instanceVbo);
|
||||
if (changedPages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void uploadModelIndices(StagingBuffer stagingBuffer, int modelIndexVbo) {
|
||||
long modelIndexBaseByte = baseInstance * IndirectBuffers.INT_SIZE;
|
||||
int numPages = mapping.pageCount();
|
||||
|
||||
if (baseInstance != lastBaseInstance || modelIndex != lastModelIndex || instances.size() > lastInstanceCount) {
|
||||
uploadAllModelIndices(stagingBuffer, modelIndexBaseByte, modelIndexVbo);
|
||||
}
|
||||
}
|
||||
var instanceCount = instances.size();
|
||||
|
||||
public void resetChanged() {
|
||||
lastModelIndex = modelIndex;
|
||||
lastBaseInstance = baseInstance;
|
||||
lastInstanceCount = instances.size();
|
||||
changed.clear();
|
||||
}
|
||||
for (int page = changedPages.nextSetBit(0); page >= 0 && page < numPages; page = changedPages.nextSetBit(page + 1)) {
|
||||
int startObject = ObjectStorage.pageIndex2ObjectIndex(page);
|
||||
|
||||
private void uploadChangedInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) {
|
||||
changed.forEachSetSpan((startInclusive, endInclusive) -> {
|
||||
// Generally we're good about ensuring we don't have changed bits set out of bounds, but check just in case
|
||||
if (startInclusive >= instances.size()) {
|
||||
return;
|
||||
if (startObject >= instanceCount) {
|
||||
break;
|
||||
}
|
||||
int actualEnd = Math.min(endInclusive, instances.size() - 1);
|
||||
|
||||
int instanceCount = actualEnd - startInclusive + 1;
|
||||
long totalSize = instanceCount * instanceStride;
|
||||
int endObject = Math.min(instanceCount, ObjectStorage.pageIndex2ObjectIndex(page + 1));
|
||||
|
||||
stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte + startInclusive * instanceStride, ptr -> {
|
||||
for (int i = startInclusive; i <= actualEnd; i++) {
|
||||
long baseByte = mapping.page2ByteOffset(page);
|
||||
long size = (endObject - startObject) * instanceStride;
|
||||
|
||||
// Because writes are broken into pages, we end up with significantly more calls into
|
||||
// StagingBuffer#enqueueCopy and the allocations for the writer got out of hand. Here
|
||||
// we've inlined the enqueueCopy call and do not allocate the write lambda at all.
|
||||
// Doing so cut upload times in half.
|
||||
|
||||
// Try to write directly into the staging buffer if there is enough contiguous space.
|
||||
long direct = stagingBuffer.reserveForCopy(size, instanceVbo, baseByte);
|
||||
|
||||
if (direct != MemoryUtil.NULL) {
|
||||
for (int i = startObject; i < endObject; i++) {
|
||||
var instance = instances.get(i);
|
||||
writer.write(ptr, instance);
|
||||
ptr += instanceStride;
|
||||
writer.write(direct, instance);
|
||||
direct += instanceStride;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
private void uploadAllInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) {
|
||||
long totalSize = instances.size() * instanceStride;
|
||||
|
||||
stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte, ptr -> {
|
||||
for (I instance : instances) {
|
||||
// Otherwise, write to a scratch buffer and enqueue a copy.
|
||||
var block = stagingBuffer.getScratch(size);
|
||||
var ptr = block.ptr();
|
||||
for (int i = startObject; i < endObject; i++) {
|
||||
var instance = instances.get(i);
|
||||
writer.write(ptr, instance);
|
||||
ptr += instanceStride;
|
||||
}
|
||||
});
|
||||
}
|
||||
stagingBuffer.enqueueCopy(block.ptr(), size, instanceVbo, baseByte);
|
||||
}
|
||||
|
||||
private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexBaseByte, int modelIndexVbo) {
|
||||
long modelIndexTotalSize = instances.size() * IndirectBuffers.INT_SIZE;
|
||||
|
||||
stagingBuffer.enqueueCopy(modelIndexTotalSize, modelIndexVbo, modelIndexBaseByte, ptr -> {
|
||||
for (int i = 0; i < instances.size(); i++) {
|
||||
MemoryUtil.memPutInt(ptr, modelIndex);
|
||||
ptr += IndirectBuffers.INT_SIZE;
|
||||
}
|
||||
});
|
||||
changedPages.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,5 +136,15 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||
for (IndirectDraw draw : draws()) {
|
||||
draw.delete();
|
||||
}
|
||||
|
||||
mapping.delete();
|
||||
}
|
||||
|
||||
public int modelIndex() {
|
||||
return modelIndex;
|
||||
}
|
||||
|
||||
public int baseInstance() {
|
||||
return baseInstance;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,220 @@
|
||||
package dev.engine_room.flywheel.backend.engine.indirect;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import dev.engine_room.flywheel.backend.engine.AbstractArena;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
|
||||
public class ObjectStorage extends AbstractArena {
|
||||
// 32 objects per page. Allows for convenient bitsets on the gpu.
|
||||
public static final int LOG_2_PAGE_SIZE = 5;
|
||||
public static final int PAGE_SIZE = 1 << LOG_2_PAGE_SIZE;
|
||||
public static final int PAGE_MASK = PAGE_SIZE - 1;
|
||||
|
||||
public static final int INITIAL_PAGES_ALLOCATED = 4;
|
||||
|
||||
/**
|
||||
* The GPU side buffer containing all the objects, logically divided into page frames.
|
||||
*/
|
||||
public final ResizableStorageBuffer objectBuffer;
|
||||
/**
|
||||
* The GPU side buffer containing 32 bit descriptors for each page frame.
|
||||
*/
|
||||
public final ResizableStorageBuffer frameDescriptorBuffer;
|
||||
/**
|
||||
* The CPU side memory block containing the page descriptors.
|
||||
*/
|
||||
private MemoryBlock frameDescriptors;
|
||||
|
||||
private boolean needsUpload = false;
|
||||
|
||||
public ObjectStorage(long objectSizeBytes) {
|
||||
super(PAGE_SIZE * objectSizeBytes);
|
||||
|
||||
this.objectBuffer = new ResizableStorageBuffer();
|
||||
this.frameDescriptorBuffer = new ResizableStorageBuffer();
|
||||
|
||||
objectBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes);
|
||||
frameDescriptorBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
|
||||
frameDescriptors = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
|
||||
}
|
||||
|
||||
public Mapping createMapping() {
|
||||
return new Mapping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long byteCapacity() {
|
||||
return objectBuffer.capacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(int i) {
|
||||
super.free(i);
|
||||
MemoryUtil.memPutInt(ptrForPage(i), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void grow() {
|
||||
objectBuffer.ensureCapacity(objectBuffer.capacity() * 2);
|
||||
frameDescriptorBuffer.ensureCapacity(frameDescriptorBuffer.capacity() * 2);
|
||||
frameDescriptors = frameDescriptors.realloc(frameDescriptors.size() * 2);
|
||||
}
|
||||
|
||||
public void uploadDescriptors(StagingBuffer stagingBuffer) {
|
||||
if (!needsUpload) {
|
||||
return;
|
||||
}
|
||||
// We could be smarter about which spans are uploaded but this thing is so small it's probably not worth it.
|
||||
stagingBuffer.enqueueCopy(frameDescriptors.ptr(), frameDescriptors.size(), frameDescriptorBuffer.handle(), 0);
|
||||
needsUpload = false;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
objectBuffer.delete();
|
||||
frameDescriptorBuffer.delete();
|
||||
frameDescriptors.free();
|
||||
}
|
||||
|
||||
private long ptrForPage(int page) {
|
||||
return frameDescriptors.ptr() + (long) page * Integer.BYTES;
|
||||
}
|
||||
|
||||
public static int objectIndex2PageIndex(int objectIndex) {
|
||||
return objectIndex >> LOG_2_PAGE_SIZE;
|
||||
}
|
||||
|
||||
public static int pageIndex2ObjectIndex(int pageIndex) {
|
||||
return pageIndex << LOG_2_PAGE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps serial object indices to pages, and manages the allocation of pages.
|
||||
*/
|
||||
public class Mapping {
|
||||
private static final int[] EMPTY_ALLOCATION = new int[0];
|
||||
private int[] pages = EMPTY_ALLOCATION;
|
||||
|
||||
private int modelIndex = -1;
|
||||
private int objectCount = 0;
|
||||
|
||||
/**
|
||||
* Adjust this allocation to the given model index and object count.
|
||||
*
|
||||
* <p>This method triggers eager resizing of the allocation to fit the new object count.
|
||||
* If the model index is different from the current one, all frame descriptors will be updated.
|
||||
*
|
||||
* @param modelIndex The model index the objects in this allocation are associated with.
|
||||
* @param objectCount The number of objects in this allocation.
|
||||
*/
|
||||
public void update(int modelIndex, int objectCount) {
|
||||
boolean incremental = this.modelIndex == modelIndex;
|
||||
|
||||
if (incremental && objectCount == this.objectCount) {
|
||||
// Nothing will change.
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectStorage.this.needsUpload = true;
|
||||
|
||||
this.modelIndex = modelIndex;
|
||||
this.objectCount = objectCount;
|
||||
|
||||
var oldLength = pages.length;
|
||||
var newLength = objectIndex2PageIndex((objectCount + PAGE_MASK));
|
||||
|
||||
if (oldLength > newLength) {
|
||||
// Eagerly free the now unnecessary pages.
|
||||
// shrink will zero out the pageTable entries for the freed pages.
|
||||
shrink(oldLength, newLength);
|
||||
|
||||
if (incremental) {
|
||||
// Only update the last page, everything else is unchanged.
|
||||
updateRange(newLength - 1, newLength);
|
||||
}
|
||||
} else if (oldLength < newLength) {
|
||||
// Allocate new pages to fit the new object count.
|
||||
grow(newLength, oldLength);
|
||||
|
||||
if (incremental) {
|
||||
// Update the old last page + all new pages
|
||||
updateRange(oldLength - 1, newLength);
|
||||
}
|
||||
} else {
|
||||
if (incremental) {
|
||||
// Only update the last page.
|
||||
updateRange(oldLength - 1, oldLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (!incremental) {
|
||||
// Update all pages.
|
||||
updateRange(0, newLength);
|
||||
}
|
||||
}
|
||||
|
||||
public int pageCount() {
|
||||
return pages.length;
|
||||
}
|
||||
|
||||
public long page2ByteOffset(int page) {
|
||||
return ObjectStorage.this.byteOffsetOf(pages[page]);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
for (int page : pages) {
|
||||
ObjectStorage.this.free(page);
|
||||
}
|
||||
pages = EMPTY_ALLOCATION;
|
||||
modelIndex = -1;
|
||||
objectCount = 0;
|
||||
|
||||
ObjectStorage.this.needsUpload = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the page descriptor for the given page index.
|
||||
* Runs under the assumption than all pages are full except maybe the last one.
|
||||
*/
|
||||
private int calculatePageDescriptor(int pageIndex) {
|
||||
int countInPage;
|
||||
if (objectCount % PAGE_SIZE != 0 && pageIndex == pages.length - 1) {
|
||||
// Last page && it isn't full -> use the remainder.
|
||||
countInPage = objectCount & PAGE_MASK;
|
||||
} else if (objectCount > 0) {
|
||||
// Full page.
|
||||
countInPage = PAGE_SIZE;
|
||||
} else {
|
||||
// Empty page, this shouldn't be reachable because we eagerly free empty pages.
|
||||
countInPage = 0;
|
||||
}
|
||||
return (modelIndex & 0x3FFFFF) | (countInPage << 26);
|
||||
}
|
||||
|
||||
private void updateRange(int start, int oldLength) {
|
||||
for (int i = start; i < oldLength; i++) {
|
||||
MemoryUtil.memPutInt(ptrForPage(pages[i]), calculatePageDescriptor(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void grow(int neededPages, int oldLength) {
|
||||
pages = Arrays.copyOf(pages, neededPages);
|
||||
|
||||
for (int i = oldLength; i < neededPages; i++) {
|
||||
var page = ObjectStorage.this.alloc();
|
||||
pages[i] = page;
|
||||
}
|
||||
}
|
||||
|
||||
private void shrink(int oldLength, int neededPages) {
|
||||
for (int i = oldLength - 1; i >= neededPages; i--) {
|
||||
var page = pages[i];
|
||||
ObjectStorage.this.free(page);
|
||||
}
|
||||
|
||||
pages = Arrays.copyOf(pages, neededPages);
|
||||
}
|
||||
}
|
||||
}
|
@ -223,7 +223,7 @@ public class StagingBuffer {
|
||||
FlwMemoryTracker._freeCpuMemory(capacity);
|
||||
}
|
||||
|
||||
private MemoryBlock getScratch(long size) {
|
||||
public MemoryBlock getScratch(long size) {
|
||||
if (scratch == null) {
|
||||
scratch = MemoryBlock.malloc(size);
|
||||
} else if (scratch.size() < size) {
|
||||
|
@ -128,7 +128,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
|
||||
|
||||
@Override
|
||||
protected <I extends Instance> InstancedInstancer<I> create(InstancerKey<I> key) {
|
||||
return new InstancedInstancer<>(key.type(), key.environment());
|
||||
return new InstancedInstancer<>(key, () -> getInstancer(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -180,8 +180,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
InstancedInstancer<?> instancer = instanceHandlePair.first();
|
||||
var index = instanceHandlePair.second().index;
|
||||
InstancedInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
var index = instanceHandlePair.getSecond().index;
|
||||
|
||||
program.setInt("_flw_baseInstance", index);
|
||||
|
||||
|
@ -2,14 +2,14 @@ package dev.engine_room.flywheel.backend.engine.instancing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.Instance;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceWriter;
|
||||
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.Environment;
|
||||
import dev.engine_room.flywheel.backend.engine.InstancerKey;
|
||||
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage;
|
||||
@ -25,8 +25,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||
|
||||
private final List<InstancedDraw> draws = new ArrayList<>();
|
||||
|
||||
public InstancedInstancer(InstanceType<I> type, Environment environment) {
|
||||
super(type, environment);
|
||||
public InstancedInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
|
||||
super(key, recreate);
|
||||
var layout = type.layout();
|
||||
// Align to one texel in the texture buffer
|
||||
instanceStride = MoreMath.align16(layout.byteSize());
|
||||
|
@ -8,6 +8,7 @@ import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import dev.engine_room.flywheel.api.RenderContext;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
|
||||
import dev.engine_room.flywheel.backend.engine.indirect.DepthPyramid;
|
||||
import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -17,7 +18,7 @@ import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class FrameUniforms extends UniformWriter {
|
||||
private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 10;
|
||||
private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 17;
|
||||
static final UniformBuffer BUFFER = new UniformBuffer(Uniforms.FRAME_INDEX, SIZE);
|
||||
|
||||
private static final Matrix4f VIEW = new Matrix4f();
|
||||
@ -112,6 +113,8 @@ public final class FrameUniforms extends UniformWriter {
|
||||
|
||||
ptr = writeInt(ptr, debugMode);
|
||||
|
||||
ptr = writeCullData(ptr);
|
||||
|
||||
firstWrite = false;
|
||||
BUFFER.markDirty();
|
||||
}
|
||||
@ -179,6 +182,26 @@ public final class FrameUniforms extends UniformWriter {
|
||||
return writeInFluidAndBlock(ptr, level, blockPos, cameraPos);
|
||||
}
|
||||
|
||||
private static long writeCullData(long ptr) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var mainRenderTarget = mc.getMainRenderTarget();
|
||||
|
||||
int pyramidWidth = DepthPyramid.mip0Size(mainRenderTarget.width);
|
||||
int pyramidHeight = DepthPyramid.mip0Size(mainRenderTarget.height);
|
||||
int pyramidDepth = DepthPyramid.getImageMipLevels(pyramidWidth, pyramidHeight);
|
||||
|
||||
ptr = writeFloat(ptr, 0.05F); // zNear
|
||||
ptr = writeFloat(ptr, mc.gameRenderer.getDepthFar()); // zFar
|
||||
ptr = writeFloat(ptr, PROJECTION.m00()); // P00
|
||||
ptr = writeFloat(ptr, PROJECTION.m11()); // P11
|
||||
ptr = writeFloat(ptr, pyramidWidth); // pyramidWidth
|
||||
ptr = writeFloat(ptr, pyramidHeight); // pyramidHeight
|
||||
ptr = writeInt(ptr, pyramidDepth - 1); // pyramidLevels
|
||||
ptr = writeInt(ptr, 0); // useMin
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the frustum planes of the given projection matrix to the given buffer.<p>
|
||||
* Uses a different format that is friendly towards an optimized instruction-parallel
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.gl.array;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
@ -7,13 +8,13 @@ import org.lwjgl.opengl.GL45C;
|
||||
import org.lwjgl.system.Checks;
|
||||
|
||||
import dev.engine_room.flywheel.backend.gl.GlCompat;
|
||||
import dev.engine_room.flywheel.lib.util.FlwUtil;
|
||||
import net.minecraft.Util;
|
||||
|
||||
public class GlVertexArrayDSA extends GlVertexArray {
|
||||
public static final boolean SUPPORTED = isSupported();
|
||||
private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS);
|
||||
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
|
||||
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
|
||||
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
|
||||
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
|
||||
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
|
||||
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.gl.array;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
@ -12,13 +13,13 @@ import org.lwjgl.system.Checks;
|
||||
|
||||
import dev.engine_room.flywheel.backend.gl.GlCompat;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType;
|
||||
import dev.engine_room.flywheel.lib.util.FlwUtil;
|
||||
import net.minecraft.Util;
|
||||
|
||||
public abstract class GlVertexArrayGL3 extends GlVertexArray {
|
||||
private final BitSet attributeDirty = new BitSet(MAX_ATTRIBS);
|
||||
private final int[] attributeOffsets = new int[MAX_ATTRIBS];
|
||||
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
|
||||
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
|
||||
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
|
||||
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
|
||||
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
|
||||
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.gl.array;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
@ -9,13 +10,13 @@ import org.lwjgl.system.Checks;
|
||||
import dev.engine_room.flywheel.backend.gl.GlCompat;
|
||||
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType;
|
||||
import dev.engine_room.flywheel.lib.util.FlwUtil;
|
||||
import net.minecraft.Util;
|
||||
|
||||
public class GlVertexArraySeparateAttributes extends GlVertexArray {
|
||||
public static final boolean SUPPORTED = isSupported();
|
||||
private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS);
|
||||
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
|
||||
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
|
||||
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
|
||||
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
|
||||
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
|
||||
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];
|
||||
|
@ -11,6 +11,7 @@ import static org.lwjgl.opengl.GL20.glUniform4f;
|
||||
import static org.lwjgl.opengl.GL20.glUniformMatrix3fv;
|
||||
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
|
||||
import static org.lwjgl.opengl.GL30.glUniform1ui;
|
||||
import static org.lwjgl.opengl.GL30.glUniform2ui;
|
||||
import static org.lwjgl.opengl.GL31.GL_INVALID_INDEX;
|
||||
import static org.lwjgl.opengl.GL31.glGetUniformBlockIndex;
|
||||
import static org.lwjgl.opengl.GL31.glUniformBlockBinding;
|
||||
@ -118,6 +119,16 @@ public class GlProgram extends GlObject {
|
||||
glUniform1ui(uniform, value);
|
||||
}
|
||||
|
||||
public void setUVec2(String name, int x, int y) {
|
||||
int uniform = getUniformLocation(name);
|
||||
|
||||
if (uniform < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
glUniform2ui(uniform, x, y);
|
||||
}
|
||||
|
||||
public void setInt(String glslName, int value) {
|
||||
int uniform = getUniformLocation(glslName);
|
||||
|
||||
|
@ -5,9 +5,10 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.backend.glsl.error.ErrorBuilder;
|
||||
import dev.engine_room.flywheel.backend.glsl.span.Span;
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@ -38,9 +39,9 @@ sealed public interface LoadError {
|
||||
.pointAtFile(location);
|
||||
|
||||
for (var innerError : innerErrors) {
|
||||
var err = innerError.second()
|
||||
var err = innerError.getSecond()
|
||||
.generateMessage();
|
||||
out.pointAt(innerError.first())
|
||||
out.pointAt(innerError.getFirst())
|
||||
.nested(err);
|
||||
}
|
||||
|
||||
|
@ -4,13 +4,13 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.backend.glsl.parse.Import;
|
||||
import dev.engine_room.flywheel.backend.glsl.parse.ShaderField;
|
||||
@ -18,7 +18,6 @@ import dev.engine_room.flywheel.backend.glsl.parse.ShaderFunction;
|
||||
import dev.engine_room.flywheel.backend.glsl.parse.ShaderStruct;
|
||||
import dev.engine_room.flywheel.backend.glsl.span.Span;
|
||||
import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import dev.engine_room.flywheel.lib.util.ResourceUtil;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@ -172,44 +171,48 @@ public class SourceFile implements SourceComponent {
|
||||
* @param name The name of the struct to find.
|
||||
* @return null if no definition matches the name.
|
||||
*/
|
||||
public Optional<ShaderStruct> findStructByName(String name) {
|
||||
@Nullable
|
||||
public ShaderStruct findStruct(String name) {
|
||||
ShaderStruct struct = structs.get(name);
|
||||
|
||||
if (struct != null) {
|
||||
return Optional.of(struct);
|
||||
return struct;
|
||||
}
|
||||
|
||||
for (var include : included) {
|
||||
var external = include.structs.get(name);
|
||||
var external = include.findStruct(name);
|
||||
|
||||
if (external != null) {
|
||||
return Optional.of(external);
|
||||
return external;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search this file and recursively search all imports to find a function definition matching the given name.
|
||||
*
|
||||
* @param name The name of the function to find.
|
||||
* @return Optional#empty() if no definition matches the name.
|
||||
* @return null if no definition matches the name.
|
||||
*/
|
||||
public Optional<ShaderFunction> findFunction(String name) {
|
||||
ShaderFunction local = functions.get(name);
|
||||
@Nullable
|
||||
public ShaderFunction findFunction(String name) {
|
||||
ShaderFunction function = functions.get(name);
|
||||
|
||||
if (local != null) return Optional.of(local);
|
||||
if (function != null) {
|
||||
return function;
|
||||
}
|
||||
|
||||
for (var include : included) {
|
||||
var external = include.functions.get(name);
|
||||
var external = include.findFunction(name);
|
||||
|
||||
if (external != null) {
|
||||
return Optional.of(external);
|
||||
return external;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -128,10 +128,6 @@ public class SourceLines implements CharSequence {
|
||||
return raw.length();
|
||||
}
|
||||
|
||||
public int lineStartCol(int spanLine) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int lineWidth(int spanLine) {
|
||||
return lines.get(spanLine)
|
||||
.length();
|
||||
|
@ -29,7 +29,6 @@ public class ErrorBuilder {
|
||||
private final List<ErrorLine> lines = new ArrayList<>();
|
||||
|
||||
private ErrorBuilder() {
|
||||
|
||||
}
|
||||
|
||||
public static ErrorBuilder create() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.error.lines;
|
||||
|
||||
public interface ErrorLine {
|
||||
|
||||
default int neededMargin() {
|
||||
return left().length();
|
||||
}
|
||||
@ -17,6 +16,7 @@ public interface ErrorLine {
|
||||
default String left() {
|
||||
return "";
|
||||
}
|
||||
|
||||
default String right() {
|
||||
return "";
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.error.lines;
|
||||
|
||||
public record FileLine(String fileName) implements ErrorLine {
|
||||
|
||||
@Override
|
||||
public String left() {
|
||||
return "-";
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.error.lines;
|
||||
|
||||
public record HeaderLine(String level, CharSequence message) implements ErrorLine {
|
||||
|
||||
@Override
|
||||
public int neededMargin() {
|
||||
return -1;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.error.lines;
|
||||
|
||||
public record SourceLine(String number, String line) implements ErrorLine {
|
||||
|
||||
public static SourceLine numbered(int number, String line) {
|
||||
return new SourceLine(Integer.toString(number), line);
|
||||
}
|
||||
|
@ -7,11 +7,6 @@ public class SpanHighlightLine implements ErrorLine {
|
||||
line = generateUnderline(firstCol, lastCol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String left() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String right() {
|
||||
return line;
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.error.lines;
|
||||
|
||||
public record TextLine(String msg) implements ErrorLine {
|
||||
|
||||
@Override
|
||||
public String build() {
|
||||
return msg;
|
||||
|
@ -3,12 +3,12 @@ package dev.engine_room.flywheel.backend.glsl.generate;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
public record FnSignature(String returnType, String name, ImmutableList<Pair<String, String>> args) {
|
||||
|
||||
public static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
@ -26,7 +26,7 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
|
||||
|
||||
public Collection<? extends GlslExpr> createArgExpressions() {
|
||||
return args.stream()
|
||||
.map(Pair::second)
|
||||
.map(Pair::getSecond)
|
||||
.map(GlslExpr::variable)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@ -37,18 +37,20 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
|
||||
|
||||
public String fullDeclaration() {
|
||||
return returnType + ' ' + name + '(' + args.stream()
|
||||
.map(p -> p.first() + ' ' + p.second())
|
||||
.map(p -> p.getFirst() + ' ' + p.getSecond())
|
||||
.collect(Collectors.joining(", ")) + ')';
|
||||
}
|
||||
|
||||
public String signatureDeclaration() {
|
||||
return returnType + ' ' + name + '(' + args.stream()
|
||||
.map(Pair::first)
|
||||
.map(Pair::getFirst)
|
||||
.collect(Collectors.joining(", ")) + ')';
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@Nullable
|
||||
private String returnType;
|
||||
@Nullable
|
||||
private String name;
|
||||
private final ImmutableList.Builder<Pair<String, String>> args = ImmutableList.builder();
|
||||
|
||||
@ -77,5 +79,4 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
|
||||
return new FnSignature(returnType, name, args.build());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,29 +19,33 @@ public class GlslBuilder {
|
||||
return add(new GlslStruct());
|
||||
}
|
||||
|
||||
public GlslFn function() {
|
||||
return add(new GlslFn());
|
||||
}
|
||||
|
||||
public GlslVertexInput vertexInput() {
|
||||
return add(new GlslVertexInput());
|
||||
}
|
||||
|
||||
public GlslUniform uniform() {
|
||||
return add(new GlslUniform());
|
||||
}
|
||||
|
||||
public GlslUniformBlock uniformBlock() {
|
||||
return add(new GlslUniformBlock());
|
||||
}
|
||||
|
||||
public <T extends Declaration> T add(T element) {
|
||||
elements.add(element);
|
||||
return element;
|
||||
public GlslFn function() {
|
||||
return add(new GlslFn());
|
||||
}
|
||||
|
||||
public void blankLine() {
|
||||
elements.add(Separators.BLANK_LINE);
|
||||
add(Separators.BLANK_LINE);
|
||||
}
|
||||
|
||||
public void _addRaw(String sourceString) {
|
||||
elements.add(() -> sourceString);
|
||||
public void _raw(String sourceString) {
|
||||
add(new Raw(sourceString));
|
||||
}
|
||||
|
||||
public <T extends Declaration> T add(T element) {
|
||||
elements.add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
@ -54,6 +58,20 @@ public class GlslBuilder {
|
||||
String prettyPrint();
|
||||
}
|
||||
|
||||
public record Define(String name, String value) implements Declaration {
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return "#define " + name + " " + value;
|
||||
}
|
||||
}
|
||||
|
||||
public record Undef(String name) implements Declaration {
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return "#undef " + name;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Separators implements Declaration {
|
||||
BLANK_LINE(""),
|
||||
;
|
||||
@ -70,18 +88,10 @@ public class GlslBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
public record Define(String name, String value) implements Declaration {
|
||||
public record Raw(String sourceString) implements Declaration {
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return "#define " + name + " " + value;
|
||||
return sourceString;
|
||||
}
|
||||
}
|
||||
|
||||
public record Undef(String name) implements Declaration {
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return "#undef " + name;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import java.util.stream.Collectors;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public interface GlslExpr {
|
||||
|
||||
/**
|
||||
* Create a glsl variable with the given name.
|
||||
*
|
||||
@ -129,7 +128,6 @@ public interface GlslExpr {
|
||||
public String prettyPrint() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record FunctionCall(String name, Collection<? extends GlslExpr> args) implements GlslExpr {
|
||||
@ -144,7 +142,6 @@ public interface GlslExpr {
|
||||
.collect(Collectors.joining(", "));
|
||||
return name + "(" + args + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record FunctionCall0(String name) implements GlslExpr {
|
||||
@ -152,7 +149,6 @@ public interface GlslExpr {
|
||||
public String prettyPrint() {
|
||||
return name + "()";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Swizzle(GlslExpr target, String selection) implements GlslExpr {
|
||||
@ -160,7 +156,6 @@ public interface GlslExpr {
|
||||
public String prettyPrint() {
|
||||
return target.prettyPrint() + "." + selection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Access(GlslExpr target, String argName) implements GlslExpr {
|
||||
@ -168,7 +163,6 @@ public interface GlslExpr {
|
||||
public String prettyPrint() {
|
||||
return target.prettyPrint() + "." + argName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record Clamp(GlslExpr value, GlslExpr from, GlslExpr to) implements GlslExpr {
|
||||
|
@ -5,24 +5,25 @@ import java.util.function.Consumer;
|
||||
import dev.engine_room.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslFn implements GlslBuilder.Declaration {
|
||||
private GlslBlock body = new GlslBlock();
|
||||
private FnSignature signature;
|
||||
private GlslBlock body = new GlslBlock();
|
||||
|
||||
public GlslFn signature(FnSignature signature) {
|
||||
this.signature = signature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlslFn body(Consumer<GlslBlock> f) {
|
||||
f.accept(body);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlslFn body(GlslBlock block) {
|
||||
body = block;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlslFn body(Consumer<GlslBlock> f) {
|
||||
f.accept(body);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
%s {
|
||||
|
@ -4,15 +4,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslStruct implements GlslBuilder.Declaration {
|
||||
|
||||
private final List<Pair<String, String>> fields = new ArrayList<>();
|
||||
private String name;
|
||||
private final List<Pair<String, String>> fields = new ArrayList<>();
|
||||
|
||||
public GlslStruct setName(String name) {
|
||||
public GlslStruct name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
@ -22,12 +22,6 @@ public class GlslStruct implements GlslBuilder.Declaration {
|
||||
return this;
|
||||
}
|
||||
|
||||
private String buildFields() {
|
||||
return fields.stream()
|
||||
.map(p -> p.first() + ' ' + p.second() + ';')
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
@ -35,4 +29,10 @@ public class GlslStruct implements GlslBuilder.Declaration {
|
||||
%s
|
||||
};""".formatted(name, StringUtil.indent(buildFields(), 4));
|
||||
}
|
||||
|
||||
private String buildFields() {
|
||||
return fields.stream()
|
||||
.map(p -> p.getFirst() + ' ' + p.getSecond() + ';')
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,17 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslSwitch implements GlslStmt {
|
||||
private final GlslExpr on;
|
||||
|
||||
private final List<Pair<GlslExpr, GlslBlock>> cases = new ArrayList<>();
|
||||
@Nullable
|
||||
private GlslBlock defaultCase = null;
|
||||
|
||||
private GlslSwitch(GlslExpr on) {
|
||||
@ -52,9 +56,9 @@ public class GlslSwitch implements GlslStmt {
|
||||
}
|
||||
|
||||
private static String prettyPrintCase(Pair<GlslExpr, GlslBlock> p) {
|
||||
var variant = p.first()
|
||||
var variant = p.getFirst()
|
||||
.prettyPrint();
|
||||
var block = p.second()
|
||||
var block = p.getSecond()
|
||||
.prettyPrint();
|
||||
return """
|
||||
case %s:
|
||||
|
@ -0,0 +1,21 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.generate;
|
||||
|
||||
public class GlslUniform implements GlslBuilder.Declaration {
|
||||
private String type;
|
||||
private String name;
|
||||
|
||||
public GlslUniform type(String typeName) {
|
||||
type = typeName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlslUniform name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return "uniform " + type + " " + name + ";";
|
||||
}
|
||||
}
|
@ -4,7 +4,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.Pair;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
|
||||
import dev.engine_room.flywheel.lib.util.StringUtil;
|
||||
|
||||
public class GlslUniformBlock implements GlslBuilder.Declaration {
|
||||
@ -12,20 +13,6 @@ public class GlslUniformBlock implements GlslBuilder.Declaration {
|
||||
private String name;
|
||||
private final List<Pair<String, String>> members = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
layout(%s) uniform %s {
|
||||
%s
|
||||
};""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4));
|
||||
}
|
||||
|
||||
private String formatMembers() {
|
||||
return members.stream()
|
||||
.map(p -> p.first() + " " + p.second() + ";")
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
public GlslUniformBlock layout(String qualifier) {
|
||||
this.qualifier = qualifier;
|
||||
return this;
|
||||
@ -40,4 +27,18 @@ public class GlslUniformBlock implements GlslBuilder.Declaration {
|
||||
members.add(Pair.of(typeName, variableName));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String prettyPrint() {
|
||||
return """
|
||||
layout(%s) uniform %s {
|
||||
%s
|
||||
};""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4));
|
||||
}
|
||||
|
||||
private String formatMembers() {
|
||||
return members.stream()
|
||||
.map(p -> p.getFirst() + " " + p.getSecond() + ";")
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.generate;
|
||||
|
||||
public class GlslVertexInput implements GlslBuilder.Declaration {
|
||||
|
||||
private int binding;
|
||||
private String type;
|
||||
private String name;
|
||||
|
@ -12,17 +12,17 @@ public record Import(Span self, Span file) {
|
||||
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*include\\s+\"(.*)\"", Pattern.MULTILINE);
|
||||
|
||||
/**
|
||||
* Scan the source for {@code #use "..."} directives.
|
||||
* Scan the source for {@code #include "..."} directives.
|
||||
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
|
||||
*/
|
||||
public static ImmutableList<Import> parseImports(SourceLines source) {
|
||||
Matcher uses = PATTERN.matcher(source);
|
||||
Matcher matcher = PATTERN.matcher(source);
|
||||
|
||||
var imports = ImmutableList.<Import>builder();
|
||||
|
||||
while (uses.find()) {
|
||||
Span use = Span.fromMatcher(source, uses);
|
||||
Span file = Span.fromMatcher(source, uses, 1);
|
||||
while (matcher.find()) {
|
||||
Span use = Span.fromMatcher(source, matcher);
|
||||
Span file = Span.fromMatcher(source, matcher, 1);
|
||||
|
||||
imports.add(new Import(use, file));
|
||||
}
|
||||
|
@ -13,17 +13,19 @@ import dev.engine_room.flywheel.backend.glsl.span.Span;
|
||||
public class ShaderField {
|
||||
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 self;
|
||||
public final Span location;
|
||||
public final @Nullable Decoration decoration;
|
||||
public final Span qualifierSpan;
|
||||
@Nullable
|
||||
public final Qualifier qualifier;
|
||||
public final Span type;
|
||||
public final Span name;
|
||||
public final Span self;
|
||||
|
||||
public ShaderField(Span self, Span location, Span inOut, Span type, Span name) {
|
||||
public ShaderField(Span self, Span location, Span qualifier, Span type, Span name) {
|
||||
this.self = self;
|
||||
|
||||
this.location = location;
|
||||
this.decoration = Decoration.fromSpan(inOut);
|
||||
this.qualifierSpan = qualifier;
|
||||
this.qualifier = Qualifier.fromSpan(qualifier);
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
@ -48,14 +50,14 @@ public class ShaderField {
|
||||
return fields.build();
|
||||
}
|
||||
|
||||
public enum Decoration {
|
||||
public enum Qualifier {
|
||||
IN,
|
||||
OUT,
|
||||
FLAT,
|
||||
;
|
||||
|
||||
@Nullable
|
||||
public static Decoration fromSpan(Span span) {
|
||||
public static Qualifier fromSpan(Span span) {
|
||||
return switch (span.toString()) {
|
||||
case "in" -> IN;
|
||||
case "out" -> OUT;
|
||||
|
@ -17,17 +17,16 @@ import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
|
||||
public class ShaderFunction {
|
||||
// https://regexr.com/60n3d
|
||||
public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
|
||||
public static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
|
||||
public static final Pattern ASSIGNMENT_PATTERN = Pattern.compile("(\\w+)\\s*=");
|
||||
|
||||
public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
|
||||
public static final Pattern assignment = Pattern.compile("(\\w+)\\s*=");
|
||||
public final Span self;
|
||||
public final Span type;
|
||||
public final Span name;
|
||||
public final Span args;
|
||||
public final Span body;
|
||||
|
||||
private final Span type;
|
||||
private final Span name;
|
||||
private final Span args;
|
||||
private final Span body;
|
||||
|
||||
private final ImmutableList<ShaderVariable> parameters;
|
||||
public final ImmutableList<ShaderVariable> parameters;
|
||||
|
||||
public ShaderFunction(Span self, Span type, Span name, Span args, Span body) {
|
||||
this.self = self;
|
||||
@ -95,22 +94,6 @@ public class ShaderFunction {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Span getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Span getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Span getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public Span getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String call(String... args) {
|
||||
return name + "(" + String.join(", ", args) + ")";
|
||||
}
|
||||
@ -119,18 +102,10 @@ public class ShaderFunction {
|
||||
return parameters.get(index).type;
|
||||
}
|
||||
|
||||
public ImmutableList<ShaderVariable> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public String returnTypeName() {
|
||||
return type.get();
|
||||
}
|
||||
|
||||
protected ImmutableList<ShaderVariable> parseArguments() {
|
||||
if (args.isErr() || args.isEmpty()) return ImmutableList.of();
|
||||
|
||||
Matcher arguments = argument.matcher(args.get());
|
||||
Matcher arguments = ARGUMENT_PATTERN.matcher(args.get());
|
||||
|
||||
ImmutableList.Builder<ShaderVariable> builder = ImmutableList.builder();
|
||||
|
||||
|
@ -13,19 +13,20 @@ public class ShaderStruct {
|
||||
// https://regexr.com/61rpe
|
||||
public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w_]*)\\s*\\{(.*?)}\\s*([\\w_]*)?\\s*;\\s", Pattern.DOTALL);
|
||||
|
||||
public final Span self;
|
||||
public final Span name;
|
||||
public final Span body;
|
||||
public final Span self;
|
||||
public final Span variableName;
|
||||
|
||||
private final ImmutableList<StructField> fields;
|
||||
private final ImmutableMap<String, Span> fields2Types;
|
||||
public final ImmutableList<StructField> fields;
|
||||
public final ImmutableMap<String, Span> fields2Types;
|
||||
|
||||
public ShaderStruct(Span self, Span name, Span body, Span variableName) {
|
||||
this.self = self;
|
||||
this.name = name;
|
||||
this.body = body;
|
||||
this.variableName = variableName;
|
||||
|
||||
this.fields = parseFields();
|
||||
this.fields2Types = createTypeLookup();
|
||||
}
|
||||
@ -51,29 +52,8 @@ public class ShaderStruct {
|
||||
return structs.build();
|
||||
}
|
||||
|
||||
public Span getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Span getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public ImmutableList<StructField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
private ImmutableMap<String, Span> createTypeLookup() {
|
||||
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
|
||||
for (StructField field : fields) {
|
||||
lookup.put(field.name.get(), field.type);
|
||||
}
|
||||
|
||||
return lookup.build();
|
||||
}
|
||||
|
||||
private ImmutableList<StructField> parseFields() {
|
||||
Matcher matcher = StructField.fieldPattern.matcher(body);
|
||||
Matcher matcher = StructField.PATTERN.matcher(body);
|
||||
|
||||
ImmutableList.Builder<StructField> fields = ImmutableList.builder();
|
||||
|
||||
@ -88,6 +68,15 @@ public class ShaderStruct {
|
||||
return fields.build();
|
||||
}
|
||||
|
||||
private ImmutableMap<String, Span> createTypeLookup() {
|
||||
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
|
||||
for (StructField field : fields) {
|
||||
lookup.put(field.name.get(), field.type);
|
||||
}
|
||||
|
||||
return lookup.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "struct " + name;
|
||||
|
@ -1,21 +1,23 @@
|
||||
package dev.engine_room.flywheel.backend.glsl.parse;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.backend.glsl.span.Span;
|
||||
|
||||
public class ShaderVariable {
|
||||
|
||||
public final Span self;
|
||||
public final Span qualifierSpan;
|
||||
@Nullable
|
||||
public final Qualifier qualifier;
|
||||
public final Span type;
|
||||
public final Span name;
|
||||
public final Qualifier qualifier;
|
||||
public final Span self;
|
||||
|
||||
public ShaderVariable(Span self, Span qualifier, Span type, Span name) {
|
||||
this.self = self;
|
||||
this.qualifierSpan = qualifier;
|
||||
this.qualifier = Qualifier.fromSpan(qualifierSpan);
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.qualifier = Qualifier.fromSpan(qualifierSpan);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -27,9 +29,9 @@ public class ShaderVariable {
|
||||
NONE,
|
||||
IN,
|
||||
OUT,
|
||||
INOUT,
|
||||
ERROR;
|
||||
INOUT;
|
||||
|
||||
@Nullable
|
||||
public static Qualifier fromSpan(Span s) {
|
||||
String span = s.toString();
|
||||
|
||||
@ -38,7 +40,7 @@ public class ShaderVariable {
|
||||
case "in" -> IN;
|
||||
case "inout" -> INOUT;
|
||||
case "out" -> OUT;
|
||||
default -> ERROR;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ import java.util.regex.Pattern;
|
||||
import dev.engine_room.flywheel.backend.glsl.span.Span;
|
||||
|
||||
public class StructField {
|
||||
public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);");
|
||||
public final Span self;
|
||||
public static final Pattern PATTERN = Pattern.compile("(\\S+)\\s*(\\S+);");
|
||||
|
||||
public Span type;
|
||||
public Span name;
|
||||
public final Span self;
|
||||
public final Span type;
|
||||
public final Span name;
|
||||
|
||||
public StructField(Span self, Span type, Span name) {
|
||||
this.self = self;
|
||||
@ -17,14 +17,6 @@ public class StructField {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Span getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Span getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + " " + name;
|
||||
|
@ -3,7 +3,6 @@ package dev.engine_room.flywheel.backend.glsl.span;
|
||||
import dev.engine_room.flywheel.backend.glsl.SourceLines;
|
||||
|
||||
public class StringSpan extends Span {
|
||||
|
||||
public StringSpan(SourceLines in, int start, int end) {
|
||||
super(in, start, end);
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
#define _FLW_INSTANCE_BUFFER_BINDING 0
|
||||
#define _FLW_TARGET_BUFFER_BINDING 1
|
||||
#define _FLW_MODEL_INDEX_BUFFER_BINDING 2
|
||||
#define _FLW_MODEL_BUFFER_BINDING 3
|
||||
#define _FLW_DRAW_BUFFER_BINDING 4
|
||||
// Per culling group
|
||||
#define _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING 0// cull
|
||||
#define _FLW_INSTANCE_BUFFER_BINDING 1// cull, draw
|
||||
#define _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING 2// cull, draw
|
||||
#define _FLW_MODEL_BUFFER_BINDING 3// cull, apply
|
||||
#define _FLW_DRAW_BUFFER_BINDING 4// apply, draw
|
||||
|
||||
// Global to the engine
|
||||
#define _FLW_LIGHT_LUT_BUFFER_BINDING 5
|
||||
#define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6
|
||||
|
||||
#define _FLW_MATRIX_BUFFER_BINDING 7
|
||||
|
@ -4,24 +4,31 @@
|
||||
#include "flywheel:util/matrix.glsl"
|
||||
#include "flywheel:internal/indirect/matrices.glsl"
|
||||
|
||||
layout(local_size_x = _FLW_SUBGROUP_SIZE) in;
|
||||
layout(local_size_x = 32) in;
|
||||
|
||||
layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict writeonly buffer TargetBuffer {
|
||||
layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict writeonly buffer TargetBuffer {
|
||||
uint _flw_instanceIndices[];
|
||||
};
|
||||
|
||||
layout(std430, binding = _FLW_MODEL_INDEX_BUFFER_BINDING) restrict readonly buffer ModelIndexBuffer {
|
||||
uint _flw_modelIndices[];
|
||||
// High 6 bits for the number of instances in the page.
|
||||
const uint _FLW_PAGE_COUNT_OFFSET = 26u;
|
||||
// Bottom 26 bits for the model index.
|
||||
const uint _FLW_MODEL_INDEX_MASK = 0x3FFFFFF;
|
||||
|
||||
layout(std430, binding = _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING) restrict readonly buffer PageFrameDescriptorBuffer {
|
||||
uint _flw_pageFrameDescriptors[];
|
||||
};
|
||||
|
||||
layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer {
|
||||
ModelDescriptor _flw_models[];
|
||||
};
|
||||
|
||||
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer {
|
||||
layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict readonly buffer MatrixBuffer {
|
||||
Matrices _flw_matrices[];
|
||||
};
|
||||
|
||||
layout(binding = 0) uniform sampler2D _flw_depthPyramid;
|
||||
|
||||
// Disgustingly vectorized sphere frustum intersection taking advantage of ahead of time packing.
|
||||
// Only uses 6 fmas and some boolean ops.
|
||||
// See also:
|
||||
@ -35,6 +42,29 @@ bool _flw_testSphere(vec3 center, float radius) {
|
||||
return all(xyInside) && all(zInside);
|
||||
}
|
||||
|
||||
bool projectSphere(vec3 c, float r, float znear, float P00, float P11, out vec4 aabb) {
|
||||
// Closest point on the sphere is between the camera and the near plane, don't even attempt to cull.
|
||||
if (c.z + r > -znear) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vec3 cr = c * r;
|
||||
float czr2 = c.z * c.z - r * r;
|
||||
|
||||
float vx = sqrt(c.x * c.x + czr2);
|
||||
float minx = (vx * c.x - cr.z) / (vx * c.z + cr.x);
|
||||
float maxx = (vx * c.x + cr.z) / (vx * c.z - cr.x);
|
||||
|
||||
float vy = sqrt(c.y * c.y + czr2);
|
||||
float miny = (vy * c.y - cr.z) / (vy * c.z + cr.y);
|
||||
float maxy = (vy * c.y + cr.z) / (vy * c.z - cr.y);
|
||||
|
||||
aabb = vec4(minx * P00, miny * P11, maxx * P00, maxy * P11);
|
||||
aabb = aabb.xwzy * vec4(-0.5f, -0.5f, -0.5f, -0.5f) + vec4(0.5f); // clip space -> uv space
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _flw_isVisible(uint instanceIndex, uint modelIndex) {
|
||||
uint matrixIndex = _flw_models[modelIndex].matrixIndex;
|
||||
BoundingSphere sphere = _flw_models[modelIndex].boundingSphere;
|
||||
@ -51,17 +81,64 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) {
|
||||
transformBoundingSphere(_flw_matrices[matrixIndex].pose, center, radius);
|
||||
}
|
||||
|
||||
return _flw_testSphere(center, radius);
|
||||
bool isVisible = _flw_testSphere(center, radius);
|
||||
|
||||
if (isVisible) {
|
||||
transformBoundingSphere(flw_view, center, radius);
|
||||
|
||||
vec4 aabb;
|
||||
if (projectSphere(center, radius, _flw_cullData.znear, _flw_cullData.P00, _flw_cullData.P11, aabb))
|
||||
{
|
||||
float width = (aabb.z - aabb.x) * _flw_cullData.pyramidWidth;
|
||||
float height = (aabb.w - aabb.y) * _flw_cullData.pyramidHeight;
|
||||
|
||||
int level = clamp(int(ceil(log2(max(width, height)))), 0, _flw_cullData.pyramidLevels);
|
||||
|
||||
ivec2 levelSize = textureSize(_flw_depthPyramid, level);
|
||||
|
||||
ivec4 levelSizePair = ivec4(levelSize, levelSize);
|
||||
|
||||
ivec4 bounds = ivec4(aabb * vec4(levelSizePair));
|
||||
|
||||
float depth01 = texelFetch(_flw_depthPyramid, bounds.xw, level).r;
|
||||
float depth11 = texelFetch(_flw_depthPyramid, bounds.zw, level).r;
|
||||
float depth10 = texelFetch(_flw_depthPyramid, bounds.zy, level).r;
|
||||
float depth00 = texelFetch(_flw_depthPyramid, bounds.xy, level).r;
|
||||
|
||||
float depth;
|
||||
if (_flw_cullData.useMin == 0) {
|
||||
depth = max(max(depth00, depth01), max(depth10, depth11));
|
||||
} else {
|
||||
depth = min(min(depth00, depth01), min(depth10, depth11));
|
||||
}
|
||||
|
||||
float depthSphere = 1. + _flw_cullData.znear / (center.z + radius);
|
||||
|
||||
isVisible = isVisible && depthSphere <= depth;
|
||||
}
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint instanceIndex = gl_GlobalInvocationID.x;
|
||||
uint pageIndex = gl_WorkGroupID.x;
|
||||
|
||||
if (instanceIndex >= _flw_modelIndices.length()) {
|
||||
if (pageIndex >= _flw_pageFrameDescriptors.length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint modelIndex = _flw_modelIndices[instanceIndex];
|
||||
uint packedModelIndexAndCount = _flw_pageFrameDescriptors[pageIndex];
|
||||
|
||||
uint pageInstanceCount = packedModelIndexAndCount >> _FLW_PAGE_COUNT_OFFSET;
|
||||
|
||||
if (gl_LocalInvocationID.x >= pageInstanceCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint instanceIndex = gl_GlobalInvocationID.x;
|
||||
|
||||
uint modelIndex = packedModelIndexAndCount & _FLW_MODEL_INDEX_MASK;
|
||||
|
||||
if (_flw_isVisible(instanceIndex, modelIndex)) {
|
||||
uint localIndex = atomicAdd(_flw_models[modelIndex].instanceCount, 1);
|
||||
|
@ -0,0 +1,33 @@
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
uniform uint max_mip_level;
|
||||
|
||||
/// Generates a hierarchical depth buffer.
|
||||
/// Based on FidelityFX SPD v2.1 https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/sdk/include/FidelityFX/gpu/spd/ffx_spd.h#L528
|
||||
/// Based on Bevy's more readable implementation https://github.com/JMS55/bevy/blob/ca2c8e63b9562f88c8cd7e1d88a17a4eea20aaf4/crates/bevy_pbr/src/meshlet/downsample_depth.wgsl
|
||||
|
||||
shared float[16][16] intermediate_memory;
|
||||
|
||||
// These are builtins in wgsl but we can trivially emulate them.
|
||||
uint extractBits(uint e, uint offset, uint count) {
|
||||
return (e >> offset) & ((1u << count) - 1u);
|
||||
}
|
||||
|
||||
uint insertBits(uint e, uint newbits, uint offset, uint count) {
|
||||
uint countMask = ((1u << count) - 1u);
|
||||
// zero out the bits we're going to replace first
|
||||
return (e & ~(countMask << offset)) | ((newbits & countMask) << offset);
|
||||
}
|
||||
|
||||
// I do not understand how this works but it seems cool.
|
||||
uvec2 remap_for_wave_reduction(uint a) {
|
||||
return uvec2(
|
||||
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
|
||||
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
|
||||
);
|
||||
}
|
||||
|
||||
float reduce_4(vec4 v) {
|
||||
return max(max(v.x, v.y), max(v.z, v.w));
|
||||
}
|
||||
|
@ -0,0 +1,150 @@
|
||||
#include "flywheel:internal/indirect/downsample.glsl"
|
||||
|
||||
layout(binding = 0) uniform sampler2D mip_0;
|
||||
layout(binding = 1, r32f) uniform restrict writeonly image2D mip_1;
|
||||
layout(binding = 2, r32f) uniform restrict writeonly image2D mip_2;
|
||||
layout(binding = 3, r32f) uniform restrict writeonly image2D mip_3;
|
||||
layout(binding = 4, r32f) uniform restrict writeonly image2D mip_4;
|
||||
layout(binding = 5, r32f) uniform restrict writeonly image2D mip_5;
|
||||
layout(binding = 6, r32f) uniform restrict writeonly image2D mip_6;
|
||||
|
||||
float reduce_load_mip_0(uvec2 tex) {
|
||||
// NOTE: mip_0 is the actual depth buffer, and mip_1 is the "base" of our depth pyramid and has the next
|
||||
// smallest Po2 dimensions to mip_1's dimensions. We dispatch enough invocations to cover the entire mip_1
|
||||
// and will very likely oversample mip_0, but that's okay because we need to ensure conservative coverage.
|
||||
// All following mip levels are proper halvings of their parents and will not waste any work.
|
||||
vec2 uv = (vec2(tex) + 0.5) / vec2(imageSize(mip_1)) * 0.5;
|
||||
return reduce_4(textureGather(mip_0, uv));
|
||||
}
|
||||
|
||||
void downsample_mips_0_and_1(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) {
|
||||
vec4 v;
|
||||
|
||||
ivec2 tex = workgroup_id * 64 + ivec2(x * 2u, y * 2u);
|
||||
ivec2 pix = workgroup_id * 32 + ivec2(x, y);
|
||||
v[0] = reduce_load_mip_0(tex);
|
||||
imageStore(mip_1, pix, vec4(v[0]));
|
||||
|
||||
tex = workgroup_id * 64 + ivec2(x * 2u + 32u, y * 2u);
|
||||
pix = workgroup_id * 32 + ivec2(x + 16u, y);
|
||||
v[1] = reduce_load_mip_0(tex);
|
||||
imageStore(mip_1, pix, vec4(v[1]));
|
||||
|
||||
tex = workgroup_id * 64 + ivec2(x * 2u, y * 2u + 32u);
|
||||
pix = workgroup_id * 32 + ivec2(x, y + 16u);
|
||||
v[2] = reduce_load_mip_0(tex);
|
||||
imageStore(mip_1, pix, vec4(v[2]));
|
||||
|
||||
tex = workgroup_id * 64 + ivec2(x * 2u + 32u, y * 2u + 32u);
|
||||
pix = workgroup_id * 32 + ivec2(x + 16u, y + 16u);
|
||||
v[3] = reduce_load_mip_0(tex);
|
||||
imageStore(mip_1, pix, vec4(v[3]));
|
||||
|
||||
if (max_mip_level <= 1u) { return; }
|
||||
|
||||
for (uint i = 0u; i < 4u; i++) {
|
||||
intermediate_memory[x][y] = v[i];
|
||||
barrier();
|
||||
if (local_invocation_index < 64u) {
|
||||
v[i] = reduce_4(vec4(
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 1u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 1u]
|
||||
));
|
||||
pix = (workgroup_id * 16) + ivec2(
|
||||
x + (i % 2u) * 8u,
|
||||
y + (i / 2u) * 8u
|
||||
);
|
||||
imageStore(mip_2, pix, vec4(v[i]));
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
|
||||
if (local_invocation_index < 64u) {
|
||||
intermediate_memory[x + 0u][y + 0u] = v[0];
|
||||
intermediate_memory[x + 8u][y + 0u] = v[1];
|
||||
intermediate_memory[x + 0u][y + 8u] = v[2];
|
||||
intermediate_memory[x + 8u][y + 8u] = v[3];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void downsample_mip_2(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) {
|
||||
if (local_invocation_index < 64u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 1u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 1u]
|
||||
));
|
||||
imageStore(mip_3, (workgroup_id * 8) + ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x * 2u + y % 2u][y * 2u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_3(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) {
|
||||
if (local_invocation_index < 16u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 4u + 0u + 0u][y * 4u + 0u],
|
||||
intermediate_memory[x * 4u + 2u + 0u][y * 4u + 0u],
|
||||
intermediate_memory[x * 4u + 0u + 1u][y * 4u + 2u],
|
||||
intermediate_memory[x * 4u + 2u + 1u][y * 4u + 2u]
|
||||
));
|
||||
imageStore(mip_4, (workgroup_id * 4) + ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x * 4u + y][y * 4u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_4(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) {
|
||||
if (local_invocation_index < 4u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 8u + 0u + 0u + y * 2u][y * 8u + 0u],
|
||||
intermediate_memory[x * 8u + 4u + 0u + y * 2u][y * 8u + 0u],
|
||||
intermediate_memory[x * 8u + 0u + 1u + y * 2u][y * 8u + 4u],
|
||||
intermediate_memory[x * 8u + 4u + 1u + y * 2u][y * 8u + 4u]
|
||||
));
|
||||
imageStore(mip_5, (workgroup_id * 2) + ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x + y * 2u][0u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_5(ivec2 workgroup_id, uint local_invocation_index) {
|
||||
if (local_invocation_index < 1u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[0u][0u],
|
||||
intermediate_memory[1u][0u],
|
||||
intermediate_memory[2u][0u],
|
||||
intermediate_memory[3u][0u]
|
||||
));
|
||||
imageStore(mip_6, workgroup_id, vec4(v));
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mips_2_to_5(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) {
|
||||
if (max_mip_level <= 2u) { return; }
|
||||
barrier();
|
||||
downsample_mip_2(x, y, workgroup_id, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 3u) { return; }
|
||||
barrier();
|
||||
downsample_mip_3(x, y, workgroup_id, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 4u) { return; }
|
||||
barrier();
|
||||
downsample_mip_4(x, y, workgroup_id, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 5u) { return; }
|
||||
barrier();
|
||||
downsample_mip_5(workgroup_id, local_invocation_index);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec2 sub_xy = remap_for_wave_reduction(gl_LocalInvocationIndex % 64u);
|
||||
uint x = sub_xy.x + 8u * ((gl_LocalInvocationIndex >> 6u) % 2u);
|
||||
uint y = sub_xy.y + 8u * (gl_LocalInvocationIndex >> 7u);
|
||||
|
||||
downsample_mips_0_and_1(x, y, ivec2(gl_WorkGroupID.xy), gl_LocalInvocationIndex);
|
||||
|
||||
downsample_mips_2_to_5(x, y, ivec2(gl_WorkGroupID.xy), gl_LocalInvocationIndex);
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
#include "flywheel:internal/indirect/downsample.glsl"
|
||||
|
||||
layout(binding = 0, r32f) uniform restrict readonly image2D mip_6;
|
||||
layout(binding = 1, r32f) uniform restrict writeonly image2D mip_7;
|
||||
layout(binding = 2, r32f) uniform restrict writeonly image2D mip_8;
|
||||
layout(binding = 3, r32f) uniform restrict writeonly image2D mip_9;
|
||||
layout(binding = 4, r32f) uniform restrict writeonly image2D mip_10;
|
||||
layout(binding = 5, r32f) uniform restrict writeonly image2D mip_11;
|
||||
layout(binding = 6, r32f) uniform restrict writeonly image2D mip_12;
|
||||
|
||||
float reduce_load_mip_6(ivec2 tex) {
|
||||
// NOTE: We could bind mip_6 as a sampler2D and use textureGather,
|
||||
// but it's already written to as an image in the first pass so I think this is fine.
|
||||
return reduce_4(vec4(
|
||||
imageLoad(mip_6, tex + ivec2(0u, 0u)).r,
|
||||
imageLoad(mip_6, tex + ivec2(0u, 1u)).r,
|
||||
imageLoad(mip_6, tex + ivec2(1u, 0u)).r,
|
||||
imageLoad(mip_6, tex + ivec2(1u, 1u)).r
|
||||
));
|
||||
}
|
||||
|
||||
void downsample_mips_6_and_7(uint x, uint y) {
|
||||
vec4 v;
|
||||
|
||||
ivec2 tex = ivec2(x * 4u + 0u, y * 4u + 0u);
|
||||
ivec2 pix = ivec2(x * 2u + 0u, y * 2u + 0u);
|
||||
v[0] = reduce_load_mip_6(tex);
|
||||
imageStore(mip_7, pix, vec4(v[0]));
|
||||
|
||||
tex = ivec2(x * 4u + 2u, y * 4u + 0u);
|
||||
pix = ivec2(x * 2u + 1u, y * 2u + 0u);
|
||||
v[1] = reduce_load_mip_6(tex);
|
||||
imageStore(mip_7, pix, vec4(v[1]));
|
||||
|
||||
tex = ivec2(x * 4u + 0u, y * 4u + 2u);
|
||||
pix = ivec2(x * 2u + 0u, y * 2u + 1u);
|
||||
v[2] = reduce_load_mip_6(tex);
|
||||
imageStore(mip_7, pix, vec4(v[2]));
|
||||
|
||||
tex = ivec2(x * 4u + 2u, y * 4u + 2u);
|
||||
pix = ivec2(x * 2u + 1u, y * 2u + 1u);
|
||||
v[3] = reduce_load_mip_6(tex);
|
||||
imageStore(mip_7, pix, vec4(v[3]));
|
||||
|
||||
if (max_mip_level <= 7u) { return; }
|
||||
|
||||
float vr = reduce_4(v);
|
||||
imageStore(mip_8, ivec2(x, y), vec4(vr));
|
||||
intermediate_memory[x][y] = vr;
|
||||
}
|
||||
|
||||
|
||||
void downsample_mip_8(uint x, uint y, uint local_invocation_index) {
|
||||
if (local_invocation_index < 64u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 0u],
|
||||
intermediate_memory[x * 2u + 0u][y * 2u + 1u],
|
||||
intermediate_memory[x * 2u + 1u][y * 2u + 1u]
|
||||
));
|
||||
imageStore(mip_9, ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x * 2u + y % 2u][y * 2u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_9(uint x, uint y, uint local_invocation_index) {
|
||||
if (local_invocation_index < 16u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 4u + 0u + 0u][y * 4u + 0u],
|
||||
intermediate_memory[x * 4u + 2u + 0u][y * 4u + 0u],
|
||||
intermediate_memory[x * 4u + 0u + 1u][y * 4u + 2u],
|
||||
intermediate_memory[x * 4u + 2u + 1u][y * 4u + 2u]
|
||||
));
|
||||
imageStore(mip_10, ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x * 4u + y][y * 4u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_10(uint x, uint y, uint local_invocation_index) {
|
||||
if (local_invocation_index < 4u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[x * 8u + 0u + 0u + y * 2u][y * 8u + 0u],
|
||||
intermediate_memory[x * 8u + 4u + 0u + y * 2u][y * 8u + 0u],
|
||||
intermediate_memory[x * 8u + 0u + 1u + y * 2u][y * 8u + 4u],
|
||||
intermediate_memory[x * 8u + 4u + 1u + y * 2u][y * 8u + 4u]
|
||||
));
|
||||
imageStore(mip_11, ivec2(x, y), vec4(v));
|
||||
intermediate_memory[x + y * 2u][0u] = v;
|
||||
}
|
||||
}
|
||||
|
||||
void downsample_mip_11(uint local_invocation_index) {
|
||||
if (local_invocation_index < 1u) {
|
||||
float v = reduce_4(vec4(
|
||||
intermediate_memory[0u][0u],
|
||||
intermediate_memory[1u][0u],
|
||||
intermediate_memory[2u][0u],
|
||||
intermediate_memory[3u][0u]
|
||||
));
|
||||
|
||||
imageStore(mip_12, ivec2(0u, 0u), vec4(v));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void downsample_mips_8_to_11(uint x, uint y, uint local_invocation_index) {
|
||||
if (max_mip_level <= 8u) { return; }
|
||||
barrier();
|
||||
downsample_mip_8(x, y, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 9u) { return; }
|
||||
barrier();
|
||||
downsample_mip_9(x, y, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 10u) { return; }
|
||||
barrier();
|
||||
downsample_mip_10(x, y, local_invocation_index);
|
||||
|
||||
if (max_mip_level <= 11u) { return; }
|
||||
barrier();
|
||||
downsample_mip_11(local_invocation_index);
|
||||
}
|
||||
|
||||
void downsample_depth_second() {
|
||||
uvec2 sub_xy = remap_for_wave_reduction(gl_LocalInvocationIndex % 64u);
|
||||
uint x = sub_xy.x + 8u * ((gl_LocalInvocationIndex >> 6u) % 2u);
|
||||
uint y = sub_xy.y + 8u * (gl_LocalInvocationIndex >> 7u);
|
||||
|
||||
downsample_mips_6_and_7(x, y);
|
||||
|
||||
downsample_mips_8_to_11(x, y, gl_LocalInvocationIndex);
|
||||
}
|
||||
|
||||
void main() {
|
||||
downsample_depth_second();
|
||||
}
|
@ -5,7 +5,7 @@
|
||||
#include "flywheel:internal/indirect/light.glsl"
|
||||
#include "flywheel:internal/indirect/matrices.glsl"
|
||||
|
||||
layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
|
||||
layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
|
||||
uint _flw_instanceIndices[];
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,17 @@ struct FrustumPlanes {
|
||||
vec2 zW; // <nz.w, pz.w>
|
||||
};
|
||||
|
||||
struct _FlwCullData {
|
||||
float znear;
|
||||
float zfar;
|
||||
float P00;
|
||||
float P11;
|
||||
float pyramidWidth;
|
||||
float pyramidHeight;
|
||||
int pyramidLevels;
|
||||
uint useMin;
|
||||
};
|
||||
|
||||
layout(std140) uniform _FlwFrameUniforms {
|
||||
FrustumPlanes flw_frustumPlanes;
|
||||
|
||||
@ -47,6 +58,8 @@ layout(std140) uniform _FlwFrameUniforms {
|
||||
uint flw_cameraInBlock;
|
||||
|
||||
uint _flw_debugMode;
|
||||
|
||||
_FlwCullData _flw_cullData;
|
||||
};
|
||||
|
||||
#define flw_renderOrigin (_flw_renderOrigin.xyz)
|
||||
|
@ -12,6 +12,26 @@ import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
|
||||
|
||||
public final class InstanceTypes {
|
||||
public static final InstanceType<TransformedInstance> TRANSFORMED = SimpleInstanceType.builder(TransformedInstance::new)
|
||||
.layout(LayoutBuilder.create()
|
||||
.vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4)
|
||||
.vector("overlay", IntegerRepr.SHORT, 2)
|
||||
.vector("light", FloatRepr.UNSIGNED_SHORT, 2)
|
||||
.matrix("pose", FloatRepr.FLOAT, 4)
|
||||
.build())
|
||||
.writer((ptr, instance) -> {
|
||||
MemoryUtil.memPutByte(ptr, instance.red);
|
||||
MemoryUtil.memPutByte(ptr + 1, instance.green);
|
||||
MemoryUtil.memPutByte(ptr + 2, instance.blue);
|
||||
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
|
||||
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
|
||||
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
|
||||
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
|
||||
})
|
||||
.vertexShader(Flywheel.rl("instance/transformed.vert"))
|
||||
.cullShader(Flywheel.rl("instance/cull/transformed.glsl"))
|
||||
.register();
|
||||
|
||||
public static final InstanceType<PosedInstance> POSED = SimpleInstanceType.builder(PosedInstance::new)
|
||||
.layout(LayoutBuilder.create()
|
||||
.vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4)
|
||||
.vector("overlay", IntegerRepr.SHORT, 2)
|
||||
@ -26,11 +46,11 @@ public final class InstanceTypes {
|
||||
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
|
||||
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
|
||||
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
|
||||
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model);
|
||||
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
|
||||
ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal);
|
||||
})
|
||||
.vertexShader(Flywheel.rl("instance/transformed.vert"))
|
||||
.cullShader(Flywheel.rl("instance/cull/transformed.glsl"))
|
||||
.vertexShader(Flywheel.rl("instance/posed.vert"))
|
||||
.cullShader(Flywheel.rl("instance/cull/posed.glsl"))
|
||||
.register();
|
||||
|
||||
public static final InstanceType<OrientedInstance> ORIENTED = SimpleInstanceType.builder(OrientedInstance::new)
|
||||
|
@ -0,0 +1,110 @@
|
||||
package dev.engine_room.flywheel.lib.instance;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix3fc;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Quaternionfc;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstanceHandle;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.lib.transform.Transform;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class PosedInstance extends ColoredLitInstance implements Transform<PosedInstance> {
|
||||
public final Matrix4f pose = new Matrix4f();
|
||||
public final Matrix3f normal = new Matrix3f();
|
||||
|
||||
public PosedInstance(InstanceType<? extends PosedInstance> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance mulPose(Matrix4fc pose) {
|
||||
this.pose.mul(pose);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance mulNormal(Matrix3fc normal) {
|
||||
this.normal.mul(normal);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) {
|
||||
pose.rotateAround(quaternion, x, y, z);
|
||||
normal.rotate(quaternion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance translate(float x, float y, float z) {
|
||||
pose.translate(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance rotate(Quaternionfc quaternion) {
|
||||
pose.rotate(quaternion);
|
||||
normal.rotate(quaternion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosedInstance scale(float x, float y, float z) {
|
||||
pose.scale(x, y, z);
|
||||
|
||||
if (x == y && y == z) {
|
||||
if (x < 0.0f) {
|
||||
normal.scale(-1.0f);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
float invX = 1.0f / x;
|
||||
float invY = 1.0f / y;
|
||||
float invZ = 1.0f / z;
|
||||
float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ));
|
||||
normal.scale(f * invX, f * invY, f * invZ);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PosedInstance setTransform(Matrix4fc pose, Matrix3fc normal) {
|
||||
this.pose.set(pose);
|
||||
this.normal.set(normal);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PosedInstance setTransform(PoseStack.Pose pose) {
|
||||
this.pose.set(pose.pose());
|
||||
normal.set(pose.normal());
|
||||
return this;
|
||||
}
|
||||
|
||||
public PosedInstance setTransform(PoseStack stack) {
|
||||
return setTransform(stack.last());
|
||||
}
|
||||
|
||||
public PosedInstance setIdentityTransform() {
|
||||
pose.identity();
|
||||
normal.identity();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transform matrices to be all zeros.
|
||||
*
|
||||
* <p>
|
||||
* This will allow the GPU to quickly discard all geometry for this instance, effectively "turning it off".
|
||||
* </p>
|
||||
*/
|
||||
public PosedInstance setZeroTransform() {
|
||||
pose.zero();
|
||||
normal.zero();
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package dev.engine_room.flywheel.lib.instance;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix3fc;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Quaternionfc;
|
||||
@ -10,72 +8,46 @@ import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstanceHandle;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.lib.transform.Transform;
|
||||
import net.minecraft.util.Mth;
|
||||
import dev.engine_room.flywheel.lib.transform.Affine;
|
||||
|
||||
public class TransformedInstance extends ColoredLitInstance implements Transform<TransformedInstance> {
|
||||
public final Matrix4f model = new Matrix4f();
|
||||
public final Matrix3f normal = new Matrix3f();
|
||||
public class TransformedInstance extends ColoredLitInstance implements Affine<TransformedInstance> {
|
||||
public final Matrix4f pose = new Matrix4f();
|
||||
|
||||
public TransformedInstance(InstanceType<? extends TransformedInstance> type, InstanceHandle handle) {
|
||||
super(type, handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance mulPose(Matrix4fc pose) {
|
||||
this.model.mul(pose);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance mulNormal(Matrix3fc normal) {
|
||||
this.normal.mul(normal);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) {
|
||||
model.rotateAround(quaternion, x, y, z);
|
||||
normal.rotate(quaternion);
|
||||
pose.rotateAround(quaternion, x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance translate(float x, float y, float z) {
|
||||
model.translate(x, y, z);
|
||||
pose.translate(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance rotate(Quaternionfc quaternion) {
|
||||
model.rotate(quaternion);
|
||||
normal.rotate(quaternion);
|
||||
pose.rotate(quaternion);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformedInstance scale(float x, float y, float z) {
|
||||
model.scale(x, y, z);
|
||||
pose.scale(x, y, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
if (x == y && y == z) {
|
||||
if (x < 0.0f) {
|
||||
normal.scale(-1.0f);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
float invX = 1.0f / x;
|
||||
float invY = 1.0f / y;
|
||||
float invZ = 1.0f / z;
|
||||
float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ));
|
||||
normal.scale(f * invX, f * invY, f * invZ);
|
||||
public TransformedInstance setTransform(Matrix4fc pose) {
|
||||
this.pose.set(pose);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransformedInstance setTransform(PoseStack.Pose pose) {
|
||||
model.set(pose.pose());
|
||||
normal.set(pose.normal());
|
||||
this.pose.set(pose.pose());
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -84,8 +56,7 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
|
||||
}
|
||||
|
||||
public TransformedInstance setIdentityTransform() {
|
||||
model.identity();
|
||||
normal.identity();
|
||||
pose.identity();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -97,8 +68,7 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
|
||||
* </p>
|
||||
*/
|
||||
public TransformedInstance setZeroTransform() {
|
||||
model.zero();
|
||||
normal.zero();
|
||||
pose.zero();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
package dev.engine_room.flywheel.lib.internal;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.engine_room.flywheel.api.internal.DependencyInjection;
|
||||
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
public interface FlwLibLink {
|
||||
FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl");
|
||||
@ -13,4 +18,10 @@ public interface FlwLibLink {
|
||||
Logger getLogger();
|
||||
|
||||
PoseTransformStack getPoseTransformStackOf(PoseStack stack);
|
||||
|
||||
Map<String, ModelPart> getModelPartChildren(ModelPart part);
|
||||
|
||||
void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha);
|
||||
|
||||
Deque<PoseStack.Pose> getPoseStack(PoseStack stack);
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.model;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.lib.util.FlwUtil;
|
||||
|
||||
public final class ModelCache<T> {
|
||||
private static final Set<ModelCache<?>> ALL = FlwUtil.createWeakHashSet();
|
||||
private final Function<T, Model> factory;
|
||||
private final Map<T, Model> map = new ConcurrentHashMap<>();
|
||||
|
||||
public ModelCache(Function<T, Model> factory) {
|
||||
this.factory = factory;
|
||||
|
||||
synchronized (ALL) {
|
||||
ALL.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Model get(T key) {
|
||||
return map.computeIfAbsent(key, factory);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onEndClientResourceReload() {
|
||||
for (ModelCache<?> cache : ALL) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.model;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.lib.util.FlwUtil;
|
||||
|
||||
public final class ModelHolder {
|
||||
private static final Set<ModelHolder> ALL = FlwUtil.createWeakHashSet();
|
||||
private final Supplier<Model> factory;
|
||||
@Nullable
|
||||
private volatile Model model;
|
||||
|
||||
public ModelHolder(Supplier<Model> factory) {
|
||||
this.factory = factory;
|
||||
|
||||
synchronized (ALL) {
|
||||
ALL.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Model get() {
|
||||
Model model = this.model;
|
||||
|
||||
if (model == null) {
|
||||
synchronized (this) {
|
||||
model = this.model;
|
||||
if (model == null) {
|
||||
this.model = model = factory.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
Model model = this.model;
|
||||
|
||||
if (model != null) {
|
||||
synchronized (this) {
|
||||
model = this.model;
|
||||
if (model != null) {
|
||||
this.model = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onEndClientResourceReload() {
|
||||
for (ModelHolder holder : ALL) {
|
||||
holder.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import dev.engine_room.flywheel.lib.model.baked.BakedModelBuilder;
|
||||
import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder;
|
||||
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
|
||||
import dev.engine_room.flywheel.lib.transform.TransformStack;
|
||||
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
@ -19,11 +20,11 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
* method with the same parameters will return the same object.
|
||||
*/
|
||||
public final class Models {
|
||||
private static final ModelCache<BlockState> BLOCK_STATE = new ModelCache<>(it -> BlockModelBuilder.create(it)
|
||||
private static final ResourceReloadCache<BlockState, Model> BLOCK_STATE = new ResourceReloadCache<>(it -> BlockModelBuilder.create(it)
|
||||
.build());
|
||||
private static final ModelCache<PartialModel> PARTIAL = new ModelCache<>(it -> BakedModelBuilder.create(it.get())
|
||||
private static final ResourceReloadCache<PartialModel, Model> PARTIAL = new ResourceReloadCache<>(it -> BakedModelBuilder.create(it.get())
|
||||
.build());
|
||||
private static final ModelCache<TransformedPartial<?>> TRANSFORMED_PARTIAL = new ModelCache<>(TransformedPartial::create);
|
||||
private static final ResourceReloadCache<TransformedPartial<?>, Model> TRANSFORMED_PARTIAL = new ResourceReloadCache<>(TransformedPartial::create);
|
||||
|
||||
private Models() {
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package dev.engine_room.flywheel.lib.model;
|
||||
|
||||
import org.joml.Vector4fc;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.IndexSequence;
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
|
||||
import dev.engine_room.flywheel.lib.vertex.VertexTransformations;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
public record RetexturedMesh(Mesh mesh, TextureAtlasSprite sprite) implements Mesh {
|
||||
@Override
|
||||
public int vertexCount() {
|
||||
return mesh.vertexCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(MutableVertexList vertexList) {
|
||||
mesh.write(vertexList);
|
||||
VertexTransformations.retexture(vertexList, sprite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSequence indexSequence() {
|
||||
return mesh.indexSequence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexCount() {
|
||||
return mesh.indexCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4fc boundingSphere() {
|
||||
return mesh.boundingSphere();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,483 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.ObjIntConsumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3fc;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.instance.InstancerProvider;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
|
||||
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
|
||||
import dev.engine_room.flywheel.lib.transform.Affine;
|
||||
import dev.engine_room.flywheel.lib.transform.TransformStack;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
|
||||
public final class InstanceTree {
|
||||
private final ModelTree source;
|
||||
@Nullable
|
||||
private final TransformedInstance instance;
|
||||
private final InstanceTree[] children;
|
||||
|
||||
private final Matrix4f poseMatrix;
|
||||
|
||||
private float x;
|
||||
private float y;
|
||||
private float z;
|
||||
private float xRot;
|
||||
private float yRot;
|
||||
private float zRot;
|
||||
private float xScale;
|
||||
private float yScale;
|
||||
private float zScale;
|
||||
@ApiStatus.Experimental
|
||||
public boolean visible = true;
|
||||
@ApiStatus.Experimental
|
||||
public boolean skipDraw;
|
||||
|
||||
private boolean changed;
|
||||
|
||||
private InstanceTree(ModelTree source, @Nullable TransformedInstance instance, InstanceTree[] children) {
|
||||
this.source = source;
|
||||
this.instance = instance;
|
||||
this.children = children;
|
||||
|
||||
if (instance != null) {
|
||||
poseMatrix = instance.pose;
|
||||
} else {
|
||||
poseMatrix = new Matrix4f();
|
||||
}
|
||||
|
||||
resetPose();
|
||||
}
|
||||
|
||||
public static InstanceTree create(InstancerProvider provider, ModelTree meshTree) {
|
||||
InstanceTree[] children = new InstanceTree[meshTree.childCount()];
|
||||
for (int i = 0; i < meshTree.childCount(); i++) {
|
||||
children[i] = create(provider, meshTree.child(i));
|
||||
}
|
||||
|
||||
Model model = meshTree.model();
|
||||
TransformedInstance instance;
|
||||
if (model != null) {
|
||||
instance = provider.instancer(InstanceTypes.TRANSFORMED, model)
|
||||
.createInstance();
|
||||
} else {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
return new InstanceTree(meshTree, instance, children);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TransformedInstance instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public PartPose initialPose() {
|
||||
return source.initialPose();
|
||||
}
|
||||
|
||||
public int childCount() {
|
||||
return children.length;
|
||||
}
|
||||
|
||||
public InstanceTree child(int index) {
|
||||
return children[index];
|
||||
}
|
||||
|
||||
public String childName(int index) {
|
||||
return source.childName(index);
|
||||
}
|
||||
|
||||
public int childIndex(String name) {
|
||||
return source.childIndex(name);
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return childIndex(name) >= 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InstanceTree child(String name) {
|
||||
int index = childIndex(name);
|
||||
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return child(index);
|
||||
}
|
||||
|
||||
public InstanceTree childOrThrow(String name) {
|
||||
InstanceTree child = child(name);
|
||||
|
||||
if (child == null) {
|
||||
throw new NoSuchElementException("Can't find part " + name);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public void traverse(Consumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance);
|
||||
}
|
||||
for (InstanceTree child : children) {
|
||||
child.traverse(consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public void traverse(int i, ObjIntConsumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance, i);
|
||||
}
|
||||
for (InstanceTree child : children) {
|
||||
child.traverse(i, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public void traverse(int i, int j, ObjIntIntConsumer<? super TransformedInstance> consumer) {
|
||||
if (instance != null) {
|
||||
consumer.accept(instance, i, j);
|
||||
}
|
||||
for (InstanceTree child : children) {
|
||||
child.traverse(i, j, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
public void translateAndRotate(Affine<?> affine, Quaternionf tempQuaternion) {
|
||||
affine.translate(x / 16.0F, y / 16.0F, z / 16.0F);
|
||||
|
||||
if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) {
|
||||
affine.rotate(tempQuaternion.rotationZYX(zRot, yRot, xRot));
|
||||
}
|
||||
|
||||
if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) {
|
||||
affine.scale(xScale, yScale, zScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void translateAndRotate(PoseStack poseStack, Quaternionf tempQuaternion) {
|
||||
translateAndRotate(TransformStack.of(poseStack), tempQuaternion);
|
||||
}
|
||||
|
||||
public void translateAndRotate(Matrix4f pose) {
|
||||
pose.translate(x / 16.0F, y / 16.0F, z / 16.0F);
|
||||
|
||||
if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) {
|
||||
pose.rotateZYX(zRot, yRot, xRot);
|
||||
}
|
||||
|
||||
if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) {
|
||||
pose.scale(xScale, yScale, zScale);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the instances in this tree, assuming initialPose changes.
|
||||
*
|
||||
* <p>This is the preferred method for entity visuals, or if you're not sure which you need.
|
||||
*
|
||||
* @param initialPose The root transformation matrix.
|
||||
*/
|
||||
public void updateInstances(Matrix4fc initialPose) {
|
||||
propagateAnimation(initialPose, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the instances in this tree, assuming initialPose doesn't change between invocations.
|
||||
*
|
||||
* <p>This is the preferred method for block entity visuals.
|
||||
*
|
||||
* @param initialPose The root transformation matrix.
|
||||
*/
|
||||
public void updateInstancesStatic(Matrix4fc initialPose) {
|
||||
propagateAnimation(initialPose, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate pose transformations to this tree and all its children.
|
||||
*
|
||||
* @param initialPose The root transformation matrix.
|
||||
* @param force Whether to force the update even if this node's transformations haven't changed.
|
||||
*/
|
||||
public void propagateAnimation(Matrix4fc initialPose, boolean force) {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changed || force) {
|
||||
poseMatrix.set(initialPose);
|
||||
translateAndRotate(poseMatrix);
|
||||
force = true;
|
||||
|
||||
if (instance != null && !skipDraw) {
|
||||
instance.setChanged();
|
||||
}
|
||||
}
|
||||
|
||||
for (InstanceTree child : children) {
|
||||
child.propagateAnimation(poseMatrix, force);
|
||||
}
|
||||
|
||||
changed = false;
|
||||
}
|
||||
|
||||
public float xPos() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float yPos() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public float zPos() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public float xRot() {
|
||||
return xRot;
|
||||
}
|
||||
|
||||
public float yRot() {
|
||||
return yRot;
|
||||
}
|
||||
|
||||
public float zRot() {
|
||||
return zRot;
|
||||
}
|
||||
|
||||
public float xScale() {
|
||||
return xScale;
|
||||
}
|
||||
|
||||
public float yScale() {
|
||||
return yScale;
|
||||
}
|
||||
|
||||
public float zScale() {
|
||||
return zScale;
|
||||
}
|
||||
|
||||
public void xPos(float x) {
|
||||
this.x = x;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void yPos(float y) {
|
||||
this.y = y;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void zPos(float z) {
|
||||
this.z = z;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void pos(float x, float y, float z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void xRot(float xRot) {
|
||||
this.xRot = xRot;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void yRot(float yRot) {
|
||||
this.yRot = yRot;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void zRot(float zRot) {
|
||||
this.zRot = zRot;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void rotation(float xRot, float yRot, float zRot) {
|
||||
this.xRot = xRot;
|
||||
this.yRot = yRot;
|
||||
this.zRot = zRot;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void xScale(float xScale) {
|
||||
this.xScale = xScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void yScale(float yScale) {
|
||||
this.yScale = yScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void zScale(float zScale) {
|
||||
this.zScale = zScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void scale(float xScale, float yScale, float zScale) {
|
||||
this.xScale = xScale;
|
||||
this.yScale = yScale;
|
||||
this.zScale = zScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetPos(float xOffset, float yOffset, float zOffset) {
|
||||
x += xOffset;
|
||||
y += yOffset;
|
||||
z += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetXPos(float xOffset) {
|
||||
x += xOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetYPos(float yOffset) {
|
||||
y += yOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetZPos(float zOffset) {
|
||||
z += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetPos(Vector3fc offset) {
|
||||
offsetPos(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public void offsetRotation(float xOffset, float yOffset, float zOffset) {
|
||||
xRot += xOffset;
|
||||
yRot += yOffset;
|
||||
zRot += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetXRot(float xOffset) {
|
||||
xRot += xOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetYRot(float yOffset) {
|
||||
yRot += yOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetZRot(float zOffset) {
|
||||
zRot += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetRotation(Vector3fc offset) {
|
||||
offsetRotation(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public void offsetScale(float xOffset, float yOffset, float zOffset) {
|
||||
xScale += xOffset;
|
||||
yScale += yOffset;
|
||||
zScale += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetXScale(float xOffset) {
|
||||
xScale += xOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetYScale(float yOffset) {
|
||||
yScale += yOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetZScale(float zOffset) {
|
||||
zScale += zOffset;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void offsetScale(Vector3fc offset) {
|
||||
offsetScale(offset.x(), offset.y(), offset.z());
|
||||
}
|
||||
|
||||
public PartPose storePose() {
|
||||
return PartPose.offsetAndRotation(x, y, z, xRot, yRot, zRot);
|
||||
}
|
||||
|
||||
public void loadPose(PartPose pose) {
|
||||
x = pose.x;
|
||||
y = pose.y;
|
||||
z = pose.z;
|
||||
xRot = pose.xRot;
|
||||
yRot = pose.yRot;
|
||||
zRot = pose.zRot;
|
||||
xScale = ModelPart.DEFAULT_SCALE;
|
||||
yScale = ModelPart.DEFAULT_SCALE;
|
||||
zScale = ModelPart.DEFAULT_SCALE;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void resetPose() {
|
||||
loadPose(source.initialPose());
|
||||
}
|
||||
|
||||
public void copyTransform(InstanceTree tree) {
|
||||
x = tree.x;
|
||||
y = tree.y;
|
||||
z = tree.z;
|
||||
xRot = tree.xRot;
|
||||
yRot = tree.yRot;
|
||||
zRot = tree.zRot;
|
||||
xScale = tree.xScale;
|
||||
yScale = tree.yScale;
|
||||
zScale = tree.zScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void copyTransform(ModelPart modelPart) {
|
||||
x = modelPart.x;
|
||||
y = modelPart.y;
|
||||
z = modelPart.z;
|
||||
xRot = modelPart.xRot;
|
||||
yRot = modelPart.yRot;
|
||||
zRot = modelPart.zRot;
|
||||
xScale = modelPart.xScale;
|
||||
yScale = modelPart.yScale;
|
||||
zScale = modelPart.zScale;
|
||||
setChanged();
|
||||
}
|
||||
|
||||
private void setChanged() {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (instance != null) {
|
||||
instance.delete();
|
||||
}
|
||||
for (InstanceTree child : children) {
|
||||
child.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@FunctionalInterface
|
||||
public interface ObjIntIntConsumer<T> {
|
||||
void accept(T t, int i, int j);
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
|
||||
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
||||
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
|
||||
import dev.engine_room.flywheel.lib.vertex.VertexView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.geom.EntityModelSet;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
|
||||
public final class MeshTree {
|
||||
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
|
||||
private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last();
|
||||
private static final ResourceReloadCache<ModelLayerLocation, MeshTree> CACHE = new ResourceReloadCache<>(MeshTree::convert);
|
||||
|
||||
@Nullable
|
||||
private final Mesh mesh;
|
||||
private final PartPose initialPose;
|
||||
private final MeshTree[] children;
|
||||
private final String[] childNames;
|
||||
|
||||
private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childNames) {
|
||||
this.mesh = mesh;
|
||||
this.initialPose = initialPose;
|
||||
this.children = children;
|
||||
this.childNames = childNames;
|
||||
}
|
||||
|
||||
public static MeshTree of(ModelLayerLocation layer) {
|
||||
return CACHE.get(layer);
|
||||
}
|
||||
|
||||
private static MeshTree convert(ModelLayerLocation layer) {
|
||||
EntityModelSet entityModels = Minecraft.getInstance()
|
||||
.getEntityModels();
|
||||
ModelPart modelPart = entityModels.bakeLayer(layer);
|
||||
|
||||
return convert(modelPart, THREAD_LOCAL_OBJECTS.get());
|
||||
}
|
||||
|
||||
private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) {
|
||||
var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart);
|
||||
|
||||
String[] childNames = modelPartChildren.keySet()
|
||||
.toArray(String[]::new);
|
||||
Arrays.sort(childNames);
|
||||
|
||||
MeshTree[] children = new MeshTree[childNames.length];
|
||||
for (int i = 0; i < childNames.length; i++) {
|
||||
children[i] = convert(modelPartChildren.get(childNames[i]), objects);
|
||||
}
|
||||
|
||||
return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childNames);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) {
|
||||
if (modelPart.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VertexWriter vertexWriter = objects.vertexWriter;
|
||||
FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY_POSE, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
|
||||
MemoryBlock data = vertexWriter.copyDataAndReset();
|
||||
|
||||
VertexView vertexView = new PosTexNormalVertexView();
|
||||
vertexView.load(data);
|
||||
return new SimpleQuadMesh(vertexView, "source=MeshTree");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Mesh mesh() {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
public PartPose initialPose() {
|
||||
return initialPose;
|
||||
}
|
||||
|
||||
public int childCount() {
|
||||
return children.length;
|
||||
}
|
||||
|
||||
public MeshTree child(int index) {
|
||||
return children[index];
|
||||
}
|
||||
|
||||
public String childName(int index) {
|
||||
return childNames[index];
|
||||
}
|
||||
|
||||
public int childIndex(String name) {
|
||||
return Arrays.binarySearch(childNames, name);
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return childIndex(name) >= 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MeshTree child(String name) {
|
||||
int index = childIndex(name);
|
||||
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return child(index);
|
||||
}
|
||||
|
||||
public MeshTree childOrThrow(String name) {
|
||||
MeshTree child = child(name);
|
||||
|
||||
if (child == null) {
|
||||
throw new NoSuchElementException("Can't find part " + name);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
private static class ThreadLocalObjects {
|
||||
public final VertexWriter vertexWriter = new VertexWriter();
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector2f;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
|
||||
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
|
||||
import dev.engine_room.flywheel.lib.vertex.VertexView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.model.geom.EntityModelSet;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
public final class ModelPartConverter {
|
||||
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
|
||||
|
||||
private ModelPartConverter() {
|
||||
}
|
||||
|
||||
public static Mesh convert(ModelPart modelPart, @Nullable PoseStack poseStack, @Nullable TextureMapper textureMapper) {
|
||||
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
|
||||
if (poseStack == null) {
|
||||
poseStack = objects.identityPoseStack;
|
||||
}
|
||||
VertexWriter vertexWriter = objects.vertexWriter;
|
||||
|
||||
vertexWriter.setTextureMapper(textureMapper);
|
||||
modelPart.render(poseStack, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY);
|
||||
MemoryBlock data = vertexWriter.copyDataAndReset();
|
||||
|
||||
VertexView vertexView = new PosTexNormalVertexView();
|
||||
vertexView.load(data);
|
||||
return new SimpleQuadMesh(vertexView, "source=ModelPartConverter");
|
||||
}
|
||||
|
||||
public static Mesh convert(ModelLayerLocation layer, @Nullable TextureAtlasSprite sprite, String... childPath) {
|
||||
EntityModelSet entityModels = Minecraft.getInstance().getEntityModels();
|
||||
ModelPart modelPart = entityModels.bakeLayer(layer);
|
||||
for (String pathPart : childPath) {
|
||||
modelPart = modelPart.getChild(pathPart);
|
||||
}
|
||||
TextureMapper textureMapper = sprite == null ? null : TextureMapper.toSprite(sprite);
|
||||
return convert(modelPart, null, textureMapper);
|
||||
}
|
||||
|
||||
public static Mesh convert(ModelLayerLocation layer, String... childPath) {
|
||||
return convert(layer, null, childPath);
|
||||
}
|
||||
|
||||
public interface TextureMapper {
|
||||
void map(Vector2f uv);
|
||||
|
||||
static TextureMapper toSprite(TextureAtlasSprite sprite) {
|
||||
return uv -> uv.set(sprite.getU(uv.x), sprite.getV(uv.y));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThreadLocalObjects {
|
||||
public final PoseStack identityPoseStack = new PoseStack();
|
||||
public final VertexWriter vertexWriter = new VertexWriter();
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
|
||||
public final class ModelTree {
|
||||
@Nullable
|
||||
private final Model model;
|
||||
private final PartPose initialPose;
|
||||
private final ModelTree[] children;
|
||||
private final String[] childNames;
|
||||
|
||||
/**
|
||||
* Create a new ModelTree node.
|
||||
*
|
||||
* @param model The model to associate with this node, or null if this node does not render.
|
||||
* @param initialPose The initial pose of this node.
|
||||
* @param children The children of this node.
|
||||
*/
|
||||
public ModelTree(@Nullable Model model, PartPose initialPose, Map<String, ModelTree> children) {
|
||||
this.model = model;
|
||||
this.initialPose = initialPose;
|
||||
|
||||
String[] childNames = children.keySet().toArray(String[]::new);
|
||||
Arrays.sort(childNames);
|
||||
|
||||
ModelTree[] childArray = new ModelTree[childNames.length];
|
||||
for (int i = 0; i < childNames.length; i++) {
|
||||
childArray[i] = children.get(childNames[i]);
|
||||
}
|
||||
|
||||
this.children = childArray;
|
||||
this.childNames = childNames;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Model model() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public PartPose initialPose() {
|
||||
return initialPose;
|
||||
}
|
||||
|
||||
public int childCount() {
|
||||
return children.length;
|
||||
}
|
||||
|
||||
public ModelTree child(int index) {
|
||||
return children[index];
|
||||
}
|
||||
|
||||
public String childName(int index) {
|
||||
return childNames[index];
|
||||
}
|
||||
|
||||
public int childIndex(String name) {
|
||||
return Arrays.binarySearch(childNames, name);
|
||||
}
|
||||
|
||||
public boolean hasChild(String name) {
|
||||
return childIndex(name) >= 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ModelTree child(String name) {
|
||||
int index = childIndex(name);
|
||||
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return child(index);
|
||||
}
|
||||
|
||||
public ModelTree childOrThrow(String name) {
|
||||
ModelTree child = child(name);
|
||||
|
||||
if (child == null) {
|
||||
throw new NoSuchElementException("Can't find part " + name);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import dev.engine_room.flywheel.api.material.Material;
|
||||
import dev.engine_room.flywheel.api.model.Mesh;
|
||||
import dev.engine_room.flywheel.api.model.Model;
|
||||
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
|
||||
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
|
||||
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
||||
import net.minecraft.client.model.geom.ModelLayerLocation;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
public final class ModelTrees {
|
||||
private static final ResourceReloadCache<ModelTreeKey, ModelTree> CACHE = new ResourceReloadCache<>(k -> {
|
||||
ModelTree tree = convert("", MeshTree.of(k.layer), k.pathsToPrune, k.texture != null ? k.texture.sprite() : null, k.material);
|
||||
|
||||
if (tree == null) {
|
||||
throw new IllegalArgumentException("Cannot prune root node!");
|
||||
}
|
||||
|
||||
return tree;
|
||||
});
|
||||
|
||||
private ModelTrees() {
|
||||
}
|
||||
|
||||
public static ModelTree of(ModelLayerLocation layer, Material material) {
|
||||
return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), null, material));
|
||||
}
|
||||
|
||||
public static ModelTree of(ModelLayerLocation layer, net.minecraft.client.resources.model.Material texture, Material material) {
|
||||
return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), texture, material));
|
||||
}
|
||||
|
||||
public static ModelTree of(ModelLayerLocation layer, Set<String> pathsToPrune, Material material) {
|
||||
return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), null, material));
|
||||
}
|
||||
|
||||
public static ModelTree of(ModelLayerLocation layer, Set<String> pathsToPrune, net.minecraft.client.resources.model.Material texture, Material material) {
|
||||
return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), texture, material));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ModelTree convert(String path, MeshTree meshTree, Set<String> pathsToPrune, @Nullable TextureAtlasSprite sprite, Material material) {
|
||||
if (pathsToPrune.contains(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Model model = null;
|
||||
Mesh mesh = meshTree.mesh();
|
||||
|
||||
if (mesh != null) {
|
||||
if (sprite != null) {
|
||||
mesh = new RetexturedMesh(mesh, sprite);
|
||||
}
|
||||
|
||||
model = new SingleMeshModel(mesh, material);
|
||||
}
|
||||
|
||||
Map<String, ModelTree> children = new HashMap<>();
|
||||
String pathSlash = path + "/";
|
||||
|
||||
for (int i = 0; i < meshTree.childCount(); i++) {
|
||||
String childName = meshTree.childName(i);
|
||||
var child = convert(pathSlash + childName, meshTree.child(i), pathsToPrune, sprite, material);
|
||||
|
||||
if (child != null) {
|
||||
children.put(childName, child);
|
||||
}
|
||||
}
|
||||
|
||||
return new ModelTree(model, meshTree.initialPose(), children);
|
||||
}
|
||||
|
||||
private record ModelTreeKey(ModelLayerLocation layer, Set<String> pathsToPrune, @Nullable net.minecraft.client.resources.model.Material texture, Material material) {
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
package dev.engine_room.flywheel.lib.model.part;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector2f;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.engine_room.flywheel.lib.math.DataPacker;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import dev.engine_room.flywheel.lib.model.part.ModelPartConverter.TextureMapper;
|
||||
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
|
||||
|
||||
class VertexWriter implements VertexConsumer {
|
||||
@ -16,10 +13,6 @@ class VertexWriter implements VertexConsumer {
|
||||
|
||||
private MemoryBlock data;
|
||||
|
||||
@Nullable
|
||||
private TextureMapper textureMapper;
|
||||
private final Vector2f uvVec = new Vector2f();
|
||||
|
||||
private int vertexCount;
|
||||
private boolean filledPosition;
|
||||
private boolean filledTexture;
|
||||
@ -29,10 +22,6 @@ class VertexWriter implements VertexConsumer {
|
||||
data = MemoryBlock.malloc(128 * STRIDE);
|
||||
}
|
||||
|
||||
public void setTextureMapper(@Nullable TextureMapper mapper) {
|
||||
textureMapper = mapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexConsumer addVertex(float x, float y, float z) {
|
||||
if (!filledPosition) {
|
||||
@ -53,13 +42,6 @@ class VertexWriter implements VertexConsumer {
|
||||
@Override
|
||||
public VertexConsumer setUv(float u, float v) {
|
||||
if (!filledTexture) {
|
||||
if (textureMapper != null) {
|
||||
uvVec.set(u, v);
|
||||
textureMapper.map(uvVec);
|
||||
u = uvVec.x;
|
||||
v = uvVec.y;
|
||||
}
|
||||
|
||||
long ptr = vertexPtr();
|
||||
MemoryUtil.memPutFloat(ptr + 12, u);
|
||||
MemoryUtil.memPutFloat(ptr + 16, v);
|
||||
@ -123,7 +105,6 @@ class VertexWriter implements VertexConsumer {
|
||||
filledPosition = false;
|
||||
filledTexture = false;
|
||||
filledNormal = false;
|
||||
textureMapper = null;
|
||||
|
||||
return dataCopy;
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public final class FlwUtil {
|
||||
private FlwUtil() {
|
||||
}
|
||||
|
||||
public static int[] initArray(int size, int fill) {
|
||||
var out = new int[size];
|
||||
Arrays.fill(out, fill);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static <T> Set<T> createWeakHashSet() {
|
||||
return Collections.newSetFromMap(new WeakHashMap<>());
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Pair<F, S>(F first, S second) {
|
||||
public static <F, S> Pair<F, S> of(F first, S second) {
|
||||
return new Pair<>(first, second);
|
||||
}
|
||||
|
||||
public Pair<S, F> swap() {
|
||||
return Pair.of(second, first);
|
||||
}
|
||||
|
||||
public Pair<F, S> copy() {
|
||||
return Pair.of(first, second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj instanceof final Pair<?, ?> other) {
|
||||
return Objects.equals(first, other.first) && Objects.equals(second, other.second);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (Objects.hashCode(first) * 31) ^ Objects.hashCode(second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + first + ", " + second + ")";
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
|
||||
|
||||
/**
|
||||
* A {@link PoseStack} that recycles {@link PoseStack.Pose} objects.
|
||||
*
|
||||
* <p>Vanilla's {@link PoseStack} can get quite expensive to use when each game object needs to
|
||||
* maintain their own stack. This class helps alleviate memory pressure by making Pose objects
|
||||
* long-lived. Note that this means that you <em>CANNOT</em> safely store a Pose object outside
|
||||
* the RecyclingPoseStack that created it.
|
||||
*/
|
||||
public class RecyclingPoseStack extends PoseStack {
|
||||
private final Deque<Pose> recycleBin = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void pushPose() {
|
||||
if (recycleBin.isEmpty()) {
|
||||
super.pushPose();
|
||||
} else {
|
||||
var last = last();
|
||||
var recycle = recycleBin.removeLast();
|
||||
recycle.pose()
|
||||
.set(last.pose());
|
||||
recycle.normal()
|
||||
.set(last.normal());
|
||||
FlwLibLink.INSTANCE.getPoseStack(this)
|
||||
.addLast(recycle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popPose() {
|
||||
recycleBin.addLast(FlwLibLink.INSTANCE.getPoseStack(this)
|
||||
.removeLast());
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public final class ResourceReloadCache<T, U> implements Function<T, U> {
|
||||
private static final Set<ResourceReloadCache<?, ?>> ALL = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final Function<T, U> factory;
|
||||
private final Map<T, U> map = new ConcurrentHashMap<>();
|
||||
|
||||
public ResourceReloadCache(Function<T, U> factory) {
|
||||
this.factory = factory;
|
||||
|
||||
synchronized (ALL) {
|
||||
ALL.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public final U get(T key) {
|
||||
return map.computeIfAbsent(key, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final U apply(T t) {
|
||||
return get(t);
|
||||
}
|
||||
|
||||
public final void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onEndClientResourceReload() {
|
||||
for (ResourceReloadCache<?, ?> cache : ALL) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class ResourceReloadHolder<T> implements Supplier<T> {
|
||||
private static final Set<ResourceReloadHolder<?>> ALL = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final Supplier<T> factory;
|
||||
@Nullable
|
||||
private volatile T obj;
|
||||
|
||||
public ResourceReloadHolder(Supplier<T> factory) {
|
||||
this.factory = factory;
|
||||
|
||||
synchronized (ALL) {
|
||||
ALL.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T get() {
|
||||
T obj = this.obj;
|
||||
|
||||
if (obj == null) {
|
||||
synchronized (this) {
|
||||
obj = this.obj;
|
||||
if (obj == null) {
|
||||
this.obj = obj = factory.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public final void clear() {
|
||||
T obj = this.obj;
|
||||
|
||||
if (obj != null) {
|
||||
synchronized (this) {
|
||||
obj = this.obj;
|
||||
if (obj != null) {
|
||||
this.obj = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void onEndClientResourceReload() {
|
||||
for (ResourceReloadHolder<?> holder : ALL) {
|
||||
holder.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -53,10 +53,11 @@ public final class ResourceUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link ResourceLocation#toDebugFileName()}, but also removes the file extension.
|
||||
*/
|
||||
public static String toDebugFileNameNoExtension(ResourceLocation resourceLocation) {
|
||||
var stringLoc = resourceLocation.toString();
|
||||
return stringLoc.substring(0, stringLoc.lastIndexOf('.'))
|
||||
.replace('/', '_')
|
||||
.replace(':', '_');
|
||||
var stringLoc = resourceLocation.toDebugFileName();
|
||||
return stringLoc.substring(0, stringLoc.lastIndexOf('.'));
|
||||
}
|
||||
}
|
||||
|
@ -57,12 +57,6 @@ public final class StringUtil {
|
||||
return "0x" + Long.toHexString(address);
|
||||
}
|
||||
|
||||
public static String args(String functionName, Object... args) {
|
||||
return functionName + '(' + Arrays.stream(args)
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.joining(", ")) + ')';
|
||||
}
|
||||
|
||||
public static String trimPrefix(String s, String prefix) {
|
||||
if (s.startsWith(prefix)) {
|
||||
return s.substring(prefix.length());
|
||||
|
@ -1,5 +0,0 @@
|
||||
package dev.engine_room.flywheel.lib.util;
|
||||
|
||||
public enum Unit {
|
||||
INSTANCE;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package dev.engine_room.flywheel.lib.vertex;
|
||||
|
||||
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
|
||||
public final class VertexTransformations {
|
||||
private VertexTransformations() {
|
||||
}
|
||||
|
||||
public static void retexture(MutableVertexList vertexList, int index, TextureAtlasSprite sprite) {
|
||||
vertexList.u(index, sprite.getU(vertexList.u(index) * 16));
|
||||
vertexList.v(index, sprite.getV(vertexList.v(index) * 16));
|
||||
}
|
||||
|
||||
public static void retexture(MutableVertexList vertexList, TextureAtlasSprite sprite) {
|
||||
for (int i = 0; i < vertexList.vertexCount(); i++) {
|
||||
retexture(vertexList, i, sprite);
|
||||
}
|
||||
}
|
||||
}
|
@ -52,7 +52,7 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
|
||||
this.blockEntity = blockEntity;
|
||||
this.pos = blockEntity.getBlockPos();
|
||||
this.blockState = blockEntity.getBlockState();
|
||||
this.visualPos = pos.subtract(renderOrigin);
|
||||
this.visualPos = pos.subtract(ctx.renderOrigin());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -99,6 +99,10 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
|
||||
.shouldUpdate(pos.distToCenterSqr(context.camera().getPosition()));
|
||||
}
|
||||
|
||||
protected int computePackedLight() {
|
||||
return LevelRenderer.getLightColor(level, pos);
|
||||
}
|
||||
|
||||
protected void relight(BlockPos pos, @Nullable FlatLit... instances) {
|
||||
FlatLit.relight(LevelRenderer.getLightColor(level, pos), instances);
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
|
||||
*/
|
||||
public Vector3f getVisualPosition() {
|
||||
Vec3 pos = entity.position();
|
||||
var renderOrigin = renderOrigin();
|
||||
return new Vector3f((float) (pos.x - renderOrigin.getX()),
|
||||
(float) (pos.y - renderOrigin.getY()),
|
||||
(float) (pos.z - renderOrigin.getZ()));
|
||||
@ -81,6 +82,7 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
|
||||
*/
|
||||
public Vector3f getVisualPosition(float partialTick) {
|
||||
Vec3 pos = entity.position();
|
||||
var renderOrigin = renderOrigin();
|
||||
return new Vector3f((float) (Mth.lerp(partialTick, entity.xOld, pos.x) - renderOrigin.getX()),
|
||||
(float) (Mth.lerp(partialTick, entity.yOld, pos.y) - renderOrigin.getY()),
|
||||
(float) (Mth.lerp(partialTick, entity.zOld, pos.z) - renderOrigin.getZ()));
|
||||
@ -90,11 +92,14 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
|
||||
return entity.noCulling || visibilityTester.check(frustum);
|
||||
}
|
||||
|
||||
protected void relight(float partialTick, @Nullable FlatLit... instances) {
|
||||
protected int computePackedLight(float partialTick) {
|
||||
BlockPos pos = BlockPos.containing(entity.getLightProbePosition(partialTick));
|
||||
int blockLight = entity.isOnFire() ? 15 : level.getBrightness(LightLayer.BLOCK, pos);
|
||||
int skyLight = level.getBrightness(LightLayer.SKY, pos);
|
||||
int light = LightTexture.pack(blockLight, skyLight);
|
||||
FlatLit.relight(light, instances);
|
||||
return LightTexture.pack(blockLight, skyLight);
|
||||
}
|
||||
|
||||
protected void relight(float partialTick, @Nullable FlatLit... instances) {
|
||||
FlatLit.relight(computePackedLight(partialTick), instances);
|
||||
}
|
||||
}
|
||||
|
@ -13,16 +13,12 @@ public abstract class AbstractVisual implements Visual {
|
||||
* Useful for passing to child visuals.
|
||||
*/
|
||||
protected final VisualizationContext visualizationContext;
|
||||
protected final InstancerProvider instancerProvider;
|
||||
protected final Vec3i renderOrigin;
|
||||
protected final Level level;
|
||||
|
||||
protected boolean deleted = false;
|
||||
|
||||
public AbstractVisual(VisualizationContext ctx, Level level, float partialTick) {
|
||||
this.visualizationContext = ctx;
|
||||
this.instancerProvider = ctx.instancerProvider();
|
||||
this.renderOrigin = ctx.renderOrigin();
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@ -32,6 +28,14 @@ public abstract class AbstractVisual implements Visual {
|
||||
|
||||
protected abstract void _delete();
|
||||
|
||||
protected InstancerProvider instancerProvider() {
|
||||
return visualizationContext.instancerProvider();
|
||||
}
|
||||
|
||||
protected Vec3i renderOrigin() {
|
||||
return visualizationContext.renderOrigin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void delete() {
|
||||
if (deleted) {
|
||||
|
@ -15,9 +15,9 @@ import dev.engine_room.flywheel.lib.instance.InstanceTypes;
|
||||
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
|
||||
import dev.engine_room.flywheel.lib.material.Materials;
|
||||
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
|
||||
import dev.engine_room.flywheel.lib.model.ModelCache;
|
||||
import dev.engine_room.flywheel.lib.model.QuadMesh;
|
||||
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
|
||||
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
|
||||
import dev.engine_room.flywheel.lib.visual.util.SmartRecycler;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
@ -36,7 +36,7 @@ public final class FireComponent implements EntityComponent {
|
||||
// Parameterize by the material instead of the sprite
|
||||
// because Material#sprite is a surprisingly heavy operation
|
||||
// and because sprites are invalidated after a resource reload.
|
||||
private static final ModelCache<net.minecraft.client.resources.model.Material> FIRE_MODELS = new ModelCache<>(texture -> {
|
||||
private static final ResourceReloadCache<net.minecraft.client.resources.model.Material, Model> FIRE_MODELS = new ResourceReloadCache<>(texture -> {
|
||||
return new SingleMeshModel(new FireMesh(texture.sprite()), FIRE_MATERIAL);
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
#include "flywheel:util/matrix.glsl"
|
||||
|
||||
void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) {
|
||||
transformBoundingSphere(i.pose, center, radius);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
void flw_instanceVertex(in FlwInstance i) {
|
||||
flw_vertexPos = i.pose * flw_vertexPos;
|
||||
flw_vertexNormal = i.normal * flw_vertexNormal;
|
||||
flw_vertexColor *= i.color;
|
||||
flw_vertexOverlay = i.overlay;
|
||||
// Some drivers have a bug where uint over float division is invalid, so use an explicit cast.
|
||||
flw_vertexLight = vec2(i.light) / 256.0;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
void flw_instanceVertex(in FlwInstance i) {
|
||||
flw_vertexPos = i.pose * flw_vertexPos;
|
||||
flw_vertexNormal = i.normal * flw_vertexNormal;
|
||||
flw_vertexNormal = mat3(transpose(inverse(i.pose))) * flw_vertexNormal;
|
||||
flw_vertexColor *= i.color;
|
||||
flw_vertexOverlay = i.overlay;
|
||||
// Some drivers have a bug where uint over float division is invalid, so use an explicit cast.
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.engine_room.flywheel.impl;
|
||||
|
||||
import dev.engine_room.flywheel.api.backend.Backend;
|
||||
import dev.engine_room.flywheel.backend.BackendConfig;
|
||||
|
||||
public interface FlwConfig {
|
||||
FlwConfig INSTANCE = FlwImplXplat.INSTANCE.getConfig();
|
||||
@ -10,4 +11,6 @@ public interface FlwConfig {
|
||||
boolean limitUpdates();
|
||||
|
||||
int workerThreads();
|
||||
|
||||
BackendConfig backendConfig();
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public final class FlwImpl {
|
||||
StandardMaterialShaders.init();
|
||||
|
||||
// backend
|
||||
FlwBackend.init();
|
||||
FlwBackend.init(FlwConfig.INSTANCE.backendConfig());
|
||||
|
||||
// vanilla
|
||||
VanillaVisuals.init();
|
||||
|
@ -1,12 +1,19 @@
|
||||
package dev.engine_room.flywheel.impl;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import dev.engine_room.flywheel.impl.extension.PoseStackExtension;
|
||||
import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor;
|
||||
import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor;
|
||||
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
|
||||
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
public class FlwLibLinkImpl implements FlwLibLink {
|
||||
@Override
|
||||
@ -18,4 +25,19 @@ public class FlwLibLinkImpl implements FlwLibLink {
|
||||
public PoseTransformStack getPoseTransformStackOf(PoseStack stack) {
|
||||
return ((PoseStackExtension) stack).flywheel$transformStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ModelPart> getModelPartChildren(ModelPart part) {
|
||||
return ((ModelPartAccessor) (Object) part).flywheel$children();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) {
|
||||
((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Deque<PoseStack.Pose> getPoseStack(PoseStack stack) {
|
||||
return ((PoseStackAccessor) stack).flywheel$getPoseStack();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.engine_room.flywheel.backend;
|
||||
package dev.engine_room.flywheel.impl;
|
||||
|
||||
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
|
||||
import net.minecraft.commands.arguments.StringRepresentableArgument;
|
@ -0,0 +1,21 @@
|
||||
package dev.engine_room.flywheel.impl.mixin;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
|
||||
import net.minecraft.client.model.geom.ModelPart;
|
||||
|
||||
@Mixin(ModelPart.class)
|
||||
public interface ModelPartAccessor {
|
||||
@Accessor("children")
|
||||
Map<String, ModelPart> flywheel$children();
|
||||
|
||||
@Invoker("compile")
|
||||
void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dev.engine_room.flywheel.impl.mixin;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
|
||||
@Mixin(PoseStack.class)
|
||||
public interface PoseStackAccessor {
|
||||
@Accessor("poseStack")
|
||||
Deque<PoseStack.Pose> flywheel$getPoseStack();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user