Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved smooth section lighting in shaders #251

Merged
merged 18 commits into from
Jul 15, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
Expand Down Expand Up @@ -61,6 +62,15 @@ public interface Engine {
*/
Vec3i renderOrigin();

/**
* Assign the set of sections that visuals have requested GPU light for.
*
* <p> This will be called at most once per frame, and not necessarily every frame.
*
* @param sections The set of sections.
*/
void lightSections(LongSet sections);

/**
* Free all resources associated with this engine.
* <br>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.engine_room.flywheel.api.visual;

/**
* A visual that listens to light updates.
*
* <p>If your visual moves around in the level at all, you should use {@link TickableVisual} or {@link DynamicVisual},
* and poll for light yourself along with listening for updates. When your visual moves to a different section, call
* {@link SectionCollector#sections}.</p>
*/
public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual {
/**
* Called when a section this visual is contained in receives a light update. It is not called
* unconditionally after visual creation or {@link SectionCollector#sections}.
*
* <p>Even if multiple sections are updated at the same time, this method will only be called once.</p>
*
* <p>The implementation is free to parallelize calls to this method, as well as execute the plan
* returned by {@link DynamicVisual#planFrame} simultaneously. It is safe to query/update light here,
* but you must ensure proper synchronization if you want to mutate anything outside this visual or
* anything that is also mutated within {@link DynamicVisual#planFrame}.</p>
*/
void updateLight(float partialTick);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.engine_room.flywheel.api.visual;

import org.jetbrains.annotations.ApiStatus;

import it.unimi.dsi.fastutil.longs.LongSet;

public sealed interface SectionTrackedVisual extends Visual permits ShaderLightVisual, LightUpdatedVisual {
/**
* Set the section collector object.
*
* <p>This method is only called once, upon visual creation.
* <br>If the collector is invoked in this method, the
* visual will immediately be tracked in the given sections.
*
* @param collector The collector.
*/
void setSectionCollector(SectionCollector collector);

@ApiStatus.NonExtendable
interface SectionCollector {
/**
* Assign the set of sections this visual wants to track itself in.
*/
void sections(LongSet sections);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.engine_room.flywheel.api.visual;

/**
* A marker interface allowing visuals to request light data on the GPU for a set of sections.
*
* <p> Sections passed into {@link SectionCollector#sections} will have their light data handed to the
* backend and queryable by {@code flw_light*} functions in shaders.
* <br>
* Note that the queryable light data is shared across all visuals, so even if one specific visual does not
* request a given section, the data will be available if another visual does.
*/
public non-sealed interface ShaderLightVisual extends SectionTrackedVisual {

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*
* @see DynamicVisual
* @see TickableVisual
* @see LitVisual
* @see LightUpdatedVisual
* @see ShaderLightVisual
*/
public interface Visual {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.joml.Matrix4fc;

import dev.engine_room.flywheel.api.BackendImplemented;
import net.minecraft.world.level.BlockAndTintGetter;

@BackendImplemented
public interface VisualEmbedding extends VisualizationContext {
Expand All @@ -16,31 +15,6 @@ public interface VisualEmbedding extends VisualizationContext {
*/
void transforms(Matrix4fc pose, Matrix3fc normal);

/**
* Collect light information from the given level for the given box.
*
* <p>Call this method on as many or as few boxes as you need to
* encompass all child visuals of this embedding.</p>
*
* <p>After this method is called, instances rendered from this
* embedding within the given box will be lit as if they were in
* the given level.</p>
*
* @param level The level to collect light information from.
* @param minX The minimum x coordinate of the box.
* @param minY The minimum y coordinate of the box.
* @param minZ The minimum z coordinate of the box.
* @param sizeX The size of the box in the x direction.
* @param sizeY The size of the box in the y direction.
* @param sizeZ The size of the box in the z direction.
*/
void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ);

/**
* Reset any collected lighting information.
*/
void invalidateLight();

/**
* Delete this embedding.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ public final class Backends {
* Use GPU instancing to render everything.
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(new InstancedDrawManager(InstancingPrograms.get()), 256))
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("instancing"));

/**
* Use Compute shaders to cull instances.
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(new IndirectDrawManager(IndirectPrograms.get()), 256))
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.fallback(() -> Backends.INSTANCING)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.register(Flywheel.rl("indirect"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package dev.engine_room.flywheel.backend;

import dev.engine_room.flywheel.lib.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.LevelAccessor;

/**
* Stores the set of updates light sections for LightStorage to poll in its frame plan.
*/
public class LightUpdateHolder {
private static final LevelAttached<LightUpdateHolder> HOLDERS = new LevelAttached<>(level -> new LightUpdateHolder());

private final LongSet updatedSections = new LongOpenHashSet();

private LightUpdateHolder() {
}

public static LightUpdateHolder get(LevelAccessor level) {
return HOLDERS.get(level);
}

public LongSet getAndClearUpdatedSections() {
if (updatedSections.isEmpty()) {
return LongSet.of();
}

var out = new LongArraySet(updatedSections);
updatedSections.clear();
return out;
}

public void add(long section) {
updatedSections.add(section);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class Samplers {
public static final GlTextureUnit LIGHT = GlTextureUnit.T2;
public static final GlTextureUnit CRUMBLING = GlTextureUnit.T3;
public static final GlTextureUnit INSTANCE_BUFFER = GlTextureUnit.T4;
public static final GlTextureUnit EMBEDDED_LIGHT = GlTextureUnit.T5;
public static final GlTextureUnit LIGHT_LUT = GlTextureUnit.T5;
public static final GlTextureUnit LIGHT_SECTIONS = GlTextureUnit.T6;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public enum ContextShader {
DEFAULT(null, $ -> {
}),
CRUMBLING("_FLW_CRUMBLING", program -> program.setSamplerBinding("_flw_crumblingTex", Samplers.CRUMBLING)),
EMBEDDED("_FLW_EMBEDDED", program -> program.setSamplerBinding("_flw_lightVolume", Samplers.EMBEDDED_LIGHT));
EMBEDDED("FLW_EMBEDDED", $ -> {
});

@Nullable
private final String define;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ public final class Pipelines {
.vertexMain(Flywheel.rl("internal/instancing/main.vert"))
.fragmentMain(Flywheel.rl("internal/instancing/main.frag"))
.assembler(BufferTextureInstanceComponent::new)
.onLink(program -> program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER))
.onLink(program -> {
program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER);
program.setSamplerBinding("_flw_lightLut", Samplers.LIGHT_LUT);
program.setSamplerBinding("_flw_lightSections", Samplers.LIGHT_SECTIONS);
})
.build();

public static final Pipeline INDIRECT = Pipeline.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.backend.engine.indirect.IndirectBuffers;
import dev.engine_room.flywheel.backend.engine.indirect.BufferBindings;
import dev.engine_room.flywheel.backend.glsl.generate.FnSignature;
import dev.engine_room.flywheel.backend.glsl.generate.GlslBlock;
import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder;
Expand Down Expand Up @@ -43,7 +43,7 @@ protected void generateUnpacking(GlslBuilder builder) {

fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));

builder._addRaw("layout(std430, binding = " + IndirectBuffers.INSTANCE_INDEX + ") restrict readonly buffer InstanceBuffer {\n"
builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
+ " uint _flw_instances[];\n"
+ "};");
builder.blankLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
protected AbstractInstancer(InstanceType<I> type, Environment environment) {
this.type = type;
this.environment = environment;

environment.acquire();
}

@Override
Expand Down Expand Up @@ -177,9 +175,7 @@ public void clear() {
deleted.clear();
}

public void delete() {
environment.release();
}
public abstract void delete();

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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;
// List of free indices.
private final IntList freeStack = new IntArrayList();

public Arena(long elementSizeBytes, int initialCapacity) {
this.elementSizeBytes = elementSizeBytes;

memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity);
}

public int alloc() {
// First re-use freed elements.
if (!freeStack.isEmpty()) {
return freeStack.removeInt(freeStack.size() - 1);
}

// Make sure there's room to increment top.
if (top * elementSizeBytes >= memoryBlock.size()) {
memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2);
}

// Return the top index and increment.
return top++;
}

public void free(int i) {
// That's it! Now pls don't try to use it.
freeStack.add(i);
}

public long indexToPointer(int i) {
return memoryBlock.ptr() + i * elementSizeBytes;
}

public void delete() {
memoryBlock.free();
}

public int capacity() {
return top;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void delete() {
initializationQueue.clear();
}

public void flush() {
public void flush(LightStorage lightStorage) {
// Thread safety: flush is called from the render thread after all visual updates have been made,
// so there are no:tm: threads we could be racing with.
for (var instancer : initializationQueue) {
Expand Down
Loading