Turn it up to 11

- Consolidate context shaders into the common shaders, guarded by ifdefs
- Make ContextShaders into an enum
- Cache shaders by name instead of SourceComponents so the defines for
  contexts actually get compiled
- Move all embedding stuff into backend.embed
- Cannibalize GPULightVolume into an api better suited for environments
- Separate light storage from 3d texture management
- Add #delete to Environment
- Add light volume debug mode
This commit is contained in:
Jozufozu 2024-03-03 00:54:03 -08:00
parent 5d142d2f13
commit b90b80c8b0
35 changed files with 509 additions and 373 deletions

View file

@ -5,6 +5,8 @@ import org.joml.Matrix4fc;
import com.jozufozu.flywheel.api.BackendImplemented;
import net.minecraft.world.level.BlockAndTintGetter;
@BackendImplemented
public interface VisualEmbedding extends VisualizationContext {
/**
@ -14,4 +16,36 @@ public interface VisualEmbedding extends VisualizationContext {
* @param normal The normal matrix.
*/
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 light(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ);
/**
* Reset any collected lighting information for the given box.
*
* @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 invalidateLight(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ);
}

View file

@ -8,4 +8,5 @@ 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;
}

View file

@ -1,48 +1,40 @@
package com.jozufozu.flywheel.backend.compile;
import java.util.Objects;
import java.util.Locale;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.core.Compilation;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
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));
public record ContextShader(ResourceLocation vertexShader, ResourceLocation fragmentShader,
Consumer<GlProgram> onLink) {
public static Builder builder() {
return new Builder();
@Nullable
private final String define;
private final Consumer<GlProgram> onLink;
ContextShader(@Nullable String define, Consumer<GlProgram> onLink) {
this.define = define;
this.onLink = onLink;
}
public static class Builder {
@Nullable
private ResourceLocation vertexShader;
@Nullable
private ResourceLocation fragmentShader;
@Nullable
private Consumer<GlProgram> onLink;
public void onLink(GlProgram program) {
onLink.accept(program);
}
public Builder vertexShader(ResourceLocation shader) {
this.vertexShader = shader;
return this;
public void onCompile(Compilation comp) {
if (define != null) {
comp.define(define);
}
}
public Builder fragmentShader(ResourceLocation shader) {
this.fragmentShader = shader;
return this;
}
public Builder onLink(Consumer<GlProgram> onLink) {
this.onLink = onLink;
return this;
}
public ContextShader build() {
Objects.requireNonNull(vertexShader);
Objects.requireNonNull(fragmentShader);
Objects.requireNonNull(onLink);
return new ContextShader(vertexShader, fragmentShader, onLink);
}
public String nameLowerCase() {
return name().toLowerCase(Locale.ROOT);
}
}

View file

@ -1,38 +0,0 @@
package com.jozufozu.flywheel.backend.compile;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.internal.InternalFlywheelApi;
import com.jozufozu.flywheel.api.registry.Registry;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.Samplers;
public class ContextShaders {
public static final Registry<ContextShader> REGISTRY = InternalFlywheelApi.INSTANCE.createRegistry();
public static final ContextShader DEFAULT = REGISTRY.registerAndGet(ContextShader.builder()
.vertexShader(Flywheel.rl("internal/context/default.vert"))
.fragmentShader(Flywheel.rl("internal/context/default.frag"))
.onLink($ -> {
})
.build());
public static final ContextShader CRUMBLING = REGISTRY.registerAndGet(ContextShader.builder()
.vertexShader(Flywheel.rl("internal/context/crumbling.vert"))
.fragmentShader(Flywheel.rl("internal/context/crumbling.frag"))
.onLink(program -> program.setSamplerBinding("_flw_crumblingTex", Samplers.CRUMBLING))
.build());
public static final ContextShader EMBEDDED = REGISTRY.registerAndGet(ContextShader.builder()
.vertexShader(Flywheel.rl("internal/context/embedded.vert"))
.fragmentShader(Flywheel.rl("internal/context/embedded.frag"))
.onLink($ -> {
})
.build());
public static ContextShader forEmbedding(@Nullable VisualEmbedding level) {
if (level == null) {
return DEFAULT;
} else {
return EMBEDDED;
}
}
}

View file

@ -57,7 +57,7 @@ public final class FlwPrograms {
private static ImmutableList<PipelineProgramKey> createPipelineKeys() {
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
for (ContextShader contextShader : ContextShaders.REGISTRY) {
for (ContextShader contextShader : ContextShader.values()) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
builder.add(new PipelineProgramKey(instanceType, contextShader));
}

View file

@ -22,10 +22,12 @@ public class PipelineCompiler {
var instance = ResourceUtil.toDebugFileNameNoExtension(key.instanceType()
.vertexShader());
var context = ResourceUtil.toDebugFileNameNoExtension(key.contextShader()
.vertexShader());
var context = key.contextShader()
.nameLowerCase();
return "pipeline/" + pipeline.compilerMarker() + "/" + instance + "_" + context;
})
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.withResource(pipeline.vertexApiImpl())
.withResource(InternalVertex.LAYOUT_SHADER)
.withComponent(key -> pipeline.assembler()
@ -33,23 +35,18 @@ public class PipelineCompiler {
.withComponents(vertexComponents)
.withResource(key -> key.instanceType()
.vertexShader())
.withResource(key -> key.contextShader()
.vertexShader())
.withResource(pipeline.vertexMain()))
.link(PIPELINE.shader(pipeline.glslVersion(), ShaderType.FRAGMENT)
.nameMapper(key -> {
var instance = ResourceUtil.toDebugFileNameNoExtension(key.instanceType()
.vertexShader());
var context = ResourceUtil.toDebugFileNameNoExtension(key.contextShader()
.fragmentShader());
return "pipeline/" + pipeline.compilerMarker() + "/" + instance + "_" + context;
var context = key.contextShader()
.nameLowerCase();
return "pipeline/" + pipeline.compilerMarker() + "/" + context;
})
.enableExtension("GL_ARB_conservative_depth")
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.withResource(pipeline.fragmentApiImpl())
.withComponents(fragmentComponents)
.withResource(key -> key.contextShader()
.fragmentShader())
.withResource(pipeline.fragmentMain()))
.preLink((key, program) -> {
program.bindAttribLocation("_flw_a_pos", 0);
@ -71,8 +68,7 @@ public class PipelineCompiler {
pipeline.onLink()
.accept(program);
key.contextShader()
.onLink()
.accept(program);
.onLink(program);
GlProgram.unbind();
})

View file

@ -106,7 +106,7 @@ public class Compile<K> {
private final GlslVersion glslVersion;
private final ShaderType shaderType;
private final List<BiFunction<K, SourceLoader, @Nullable SourceComponent>> fetchers = new ArrayList<>();
private Consumer<Compilation> compilationCallbacks = $ -> {
private BiConsumer<K, Compilation> compilationCallbacks = ($, $$) -> {
};
private Function<K, String> nameMapper = Object::toString;
@ -146,17 +146,17 @@ public class Compile<K> {
return withResource($ -> resourceLocation);
}
public ShaderCompiler<K> onCompile(Consumer<Compilation> cb) {
public ShaderCompiler<K> onCompile(BiConsumer<K, Compilation> cb) {
compilationCallbacks = compilationCallbacks.andThen(cb);
return this;
}
public ShaderCompiler<K> define(String def, int value) {
return onCompile(ctx -> ctx.define(def, String.valueOf(value)));
return onCompile(($, ctx) -> ctx.define(def, String.valueOf(value)));
}
public ShaderCompiler<K> enableExtension(String extension) {
return onCompile(ctx -> ctx.enableExtension(extension));
return onCompile(($, ctx) -> ctx.enableExtension(extension));
}
@Nullable
@ -175,7 +175,8 @@ public class Compile<K> {
return null;
}
return compiler.compile(glslVersion, shaderType, nameMapper.apply(key), compilationCallbacks, components);
Consumer<Compilation> cb = ctx -> compilationCallbacks.accept(key, ctx);
return compiler.compile(glslVersion, shaderType, nameMapper.apply(key), cb, components);
}
}
}

View file

@ -25,7 +25,7 @@ public class ShaderCache {
@Nullable
public GlShader compile(GlslVersion glslVersion, ShaderType shaderType, String name, Consumer<Compilation> callback, List<SourceComponent> sourceComponents) {
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
var key = new ShaderKey(glslVersion, shaderType, name);
var cached = inner.get(key);
if (cached != null) {
return cached.unwrap();
@ -69,6 +69,6 @@ public class ShaderCache {
included.addAll(component.included());
}
private record ShaderKey(GlslVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
private record ShaderKey(GlslVersion glslVersion, ShaderType shaderType, String name) {
}
}

View file

@ -9,6 +9,8 @@ import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;

View file

@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.lib.util.AtomicBitset;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {

View file

@ -4,6 +4,7 @@ import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model,
RenderStage stage) {

View file

@ -6,6 +6,7 @@ import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.embed.GlobalEnvironment;
public record InstancerProviderImpl(AbstractEngine engine, RenderStage renderStage) implements InstancerProvider {
@Override

View file

@ -11,6 +11,10 @@ import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
/**
@ -19,6 +23,7 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
* This map is populated as instancers are requested and contains both initialized and uninitialized instancers.
*/
protected final Map<InstancerKey<?>, N> instancers = new ConcurrentHashMap<>();
protected final ReferenceSet<Environment> environments = new ReferenceLinkedOpenHashSet<>();
/**
* A list of instancers that have not yet been initialized.
* <br>
@ -32,6 +37,12 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
}
public void delete() {
// FIXME: The ownership of environments is a bit weird. Their resources are created and destroyed by the engine,
// but the engine doesn't own the things themselves. This makes it hard for the engine to know when to delete
// environments. For now, we just delete all environments when the engine is deleted, but this is not ideal.
environments.forEach(Environment::delete);
environments.clear();
instancers.clear();
initializationQueue.clear();
}
@ -57,6 +68,8 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
private N createAndDeferInit(InstancerKey<?> key) {
var out = create(key);
environments.add(key.environment());
// Only queue the instancer for initialization if it has anything to render.
if (checkAndWarnEmptyModel(key.model())) {
// Thread safety: this method is called atomically from within computeIfAbsent,

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.engine;
package com.jozufozu.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
@ -12,16 +12,21 @@ import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.ContextShaders;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.BlockAndTintGetter;
public class EmbeddedEnvironment implements Environment, VisualEmbedding {
private final Matrix4f pose = new Matrix4f();
private final Matrix3f normal = new Matrix3f();
private final EmbeddedLightVolume lightVolume = new EmbeddedLightVolume();
private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture();
private final InstancerProvider instancerProvider;
private final AbstractEngine engine;
private final RenderStage renderStage;
@ -45,15 +50,39 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
this.normal.set(normal);
}
@Override
public void light(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
lightVolume.collect(level, minX, minY, minZ, sizeX, sizeY, sizeZ);
}
@Override
public void invalidateLight(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
lightVolume.invalidate(minX, minY, minZ, sizeX, sizeY, sizeZ);
}
@Override
public ContextShader contextShader() {
return ContextShaders.EMBEDDED;
return ContextShader.EMBEDDED;
}
@Override
public void setupDraw(GlProgram drawProgram) {
drawProgram.setVec3("_flw_oneOverLightBoxSize", 1, 1, 1);
drawProgram.setVec3("_flw_lightVolumeMin", 0, 0, 0);
if (!lightVolume.empty()) {
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
lightTexture.ensureCapacity(lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
float oneOverSizeX = 1f / (float) lightTexture.sizeX;
float oneOverSizeY = 1f / (float) lightTexture.sizeY;
float oneOverSizeZ = 1f / (float) lightTexture.sizeZ;
drawProgram.setVec3("_flw_oneOverLightBoxSize", oneOverSizeX, oneOverSizeY, oneOverSizeZ);
drawProgram.setVec3("_flw_lightVolumeMin", lightVolume.minX, lightVolume.minY, lightVolume.minZ);
}
drawProgram.setMat4("_flw_model", pose);
drawProgram.setMat3("_flw_normal", normal);
}
@ -78,4 +107,10 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
public VisualEmbedding createEmbedding() {
return new EmbeddedEnvironment(engine, renderStage);
}
@Override
public void delete() {
lightVolume.delete();
lightTexture.delete();
}
}

View file

@ -0,0 +1,88 @@
package com.jozufozu.flywheel.backend.engine.embed;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT;
import static org.lwjgl.opengl.GL11.GL_UNPACK_ROW_LENGTH;
import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_PIXELS;
import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_ROWS;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glPixelStorei;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D;
import static org.lwjgl.opengl.GL12.GL_TEXTURE_WRAP_R;
import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES;
import static org.lwjgl.opengl.GL12.glTexImage3D;
import static org.lwjgl.opengl.GL12.glTexSubImage3D;
import static org.lwjgl.opengl.GL14.GL_MIRRORED_REPEAT;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.gl.GlTexture;
import net.minecraft.util.Mth;
public class EmbeddedLightTexture {
@Nullable
private GlTexture texture;
public int sizeX;
public int sizeY;
public int sizeZ;
public void bind() {
texture().bind();
}
private GlTexture texture() {
if (texture == null) {
texture = new GlTexture(GL_TEXTURE_3D);
}
return texture;
}
public void ensureCapacity(int sizeX, int sizeY, int sizeZ) {
sizeX = Mth.smallestEncompassingPowerOfTwo(sizeX);
sizeY = Mth.smallestEncompassingPowerOfTwo(sizeY);
sizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ);
if (sizeX > this.sizeX || sizeY > this.sizeY || sizeZ > this.sizeZ) {
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
glTexImage3D(GL_TEXTURE_3D, 0, GL30.GL_RG8, sizeX, sizeY, sizeZ, 0, GL30.GL_RG, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
}
}
public void upload(long ptr, int sizeX, int sizeY, int sizeZ) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, (int) EmbeddedLightVolume.STRIDE);
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, GL30.GL_RG, GL_UNSIGNED_BYTE, ptr);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
}
public void delete() {
if (texture != null) {
texture.delete();
}
}
}

View file

@ -0,0 +1,147 @@
package com.jozufozu.flywheel.backend.engine.embed;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
public class EmbeddedLightVolume {
public static final long STRIDE = Short.BYTES;
public int minX;
public int minY;
public int minZ;
public int sizeX;
public int sizeY;
public int sizeZ;
private final BlockPos.MutableBlockPos scratchPos = new BlockPos.MutableBlockPos();
@Nullable
protected MemoryBlock block;
protected boolean dirty;
public boolean empty() {
return block == null;
}
public void collect(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
maybeExpandForBox(minX, minY, minZ, sizeX, sizeY, sizeZ);
for (int z = minZ; z < minZ + sizeZ; z++) {
for (int y = minY; y < minY + sizeY; y++) {
for (int x = minX; x < minX + sizeX; x++) {
paintLight(level, x, y, z);
}
}
}
markDirty();
}
public void invalidate(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
// TODO: shrink the volume
}
private void paintLight(BlockAndTintGetter level, int x, int y, int z) {
scratchPos.set(x, y, z);
int block = level.getBrightness(LightLayer.BLOCK, scratchPos);
int sky = level.getBrightness(LightLayer.SKY, scratchPos);
long ptr = worldPosToPtr(x, y, z);
MemoryUtil.memPutShort(ptr, (short) ((block << 4) | sky << 12));
}
private void maybeExpandForBox(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
if (block == null) {
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
int volume = sizeX * sizeY * sizeZ;
block = MemoryBlock.malloc(volume * STRIDE);
block.clear();
return;
}
int newMinX = Math.min(this.minX, minX);
int newMinY = Math.min(this.minY, minY);
int newMinZ = Math.min(this.minZ, minZ);
int newSizeX = Math.max(this.minX + this.sizeX, minX + sizeX) - newMinX;
int newSizeY = Math.max(this.minY + this.sizeY, minY + sizeY) - newMinY;
int newSizeZ = Math.max(this.minZ + this.sizeZ, minZ + sizeZ) - newMinZ;
if (newMinX == this.minX && newMinY == this.minY && newMinZ == this.minZ && newSizeX == this.sizeX && newSizeY == this.sizeY && newSizeZ == this.sizeZ) {
return;
}
int newVolume = newSizeX * newSizeY * newSizeZ;
MemoryBlock newBlock = MemoryBlock.malloc(newVolume * STRIDE);
newBlock.clear();
int xOff = newMinX - this.minX;
int yOff = newMinY - this.minY;
int zOff = newMinZ - this.minZ;
for (int z = 0; z < this.sizeZ; z++) {
for (int y = 0; y < this.sizeY; y++) {
for (int x = 0; x < this.sizeX; x++) {
long oldPtr = boxPosToPtr(x, y, z);
long newPtr = newBlock.ptr() + x + xOff + (newSizeX * (y + yOff + (z + zOff) * newSizeY)) * STRIDE;
MemoryUtil.memPutShort(newPtr, MemoryUtil.memGetShort(oldPtr));
}
}
}
this.minX = newMinX;
this.minY = newMinY;
this.minZ = newMinZ;
this.sizeX = newSizeX;
this.sizeY = newSizeY;
this.sizeZ = newSizeZ;
block.free();
block = newBlock;
}
protected long worldPosToPtr(int x, int y, int z) {
return block.ptr() + worldPosToPtrOffset(x, y, z);
}
protected long boxPosToPtr(int x, int y, int z) {
return block.ptr() + boxPosToPtrOffset(x, y, z);
}
protected long worldPosToPtrOffset(int x, int y, int z) {
return boxPosToPtrOffset(x - minX, y - minY, z - minZ);
}
protected long boxPosToPtrOffset(int x, int y, int z) {
return (x + sizeX * (y + z * sizeY)) * STRIDE;
}
public void delete() {
if (block != null) {
block.free();
block = null;
}
}
protected void markDirty() {
this.dirty = true;
}
public long ptr() {
return block.ptr();
}
}

View file

@ -1,4 +1,4 @@
package com.jozufozu.flywheel.backend.engine;
package com.jozufozu.flywheel.backend.engine.embed;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
@ -9,4 +9,6 @@ public interface Environment {
void setupDraw(GlProgram drawProgram);
void setupCull(GlProgram cullProgram);
void delete();
}

View file

@ -1,7 +1,6 @@
package com.jozufozu.flywheel.backend.engine;
package com.jozufozu.flywheel.backend.engine.embed;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.ContextShaders;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
public class GlobalEnvironment implements Environment {
@ -12,7 +11,7 @@ public class GlobalEnvironment implements Environment {
@Override
public ContextShader contextShader() {
return ContextShaders.DEFAULT;
return ContextShader.DEFAULT;
}
@Override
@ -24,4 +23,9 @@ public class GlobalEnvironment implements Environment {
public void setupCull(GlProgram cullProgram) {
cullProgram.setBool("_flw_useEmbeddedModel", false);
}
@Override
public void delete() {
}
}

View file

@ -23,9 +23,9 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.Environment;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.Driver;
import com.jozufozu.flywheel.backend.gl.GlCompat;

View file

@ -16,16 +16,16 @@ import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.ContextShaders;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
import com.jozufozu.flywheel.backend.engine.Environment;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.engine.TextureBinder;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
@ -164,7 +164,7 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
// Set up the crumbling program buffers. Nothing changes here between draws.
var program = cullingGroups.get(instanceTypeEntry.getKey())
.bindWithContextShader(ContextShaders.CRUMBLING);
.bindWithContextShader(ContextShader.CRUMBLING);
program.setSamplerBinding("crumblingTex", Samplers.CRUMBLING);

View file

@ -11,7 +11,7 @@ import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.Environment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
private final long objectStride;

View file

@ -20,7 +20,7 @@ import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.ShaderIndices;
import com.jozufozu.flywheel.backend.compile.ContextShaders;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
@ -203,7 +203,7 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, shader.material());
var program = programs.get(shader.instanceType(), ContextShaders.CRUMBLING);
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING);
program.bind();
uploadMaterialUniform(program, crumblingMaterial);

View file

@ -9,7 +9,7 @@ import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.Environment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.backend.gl.TextureBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferUsage;

View file

@ -2,7 +2,7 @@ package com.jozufozu.flywheel.backend.engine.instancing;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.backend.engine.Environment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
public record ShaderState(Material material, InstanceType<?> instanceType, Environment environment) {
}

View file

@ -23,7 +23,4 @@ public class GlTexture extends GlObject {
GL20.glBindTexture(textureType, 0);
}
public void setParameteri(int parameter, int value) {
GL20.glTexParameteri(textureType, parameter, value);
}
}

View file

@ -6,4 +6,5 @@ public enum DebugMode {
INSTANCE_ID,
LIGHT,
OVERLAY,
LIGHT_VOLUME,
}

View file

@ -1,133 +0,0 @@
package com.jozufozu.flywheel.lib.light;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT;
import static org.lwjgl.opengl.GL11.GL_UNPACK_ROW_LENGTH;
import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_PIXELS;
import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_ROWS;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
import static org.lwjgl.opengl.GL11.glPixelStorei;
import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D;
import static org.lwjgl.opengl.GL12.GL_TEXTURE_WRAP_R;
import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES;
import static org.lwjgl.opengl.GL12.glTexImage3D;
import static org.lwjgl.opengl.GL12.glTexSubImage3D;
import static org.lwjgl.opengl.GL14.GL_MIRRORED_REPEAT;
import org.lwjgl.opengl.GL30;
import com.jozufozu.flywheel.backend.gl.GlTexture;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.lib.box.Box;
import com.jozufozu.flywheel.lib.box.MutableBox;
import net.minecraft.world.level.BlockAndTintGetter;
public class GPULightVolume extends LightVolume {
protected final MutableBox sampleVolume = new MutableBox();
private final GlTexture glTexture;
private final GlTextureUnit textureUnit = GlTextureUnit.T4;
protected boolean bufferDirty;
public GPULightVolume(BlockAndTintGetter level, Box sampleVolume) {
super(level, sampleVolume);
this.sampleVolume.assign(sampleVolume);
glTexture = new GlTexture(GL_TEXTURE_3D);
GlTextureUnit oldState = GlTextureUnit.getActive();
// allocate space for the texture
textureUnit.makeActive();
glTexture.bind();
int sizeX = box.sizeX();
int sizeY = box.sizeY();
int sizeZ = box.sizeZ();
glTexImage3D(GL_TEXTURE_3D, 0, GL30.GL_RG8, sizeX, sizeY, sizeZ, 0, GL30.GL_RG, GL_UNSIGNED_BYTE, 0);
glTexture.setParameteri(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexture.setParameteri(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexture.setParameteri(GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexture.setParameteri(GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
glTexture.setParameteri(GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexture.unbind();
oldState.makeActive();
}
@Override
protected void setBox(Box box) {
this.box.assign(box);
this.box.nextPowerOf2Centered();
// called during super ctor
if (sampleVolume != null) this.sampleVolume.assign(box);
}
public void bind() {
// just in case something goes wrong, or we accidentally call this before this volume is properly disposed of.
if (lightData == null || lightData.size() == 0) return;
textureUnit.makeActive();
glTexture.bind();
uploadTexture();
}
private void uploadTexture() {
if (bufferDirty) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2); // we use 2 bytes per texel
int sizeX = box.sizeX();
int sizeY = box.sizeY();
int sizeZ = box.sizeZ();
glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, GL30.GL_RG, GL_UNSIGNED_BYTE, lightData.ptr());
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
bufferDirty = false;
}
}
public void unbind() {
glTexture.unbind();
}
@Override
public void delete() {
super.delete();
glTexture.delete();
}
public void move(Box newSampleVolume) {
if (lightData == null) return;
if (box.contains(newSampleVolume)) {
sampleVolume.assign(newSampleVolume);
initialize();
} else {
super.move(newSampleVolume);
}
}
@Override
public Box getVolume() {
return sampleVolume;
}
@Override
protected void markDirty() {
this.bufferDirty = true;
}
}

View file

@ -6,19 +6,42 @@
layout (depth_greater) out float gl_FragDepth;
#endif
out vec4 _flw_outputColor;
#ifdef _FLW_CRUMBLING
uniform sampler2D _flw_crumblingTex;
in vec2 _flw_crumblingTexCoord;
#endif
#ifdef _FLW_EMBEDDED
uniform sampler3D _flw_lightVolume;
in vec3 _flw_lightVolumeCoord;
#endif
in vec4 _flw_debugColor;
out vec4 _flw_outputColor;
void _flw_main() {
flw_sampleColor = texture(flw_diffuseTex, flw_vertexTexCoord);
flw_fragColor = flw_vertexColor * flw_sampleColor;
flw_fragOverlay = flw_vertexOverlay;
flw_fragLight = flw_vertexLight;
flw_beginFragment();
#ifdef _FLW_EMBEDDED
flw_fragLight = max(flw_fragLight, texture(_flw_lightVolume, _flw_lightVolumeCoord).rg);
#endif
flw_materialFragment();
flw_endFragment();
#ifdef _FLW_CRUMBLING
vec4 crumblingSampleColor = texture(_flw_crumblingTex, _flw_crumblingTexCoord);
// Make the crumbling overlay transparent when the fragment color after the material shader is transparent.
flw_fragColor.rgb = crumblingSampleColor.rgb;
flw_fragColor.a *= crumblingSampleColor.a;
#endif
vec4 color = flw_fragColor;

View file

@ -23,12 +23,97 @@ vec4 _flw_id2Color(in uint id) {
out vec4 _flw_debugColor;
#ifdef _FLW_CRUMBLING
out vec2 _flw_crumblingTexCoord;
const int DOWN = 0;
const int UP = 1;
const int NORTH = 2;
const int SOUTH = 3;
const int WEST = 4;
const int EAST = 5;
// based on net.minecraftforge.client.ForgeHooksClient.getNearestStable
int getNearestFacing(vec3 normal) {
float maxAlignment = -2;
int face = 2;
// Calculate the alignment of the normal vector with each axis.
// Note that `-dot(normal, axis) == dot(normal, -axis)`.
vec3 alignment = vec3(
dot(normal, vec3(1., 0., 0.)),
dot(normal, vec3(0., 1., 0.)),
dot(normal, vec3(0., 0., 1.))
);
if (-alignment.y > maxAlignment) {
maxAlignment = -alignment.y;
face = DOWN;
}
if (alignment.y > maxAlignment) {
maxAlignment = alignment.y;
face = UP;
}
if (-alignment.z > maxAlignment) {
maxAlignment = -alignment.z;
face = NORTH;
}
if (alignment.z > maxAlignment) {
maxAlignment = alignment.z;
face = SOUTH;
}
if (-alignment.x > maxAlignment) {
maxAlignment = -alignment.x;
face = WEST;
}
if (alignment.x > maxAlignment) {
maxAlignment = alignment.x;
face = EAST;
}
return face;
}
vec2 getCrumblingTexCoord() {
switch (getNearestFacing(flw_vertexNormal)) {
case DOWN: return vec2(flw_vertexPos.x, -flw_vertexPos.z);
case UP: return vec2(flw_vertexPos.x, flw_vertexPos.z);
case NORTH: return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
case SOUTH: return vec2(flw_vertexPos.x, -flw_vertexPos.y);
case WEST: return vec2(-flw_vertexPos.z, -flw_vertexPos.y);
case EAST: return vec2(flw_vertexPos.z, -flw_vertexPos.y);
}
// default to north
return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
}
#endif
#ifdef _FLW_EMBEDDED
uniform vec3 _flw_oneOverLightBoxSize;
uniform vec3 _flw_lightVolumeMin;
uniform mat4 _flw_model;
uniform mat3 _flw_normal;
out vec3 _flw_lightVolumeCoord;
#endif
void _flw_main(in FlwInstance instance, in uint stableInstanceID) {
_flw_layoutVertex();
flw_beginVertex();
flw_instanceVertex(instance);
flw_materialVertex();
flw_endVertex();
#ifdef _FLW_CRUMBLING
_flw_crumblingTexCoord = getCrumblingTexCoord();
#endif
#ifdef _FLW_EMBEDDED
flw_vertexPos = _flw_model * flw_vertexPos;
flw_vertexNormal = _flw_normal * flw_vertexNormal;
_flw_lightVolumeCoord = (flw_vertexPos.xyz - _flw_lightVolumeMin) * _flw_oneOverLightBoxSize;
#endif
flw_vertexNormal = normalize(flw_vertexNormal);
@ -52,5 +137,10 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) {
case 4u:
_flw_debugColor = vec4(flw_vertexOverlay / 16., 0., 1.);
break;
#ifdef _FLW_LIGHT_VOLUME
case 5u:
_flw_debugColor = vec4(_flw_lightVolumeCoord, 1.);
break;
#endif
}
}

View file

@ -1,16 +0,0 @@
uniform sampler2D _flw_crumblingTex;
in vec2 crumblingTexCoord;
vec4 crumblingSampleColor;
void flw_beginFragment() {
}
void flw_endFragment() {
crumblingSampleColor = texture(_flw_crumblingTex, crumblingTexCoord);
// Make the crumbling overlay transparent when the fragment color after the material shader is transparent.
flw_fragColor.rgb = crumblingSampleColor.rgb;
flw_fragColor.a *= crumblingSampleColor.a;
}

View file

@ -1,70 +0,0 @@
out vec2 crumblingTexCoord;
const int DOWN = 0;
const int UP = 1;
const int NORTH = 2;
const int SOUTH = 3;
const int WEST = 4;
const int EAST = 5;
// based on net.minecraftforge.client.ForgeHooksClient.getNearestStable
int getNearestFacing(vec3 normal) {
float maxAlignment = -2;
int face = 2;
// Calculate the alignment of the normal vector with each axis.
// Note that `-dot(normal, axis) == dot(normal, -axis)`.
vec3 alignment = vec3(
dot(normal, vec3(1., 0., 0.)),
dot(normal, vec3(0., 1., 0.)),
dot(normal, vec3(0., 0., 1.))
);
if (-alignment.y > maxAlignment) {
maxAlignment = -alignment.y;
face = DOWN;
}
if (alignment.y > maxAlignment) {
maxAlignment = alignment.y;
face = UP;
}
if (-alignment.z > maxAlignment) {
maxAlignment = -alignment.z;
face = NORTH;
}
if (alignment.z > maxAlignment) {
maxAlignment = alignment.z;
face = SOUTH;
}
if (-alignment.x > maxAlignment) {
maxAlignment = -alignment.x;
face = WEST;
}
if (alignment.x > maxAlignment) {
maxAlignment = alignment.x;
face = EAST;
}
return face;
}
vec2 getCrumblingTexCoord() {
switch (getNearestFacing(flw_vertexNormal)) {
case DOWN: return vec2(flw_vertexPos.x, -flw_vertexPos.z);
case UP: return vec2(flw_vertexPos.x, flw_vertexPos.z);
case NORTH: return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
case SOUTH: return vec2(flw_vertexPos.x, -flw_vertexPos.y);
case WEST: return vec2(-flw_vertexPos.z, -flw_vertexPos.y);
case EAST: return vec2(flw_vertexPos.z, -flw_vertexPos.y);
}
// default to north
return vec2(-flw_vertexPos.x, -flw_vertexPos.y);
}
void flw_beginVertex() {
}
void flw_endVertex() {
crumblingTexCoord = getCrumblingTexCoord();
}

View file

@ -1,5 +0,0 @@
void flw_beginFragment() {
}
void flw_endFragment() {
}

View file

@ -1,5 +0,0 @@
void flw_beginVertex() {
}
void flw_endVertex() {
}

View file

@ -1,10 +0,0 @@
uniform sampler3D _flw_lightVolume;
in vec3 _flw_lightVolumeCoord;
void flw_beginFragment() {
flw_fragLight = max(flw_fragLight, texture(_flw_lightVolume, _flw_lightVolumeCoord).rg);
}
void flw_endFragment() {
}

View file

@ -1,16 +0,0 @@
uniform vec3 _flw_oneOverLightBoxSize;
uniform vec3 _flw_lightVolumeMin;
uniform mat4 _flw_model;
uniform mat3 _flw_normal;
out vec3 _flw_lightVolumeCoord;
void flw_beginVertex() {
}
void flw_endVertex() {
_flw_lightVolumeCoord = (flw_vertexPos.xyz - _flw_lightVolumeMin) * _flw_oneOverLightBoxSize;
flw_vertexPos = _flw_model * flw_vertexPos;
flw_vertexNormal = _flw_normal * flw_vertexNormal;
}