diff --git a/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java b/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java index ea2065dd0..3815f5f7e 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java @@ -7,4 +7,8 @@ public interface InstanceHandle { void setChanged(); void setDeleted(); + + void setVisible(boolean visible); + + boolean isVisible(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java index 779ab2f9c..b61b3bef5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java @@ -3,7 +3,7 @@ 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. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java index a51878268..6e1dac124 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java @@ -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 @@ 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(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java index 8cd216c59..6acb2832a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java @@ -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(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java index 3b705754a..645f00451 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java @@ -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 UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND); private static final Compile> CULL = new Compile<>(); private static final Compile UTIL = new Compile<>(); @@ -42,14 +45,12 @@ public class IndirectPrograms extends AtomicReferenceCounted { private final Map pipeline; private final Map, GlProgram> culling; - private final GlProgram apply; - private final GlProgram scatter; + private final Map utils; - private IndirectPrograms(Map pipeline, Map, GlProgram> culling, GlProgram apply, GlProgram scatter) { + private IndirectPrograms(Map pipeline, Map, GlProgram> culling, Map utils) { this.pipeline = pipeline; this.culling = culling; - this.apply = apply; - this.scatter = scatter; + this.utils = utils; } private static List getExtensions(GlslVersion glslVersion) { @@ -94,10 +95,10 @@ static void reload(ShaderSources sources, ImmutableList pipe 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 GlProgram getCullingProgram(InstanceType instanceType) { } 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 @@ protected void _delete() { .forEach(GlProgram::delete); culling.values() .forEach(GlProgram::delete); - apply.delete(); + utils.values() + .forEach(GlProgram::delete); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java index 9a6a02012..262f051b8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java @@ -44,7 +44,9 @@ protected void generateUnpacking(GlslBuilder builder) { 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() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java index 76d56e755..ec875e095 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java @@ -34,7 +34,7 @@ public String source() { 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()); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java index c8902b2d4..2b7df8af8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java @@ -43,7 +43,7 @@ protected void generateUnpacking(GlslBuilder builder) { 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(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java similarity index 55% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java index e7aa67071..2493a339b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java @@ -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 int alloc() { } // 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 void free(int i) { 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(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java index 73f8c1714..8938fe080 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java @@ -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 @@ public abstract class AbstractInstancer implements Instancer { public final InstanceType type; public final Environment environment; + private final Supplier> 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 instances = new ArrayList<>(); - protected final ArrayList handles = new ArrayList<>(); + protected final ArrayList> handles = new ArrayList<>(); protected final AtomicBitSet changed = new AtomicBitSet(); protected final AtomicBitSet deleted = new AtomicBitSet(); - protected AbstractInstancer(InstanceType type, Environment environment) { - this.type = type; - this.environment = environment; + protected AbstractInstancer(InstancerKey key, Supplier> 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(); + 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 void stealInstance(@Nullable I instance) { 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) instanceHandle; + + if (handle.instancer == this && handle.visible) { return; } @@ -65,19 +75,23 @@ public void stealInstance(@Nullable I instance) { // 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 handle) { instances.add(instance); handles.add(handle); changed.set(handle.index); @@ -122,7 +136,7 @@ protected void removeDeletedInstances() { 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 @@ protected void removeDeletedInstances() { .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 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. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java new file mode 100644 index 000000000..33dfa3812 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java @@ -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); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java index 476a9abc4..19bdfeae1 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java @@ -8,16 +8,16 @@ 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> { */ protected final Queue> initializationQueue = new ConcurrentLinkedQueue<>(); + public AbstractInstancer getInstancer(Environment environment, InstanceType type, Model model, VisualType visualType, int bias) { + return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias)); + } + @SuppressWarnings("unchecked") - public Instancer getInstancer(Environment environment, InstanceType type, Model model, VisualType visualType, int bias) { - return (Instancer) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit); + public AbstractInstancer getInstancer(InstancerKey key) { + return (AbstractInstancer) instancers.computeIfAbsent(key, this::createAndDeferInit); } public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) { @@ -94,8 +98,8 @@ private static boolean checkAndWarnEmptyModel(Model model) { return false; } - protected static > Map, Int2ObjectMap>>> doCrumblingSort(Class clazz, List crumblingBlocks) { - Map, Int2ObjectMap>>> byType = new HashMap<>(); + protected static > Map, Int2ObjectMap>>>> doCrumblingSort(Class clazz, List crumblingBlocks) { + Map, Int2ObjectMap>>>> byType = new HashMap<>(); for (Engine.CrumblingBlock block : crumblingBlocks) { int progress = block.progress(); @@ -107,7 +111,7 @@ protected static > Map, Int2ObjectMap // 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; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java index 3a1de3200..5641cb96c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java @@ -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 implements InstanceHandle { + @UnknownNullability + public AbstractInstancer instancer; + @UnknownNullability + public I instance; + @UnknownNullability + public Supplier> 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 void setDeleted() { 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; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index 214a56795..47e884a52 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -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); } /** diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java index 2b707a3b8..85ad55387 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java @@ -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. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java index 479eaed74..0b9eea6fd 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java @@ -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; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java new file mode 100644 index 000000000..56400e6f6 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java @@ -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; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java index b0766e171..f8597184b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java @@ -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 bindForDraw() { - multiBind(); - GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle()); + public void bindForApply() { + multiBind(3, 2); } - 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); + public void bindForDraw() { + multiBind(1, 4); + GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle()); } /** * 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(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 9a376ab14..c12277860 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -74,8 +74,7 @@ public void flushInstancers() { continue; } - instancer.modelIndex = modelIndex; - instancer.baseInstance = instanceCountThisFrame; + instancer.postUpdate(modelIndex, instanceCountThisFrame); instanceCountThisFrame += instanceCount; modelIndex++; @@ -96,6 +95,8 @@ public void upload(StagingBuffer stagingBuffer) { // 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 void dispatchCull() { 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 void dispatchApply() { return; } - buffers.bindForCompute(); + buffers.bindForApply(); glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1); } @@ -171,7 +172,9 @@ public boolean hasVisualType(VisualType visualType) { } public void add(IndirectInstancer instancer, InstancerKey key, MeshPool meshPool) { - instancer.modelIndex = instancers.size(); + instancer.mapping = buffers.objectStorage.createMapping(); + instancer.postUpdate(instancers.size(), -1); + instancers.add(instancer); List meshes = key.model() @@ -242,12 +245,7 @@ private void drawBarrier() { 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()); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java index fb763d006..48517d1a2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java @@ -72,9 +72,9 @@ public void write(long ptr) { 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 void writeWithOverrides(long ptr, int instanceIndex, Material materialOve 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 diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index adabbf653..e19999b5c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -46,6 +46,8 @@ public class IndirectDrawManager extends DrawManager> { 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 IndirectDrawManager(IndirectPrograms programs) { meshPool.bind(vertexArray); lightBuffers = new LightBuffers(); matrixBuffer = new MatrixBuffer(); + + depthPyramid = new DepthPyramid(programs.getDownsampleFirstProgram(), programs.getDownsampleSecondProgram()); } @Override protected IndirectInstancer create(InstancerKey key) { - return new IndirectInstancer<>(key.type(), key.environment(), key.model()); + return new IndirectInstancer<>(key, () -> getInstancer(key)); } @SuppressWarnings("unchecked") @@ -136,6 +140,8 @@ public void flush(LightStorage lightStorage, EnvironmentStorage environmentStora 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 void flush(LightStorage lightStorage, EnvironmentStorage environmentStora matrixBuffer.bind(); + depthPyramid.bindForCull(); + for (var group : cullingGroups.values()) { group.dispatchCull(); } @@ -174,6 +182,8 @@ public void delete() { crumblingDrawBuffer.delete(); programs.release(); + + depthPyramid.delete(); } public void renderCrumbling(List crumblingBlocks) { @@ -209,8 +219,8 @@ public void renderCrumbling(List crumblingBlocks) { 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. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java index 75dc2b8e3..26ada98bb 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -2,16 +2,17 @@ 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 extends AbstractInstancer { @@ -20,18 +21,34 @@ public class IndirectInstancer extends AbstractInstancer private final List 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 type, Environment environment, Model model) { - super(type, environment); + public ObjectStorage.@UnknownNullability Mapping mapping; + + private int modelIndex = -1; + private int baseInstance = -1; + + public IndirectInstancer(InstancerKey key, Supplier> 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 void update() { 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 void writeModel(long ptr) { } 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)); + + 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); - stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte + startInclusive * instanceStride, ptr -> { - for (int i = startInclusive; i <= actualEnd; i++) { + 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; } - }); - }); - } - - private void uploadAllInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) { - long totalSize = instances.size() * instanceStride; + continue; + } - 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; } - }); - } - - private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexBaseByte, int modelIndexVbo) { - long modelIndexTotalSize = instances.size() * IndirectBuffers.INT_SIZE; + stagingBuffer.enqueueCopy(block.ptr(), size, instanceVbo, baseByte); + } - 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 void delete() { for (IndirectDraw draw : draws()) { draw.delete(); } + + mapping.delete(); + } + + public int modelIndex() { + return modelIndex; + } + + public int baseInstance() { + return baseInstance; } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java new file mode 100644 index 000000000..027af4c6c --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java @@ -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. + * + *

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); + } + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java index c976dcfce..308f97af4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java @@ -223,7 +223,7 @@ public void delete() { 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) { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java index 8b241f6c0..400dae79c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -128,7 +128,7 @@ public void delete() { @Override protected InstancedInstancer create(InstancerKey key) { - return new InstancedInstancer<>(key.type(), key.environment()); + return new InstancedInstancer<>(key, () -> getInstancer(key)); } @Override @@ -180,8 +180,8 @@ public void renderCrumbling(List crumblingBlocks) { 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); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java index a5dff27d9..d72117e7d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java @@ -2,14 +2,14 @@ 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 extends AbstractInstancer private final List draws = new ArrayList<>(); - public InstancedInstancer(InstanceType type, Environment environment) { - super(type, environment); + public InstancedInstancer(InstancerKey key, Supplier> recreate) { + super(key, recreate); var layout = type.layout(); // Align to one texel in the texture buffer instanceStride = MoreMath.align16(layout.byteSize()); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java index 7ca60b0d9..a82d92e52 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java @@ -8,6 +8,7 @@ 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.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 static void update(RenderContext context) { ptr = writeInt(ptr, debugMode); + ptr = writeCullData(ptr); + firstWrite = false; BUFFER.markDirty(); } @@ -179,6 +182,26 @@ private static long writeCameraIn(long ptr, Camera camera) { 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.

* Uses a different format that is friendly towards an optimized instruction-parallel diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java index 4dca28b58..a3c67b0d2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java @@ -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.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]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java index bdf048ff4..785b17fca 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java @@ -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 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]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java index a7bdbf978..6488ea03d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java @@ -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 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]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java index b221fdddf..9438ef355 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java @@ -11,6 +11,7 @@ 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 void setUInt(String glslName, int value) { 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); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java index 0cfd3058b..5d4347ad4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java @@ -5,9 +5,10 @@ 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 @@ public ErrorBuilder generateMessage() { .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); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java index 020b85692..1f9646c56 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java @@ -4,13 +4,13 @@ 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.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 Span getLineSpanMatching(int line, @Nullable String match) { * @param name The name of the struct to find. * @return null if no definition matches the name. */ - public Optional 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 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 diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java index ebc40e19e..934c40865 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java @@ -128,10 +128,6 @@ public int length() { return raw.length(); } - public int lineStartCol(int spanLine) { - return 0; - } - public int lineWidth(int spanLine) { return lines.get(spanLine) .length(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java index f4e5fe023..b23a05954 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java @@ -29,7 +29,6 @@ public class ErrorBuilder { private final List lines = new ArrayList<>(); private ErrorBuilder() { - } public static ErrorBuilder create() { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java index d2622ffcd..3e7811a04 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java @@ -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 @@ default String build() { default String left() { return ""; } + default String right() { return ""; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java index e9f77a090..d7571cec8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java @@ -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 "-"; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java index dc4f24703..d6af1f811 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java @@ -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; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java index 8015f5abe..96a2c7a93 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java @@ -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); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java index 1d1e41a72..1fe6c1b28 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java @@ -7,11 +7,6 @@ public SpanHighlightLine(int firstCol, int lastCol) { line = generateUnderline(firstCol, lastCol); } - @Override - public String left() { - return ""; - } - @Override public String right() { return line; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java index e957106db..5880d8566 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java @@ -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; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java index dcfea176c..6dd81ff65 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java @@ -3,12 +3,12 @@ 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> args) { - public static Builder create() { return new Builder(); } @@ -26,7 +26,7 @@ public static FnSignature ofVoid(String name) { public Collection createArgExpressions() { return args.stream() - .map(Pair::second) + .map(Pair::getSecond) .map(GlslExpr::variable) .collect(Collectors.toList()); } @@ -37,18 +37,20 @@ public boolean isVoid() { 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> args = ImmutableList.builder(); @@ -77,5 +79,4 @@ public FnSignature build() { return new FnSignature(returnType, name, args.build()); } } - } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java index 74f95b389..ba28d74ab 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java @@ -19,29 +19,33 @@ public GlslStruct struct() { 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 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 _raw(String sourceString) { + add(new Raw(sourceString)); } - public void _addRaw(String sourceString) { - elements.add(() -> sourceString); + public T add(T element) { + elements.add(element); + return element; } public String build() { @@ -54,6 +58,20 @@ public interface Declaration { 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 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 { + public record Raw(String sourceString) implements Declaration { @Override public String prettyPrint() { - return "#undef " + name; + return sourceString; } } - } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java index c9f15ea73..0d4d36b86 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java @@ -8,7 +8,6 @@ import com.google.common.collect.ImmutableList; public interface GlslExpr { - /** * Create a glsl variable with the given name. * @@ -129,7 +128,6 @@ record Variable(String name) implements GlslExpr { public String prettyPrint() { return name; } - } record FunctionCall(String name, Collection args) implements GlslExpr { @@ -144,7 +142,6 @@ public String prettyPrint() { .collect(Collectors.joining(", ")); return name + "(" + args + ")"; } - } record FunctionCall0(String name) implements GlslExpr { @@ -152,7 +149,6 @@ record FunctionCall0(String name) implements GlslExpr { public String prettyPrint() { return name + "()"; } - } record Swizzle(GlslExpr target, String selection) implements GlslExpr { @@ -160,7 +156,6 @@ record Swizzle(GlslExpr target, String selection) implements GlslExpr { public String prettyPrint() { return target.prettyPrint() + "." + selection; } - } record Access(GlslExpr target, String argName) implements GlslExpr { @@ -168,7 +163,6 @@ record Access(GlslExpr target, String argName) implements GlslExpr { public String prettyPrint() { return target.prettyPrint() + "." + argName; } - } record Clamp(GlslExpr value, GlslExpr from, GlslExpr to) implements GlslExpr { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java index cd6bbea68..f56735a52 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java @@ -5,24 +5,25 @@ 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 f) { - f.accept(body); + public GlslFn body(GlslBlock block) { + body = block; return this; } - public GlslFn body(GlslBlock block) { - body = block; + public GlslFn body(Consumer f) { + f.accept(body); return this; } + @Override public String prettyPrint() { return """ %s { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java index 5da578f56..f60c514d2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java @@ -4,15 +4,15 @@ 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> fields = new ArrayList<>(); private String name; + private final List> fields = new ArrayList<>(); - public GlslStruct setName(String name) { + public GlslStruct name(String name) { this.name = name; return this; } @@ -22,12 +22,6 @@ public GlslStruct addField(String type, String name) { 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 String prettyPrint() { %s };""".formatted(name, StringUtil.indent(buildFields(), 4)); } + + private String buildFields() { + return fields.stream() + .map(p -> p.getFirst() + ' ' + p.getSecond() + ';') + .collect(Collectors.joining("\n")); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java index 37ef474b8..5112165cb 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java @@ -4,13 +4,17 @@ 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> cases = new ArrayList<>(); + @Nullable private GlslBlock defaultCase = null; private GlslSwitch(GlslExpr on) { @@ -52,9 +56,9 @@ private String formatCases() { } private static String prettyPrintCase(Pair p) { - var variant = p.first() + var variant = p.getFirst() .prettyPrint(); - var block = p.second() + var block = p.getSecond() .prettyPrint(); return """ case %s: diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java new file mode 100644 index 000000000..bc87770b6 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java @@ -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 + ";"; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java index 011cfe822..f1edcbebc 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java @@ -4,7 +4,8 @@ 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> 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 GlslUniformBlock member(String typeName, String variableName) { 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")); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java index c33f11c12..9763026b4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java @@ -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; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java index 159009b21..006338c6f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java @@ -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 parseImports(SourceLines source) { - Matcher uses = PATTERN.matcher(source); + Matcher matcher = PATTERN.matcher(source); var imports = ImmutableList.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)); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java index 5ed22b77a..3f7871d43 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java @@ -13,17 +13,19 @@ 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 static ImmutableMap parseFields(SourceLines source) 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; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java index 5d6c06bbf..4ddec0e01 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java @@ -17,17 +17,16 @@ 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 parameters; + public final ImmutableList parameters; public ShaderFunction(Span self, Span type, Span name, Span args, Span body) { this.self = self; @@ -95,22 +94,6 @@ private static int findEndOfBlock(CharSequence source, int start) { 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 Span getParameterType(int index) { return parameters.get(index).type; } - public ImmutableList getParameters() { - return parameters; - } - - public String returnTypeName() { - return type.get(); - } - protected ImmutableList parseArguments() { if (args.isErr() || args.isEmpty()) return ImmutableList.of(); - Matcher arguments = argument.matcher(args.get()); + Matcher arguments = ARGUMENT_PATTERN.matcher(args.get()); ImmutableList.Builder builder = ImmutableList.builder(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java index d1af3342e..3b4a7fe7a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java @@ -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 fields; - private final ImmutableMap fields2Types; + public final ImmutableList fields; + public final ImmutableMap 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 static ImmutableMap parseStructs(SourceLines source return structs.build(); } - public Span getName() { - return name; - } - - public Span getBody() { - return body; - } - - public ImmutableList getFields() { - return fields; - } - - private ImmutableMap createTypeLookup() { - ImmutableMap.Builder lookup = ImmutableMap.builder(); - for (StructField field : fields) { - lookup.put(field.name.get(), field.type); - } - - return lookup.build(); - } - private ImmutableList parseFields() { - Matcher matcher = StructField.fieldPattern.matcher(body); + Matcher matcher = StructField.PATTERN.matcher(body); ImmutableList.Builder fields = ImmutableList.builder(); @@ -88,6 +68,15 @@ private ImmutableList parseFields() { return fields.build(); } + private ImmutableMap createTypeLookup() { + ImmutableMap.Builder lookup = ImmutableMap.builder(); + for (StructField field : fields) { + lookup.put(field.name.get(), field.type); + } + + return lookup.build(); + } + @Override public String toString() { return "struct " + name; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java index 707c92830..6eee86538 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java @@ -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 enum Qualifier { NONE, IN, OUT, - INOUT, - ERROR; + INOUT; + @Nullable public static Qualifier fromSpan(Span s) { String span = s.toString(); @@ -38,7 +40,7 @@ public static Qualifier fromSpan(Span s) { case "in" -> IN; case "inout" -> INOUT; case "out" -> OUT; - default -> ERROR; + default -> null; }; } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java index b0b3f7448..016494607 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java @@ -5,11 +5,11 @@ 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 StructField(Span self, Span type, Span name) { this.name = name; } - public Span getType() { - return type; - } - - public Span getName() { - return name; - } - @Override public String toString() { return type + " " + name; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java index 547ffbb0c..75d50165b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java @@ -3,7 +3,6 @@ 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); } diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl index 346adfa93..256bd68eb 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl @@ -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 diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl index 65d5baae0..38724599f 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl @@ -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); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl new file mode 100644 index 000000000..48fd55ec2 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl @@ -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)); +} + diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl new file mode 100644 index 000000000..394667598 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl @@ -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); +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl new file mode 100644 index 000000000..29ba31ea0 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl @@ -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(); +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert index e53dff313..d94725fcb 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert @@ -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[]; }; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index 4b3cfe69f..05b3110dc 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -9,6 +9,17 @@ struct FrustumPlanes { vec2 zW; // }; +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) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java index 8cb66a7f0..ffb0d4f0a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java @@ -17,7 +17,6 @@ public final class InstanceTypes { .vector("overlay", IntegerRepr.SHORT, 2) .vector("light", FloatRepr.UNSIGNED_SHORT, 2) .matrix("pose", FloatRepr.FLOAT, 4) - .matrix("normal", FloatRepr.FLOAT, 3) .build()) .writer((ptr, instance) -> { MemoryUtil.memPutByte(ptr, instance.red); @@ -26,13 +25,34 @@ 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.putMatrix3f(ptr + 76, instance.normal); + ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose); }) .vertexShader(Flywheel.rl("instance/transformed.vert")) .cullShader(Flywheel.rl("instance/cull/transformed.glsl")) .register(); + public static final InstanceType POSED = SimpleInstanceType.builder(PosedInstance::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) + .matrix("normal", FloatRepr.FLOAT, 3) + .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); + ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal); + }) + .vertexShader(Flywheel.rl("instance/posed.vert")) + .cullShader(Flywheel.rl("instance/cull/posed.glsl")) + .register(); + public static final InstanceType ORIENTED = SimpleInstanceType.builder(OrientedInstance::new) .layout(LayoutBuilder.create() .vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java new file mode 100644 index 000000000..43e6df47b --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java @@ -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 { + public final Matrix4f pose = new Matrix4f(); + public final Matrix3f normal = new Matrix3f(); + + public PosedInstance(InstanceType 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. + * + *

+ * This will allow the GPU to quickly discard all geometry for this instance, effectively "turning it off". + *

+ */ + public PosedInstance setZeroTransform() { + pose.zero(); + normal.zero(); + return this; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 13938d201..74700e480 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -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 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 { - public final Matrix4f model = new Matrix4f(); - public final Matrix3f normal = new Matrix3f(); +public class TransformedInstance extends ColoredLitInstance implements Affine { + public final Matrix4f pose = new Matrix4f(); public TransformedInstance(InstanceType 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); - - if (x == y && y == z) { - if (x < 0.0f) { - normal.scale(-1.0f); - } - - return this; - } + pose.scale(x, y, z); + 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 TransformedInstance setTransform(PoseStack stack) { } public TransformedInstance setIdentityTransform() { - model.identity(); - normal.identity(); + pose.identity(); return this; } @@ -97,8 +68,7 @@ public TransformedInstance setIdentityTransform() { *

*/ public TransformedInstance setZeroTransform() { - model.zero(); - normal.zero(); + pose.zero(); return this; } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 158c12924..9a112c07d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -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 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 getPoseStack(PoseStack stack); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java deleted file mode 100644 index 9cd66069e..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java +++ /dev/null @@ -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 { - private static final Set> ALL = FlwUtil.createWeakHashSet(); - private final Function factory; - private final Map map = new ConcurrentHashMap<>(); - - public ModelCache(Function 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(); - } - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java deleted file mode 100644 index 7ca5f5199..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java +++ /dev/null @@ -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 ALL = FlwUtil.createWeakHashSet(); - private final Supplier factory; - @Nullable - private volatile Model model; - - public ModelHolder(Supplier 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(); - } - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java index 93a4849a5..9e0e3a813 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java @@ -9,6 +9,7 @@ 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 @@ * method with the same parameters will return the same object. */ public final class Models { - private static final ModelCache BLOCK_STATE = new ModelCache<>(it -> BlockModelBuilder.create(it) + private static final ResourceReloadCache BLOCK_STATE = new ResourceReloadCache<>(it -> BlockModelBuilder.create(it) .build()); - private static final ModelCache PARTIAL = new ModelCache<>(it -> BakedModelBuilder.create(it.get()) + private static final ResourceReloadCache PARTIAL = new ResourceReloadCache<>(it -> BakedModelBuilder.create(it.get()) .build()); - private static final ModelCache> TRANSFORMED_PARTIAL = new ModelCache<>(TransformedPartial::create); + private static final ResourceReloadCache, Model> TRANSFORMED_PARTIAL = new ResourceReloadCache<>(TransformedPartial::create); private Models() { } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java new file mode 100644 index 000000000..2aa476891 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java @@ -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(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java new file mode 100644 index 000000000..da3d64bd6 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -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 consumer) { + if (instance != null) { + consumer.accept(instance); + } + for (InstanceTree child : children) { + child.traverse(consumer); + } + } + + @ApiStatus.Experimental + public void traverse(int i, ObjIntConsumer 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 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. + * + *

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. + * + *

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 { + void accept(T t, int i, int j); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java new file mode 100644 index 000000000..98c10fc0c --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -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 THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); + private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last(); + private static final ResourceReloadCache 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(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java deleted file mode 100644 index 63560d857..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java +++ /dev/null @@ -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 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(); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java new file mode 100644 index 000000000..5774ebf44 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java @@ -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 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; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java new file mode 100644 index 000000000..7b9599f82 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java @@ -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 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 pathsToPrune, Material material) { + return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), null, material)); + } + + public static ModelTree of(ModelLayerLocation layer, Set 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 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 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 pathsToPrune, @Nullable net.minecraft.client.resources.model.Material texture, Material material) { + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java index 15304cb38..ec85caf3b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java @@ -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 @@ public VertexWriter() { 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 @@ public VertexConsumer setColor(int red, int green, int blue, int alpha) { @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 @@ public MemoryBlock copyDataAndReset() { filledPosition = false; filledTexture = false; filledNormal = false; - textureMapper = null; return dataCopy; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java deleted file mode 100644 index c00d10d97..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java +++ /dev/null @@ -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 Set createWeakHashSet() { - return Collections.newSetFromMap(new WeakHashMap<>()); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java deleted file mode 100644 index a54e9e297..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -import java.util.Objects; - -public record Pair(F first, S second) { - public static Pair of(F first, S second) { - return new Pair<>(first, second); - } - - public Pair swap() { - return Pair.of(second, first); - } - - public Pair 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 + ")"; - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java new file mode 100644 index 000000000..fb73e087a --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java @@ -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. + * + *

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 CANNOT safely store a Pose object outside + * the RecyclingPoseStack that created it. + */ +public class RecyclingPoseStack extends PoseStack { + private final Deque 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()); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java new file mode 100644 index 000000000..dcacb38d3 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java @@ -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 implements Function { + private static final Set> ALL = Collections.newSetFromMap(new WeakHashMap<>()); + private final Function factory; + private final Map map = new ConcurrentHashMap<>(); + + public ResourceReloadCache(Function 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(); + } + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java new file mode 100644 index 000000000..9ff36d40c --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java @@ -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 implements Supplier { + private static final Set> ALL = Collections.newSetFromMap(new WeakHashMap<>()); + private final Supplier factory; + @Nullable + private volatile T obj; + + public ResourceReloadHolder(Supplier 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(); + } + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java index 2563e78ff..0c45179f1 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java @@ -53,10 +53,11 @@ public static ResourceLocation readFlywheelDefault(StringReader reader) throws C } } + /** + * 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('.')); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java index 93364d43a..6cf5bfcbd 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java @@ -57,12 +57,6 @@ public static String formatAddress(long address) { 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()); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java deleted file mode 100644 index 2544e4286..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -public enum Unit { - INSTANCE; -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java new file mode 100644 index 000000000..bf06084a2 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java @@ -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); + } + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java index a82e08459..25d05d481 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java @@ -52,7 +52,7 @@ public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float 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 boolean doDistanceLimitThisFrame(DynamicVisual.Context context) { .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); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java index f9aecce1e..80ffa73a0 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java @@ -67,6 +67,7 @@ public double distanceSquared(double x, double y, double z) { */ 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 Vector3f getVisualPosition() { */ 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 boolean isVisible(FrustumIntersection frustum) { 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); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java index c21866435..e6ed8c227 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java @@ -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 void update(float partialTick) { protected abstract void _delete(); + protected InstancerProvider instancerProvider() { + return visualizationContext.instancerProvider(); + } + + protected Vec3i renderOrigin() { + return visualizationContext.renderOrigin(); + } + @Override public final void delete() { if (deleted) { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java index 13d60a58d..0e0cb8301 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java @@ -15,9 +15,9 @@ 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 FIRE_MODELS = new ModelCache<>(texture -> { + private static final ResourceReloadCache FIRE_MODELS = new ResourceReloadCache<>(texture -> { return new SingleMeshModel(new FireMesh(texture.sprite()), FIRE_MATERIAL); }); diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl new file mode 100644 index 000000000..c8d9a275c --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl @@ -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); +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert new file mode 100644 index 000000000..918883e84 --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert @@ -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; +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert index 918883e84..e0abf722b 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert @@ -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. diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java index ba661d25c..760711c0f 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java @@ -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(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java index d1eafb15e..2b33206ac 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java @@ -35,7 +35,7 @@ public static void init() { StandardMaterialShaders.init(); // backend - FlwBackend.init(); + FlwBackend.init(FlwConfig.INSTANCE.backendConfig()); // vanilla VanillaVisuals.init(); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 7b0e0e682..47fffeeb6 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -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 Logger getLogger() { public PoseTransformStack getPoseTransformStackOf(PoseStack stack) { return ((PoseStackExtension) stack).flywheel$transformStack(); } + + @Override + public Map 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 getPoseStack(PoseStack stack) { + return ((PoseStackAccessor) stack).flywheel$getPoseStack(); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java b/common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java similarity index 93% rename from common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java rename to common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java index 871184d5d..da04731c5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java @@ -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; diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java new file mode 100644 index 000000000..274e7a829 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java @@ -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 flywheel$children(); + + @Invoker("compile") + void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java new file mode 100644 index 000000000..22478d2a3 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java @@ -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 flywheel$getPoseStack(); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index ad7648c7b..c1ebae568 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -2,23 +2,20 @@ import java.util.function.Consumer; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; -import org.joml.Vector3f; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.OrientedInstance; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelHolder; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.blockentity.BellRenderer; +import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BellBlockEntity; @@ -27,27 +24,23 @@ public class BellVisual extends AbstractBlockEntityVisual imple .mipmap(false) .build(); - private static final ModelHolder BELL_MODEL = new ModelHolder(() -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.BELL, BellRenderer.BELL_RESOURCE_LOCATION.sprite(), "bell_body"), MATERIAL); - }); + private final InstanceTree instances; + private final InstanceTree bellBody; - private final OrientedInstance bell; + private final Matrix4fc initialPose; private boolean wasShaking = false; public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - bell = createBellInstance().pivot(0.5f, 0.75f, 0.5f) - .position(getVisualPosition()); - bell.setChanged(); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(ModelLayers.BELL, BellRenderer.BELL_RESOURCE_LOCATION, MATERIAL)); + bellBody = instances.childOrThrow("bell_body"); - updateRotation(partialTick); - } + BlockPos visualPos = getVisualPosition(); + initialPose = new Matrix4f().translate(visualPos.getX(), visualPos.getY(), visualPos.getZ()); - private OrientedInstance createBellInstance() { - return instancerProvider.instancer(InstanceTypes.ORIENTED, BELL_MODEL.get()) - .createInstance(); + updateRotation(partialTick); } @Override @@ -60,37 +53,46 @@ public void beginFrame(Context context) { } private void updateRotation(float partialTick) { + float xRot = 0; + float zRot = 0; + if (blockEntity.shaking) { float ringTime = (float) blockEntity.ticks + partialTick; float angle = Mth.sin(ringTime / (float) Math.PI) / (4.0F + ringTime / 3.0F); - Vector3f ringAxis = blockEntity.clickDirection.getCounterClockWise() - .step(); - - bell.rotation(new Quaternionf(new AxisAngle4f(angle, ringAxis))) - .setChanged(); + switch (blockEntity.clickDirection) { + case NORTH -> xRot = -angle; + case SOUTH -> xRot = angle; + case EAST -> zRot = -angle; + case WEST -> zRot = angle; + } wasShaking = true; } else if (wasShaking) { - bell.rotation(new Quaternionf()) - .setChanged(); - wasShaking = false; } + + bellBody.xRot(xRot); + bellBody.zRot(zRot); + instances.updateInstancesStatic(initialPose); } @Override public void updateLight(float partialTick) { - relight(bell); + int packedLight = computePackedLight(); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(bell); + instances.traverse(consumer); } @Override protected void _delete() { - bell.delete(); + instances.delete(); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 086a11841..ebf0d2456 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -5,26 +5,29 @@ import java.util.Map; import java.util.function.Consumer; -import org.joml.Quaternionf; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.OrientedInstance; -import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.util.Pair; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.Material; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.util.Mth; import net.minecraft.world.level.block.AbstractChestBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ChestBlock; @@ -35,7 +38,7 @@ import net.minecraft.world.level.block.state.properties.ChestType; public class ChestVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - public static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.CHEST_SHEET) .mipmap(false) @@ -48,77 +51,81 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } - private static final ModelCache> BOTTOM_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), MATERIAL); - }); - private static final ModelCache> LID_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), MATERIAL); - }); - private static final ModelCache> LOCK_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), MATERIAL); - }); - - private final OrientedInstance bottom; - private final TransformedInstance lid; - private final TransformedInstance lock; - - private final ChestType chestType; + @Nullable + private final InstanceTree instances; + @Nullable + private final InstanceTree lid; + @Nullable + private final InstanceTree lock; + + @Nullable + private final Matrix4fc initialPose; + private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner(); + @Nullable + private final DoubleBlockCombiner.NeighborCombineResult neighborCombineResult; + @Nullable private final Float2FloatFunction lidProgress; - private final Quaternionf baseRotation = new Quaternionf(); - private float lastProgress = Float.NaN; public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; - Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()); - - bottom = createBottomInstance(texture).position(getVisualPosition()); - lid = createLidInstance(texture); - lock = createLockInstance(texture); - Block block = blockState.getBlock(); if (block instanceof AbstractChestBlock chestBlock) { - float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); - baseRotation.setAngleAxis(Math.toRadians(-horizontalAngle), 0, 1, 0); - - DoubleBlockCombiner.NeighborCombineResult wrapper = chestBlock.combine(blockState, level, pos, true); - lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity)); + ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; + net.minecraft.client.resources.model.Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(LAYER_LOCATIONS.get(chestType), texture, MATERIAL)); + lid = instances.childOrThrow("lid"); + lock = instances.childOrThrow("lock"); + + initialPose = createInitialPose(); + neighborCombineResult = chestBlock.combine(blockState, level, pos, true); + lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity)); + + lastProgress = lidProgress.get(partialTick); + applyLidTransform(lastProgress); } else { - baseRotation.identity(); - lidProgress = $ -> 0f; + instances = null; + lid = null; + lock = null; + initialPose = null; + neighborCombineResult = null; + lidProgress = null; } - - bottom.rotation(baseRotation); - bottom.setChanged(); - - applyLidTransform(lidProgress.get(partialTick)); } - private OrientedInstance createBottomInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.ORIENTED, BOTTOM_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); + private static boolean isChristmas() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26; } - private TransformedInstance createLidInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); + private Matrix4f createInitialPose() { + BlockPos visualPos = getVisualPosition(); + float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); + return new Matrix4f().translate(visualPos.getX(), visualPos.getY(), visualPos.getZ()) + .translate(0.5F, 0.5F, 0.5F) + .rotateY(-horizontalAngle * Mth.DEG_TO_RAD) + .translate(-0.5F, -0.5F, -0.5F); } - private TransformedInstance createLockInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LOCK_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); - } + @Override + public void setSectionCollector(SectionCollector sectionCollector) { + this.lightSections = sectionCollector; - private static boolean isChristmas() { - Calendar calendar = Calendar.getInstance(); - return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26; + if (neighborCombineResult != null) { + lightSections.sections(neighborCombineResult.apply(new SectionPosCombiner())); + } else { + lightSections.sections(LongSet.of(SectionPos.asLong(pos))); + } } @Override public void beginFrame(Context context) { + if (instances == null) { + return; + } + if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) { return; } @@ -136,41 +143,80 @@ private void applyLidTransform(float progress) { progress = 1.0F - progress; progress = 1.0F - progress * progress * progress; - float angleX = -(progress * ((float) Math.PI / 2F)); - - lid.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 9f / 16f, 1f / 16f) - .rotateX(angleX) - .translate(0, -9f / 16f, -1f / 16f) - .setChanged(); - - lock.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 8f / 16f, 0) - .rotateX(angleX) - .translate(0, -8f / 16f, 0) - .setChanged(); + lid.xRot(-(progress * ((float) Math.PI / 2F))); + lock.xRot(lid.xRot()); + instances.updateInstancesStatic(initialPose); } @Override public void updateLight(float partialTick) { - relight(bottom, lid, lock); + if (instances != null) { + int packedLight = neighborCombineResult.apply(brightnessCombiner); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + } } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(bottom); - consumer.accept(lid); - consumer.accept(lock); + if (instances != null) { + instances.traverse(consumer); + } } @Override protected void _delete() { - bottom.delete(); - lid.delete(); - lock.delete(); + if (instances != null) { + instances.delete(); + } + } + + private class SectionPosCombiner implements DoubleBlockCombiner.Combiner { + @Override + public LongSet acceptDouble(BlockEntity first, BlockEntity second) { + long firstSection = SectionPos.asLong(first.getBlockPos()); + long secondSection = SectionPos.asLong(second.getBlockPos()); + + if (firstSection == secondSection) { + return LongSet.of(firstSection); + } else { + return LongSet.of(firstSection, secondSection); + } + } + + @Override + public LongSet acceptSingle(BlockEntity single) { + return LongSet.of(SectionPos.asLong(single.getBlockPos())); + } + + @Override + public LongSet acceptNone() { + return LongSet.of(SectionPos.asLong(pos)); + } + } + + private class BrightnessCombiner implements DoubleBlockCombiner.Combiner { + @Override + public Integer acceptDouble(BlockEntity first, BlockEntity second) { + int firstLight = LevelRenderer.getLightColor(first.getLevel(), first.getBlockPos()); + int secondLight = LevelRenderer.getLightColor(second.getLevel(), second.getBlockPos()); + int firstBlockLight = LightTexture.block(firstLight); + int secondBlockLight = LightTexture.block(secondLight); + int firstSkyLight = LightTexture.sky(firstLight); + int secondSkyLight = LightTexture.sky(secondLight); + return LightTexture.pack(Math.max(firstBlockLight, secondBlockLight), Math.max(firstSkyLight, secondSkyLight)); + } + + @Override + public Integer acceptSingle(BlockEntity single) { + return LevelRenderer.getLightColor(single.getLevel(), single.getBlockPos()); + } + + @Override + public Integer acceptNone() { + return LevelRenderer.getLightColor(level, pos); + } } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index c11e99d1a..09708ed46 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -1,21 +1,20 @@ package dev.engine_room.flywheel.vanilla; import org.jetbrains.annotations.Nullable; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; +import org.joml.Matrix4f; +import org.joml.Matrix4fStack; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visual.DynamicVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.lib.instance.FlatLit; import dev.engine_room.flywheel.lib.instance.InstanceTypes; import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.Models; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; @@ -23,7 +22,6 @@ import dev.engine_room.flywheel.lib.visual.component.HitboxComponent; import dev.engine_room.flywheel.lib.visual.component.ShadowComponent; import net.minecraft.client.model.geom.ModelLayerLocation; -import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.entity.vehicle.AbstractMinecart; @@ -38,31 +36,19 @@ public class MinecartVisual extends ComponentEntityV .mipmap(false) .build(); - public static final ModelHolder CHEST_BODY_MODEL = createBodyModelHolder(ModelLayers.CHEST_MINECART); - public static final ModelHolder COMMAND_BLOCK_BODY_MODEL = createBodyModelHolder(ModelLayers.COMMAND_BLOCK_MINECART); - public static final ModelHolder FURNACE_BODY_MODEL = createBodyModelHolder(ModelLayers.FURNACE_MINECART); - public static final ModelHolder HOPPER_BODY_MODEL = createBodyModelHolder(ModelLayers.HOPPER_MINECART); - public static final ModelHolder STANDARD_BODY_MODEL = createBodyModelHolder(ModelLayers.MINECART); - public static final ModelHolder SPAWNER_BODY_MODEL = createBodyModelHolder(ModelLayers.SPAWNER_MINECART); - public static final ModelHolder TNT_BODY_MODEL = createBodyModelHolder(ModelLayers.TNT_MINECART); - - private final TransformedInstance body; + private final InstanceTree instances; @Nullable private TransformedInstance contents; - private final ModelHolder bodyModel; - - private final PoseStack stack = new PoseStack(); + private final Matrix4fStack stack = new Matrix4fStack(2); private BlockState blockState; private boolean active; - public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelHolder bodyModel) { + public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) { super(ctx, entity, partialTick); - this.bodyModel = bodyModel; - - body = createBodyInstance(); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(layerLocation, MATERIAL)); blockState = entity.getDisplayBlockState(); contents = createContentsInstance(); @@ -74,23 +60,12 @@ public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, Mod updateLight(partialTick); } - private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) { - return new ModelHolder(() -> { - return new SingleMeshModel(ModelPartConverter.convert(layer), MATERIAL); - }); - } - - private TransformedInstance createBodyInstance() { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, bodyModel.get()) - .createInstance(); - } - @Nullable private TransformedInstance createContentsInstance() { RenderShape shape = blockState.getRenderShape(); if (shape == RenderShape.ENTITYBLOCK_ANIMATED) { - body.setZeroTransform(); + instances.traverse(instance -> instance.setZeroTransform().setChanged()); active = false; return null; } @@ -100,7 +75,7 @@ private TransformedInstance createContentsInstance() { return null; } - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.block(blockState)) + return instancerProvider().instancer(InstanceTypes.TRANSFORMED, Models.block(blockState)) .createInstance(); } @@ -134,13 +109,14 @@ public void beginFrame(DynamicVisual.Context context) { } private void updateInstances(float partialTick) { - stack.setIdentity(); + stack.identity(); double posX = Mth.lerp(partialTick, entity.xOld, entity.getX()); double posY = Mth.lerp(partialTick, entity.yOld, entity.getY()); double posZ = Mth.lerp(partialTick, entity.zOld, entity.getZ()); - stack.translate(posX - renderOrigin.getX(), posY - renderOrigin.getY(), posZ - renderOrigin.getZ()); + var renderOrigin = renderOrigin(); + stack.translate((float) (posX - renderOrigin.getX()), (float) (posY - renderOrigin.getY()), (float) (posZ - renderOrigin.getZ())); float yaw = Mth.lerp(partialTick, entity.yRotO, entity.getYRot()); long randomBits = entity.getId() * 493286711L; @@ -164,7 +140,7 @@ private void updateInstances(float partialTick) { offset2 = pos; } - stack.translate(pos.x - posX, (offset1.y + offset2.y) / 2.0D - posY, pos.z - posZ); + stack.translate((float) (pos.x - posX), (float) ((offset1.y + offset2.y) / 2.0D - posY), (float) (pos.z - posZ)); Vec3 vec = offset2.add(-offset1.x, -offset1.y, -offset1.z); if (vec.length() != 0.0D) { vec = vec.normalize(); @@ -173,9 +149,9 @@ private void updateInstances(float partialTick) { } } - stack.translate(0.0D, 0.375D, 0.0D); - stack.mulPose(Axis.YP.rotationDegrees(180 - yaw)); - stack.mulPose(Axis.ZP.rotationDegrees(-pitch)); + stack.translate(0.0F, 0.375F, 0.0F); + stack.rotateY((180 - yaw) * Mth.DEG_TO_RAD); + stack.rotateZ(-pitch * Mth.DEG_TO_RAD); float hurtTime = entity.getHurtTime() - partialTick; float damage = entity.getDamage() - partialTick; @@ -185,40 +161,44 @@ private void updateInstances(float partialTick) { } if (hurtTime > 0) { - stack.mulPose(Axis.XP.rotationDegrees(Mth.sin(hurtTime) * hurtTime * damage / 10.0F * (float) entity.getHurtDir())); + stack.rotateX((Mth.sin(hurtTime) * hurtTime * damage / 10.0F * (float) entity.getHurtDir()) * Mth.DEG_TO_RAD); } - int displayOffset = entity.getDisplayOffset(); if (contents != null) { - stack.pushPose(); + int displayOffset = entity.getDisplayOffset(); + stack.pushMatrix(); stack.scale(0.75F, 0.75F, 0.75F); stack.translate(-0.5F, (float) (displayOffset - 8) / 16, 0.5F); - stack.mulPose(Axis.YP.rotationDegrees(90)); + stack.rotateY(90 * Mth.DEG_TO_RAD); updateContents(contents, stack, partialTick); - stack.popPose(); + stack.popMatrix(); } stack.scale(-1.0F, -1.0F, 1.0F); - body.setTransform(stack) - .setChanged(); + instances.updateInstances(stack); // TODO: Use LightUpdatedVisual/ShaderLightVisual if possible. updateLight(partialTick); } - protected void updateContents(TransformedInstance contents, PoseStack stack, float partialTick) { - contents.setTransform(stack) + protected void updateContents(TransformedInstance contents, Matrix4f pose, float partialTick) { + contents.setTransform(pose) .setChanged(); } public void updateLight(float partialTick) { - relight(partialTick, body, contents); + int packedLight = computePackedLight(partialTick); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + FlatLit.relight(packedLight, contents); } @Override protected void _delete() { super._delete(); - body.delete(); + instances.delete(); if (contents != null) { contents.delete(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index 9e9e17925..0c7abb853 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -1,51 +1,40 @@ package dev.engine_room.flywheel.vanilla; +import java.util.Set; import java.util.function.Consumer; -import org.joml.Quaternionf; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; +import org.joml.Matrix4f; import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.Material; import net.minecraft.core.Direction; +import net.minecraft.util.Mth; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.block.ShulkerBoxBlock; import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; public class ShulkerBoxVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.SHULKER_SHEET) .mipmap(false) .backfaceCulling(false) .build(); + private static final Set PATHS_TO_PRUNE = Set.of("/head"); - private static final ModelCache BASE_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "base"), MATERIAL); - }); - private static final ModelCache LID_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "lid"), MATERIAL); - }); - - private final TransformedInstance base; - private final TransformedInstance lid; + private final InstanceTree instances; + private final InstanceTree lid; - private final PoseStack stack = new PoseStack(); + private final Matrix4f initialPose; private float lastProgress = Float.NaN; @@ -53,38 +42,28 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt super(ctx, blockEntity, partialTick); DyeColor color = blockEntity.getColor(); - Material texture; + net.minecraft.client.resources.model.Material texture; if (color == null) { texture = Sheets.DEFAULT_SHULKER_TEXTURE_LOCATION; } else { texture = Sheets.SHULKER_TEXTURE_LOCATION.get(color.getId()); } - var rotation = getDirection().getRotation(); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(ModelLayers.SHULKER, PATHS_TO_PRUNE, texture, MATERIAL)); + lid = instances.childOrThrow("lid"); - stack.setIdentity(); - TransformStack.of(stack) - .translate(getVisualPosition()) - .translate(0.5f) + initialPose = createInitialPose(); + } + + private Matrix4f createInitialPose() { + var visualPosition = getVisualPosition(); + var rotation = getDirection().getRotation(); + return new Matrix4f().translate(visualPosition.getX(), visualPosition.getY(), visualPosition.getZ()) + .translate(0.5f, 0.5f, 0.5f) .scale(0.9995f) .rotate(rotation) .scale(1, -1, -1) - .translateY(-1); - - base = createBaseInstance(texture).setTransform(stack); - base.setChanged(); - lid = createLidInstance(texture).setTransform(stack); - lid.setChanged(); - } - - private TransformedInstance createBaseInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BASE_MODELS.get(texture)) - .createInstance(); - } - - private TransformedInstance createLidInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(texture)) - .createInstance(); + .translate(0, -1, 0); } private Direction getDirection() { @@ -107,33 +86,28 @@ public void beginFrame(Context context) { } lastProgress = progress; - Quaternionf spin = Axis.YP.rotationDegrees(270.0f * progress); - - TransformStack.of(stack) - .pushPose() - .translateY(-progress * 0.5f) - .rotate(spin); - - lid.setTransform(stack) - .setChanged(); + lid.yRot(1.5f * Mth.PI * progress); + lid.yPos(24f - progress * 8f); - stack.popPose(); + instances.updateInstancesStatic(initialPose); } @Override public void updateLight(float partialTick) { - relight(base, lid); + int packedLight = computePackedLight(); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(base); - consumer.accept(lid); + instances.traverse(consumer); } @Override protected void _delete() { - base.delete(); - lid.delete(); + instances.delete(); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java index 223c511b9..4ba546586 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java @@ -1,9 +1,10 @@ package dev.engine_room.flywheel.vanilla; -import com.mojang.blaze3d.vertex.PoseStack; +import org.joml.Matrix4f; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.instance.TransformedInstance; +import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.util.Mth; import net.minecraft.world.entity.vehicle.MinecartTNT; @@ -12,11 +13,11 @@ public class TntMinecartVisual extends MinecartVisual private static final int WHITE_OVERLAY = OverlayTexture.pack(OverlayTexture.u(1.0F), 10); public TntMinecartVisual(VisualizationContext ctx, T entity, float partialTick) { - super(ctx, entity, partialTick, TNT_BODY_MODEL); + super(ctx, entity, partialTick, ModelLayers.TNT_MINECART); } @Override - protected void updateContents(TransformedInstance contents, PoseStack stack, float partialTick) { + protected void updateContents(TransformedInstance contents, Matrix4f pose, float partialTick) { int fuseTime = entity.getFuse(); if (fuseTime > -1 && (float) fuseTime - partialTick + 1.0F < 10.0F) { float f = 1.0F - ((float) fuseTime - partialTick + 1.0F) / 10.0F; @@ -24,7 +25,7 @@ protected void updateContents(TransformedInstance contents, PoseStack stack, flo f *= f; f *= f; float scale = 1.0F + f * 0.3F; - stack.scale(scale, scale, scale); + pose.scale(scale); } int overlay; @@ -34,7 +35,7 @@ protected void updateContents(TransformedInstance contents, PoseStack stack, flo overlay = OverlayTexture.NO_OVERLAY; } - contents.setTransform(stack) + contents.setTransform(pose) .overlay(overlay) .setChanged(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java index ce7410d18..53efc4562 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java @@ -3,6 +3,7 @@ import static dev.engine_room.flywheel.lib.visualization.SimpleBlockEntityVisualizer.builder; import static dev.engine_room.flywheel.lib.visualization.SimpleEntityVisualizer.builder; +import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -47,27 +48,27 @@ public static void init() { .apply(); builder(EntityType.CHEST_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.CHEST_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.CHEST_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.COMMAND_BLOCK_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.COMMAND_BLOCK_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.COMMAND_BLOCK_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.FURNACE_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.FURNACE_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.FURNACE_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.HOPPER_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.HOPPER_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.HOPPER_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.STANDARD_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.SPAWNER_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.SPAWNER_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.SPAWNER_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.TNT_MINECART) diff --git a/common/src/main/resources/flywheel.impl.mixins.json b/common/src/main/resources/flywheel.impl.mixins.json index a932159b9..97a112ad7 100644 --- a/common/src/main/resources/flywheel.impl.mixins.json +++ b/common/src/main/resources/flywheel.impl.mixins.json @@ -13,6 +13,8 @@ "LevelMixin", "LevelRendererMixin", "MinecraftMixin", + "ModelPartAccessor", + "PoseStackAccessor", "PoseStackMixin", "fix.FixFabulousDepthMixin", "visualmanage.BlockEntityMixin", diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java index fe6be7887..5c159daf8 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java @@ -30,10 +30,10 @@ static E assertSimpleNestedErrorsToDepth(Class finalErr var pair = assertSingletonList(includeError.innerErrors()); for (int i = 1; i < depth; i++) { - includeError = assertInstanceOf(LoadError.IncludeError.class, pair.second()); + includeError = assertInstanceOf(LoadError.IncludeError.class, pair.getSecond()); pair = assertSingletonList(includeError.innerErrors()); } - return assertInstanceOf(finalErrType, pair.second()); + return assertInstanceOf(finalErrType, pair.getSecond()); } public static SourceFile findAndAssertSuccess(MockShaderSources sources, ResourceLocation loc) { diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java index 51bcccf91..3acf1ba2d 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java @@ -129,11 +129,11 @@ void testSelfInclude() { var shouldBeRecursiveIncludePair = assertSingletonList(aErr.innerErrors()); - var circularDependency = assertInstanceOf(LoadError.CircularDependency.class, shouldBeRecursiveIncludePair.second()); + var circularDependency = assertInstanceOf(LoadError.CircularDependency.class, shouldBeRecursiveIncludePair.getSecond()); assertEquals(ImmutableList.of(FLW_A, FLW_A), circularDependency.stack()); assertEquals(FLW_A, circularDependency.offender()); - assertEquals(FLW_A.toString(), shouldBeRecursiveIncludePair.first() + assertEquals(FLW_A.toString(), shouldBeRecursiveIncludePair.getFirst() .toString()); } diff --git a/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java b/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java index 90991fbd8..9a967baac 100644 --- a/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java +++ b/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java @@ -21,8 +21,8 @@ import dev.engine_room.flywheel.lib.task.Synchronizer; import dev.engine_room.flywheel.lib.task.UnitPlan; import dev.engine_room.flywheel.lib.task.functional.RunnableWithContext; -import dev.engine_room.flywheel.lib.util.Unit; import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.util.Unit; class PlanExecutionTest { protected static ParallelTaskExecutor EXECUTOR; diff --git a/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java b/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java index 2569b5848..d35b7ac26 100644 --- a/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java +++ b/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import dev.engine_room.flywheel.api.task.Plan; -import dev.engine_room.flywheel.lib.util.Unit; +import net.minecraft.util.Unit; public class PlanCompositionTest { public static final Plan SIMPLE = SimplePlan.of(() -> { diff --git a/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java b/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java index 23a08243e..661f58bc7 100644 --- a/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java +++ b/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java @@ -1,22 +1,12 @@ package dev.engine_room.flywheel.backend; -import org.jetbrains.annotations.UnknownNullability; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.state.BlockState; public class FlwBackendXplatImpl implements FlwBackendXplat { - @UnknownNullability - public static BackendConfig CONFIG; - @Override public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { return state.getLightEmission(); } - - @Override - public BackendConfig getConfig() { - return CONFIG; - } } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java index d82fabfc7..f5db4e6c5 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java @@ -16,7 +16,6 @@ import dev.engine_room.flywheel.api.backend.BackendManager; import dev.engine_room.flywheel.backend.BackendConfig; import dev.engine_room.flywheel.backend.FlwBackend; -import dev.engine_room.flywheel.backend.FlwBackendXplatImpl; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.ResourceLocationException; @@ -39,16 +38,14 @@ public class FabricFlwConfig implements FlwConfig { private final File file; - public final FabricBackendConfig backendConfig = new FabricBackendConfig(); - public Backend backend = BackendManager.defaultBackend(); public boolean limitUpdates = LIMIT_UPDATES_DEFAULT; public int workerThreads = WORKER_THREADS_DEFAULT; + public final FabricBackendConfig backendConfig = new FabricBackendConfig(); + public FabricFlwConfig(File file) { this.file = file; - - FlwBackendXplatImpl.CONFIG = backendConfig; } @Override @@ -66,6 +63,11 @@ public int workerThreads() { return workerThreads; } + @Override + public BackendConfig backendConfig() { + return backendConfig; + } + public void load() { if (file.exists()) { try (FileReader reader = new FileReader(file)) { @@ -177,7 +179,7 @@ public JsonObject toJson() { object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString()); object.addProperty("limitUpdates", limitUpdates); object.addProperty("workerThreads", workerThreads); - object.add("flw_backend", backendConfig.toJson()); + object.add("flw_backends", backendConfig.toJson()); return object; } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 6bf004116..72ff04be9 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -8,7 +8,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index 5e8ab76ac..50d5129a9 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -5,13 +5,12 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.event.EndClientResourceReloadCallback; import dev.engine_room.flywheel.api.event.ReloadLevelRendererCallback; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.util.ResourceReloadHolder; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -67,10 +66,8 @@ private static void setupImpl() { } private static void setupLib() { - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> - ModelCache.onEndClientResourceReload()); - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> - ModelHolder.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadCache.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadHolder.onEndClientResourceReload()); ModelLoadingPlugin.register(ctx -> { ctx.addModels(PartialModelEventHandler.onRegisterAdditional()); diff --git a/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java b/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java index c8eb18fb7..f49549f3e 100644 --- a/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java +++ b/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java @@ -1,22 +1,12 @@ package dev.engine_room.flywheel.backend; -import org.jetbrains.annotations.UnknownNullability; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.state.BlockState; public class FlwBackendXplatImpl implements FlwBackendXplat { - @UnknownNullability - public static BackendConfig CONFIG; - @Override public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { return state.getLightEmission(level, pos); } - - @Override - public BackendConfig getConfig() { - return CONFIG; - } } diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 1d8d05ba0..d601cfab6 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -6,7 +6,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index 2bc2f9cd9..6625315ea 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -8,14 +8,13 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.event.EndClientResourceReloadEvent; import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; import dev.engine_room.flywheel.lib.util.LevelAttached; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.util.ResourceReloadHolder; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; import net.minecraft.core.registries.Registries; @@ -109,8 +108,8 @@ private static void registerImplEventListeners(IEventBus forgeEventBus, IEventBu private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus modEventBus) { forgeEventBus.addListener((LevelEvent.Unload e) -> LevelAttached.invalidateLevel(e.getLevel())); - modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload()); - modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadCache.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadHolder.onEndClientResourceReload()); modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional); modEventBus.addListener(PartialModelEventHandler::onBakingCompleted); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java b/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java index db8563519..f7a450d3a 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java @@ -6,7 +6,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; import dev.engine_room.flywheel.backend.BackendConfig; -import dev.engine_room.flywheel.backend.FlwBackendXplatImpl; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import net.minecraft.ResourceLocationException; import net.minecraft.resources.ResourceLocation; @@ -24,8 +23,6 @@ private ForgeFlwConfig() { Pair clientPair = new ModConfigSpec.Builder().configure(ClientConfig::new); this.client = clientPair.getLeft(); clientSpec = clientPair.getRight(); - - FlwBackendXplatImpl.CONFIG = client.backendConfig; } @Override @@ -68,6 +65,11 @@ public int workerThreads() { return client.workerThreads.get(); } + @Override + public BackendConfig backendConfig() { + return client.backendConfig; + } + public void registerSpecs(ModContainer context) { context.registerConfig(ModConfig.Type.CLIENT, clientSpec); } @@ -90,7 +92,7 @@ private ClientConfig(ModConfigSpec.Builder builder) { .defineInRange("workerThreads", -1, -1, Runtime.getRuntime() .availableProcessors()); - builder.comment("Config options for flywheel's build-in backends.") + builder.comment("Config options for Flywheel's built-in backends.") .push("flw_backends"); backendConfig = new ForgeBackendConfig(builder); diff --git a/gradle.properties b/gradle.properties index 2be244cd0..77dbd3590 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,8 @@ neoforge_version_range = [21.0.167,) # General build dependency versions java_version = 21 -arch_loom_version=1.7.412 +arch_loom_version = 1.7.412 +cursegradle_version = 1.4.0 parchment_version = 2024.07.07 # Minecraft build dependency versions