Merge branch '1.20/dev' into feat/multi-loader-1.21

# Conflicts:
#	common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java
#	forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java
#	gradle.properties
This commit is contained in:
IThundxr 2024-09-20 09:37:06 -04:00
commit e7e3579315
No known key found for this signature in database
GPG Key ID: E291EC97BAF935E6
120 changed files with 2847 additions and 1026 deletions

View File

@ -7,4 +7,8 @@ public interface InstanceHandle {
void setChanged();
void setDeleted();
void setVisible(boolean visible);
boolean isVisible();
}

View File

@ -3,7 +3,7 @@ package dev.engine_room.flywheel.backend;
import dev.engine_room.flywheel.backend.compile.LightSmoothness;
public interface BackendConfig {
BackendConfig INSTANCE = FlwBackendXplat.INSTANCE.getConfig();
BackendConfig INSTANCE = FlwBackend.config();
/**
* How smooth/accurate our flw_light impl is.

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend;
import org.jetbrains.annotations.UnknownNullability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -7,11 +8,18 @@ import dev.engine_room.flywheel.api.Flywheel;
public final class FlwBackend {
public static final Logger LOGGER = LoggerFactory.getLogger(Flywheel.ID + "/backend");
@UnknownNullability
private static BackendConfig config;
private FlwBackend() {
}
public static void init() {
public static BackendConfig config() {
return config;
}
public static void init(BackendConfig config) {
FlwBackend.config = config;
Backends.init();
}
}

View File

@ -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();
}

View File

@ -30,6 +30,9 @@ public class IndirectPrograms extends AtomicReferenceCounted {
private static final ResourceLocation CULL_SHADER_MAIN = Flywheel.rl("internal/indirect/cull.glsl");
private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl");
private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl");
private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl");
private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl");
public static final List<ResourceLocation> UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND);
private static final Compile<InstanceType<?>> CULL = new Compile<>();
private static final Compile<ResourceLocation> UTIL = new Compile<>();
@ -42,14 +45,12 @@ public class IndirectPrograms extends AtomicReferenceCounted {
private final Map<PipelineProgramKey, GlProgram> pipeline;
private final Map<InstanceType<?>, GlProgram> culling;
private final GlProgram apply;
private final GlProgram scatter;
private final Map<ResourceLocation, GlProgram> utils;
private IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling, GlProgram apply, GlProgram scatter) {
private IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling, Map<ResourceLocation, GlProgram> utils) {
this.pipeline = pipeline;
this.culling = culling;
this.apply = apply;
this.scatter = scatter;
this.utils = utils;
}
private static List<String> getExtensions(GlslVersion glslVersion) {
@ -94,10 +95,10 @@ public class IndirectPrograms extends AtomicReferenceCounted {
try {
var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys);
var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys());
var utils = utilCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN));
var utils = utilCompiler.compileAndReportErrors(UTIL_SHADERS);
if (pipelineResult != null && cullingResult != null && utils != null) {
newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN));
newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils);
}
} catch (Throwable t) {
FlwPrograms.LOGGER.error("Failed to compile indirect programs", t);
@ -177,11 +178,19 @@ public class IndirectPrograms extends AtomicReferenceCounted {
}
public GlProgram getApplyProgram() {
return apply;
return utils.get(APPLY_SHADER_MAIN);
}
public GlProgram getScatterProgram() {
return scatter;
return utils.get(SCATTER_SHADER_MAIN);
}
public GlProgram getDownsampleFirstProgram() {
return utils.get(DOWNSAMPLE_FIRST);
}
public GlProgram getDownsampleSecondProgram() {
return utils.get(DOWNSAMPLE_SECOND);
}
@Override
@ -190,6 +199,7 @@ public class IndirectPrograms extends AtomicReferenceCounted {
.forEach(GlProgram::delete);
culling.values()
.forEach(GlProgram::delete);
apply.delete();
utils.values()
.forEach(GlProgram::delete);
}
}

View File

@ -44,7 +44,9 @@ public class BufferTextureInstanceComponent extends InstanceAssemblerComponent {
fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder._addRaw("uniform usamplerBuffer _flw_instances;");
builder.uniform()
.type("usamplerBuffer")
.name("_flw_instances");
builder.blankLine();
builder.function()
.signature(FnSignature.create()

View File

@ -34,7 +34,7 @@ public class InstanceStructComponent implements SourceComponent {
var builder = new GlslBuilder();
var instance = builder.struct();
instance.setName(STRUCT_NAME);
instance.name(STRUCT_NAME);
for (var element : layout.elements()) {
instance.addField(LayoutInterpreter.typeName(element.type()), element.name());
}

View File

@ -43,7 +43,7 @@ public class SsboInstanceComponent extends InstanceAssemblerComponent {
fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
builder._raw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
+ " uint _flw_instances[];\n"
+ "};");
builder.blankLine();

View File

@ -1,23 +1,17 @@
package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class Arena {
private final long elementSizeBytes;
private MemoryBlock memoryBlock;
// Monotonic index, generally represents the size of the arena.
private int top = 0;
public abstract class AbstractArena {
protected final long elementSizeBytes;
// List of free indices.
private final IntList freeStack = new IntArrayList();
// Monotonic index, generally represents the size of the arena.
private int top = 0;
public Arena(long elementSizeBytes, int initialCapacity) {
public AbstractArena(long elementSizeBytes) {
this.elementSizeBytes = elementSizeBytes;
memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity);
}
public int alloc() {
@ -27,8 +21,8 @@ public class Arena {
}
// Make sure there's room to increment top.
if (top * elementSizeBytes >= memoryBlock.size()) {
memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2);
if (top * elementSizeBytes >= byteCapacity()) {
grow();
}
// Return the top index and increment.
@ -40,19 +34,15 @@ public class Arena {
freeStack.add(i);
}
public long indexToPointer(int i) {
return memoryBlock.ptr() + i * elementSizeBytes;
}
public void delete() {
memoryBlock.free();
public long byteOffsetOf(int i) {
return i * elementSizeBytes;
}
public int capacity() {
return top;
}
public long byteCapacity() {
return memoryBlock.size();
}
public abstract long byteCapacity();
protected abstract void grow();
}

View File

@ -1,6 +1,7 @@
package dev.engine_room.flywheel.backend.engine;
import java.util.ArrayList;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
@ -13,26 +14,32 @@ import dev.engine_room.flywheel.backend.util.AtomicBitSet;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
public final InstanceType<I> type;
public final Environment environment;
private final Supplier<AbstractInstancer<I>> recreate;
// Lock for all instances, only needs to be used in methods that may run on the TaskExecutor.
protected final Object lock = new Object();
protected final ArrayList<I> instances = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl<I>> handles = new ArrayList<>();
protected final AtomicBitSet changed = new AtomicBitSet();
protected final AtomicBitSet deleted = new AtomicBitSet();
protected AbstractInstancer(InstanceType<I> type, Environment environment) {
this.type = type;
this.environment = environment;
protected AbstractInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
this.type = key.type();
this.environment = key.environment();
this.recreate = recreate;
}
@Override
public I createInstance() {
synchronized (lock) {
var i = instances.size();
var handle = new InstanceHandleImpl(this, i);
var handle = new InstanceHandleImpl<I>();
handle.instancer = this;
handle.recreate = recreate;
handle.index = i;
I instance = type.create(handle);
handle.instance = instance;
addLocked(instance, handle);
return instance;
@ -47,12 +54,15 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
var instanceHandle = instance.handle();
if (!(instanceHandle instanceof InstanceHandleImpl handle)) {
if (!(instanceHandle instanceof InstanceHandleImpl<?>)) {
// UB: do nothing
return;
}
if (handle.instancer == this) {
// Should InstanceType have an isInstance method?
var handle = (InstanceHandleImpl<I>) instanceHandle;
if (handle.instancer == this && handle.visible) {
return;
}
@ -65,19 +75,23 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
// is filtering deleted instances later, so is safe.
handle.setDeleted();
// Only lock now that we'll be mutating our state.
synchronized (lock) {
// Add the instance to this instancer.
handle.instancer = this;
handle.index = instances.size();
addLocked(instance, handle);
// Add the instance to this instancer.
handle.instancer = this;
handle.recreate = recreate;
if (handle.visible) {
// Only lock now that we'll be mutating our state.
synchronized (lock) {
handle.index = instances.size();
addLocked(instance, handle);
}
}
}
/**
* Calls must be synchronized on {@link #lock}.
*/
private void addLocked(I instance, InstanceHandleImpl handle) {
private void addLocked(I instance, InstanceHandleImpl<I> handle) {
instances.add(instance);
handles.add(handle);
changed.set(handle.index);
@ -122,7 +136,7 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
if (writePos < newSize) {
// Since we'll be shifting everything into this space we can consider it all changed.
changed.set(writePos, newSize);
setRangeChanged(writePos, newSize);
}
// We definitely shouldn't consider the deleted instances as changed though,
@ -155,11 +169,15 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
.clear();
}
protected void setRangeChanged(int start, int end) {
changed.set(start, end);
}
/**
* Clear all instances without freeing resources.
*/
public void clear() {
for (InstanceHandleImpl handle : handles) {
for (InstanceHandleImpl<I> handle : handles) {
// Only clear instances that belong to this instancer.
// If one of these handles was stolen by another instancer,
// clearing it here would cause significant visual artifacts and instance leaks.

View File

@ -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);
}
}

View File

@ -8,16 +8,16 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.lib.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.resources.model.ModelBakery;
@ -36,9 +36,13 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
*/
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
public <I extends Instance> AbstractInstancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias));
}
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit);
public <I extends Instance> AbstractInstancer<I> getInstancer(InstancerKey<I> key) {
return (AbstractInstancer<I>) instancers.computeIfAbsent(key, this::createAndDeferInit);
}
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
@ -94,8 +98,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
return false;
}
protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) {
Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> byType = new HashMap<>();
protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) {
Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> byType = new HashMap<>();
for (Engine.CrumblingBlock block : crumblingBlocks) {
int progress = block.progress();
@ -107,7 +111,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
// Filter out instances that weren't created by this engine.
// If all is well, we probably shouldn't take the `continue`
// branches but better to do checked casts.
if (!(instance.handle() instanceof InstanceHandleImpl impl)) {
if (!(instance.handle() instanceof InstanceHandleImpl<?> impl)) {
continue;
}

View File

@ -1,16 +1,22 @@
package dev.engine_room.flywheel.backend.engine;
import java.util.function.Supplier;
import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceHandle;
public class InstanceHandleImpl implements InstanceHandle {
public AbstractInstancer<?> instancer;
public class InstanceHandleImpl<I extends Instance> implements InstanceHandle {
@UnknownNullability
public AbstractInstancer<I> instancer;
@UnknownNullability
public I instance;
@UnknownNullability
public Supplier<AbstractInstancer<I>> recreate;
public boolean visible = true;
public int index;
public InstanceHandleImpl(AbstractInstancer<?> instancer, int index) {
this.instancer = instancer;
this.index = index;
}
@Override
public void setChanged() {
instancer.notifyDirty(index);
@ -23,6 +29,27 @@ public class InstanceHandleImpl implements InstanceHandle {
clear();
}
@Override
public void setVisible(boolean visible) {
if (this.visible == visible) {
return;
}
this.visible = visible;
if (visible) {
recreate.get().stealInstance(instance);
} else {
instancer.notifyRemoval(index);
clear();
}
}
@Override
public boolean isVisible() {
return visible;
}
public void clear() {
index = -1;
}

View File

@ -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);
}
/**

View File

@ -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.

View File

@ -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;

View File

@ -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;
}
}

View File

@ -30,18 +30,19 @@ public class IndirectBuffers {
private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE;
// Offsets to the vbos
private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET;
private static final long TARGET_HANDLE_OFFSET = INT_SIZE;
private static final long MODEL_INDEX_HANDLE_OFFSET = INT_SIZE * 2;
private static final long MODEL_HANDLE_OFFSET = INT_SIZE * 3;
private static final long DRAW_HANDLE_OFFSET = INT_SIZE * 4;
private static final long PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * INT_SIZE;
private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.INSTANCE * INT_SIZE;
private static final long DRAW_INSTANCE_INDEX_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * INT_SIZE;
private static final long MODEL_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.MODEL * INT_SIZE;
private static final long DRAW_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW * INT_SIZE;
// Offsets to the sizes
private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET;
private static final long TARGET_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE;
private static final long MODEL_INDEX_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2;
private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 3;
private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 4;
private static final long PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * PTR_SIZE;
private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.INSTANCE * PTR_SIZE;
private static final long DRAW_INSTANCE_INDEX_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * PTR_SIZE;
private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.MODEL * PTR_SIZE;
private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW * PTR_SIZE;
private static final float INSTANCE_GROWTH_FACTOR = 1.25f;
private static final float MODEL_GROWTH_FACTOR = 2f;
@ -60,73 +61,71 @@ public class IndirectBuffers {
* one for the instance buffer, target buffer, model index buffer, model buffer, and draw buffer.
*/
private final MemoryBlock multiBindBlock;
private final long instanceStride;
public final ResizableStorageArray instance;
public final ResizableStorageArray target;
public final ResizableStorageArray modelIndex;
public final ObjectStorage objectStorage;
public final ResizableStorageArray drawInstanceIndex;
public final ResizableStorageArray model;
public final ResizableStorageArray draw;
IndirectBuffers(long instanceStride) {
this.instanceStride = instanceStride;
this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
instance = new ResizableStorageArray(instanceStride, INSTANCE_GROWTH_FACTOR);
target = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
modelIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
objectStorage = new ObjectStorage(instanceStride);
drawInstanceIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR);
model = new ResizableStorageArray(MODEL_STRIDE, MODEL_GROWTH_FACTOR);
draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, DRAW_GROWTH_FACTOR);
}
void updateCounts(int instanceCount, int modelCount, int drawCount) {
instance.ensureCapacity(instanceCount);
target.ensureCapacity(instanceCount);
modelIndex.ensureCapacity(instanceCount);
drawInstanceIndex.ensureCapacity(instanceCount);
model.ensureCapacity(modelCount);
draw.ensureCapacity(drawCount);
final long ptr = multiBindBlock.ptr();
MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, instance.handle());
MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle());
MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, modelIndex.handle());
MemoryUtil.memPutInt(ptr + PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET, objectStorage.frameDescriptorBuffer.handle());
MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, objectStorage.objectBuffer.handle());
MemoryUtil.memPutInt(ptr + DRAW_INSTANCE_INDEX_HANDLE_OFFSET, drawInstanceIndex.handle());
MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle());
MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle());
MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, instanceStride * instanceCount);
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount);
MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount);
MemoryUtil.memPutAddress(ptr + PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET, objectStorage.frameDescriptorBuffer.capacity());
MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, objectStorage.objectBuffer.capacity());
MemoryUtil.memPutAddress(ptr + DRAW_INSTANCE_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount);
MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount);
MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount);
}
public void bindForCompute() {
multiBind();
public void bindForCull() {
multiBind(0, 4);
}
public void bindForApply() {
multiBind(3, 2);
}
public void bindForDraw() {
multiBind();
multiBind(1, 4);
GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle());
}
private void multiBind() {
final long ptr = multiBindBlock.ptr();
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
}
/**
* Bind all buffers except the draw command buffer.
*/
public void bindForCrumbling() {
multiBind(3, 3);
}
private void multiBind(int base, int count) {
final long ptr = multiBindBlock.ptr();
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, base, count, ptr + base * INT_SIZE, ptr + OFFSET_OFFSET + base * PTR_SIZE, ptr + SIZE_OFFSET + base * PTR_SIZE);
}
public void delete() {
multiBindBlock.free();
instance.delete();
target.delete();
modelIndex.delete();
objectStorage.delete();
drawInstanceIndex.delete();
model.delete();
draw.delete();
}

View File

@ -74,8 +74,7 @@ public class IndirectCullingGroup<I extends Instance> {
continue;
}
instancer.modelIndex = modelIndex;
instancer.baseInstance = instanceCountThisFrame;
instancer.postUpdate(modelIndex, instanceCountThisFrame);
instanceCountThisFrame += instanceCount;
modelIndex++;
@ -96,6 +95,8 @@ public class IndirectCullingGroup<I extends Instance> {
// Upload only instances that have changed.
uploadInstances(stagingBuffer);
buffers.objectStorage.uploadDescriptors(stagingBuffer);
// We need to upload the models every frame to reset the instance count.
uploadModels(stagingBuffer);
@ -117,8 +118,8 @@ public class IndirectCullingGroup<I extends Instance> {
Uniforms.bindAll();
cullProgram.bind();
buffers.bindForCompute();
glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1);
buffers.bindForCull();
glDispatchCompute(buffers.objectStorage.capacity(), 1, 1);
}
public void dispatchApply() {
@ -126,7 +127,7 @@ public class IndirectCullingGroup<I extends Instance> {
return;
}
buffers.bindForCompute();
buffers.bindForApply();
glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1);
}
@ -171,7 +172,9 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) {
instancer.modelIndex = instancers.size();
instancer.mapping = buffers.objectStorage.createMapping();
instancer.postUpdate(instancers.size(), -1);
instancers.add(instancer);
List<Model.ConfiguredMesh> meshes = key.model()
@ -242,12 +245,7 @@ public class IndirectCullingGroup<I extends Instance> {
private void uploadInstances(StagingBuffer stagingBuffer) {
for (var instancer : instancers) {
instancer.uploadInstances(stagingBuffer, buffers.instance.handle());
}
for (var instancer : instancers) {
instancer.uploadModelIndices(stagingBuffer, buffers.modelIndex.handle());
instancer.resetChanged();
instancer.uploadInstances(stagingBuffer, buffers.objectStorage.objectBuffer.handle());
}
}

View File

@ -72,9 +72,9 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance); // baseInstance
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance()); // baseInstance
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex
@ -89,9 +89,9 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance + instanceIndex); // baseInstance
MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance() + instanceIndex); // baseInstance
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex
MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex

View File

@ -46,6 +46,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final LightBuffers lightBuffers;
private final MatrixBuffer matrixBuffer;
private final DepthPyramid depthPyramid;
private boolean needsBarrier = false;
public IndirectDrawManager(IndirectPrograms programs) {
@ -58,11 +60,13 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
meshPool.bind(vertexArray);
lightBuffers = new LightBuffers();
matrixBuffer = new MatrixBuffer();
depthPyramid = new DepthPyramid(programs.getDownsampleFirstProgram(), programs.getDownsampleSecondProgram());
}
@Override
protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) {
return new IndirectInstancer<>(key.type(), key.environment(), key.model());
return new IndirectInstancer<>(key, () -> getInstancer(key));
}
@SuppressWarnings("unchecked")
@ -136,6 +140,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
stagingBuffer.flush();
depthPyramid.generate();
// We could probably save some driver calls here when there are
// actually zero instances, but that feels like a very rare case
@ -143,6 +149,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
matrixBuffer.bind();
depthPyramid.bindForCull();
for (var group : cullingGroups.values()) {
group.dispatchCull();
}
@ -174,6 +182,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
crumblingDrawBuffer.delete();
programs.release();
depthPyramid.delete();
}
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {
@ -209,8 +219,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
for (var instanceHandlePair : progressEntry.getValue()) {
IndirectInstancer<?> instancer = instanceHandlePair.first();
int instanceIndex = instanceHandlePair.second().index;
IndirectInstancer<?> instancer = instanceHandlePair.getFirst();
int instanceIndex = instanceHandlePair.getSecond().index;
for (IndirectDraw draw : instancer.draws()) {
// Transform the material to be suited for crumbling.

View File

@ -2,16 +2,17 @@ package dev.engine_room.flywheel.backend.engine.indirect;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.UnknownNullability;
import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstanceWriter;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.util.AtomicBitSet;
import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
@ -20,18 +21,34 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
private final List<IndirectDraw> associatedDraws = new ArrayList<>();
private final Vector4fc boundingSphere;
public int modelIndex = -1;
public int baseInstance = -1;
private int lastModelIndex = -1;
private int lastBaseInstance = -1;
private int lastInstanceCount = -1;
private final AtomicBitSet changedPages = new AtomicBitSet();
public IndirectInstancer(InstanceType<I> type, Environment environment, Model model) {
super(type, environment);
public ObjectStorage.@UnknownNullability Mapping mapping;
private int modelIndex = -1;
private int baseInstance = -1;
public IndirectInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
super(key, recreate);
instanceStride = MoreMath.align4(type.layout()
.byteSize());
writer = this.type.writer();
boundingSphere = model.boundingSphere();
boundingSphere = key.model().boundingSphere();
}
@Override
public void notifyDirty(int index) {
if (index < 0 || index >= instanceCount()) {
return;
}
changedPages.set(ObjectStorage.objectIndex2PageIndex(index));
}
@Override
protected void setRangeChanged(int start, int end) {
super.setRangeChanged(start, end);
changedPages.set(ObjectStorage.objectIndex2PageIndex(start), ObjectStorage.objectIndex2PageIndex(end) + 1);
}
public void addDraw(IndirectDraw draw) {
@ -46,6 +63,12 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
removeDeletedInstances();
}
public void postUpdate(int modelIndex, int baseInstance) {
this.modelIndex = modelIndex;
this.baseInstance = baseInstance;
mapping.update(modelIndex, instanceCount());
}
public void writeModel(long ptr) {
MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader
MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance
@ -57,71 +80,55 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
}
public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) {
long baseByte = baseInstance * instanceStride;
if (baseInstance != lastBaseInstance) {
uploadAllInstances(stagingBuffer, baseByte, instanceVbo);
} else {
uploadChangedInstances(stagingBuffer, baseByte, instanceVbo);
if (changedPages.isEmpty()) {
return;
}
}
public void uploadModelIndices(StagingBuffer stagingBuffer, int modelIndexVbo) {
long modelIndexBaseByte = baseInstance * IndirectBuffers.INT_SIZE;
int numPages = mapping.pageCount();
if (baseInstance != lastBaseInstance || modelIndex != lastModelIndex || instances.size() > lastInstanceCount) {
uploadAllModelIndices(stagingBuffer, modelIndexBaseByte, modelIndexVbo);
}
}
var instanceCount = instances.size();
public void resetChanged() {
lastModelIndex = modelIndex;
lastBaseInstance = baseInstance;
lastInstanceCount = instances.size();
changed.clear();
}
for (int page = changedPages.nextSetBit(0); page >= 0 && page < numPages; page = changedPages.nextSetBit(page + 1)) {
int startObject = ObjectStorage.pageIndex2ObjectIndex(page);
private void uploadChangedInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) {
changed.forEachSetSpan((startInclusive, endInclusive) -> {
// Generally we're good about ensuring we don't have changed bits set out of bounds, but check just in case
if (startInclusive >= instances.size()) {
return;
if (startObject >= instanceCount) {
break;
}
int actualEnd = Math.min(endInclusive, instances.size() - 1);
int instanceCount = actualEnd - startInclusive + 1;
long totalSize = instanceCount * instanceStride;
int endObject = Math.min(instanceCount, ObjectStorage.pageIndex2ObjectIndex(page + 1));
stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte + startInclusive * instanceStride, ptr -> {
for (int i = startInclusive; i <= actualEnd; i++) {
long baseByte = mapping.page2ByteOffset(page);
long size = (endObject - startObject) * instanceStride;
// Because writes are broken into pages, we end up with significantly more calls into
// StagingBuffer#enqueueCopy and the allocations for the writer got out of hand. Here
// we've inlined the enqueueCopy call and do not allocate the write lambda at all.
// Doing so cut upload times in half.
// Try to write directly into the staging buffer if there is enough contiguous space.
long direct = stagingBuffer.reserveForCopy(size, instanceVbo, baseByte);
if (direct != MemoryUtil.NULL) {
for (int i = startObject; i < endObject; i++) {
var instance = instances.get(i);
writer.write(ptr, instance);
ptr += instanceStride;
writer.write(direct, instance);
direct += instanceStride;
}
});
});
}
continue;
}
private void uploadAllInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) {
long totalSize = instances.size() * instanceStride;
stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte, ptr -> {
for (I instance : instances) {
// Otherwise, write to a scratch buffer and enqueue a copy.
var block = stagingBuffer.getScratch(size);
var ptr = block.ptr();
for (int i = startObject; i < endObject; i++) {
var instance = instances.get(i);
writer.write(ptr, instance);
ptr += instanceStride;
}
});
}
stagingBuffer.enqueueCopy(block.ptr(), size, instanceVbo, baseByte);
}
private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexBaseByte, int modelIndexVbo) {
long modelIndexTotalSize = instances.size() * IndirectBuffers.INT_SIZE;
stagingBuffer.enqueueCopy(modelIndexTotalSize, modelIndexVbo, modelIndexBaseByte, ptr -> {
for (int i = 0; i < instances.size(); i++) {
MemoryUtil.memPutInt(ptr, modelIndex);
ptr += IndirectBuffers.INT_SIZE;
}
});
changedPages.clear();
}
@Override
@ -129,5 +136,15 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
for (IndirectDraw draw : draws()) {
draw.delete();
}
mapping.delete();
}
public int modelIndex() {
return modelIndex;
}
public int baseInstance() {
return baseInstance;
}
}

View File

@ -0,0 +1,220 @@
package dev.engine_room.flywheel.backend.engine.indirect;
import java.util.Arrays;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.engine.AbstractArena;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
public class ObjectStorage extends AbstractArena {
// 32 objects per page. Allows for convenient bitsets on the gpu.
public static final int LOG_2_PAGE_SIZE = 5;
public static final int PAGE_SIZE = 1 << LOG_2_PAGE_SIZE;
public static final int PAGE_MASK = PAGE_SIZE - 1;
public static final int INITIAL_PAGES_ALLOCATED = 4;
/**
* The GPU side buffer containing all the objects, logically divided into page frames.
*/
public final ResizableStorageBuffer objectBuffer;
/**
* The GPU side buffer containing 32 bit descriptors for each page frame.
*/
public final ResizableStorageBuffer frameDescriptorBuffer;
/**
* The CPU side memory block containing the page descriptors.
*/
private MemoryBlock frameDescriptors;
private boolean needsUpload = false;
public ObjectStorage(long objectSizeBytes) {
super(PAGE_SIZE * objectSizeBytes);
this.objectBuffer = new ResizableStorageBuffer();
this.frameDescriptorBuffer = new ResizableStorageBuffer();
objectBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes);
frameDescriptorBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
frameDescriptors = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
}
public Mapping createMapping() {
return new Mapping();
}
@Override
public long byteCapacity() {
return objectBuffer.capacity();
}
@Override
public void free(int i) {
super.free(i);
MemoryUtil.memPutInt(ptrForPage(i), 0);
}
@Override
protected void grow() {
objectBuffer.ensureCapacity(objectBuffer.capacity() * 2);
frameDescriptorBuffer.ensureCapacity(frameDescriptorBuffer.capacity() * 2);
frameDescriptors = frameDescriptors.realloc(frameDescriptors.size() * 2);
}
public void uploadDescriptors(StagingBuffer stagingBuffer) {
if (!needsUpload) {
return;
}
// We could be smarter about which spans are uploaded but this thing is so small it's probably not worth it.
stagingBuffer.enqueueCopy(frameDescriptors.ptr(), frameDescriptors.size(), frameDescriptorBuffer.handle(), 0);
needsUpload = false;
}
public void delete() {
objectBuffer.delete();
frameDescriptorBuffer.delete();
frameDescriptors.free();
}
private long ptrForPage(int page) {
return frameDescriptors.ptr() + (long) page * Integer.BYTES;
}
public static int objectIndex2PageIndex(int objectIndex) {
return objectIndex >> LOG_2_PAGE_SIZE;
}
public static int pageIndex2ObjectIndex(int pageIndex) {
return pageIndex << LOG_2_PAGE_SIZE;
}
/**
* Maps serial object indices to pages, and manages the allocation of pages.
*/
public class Mapping {
private static final int[] EMPTY_ALLOCATION = new int[0];
private int[] pages = EMPTY_ALLOCATION;
private int modelIndex = -1;
private int objectCount = 0;
/**
* Adjust this allocation to the given model index and object count.
*
* <p>This method triggers eager resizing of the allocation to fit the new object count.
* If the model index is different from the current one, all frame descriptors will be updated.
*
* @param modelIndex The model index the objects in this allocation are associated with.
* @param objectCount The number of objects in this allocation.
*/
public void update(int modelIndex, int objectCount) {
boolean incremental = this.modelIndex == modelIndex;
if (incremental && objectCount == this.objectCount) {
// Nothing will change.
return;
}
ObjectStorage.this.needsUpload = true;
this.modelIndex = modelIndex;
this.objectCount = objectCount;
var oldLength = pages.length;
var newLength = objectIndex2PageIndex((objectCount + PAGE_MASK));
if (oldLength > newLength) {
// Eagerly free the now unnecessary pages.
// shrink will zero out the pageTable entries for the freed pages.
shrink(oldLength, newLength);
if (incremental) {
// Only update the last page, everything else is unchanged.
updateRange(newLength - 1, newLength);
}
} else if (oldLength < newLength) {
// Allocate new pages to fit the new object count.
grow(newLength, oldLength);
if (incremental) {
// Update the old last page + all new pages
updateRange(oldLength - 1, newLength);
}
} else {
if (incremental) {
// Only update the last page.
updateRange(oldLength - 1, oldLength);
}
}
if (!incremental) {
// Update all pages.
updateRange(0, newLength);
}
}
public int pageCount() {
return pages.length;
}
public long page2ByteOffset(int page) {
return ObjectStorage.this.byteOffsetOf(pages[page]);
}
public void delete() {
for (int page : pages) {
ObjectStorage.this.free(page);
}
pages = EMPTY_ALLOCATION;
modelIndex = -1;
objectCount = 0;
ObjectStorage.this.needsUpload = true;
}
/**
* Calculates the page descriptor for the given page index.
* Runs under the assumption than all pages are full except maybe the last one.
*/
private int calculatePageDescriptor(int pageIndex) {
int countInPage;
if (objectCount % PAGE_SIZE != 0 && pageIndex == pages.length - 1) {
// Last page && it isn't full -> use the remainder.
countInPage = objectCount & PAGE_MASK;
} else if (objectCount > 0) {
// Full page.
countInPage = PAGE_SIZE;
} else {
// Empty page, this shouldn't be reachable because we eagerly free empty pages.
countInPage = 0;
}
return (modelIndex & 0x3FFFFF) | (countInPage << 26);
}
private void updateRange(int start, int oldLength) {
for (int i = start; i < oldLength; i++) {
MemoryUtil.memPutInt(ptrForPage(pages[i]), calculatePageDescriptor(i));
}
}
private void grow(int neededPages, int oldLength) {
pages = Arrays.copyOf(pages, neededPages);
for (int i = oldLength; i < neededPages; i++) {
var page = ObjectStorage.this.alloc();
pages[i] = page;
}
}
private void shrink(int oldLength, int neededPages) {
for (int i = oldLength - 1; i >= neededPages; i--) {
var page = pages[i];
ObjectStorage.this.free(page);
}
pages = Arrays.copyOf(pages, neededPages);
}
}
}

View File

@ -223,7 +223,7 @@ public class StagingBuffer {
FlwMemoryTracker._freeCpuMemory(capacity);
}
private MemoryBlock getScratch(long size) {
public MemoryBlock getScratch(long size) {
if (scratch == null) {
scratch = MemoryBlock.malloc(size);
} else if (scratch.size() < size) {

View File

@ -128,7 +128,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
@Override
protected <I extends Instance> InstancedInstancer<I> create(InstancerKey<I> key) {
return new InstancedInstancer<>(key.type(), key.environment());
return new InstancedInstancer<>(key, () -> getInstancer(key));
}
@Override
@ -180,8 +180,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
for (var instanceHandlePair : progressEntry.getValue()) {
InstancedInstancer<?> instancer = instanceHandlePair.first();
var index = instanceHandlePair.second().index;
InstancedInstancer<?> instancer = instanceHandlePair.getFirst();
var index = instanceHandlePair.getSecond().index;
program.setInt("_flw_baseInstance", index);

View File

@ -2,14 +2,14 @@ package dev.engine_room.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstanceWriter;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage;
@ -25,8 +25,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
private final List<InstancedDraw> draws = new ArrayList<>();
public InstancedInstancer(InstanceType<I> type, Environment environment) {
super(type, environment);
public InstancedInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
super(key, recreate);
var layout = type.layout();
// Align to one texel in the texture buffer
instanceStride = MoreMath.align16(layout.byteSize());

View File

@ -8,6 +8,7 @@ import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
import dev.engine_room.flywheel.backend.engine.indirect.DepthPyramid;
import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
@ -17,7 +18,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public final class FrameUniforms extends UniformWriter {
private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 10;
private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 17;
static final UniformBuffer BUFFER = new UniformBuffer(Uniforms.FRAME_INDEX, SIZE);
private static final Matrix4f VIEW = new Matrix4f();
@ -112,6 +113,8 @@ public final class FrameUniforms extends UniformWriter {
ptr = writeInt(ptr, debugMode);
ptr = writeCullData(ptr);
firstWrite = false;
BUFFER.markDirty();
}
@ -179,6 +182,26 @@ public final class FrameUniforms extends UniformWriter {
return writeInFluidAndBlock(ptr, level, blockPos, cameraPos);
}
private static long writeCullData(long ptr) {
var mc = Minecraft.getInstance();
var mainRenderTarget = mc.getMainRenderTarget();
int pyramidWidth = DepthPyramid.mip0Size(mainRenderTarget.width);
int pyramidHeight = DepthPyramid.mip0Size(mainRenderTarget.height);
int pyramidDepth = DepthPyramid.getImageMipLevels(pyramidWidth, pyramidHeight);
ptr = writeFloat(ptr, 0.05F); // zNear
ptr = writeFloat(ptr, mc.gameRenderer.getDepthFar()); // zFar
ptr = writeFloat(ptr, PROJECTION.m00()); // P00
ptr = writeFloat(ptr, PROJECTION.m11()); // P11
ptr = writeFloat(ptr, pyramidWidth); // pyramidWidth
ptr = writeFloat(ptr, pyramidHeight); // pyramidHeight
ptr = writeInt(ptr, pyramidDepth - 1); // pyramidLevels
ptr = writeInt(ptr, 0); // useMin
return ptr;
}
/**
* Writes the frustum planes of the given projection matrix to the given buffer.<p>
* Uses a different format that is friendly towards an optimized instruction-parallel

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend.gl.array;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
@ -7,13 +8,13 @@ import org.lwjgl.opengl.GL45C;
import org.lwjgl.system.Checks;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.lib.util.FlwUtil;
import net.minecraft.Util;
public class GlVertexArrayDSA extends GlVertexArray {
public static final boolean SUPPORTED = isSupported();
private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS);
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend.gl.array;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
@ -12,13 +13,13 @@ import org.lwjgl.system.Checks;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType;
import dev.engine_room.flywheel.lib.util.FlwUtil;
import net.minecraft.Util;
public abstract class GlVertexArrayGL3 extends GlVertexArray {
private final BitSet attributeDirty = new BitSet(MAX_ATTRIBS);
private final int[] attributeOffsets = new int[MAX_ATTRIBS];
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend.gl.array;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
@ -9,13 +10,13 @@ import org.lwjgl.system.Checks;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType;
import dev.engine_room.flywheel.lib.util.FlwUtil;
import net.minecraft.Util;
public class GlVertexArraySeparateAttributes extends GlVertexArray {
public static final boolean SUPPORTED = isSupported();
private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS);
private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS];
private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1);
private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1));
private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS];
private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS];
private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS];

View File

@ -11,6 +11,7 @@ import static org.lwjgl.opengl.GL20.glUniform4f;
import static org.lwjgl.opengl.GL20.glUniformMatrix3fv;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL30.glUniform1ui;
import static org.lwjgl.opengl.GL30.glUniform2ui;
import static org.lwjgl.opengl.GL31.GL_INVALID_INDEX;
import static org.lwjgl.opengl.GL31.glGetUniformBlockIndex;
import static org.lwjgl.opengl.GL31.glUniformBlockBinding;
@ -118,6 +119,16 @@ public class GlProgram extends GlObject {
glUniform1ui(uniform, value);
}
public void setUVec2(String name, int x, int y) {
int uniform = getUniformLocation(name);
if (uniform < 0) {
return;
}
glUniform2ui(uniform, x, y);
}
public void setInt(String glslName, int value) {
int uniform = getUniformLocation(glslName);

View File

@ -5,9 +5,10 @@ import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.backend.glsl.error.ErrorBuilder;
import dev.engine_room.flywheel.backend.glsl.span.Span;
import dev.engine_room.flywheel.lib.util.Pair;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
@ -38,9 +39,9 @@ sealed public interface LoadError {
.pointAtFile(location);
for (var innerError : innerErrors) {
var err = innerError.second()
var err = innerError.getSecond()
.generateMessage();
out.pointAt(innerError.first())
out.pointAt(innerError.getFirst())
.nested(err);
}

View File

@ -4,13 +4,13 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.backend.glsl.parse.Import;
import dev.engine_room.flywheel.backend.glsl.parse.ShaderField;
@ -18,7 +18,6 @@ import dev.engine_room.flywheel.backend.glsl.parse.ShaderFunction;
import dev.engine_room.flywheel.backend.glsl.parse.ShaderStruct;
import dev.engine_room.flywheel.backend.glsl.span.Span;
import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
import dev.engine_room.flywheel.lib.util.Pair;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.ResourceLocationException;
import net.minecraft.resources.ResourceLocation;
@ -172,44 +171,48 @@ public class SourceFile implements SourceComponent {
* @param name The name of the struct to find.
* @return null if no definition matches the name.
*/
public Optional<ShaderStruct> findStructByName(String name) {
@Nullable
public ShaderStruct findStruct(String name) {
ShaderStruct struct = structs.get(name);
if (struct != null) {
return Optional.of(struct);
return struct;
}
for (var include : included) {
var external = include.structs.get(name);
var external = include.findStruct(name);
if (external != null) {
return Optional.of(external);
return external;
}
}
return Optional.empty();
return null;
}
/**
* Search this file and recursively search all imports to find a function definition matching the given name.
*
* @param name The name of the function to find.
* @return Optional#empty() if no definition matches the name.
* @return null if no definition matches the name.
*/
public Optional<ShaderFunction> findFunction(String name) {
ShaderFunction local = functions.get(name);
@Nullable
public ShaderFunction findFunction(String name) {
ShaderFunction function = functions.get(name);
if (local != null) return Optional.of(local);
if (function != null) {
return function;
}
for (var include : included) {
var external = include.functions.get(name);
var external = include.findFunction(name);
if (external != null) {
return Optional.of(external);
return external;
}
}
return Optional.empty();
return null;
}
@Override

View File

@ -128,10 +128,6 @@ public class SourceLines implements CharSequence {
return raw.length();
}
public int lineStartCol(int spanLine) {
return 0;
}
public int lineWidth(int spanLine) {
return lines.get(spanLine)
.length();

View File

@ -29,7 +29,6 @@ public class ErrorBuilder {
private final List<ErrorLine> lines = new ArrayList<>();
private ErrorBuilder() {
}
public static ErrorBuilder create() {

View File

@ -1,7 +1,6 @@
package dev.engine_room.flywheel.backend.glsl.error.lines;
public interface ErrorLine {
default int neededMargin() {
return left().length();
}
@ -17,6 +16,7 @@ public interface ErrorLine {
default String left() {
return "";
}
default String right() {
return "";
}

View File

@ -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 "-";

View File

@ -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;

View File

@ -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);
}

View File

@ -7,11 +7,6 @@ public class SpanHighlightLine implements ErrorLine {
line = generateUnderline(firstCol, lastCol);
}
@Override
public String left() {
return "";
}
@Override
public String right() {
return line;

View File

@ -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;

View File

@ -3,12 +3,12 @@ package dev.engine_room.flywheel.backend.glsl.generate;
import java.util.Collection;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.lib.util.Pair;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
public record FnSignature(String returnType, String name, ImmutableList<Pair<String, String>> args) {
public static Builder create() {
return new Builder();
}
@ -26,7 +26,7 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
public Collection<? extends GlslExpr> createArgExpressions() {
return args.stream()
.map(Pair::second)
.map(Pair::getSecond)
.map(GlslExpr::variable)
.collect(Collectors.toList());
}
@ -37,18 +37,20 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
public String fullDeclaration() {
return returnType + ' ' + name + '(' + args.stream()
.map(p -> p.first() + ' ' + p.second())
.map(p -> p.getFirst() + ' ' + p.getSecond())
.collect(Collectors.joining(", ")) + ')';
}
public String signatureDeclaration() {
return returnType + ' ' + name + '(' + args.stream()
.map(Pair::first)
.map(Pair::getFirst)
.collect(Collectors.joining(", ")) + ')';
}
public static class Builder {
@Nullable
private String returnType;
@Nullable
private String name;
private final ImmutableList.Builder<Pair<String, String>> args = ImmutableList.builder();
@ -77,5 +79,4 @@ public record FnSignature(String returnType, String name, ImmutableList<Pair<Str
return new FnSignature(returnType, name, args.build());
}
}
}

View File

@ -19,29 +19,33 @@ public class GlslBuilder {
return add(new GlslStruct());
}
public GlslFn function() {
return add(new GlslFn());
}
public GlslVertexInput vertexInput() {
return add(new GlslVertexInput());
}
public GlslUniform uniform() {
return add(new GlslUniform());
}
public GlslUniformBlock uniformBlock() {
return add(new GlslUniformBlock());
}
public <T extends Declaration> T add(T element) {
elements.add(element);
return element;
public GlslFn function() {
return add(new GlslFn());
}
public void blankLine() {
elements.add(Separators.BLANK_LINE);
add(Separators.BLANK_LINE);
}
public void _addRaw(String sourceString) {
elements.add(() -> sourceString);
public void _raw(String sourceString) {
add(new Raw(sourceString));
}
public <T extends Declaration> T add(T element) {
elements.add(element);
return element;
}
public String build() {
@ -54,6 +58,20 @@ public class GlslBuilder {
String prettyPrint();
}
public record Define(String name, String value) implements Declaration {
@Override
public String prettyPrint() {
return "#define " + name + " " + value;
}
}
public record Undef(String name) implements Declaration {
@Override
public String prettyPrint() {
return "#undef " + name;
}
}
public enum Separators implements Declaration {
BLANK_LINE(""),
;
@ -70,18 +88,10 @@ public class GlslBuilder {
}
}
public record Define(String name, String value) implements Declaration {
public record Raw(String sourceString) implements Declaration {
@Override
public String prettyPrint() {
return "#define " + name + " " + value;
return sourceString;
}
}
public record Undef(String name) implements Declaration {
@Override
public String prettyPrint() {
return "#undef " + name;
}
}
}

View File

@ -8,7 +8,6 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
public interface GlslExpr {
/**
* Create a glsl variable with the given name.
*
@ -129,7 +128,6 @@ public interface GlslExpr {
public String prettyPrint() {
return name;
}
}
record FunctionCall(String name, Collection<? extends GlslExpr> args) implements GlslExpr {
@ -144,7 +142,6 @@ public interface GlslExpr {
.collect(Collectors.joining(", "));
return name + "(" + args + ")";
}
}
record FunctionCall0(String name) implements GlslExpr {
@ -152,7 +149,6 @@ public interface GlslExpr {
public String prettyPrint() {
return name + "()";
}
}
record Swizzle(GlslExpr target, String selection) implements GlslExpr {
@ -160,7 +156,6 @@ public interface GlslExpr {
public String prettyPrint() {
return target.prettyPrint() + "." + selection;
}
}
record Access(GlslExpr target, String argName) implements GlslExpr {
@ -168,7 +163,6 @@ public interface GlslExpr {
public String prettyPrint() {
return target.prettyPrint() + "." + argName;
}
}
record Clamp(GlslExpr value, GlslExpr from, GlslExpr to) implements GlslExpr {

View File

@ -5,24 +5,25 @@ import java.util.function.Consumer;
import dev.engine_room.flywheel.lib.util.StringUtil;
public class GlslFn implements GlslBuilder.Declaration {
private GlslBlock body = new GlslBlock();
private FnSignature signature;
private GlslBlock body = new GlslBlock();
public GlslFn signature(FnSignature signature) {
this.signature = signature;
return this;
}
public GlslFn body(Consumer<GlslBlock> f) {
f.accept(body);
return this;
}
public GlslFn body(GlslBlock block) {
body = block;
return this;
}
public GlslFn body(Consumer<GlslBlock> f) {
f.accept(body);
return this;
}
@Override
public String prettyPrint() {
return """
%s {

View File

@ -4,15 +4,15 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import dev.engine_room.flywheel.lib.util.Pair;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.lib.util.StringUtil;
public class GlslStruct implements GlslBuilder.Declaration {
private final List<Pair<String, String>> fields = new ArrayList<>();
private String name;
private final List<Pair<String, String>> fields = new ArrayList<>();
public GlslStruct setName(String name) {
public GlslStruct name(String name) {
this.name = name;
return this;
}
@ -22,12 +22,6 @@ public class GlslStruct implements GlslBuilder.Declaration {
return this;
}
private String buildFields() {
return fields.stream()
.map(p -> p.first() + ' ' + p.second() + ';')
.collect(Collectors.joining("\n"));
}
@Override
public String prettyPrint() {
return """
@ -35,4 +29,10 @@ public class GlslStruct implements GlslBuilder.Declaration {
%s
};""".formatted(name, StringUtil.indent(buildFields(), 4));
}
private String buildFields() {
return fields.stream()
.map(p -> p.getFirst() + ' ' + p.getSecond() + ';')
.collect(Collectors.joining("\n"));
}
}

View File

@ -4,13 +4,17 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import dev.engine_room.flywheel.lib.util.Pair;
import org.jetbrains.annotations.Nullable;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.lib.util.StringUtil;
public class GlslSwitch implements GlslStmt {
private final GlslExpr on;
private final List<Pair<GlslExpr, GlslBlock>> cases = new ArrayList<>();
@Nullable
private GlslBlock defaultCase = null;
private GlslSwitch(GlslExpr on) {
@ -52,9 +56,9 @@ public class GlslSwitch implements GlslStmt {
}
private static String prettyPrintCase(Pair<GlslExpr, GlslBlock> p) {
var variant = p.first()
var variant = p.getFirst()
.prettyPrint();
var block = p.second()
var block = p.getSecond()
.prettyPrint();
return """
case %s:

View File

@ -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 + ";";
}
}

View File

@ -4,7 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import dev.engine_room.flywheel.lib.util.Pair;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.lib.util.StringUtil;
public class GlslUniformBlock implements GlslBuilder.Declaration {
@ -12,20 +13,6 @@ public class GlslUniformBlock implements GlslBuilder.Declaration {
private String name;
private final List<Pair<String, String>> members = new ArrayList<>();
@Override
public String prettyPrint() {
return """
layout(%s) uniform %s {
%s
};""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4));
}
private String formatMembers() {
return members.stream()
.map(p -> p.first() + " " + p.second() + ";")
.collect(Collectors.joining("\n"));
}
public GlslUniformBlock layout(String qualifier) {
this.qualifier = qualifier;
return this;
@ -40,4 +27,18 @@ public class GlslUniformBlock implements GlslBuilder.Declaration {
members.add(Pair.of(typeName, variableName));
return this;
}
@Override
public String prettyPrint() {
return """
layout(%s) uniform %s {
%s
};""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4));
}
private String formatMembers() {
return members.stream()
.map(p -> p.getFirst() + " " + p.getSecond() + ";")
.collect(Collectors.joining("\n"));
}
}

View File

@ -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;

View File

@ -12,17 +12,17 @@ public record Import(Span self, Span file) {
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*include\\s+\"(.*)\"", Pattern.MULTILINE);
/**
* Scan the source for {@code #use "..."} directives.
* Scan the source for {@code #include "..."} directives.
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
*/
public static ImmutableList<Import> parseImports(SourceLines source) {
Matcher uses = PATTERN.matcher(source);
Matcher matcher = PATTERN.matcher(source);
var imports = ImmutableList.<Import>builder();
while (uses.find()) {
Span use = Span.fromMatcher(source, uses);
Span file = Span.fromMatcher(source, uses, 1);
while (matcher.find()) {
Span use = Span.fromMatcher(source, matcher);
Span file = Span.fromMatcher(source, matcher, 1);
imports.add(new Import(use, file));
}

View File

@ -13,17 +13,19 @@ import dev.engine_room.flywheel.backend.glsl.span.Span;
public class ShaderField {
public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public final Span self;
public final Span location;
public final @Nullable Decoration decoration;
public final Span qualifierSpan;
@Nullable
public final Qualifier qualifier;
public final Span type;
public final Span name;
public final Span self;
public ShaderField(Span self, Span location, Span inOut, Span type, Span name) {
public ShaderField(Span self, Span location, Span qualifier, Span type, Span name) {
this.self = self;
this.location = location;
this.decoration = Decoration.fromSpan(inOut);
this.qualifierSpan = qualifier;
this.qualifier = Qualifier.fromSpan(qualifier);
this.type = type;
this.name = name;
}
@ -48,14 +50,14 @@ public class ShaderField {
return fields.build();
}
public enum Decoration {
public enum Qualifier {
IN,
OUT,
FLAT,
;
@Nullable
public static Decoration fromSpan(Span span) {
public static Qualifier fromSpan(Span span) {
return switch (span.toString()) {
case "in" -> IN;
case "out" -> OUT;

View File

@ -17,17 +17,16 @@ import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
public class ShaderFunction {
// https://regexr.com/60n3d
public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
public static final Pattern ASSIGNMENT_PATTERN = Pattern.compile("(\\w+)\\s*=");
public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
public static final Pattern assignment = Pattern.compile("(\\w+)\\s*=");
public final Span self;
public final Span type;
public final Span name;
public final Span args;
public final Span body;
private final Span type;
private final Span name;
private final Span args;
private final Span body;
private final ImmutableList<ShaderVariable> parameters;
public final ImmutableList<ShaderVariable> parameters;
public ShaderFunction(Span self, Span type, Span name, Span args, Span body) {
this.self = self;
@ -95,22 +94,6 @@ public class ShaderFunction {
return -1;
}
public Span getType() {
return type;
}
public Span getName() {
return name;
}
public Span getArgs() {
return args;
}
public Span getBody() {
return body;
}
public String call(String... args) {
return name + "(" + String.join(", ", args) + ")";
}
@ -119,18 +102,10 @@ public class ShaderFunction {
return parameters.get(index).type;
}
public ImmutableList<ShaderVariable> getParameters() {
return parameters;
}
public String returnTypeName() {
return type.get();
}
protected ImmutableList<ShaderVariable> parseArguments() {
if (args.isErr() || args.isEmpty()) return ImmutableList.of();
Matcher arguments = argument.matcher(args.get());
Matcher arguments = ARGUMENT_PATTERN.matcher(args.get());
ImmutableList.Builder<ShaderVariable> builder = ImmutableList.builder();

View File

@ -13,19 +13,20 @@ public class ShaderStruct {
// https://regexr.com/61rpe
public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w_]*)\\s*\\{(.*?)}\\s*([\\w_]*)?\\s*;\\s", Pattern.DOTALL);
public final Span self;
public final Span name;
public final Span body;
public final Span self;
public final Span variableName;
private final ImmutableList<StructField> fields;
private final ImmutableMap<String, Span> fields2Types;
public final ImmutableList<StructField> fields;
public final ImmutableMap<String, Span> fields2Types;
public ShaderStruct(Span self, Span name, Span body, Span variableName) {
this.self = self;
this.name = name;
this.body = body;
this.variableName = variableName;
this.fields = parseFields();
this.fields2Types = createTypeLookup();
}
@ -51,29 +52,8 @@ public class ShaderStruct {
return structs.build();
}
public Span getName() {
return name;
}
public Span getBody() {
return body;
}
public ImmutableList<StructField> getFields() {
return fields;
}
private ImmutableMap<String, Span> createTypeLookup() {
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
for (StructField field : fields) {
lookup.put(field.name.get(), field.type);
}
return lookup.build();
}
private ImmutableList<StructField> parseFields() {
Matcher matcher = StructField.fieldPattern.matcher(body);
Matcher matcher = StructField.PATTERN.matcher(body);
ImmutableList.Builder<StructField> fields = ImmutableList.builder();
@ -88,6 +68,15 @@ public class ShaderStruct {
return fields.build();
}
private ImmutableMap<String, Span> createTypeLookup() {
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
for (StructField field : fields) {
lookup.put(field.name.get(), field.type);
}
return lookup.build();
}
@Override
public String toString() {
return "struct " + name;

View File

@ -1,21 +1,23 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class ShaderVariable {
public final Span self;
public final Span qualifierSpan;
@Nullable
public final Qualifier qualifier;
public final Span type;
public final Span name;
public final Qualifier qualifier;
public final Span self;
public ShaderVariable(Span self, Span qualifier, Span type, Span name) {
this.self = self;
this.qualifierSpan = qualifier;
this.qualifier = Qualifier.fromSpan(qualifierSpan);
this.type = type;
this.name = name;
this.qualifier = Qualifier.fromSpan(qualifierSpan);
}
@Override
@ -27,9 +29,9 @@ public class ShaderVariable {
NONE,
IN,
OUT,
INOUT,
ERROR;
INOUT;
@Nullable
public static Qualifier fromSpan(Span s) {
String span = s.toString();
@ -38,7 +40,7 @@ public class ShaderVariable {
case "in" -> IN;
case "inout" -> INOUT;
case "out" -> OUT;
default -> ERROR;
default -> null;
};
}
}

View File

@ -5,11 +5,11 @@ import java.util.regex.Pattern;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class StructField {
public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);");
public final Span self;
public static final Pattern PATTERN = Pattern.compile("(\\S+)\\s*(\\S+);");
public Span type;
public Span name;
public final Span self;
public final Span type;
public final Span name;
public StructField(Span self, Span type, Span name) {
this.self = self;
@ -17,14 +17,6 @@ public class StructField {
this.name = name;
}
public Span getType() {
return type;
}
public Span getName() {
return name;
}
@Override
public String toString() {
return type + " " + name;

View File

@ -3,7 +3,6 @@ package dev.engine_room.flywheel.backend.glsl.span;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
public class StringSpan extends Span {
public StringSpan(SourceLines in, int start, int end) {
super(in, start, end);
}

View File

@ -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

View File

@ -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);

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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[];
};

View File

@ -9,6 +9,17 @@ struct FrustumPlanes {
vec2 zW; // <nz.w, pz.w>
};
struct _FlwCullData {
float znear;
float zfar;
float P00;
float P11;
float pyramidWidth;
float pyramidHeight;
int pyramidLevels;
uint useMin;
};
layout(std140) uniform _FlwFrameUniforms {
FrustumPlanes flw_frustumPlanes;
@ -47,6 +58,8 @@ layout(std140) uniform _FlwFrameUniforms {
uint flw_cameraInBlock;
uint _flw_debugMode;
_FlwCullData _flw_cullData;
};
#define flw_renderOrigin (_flw_renderOrigin.xyz)

View File

@ -12,6 +12,26 @@ import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
public final class InstanceTypes {
public static final InstanceType<TransformedInstance> TRANSFORMED = SimpleInstanceType.builder(TransformedInstance::new)
.layout(LayoutBuilder.create()
.vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4)
.vector("overlay", IntegerRepr.SHORT, 2)
.vector("light", FloatRepr.UNSIGNED_SHORT, 2)
.matrix("pose", FloatRepr.FLOAT, 4)
.build())
.writer((ptr, instance) -> {
MemoryUtil.memPutByte(ptr, instance.red);
MemoryUtil.memPutByte(ptr + 1, instance.green);
MemoryUtil.memPutByte(ptr + 2, instance.blue);
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
})
.vertexShader(Flywheel.rl("instance/transformed.vert"))
.cullShader(Flywheel.rl("instance/cull/transformed.glsl"))
.register();
public static final InstanceType<PosedInstance> POSED = SimpleInstanceType.builder(PosedInstance::new)
.layout(LayoutBuilder.create()
.vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4)
.vector("overlay", IntegerRepr.SHORT, 2)
@ -26,11 +46,11 @@ public final class InstanceTypes {
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model);
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal);
})
.vertexShader(Flywheel.rl("instance/transformed.vert"))
.cullShader(Flywheel.rl("instance/cull/transformed.glsl"))
.vertexShader(Flywheel.rl("instance/posed.vert"))
.cullShader(Flywheel.rl("instance/cull/posed.glsl"))
.register();
public static final InstanceType<OrientedInstance> ORIENTED = SimpleInstanceType.builder(OrientedInstance::new)

View File

@ -0,0 +1,110 @@
package dev.engine_room.flywheel.lib.instance;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Quaternionfc;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.instance.InstanceHandle;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.lib.transform.Transform;
import net.minecraft.util.Mth;
public class PosedInstance extends ColoredLitInstance implements Transform<PosedInstance> {
public final Matrix4f pose = new Matrix4f();
public final Matrix3f normal = new Matrix3f();
public PosedInstance(InstanceType<? extends PosedInstance> type, InstanceHandle handle) {
super(type, handle);
}
@Override
public PosedInstance mulPose(Matrix4fc pose) {
this.pose.mul(pose);
return this;
}
@Override
public PosedInstance mulNormal(Matrix3fc normal) {
this.normal.mul(normal);
return this;
}
@Override
public PosedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) {
pose.rotateAround(quaternion, x, y, z);
normal.rotate(quaternion);
return this;
}
@Override
public PosedInstance translate(float x, float y, float z) {
pose.translate(x, y, z);
return this;
}
@Override
public PosedInstance rotate(Quaternionfc quaternion) {
pose.rotate(quaternion);
normal.rotate(quaternion);
return this;
}
@Override
public PosedInstance scale(float x, float y, float z) {
pose.scale(x, y, z);
if (x == y && y == z) {
if (x < 0.0f) {
normal.scale(-1.0f);
}
return this;
}
float invX = 1.0f / x;
float invY = 1.0f / y;
float invZ = 1.0f / z;
float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ));
normal.scale(f * invX, f * invY, f * invZ);
return this;
}
public PosedInstance setTransform(Matrix4fc pose, Matrix3fc normal) {
this.pose.set(pose);
this.normal.set(normal);
return this;
}
public PosedInstance setTransform(PoseStack.Pose pose) {
this.pose.set(pose.pose());
normal.set(pose.normal());
return this;
}
public PosedInstance setTransform(PoseStack stack) {
return setTransform(stack.last());
}
public PosedInstance setIdentityTransform() {
pose.identity();
normal.identity();
return this;
}
/**
* Sets the transform matrices to be all zeros.
*
* <p>
* This will allow the GPU to quickly discard all geometry for this instance, effectively "turning it off".
* </p>
*/
public PosedInstance setZeroTransform() {
pose.zero();
normal.zero();
return this;
}
}

View File

@ -1,7 +1,5 @@
package dev.engine_room.flywheel.lib.instance;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Quaternionfc;
@ -10,72 +8,46 @@ import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.instance.InstanceHandle;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.lib.transform.Transform;
import net.minecraft.util.Mth;
import dev.engine_room.flywheel.lib.transform.Affine;
public class TransformedInstance extends ColoredLitInstance implements Transform<TransformedInstance> {
public final Matrix4f model = new Matrix4f();
public final Matrix3f normal = new Matrix3f();
public class TransformedInstance extends ColoredLitInstance implements Affine<TransformedInstance> {
public final Matrix4f pose = new Matrix4f();
public TransformedInstance(InstanceType<? extends TransformedInstance> type, InstanceHandle handle) {
super(type, handle);
}
@Override
public TransformedInstance mulPose(Matrix4fc pose) {
this.model.mul(pose);
return this;
}
@Override
public TransformedInstance mulNormal(Matrix3fc normal) {
this.normal.mul(normal);
return this;
}
@Override
public TransformedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) {
model.rotateAround(quaternion, x, y, z);
normal.rotate(quaternion);
pose.rotateAround(quaternion, x, y, z);
return this;
}
@Override
public TransformedInstance translate(float x, float y, float z) {
model.translate(x, y, z);
pose.translate(x, y, z);
return this;
}
@Override
public TransformedInstance rotate(Quaternionfc quaternion) {
model.rotate(quaternion);
normal.rotate(quaternion);
pose.rotate(quaternion);
return this;
}
@Override
public TransformedInstance scale(float x, float y, float z) {
model.scale(x, y, z);
pose.scale(x, y, z);
return this;
}
if (x == y && y == z) {
if (x < 0.0f) {
normal.scale(-1.0f);
}
return this;
}
float invX = 1.0f / x;
float invY = 1.0f / y;
float invZ = 1.0f / z;
float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ));
normal.scale(f * invX, f * invY, f * invZ);
public TransformedInstance setTransform(Matrix4fc pose) {
this.pose.set(pose);
return this;
}
public TransformedInstance setTransform(PoseStack.Pose pose) {
model.set(pose.pose());
normal.set(pose.normal());
this.pose.set(pose.pose());
return this;
}
@ -84,8 +56,7 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
}
public TransformedInstance setIdentityTransform() {
model.identity();
normal.identity();
pose.identity();
return this;
}
@ -97,8 +68,7 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
* </p>
*/
public TransformedInstance setZeroTransform() {
model.zero();
normal.zero();
pose.zero();
return this;
}
}

View File

@ -1,11 +1,16 @@
package dev.engine_room.flywheel.lib.internal;
import java.util.Deque;
import java.util.Map;
import org.slf4j.Logger;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dev.engine_room.flywheel.api.internal.DependencyInjection;
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
import net.minecraft.client.model.geom.ModelPart;
public interface FlwLibLink {
FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl");
@ -13,4 +18,10 @@ public interface FlwLibLink {
Logger getLogger();
PoseTransformStack getPoseTransformStackOf(PoseStack stack);
Map<String, ModelPart> getModelPartChildren(ModelPart part);
void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha);
Deque<PoseStack.Pose> getPoseStack(PoseStack stack);
}

View File

@ -1,40 +0,0 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public final class ModelCache<T> {
private static final Set<ModelCache<?>> ALL = FlwUtil.createWeakHashSet();
private final Function<T, Model> factory;
private final Map<T, Model> map = new ConcurrentHashMap<>();
public ModelCache(Function<T, Model> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public Model get(T key) {
return map.computeIfAbsent(key, factory);
}
public void clear() {
map.clear();
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ModelCache<?> cache : ALL) {
cache.clear();
}
}
}

View File

@ -1,60 +0,0 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Set;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public final class ModelHolder {
private static final Set<ModelHolder> ALL = FlwUtil.createWeakHashSet();
private final Supplier<Model> factory;
@Nullable
private volatile Model model;
public ModelHolder(Supplier<Model> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public Model get() {
Model model = this.model;
if (model == null) {
synchronized (this) {
model = this.model;
if (model == null) {
this.model = model = factory.get();
}
}
}
return model;
}
public void clear() {
Model model = this.model;
if (model != null) {
synchronized (this) {
model = this.model;
if (model != null) {
this.model = null;
}
}
}
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ModelHolder holder : ALL) {
holder.clear();
}
}
}

View File

@ -9,6 +9,7 @@ import dev.engine_room.flywheel.lib.model.baked.BakedModelBuilder;
import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder;
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
@ -19,11 +20,11 @@ import net.minecraft.world.level.block.state.BlockState;
* method with the same parameters will return the same object.
*/
public final class Models {
private static final ModelCache<BlockState> BLOCK_STATE = new ModelCache<>(it -> BlockModelBuilder.create(it)
private static final ResourceReloadCache<BlockState, Model> BLOCK_STATE = new ResourceReloadCache<>(it -> BlockModelBuilder.create(it)
.build());
private static final ModelCache<PartialModel> PARTIAL = new ModelCache<>(it -> BakedModelBuilder.create(it.get())
private static final ResourceReloadCache<PartialModel, Model> PARTIAL = new ResourceReloadCache<>(it -> BakedModelBuilder.create(it.get())
.build());
private static final ModelCache<TransformedPartial<?>> TRANSFORMED_PARTIAL = new ModelCache<>(TransformedPartial::create);
private static final ResourceReloadCache<TransformedPartial<?>, Model> TRANSFORMED_PARTIAL = new ResourceReloadCache<>(TransformedPartial::create);
private Models() {
}

View File

@ -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();
}
}

View File

@ -0,0 +1,483 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import java.util.function.ObjIntConsumer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Quaternionf;
import org.joml.Vector3fc;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.transform.Affine;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
public final class InstanceTree {
private final ModelTree source;
@Nullable
private final TransformedInstance instance;
private final InstanceTree[] children;
private final Matrix4f poseMatrix;
private float x;
private float y;
private float z;
private float xRot;
private float yRot;
private float zRot;
private float xScale;
private float yScale;
private float zScale;
@ApiStatus.Experimental
public boolean visible = true;
@ApiStatus.Experimental
public boolean skipDraw;
private boolean changed;
private InstanceTree(ModelTree source, @Nullable TransformedInstance instance, InstanceTree[] children) {
this.source = source;
this.instance = instance;
this.children = children;
if (instance != null) {
poseMatrix = instance.pose;
} else {
poseMatrix = new Matrix4f();
}
resetPose();
}
public static InstanceTree create(InstancerProvider provider, ModelTree meshTree) {
InstanceTree[] children = new InstanceTree[meshTree.childCount()];
for (int i = 0; i < meshTree.childCount(); i++) {
children[i] = create(provider, meshTree.child(i));
}
Model model = meshTree.model();
TransformedInstance instance;
if (model != null) {
instance = provider.instancer(InstanceTypes.TRANSFORMED, model)
.createInstance();
} else {
instance = null;
}
return new InstanceTree(meshTree, instance, children);
}
@Nullable
public TransformedInstance instance() {
return instance;
}
public PartPose initialPose() {
return source.initialPose();
}
public int childCount() {
return children.length;
}
public InstanceTree child(int index) {
return children[index];
}
public String childName(int index) {
return source.childName(index);
}
public int childIndex(String name) {
return source.childIndex(name);
}
public boolean hasChild(String name) {
return childIndex(name) >= 0;
}
@Nullable
public InstanceTree child(String name) {
int index = childIndex(name);
if (index < 0) {
return null;
}
return child(index);
}
public InstanceTree childOrThrow(String name) {
InstanceTree child = child(name);
if (child == null) {
throw new NoSuchElementException("Can't find part " + name);
}
return child;
}
public void traverse(Consumer<? super TransformedInstance> consumer) {
if (instance != null) {
consumer.accept(instance);
}
for (InstanceTree child : children) {
child.traverse(consumer);
}
}
@ApiStatus.Experimental
public void traverse(int i, ObjIntConsumer<? super TransformedInstance> consumer) {
if (instance != null) {
consumer.accept(instance, i);
}
for (InstanceTree child : children) {
child.traverse(i, consumer);
}
}
@ApiStatus.Experimental
public void traverse(int i, int j, ObjIntIntConsumer<? super TransformedInstance> consumer) {
if (instance != null) {
consumer.accept(instance, i, j);
}
for (InstanceTree child : children) {
child.traverse(i, j, consumer);
}
}
public void translateAndRotate(Affine<?> affine, Quaternionf tempQuaternion) {
affine.translate(x / 16.0F, y / 16.0F, z / 16.0F);
if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) {
affine.rotate(tempQuaternion.rotationZYX(zRot, yRot, xRot));
}
if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) {
affine.scale(xScale, yScale, zScale);
}
}
public void translateAndRotate(PoseStack poseStack, Quaternionf tempQuaternion) {
translateAndRotate(TransformStack.of(poseStack), tempQuaternion);
}
public void translateAndRotate(Matrix4f pose) {
pose.translate(x / 16.0F, y / 16.0F, z / 16.0F);
if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) {
pose.rotateZYX(zRot, yRot, xRot);
}
if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) {
pose.scale(xScale, yScale, zScale);
}
}
/**
* Update the instances in this tree, assuming initialPose changes.
*
* <p>This is the preferred method for entity visuals, or if you're not sure which you need.
*
* @param initialPose The root transformation matrix.
*/
public void updateInstances(Matrix4fc initialPose) {
propagateAnimation(initialPose, true);
}
/**
* Update the instances in this tree, assuming initialPose doesn't change between invocations.
*
* <p>This is the preferred method for block entity visuals.
*
* @param initialPose The root transformation matrix.
*/
public void updateInstancesStatic(Matrix4fc initialPose) {
propagateAnimation(initialPose, false);
}
/**
* Propagate pose transformations to this tree and all its children.
*
* @param initialPose The root transformation matrix.
* @param force Whether to force the update even if this node's transformations haven't changed.
*/
public void propagateAnimation(Matrix4fc initialPose, boolean force) {
if (!visible) {
return;
}
if (changed || force) {
poseMatrix.set(initialPose);
translateAndRotate(poseMatrix);
force = true;
if (instance != null && !skipDraw) {
instance.setChanged();
}
}
for (InstanceTree child : children) {
child.propagateAnimation(poseMatrix, force);
}
changed = false;
}
public float xPos() {
return x;
}
public float yPos() {
return y;
}
public float zPos() {
return z;
}
public float xRot() {
return xRot;
}
public float yRot() {
return yRot;
}
public float zRot() {
return zRot;
}
public float xScale() {
return xScale;
}
public float yScale() {
return yScale;
}
public float zScale() {
return zScale;
}
public void xPos(float x) {
this.x = x;
setChanged();
}
public void yPos(float y) {
this.y = y;
setChanged();
}
public void zPos(float z) {
this.z = z;
setChanged();
}
public void pos(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
setChanged();
}
public void xRot(float xRot) {
this.xRot = xRot;
setChanged();
}
public void yRot(float yRot) {
this.yRot = yRot;
setChanged();
}
public void zRot(float zRot) {
this.zRot = zRot;
setChanged();
}
public void rotation(float xRot, float yRot, float zRot) {
this.xRot = xRot;
this.yRot = yRot;
this.zRot = zRot;
setChanged();
}
public void xScale(float xScale) {
this.xScale = xScale;
setChanged();
}
public void yScale(float yScale) {
this.yScale = yScale;
setChanged();
}
public void zScale(float zScale) {
this.zScale = zScale;
setChanged();
}
public void scale(float xScale, float yScale, float zScale) {
this.xScale = xScale;
this.yScale = yScale;
this.zScale = zScale;
setChanged();
}
public void offsetPos(float xOffset, float yOffset, float zOffset) {
x += xOffset;
y += yOffset;
z += zOffset;
setChanged();
}
public void offsetXPos(float xOffset) {
x += xOffset;
setChanged();
}
public void offsetYPos(float yOffset) {
y += yOffset;
setChanged();
}
public void offsetZPos(float zOffset) {
z += zOffset;
setChanged();
}
public void offsetPos(Vector3fc offset) {
offsetPos(offset.x(), offset.y(), offset.z());
}
public void offsetRotation(float xOffset, float yOffset, float zOffset) {
xRot += xOffset;
yRot += yOffset;
zRot += zOffset;
setChanged();
}
public void offsetXRot(float xOffset) {
xRot += xOffset;
setChanged();
}
public void offsetYRot(float yOffset) {
yRot += yOffset;
setChanged();
}
public void offsetZRot(float zOffset) {
zRot += zOffset;
setChanged();
}
public void offsetRotation(Vector3fc offset) {
offsetRotation(offset.x(), offset.y(), offset.z());
}
public void offsetScale(float xOffset, float yOffset, float zOffset) {
xScale += xOffset;
yScale += yOffset;
zScale += zOffset;
setChanged();
}
public void offsetXScale(float xOffset) {
xScale += xOffset;
setChanged();
}
public void offsetYScale(float yOffset) {
yScale += yOffset;
setChanged();
}
public void offsetZScale(float zOffset) {
zScale += zOffset;
setChanged();
}
public void offsetScale(Vector3fc offset) {
offsetScale(offset.x(), offset.y(), offset.z());
}
public PartPose storePose() {
return PartPose.offsetAndRotation(x, y, z, xRot, yRot, zRot);
}
public void loadPose(PartPose pose) {
x = pose.x;
y = pose.y;
z = pose.z;
xRot = pose.xRot;
yRot = pose.yRot;
zRot = pose.zRot;
xScale = ModelPart.DEFAULT_SCALE;
yScale = ModelPart.DEFAULT_SCALE;
zScale = ModelPart.DEFAULT_SCALE;
setChanged();
}
public void resetPose() {
loadPose(source.initialPose());
}
public void copyTransform(InstanceTree tree) {
x = tree.x;
y = tree.y;
z = tree.z;
xRot = tree.xRot;
yRot = tree.yRot;
zRot = tree.zRot;
xScale = tree.xScale;
yScale = tree.yScale;
zScale = tree.zScale;
setChanged();
}
public void copyTransform(ModelPart modelPart) {
x = modelPart.x;
y = modelPart.y;
z = modelPart.z;
xRot = modelPart.xRot;
yRot = modelPart.yRot;
zRot = modelPart.zRot;
xScale = modelPart.xScale;
yScale = modelPart.yScale;
zScale = modelPart.zScale;
setChanged();
}
private void setChanged() {
changed = true;
}
public void delete() {
if (instance != null) {
instance.delete();
}
for (InstanceTree child : children) {
child.delete();
}
}
@ApiStatus.Experimental
@FunctionalInterface
public interface ObjIntIntConsumer<T> {
void accept(T t, int i, int j);
}
}

View File

@ -0,0 +1,139 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.Arrays;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
public final class MeshTree {
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last();
private static final ResourceReloadCache<ModelLayerLocation, MeshTree> CACHE = new ResourceReloadCache<>(MeshTree::convert);
@Nullable
private final Mesh mesh;
private final PartPose initialPose;
private final MeshTree[] children;
private final String[] childNames;
private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childNames) {
this.mesh = mesh;
this.initialPose = initialPose;
this.children = children;
this.childNames = childNames;
}
public static MeshTree of(ModelLayerLocation layer) {
return CACHE.get(layer);
}
private static MeshTree convert(ModelLayerLocation layer) {
EntityModelSet entityModels = Minecraft.getInstance()
.getEntityModels();
ModelPart modelPart = entityModels.bakeLayer(layer);
return convert(modelPart, THREAD_LOCAL_OBJECTS.get());
}
private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) {
var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart);
String[] childNames = modelPartChildren.keySet()
.toArray(String[]::new);
Arrays.sort(childNames);
MeshTree[] children = new MeshTree[childNames.length];
for (int i = 0; i < childNames.length; i++) {
children[i] = convert(modelPartChildren.get(childNames[i]), objects);
}
return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childNames);
}
@Nullable
private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) {
if (modelPart.isEmpty()) {
return null;
}
VertexWriter vertexWriter = objects.vertexWriter;
FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY_POSE, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
MemoryBlock data = vertexWriter.copyDataAndReset();
VertexView vertexView = new PosTexNormalVertexView();
vertexView.load(data);
return new SimpleQuadMesh(vertexView, "source=MeshTree");
}
@Nullable
public Mesh mesh() {
return mesh;
}
public PartPose initialPose() {
return initialPose;
}
public int childCount() {
return children.length;
}
public MeshTree child(int index) {
return children[index];
}
public String childName(int index) {
return childNames[index];
}
public int childIndex(String name) {
return Arrays.binarySearch(childNames, name);
}
public boolean hasChild(String name) {
return childIndex(name) >= 0;
}
@Nullable
public MeshTree child(String name) {
int index = childIndex(name);
if (index < 0) {
return null;
}
return child(index);
}
public MeshTree childOrThrow(String name) {
MeshTree child = child(name);
if (child == null) {
throw new NoSuchElementException("Can't find part " + name);
}
return child;
}
private static class ThreadLocalObjects {
public final VertexWriter vertexWriter = new VertexWriter();
}
}

View File

@ -1,69 +0,0 @@
package dev.engine_room.flywheel.lib.model.part;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
public final class ModelPartConverter {
private static final ThreadLocal<ThreadLocalObjects> THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
private ModelPartConverter() {
}
public static Mesh convert(ModelPart modelPart, @Nullable PoseStack poseStack, @Nullable TextureMapper textureMapper) {
ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
if (poseStack == null) {
poseStack = objects.identityPoseStack;
}
VertexWriter vertexWriter = objects.vertexWriter;
vertexWriter.setTextureMapper(textureMapper);
modelPart.render(poseStack, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY);
MemoryBlock data = vertexWriter.copyDataAndReset();
VertexView vertexView = new PosTexNormalVertexView();
vertexView.load(data);
return new SimpleQuadMesh(vertexView, "source=ModelPartConverter");
}
public static Mesh convert(ModelLayerLocation layer, @Nullable TextureAtlasSprite sprite, String... childPath) {
EntityModelSet entityModels = Minecraft.getInstance().getEntityModels();
ModelPart modelPart = entityModels.bakeLayer(layer);
for (String pathPart : childPath) {
modelPart = modelPart.getChild(pathPart);
}
TextureMapper textureMapper = sprite == null ? null : TextureMapper.toSprite(sprite);
return convert(modelPart, null, textureMapper);
}
public static Mesh convert(ModelLayerLocation layer, String... childPath) {
return convert(layer, null, childPath);
}
public interface TextureMapper {
void map(Vector2f uv);
static TextureMapper toSprite(TextureAtlasSprite sprite) {
return uv -> uv.set(sprite.getU(uv.x), sprite.getV(uv.y));
}
}
private static class ThreadLocalObjects {
public final PoseStack identityPoseStack = new PoseStack();
public final VertexWriter vertexWriter = new VertexWriter();
}
}

View File

@ -0,0 +1,91 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.model.Model;
import net.minecraft.client.model.geom.PartPose;
public final class ModelTree {
@Nullable
private final Model model;
private final PartPose initialPose;
private final ModelTree[] children;
private final String[] childNames;
/**
* Create a new ModelTree node.
*
* @param model The model to associate with this node, or null if this node does not render.
* @param initialPose The initial pose of this node.
* @param children The children of this node.
*/
public ModelTree(@Nullable Model model, PartPose initialPose, Map<String, ModelTree> children) {
this.model = model;
this.initialPose = initialPose;
String[] childNames = children.keySet().toArray(String[]::new);
Arrays.sort(childNames);
ModelTree[] childArray = new ModelTree[childNames.length];
for (int i = 0; i < childNames.length; i++) {
childArray[i] = children.get(childNames[i]);
}
this.children = childArray;
this.childNames = childNames;
}
@Nullable
public Model model() {
return model;
}
public PartPose initialPose() {
return initialPose;
}
public int childCount() {
return children.length;
}
public ModelTree child(int index) {
return children[index];
}
public String childName(int index) {
return childNames[index];
}
public int childIndex(String name) {
return Arrays.binarySearch(childNames, name);
}
public boolean hasChild(String name) {
return childIndex(name) >= 0;
}
@Nullable
public ModelTree child(String name) {
int index = childIndex(name);
if (index < 0) {
return null;
}
return child(index);
}
public ModelTree childOrThrow(String name) {
ModelTree child = child(name);
if (child == null) {
throw new NoSuchElementException("Can't find part " + name);
}
return child;
}
}

View File

@ -0,0 +1,83 @@
package dev.engine_room.flywheel.lib.model.part;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.model.RetexturedMesh;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
public final class ModelTrees {
private static final ResourceReloadCache<ModelTreeKey, ModelTree> CACHE = new ResourceReloadCache<>(k -> {
ModelTree tree = convert("", MeshTree.of(k.layer), k.pathsToPrune, k.texture != null ? k.texture.sprite() : null, k.material);
if (tree == null) {
throw new IllegalArgumentException("Cannot prune root node!");
}
return tree;
});
private ModelTrees() {
}
public static ModelTree of(ModelLayerLocation layer, Material material) {
return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), null, material));
}
public static ModelTree of(ModelLayerLocation layer, net.minecraft.client.resources.model.Material texture, Material material) {
return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), texture, material));
}
public static ModelTree of(ModelLayerLocation layer, Set<String> pathsToPrune, Material material) {
return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), null, material));
}
public static ModelTree of(ModelLayerLocation layer, Set<String> pathsToPrune, net.minecraft.client.resources.model.Material texture, Material material) {
return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), texture, material));
}
@Nullable
private static ModelTree convert(String path, MeshTree meshTree, Set<String> pathsToPrune, @Nullable TextureAtlasSprite sprite, Material material) {
if (pathsToPrune.contains(path)) {
return null;
}
Model model = null;
Mesh mesh = meshTree.mesh();
if (mesh != null) {
if (sprite != null) {
mesh = new RetexturedMesh(mesh, sprite);
}
model = new SingleMeshModel(mesh, material);
}
Map<String, ModelTree> children = new HashMap<>();
String pathSlash = path + "/";
for (int i = 0; i < meshTree.childCount(); i++) {
String childName = meshTree.childName(i);
var child = convert(pathSlash + childName, meshTree.child(i), pathsToPrune, sprite, material);
if (child != null) {
children.put(childName, child);
}
}
return new ModelTree(model, meshTree.initialPose(), children);
}
private record ModelTreeKey(ModelLayerLocation layer, Set<String> pathsToPrune, @Nullable net.minecraft.client.resources.model.Material texture, Material material) {
}
}

View File

@ -1,14 +1,11 @@
package dev.engine_room.flywheel.lib.model.part;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.lwjgl.system.MemoryUtil;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dev.engine_room.flywheel.lib.math.DataPacker;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.part.ModelPartConverter.TextureMapper;
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
class VertexWriter implements VertexConsumer {
@ -16,10 +13,6 @@ class VertexWriter implements VertexConsumer {
private MemoryBlock data;
@Nullable
private TextureMapper textureMapper;
private final Vector2f uvVec = new Vector2f();
private int vertexCount;
private boolean filledPosition;
private boolean filledTexture;
@ -29,10 +22,6 @@ class VertexWriter implements VertexConsumer {
data = MemoryBlock.malloc(128 * STRIDE);
}
public void setTextureMapper(@Nullable TextureMapper mapper) {
textureMapper = mapper;
}
@Override
public VertexConsumer addVertex(float x, float y, float z) {
if (!filledPosition) {
@ -53,13 +42,6 @@ class VertexWriter implements VertexConsumer {
@Override
public VertexConsumer setUv(float u, float v) {
if (!filledTexture) {
if (textureMapper != null) {
uvVec.set(u, v);
textureMapper.map(uvVec);
u = uvVec.x;
v = uvVec.y;
}
long ptr = vertexPtr();
MemoryUtil.memPutFloat(ptr + 12, u);
MemoryUtil.memPutFloat(ptr + 16, v);
@ -123,7 +105,6 @@ class VertexWriter implements VertexConsumer {
filledPosition = false;
filledTexture = false;
filledNormal = false;
textureMapper = null;
return dataCopy;
}

View File

@ -1,21 +0,0 @@
package dev.engine_room.flywheel.lib.util;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
public final class FlwUtil {
private FlwUtil() {
}
public static int[] initArray(int size, int fill) {
var out = new int[size];
Arrays.fill(out, fill);
return out;
}
public static <T> Set<T> createWeakHashSet() {
return Collections.newSetFromMap(new WeakHashMap<>());
}
}

View File

@ -1,36 +0,0 @@
package dev.engine_room.flywheel.lib.util;
import java.util.Objects;
public record Pair<F, S>(F first, S second) {
public static <F, S> Pair<F, S> of(F first, S second) {
return new Pair<>(first, second);
}
public Pair<S, F> swap() {
return Pair.of(second, first);
}
public Pair<F, S> copy() {
return Pair.of(first, second);
}
@Override
public boolean equals(final Object obj) {
if (obj == this) return true;
if (obj instanceof final Pair<?, ?> other) {
return Objects.equals(first, other.first) && Objects.equals(second, other.second);
}
return false;
}
@Override
public int hashCode() {
return (Objects.hashCode(first) * 31) ^ Objects.hashCode(second);
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}

View File

@ -0,0 +1,42 @@
package dev.engine_room.flywheel.lib.util;
import java.util.ArrayDeque;
import java.util.Deque;
import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
/**
* A {@link PoseStack} that recycles {@link PoseStack.Pose} objects.
*
* <p>Vanilla's {@link PoseStack} can get quite expensive to use when each game object needs to
* maintain their own stack. This class helps alleviate memory pressure by making Pose objects
* long-lived. Note that this means that you <em>CANNOT</em> safely store a Pose object outside
* the RecyclingPoseStack that created it.
*/
public class RecyclingPoseStack extends PoseStack {
private final Deque<Pose> recycleBin = new ArrayDeque<>();
@Override
public void pushPose() {
if (recycleBin.isEmpty()) {
super.pushPose();
} else {
var last = last();
var recycle = recycleBin.removeLast();
recycle.pose()
.set(last.pose());
recycle.normal()
.set(last.normal());
FlwLibLink.INSTANCE.getPoseStack(this)
.addLast(recycle);
}
}
@Override
public void popPose() {
recycleBin.addLast(FlwLibLink.INSTANCE.getPoseStack(this)
.removeLast());
}
}

View File

@ -0,0 +1,44 @@
package dev.engine_room.flywheel.lib.util;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
public final class ResourceReloadCache<T, U> implements Function<T, U> {
private static final Set<ResourceReloadCache<?, ?>> ALL = Collections.newSetFromMap(new WeakHashMap<>());
private final Function<T, U> factory;
private final Map<T, U> map = new ConcurrentHashMap<>();
public ResourceReloadCache(Function<T, U> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
public final U get(T key) {
return map.computeIfAbsent(key, factory);
}
@Override
public final U apply(T t) {
return get(t);
}
public final void clear() {
map.clear();
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ResourceReloadCache<?, ?> cache : ALL) {
cache.clear();
}
}
}

View File

@ -0,0 +1,60 @@
package dev.engine_room.flywheel.lib.util;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
public final class ResourceReloadHolder<T> implements Supplier<T> {
private static final Set<ResourceReloadHolder<?>> ALL = Collections.newSetFromMap(new WeakHashMap<>());
private final Supplier<T> factory;
@Nullable
private volatile T obj;
public ResourceReloadHolder(Supplier<T> factory) {
this.factory = factory;
synchronized (ALL) {
ALL.add(this);
}
}
@Override
public final T get() {
T obj = this.obj;
if (obj == null) {
synchronized (this) {
obj = this.obj;
if (obj == null) {
this.obj = obj = factory.get();
}
}
}
return obj;
}
public final void clear() {
T obj = this.obj;
if (obj != null) {
synchronized (this) {
obj = this.obj;
if (obj != null) {
this.obj = null;
}
}
}
}
@ApiStatus.Internal
public static void onEndClientResourceReload() {
for (ResourceReloadHolder<?> holder : ALL) {
holder.clear();
}
}
}

View File

@ -53,10 +53,11 @@ public final class ResourceUtil {
}
}
/**
* Same as {@link ResourceLocation#toDebugFileName()}, but also removes the file extension.
*/
public static String toDebugFileNameNoExtension(ResourceLocation resourceLocation) {
var stringLoc = resourceLocation.toString();
return stringLoc.substring(0, stringLoc.lastIndexOf('.'))
.replace('/', '_')
.replace(':', '_');
var stringLoc = resourceLocation.toDebugFileName();
return stringLoc.substring(0, stringLoc.lastIndexOf('.'));
}
}

View File

@ -57,12 +57,6 @@ public final class StringUtil {
return "0x" + Long.toHexString(address);
}
public static String args(String functionName, Object... args) {
return functionName + '(' + Arrays.stream(args)
.map(Object::toString)
.collect(Collectors.joining(", ")) + ')';
}
public static String trimPrefix(String s, String prefix) {
if (s.startsWith(prefix)) {
return s.substring(prefix.length());

View File

@ -1,5 +0,0 @@
package dev.engine_room.flywheel.lib.util;
public enum Unit {
INSTANCE;
}

View File

@ -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);
}
}
}

View File

@ -52,7 +52,7 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
this.blockEntity = blockEntity;
this.pos = blockEntity.getBlockPos();
this.blockState = blockEntity.getBlockState();
this.visualPos = pos.subtract(renderOrigin);
this.visualPos = pos.subtract(ctx.renderOrigin());
}
@Override
@ -99,6 +99,10 @@ public abstract class AbstractBlockEntityVisual<T extends BlockEntity> extends A
.shouldUpdate(pos.distToCenterSqr(context.camera().getPosition()));
}
protected int computePackedLight() {
return LevelRenderer.getLightColor(level, pos);
}
protected void relight(BlockPos pos, @Nullable FlatLit... instances) {
FlatLit.relight(LevelRenderer.getLightColor(level, pos), instances);
}

View File

@ -67,6 +67,7 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
*/
public Vector3f getVisualPosition() {
Vec3 pos = entity.position();
var renderOrigin = renderOrigin();
return new Vector3f((float) (pos.x - renderOrigin.getX()),
(float) (pos.y - renderOrigin.getY()),
(float) (pos.z - renderOrigin.getZ()));
@ -81,6 +82,7 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
*/
public Vector3f getVisualPosition(float partialTick) {
Vec3 pos = entity.position();
var renderOrigin = renderOrigin();
return new Vector3f((float) (Mth.lerp(partialTick, entity.xOld, pos.x) - renderOrigin.getX()),
(float) (Mth.lerp(partialTick, entity.yOld, pos.y) - renderOrigin.getY()),
(float) (Mth.lerp(partialTick, entity.zOld, pos.z) - renderOrigin.getZ()));
@ -90,11 +92,14 @@ public abstract class AbstractEntityVisual<T extends Entity> extends AbstractVis
return entity.noCulling || visibilityTester.check(frustum);
}
protected void relight(float partialTick, @Nullable FlatLit... instances) {
protected int computePackedLight(float partialTick) {
BlockPos pos = BlockPos.containing(entity.getLightProbePosition(partialTick));
int blockLight = entity.isOnFire() ? 15 : level.getBrightness(LightLayer.BLOCK, pos);
int skyLight = level.getBrightness(LightLayer.SKY, pos);
int light = LightTexture.pack(blockLight, skyLight);
FlatLit.relight(light, instances);
return LightTexture.pack(blockLight, skyLight);
}
protected void relight(float partialTick, @Nullable FlatLit... instances) {
FlatLit.relight(computePackedLight(partialTick), instances);
}
}

View File

@ -13,16 +13,12 @@ public abstract class AbstractVisual implements Visual {
* Useful for passing to child visuals.
*/
protected final VisualizationContext visualizationContext;
protected final InstancerProvider instancerProvider;
protected final Vec3i renderOrigin;
protected final Level level;
protected boolean deleted = false;
public AbstractVisual(VisualizationContext ctx, Level level, float partialTick) {
this.visualizationContext = ctx;
this.instancerProvider = ctx.instancerProvider();
this.renderOrigin = ctx.renderOrigin();
this.level = level;
}
@ -32,6 +28,14 @@ public abstract class AbstractVisual implements Visual {
protected abstract void _delete();
protected InstancerProvider instancerProvider() {
return visualizationContext.instancerProvider();
}
protected Vec3i renderOrigin() {
return visualizationContext.renderOrigin();
}
@Override
public final void delete() {
if (deleted) {

View File

@ -15,9 +15,9 @@ import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.material.Materials;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.QuadMesh;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
import dev.engine_room.flywheel.lib.util.ResourceReloadCache;
import dev.engine_room.flywheel.lib.visual.util.SmartRecycler;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@ -36,7 +36,7 @@ public final class FireComponent implements EntityComponent {
// Parameterize by the material instead of the sprite
// because Material#sprite is a surprisingly heavy operation
// and because sprites are invalidated after a resource reload.
private static final ModelCache<net.minecraft.client.resources.model.Material> FIRE_MODELS = new ModelCache<>(texture -> {
private static final ResourceReloadCache<net.minecraft.client.resources.model.Material, Model> FIRE_MODELS = new ResourceReloadCache<>(texture -> {
return new SingleMeshModel(new FireMesh(texture.sprite()), FIRE_MATERIAL);
});

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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();
}

View File

@ -35,7 +35,7 @@ public final class FlwImpl {
StandardMaterialShaders.init();
// backend
FlwBackend.init();
FlwBackend.init(FlwConfig.INSTANCE.backendConfig());
// vanilla
VanillaVisuals.init();

View File

@ -1,12 +1,19 @@
package dev.engine_room.flywheel.impl;
import java.util.Deque;
import java.util.Map;
import org.slf4j.Logger;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dev.engine_room.flywheel.impl.extension.PoseStackExtension;
import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor;
import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor;
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
import dev.engine_room.flywheel.lib.transform.PoseTransformStack;
import net.minecraft.client.model.geom.ModelPart;
public class FlwLibLinkImpl implements FlwLibLink {
@Override
@ -18,4 +25,19 @@ public class FlwLibLinkImpl implements FlwLibLink {
public PoseTransformStack getPoseTransformStackOf(PoseStack stack) {
return ((PoseStackExtension) stack).flywheel$transformStack();
}
@Override
public Map<String, ModelPart> getModelPartChildren(ModelPart part) {
return ((ModelPartAccessor) (Object) part).flywheel$children();
}
@Override
public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) {
((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha);
}
@Override
public Deque<PoseStack.Pose> getPoseStack(PoseStack stack) {
return ((PoseStackAccessor) stack).flywheel$getPoseStack();
}
}

View File

@ -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;

View File

@ -0,0 +1,21 @@
package dev.engine_room.flywheel.impl.mixin;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.model.geom.ModelPart;
@Mixin(ModelPart.class)
public interface ModelPartAccessor {
@Accessor("children")
Map<String, ModelPart> flywheel$children();
@Invoker("compile")
void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha);
}

View File

@ -0,0 +1,14 @@
package dev.engine_room.flywheel.impl.mixin;
import java.util.Deque;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import com.mojang.blaze3d.vertex.PoseStack;
@Mixin(PoseStack.class)
public interface PoseStackAccessor {
@Accessor("poseStack")
Deque<PoseStack.Pose> flywheel$getPoseStack();
}

Some files were not shown because too many files have changed in this diff Show More