Compare commits

...

3 Commits

Author SHA1 Message Date
Jozufozu
60c5eb6824
Merge 094acf5741 into 4f0c1cc1ae 2024-11-03 20:22:26 -05:00
Jozufozu
4f0c1cc1ae Checking the source
- Remove source checks, it was very old and untouched
- Remove FIXMEs and TODOs that were already fixed/done
2024-11-03 12:15:04 -08:00
Jozufozu
094acf5741 The version vision
- Implement rudimentary backend version requirements
- Backends ship with a hard coded backend version
- Mods can specify a minimum major/minor backend version
- If a backend's version is below any mod's required version it is
  considered disabled
2024-10-20 16:54:24 -07:00
16 changed files with 206 additions and 89 deletions

View File

@ -13,6 +13,13 @@ public interface Backend {
*/
Engine createEngine(LevelAccessor level);
/**
* The version of flywheel this backend was written against.
*
* @return A backend version.
*/
BackendVersion version();
/**
* The priority of this backend.
* <p>The backend with the highest priority upon first launch will be chosen as the default backend.

View File

@ -0,0 +1,11 @@
package dev.engine_room.flywheel.api.backend;
public record BackendVersion(int major, int minor) implements Comparable<BackendVersion> {
@Override
public int compareTo(BackendVersion o) {
if (major != o.major) {
return Integer.compare(major, o.major);
}
return Integer.compare(minor, o.minor);
}
}

View File

@ -2,6 +2,7 @@ package dev.engine_room.flywheel.backend;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
@ -12,11 +13,14 @@ import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import dev.engine_room.flywheel.lib.util.ShadersModHelper;
public final class Backends {
private static final BackendVersion VERSION = new BackendVersion(1, 0);
/**
* Use GPU instancing to render everything.
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.version(VERSION)
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("instancing"));
@ -26,6 +30,7 @@ public final class Backends {
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.version(VERSION)
.priority(1000)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("indirect"));

View File

@ -1,55 +0,0 @@
package dev.engine_room.flywheel.backend.compile;
// TODO: recycle to be invoked by the shader compiler
public class SourceChecks {
// public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = checkFunctionArity("flw_layoutVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
// public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = checkFunctionArity("flw_materialVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = checkFunctionArity("flw_materialFragment", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = checkFunctionArity("flw_contextVertex", 0);
// public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = checkFunctionArity("flw_contextFragment", 0).andThen(checkFunctionArity("flw_initFragment", 0));
// public static final BiConsumer<ErrorReporter, SourceFile> PIPELINE = checkFunctionArity("main", 0);
//
// public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) {
// return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity);
// }
//
// public static BiConsumer<ErrorReporter, SourceFile> checkFunctionParameterTypeExists(String name, int arity, int param) {
// return (errorReporter, file) -> {
// var func = checkFunctionArity(errorReporter, file, name, arity);
//
// if (func == null) {
// return;
// }
//
// var maybeStruct = func.getParameterType(param)
// .findStruct();
//
// if (maybeStruct.isEmpty()) {
// errorReporter.generateMissingStruct(file, func.getParameterType(param), "struct not defined");
// }
// };
// }
//
// /**
// * @return {@code null} if the function doesn't exist, or if the function has the wrong arity.
// */
// @Nullable
// private static ShaderFunction checkFunctionArity(ErrorReporter errorReporter, SourceFile file, String name, int arity) {
// Optional<ShaderFunction> maybeFunc = file.findFunction(name);
//
// if (maybeFunc.isEmpty()) {
// errorReporter.generateMissingFunction(file, name, "\"" + name + "\" function not defined");
// return null;
// }
//
// ShaderFunction func = maybeFunc.get();
// ImmutableList<ShaderVariable> params = func.getParameters();
// if (params.size() != arity) {
// errorReporter.generateFunctionArgumentCountError(name, arity, func.getArgs());
// return null;
// }
//
// return func;
// }
}

View File

@ -62,7 +62,7 @@ public abstract class InstanceAssemblerComponent implements SourceComponent {
FLOAT_UNPACKING_FUNCS.put(FloatRepr.UNSIGNED_INT, e -> e.cast("float"));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.NORMALIZED_UNSIGNED_INT, e -> e.cast("float").div(4294967295f));
FLOAT_UNPACKING_FUNCS.put(FloatRepr.FLOAT, e -> e.callFunction("uintBitsToFloat")); // FIXME: GLSL 330+
FLOAT_UNPACKING_FUNCS.put(FloatRepr.FLOAT, e -> e.callFunction("uintBitsToFloat"));
}
protected final Layout layout;

View File

@ -22,7 +22,6 @@ import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.lighting.LayerLightEventListener;
/**
* TODO: AO data
* A managed arena of light sections for uploading to the GPU.
*
* <p>Each section represents an 18x18x18 block volume of light data.
@ -116,7 +115,6 @@ public class LightStorage {
}
// Now actually do the collection.
// TODO: Should this be done in parallel?
sectionsToCollect.forEach(this::collectSection);
updatedSections.clear();

View File

@ -4,7 +4,10 @@ import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.api.backend.Engine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor;
@ -13,11 +16,13 @@ public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory;
private final int priority;
private final BooleanSupplier isSupported;
private final BackendVersion version;
public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported) {
public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported, BackendVersion version) {
this.priority = priority;
this.engineFactory = engineFactory;
this.isSupported = isSupported;
this.version = version;
}
public static Builder builder() {
@ -29,6 +34,11 @@ public final class SimpleBackend implements Backend {
return engineFactory.apply(level);
}
@Override
public BackendVersion version() {
return version;
}
@Override
public int priority() {
return priority;
@ -40,9 +50,13 @@ public final class SimpleBackend implements Backend {
}
public static final class Builder {
private Function<LevelAccessor, Engine> engineFactory;
private int priority = 0;
@Nullable
private Function<LevelAccessor, Engine> engineFactory;
@Nullable
private BooleanSupplier isSupported;
@Nullable
private BackendVersion version;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
this.engineFactory = engineFactory;
@ -59,11 +73,17 @@ public final class SimpleBackend implements Backend {
return this;
}
public Builder version(BackendVersion version) {
this.version = version;
return this;
}
public Backend register(ResourceLocation id) {
Objects.requireNonNull(engineFactory);
Objects.requireNonNull(isSupported);
Objects.requireNonNull(version);
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported, version));
}
}
}

View File

@ -1,9 +1,11 @@
package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import java.util.List;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.impl.visualization.VisualizationManagerImpl;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import net.minecraft.client.multiplayer.ClientLevel;
@ -15,6 +17,7 @@ public final class BackendManagerImpl {
throw new UnsupportedOperationException("Cannot create engine when backend is off.");
})
.supported(() -> true)
.version(new BackendVersion(0, 0))
.register(Flywheel.rl("off"));
public static final Backend DEFAULT_BACKEND = findDefaultBackend();
@ -33,7 +36,7 @@ public final class BackendManagerImpl {
}
// Don't store this statically because backends can theoretically change their priorities at runtime.
private static ArrayList<Backend> backendsByPriority() {
private static List<Backend> backendsByPriority() {
var backends = new ArrayList<>(Backend.REGISTRY.getAll());
// Sort with keys backwards so that the highest priority is first.
@ -53,27 +56,46 @@ public final class BackendManagerImpl {
}
private static void chooseBackend() {
var requirements = FlwImplXplat.INSTANCE.modRequirements();
var preferred = FlwConfig.INSTANCE.backend();
if (preferred.isSupported()) {
backend = preferred;
return;
if (meetsModRequirements(requirements, preferred)) {
backend = preferred;
return;
} else {
var minVersion = requirements.minimumBackendVersion();
FlwImpl.LOGGER.warn("Preferred backend '{}' does not meet minimum version requirement {}.{}", Backend.REGISTRY.getIdOrThrow(preferred), minVersion.major(), minVersion.minor());
}
}
backend = fallback(preferred, requirements);
FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(backend));
}
private static Backend fallback(Backend preferred, ModRequirements requirements) {
var backendsByPriority = backendsByPriority();
var startIndex = backendsByPriority.indexOf(preferred) + 1;
// For safety in case we don't find anything
backend = OFF_BACKEND;
var out = OFF_BACKEND;
for (int i = startIndex; i < backendsByPriority.size(); i++) {
var candidate = backendsByPriority.get(i);
if (candidate.isSupported()) {
backend = candidate;
break;
if (meetsModRequirements(requirements, candidate)) {
out = candidate;
break;
}
}
}
return out;
}
FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(backend));
private static boolean meetsModRequirements(ModRequirements requirements, Backend candidate) {
return candidate.version()
.compareTo(requirements.minimumBackendVersion()) >= 0;
}
public static String getBackendString() {

View File

@ -17,4 +17,6 @@ public interface FlwImplXplat {
boolean useSodium0_6Compat();
boolean useIrisCompat();
ModRequirements modRequirements();
}

View File

@ -0,0 +1,10 @@
package dev.engine_room.flywheel.impl;
import java.util.List;
import dev.engine_room.flywheel.api.backend.BackendVersion;
public record ModRequirements(BackendVersion minimumBackendVersion, List<Entry> entries) {
public record Entry(String modId, BackendVersion version) {
}
}

View File

@ -11,7 +11,6 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
// TODO: Add freezing
@SuppressWarnings("unchecked")
public final class VisualizerRegistryImpl {
@Nullable

View File

@ -7,26 +7,6 @@ import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.block.entity.BlockEntityType;
/**
* TODO:
* <table>
* <tr><td>{@link BlockEntityType#SIGN}</td><td> {@link net.minecraft.client.renderer.blockentity.SignRenderer SignRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#PISTON}</td><td> {@link net.minecraft.client.renderer.blockentity.PistonHeadRenderer PistonHeadRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#CONDUIT}</td><td> {@link net.minecraft.client.renderer.blockentity.ConduitRenderer ConduitRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#ENCHANTING_TABLE}</td><td> {@link net.minecraft.client.renderer.blockentity.EnchantTableRenderer EnchantTableRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#LECTERN}</td><td> {@link net.minecraft.client.renderer.blockentity.LecternRenderer LecternRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#MOB_SPAWNER}</td><td> {@link net.minecraft.client.renderer.blockentity.SpawnerRenderer SpawnerRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#BED}</td><td> {@link net.minecraft.client.renderer.blockentity.BedRenderer BedRenderer}</td></tr>
* <tr><td>^^ Interesting - Major vv</td></tr>
* <tr><td>{@link BlockEntityType#END_PORTAL}</td><td> {@link net.minecraft.client.renderer.blockentity.TheEndPortalRenderer TheEndPortalRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#END_GATEWAY}</td><td> {@link net.minecraft.client.renderer.blockentity.TheEndGatewayRenderer TheEndGatewayRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#BEACON}</td><td> {@link net.minecraft.client.renderer.blockentity.BeaconRenderer BeaconRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#SKULL}</td><td> {@link net.minecraft.client.renderer.blockentity.SkullBlockRenderer SkullBlockRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#BANNER}</td><td> {@link net.minecraft.client.renderer.blockentity.BannerRenderer BannerRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#STRUCTURE_BLOCK}</td><td> {@link net.minecraft.client.renderer.debug.StructureRenderer StructureRenderer}</td></tr>
* <tr><td>{@link BlockEntityType#CAMPFIRE}</td><td> {@link net.minecraft.client.renderer.blockentity.CampfireRenderer CampfireRenderer}</td></tr>
* </table>
*/
public class VanillaVisuals {
public static void init() {
builder(BlockEntityType.CHEST)

View File

@ -1,9 +1,17 @@
package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererCallback;
import dev.engine_room.flywheel.impl.compat.CompatMod;
import dev.engine_room.flywheel.impl.compat.FabricSodiumCompat;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import net.minecraft.client.multiplayer.ClientLevel;
public class FlwImplXplatImpl implements FlwImplXplat {
@ -36,4 +44,54 @@ public class FlwImplXplatImpl implements FlwImplXplat {
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded;
}
@Override
public ModRequirements modRequirements() {
List<ModRequirements.Entry> entries = new ArrayList<>();
for (ModContainer mod : FabricLoader.getInstance()
.getAllMods()) {
var metadata = mod.getMetadata();
var custom = metadata.getCustomValue("flywheel");
if (custom != null && custom.getType() == CustomValue.CvType.OBJECT) {
var object = custom.getAsObject();
var major = getCustomValueAsInt(object.get("backend_major_version"));
var minor = getCustomValueAsInt(object.get("backend_minor_version"));
// Major version is required
if (major != null) {
// Minor version defaults to 0
var version = new BackendVersion(major, minor == null ? 0 : minor);
entries.add(new ModRequirements.Entry(metadata.getId(), version));
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid required backend version", metadata.getId());
}
}
}
if (!entries.isEmpty()) {
var minVersion = entries.stream()
.map(ModRequirements.Entry::version)
.min(BackendVersion::compareTo)
.get();
return new ModRequirements(minVersion, entries);
} else {
return new ModRequirements(new BackendVersion(0, 0), List.of());
}
}
@Nullable
private static Integer getCustomValueAsInt(@Nullable CustomValue major) {
if (major != null && major.getType() == CustomValue.CvType.NUMBER) {
return major.getAsNumber()
.intValue();
}
return null;
}
}

View File

@ -35,5 +35,11 @@
"breaks": {
"sodium": ["<0.5.0", "~0.6.0- <0.6.0-beta.2"],
"embeddium": "*"
},
"custom" : {
"flywheel" : {
"backend_major_version" : 1,
"backend_minor_version" : 0
}
}
}

View File

@ -1,10 +1,18 @@
package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import java.util.List;
import com.electronwill.nightconfig.core.Config;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent;
import dev.engine_room.flywheel.impl.compat.CompatMod;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.forgespi.language.IModInfo;
public class FlwImplXplatImpl implements FlwImplXplat {
@Override
@ -36,4 +44,46 @@ public class FlwImplXplatImpl implements FlwImplXplat {
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded || CompatMod.OCULUS.isLoaded;
}
@Override
public ModRequirements modRequirements() {
List<ModRequirements.Entry> entries = new ArrayList<>();
ModList.get()
.forEachModFile(file -> {
var info = file.getModFileInfo();
for (IModInfo mod : info.getMods()) {
var modProperties = mod.getModProperties()
.get("flywheel");
// There's no well-defined API for custom properties like in fabric.
// It just returns an object, but internally it's represented with nightconfig.
if (modProperties instanceof Config config) {
// Minor version defaults to 0, major is required
int major = config.getIntOrElse("backend_major_version", Integer.MIN_VALUE);
int minor = config.getIntOrElse("backend_minor_version", 0);
if (major != Integer.MIN_VALUE) {
entries.add(new ModRequirements.Entry(mod.getModId(), new BackendVersion(major, minor)));
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid required backend version", mod.getModId());
}
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid flywheel properties", mod.getModId());
}
}
});
if (!entries.isEmpty()) {
var minVersion = entries.stream()
.map(ModRequirements.Entry::version)
.min(BackendVersion::compareTo)
.get();
return new ModRequirements(minVersion, entries);
} else {
return new ModRequirements(new BackendVersion(0, 0), List.of());
}
}
}

View File

@ -14,6 +14,10 @@ authors = "Jozufozu, PepperCode1"
displayURL = "${mod_homepage}"
displayTest = "IGNORE_ALL_VERSION"
[modproperties.${ mod_id }.flywheel]
backend_major_version = 1
backend_minor_version = 0
[[dependencies.${mod_id}]]
modId = "minecraft"
mandatory = true