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

# Conflicts:
#	common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHandler.java
#	common/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplat.java
#	common/src/main/java/dev/engine_room/flywheel/impl/compat/SodiumCompat.java
#	common/src/main/java/dev/engine_room/flywheel/impl/mixin/BlockEntityTypeMixin.java
#	fabric/build.gradle.kts
#	fabric/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java
#	fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java
#	fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java
#	fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java
#	fabric/src/main/resources/fabric.mod.json
#	forge/build.gradle.kts
#	forge/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java
#	forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java
#	forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java
#	forge/src/main/resources/META-INF/neoforge.mods.toml
#	gradle.properties
#	gradle/wrapper/gradle-wrapper.properties
This commit is contained in:
IThundxr 2024-10-29 17:38:23 -04:00
commit 07e66bb299
Failed to generate hash of commit
35 changed files with 591 additions and 196 deletions

View file

@ -23,16 +23,11 @@ open class PlatformExtension(val project: Project) {
var apiArtifactId: String = "flywheel-${project.name}-api-${project.property("artifact_minecraft_version")}"
private val sources = mutableSetOf<SourceSet>()
private val commonSourceSets: SourceSetContainer by lazy { commonProject.the<SourceSetContainer>() }
fun sources(vararg sourceSets: SourceSet) {
this.sources.addAll(sourceSets)
}
fun setupLoomMod() {
fun setupLoomMod(vararg sourceSets: SourceSet) {
project.the<LoomGradleExtensionAPI>().mods.maybeCreate("main").apply {
sources.forEach(::sourceSet)
sourceSets.forEach(::sourceSet)
}
}
@ -61,13 +56,13 @@ open class PlatformExtension(val project: Project) {
}
}
fun compileWithCommonSourceSets() {
fun compileWithCommonSourceSets(vararg sourceSets: SourceSet) {
project.tasks.apply {
withType<JavaCompile>().configureEach {
JarTaskSet.excludeDuplicatePackageInfos(this)
}
sources.forEach {
sourceSets.forEach {
val commonSourceSet = commonSourceSets.named(it.name).get()
named<JavaCompile>(it.compileJavaTaskName).configure {
@ -80,10 +75,10 @@ open class PlatformExtension(val project: Project) {
}
}
fun setupFatJar() {
fun setupFatJar(vararg sourceSets: SourceSet) {
project.tasks.apply {
val extraSourceSets = sources.filter { it.name != "main" }.toList()
val commonSources = sources.map { commonSourceSets.named(it.name).get() }
val extraSourceSets = sourceSets.filter { it.name != "main" }.toList()
val commonSources = sourceSets.map { commonSourceSets.named(it.name).get() }
named<Jar>("jar").configure {
extraSourceSets.forEach { from(it.output) }

View file

@ -25,7 +25,7 @@ open class TransitiveSourceSetsExtension(private val project: Project) {
fun createCompileConfigurations() {
val configs = transitives.mapValues { (sourceSet, _) ->
project.configurations.create("for${sourceSet.name.capitalize()}") {
project.configurations.create("for${sourceSet.name.replaceFirstChar { it.uppercase() }}") {
isCanBeConsumed = true
isCanBeResolved = false
}
@ -43,7 +43,7 @@ open class TransitiveSourceSetsExtension(private val project: Project) {
fun createRuntimeConfigurations() {
val configs = transitives.mapValues { (sourceSet, _) ->
project.configurations.create("run${sourceSet.name.capitalize()}") {
project.configurations.create("run${sourceSet.name.replaceFirstChar { it.uppercase() }}") {
isCanBeConsumed = true
isCanBeResolved = false
}

View file

@ -9,6 +9,7 @@ plugins {
val api = sourceSets.create("api")
val lib = sourceSets.create("lib")
val backend = sourceSets.create("backend")
val stubs = sourceSets.create("stubs")
val main = sourceSets.getByName("main")
transitiveSourceSets {
@ -25,8 +26,11 @@ transitiveSourceSets {
rootCompile()
compile(api, lib)
}
sourceSet(stubs) {
rootCompile()
}
sourceSet(main) {
compile(api, lib, backend)
compile(api, lib, backend, stubs)
}
sourceSet(sourceSets.getByName("test")) {
implementation(api, lib, backend)
@ -42,6 +46,7 @@ jarSets {
outgoing("commonApiOnly", api)
outgoing("commonLib", lib)
outgoing("commonBackend", backend)
outgoing("commonStubs", stubs)
outgoing("commonImpl", main)
// For publishing.

View file

@ -9,7 +9,7 @@ import dev.engine_room.flywheel.backend.engine.indirect.IndirectDrawManager;
import dev.engine_room.flywheel.backend.engine.instancing.InstancedDrawManager;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import dev.engine_room.flywheel.lib.util.ShadersModHandler;
import dev.engine_room.flywheel.lib.util.ShadersModHelper;
public final class Backends {
/**
@ -18,7 +18,7 @@ public final class Backends {
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("instancing"));
/**
@ -27,7 +27,7 @@ public final class Backends {
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.priority(1000)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse())
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("indirect"));
private Backends() {

View file

@ -1,7 +1,10 @@
package dev.engine_room.flywheel.backend.compile;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
@ -30,7 +33,7 @@ import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public final class PipelineCompiler {
private static final List<PipelineCompiler> ALL = List.of();
private static final Set<PipelineCompiler> ALL = Collections.newSetFromMap(new WeakHashMap<>());
private static final Compile<PipelineProgramKey> PIPELINE = new Compile<>();
@ -44,6 +47,7 @@ public final class PipelineCompiler {
public PipelineCompiler(CompilationHarness<PipelineProgramKey> harness) {
this.harness = harness;
ALL.add(this);
}
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) {
@ -57,15 +61,12 @@ public final class PipelineCompiler {
MaterialShaderIndices.fogSources()
.index(fog.source());
boolean useCutout = cutout != CutoutShaders.OFF;
if (useCutout) {
// Same thing for cutout.
// Add OFF to the index here anyway to ensure MaterialEncoder doesn't deleteAll at an inappropriate time.
MaterialShaderIndices.cutoutSources()
.index(cutout.source());
}
return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, useCutout, FrameUniforms.debugOn()));
return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, cutout != CutoutShaders.OFF, FrameUniforms.debugOn()));
}
public void delete() {

View file

@ -24,4 +24,12 @@ public interface FlwLibLink {
void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, int color);
Deque<PoseStack.Pose> getPoseStack(PoseStack stack);
boolean isIrisLoaded();
boolean isOptifineInstalled();
boolean isShaderPackInUse();
boolean isRenderingShadowPass();
}

View file

@ -1,13 +1,11 @@
package dev.engine_room.flywheel.lib.internal;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.api.internal.DependencyInjection;
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.MultiBlockModelBuilder;
import dev.engine_room.flywheel.lib.util.ShadersModHandler;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
@ -29,7 +27,4 @@ public interface FlwLibXplat {
BlockModelBuilder createBlockModelBuilder(BlockState state);
MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable<BlockPos> positions);
@Nullable
ShadersModHandler.InternalHandler createIrisHandler();
}

View file

@ -3,7 +3,7 @@ package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.LightShader;
public class LightShaders {
public final class LightShaders {
public static final LightShader SMOOTH_WHEN_EMBEDDED = new SimpleLightShader(Flywheel.rl("light/smooth_when_embedded.glsl"));
public static final LightShader SMOOTH = new SimpleLightShader(Flywheel.rl("light/smooth.glsl"));
public static final LightShader FLAT = new SimpleLightShader(Flywheel.rl("light/flat.glsl"));

View file

@ -178,6 +178,7 @@ public class SimpleMaterial implements Material {
shaders = material.shaders();
fog = material.fog();
cutout = material.cutout();
light = material.light();
texture = material.texture();
blur = material.blur();
mipmap = material.mipmap();

View file

@ -1,128 +0,0 @@
package dev.engine_room.flywheel.lib.util;
import java.lang.reflect.Field;
import java.util.function.BooleanSupplier;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
import dev.engine_room.flywheel.lib.internal.FlwLibXplat;
public final class ShadersModHandler {
private static final String OPTIFINE_ROOT_PACKAGE = "net.optifine";
private static final boolean IS_IRIS_LOADED;
private static final boolean IS_OPTIFINE_INSTALLED;
private static final InternalHandler INTERNAL_HANDLER;
static {
InternalHandler irisHandler = FlwLibXplat.INSTANCE
.createIrisHandler();
IS_IRIS_LOADED = irisHandler != null;
Package optifinePackage = Package.getPackage(OPTIFINE_ROOT_PACKAGE);
IS_OPTIFINE_INSTALLED = optifinePackage != null;
// OptiFine and Iris are assumed to be mutually exclusive
if (IS_IRIS_LOADED) {
FlwLibLink.INSTANCE.getLogger().debug("Iris detected.");
INTERNAL_HANDLER = irisHandler;
} else if (IS_OPTIFINE_INSTALLED) {
FlwLibLink.INSTANCE.getLogger().debug("OptiFine detected.");
INTERNAL_HANDLER = new OptifineHandler();
} else {
FlwLibLink.INSTANCE.getLogger().debug("No shaders mod detected.");
INTERNAL_HANDLER = new InternalHandler() {};
}
}
private ShadersModHandler() {
}
public static boolean isIrisLoaded() {
return IS_IRIS_LOADED;
}
public static boolean isOptifineInstalled() {
return IS_OPTIFINE_INSTALLED;
}
public static boolean isShaderPackInUse() {
return INTERNAL_HANDLER.isShaderPackInUse();
}
public static boolean isRenderingShadowPass() {
return INTERNAL_HANDLER.isRenderingShadowPass();
}
@ApiStatus.Internal
public static void init() {
}
@ApiStatus.Internal
public interface InternalHandler {
default boolean isShaderPackInUse() {
return false;
}
default boolean isRenderingShadowPass() {
return false;
}
}
// evil reflection
private static class OptifineHandler implements InternalHandler {
private final BooleanSupplier shadersEnabledSupplier;
private final BooleanSupplier shadowPassSupplier;
private OptifineHandler() {
shadersEnabledSupplier = createShadersEnabledSupplier();
shadowPassSupplier = createShadowPassSupplier();
}
@Override
public boolean isShaderPackInUse() {
return shadersEnabledSupplier.getAsBoolean();
}
@Override
public boolean isRenderingShadowPass() {
return shadowPassSupplier.getAsBoolean();
}
private static BooleanSupplier createShadersEnabledSupplier() {
try {
Class<?> ofShaders = Class.forName("net.optifine.shaders.Shaders");
Field field = ofShaders.getDeclaredField("shaderPackLoaded");
field.setAccessible(true);
return () -> {
try {
return field.getBoolean(null);
} catch (IllegalAccessException e) {
return false;
}
};
} catch (Exception e) {
return () -> false;
}
}
private static BooleanSupplier createShadowPassSupplier() {
try {
Class<?> ofShaders = Class.forName("net.optifine.shaders.Shaders");
Field field = ofShaders.getDeclaredField("isShadowPass");
field.setAccessible(true);
return () -> {
try {
return field.getBoolean(null);
} catch (IllegalAccessException e) {
return false;
}
};
} catch (Exception e) {
return () -> false;
}
}
}
}

View file

@ -0,0 +1,19 @@
package dev.engine_room.flywheel.lib.util;
import dev.engine_room.flywheel.lib.internal.FlwLibLink;
public final class ShadersModHelper {
public static final boolean IS_IRIS_LOADED = FlwLibLink.INSTANCE.isIrisLoaded();
public static final boolean IS_OPTIFINE_INSTALLED = FlwLibLink.INSTANCE.isOptifineInstalled();
private ShadersModHelper() {
}
public static boolean isShaderPackInUse() {
return FlwLibLink.INSTANCE.isShaderPackInUse();
}
public static boolean isRenderingShadowPass() {
return FlwLibLink.INSTANCE.isRenderingShadowPass();
}
}

View file

@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.impl.registry.IdRegistryImpl;
import dev.engine_room.flywheel.lib.util.ShadersModHandler;
import dev.engine_room.flywheel.vanilla.VanillaVisuals;
public final class FlwImpl {
@ -21,7 +20,6 @@ public final class FlwImpl {
BackendManagerImpl.init();
// lib
ShadersModHandler.init();
// backend
FlwBackend.init(FlwConfig.INSTANCE.backendConfig());

View file

@ -6,11 +6,15 @@ import net.minecraft.client.multiplayer.ClientLevel;
public interface FlwImplXplat {
FlwImplXplat INSTANCE = DependencyInjection.load(FlwImplXplat.class, "dev.engine_room.flywheel.impl.FlwImplXplatImpl");
boolean getModLoaded(String modId);
boolean isModLoaded(String modId);
void dispatchReloadLevelRendererEvent(ClientLevel level);
String getVersionStr();
FlwConfig getConfig();
boolean useSodium0_6Compat();
boolean useIrisCompat();
}

View file

@ -8,6 +8,8 @@ import org.slf4j.Logger;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dev.engine_room.flywheel.impl.compat.IrisCompat;
import dev.engine_room.flywheel.impl.compat.OptifineCompat;
import dev.engine_room.flywheel.impl.extension.PoseStackExtension;
import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor;
import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor;
@ -40,4 +42,36 @@ public class FlwLibLinkImpl implements FlwLibLink {
public Deque<PoseStack.Pose> getPoseStack(PoseStack stack) {
return ((PoseStackAccessor) stack).flywheel$getPoseStack();
}
@Override
public boolean isIrisLoaded() {
return IrisCompat.ACTIVE;
}
@Override
public boolean isOptifineInstalled() {
return OptifineCompat.IS_INSTALLED;
}
@Override
public boolean isShaderPackInUse() {
if (IrisCompat.ACTIVE) {
return IrisCompat.isShaderPackInUse();
} else if (OptifineCompat.IS_INSTALLED) {
return OptifineCompat.isShaderPackInUse();
} else {
return false;
}
}
@Override
public boolean isRenderingShadowPass() {
if (IrisCompat.ACTIVE) {
return IrisCompat.isRenderingShadowPass();
} else if (OptifineCompat.IS_INSTALLED) {
return OptifineCompat.isRenderingShadowPass();
} else {
return false;
}
}
}

View file

@ -0,0 +1,18 @@
package dev.engine_room.flywheel.impl.compat;
import dev.engine_room.flywheel.impl.FlwImplXplat;
public enum CompatMod {
EMBEDDIUM("embeddium"),
IRIS("iris"),
OCULUS("oculus"),
SODIUM("sodium");
public final String id;
public final boolean isLoaded;
CompatMod(String modId) {
id = modId;
isLoaded = FlwImplXplat.INSTANCE.isModLoaded(modId);
}
}

View file

@ -0,0 +1,46 @@
package dev.engine_room.flywheel.impl.compat;
import dev.engine_room.flywheel.impl.FlwImpl;
import dev.engine_room.flywheel.impl.FlwImplXplat;
import net.irisshaders.iris.api.v0.IrisApi;
public final class IrisCompat {
public static final boolean ACTIVE = FlwImplXplat.INSTANCE.useIrisCompat();
static {
if (ACTIVE) {
FlwImpl.LOGGER.debug("Detected Iris");
}
}
private IrisCompat() {
}
public static boolean isShaderPackInUse() {
if (!ACTIVE) {
return false;
}
return Internals.isShaderPackInUse();
}
public static boolean isRenderingShadowPass() {
if (!ACTIVE) {
return false;
}
return Internals.isRenderingShadowPass();
}
private static final class Internals {
static boolean isShaderPackInUse() {
return IrisApi.getInstance()
.isShaderPackInUse();
}
static boolean isRenderingShadowPass() {
return IrisApi.getInstance()
.isRenderingShadowPass();
}
}
}

View file

@ -0,0 +1,73 @@
package dev.engine_room.flywheel.impl.compat;
import java.lang.reflect.Field;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.impl.FlwImpl;
public final class OptifineCompat {
private static final String OPTIFINE_ROOT_PACKAGE = "net.optifine";
public static final boolean IS_INSTALLED;
@Nullable
private static final Field SHADER_PACK_LOADED_FIELD;
@Nullable
private static final Field IS_SHADOW_PASS_FIELD;
static {
Field shaderPackLoadedField = null;
Field isShadowPassField = null;
Package optifinePackage = Package.getPackage(OPTIFINE_ROOT_PACKAGE);
IS_INSTALLED = optifinePackage != null;
if (IS_INSTALLED) {
try {
Class<?> shadersClass = Class.forName("net.optifine.shaders.Shaders");
shaderPackLoadedField = shadersClass.getDeclaredField("shaderPackLoaded");
shaderPackLoadedField.setAccessible(true);
isShadowPassField = shadersClass.getDeclaredField("isShadowPass");
isShadowPassField.setAccessible(true);
} catch (Exception e) {
FlwImpl.LOGGER.debug("Failed to access OptiFine internals", e);
}
}
SHADER_PACK_LOADED_FIELD = shaderPackLoadedField;
IS_SHADOW_PASS_FIELD = isShadowPassField;
}
static {
if (IS_INSTALLED) {
FlwImpl.LOGGER.debug("Detected OptiFine");
}
}
private OptifineCompat() {
}
public static boolean isShaderPackInUse() {
if (SHADER_PACK_LOADED_FIELD == null) {
return false;
}
try {
return SHADER_PACK_LOADED_FIELD.getBoolean(null);
} catch (IllegalAccessException e) {
return false;
}
}
public static boolean isRenderingShadowPass() {
if (IS_SHADOW_PASS_FIELD == null) {
return false;
}
try {
return IS_SHADOW_PASS_FIELD.getBoolean(null);
} catch (IllegalAccessException e) {
return false;
}
}
}

View file

@ -1,20 +1,61 @@
package dev.engine_room.flywheel.impl.compat;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer;
import dev.engine_room.flywheel.impl.FlwImpl;
import dev.engine_room.flywheel.impl.FlwImplXplat;
import dev.engine_room.flywheel.lib.visualization.VisualizationHelper;
import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderHandler;
import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderPredicate;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@SuppressWarnings({"unchecked", "UnstableApiUsage"})
public class SodiumCompat {
public static <T extends BlockEntity> Object forBlockEntityType(BlockEntityType<T> type) {
public final class SodiumCompat {
public static final boolean USE_0_6_COMPAT = FlwImplXplat.INSTANCE.useSodium0_6Compat();
static {
if (USE_0_6_COMPAT) {
FlwImpl.LOGGER.debug("Detected Sodium 0.6");
}
}
private SodiumCompat() {
}
@Nullable
public static <T extends BlockEntity> Object onSetBlockEntityVisualizer(BlockEntityType<T> type, @Nullable BlockEntityVisualizer<? super T> oldVisualizer, @Nullable BlockEntityVisualizer<? super T> newVisualizer, @Nullable Object predicate) {
if (!USE_0_6_COMPAT) {
return null;
}
if (oldVisualizer == null && newVisualizer != null) {
if (predicate != null) {
throw new IllegalArgumentException("Sodium predicate must be null when old visualizer is null");
}
return Internals.addPredicate(type);
} else if (oldVisualizer != null && newVisualizer == null) {
if (predicate == null) {
throw new IllegalArgumentException("Sodium predicate must not be null when old visualizer is not null");
}
Internals.removePredicate(type, predicate);
return null;
}
return predicate;
}
private static final class Internals {
static <T extends BlockEntity> Object addPredicate(BlockEntityType<T> type) {
BlockEntityRenderPredicate<T> predicate = (getter, pos, be) -> VisualizationHelper.tryAddBlockEntity(be);
BlockEntityRenderHandler.instance().addRenderPredicate(type, predicate);
return predicate;
}
public static <T extends BlockEntity> void removePredicate(BlockEntityType<T> type, Object predicate) {
static <T extends BlockEntity> void removePredicate(BlockEntityType<T> type, Object predicate) {
BlockEntityRenderHandler.instance().removeRenderPredicate(type, (BlockEntityRenderPredicate<T>) predicate);
}
}
}

View file

@ -29,13 +29,10 @@ abstract class BlockEntityTypeMixin<T extends BlockEntity> implements BlockEntit
@Override
public void flywheel$setVisualizer(@Nullable BlockEntityVisualizer<? super T> visualizer) {
if (CompatMods.SODIUM.isLoaded) {
if (flywheel$visualizer == null && visualizer != null) {
flywheel$sodiumPredicate = SodiumCompat.forBlockEntityType((BlockEntityType<?>) (Object) this);
} else if (flywheel$visualizer != null && visualizer == null && flywheel$sodiumPredicate != null) {
SodiumCompat.removePredicate((BlockEntityType<?>) (Object) this, flywheel$sodiumPredicate);
}
}
this.flywheel$visualizer = visualizer;
if (SodiumCompat.USE_0_6_COMPAT) {
flywheel$sodiumPredicate = SodiumCompat.onSetBlockEntityVisualizer((BlockEntityType<T>) (Object) this, flywheel$visualizer, visualizer, flywheel$sodiumPredicate);
}
flywheel$visualizer = visualizer;
}
}

View file

@ -12,6 +12,7 @@ import net.minecraft.world.entity.EntityType;
@Mixin(EntityType.class)
abstract class EntityTypeMixin<T extends Entity> implements EntityTypeExtension<T> {
@Unique
@Nullable
private EntityVisualizer<? super T> flywheel$visualizer;
@Override
@ -22,6 +23,6 @@ abstract class EntityTypeMixin<T extends Entity> implements EntityTypeExtension<
@Override
public void flywheel$setVisualizer(@Nullable EntityVisualizer<? super T> visualizer) {
this.flywheel$visualizer = visualizer;
flywheel$visualizer = visualizer;
}
}

View file

@ -0,0 +1,38 @@
// https://github.com/CaffeineMC/sodium-fabric/blob/e7643f4544f61180ed2f0ff4952d7daa2c1feaf4/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java
// PolyForm Shield License 1.0.0
package net.caffeinemc.mods.sodium.api.blockentity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
@ApiStatus.AvailableSince("0.6.0")
public interface BlockEntityRenderHandler {
BlockEntityRenderHandler INSTANCE = null;
static BlockEntityRenderHandler instance() {
return INSTANCE;
}
/**
* Adds a predicate to determine if a block entity should be rendered.
*
* <p>Upon chunk bake, block entities of the given type will have {@code shouldRender} evaluated.
* <br>If <b>all predicates</b> returns {@code true} (and the block entity has a renderer), the block entity will be
* added to the chunk for future rendering.</p>
* @param type The block entity type to associate the given predicate with.
* @param shouldRender The predicate for the block entity to evaluate.
*/
<T extends BlockEntity> void addRenderPredicate(BlockEntityType<T> type, BlockEntityRenderPredicate<T> shouldRender);
/**
* Removes a predicate added by {@code addRenderPredicate}. <b>It must be the same object that was added.</b>
*
* @param type The block entity type to associate the given predicate with.
* @param shouldRender The predicate to remove.
* @return If the predicate existed and was removed.
*/
<T extends BlockEntity> boolean removeRenderPredicate(BlockEntityType<T> type, BlockEntityRenderPredicate<T> shouldRender);
}

View file

@ -0,0 +1,16 @@
// https://github.com/CaffeineMC/sodium-fabric/blob/e7643f4544f61180ed2f0ff4952d7daa2c1feaf4/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java
// PolyForm Shield License 1.0.0
package net.caffeinemc.mods.sodium.api.blockentity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
@ApiStatus.AvailableSince("0.6.0")
@FunctionalInterface
public interface BlockEntityRenderPredicate<T extends BlockEntity> {
boolean shouldRender(BlockGetter blockGetter, BlockPos blockPos, T entity);
}

View file

@ -0,0 +1,114 @@
// https://github.com/IrisShaders/Iris/blob/20be7fc1ff8a48048cb4eb787e1299782bb1caa4/src/main/java/net/irisshaders/iris/api/v0/IrisApi.java
// GNU Lesser General Public License v3.0
package net.irisshaders.iris.api.v0;
/**
* The entry point to the Iris API, major version 0. This is currently the latest
* version of the API.
*
* To access the API, use {@link #getInstance()}.
*/
public interface IrisApi {
/**
* @since API v0.0
*/
static IrisApi getInstance() {
return null;
}
/**
* Gets the minor revision of this API. This is incremented when
* new methods are added without breaking API. Mods can check this
* if they wish to check whether given API calls are available on
* the currently installed Iris version.
*
* @return The current minor revision. Currently, revision 2.
*/
int getMinorApiRevision();
/**
* Checks whether a shader pack is currently in use and being used
* for rendering. If there is no shader pack enabled or a shader
* pack failed to compile and is therefore not in use, this will
* return false.
*
* <p>Mods that need to enable custom workarounds for shaders
* should use this method.
*
* @return Whether shaders are being used for rendering.
* @since {@link #getMinorApiRevision() API v0.0}
*/
boolean isShaderPackInUse();
/**
* Checks whether the shadow pass is currently being rendered.
*
* <p>Generally, mods won't need to call this function for much.
* Mods should be fine with things being rendered multiple times
* each frame from different camera perspectives. Often, there's
* a better approach to fixing bugs than calling this function.
*
* <p>Pretty much the main legitimate use for this function that
* I've seen is in a mod like Immersive Portals, where it has
* very custom culling that doesn't work when the Iris shadow
* pass is active.
*
* <p>Naturally, this function can only return true if
* {@link #isShaderPackInUse()} returns true.
*
* @return Whether Iris is currently rendering the shadow pass.
* @since API v0.0
*/
boolean isRenderingShadowPass();
/**
* Opens the main Iris GUI screen. It's up to Iris to decide
* what this screen is, but generally this is the shader selection
* screen.
*
* This method takes and returns Objects instead of any concrete
* Minecraft screen class to avoid referencing Minecraft classes.
* Nevertheless, the passed parent must either be null, or an
* object that is a subclass of the appropriate {@code Screen}
* class for the given Minecraft version.
*
* @param parent The parent screen, an instance of the appropriate
* {@code Screen} class.
* @return A {@code Screen} class for the main Iris GUI screen.
* @since API v0.0
*/
Object openMainIrisScreenObj(Object parent);
/**
* Gets the language key of the main screen. Currently, this
* is "options.iris.shaderPackSelection".
*
* @return the language key, for use with {@code TranslatableText}
* / {@code TranslatableComponent}
* @since API v0.0
*/
String getMainScreenLanguageKey();
// /**
// * Gets a config object that can edit the Iris configuration.
// * @since API v0.0
// */
// IrisApiConfig getConfig();
// /**
// * Gets a text vertex sink to render into.
// * @param maxQuadCount Maximum amount of quads that will be rendered with this sink
// * @param bufferProvider An IntFunction that can provide a {@code ByteBuffer} with at minimum the bytes provided by the input parameter
// * @since API 0.1
// */
// IrisTextVertexSink createTextVertexSink(int maxQuadCount, IntFunction<ByteBuffer> bufferProvider);
/**
* Gets the sun path rotation used by the current shader pack.
*
* @return The sun path rotation as specified by the shader pack, or 0 if no shader pack is in use.
* @since API v0.2
*/
float getSunPathRotation();
}

View file

@ -10,6 +10,7 @@ plugins {
val api = sourceSets.create("api")
val lib = sourceSets.create("lib")
val backend = sourceSets.create("backend")
val stubs = sourceSets.create("stubs")
val main = sourceSets.getByName("main")
transitiveSourceSets {
@ -26,7 +27,12 @@ transitiveSourceSets {
rootCompile()
compile(api, lib)
}
sourceSet(stubs) {
rootCompile()
}
sourceSet(main) {
// Don't want stubs at runtime
compile(stubs)
implementation(api, lib, backend)
}
@ -35,11 +41,10 @@ transitiveSourceSets {
platform {
commonProject = project(":common")
sources(api, lib, backend, main)
compileWithCommonSourceSets()
setupLoomMod()
compileWithCommonSourceSets(api, lib, backend, stubs, main)
setupLoomMod(api, lib, backend, main)
setupLoomRuns()
setupFatJar()
setupFatJar(api, lib, backend, main)
}
jarSets {
@ -78,5 +83,6 @@ dependencies {
"forApi"(project(path = ":common", configuration = "commonApiOnly"))
"forLib"(project(path = ":common", configuration = "commonLib"))
"forBackend"(project(path = ":common", configuration = "commonBackend"))
"forStubs"(project(path = ":common", configuration = "commonStubs"))
"forMain"(project(path = ":common", configuration = "commonImpl"))
}

View file

@ -6,7 +6,7 @@ import net.minecraft.client.multiplayer.ClientLevel;
public class FlwImplXplatImpl implements FlwImplXplat {
@Override
public boolean getModLoaded(String modId) {
public boolean isModLoaded(String modId) {
return FabricLoader.getInstance().isModLoaded(modId);
}
@ -24,4 +24,14 @@ public class FlwImplXplatImpl implements FlwImplXplat {
public FlwConfig getConfig() {
return FabricFlwConfig.INSTANCE;
}
@Override
public boolean useSodium0_6Compat() {
return FabricSodiumCompat.USE_0_6_COMPAT;
}
@Override
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded;
}
}

View file

@ -1,6 +1,5 @@
package dev.engine_room.flywheel.impl;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.impl.compat.CompatMods;

View file

@ -0,0 +1,48 @@
package dev.engine_room.flywheel.impl.compat;
import java.util.Optional;
import dev.engine_room.flywheel.impl.FlwImpl;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.Version;
import net.fabricmc.loader.api.VersionParsingException;
import net.fabricmc.loader.api.metadata.version.VersionPredicate;
public final class FabricSodiumCompat {
public static final boolean USE_0_5_COMPAT;
public static final boolean USE_0_6_COMPAT;
static {
boolean use0_5Compat = false;
boolean use0_6Compat = false;
Optional<ModContainer> optionalModContainer = FabricLoader.getInstance().getModContainer(CompatMod.SODIUM.id);
if (optionalModContainer.isPresent()) {
ModContainer modContainer = optionalModContainer.get();
Version sodiumVersion = modContainer.getMetadata().getVersion();
try {
VersionPredicate predicate0_5 = VersionPredicate.parse("~0.5.0");
VersionPredicate predicate0_6 = VersionPredicate.parse(">=0.6.0-beta.2");
use0_5Compat = predicate0_5.test(sodiumVersion);
use0_6Compat = predicate0_6.test(sodiumVersion);
} catch (VersionParsingException e) {
FlwImpl.LOGGER.debug("Failed to parse Sodium version predicates", e);
}
}
USE_0_5_COMPAT = use0_5Compat;
USE_0_6_COMPAT = use0_6Compat;
}
static {
if (USE_0_5_COMPAT) {
FlwImpl.LOGGER.debug("Detected Sodium 0.5");
}
}
private FabricSodiumCompat() {
}
}

View file

@ -28,6 +28,7 @@
],
"depends": {
"minecraft": "${minecraft_semver_version_range}",
"fabricloader": ">=0.15.0",
"fabric-api": "${fabric_api_version_range}"
},
"breaks": {

View file

@ -10,6 +10,7 @@ plugins {
val api = sourceSets.create("api")
val lib = sourceSets.create("lib")
val backend = sourceSets.create("backend")
val stubs = sourceSets.create("stubs")
val main = sourceSets.getByName("main")
transitiveSourceSets {
@ -26,8 +27,11 @@ transitiveSourceSets {
rootCompile()
compile(api, lib)
}
sourceSet(stubs) {
rootCompile()
}
sourceSet(main) {
compile(api, lib, backend)
compile(api, lib, backend, stubs)
}
createCompileConfigurations()
@ -35,11 +39,10 @@ transitiveSourceSets {
platform {
commonProject = project(":common")
sources(api, lib, backend, main)
compileWithCommonSourceSets()
setupLoomMod()
compileWithCommonSourceSets(api, lib, backend, stubs, main)
setupLoomMod(api, lib, backend, main)
setupLoomRuns()
setupFatJar()
setupFatJar(api, lib, backend, main)
}
jarSets {
@ -88,5 +91,6 @@ dependencies {
"forApi"(project(path = ":common", configuration = "commonApiOnly"))
"forLib"(project(path = ":common", configuration = "commonLib"))
"forBackend"(project(path = ":common", configuration = "commonBackend"))
"forStubs"(project(path = ":common", configuration = "commonStubs"))
"forMain"(project(path = ":common", configuration = "commonImpl"))
}

View file

@ -1,13 +1,15 @@
package dev.engine_room.flywheel.impl;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent;
import dev.engine_room.flywheel.impl.compat.CompatMod;
import net.minecraft.client.multiplayer.ClientLevel;
import net.neoforged.fml.loading.LoadingModList;
import net.neoforged.neoforge.common.NeoForge;
import net.minecraftforge.fml.loading.LoadingModList;
public class FlwImplXplatImpl implements FlwImplXplat {
@Override
public boolean getModLoaded(String modId) {
public boolean isModLoaded(String modId) {
return LoadingModList.get().getModFileById(modId) != null;
}
@ -25,4 +27,14 @@ public class FlwImplXplatImpl implements FlwImplXplat {
public FlwConfig getConfig() {
return NeoForgeFlwConfig.INSTANCE;
}
@Override
public boolean useSodium0_6Compat() {
return CompatMod.SODIUM.isLoaded && !CompatMod.EMBEDDIUM.isLoaded;
}
@Override
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded || CompatMod.OCULUS.isLoaded;
}
}

View file

@ -2,7 +2,6 @@ package dev.engine_room.flywheel.impl;
import java.lang.reflect.Field;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.impl.compat.CompatMods;

View file

@ -0,0 +1,33 @@
package dev.engine_room.flywheel.impl.compat;
import org.embeddedt.embeddium.api.ChunkDataBuiltEvent;
import dev.engine_room.flywheel.impl.FlwImpl;
import dev.engine_room.flywheel.lib.visualization.VisualizationHelper;
public final class EmbeddiumCompat {
public static final boolean ACTIVE = CompatMod.EMBEDDIUM.isLoaded;
static {
if (ACTIVE) {
FlwImpl.LOGGER.debug("Detected Embeddium");
}
}
private EmbeddiumCompat() {
}
public static void init() {
if (ACTIVE) {
Internals.init();
}
}
private static final class Internals {
static void init() {
ChunkDataBuiltEvent.BUS.addListener(event -> {
event.getDataBuilder().removeBlockEntitiesIf(VisualizationHelper::tryAddBlockEntity);
});
}
}
}

View file

@ -31,6 +31,13 @@ type = "required"
versionRange = "${neoforge_version_range}"
side = "CLIENT"
[[dependencies.${mod_id}]]
modId = "sodium"
mandatory = false
versionRange = "[0.6.0-beta.2,)"
side = "CLIENT"
# Simulates a breaks/incompatible dependency
[[dependencies.${mod_id}]]
modId = "embeddium"
type = "incompatible"

View file

@ -7,9 +7,9 @@ mod_name = Flywheel
mod_version = 1.0.0-beta
mod_description = An overhauled entity and block entity rendering API.
mod_license = MIT
mod_sources = https://github.com/Jozufozu/Flywheel
mod_issues = https://github.com/Jozufozu/Flywheel/issues
mod_homepage = https://github.com/Jozufozu/Flywheel
mod_sources = https://github.com/Engine-Room/Flywheel
mod_issues = https://github.com/Engine-Room/Flywheel/issues
mod_homepage = https://github.com/Engine-Room/Flywheel
# Mod dependency declarations
minecraft_semver_version_range = >=1.21.1 <1.21.2

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME