Skip to content

Commit

Permalink
Installing a light switch
Browse files Browse the repository at this point in the history
- Guard 3 different flw_light impls via #define
- Guard the inner face correction behind another #define
- Add LightSmoothness enum to decide which flw_light impl to use
- Make LightSmoothness configurable via a new BackendConfig
- Add command to switch LightSmoothness on the fly
- Note: currently requires a resource reload so we don't need to compile
  4x as many shaders
  • Loading branch information
Jozufozu committed Aug 9, 2024
1 parent 3dc4cf0 commit 744c40a
Show file tree
Hide file tree
Showing 16 changed files with 354 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.engine_room.flywheel.backend;

import dev.engine_room.flywheel.backend.compile.LightSmoothness;

public interface BackendConfig {
BackendConfig INSTANCE = FlwBackendXplat.INSTANCE.getConfig();

/**
* How smooth/accurate our flw_light impl is.
*
* <p>This makes more sense here as a backend-specific config because it's tightly coupled to
* our backend's implementation. 3rd party backend may have different approaches and configurations.
*
* @return The current light smoothness setting.
*/
LightSmoothness lightSmoothness();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ 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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dev.engine_room.flywheel.backend;

import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.minecraft.commands.arguments.StringRepresentableArgument;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;

public class LightSmoothnessArgument extends StringRepresentableArgument<LightSmoothness> {
public static final LightSmoothnessArgument INSTANCE = new LightSmoothnessArgument();
public static final SingletonArgumentInfo<LightSmoothnessArgument> INFO = SingletonArgumentInfo.contextFree(() -> INSTANCE);

public LightSmoothnessArgument() {
super(LightSmoothness.CODEC, LightSmoothness::values);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dev.engine_room.flywheel.backend.compile;

import java.util.Locale;

import com.mojang.serialization.Codec;

import dev.engine_room.flywheel.backend.compile.core.Compilation;
import net.minecraft.util.StringRepresentable;

public enum LightSmoothness implements StringRepresentable {
FLAT(0, false),
TRI_LINEAR(1, false),
SMOOTH(2, false),
SMOOTH_INNER_FACE_CORRECTED(2, true),
;

public static final Codec<LightSmoothness> CODEC = StringRepresentable.fromEnum(LightSmoothness::values);

private final int smoothnessDefine;
private final boolean innerFaceCorrection;

LightSmoothness(int smoothnessDefine, boolean innerFaceCorrection) {
this.smoothnessDefine = smoothnessDefine;
this.innerFaceCorrection = innerFaceCorrection;
}

public void onCompile(Compilation comp) {
comp.define("_FLW_LIGHT_SMOOTHNESS", Integer.toString(smoothnessDefine));
if (innerFaceCorrection) {
comp.define("_FLW_INNER_FACE_CORRECTION");
}
}

@Override
public String getSerializedName() {
return name().toLowerCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;

import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.BackendConfig;
import dev.engine_room.flywheel.backend.InternalVertex;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.component.InstanceStructComponent;
Expand All @@ -25,6 +26,9 @@ public final class PipelineCompiler {
private static final ResourceLocation API_IMPL_FRAG = Flywheel.rl("internal/api_impl.frag");

static CompilationHarness<PipelineProgramKey> create(ShaderSources sources, Pipeline pipeline, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents, Collection<String> extensions) {
// We could technically compile every version of light smoothness ahead of time,
// but that seems unnecessary as I doubt most folks will be changing this option often.
var lightSmoothness = BackendConfig.INSTANCE.lightSmoothness();
return PIPELINE.program()
.link(PIPELINE.shader(GlCompat.MAX_GLSL_VERSION, ShaderType.VERTEX)
.nameMapper(key -> {
Expand All @@ -38,6 +42,7 @@ static CompilationHarness<PipelineProgramKey> create(ShaderSources sources, Pipe
.requireExtensions(extensions)
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.onCompile((key, comp) -> lightSmoothness.onCompile(comp))
.withResource(API_IMPL_VERT)
.withComponent(key -> new InstanceStructComponent(key.instanceType()))
.withResource(key -> key.instanceType()
Expand All @@ -57,6 +62,7 @@ static CompilationHarness<PipelineProgramKey> create(ShaderSources sources, Pipe
.enableExtension("GL_ARB_conservative_depth")
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.onCompile((key, comp) -> lightSmoothness.onCompile(comp))
.withResource(API_IMPL_FRAG)
.withComponents(fragmentComponents)
.withResource(pipeline.fragmentMain()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
}

#define _flw_index3x3x3(x, y, z) ((x) + (z) * 3u + (y) * 9u)
#define _flw_index3x3x3v(p) _flw_index3x3x3((p.x), (p.y), (p.z))
#define _flw_index3x3x3v(p) _flw_index3x3x3((p).x, (p).y, (p).z)
#define _flw_validCountToAo(validCount) (1. - (4. - (validCount)) * 0.2)

/// Calculate the light for a direction by averaging the light at the corners of the block.
Expand Down Expand Up @@ -196,23 +196,24 @@ vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uvec3 c00, uvec3
summed[i] = lights[ic00 + corner] + lights[ic01 + corner] + lights[ic10 + corner] + lights[ic11 + corner];
}

// The final light and AO value for each corner.
// The final light and number of valid blocks for each corner.
vec3[8] adjusted;
for (uint i = 0; i < 8; i++) {
uint validCount = (summed[i] >> 20u) & 0x3FFu;
// Always use the AO from the actual corner
adjusted[i].z = float(validCount);

#ifdef _FLW_INNER_FACE_CORRECTION
// If the current corner has no valid blocks, use the opposite
// corner's light based on which direction we're evaluating.
// Because of how our corners are indexed, moving along one axis is the same as flipping a bit.
uint corner = summed[(validCount == 0 ? i ^ oppositeMask : i)];
uint cornerIndex = (summed[i] & 0xFFF00000u) == 0u ? i ^ oppositeMask : i;
#else
uint cornerIndex = i;
#endif
uint corner = summed[cornerIndex];

// Still need to unpack all 3 fields of the maybe opposite corner so we can...
uvec3 unpacked = uvec3(corner, corner >> 10u, corner >> 20u) & 0x3FFu;
uvec3 unpacked = uvec3(corner & 0x3FFu, (corner >> 10u) & 0x3FFu, corner >> 20u);

// ...normalize by the number of valid blocks.
// Normalize by the number of valid blocks.
adjusted[i].xy = vec2(unpacked.xy) * normalizers[unpacked.z];
adjusted[i].z = float(unpacked.z);
}

// Trilinear interpolation, including valid count
Expand All @@ -234,7 +235,6 @@ vec3 _flw_lightForDirection(uint[27] lights, vec3 interpolant, uvec3 c00, uvec3
return light;
}

// TODO: Add config for light smoothness. Should work at a compile flag level
bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
// Always use the section of the block we are contained in to ensure accuracy.
// We don't want to interpolate between sections, but also we might not be able
Expand All @@ -251,6 +251,40 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
// The block's position in the section adjusted into 18x18x18 space
ivec3 blockInSectionPos = (blockPos & 0xF) + 1;

#if _FLW_LIGHT_SMOOTHNESS == 1// Directly trilerp as if sampling a texture

// The lowest corner of the 2x2x2 area we'll be trilinear interpolating.
// The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are.
uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5));

// The distance our fragment is from the center of the lowest corner.
vec3 interpolant = fract(worldPos - 0.5);

// Fetch everything for trilinear interpolation
// Hypothetically we could re-order these and do some calculations in-between fetches
// to help with latency hiding, but the compiler should be able to do that for us.
vec2 light000 = vec2(_flw_lightAt(sectionOffset, lowestCorner));
vec2 light100 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)));
vec2 light001 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)));
vec2 light101 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)));
vec2 light010 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)));
vec2 light110 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)));
vec2 light011 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)));
vec2 light111 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)));

vec2 light00 = mix(light000, light001, interpolant.z);
vec2 light01 = mix(light010, light011, interpolant.z);
vec2 light10 = mix(light100, light101, interpolant.z);
vec2 light11 = mix(light110, light111, interpolant.z);

vec2 light0 = mix(light00, light01, interpolant.y);
vec2 light1 = mix(light10, light11, interpolant.y);

light.light = mix(light0, light1, interpolant.x) / 15.;
light.ao = 1.;

#elif _FLW_LIGHT_SMOOTHNESS == 2// Lighting and AO accurate to chunk baking

uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos);

if (solid == _FLW_COMPLETELY_SOLID) {
Expand Down Expand Up @@ -301,6 +335,13 @@ bool flw_light(vec3 worldPos, vec3 normal, out FlwLightAo light) {
light.light = lightAo.xy;
light.ao = lightAo.z;

#else// Entirely flat lighting, the lowest setting and a fallback in case an invalid option is set

light.light = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) / 15.;
light.ao = 1.;

#endif

return true;
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,33 @@
import com.mojang.math.Axis;

import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.instance.InstanceTypes;
import dev.engine_room.flywheel.lib.instance.TransformedInstance;
import dev.engine_room.flywheel.lib.material.CutoutShaders;
import dev.engine_room.flywheel.lib.material.LightShaders;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.model.ModelCache;
import dev.engine_room.flywheel.lib.model.SingleMeshModel;
import dev.engine_room.flywheel.lib.model.part.ModelPartConverter;
import dev.engine_room.flywheel.lib.transform.TransformStack;
import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual;
import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.Material;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.level.block.ShulkerBoxBlock;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;

public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockEntity> implements SimpleDynamicVisual {
public class ShulkerBoxVisual extends AbstractBlockEntityVisual<ShulkerBoxBlockEntity> implements SimpleDynamicVisual, ShaderLightVisual {
private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.light(LightShaders.SMOOTH)
.texture(Sheets.SHULKER_SHEET)
.mipmap(false)
.backfaceCulling(false)
Expand Down Expand Up @@ -67,6 +72,7 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt
.translate(getVisualPosition())
.translate(0.5f)
.scale(0.9995f)
.scale(11f)
.rotate(rotation)
.scale(1, -1, -1)
.translateY(-1);
Expand Down Expand Up @@ -120,9 +126,26 @@ public void beginFrame(Context context) {
stack.popPose();
}

@Override
public void setSectionCollector(SectionCollector sectionCollector) {

var center = SectionPos.asLong(pos);
var out = new LongArraySet();

for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
out.add(SectionPos.offset(center, x, y, z));
}
}
}

sectionCollector.sections(out);
}

@Override
public void updateLight(float partialTick) {
relight(base, lid);
// relight(base, lid);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package dev.engine_room.flywheel.backend;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.nio.file.Path;
import java.util.Locale;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;

import dev.engine_room.flywheel.backend.compile.LightSmoothness;
import net.fabricmc.loader.api.FabricLoader;

public class FabricBackendConfig implements BackendConfig {

public static final Path PATH = FabricLoader.getInstance()
.getConfigDir()
.resolve("flywheel-backend.json");

public static final FabricBackendConfig INSTANCE = new FabricBackendConfig(PATH.toFile());

private static final Gson GSON = new GsonBuilder().setPrettyPrinting()
.create();

private final File file;

public LightSmoothness lightSmoothness = LightSmoothness.SMOOTH;

public FabricBackendConfig(File file) {
this.file = file;
}

@Override
public LightSmoothness lightSmoothness() {
return lightSmoothness;
}

public void load() {
if (file.exists()) {
try (FileReader reader = new FileReader(file)) {
fromJson(JsonParser.parseReader(reader));
} catch (Exception e) {
FlwBackend.LOGGER.warn("Could not load config from file '{}'", file.getAbsolutePath(), e);
}
}
// In case we found an error in the config file, immediately save to fix it.
save();
}

public void save() {
try (FileWriter writer = new FileWriter(file)) {
GSON.toJson(toJson(), writer);
} catch (Exception e) {
FlwBackend.LOGGER.warn("Could not save config to file '{}'", file.getAbsolutePath(), e);
}
}

public void fromJson(JsonElement json) {
if (!(json instanceof JsonObject object)) {
FlwBackend.LOGGER.warn("Config JSON must be an object");
lightSmoothness = LightSmoothness.SMOOTH;
return;
}

readLightSmoothness(object);
}

private void readLightSmoothness(JsonObject object) {
var backendJson = object.get("lightSmoothness");
String msg = null;

if (backendJson instanceof JsonPrimitive primitive && primitive.isString()) {
var value = primitive.getAsString();

for (var item : LightSmoothness.values()) {
if (item.name()
.equalsIgnoreCase(value)) {
lightSmoothness = item;
return;
}
}

msg = "Unknown 'lightSmoothness' value: " + value;
} else if (backendJson != null) {
msg = "'lightSmoothness' value must be a string";
}

// Don't log an error if the field is missing.
if (msg != null) {
FlwBackend.LOGGER.warn(msg);
}
lightSmoothness = LightSmoothness.SMOOTH;
}

public JsonObject toJson() {
JsonObject object = new JsonObject();
object.addProperty("lightSmoothness", lightSmoothness.toString()
.toLowerCase(Locale.ROOT));
return object;
}
}
Loading

0 comments on commit 744c40a

Please sign in to comment.