Formalize most public API (#253)

* Start on general API formalization

* More API improvements

- Add Engine#onLightUpdate; remove LightUpdateHolder and backend/ClientChunkCacheMixin
- Add Effect#level
- Add VisualizationHelper#queueAdd and #queueRemove for Effects
- Fix PartialModel not assigning bakedModel field when populating on init
- Fix PartialModel.ALL using weak keys instead of weak values
- Make Simple*Visualizer and corresponding inner Builder classes final
- Restore FlatLit#light overload that accepts block and sky light values separately
- Add AbstractBlockEntityVisual#relight overloads that accept Iterator and Iterable
- Reorganize classes in impl.vizualization

* TaskExecutor simplification

- Move TaskExecutor#sync* methods to TaskExecutorImpl
- Move Flag and RaisePlan to impl
- Remove TaskExecutor#scheduleForMainThread and #isMainThread methods
- Remove SyncedPlan
- Add Engine#setupRender
- Remove TaskExecutor parameters from Engine#render* methods
- Convert Engine$CrumblingBlock into an interface
- Unmark RenderContext as NonExtendable to allow fulfilling the purpose described in the doc of VisualizationManager#renderDispatcher

* Remove registry freeze callbacks

- Lazily initialize MaterialShaderIndices
- Rename MaterialShaders#*Shader to #*Source
- Move BackendImplemented to api.backend package
This commit is contained in:
PepperCode1 2024-07-26 14:21:35 -06:00 committed by GitHub
parent cc0ad90242
commit eb2ba12a98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
202 changed files with 1838 additions and 2192 deletions

View File

@ -1,7 +1,6 @@
package dev.engine_room.flywheel.api.event;
package dev.engine_room.flywheel.api;
import org.jetbrains.annotations.ApiStatus;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import com.mojang.blaze3d.vertex.PoseStack;
@ -10,7 +9,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderBuffers;
@ApiStatus.NonExtendable
public interface RenderContext {
LevelRenderer renderer();
@ -20,9 +18,9 @@ public interface RenderContext {
PoseStack stack();
Matrix4f projection();
Matrix4fc projection();
Matrix4f viewProjection();
Matrix4fc viewProjection();
Camera camera();

View File

@ -1,13 +1,12 @@
package dev.engine_room.flywheel.api.backend;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.internal.FlwApiLink;
import dev.engine_room.flywheel.api.registry.IdRegistry;
import net.minecraft.world.level.LevelAccessor;
@BackendImplemented
public interface Backend {
static IdRegistry<Backend> REGISTRY = FlwApiLink.INSTANCE.createIdRegistry();
IdRegistry<Backend> REGISTRY = FlwApiLink.INSTANCE.createIdRegistry();
/**
* Create a new engine instance.

View File

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.api;
package dev.engine_room.flywheel.api.backend;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;

View File

@ -9,19 +9,19 @@ public final class BackendManager {
/**
* Get the current backend.
*/
public static Backend getBackend() {
return FlwApiLink.INSTANCE.getBackend();
public static Backend currentBackend() {
return FlwApiLink.INSTANCE.getCurrentBackend();
}
public static boolean isBackendOn() {
return FlwApiLink.INSTANCE.isBackendOn();
}
public static Backend getOffBackend() {
public static Backend offBackend() {
return FlwApiLink.INSTANCE.getOffBackend();
}
public static Backend getDefaultBackend() {
public static Backend defaultBackend() {
return FlwApiLink.INSTANCE.getDefaultBackend();
}
}

View File

@ -2,52 +2,43 @@ package dev.engine_room.flywheel.api.backend;
import java.util.List;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.event.RenderStage;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Range;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.LightLayer;
@BackendImplemented
public interface Engine {
/**
* Create a visualization context that will render to the given stage.
* Create a visualization context that will be used to create visuals of the given type.
*
* @param stage The stage to render to.
* @param visualType The type of visual.
* @return A new visualization context.
*/
VisualizationContext createVisualizationContext(RenderStage stage);
VisualizationContext createVisualizationContext(VisualType visualType);
/**
* Create a plan that will be executed every frame.
* Create a plan that will start execution after the start of the level render and
* finish execution before {@link #setupRender} is called.
*
* @return A new plan.
*/
Plan<RenderContext> createFramePlan();
/**
* Render all instances necessary for the given stage.
* @param executor The task executor running the frame plan.
* @param context The render context for this frame.
* @param stage The stage to render.
* @return The current render origin.
*/
void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage);
/**
* Render the given instances as a crumbling overlay.
* <br>
* This is guaranteed to be called between the first and last calls to {@link #renderStage} for the current frame.
*
* @param executor The task executor running the frame plan.
* @param context The render context for this frame.
* @param crumblingBlocks The instances to render. This list is never empty.
*/
void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> crumblingBlocks);
Vec3i renderOrigin();
/**
* Maintain the render origin to be within a certain distance from the camera in all directions,
@ -57,11 +48,6 @@ public interface Engine {
*/
boolean updateRenderOrigin(Camera camera);
/**
* @return The current render origin.
*/
Vec3i renderOrigin();
/**
* Assign the set of sections that visuals have requested GPU light for.
*
@ -71,19 +57,70 @@ public interface Engine {
*/
void lightSections(LongSet sections);
void onLightUpdate(SectionPos sectionPos, LightLayer layer);
/**
* Set up rendering for the current level render.
*
* <p>This method is guaranteed to be called after
* {@linkplain #createFramePlan() the frame plan} has finished execution and before
* {@link #render} and {@link #renderCrumbling} are called. This method is guaranteed to
* be called on the render thread.
*
* @param context The context for the current level render.
*/
void setupRender(RenderContext context);
/**
* Render all instances necessary for the given visual type.
*
* <p>This method is guaranteed to be called after {@link #setupRender} for the current
* level render. This method is guaranteed to be called on the render thread.
*
* @param context The context for the current level render.
* @param visualType The type of visual.
*/
void render(RenderContext context, VisualType visualType);
/**
* Render the given instances as a crumbling overlay.
*
* <p>This method is guaranteed to be called after {@link #setupRender} for the current
* level render. This method is guaranteed to be called on the render thread.
*
* @param context The context for the current level render.
* @param crumblingBlocks The instances to render. This list is never empty.
*/
void renderCrumbling(RenderContext context, List<CrumblingBlock> crumblingBlocks);
/**
* Free all resources associated with this engine.
* <br>
* This engine will not be used again after this method is called.
*
* <p>This engine will not be used again after this method is called.
*
* <p>This method is guaranteed to be called on the render thread.
*/
void delete();
/**
* A block to be rendered as a crumbling overlay.
* @param progress The progress of the crumbling animation in the range [0, 10).
* @param pos The position of the block.
* @param instances The instances associated with the BE at this position.
*/
record CrumblingBlock(int progress, BlockPos pos, List<Instance> instances) {
@ApiStatus.NonExtendable
interface CrumblingBlock {
/**
* The position of the block.
*/
BlockPos pos();
/**
* The progress of the crumbling animation in the range [0, 10).
*/
@Range(from = 0, to = 9)
int progress();
/**
* The instances associated with the block entity visual at this position.
*/
List<Instance> instances();
}
}

View File

@ -1,27 +0,0 @@
package dev.engine_room.flywheel.api.event;
public enum RenderStage {
AFTER_ENTITIES,
AFTER_BLOCK_ENTITIES,
AFTER_TRANSLUCENT_TERRAIN,
AFTER_PARTICLES,
AFTER_WEATHER;
/**
* Is this stage the last one to be rendered in the frame?
*
* @return {@code true} if no other RenderStages will be dispatched this frame.
*/
public boolean isLast() {
return this == values()[values().length - 1];
}
/**
* Is this stage the first one to be rendered in the frame?
*
* @return {@code true} if this is the first RenderStage to be dispatched this frame.
*/
public boolean isFirst() {
return this == values()[0];
}
}

View File

@ -5,11 +5,11 @@ public interface Instance {
InstanceHandle handle();
default void delete() {
handle().setDeleted();
}
default void setChanged() {
handle().setChanged();
}
default void delete() {
handle().setDeleted();
}
}

View File

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.api.instance;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.backend.BackendImplemented;
@BackendImplemented
public interface InstanceHandle {

View File

@ -11,7 +11,7 @@ import net.minecraft.resources.ResourceLocation;
* @param <I> The java representation of the instance.
*/
public interface InstanceType<I extends Instance> {
static Registry<InstanceType<?>> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
Registry<InstanceType<?>> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
/**
* @param handle A handle that allows you to mark the instance as dirty or deleted.

View File

@ -2,7 +2,7 @@ package dev.engine_room.flywheel.api.instance;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.backend.BackendImplemented;
/**
* An instancer is how you interact with an instanced model.
@ -29,6 +29,17 @@ public interface Instancer<I extends Instance> {
*/
I createInstance();
/**
* Populate arr with new instances of this model.
*
* @param arr An array to fill.
*/
default void createInstances(I[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = createInstance();
}
}
/**
* Steal an instance from another instancer.
* <br>
@ -43,15 +54,4 @@ public interface Instancer<I extends Instance> {
* @param instance The instance to steal.
*/
void stealInstance(@Nullable I instance);
/**
* Populate arr with new instances of this model.
*
* @param arr An array to fill.
*/
default void createInstances(I[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = createInstance();
}
}
}

View File

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.api.instance;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.backend.BackendImplemented;
import dev.engine_room.flywheel.api.model.Model;
@BackendImplemented

View File

@ -22,7 +22,7 @@ public interface FlwApiLink {
<T> IdRegistry<T> createIdRegistry();
Backend getBackend();
Backend getCurrentBackend();
boolean isBackendOn();

View File

@ -5,7 +5,7 @@ import dev.engine_room.flywheel.api.registry.Registry;
import net.minecraft.resources.ResourceLocation;
public interface CutoutShader {
static Registry<CutoutShader> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
Registry<CutoutShader> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
ResourceLocation source();
}

View File

@ -5,7 +5,7 @@ import dev.engine_room.flywheel.api.registry.Registry;
import net.minecraft.resources.ResourceLocation;
public interface FogShader {
static Registry<FogShader> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
Registry<FogShader> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
ResourceLocation source();
}

View File

@ -5,9 +5,9 @@ import dev.engine_room.flywheel.api.registry.Registry;
import net.minecraft.resources.ResourceLocation;
public interface MaterialShaders {
static Registry<MaterialShaders> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
Registry<MaterialShaders> REGISTRY = FlwApiLink.INSTANCE.createRegistry();
ResourceLocation vertexShader();
ResourceLocation vertexSource();
ResourceLocation fragmentShader();
ResourceLocation fragmentSource();
}

View File

@ -31,9 +31,4 @@ public interface Mesh {
* @return A vec4 view.
*/
Vector4fc boundingSphere();
/**
* Free this mesh's resources, memory, etc.
*/
void delete();
}

View File

@ -27,8 +27,6 @@ public interface Model {
*/
Vector4fc boundingSphere();
void delete();
record ConfiguredMesh(Material material, Mesh mesh) {
}
}

View File

@ -2,11 +2,10 @@ package dev.engine_room.flywheel.api.registry;
import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.UnmodifiableView;
import net.minecraft.resources.ResourceLocation;
@ -26,13 +25,11 @@ public interface IdRegistry<T> extends Iterable<T> {
ResourceLocation getIdOrThrow(T object);
@Unmodifiable
@UnmodifiableView
Set<ResourceLocation> getAllIds();
@Unmodifiable
@UnmodifiableView
Collection<T> getAll();
void addFreezeCallback(Consumer<IdRegistry<T>> callback);
boolean isFrozen();
}

View File

@ -1,10 +1,9 @@
package dev.engine_room.flywheel.api.registry;
import java.util.Set;
import java.util.function.Consumer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.UnmodifiableView;
@ApiStatus.NonExtendable
public interface Registry<T> extends Iterable<T> {
@ -12,10 +11,8 @@ public interface Registry<T> extends Iterable<T> {
<S extends T> S registerAndGet(S object);
@Unmodifiable
@UnmodifiableView
Set<T> getAll();
void addFreezeCallback(Consumer<Registry<T>> callback);
boolean isFrozen();
}

View File

@ -1,64 +1,11 @@
package dev.engine_room.flywheel.api.task;
import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.NonExtendable
public interface TaskExecutor extends Executor {
/**
* Wait for <em>all</em> running tasks to finish.
* <br>
* This is useful as a nuclear option, but most of the time you should
* try to use {@link #syncUntil(BooleanSupplier) syncUntil}.
*/
void syncPoint();
/**
* Wait for running tasks, until the given condition is met
* ({@link BooleanSupplier#getAsBoolean()} returns {@code true}).
* <br>
* This method is equivalent to {@code syncWhile(() -> !cond.getAsBoolean())}.
*
* @param cond The condition to wait for.
* @return {@code true} if the condition is met. {@code false} if
* this executor runs out of tasks before the condition is met.
*/
boolean syncUntil(BooleanSupplier cond);
/**
* Wait for running tasks, so long as the given condition is met
* ({@link BooleanSupplier#getAsBoolean()} returns {@code true}).
* <br>
* If this method is called on the
* <br>
* This method is equivalent to {@code syncUntil(() -> !cond.getAsBoolean())}.
*
* @param cond The condition sync on.
* @return {@code true} if the condition is no longer met. {@code false} if
* this executor runs out of tasks while the condition is still met.
*/
boolean syncWhile(BooleanSupplier cond);
/**
* Schedule a task to be run on the main thread.
* <br>
* This method may be called from any thread (including the main thread),
* but the runnable will <em>only</em> be executed once somebody calls
* either {@link #syncPoint()} or {@link #syncUntil(BooleanSupplier)}
* on this task executor's main thread.
* @param runnable The task to run.
*/
void scheduleForMainThread(Runnable runnable);
/**
* Check whether the current thread is this task executor's main thread.
*
* @return {@code true} if the current thread is the main thread.
*/
boolean isMainThread();
/**
* Check for the number of threads this executor uses.
* <br>

View File

@ -1,11 +0,0 @@
package dev.engine_room.flywheel.api.vertex;
public interface VertexView extends MutableVertexList {
long ptr();
void ptr(long ptr);
void vertexCount(int vertexCount);
long stride();
}

View File

@ -1,6 +1,7 @@
package dev.engine_room.flywheel.api.visual;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import net.minecraft.world.level.LevelAccessor;
/**
* An effect is not attached to any formal game object, but allows you to hook into
@ -8,6 +9,8 @@ import dev.engine_room.flywheel.api.visualization.VisualizationContext;
* without any built in support for networking.
*/
public interface Effect {
LevelAccessor level();
/**
* Create a visual that will be keyed by this effect object.
*

View File

@ -4,7 +4,7 @@ import org.jetbrains.annotations.ApiStatus;
import it.unimi.dsi.fastutil.longs.LongSet;
public sealed interface SectionTrackedVisual extends Visual permits ShaderLightVisual, LightUpdatedVisual {
public sealed interface SectionTrackedVisual extends Visual permits LightUpdatedVisual, ShaderLightVisual {
/**
* Set the section collector object.
*

View File

@ -3,7 +3,7 @@ package dev.engine_room.flywheel.api.visualization;
import org.joml.Matrix3fc;
import org.joml.Matrix4fc;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.backend.BackendImplemented;
@BackendImplemented
public interface VisualEmbedding extends VisualizationContext {

View File

@ -9,7 +9,7 @@ public interface VisualManager<T> {
*
* @return The visual count.
*/
int getVisualCount();
int visualCount();
void queueAdd(T obj);

View File

@ -0,0 +1,7 @@
package dev.engine_room.flywheel.api.visualization;
public enum VisualType {
BLOCK_ENTITY,
ENTITY,
EFFECT;
}

View File

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.api.visualization;
import dev.engine_room.flywheel.api.BackendImplemented;
import dev.engine_room.flywheel.api.backend.BackendImplemented;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import net.minecraft.core.Vec3i;

View File

@ -1,14 +1,17 @@
package dev.engine_room.flywheel.api.visualization;
import java.util.SortedSet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.internal.FlwApiLink;
import dev.engine_room.flywheel.api.visual.Effect;
import dev.engine_room.flywheel.api.visual.Visual;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import net.minecraft.core.Vec3i;
import net.minecraft.server.level.BlockDestructionProgress;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -27,52 +30,31 @@ public interface VisualizationManager {
return FlwApiLink.INSTANCE.getVisualizationManagerOrThrow(level);
}
/**
* Call this when you want to run {@link Visual#update}.
* @param blockEntity The block entity whose visual you want to update.
*/
static void queueUpdate(BlockEntity blockEntity) {
Level level = blockEntity.getLevel();
VisualizationManager manager = get(level);
if (manager == null) {
return;
}
Vec3i renderOrigin();
manager.getBlockEntities().queueUpdate(blockEntity);
}
VisualManager<BlockEntity> blockEntities();
VisualManager<Entity> entities();
VisualManager<Effect> effects();
/**
* Call this when you want to run {@link Visual#update}.
* @param entity The entity whose visual you want to update.
* Get the render dispatcher, which can be used to invoke rendering.
* <b>This should only be used by mods which heavily rewrite rendering to restore compatibility with Flywheel
* without mixins.</b>
*/
static void queueUpdate(Entity entity) {
Level level = entity.level();
VisualizationManager manager = get(level);
if (manager == null) {
return;
}
RenderDispatcher renderDispatcher();
manager.getEntities().queueUpdate(entity);
@ApiStatus.NonExtendable
interface RenderDispatcher {
void onStartLevelRender(RenderContext ctx);
void afterBlockEntities(RenderContext ctx);
void afterEntities(RenderContext ctx);
void beforeCrumbling(RenderContext ctx, Long2ObjectMap<SortedSet<BlockDestructionProgress>> destructionProgress);
void afterParticles(RenderContext ctx);
}
/**
* Call this when you want to run {@link Visual#update}.
* @param effect The effect whose visual you want to update.
*/
static void queueUpdate(LevelAccessor level, Effect effect) {
VisualizationManager manager = get(level);
if (manager == null) {
return;
}
manager.getEffects().queueUpdate(effect);
}
Vec3i getRenderOrigin();
VisualManager<BlockEntity> getBlockEntities();
VisualManager<Entity> getEntities();
VisualManager<Effect> getEffects();
}

View File

@ -12,7 +12,6 @@ public final class FlwBackend {
}
public static void init() {
ShaderIndices.init();
Backends.init();
}
}

View File

@ -6,9 +6,9 @@ import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.layout.FloatRepr;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.api.layout.LayoutBuilder;
import dev.engine_room.flywheel.api.vertex.VertexView;
import dev.engine_room.flywheel.backend.gl.array.VertexAttribute;
import dev.engine_room.flywheel.lib.vertex.FullVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.resources.ResourceLocation;
public final class InternalVertex {

View File

@ -1,37 +0,0 @@
package dev.engine_room.flywheel.backend;
import dev.engine_room.flywheel.lib.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.LevelAccessor;
/**
* Stores the set of updates light sections for LightStorage to poll in its frame plan.
*/
public class LightUpdateHolder {
private static final LevelAttached<LightUpdateHolder> HOLDERS = new LevelAttached<>(level -> new LightUpdateHolder());
private final LongSet updatedSections = new LongOpenHashSet();
private LightUpdateHolder() {
}
public static LightUpdateHolder get(LevelAccessor level) {
return HOLDERS.get(level);
}
public LongSet getAndClearUpdatedSections() {
if (updatedSections.isEmpty()) {
return LongSet.of();
}
var out = new LongArraySet(updatedSections);
updatedSections.clear();
return out;
}
public void add(long section) {
updatedSections.add(section);
}
}

View File

@ -0,0 +1,135 @@
package dev.engine_room.flywheel.backend;
import java.util.List;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import dev.engine_room.flywheel.api.material.CutoutShader;
import dev.engine_room.flywheel.api.material.FogShader;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import dev.engine_room.flywheel.api.registry.Registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import net.minecraft.resources.ResourceLocation;
public final class MaterialShaderIndices {
@Nullable
private static Index vertexSources;
@Nullable
private static Index fragmentSources;
@Nullable
private static Index fogSources;
@Nullable
private static Index cutoutSources;
private MaterialShaderIndices() {
}
public static Index vertexSources() {
if (vertexSources == null) {
vertexSources = indexFromRegistry(MaterialShaders.REGISTRY, MaterialShaders::vertexSource);
}
return vertexSources;
}
public static Index fragmentSources() {
if (fragmentSources == null) {
fragmentSources = indexFromRegistry(MaterialShaders.REGISTRY, MaterialShaders::fragmentSource);
}
return fragmentSources;
}
public static Index fogSources() {
if (fogSources == null) {
fogSources = indexFromRegistry(FogShader.REGISTRY, FogShader::source);
}
return fogSources;
}
public static Index cutoutSources() {
if (cutoutSources == null) {
cutoutSources = indexFromRegistry(CutoutShader.REGISTRY, CutoutShader::source);
}
return cutoutSources;
}
public static int vertexIndex(MaterialShaders shaders) {
return vertexSources().index(shaders.vertexSource());
}
public static int fragmentIndex(MaterialShaders shaders) {
return fragmentSources().index(shaders.fragmentSource());
}
public static int fogIndex(FogShader fogShader) {
return fogSources().index(fogShader.source());
}
public static int cutoutIndex(CutoutShader cutoutShader) {
return cutoutSources().index(cutoutShader.source());
}
private static <T> Index indexFromRegistry(Registry<T> registry, Function<T, ResourceLocation> sourceFunc) {
if (!registry.isFrozen()) {
throw new IllegalStateException("Cannot create index from registry that is not frozen!");
}
var builder = new IndexBuilder();
for (T object : registry) {
builder.add(sourceFunc.apply(object));
}
return builder.build();
}
public static class Index {
private final Object2IntMap<ResourceLocation> sources2Index;
private final ObjectList<ResourceLocation> sources;
private Index(IndexBuilder builder) {
this.sources2Index = new Object2IntOpenHashMap<>(builder.sources2Index);
this.sources = new ObjectArrayList<>(builder.sources);
}
public int index(ResourceLocation source) {
return sources2Index.getInt(source);
}
public ResourceLocation get(int index) {
return sources.get(index);
}
@Unmodifiable
public List<ResourceLocation> all() {
return sources;
}
}
private static class IndexBuilder {
private final Object2IntMap<ResourceLocation> sources2Index;
private final ObjectList<ResourceLocation> sources;
private int index = 0;
public IndexBuilder() {
sources2Index = new Object2IntOpenHashMap<>();
sources2Index.defaultReturnValue(-1);
sources = new ObjectArrayList<>();
}
public void add(ResourceLocation source) {
if (sources2Index.putIfAbsent(source, index) == -1) {
sources.add(source);
index++;
}
}
public Index build() {
return new Index(this);
}
}
}

View File

@ -1,170 +0,0 @@
package dev.engine_room.flywheel.backend;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import dev.engine_room.flywheel.api.material.CutoutShader;
import dev.engine_room.flywheel.api.material.FogShader;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import dev.engine_room.flywheel.api.registry.Registry;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
import it.unimi.dsi.fastutil.objects.ObjectLists;
import net.minecraft.resources.ResourceLocation;
public final class ShaderIndices {
@Nullable
private static Index vertexShaders;
@Nullable
private static Index fragmentShaders;
@Nullable
private static Index fogShaders;
@Nullable
private static Index cutoutShaders;
private ShaderIndices() {
}
public static Index materialVertex() {
if (vertexShaders == null) {
throw new IllegalStateException("Not initialized!");
}
return vertexShaders;
}
public static Index materialFragment() {
if (fragmentShaders == null) {
throw new IllegalStateException("Not initialized!");
}
return fragmentShaders;
}
public static Index fog() {
if (fogShaders == null) {
throw new IllegalStateException("Not initialized!");
}
return fogShaders;
}
public static Index cutout() {
if (cutoutShaders == null) {
throw new IllegalStateException("Not initialized!");
}
return cutoutShaders;
}
public static int getVertexShaderIndex(MaterialShaders shaders) {
return materialVertex().index(shaders.vertexShader());
}
public static int getFragmentShaderIndex(MaterialShaders shaders) {
return materialFragment().index(shaders.fragmentShader());
}
public static int getFogShaderIndex(FogShader fogShader) {
return fog().index(fogShader.source());
}
public static int getCutoutShaderIndex(CutoutShader cutoutShader) {
return cutout().index(cutoutShader.source());
}
private static void initMaterialShaders(Registry<MaterialShaders> registry) {
int amount = registry.getAll()
.size();
var vertexShaders = new IndexBuilder(amount);
var fragmentShaders = new IndexBuilder(amount);
for (MaterialShaders shaders : registry) {
vertexShaders.add(shaders.vertexShader());
fragmentShaders.add(shaders.fragmentShader());
}
ShaderIndices.vertexShaders = vertexShaders.build();
ShaderIndices.fragmentShaders = fragmentShaders.build();
}
private static void initFogShaders(Registry<FogShader> registry) {
int amount = registry.getAll()
.size();
var fog = new IndexBuilder(amount);
for (FogShader shaders : registry) {
fog.add(shaders.source());
}
ShaderIndices.fogShaders = fog.build();
}
private static void initCutoutShaders(Registry<CutoutShader> registry) {
int amount = registry.getAll()
.size();
var cutout = new IndexBuilder(amount);
for (CutoutShader shaders : registry) {
cutout.add(shaders.source());
}
ShaderIndices.cutoutShaders = cutout.build();
}
public static void init() {
MaterialShaders.REGISTRY.addFreezeCallback(ShaderIndices::initMaterialShaders);
FogShader.REGISTRY.addFreezeCallback(ShaderIndices::initFogShaders);
CutoutShader.REGISTRY.addFreezeCallback(ShaderIndices::initCutoutShaders);
}
public static class Index {
private final Object2IntMap<ResourceLocation> shaders2Index;
private final ObjectList<ResourceLocation> shaders;
private Index(IndexBuilder builder) {
this.shaders2Index = Object2IntMaps.unmodifiable(builder.shaders2Index);
this.shaders = ObjectLists.unmodifiable(builder.shaders);
}
public int index(ResourceLocation shader) {
return shaders2Index.getInt(shader);
}
@Unmodifiable
public List<ResourceLocation> all() {
return shaders;
}
public ResourceLocation get(int index) {
return shaders.get(index);
}
}
private static class IndexBuilder {
private int index;
private final Object2IntMap<ResourceLocation> shaders2Index;
private final ObjectList<ResourceLocation> shaders;
public IndexBuilder(int amount) {
shaders2Index = new Object2IntOpenHashMap<>();
shaders2Index.defaultReturnValue(-1);
shaders = new ObjectArrayList<>(amount);
}
public void add(ResourceLocation shader) {
if (shaders2Index.putIfAbsent(shader, index) == -1) {
shaders.add(shader);
index++;
}
}
public Index build() {
return new Index(this);
}
}
}

View File

@ -10,7 +10,7 @@ import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.backend.ShaderIndices;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent;
import dev.engine_room.flywheel.backend.compile.core.CompilerStats;
import dev.engine_room.flywheel.backend.compile.core.SourceLoader;
@ -74,7 +74,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createVertexMaterialComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("material_vertex"))
.materialSources(ShaderIndices.materialVertex()
.materialSources(MaterialShaderIndices.vertexSources()
.all())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("_flw_uberMaterialVertexIndex"))
@ -84,7 +84,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createFragmentMaterialComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("material_fragment"))
.materialSources(ShaderIndices.materialFragment()
.materialSources(MaterialShaderIndices.fragmentSources()
.all())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
.switchOn(GlslExpr.variable("_flw_uberMaterialFragmentIndex"))
@ -94,7 +94,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createFogComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("fog"))
.materialSources(ShaderIndices.fog()
.materialSources(MaterialShaderIndices.fogSources()
.all())
.adapt(FnSignature.create()
.returnType("vec4")
@ -108,7 +108,7 @@ public final class FlwPrograms {
@Nullable
private static UberShaderComponent createCutoutComponent(SourceLoader loader) {
return UberShaderComponent.builder(Flywheel.rl("cutout"))
.materialSources(ShaderIndices.cutout()
.materialSources(MaterialShaderIndices.cutoutSources()
.all())
.adapt(FnSignature.create()
.returnType("bool")

View File

@ -8,7 +8,7 @@ import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.lib.util.AtomicBitSet;
import dev.engine_room.flywheel.backend.util.AtomicBitSet;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
public final InstanceType<I> type;

View File

@ -9,11 +9,11 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.lib.util.Pair;
@ -36,13 +36,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, stage), this::createAndDeferInit);
}
public void delete() {
instancers.clear();
initializationQueue.clear();
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType), this::createAndDeferInit);
}
public void flush(LightStorage lightStorage) {
@ -59,9 +54,9 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
.forEach(AbstractInstancer::clear);
}
public abstract void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks);
public abstract void render(VisualType visualType);
public abstract void renderStage(RenderStage stage);
public abstract void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks);
protected abstract <I extends Instance> N create(InstancerKey<I> type);
@ -80,10 +75,6 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
return out;
}
protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) {
}
private static boolean checkAndWarnEmptyModel(Model model) {
if (!model.meshes().isEmpty()) {
return true;
@ -133,4 +124,12 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
}
return byType;
}
public void delete() {
instancers.clear();
initializationQueue.clear();
}
protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) {
}
}

View File

@ -2,37 +2,34 @@ package dev.engine_room.flywheel.backend.engine;
import java.util.List;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.lib.task.Flag;
import dev.engine_room.flywheel.lib.task.NamedFlag;
import dev.engine_room.flywheel.lib.task.SyncedPlan;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.phys.Vec3;
public class EngineImpl implements Engine {
private final DrawManager<? extends AbstractInstancer<?>> drawManager;
private final int sqrMaxOriginDistance;
private final Flag flushFlag = new NamedFlag("flushed");
private final EnvironmentStorage environmentStorage;
private final LightStorage lightStorage;
@ -46,32 +43,18 @@ public class EngineImpl implements Engine {
}
@Override
public VisualizationContext createVisualizationContext(RenderStage stage) {
return new VisualizationContextImpl(stage);
public VisualizationContext createVisualizationContext(VisualType visualType) {
return new VisualizationContextImpl(visualType);
}
@Override
public Plan<RenderContext> createFramePlan() {
return lightStorage.createFramePlan()
.then(SyncedPlan.of(this::flush));
return lightStorage.createFramePlan();
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
flushFlag.lower();
}
drawManager.renderStage(stage);
}
@Override
public void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> crumblingBlocks) {
// Need to wait for flush before we can inspect instancer state.
executor.syncUntil(flushFlag::isRaised);
drawManager.renderCrumbling(crumblingBlocks);
public Vec3i renderOrigin() {
return renderOrigin;
}
@Override
@ -92,13 +75,32 @@ public class EngineImpl implements Engine {
}
@Override
public Vec3i renderOrigin() {
return renderOrigin;
public void lightSections(LongSet sections) {
lightStorage.sections(sections);
}
@Override
public void lightSections(LongSet sections) {
lightStorage.sections(sections);
public void onLightUpdate(SectionPos sectionPos, LightLayer layer) {
lightStorage.onLightUpdate(sectionPos.asLong());
}
@Override
public void setupRender(RenderContext context) {
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.update(context);
environmentStorage.flush();
drawManager.flush(lightStorage);
}
}
@Override
public void render(RenderContext context, VisualType visualType) {
drawManager.render(visualType);
}
@Override
public void renderCrumbling(RenderContext context, List<CrumblingBlock> crumblingBlocks) {
drawManager.renderCrumbling(crumblingBlocks);
}
@Override
@ -107,18 +109,8 @@ public class EngineImpl implements Engine {
lightStorage.delete();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(environment, type, model, stage);
}
private void flush(RenderContext ctx) {
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.update(ctx);
environmentStorage.flush();
drawManager.flush(lightStorage);
}
flushFlag.raise();
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType) {
return drawManager.getInstancer(environment, type, model, visualType);
}
public EnvironmentStorage environmentStorage() {
@ -131,11 +123,11 @@ public class EngineImpl implements Engine {
private class VisualizationContextImpl implements VisualizationContext {
private final InstancerProviderImpl instancerProvider;
private final RenderStage stage;
private final VisualType visualType;
public VisualizationContextImpl(RenderStage stage) {
instancerProvider = new InstancerProviderImpl(EngineImpl.this, stage);
this.stage = stage;
public VisualizationContextImpl(VisualType visualType) {
instancerProvider = new InstancerProviderImpl(EngineImpl.this, visualType);
this.visualType = visualType;
}
@Override
@ -150,7 +142,7 @@ public class EngineImpl implements Engine {
@Override
public VisualEmbedding createEmbedding() {
var out = new EmbeddedEnvironment(EngineImpl.this, stage);
var out = new EmbeddedEnvironment(EngineImpl.this, visualType);
environmentStorage.track(out);
return out;
}

View File

@ -1,11 +1,11 @@
package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model,
RenderStage stage) {
VisualType visualType) {
}

View File

@ -1,16 +1,16 @@
package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.embed.GlobalEnvironment;
public record InstancerProviderImpl(EngineImpl engine, RenderStage renderStage) implements InstancerProvider {
public record InstancerProviderImpl(EngineImpl engine, VisualType visualType) implements InstancerProvider {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, renderStage);
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType);
}
}

View File

@ -5,16 +5,14 @@ import java.util.BitSet;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.backend.LightUpdateHolder;
import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.lib.task.SimplePlan;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
@ -53,6 +51,7 @@ public class LightStorage {
private final BitSet changed = new BitSet();
private boolean needsLutRebuild = false;
private final LongSet updatedSections = new LongOpenHashSet();
@Nullable
private LongSet requestedSections;
@ -72,11 +71,12 @@ public class LightStorage {
requestedSections = sections;
}
public Plan<RenderContext> createFramePlan() {
return SimplePlan.of(() -> {
var updatedSections = LightUpdateHolder.get(level)
.getAndClearUpdatedSections();
public void onLightUpdate(long section) {
updatedSections.add(section);
}
public <C> Plan<C> createFramePlan() {
return SimplePlan.of(() -> {
if (updatedSections.isEmpty() && requestedSections == null) {
return;
}
@ -87,15 +87,15 @@ public class LightStorage {
LongSet sectionsToCollect;
if (requestedSections == null) {
// If none were requested, then we need to collect all sections that received updates.
sectionsToCollect = new LongArraySet();
sectionsToCollect = new LongOpenHashSet();
} else {
// If we did receive a new set of requested sections, we only
// need to collect the sections that weren't yet tracked.
sectionsToCollect = requestedSections;
sectionsToCollect = new LongOpenHashSet(requestedSections);
sectionsToCollect.removeAll(section2ArenaIndex.keySet());
}
// updatedSections contains all sections than received light updates,
// updatedSections contains all sections that received light updates,
// but we only care about its intersection with our tracked sections.
for (long updatedSection : updatedSections) {
// Since sections contain the border light of their neighbors, we need to collect the neighbors as well.
@ -115,6 +115,7 @@ public class LightStorage {
// TODO: Should this be done in parallel?
sectionsToCollect.forEach(this::collectSection);
updatedSections.clear();
requestedSections = null;
});
}
@ -130,7 +131,7 @@ public class LightStorage {
var entry = it.next();
var section = entry.getLongKey();
if (!this.requestedSections.contains(section)) {
if (!requestedSections.contains(section)) {
arena.free(entry.getIntValue());
needsLutRebuild = true;
it.remove();

View File

@ -4,7 +4,7 @@ import dev.engine_room.flywheel.api.material.DepthTest;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.material.Transparency;
import dev.engine_room.flywheel.api.material.WriteMask;
import dev.engine_room.flywheel.backend.ShaderIndices;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import net.minecraft.util.Mth;
// Materials are unpacked in "flywheel:flywheel/internal/packed_material.glsl"
@ -53,13 +53,8 @@ public final class MaterialEncoder {
}
public static int packFogAndCutout(Material material) {
var fog = ShaderIndices.fog()
.index(material.fog()
.source());
var cutout = ShaderIndices.cutout()
.index(material.cutout()
.source());
var fog = MaterialShaderIndices.fogIndex(material.fog());
var cutout = MaterialShaderIndices.cutoutIndex(material.cutout());
return fog & 0xFFFF | (cutout & 0xFFFF) << 16;
}

View File

@ -9,13 +9,13 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.api.vertex.VertexView;
import dev.engine_room.flywheel.backend.InternalVertex;
import dev.engine_room.flywheel.backend.gl.GlPrimitive;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.backend.util.ReferenceCounted;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.vertex.VertexView;
public class MeshPool {
private final VertexView vertexView;

View File

@ -6,13 +6,13 @@ import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
@ -20,7 +20,7 @@ import net.minecraft.core.Vec3i;
public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private final EngineImpl engine;
private final RenderStage renderStage;
private final VisualType visualType;
@Nullable
private final EmbeddedEnvironment parent;
private final InstancerProvider instancerProvider;
@ -32,22 +32,22 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private boolean deleted = false;
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage, @Nullable EmbeddedEnvironment parent) {
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, @Nullable EmbeddedEnvironment parent) {
this.engine = engine;
this.renderStage = renderStage;
this.visualType = visualType;
this.parent = parent;
instancerProvider = new InstancerProvider() {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
// Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(EmbeddedEnvironment.this, type, model, renderStage);
return engine.instancer(EmbeddedEnvironment.this, type, model, visualType);
}
};
}
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
this(engine, renderStage, null);
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType) {
this(engine, visualType, null);
}
@Override
@ -68,7 +68,7 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
@Override
public VisualEmbedding createEmbedding() {
var out = new EmbeddedEnvironment(engine, renderStage, this);
var out = new EmbeddedEnvironment(engine, visualType, this);
engine.environmentStorage()
.track(out);
return out;

View File

@ -14,11 +14,11 @@ import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
@ -30,7 +30,7 @@ import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::stage)
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
@ -42,7 +42,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final IndirectBuffers buffers;
private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
private final Map<VisualType, List<MultiDraw>> multiDraws = new EnumMap<>(VisualType.class);
private final IndirectPrograms programs;
private final GlProgram cullProgram;
@ -145,36 +145,36 @@ public class IndirectCullingGroup<I extends Instance> {
return indirectDraws.isEmpty() || instanceCountThisFrame == 0;
}
private boolean nothingToDo(RenderStage stage) {
return nothingToDo() || !multiDraws.containsKey(stage);
private boolean nothingToDo(VisualType visualType) {
return nothingToDo() || !multiDraws.containsKey(visualType);
}
private void sortDraws() {
multiDraws.clear();
// sort by stage, then material
// sort by visual type, then material
indirectDraws.sort(DRAW_COMPARATOR);
for (int start = 0, i = 0; i < indirectDraws.size(); i++) {
var draw1 = indirectDraws.get(i);
var material1 = draw1.material();
var stage1 = draw1.stage();
var visualType1 = draw1.visualType();
// if the next draw call has a different RenderStage or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || stage1 != indirectDraws.get(i + 1)
.stage() || !material1.equals(indirectDraws.get(i + 1)
// if the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || visualType1 != indirectDraws.get(i + 1)
.visualType() || !material1.equals(indirectDraws.get(i + 1)
.material())) {
multiDraws.computeIfAbsent(stage1, s -> new ArrayList<>())
multiDraws.computeIfAbsent(visualType1, s -> new ArrayList<>())
.add(new MultiDraw(material1, start, i + 1));
start = i + 1;
}
}
}
public boolean hasStage(RenderStage stage) {
return multiDraws.containsKey(stage);
public boolean hasVisualType(VisualType visualType) {
return multiDraws.containsKey(visualType);
}
public void add(IndirectInstancer<I> instancer, Model model, RenderStage stage, MeshPool meshPool) {
public void add(IndirectInstancer<I> instancer, Model model, VisualType visualType, MeshPool meshPool) {
instancer.modelIndex = instancers.size();
instancers.add(instancer);
@ -183,7 +183,7 @@ public class IndirectCullingGroup<I extends Instance> {
var entry = meshes.get(i);
MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh());
var draw = new IndirectDraw(instancer, entry.material(), mesh, stage, i);
var draw = new IndirectDraw(instancer, entry.material(), mesh, visualType, i);
indirectDraws.add(draw);
instancer.addDraw(draw);
}
@ -191,8 +191,8 @@ public class IndirectCullingGroup<I extends Instance> {
needsDrawSort = true;
}
public void submit(RenderStage stage) {
if (nothingToDo(stage)) {
public void submit(VisualType visualType) {
if (nothingToDo(visualType)) {
return;
}
@ -205,7 +205,7 @@ public class IndirectCullingGroup<I extends Instance> {
var flwBaseDraw = drawProgram.getUniformLocation("_flw_baseDraw");
for (var multiDraw : multiDraws.get(stage)) {
for (var multiDraw : multiDraws.get(visualType)) {
glUniform1ui(flwBaseDraw, multiDraw.start);
MaterialRenderState.setup(multiDraw.material);

View File

@ -2,9 +2,9 @@ package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.backend.ShaderIndices;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MeshPool;
@ -12,7 +12,7 @@ public class IndirectDraw {
private final IndirectInstancer<?> instancer;
private final Material material;
private final MeshPool.PooledMesh mesh;
private final RenderStage stage;
private final VisualType visualType;
private final int indexOfMeshInModel;
private final int materialVertexIndex;
@ -21,17 +21,17 @@ public class IndirectDraw {
private final int packedMaterialProperties;
private boolean deleted;
public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, RenderStage stage, int indexOfMeshInModel) {
public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, VisualType visualType, int indexOfMeshInModel) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
this.stage = stage;
this.visualType = visualType;
this.indexOfMeshInModel = indexOfMeshInModel;
mesh.acquire();
this.materialVertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders());
this.materialFragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders());
this.materialVertexIndex = MaterialShaderIndices.vertexIndex(material.shaders());
this.materialFragmentIndex = MaterialShaderIndices.fragmentIndex(material.shaders());
this.packedFogAndCutout = MaterialEncoder.packFogAndCutout(material);
this.packedMaterialProperties = MaterialEncoder.packProperties(material);
}
@ -48,8 +48,8 @@ public class IndirectDraw {
return mesh;
}
public RenderStage stage() {
return stage;
public VisualType visualType() {
return visualType;
}
public int indexOfMeshInModel() {
@ -80,8 +80,8 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex
MemoryUtil.memPutInt(ptr + 24, ShaderIndices.getVertexShaderIndex(materialOverride.shaders())); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, ShaderIndices.getFragmentShaderIndex(materialOverride.shaders())); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 24, MaterialShaderIndices.vertexIndex(materialOverride.shaders())); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, MaterialShaderIndices.fragmentIndex(materialOverride.shaders())); // materialFragmentIndex
MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packFogAndCutout(materialOverride)); // packedFogAndCutout
MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties
}

View File

@ -11,8 +11,8 @@ import java.util.List;
import java.util.Map;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
@ -63,24 +63,24 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) {
var groupKey = new GroupKey<>(key.type(), key.environment());
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.instanceType(), t.environment(), programs));
group.add((IndirectInstancer<I>) instancer, key.model(), key.stage(), meshPool);
group.add((IndirectInstancer<I>) instancer, key.model(), key.visualType(), meshPool);
}
public boolean hasStage(RenderStage stage) {
public boolean hasVisualType(VisualType visualType) {
for (var group : cullingGroups.values()) {
if (group.hasStage(stage)) {
if (group.hasVisualType(visualType)) {
return true;
}
}
return false;
}
public void renderStage(RenderStage stage) {
if (!hasStage(stage)) {
public void render(VisualType visualType) {
if (!hasVisualType(visualType)) {
return;
}
try (var restoreState = GlStateTracker.getRestoreState()) {
try (var state = GlStateTracker.getRestoreState()) {
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
@ -88,7 +88,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
Uniforms.bindAll();
for (var group : cullingGroups.values()) {
group.submit(stage);
group.submit(visualType);
}
MaterialRenderState.reset();

View File

@ -25,7 +25,7 @@ public class ResizableStorageBuffer extends GlObject {
}
public void ensureCapacity(long capacity) {
FlwMemoryTracker._freeGPUMemory(this.capacity);
FlwMemoryTracker._freeGpuMemory(this.capacity);
if (this.capacity > 0) {
int oldHandle = handle();
@ -42,7 +42,7 @@ public class ResizableStorageBuffer extends GlObject {
glNamedBufferStorage(handle(), capacity, 0);
}
this.capacity = capacity;
FlwMemoryTracker._allocGPUMemory(this.capacity);
FlwMemoryTracker._allocGpuMemory(this.capacity);
}
@Override
@ -53,6 +53,6 @@ public class ResizableStorageBuffer extends GlObject {
@Override
public void delete() {
super.delete();
FlwMemoryTracker._freeGPUMemory(capacity);
FlwMemoryTracker._freeGpuMemory(capacity);
}
}

View File

@ -77,7 +77,7 @@ public class StagingBuffer {
totalAvailable = capacity;
FlwMemoryTracker._allocCPUMemory(capacity);
FlwMemoryTracker._allocCpuMemory(capacity);
scatterProgram = programs.getScatterProgram();
}
@ -220,7 +220,7 @@ public class StagingBuffer {
transfers.delete();
scatterList.delete();
FlwMemoryTracker._freeCPUMemory(capacity);
FlwMemoryTracker._freeCpuMemory(capacity);
}
private MemoryBlock getScratch(long size) {

View File

@ -7,11 +7,11 @@ import java.util.Map;
import org.lwjgl.opengl.GL32;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.ShaderIndices;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
@ -33,9 +33,9 @@ import net.minecraft.client.resources.model.ModelBakery;
public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
/**
* The set of draw calls to make in each {@link RenderStage}.
* The set of draw calls to make for each {@link VisualType}.
*/
private final Map<RenderStage, InstancedRenderStage> stages = new EnumMap<>(RenderStage.class);
private final Map<VisualType, InstancedRenderStage> stages = new EnumMap<>(VisualType.class);
private final InstancingPrograms programs;
/**
* A map of vertex types to their mesh pools.
@ -74,9 +74,9 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
}
});
for (InstancedRenderStage instancedRenderStage : stages.values()) {
for (InstancedRenderStage stage : stages.values()) {
// Remove the draw calls for any instancers we deleted.
instancedRenderStage.flush();
stage.flush();
}
meshPool.flush();
@ -85,10 +85,10 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
}
@Override
public void renderStage(RenderStage stage) {
var drawSet = stages.get(stage);
public void render(VisualType visualType) {
var stage = stages.get(visualType);
if (drawSet == null || drawSet.isEmpty()) {
if (stage == null || stage.isEmpty()) {
return;
}
@ -98,7 +98,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
TextureBinder.bindLightAndOverlay();
light.bind();
drawSet.draw(instanceTexture, programs);
stage.draw(instanceTexture, programs);
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
@ -133,7 +133,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
protected <I extends Instance> void initialize(InstancerKey<I> key, InstancedInstancer<?> instancer) {
instancer.init();
InstancedRenderStage instancedRenderStage = stages.computeIfAbsent(key.stage(), $ -> new InstancedRenderStage());
InstancedRenderStage stage = stages.computeIfAbsent(key.visualType(), $ -> new InstancedRenderStage());
var meshes = key.model()
.meshes();
@ -144,7 +144,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
GroupKey<?> groupKey = new GroupKey<>(key.type(), key.environment());
InstancedDraw instancedDraw = new InstancedDraw(instancer, mesh, groupKey, entry.material(), i);
instancedRenderStage.put(groupKey, instancedDraw);
stage.put(groupKey, instancedDraw);
instancer.addDrawCall(instancedDraw);
}
}
@ -204,8 +204,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
public static void uploadMaterialUniform(GlProgram program, Material material) {
int uniformLocation = program.getUniformLocation("_flw_packedMaterial");
int vertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders());
int fragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders());
int vertexIndex = MaterialShaderIndices.vertexIndex(material.shaders());
int fragmentIndex = MaterialShaderIndices.fragmentIndex(material.shaders());
int packedFogAndCutout = MaterialEncoder.packFogAndCutout(material);
int packedMaterialProperties = MaterialEncoder.packProperties(material);
GL32.glUniform4ui(uniformLocation, vertexIndex, fragmentIndex, packedFogAndCutout, packedMaterialProperties);

View File

@ -1,13 +1,14 @@
package dev.engine_room.flywheel.backend.engine.uniform;
import org.joml.Math;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor;
import dev.engine_room.flywheel.lib.math.MatrixMath;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
@ -63,7 +64,7 @@ public final class FrameUniforms extends UniformWriter {
setPrev();
Vec3i renderOrigin = VisualizationManager.getOrThrow(context.level())
.getRenderOrigin();
.renderOrigin();
var camera = context.camera();
Vec3 cameraPos = camera.getPosition();
var camX = (float) (cameraPos.x - renderOrigin.getX());
@ -85,7 +86,7 @@ public final class FrameUniforms extends UniformWriter {
}
if (firstWrite || !frustumPaused || frustumCapture) {
MatrixMath.writePackedFrustumPlanes(ptr, VIEW_PROJECTION);
writePackedFrustumPlanes(ptr, VIEW_PROJECTION);
frustumCapture = false;
}
@ -170,4 +171,118 @@ public final class FrameUniforms extends UniformWriter {
Vec3 cameraPos = camera.getPosition();
return writeInFluidAndBlock(ptr, level, blockPos, cameraPos);
}
/**
* Writes the frustum planes of the given projection matrix to the given buffer.<p>
* Uses a different format that is friendly towards an optimized instruction-parallel
* implementation of sphere-frustum intersection.<p>
* The format is as follows:<p>
* {@code vec4(nxX, pxX, nyX, pyX)}<br>
* {@code vec4(nxY, pxY, nyY, pyY)}<br>
* {@code vec4(nxZ, pxZ, nyZ, pyZ)}<br>
* {@code vec4(nxW, pxW, nyW, pyW)}<br>
* {@code vec2(nzX, pzX)}<br>
* {@code vec2(nzY, pzY)}<br>
* {@code vec2(nzZ, pzZ)}<br>
* {@code vec2(nzW, pzW)}<br>
* <p>
* Writes 96 bytes to the buffer.
*
* @param ptr The buffer to write the planes to.
* @param m The projection matrix to compute the frustum planes for.
*/
private static void writePackedFrustumPlanes(long ptr, Matrix4f m) {
float nxX, nxY, nxZ, nxW;
float pxX, pxY, pxZ, pxW;
float nyX, nyY, nyZ, nyW;
float pyX, pyY, pyZ, pyW;
float nzX, nzY, nzZ, nzW;
float pzX, pzY, pzZ, pzW;
float invl;
nxX = m.m03() + m.m00();
nxY = m.m13() + m.m10();
nxZ = m.m23() + m.m20();
nxW = m.m33() + m.m30();
invl = Math.invsqrt(nxX * nxX + nxY * nxY + nxZ * nxZ);
nxX *= invl;
nxY *= invl;
nxZ *= invl;
nxW *= invl;
pxX = m.m03() - m.m00();
pxY = m.m13() - m.m10();
pxZ = m.m23() - m.m20();
pxW = m.m33() - m.m30();
invl = Math.invsqrt(pxX * pxX + pxY * pxY + pxZ * pxZ);
pxX *= invl;
pxY *= invl;
pxZ *= invl;
pxW *= invl;
nyX = m.m03() + m.m01();
nyY = m.m13() + m.m11();
nyZ = m.m23() + m.m21();
nyW = m.m33() + m.m31();
invl = Math.invsqrt(nyX * nyX + nyY * nyY + nyZ * nyZ);
nyX *= invl;
nyY *= invl;
nyZ *= invl;
nyW *= invl;
pyX = m.m03() - m.m01();
pyY = m.m13() - m.m11();
pyZ = m.m23() - m.m21();
pyW = m.m33() - m.m31();
invl = Math.invsqrt(pyX * pyX + pyY * pyY + pyZ * pyZ);
pyX *= invl;
pyY *= invl;
pyZ *= invl;
pyW *= invl;
nzX = m.m03() + m.m02();
nzY = m.m13() + m.m12();
nzZ = m.m23() + m.m22();
nzW = m.m33() + m.m32();
invl = Math.invsqrt(nzX * nzX + nzY * nzY + nzZ * nzZ);
nzX *= invl;
nzY *= invl;
nzZ *= invl;
nzW *= invl;
pzX = m.m03() - m.m02();
pzY = m.m13() - m.m12();
pzZ = m.m23() - m.m22();
pzW = m.m33() - m.m32();
invl = Math.invsqrt(pzX * pzX + pzY * pzY + pzZ * pzZ);
pzX *= invl;
pzY *= invl;
pzZ *= invl;
pzW *= invl;
MemoryUtil.memPutFloat(ptr, nxX);
MemoryUtil.memPutFloat(ptr + 4, pxX);
MemoryUtil.memPutFloat(ptr + 8, nyX);
MemoryUtil.memPutFloat(ptr + 12, pyX);
MemoryUtil.memPutFloat(ptr + 16, nxY);
MemoryUtil.memPutFloat(ptr + 20, pxY);
MemoryUtil.memPutFloat(ptr + 24, nyY);
MemoryUtil.memPutFloat(ptr + 28, pyY);
MemoryUtil.memPutFloat(ptr + 32, nxZ);
MemoryUtil.memPutFloat(ptr + 36, pxZ);
MemoryUtil.memPutFloat(ptr + 40, nyZ);
MemoryUtil.memPutFloat(ptr + 44, pyZ);
MemoryUtil.memPutFloat(ptr + 48, nxW);
MemoryUtil.memPutFloat(ptr + 52, pxW);
MemoryUtil.memPutFloat(ptr + 56, nyW);
MemoryUtil.memPutFloat(ptr + 60, pyW);
MemoryUtil.memPutFloat(ptr + 64, nzX);
MemoryUtil.memPutFloat(ptr + 68, pzX);
MemoryUtil.memPutFloat(ptr + 72, nzY);
MemoryUtil.memPutFloat(ptr + 76, pzY);
MemoryUtil.memPutFloat(ptr + 80, nzZ);
MemoryUtil.memPutFloat(ptr + 84, pzZ);
MemoryUtil.memPutFloat(ptr + 88, nzW);
MemoryUtil.memPutFloat(ptr + 92, pzW);
}
}

View File

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.backend.engine.uniform;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.RenderContext;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;

View File

@ -2,7 +2,7 @@ package dev.engine_room.flywheel.backend.engine.uniform;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.backend.FlwBackendXplat;
import dev.engine_room.flywheel.backend.mixin.AbstractClientPlayerAccessor;
import net.minecraft.client.Minecraft;

View File

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.backend.engine.uniform;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
public final class Uniforms {

View File

@ -27,10 +27,10 @@ public class GlBuffer extends GlObject {
}
public void upload(long ptr, long size) {
FlwMemoryTracker._freeGPUMemory(this.size);
FlwMemoryTracker._freeGpuMemory(this.size);
Buffer.IMPL.data(handle(), size, ptr, usage.glEnum);
this.size = size;
FlwMemoryTracker._allocGPUMemory(this.size);
FlwMemoryTracker._allocGpuMemory(this.size);
}
public void uploadSpan(long offset, MemoryBlock memoryBlock) {
@ -47,6 +47,6 @@ public class GlBuffer extends GlObject {
protected void deleteInternal(int handle) {
GlStateManager._glDeleteBuffers(handle);
FlwMemoryTracker._freeGPUMemory(size);
FlwMemoryTracker._freeGpuMemory(size);
}
}

View File

@ -1,29 +0,0 @@
package dev.engine_room.flywheel.backend.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.engine_room.flywheel.backend.LightUpdateHolder;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
@Mixin(ClientChunkCache.class)
abstract class ClientChunkCacheMixin {
@Shadow
@Final
ClientLevel level;
@Inject(method = "onLightUpdate", at = @At("HEAD"))
private void flywheel$backend$onLightUpdate(LightLayer layer, SectionPos pos, CallbackInfo ci) {
// This is duplicated from code in impl, but I'm not sure that it
// makes sense to be generically passed to backends.
LightUpdateHolder.get(level)
.add(pos.asLong());
}
}

View File

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.lib.util;
package dev.engine_room.flywheel.backend.util;
import java.util.Arrays;
import java.util.BitSet;

View File

@ -6,7 +6,6 @@
"refmap": "backend-flywheel.refmap.json",
"client": [
"AbstractClientPlayerAccessor",
"ClientChunkCacheMixin",
"GlStateManagerMixin",
"LevelRendererAccessor",
"OptionsMixin",

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.lib.backend;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Supplier;
@ -10,7 +11,7 @@ import dev.engine_room.flywheel.api.backend.Engine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor;
public class SimpleBackend implements Backend {
public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory;
private final Supplier<Backend> fallback;
private final BooleanSupplier isSupported;
@ -45,9 +46,9 @@ public class SimpleBackend implements Backend {
return isSupported.getAsBoolean();
}
public static class Builder {
public static final class Builder {
private Function<LevelAccessor, Engine> engineFactory;
private Supplier<Backend> fallback = BackendManager::getOffBackend;
private Supplier<Backend> fallback = BackendManager::offBackend;
private BooleanSupplier isSupported;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
@ -66,6 +67,10 @@ public class SimpleBackend implements Backend {
}
public Backend register(ResourceLocation id) {
Objects.requireNonNull(engineFactory);
Objects.requireNonNull(fallback);
Objects.requireNonNull(isSupported);
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineFactory, fallback, isSupported));
}
}

View File

@ -3,12 +3,13 @@ package dev.engine_room.flywheel.lib.instance;
import dev.engine_room.flywheel.api.instance.InstanceHandle;
import dev.engine_room.flywheel.api.instance.InstanceType;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.util.FastColor;
public abstract class ColoredLitInstance extends AbstractInstance implements FlatLit {
public byte r = (byte) 0xFF;
public byte g = (byte) 0xFF;
public byte b = (byte) 0xFF;
public byte a = (byte) 0xFF;
public byte red = (byte) 0xFF;
public byte green = (byte) 0xFF;
public byte blue = (byte) 0xFF;
public byte alpha = (byte) 0xFF;
public int overlay = OverlayTexture.NO_OVERLAY;
public int light = 0;
@ -17,39 +18,34 @@ public abstract class ColoredLitInstance extends AbstractInstance implements Fla
super(type, handle);
}
public ColoredLitInstance color(int color) {
return color(color, false);
public ColoredLitInstance colorArgb(int argb) {
return color(FastColor.ARGB32.red(argb), FastColor.ARGB32.green(argb), FastColor.ARGB32.blue(argb), FastColor.ARGB32.alpha(argb));
}
public ColoredLitInstance color(int color, boolean alpha) {
byte r = (byte) ((color >> 16) & 0xFF);
byte g = (byte) ((color >> 8) & 0xFF);
byte b = (byte) (color & 0xFF);
if (alpha) {
byte a = (byte) ((color >> 24) & 0xFF);
return color(r, g, b, a);
} else {
return color(r, g, b);
}
public ColoredLitInstance colorRgb(int rgb) {
return color(FastColor.ARGB32.red(rgb), FastColor.ARGB32.green(rgb), FastColor.ARGB32.blue(rgb));
}
public ColoredLitInstance color(int r, int g, int b) {
return color((byte) r, (byte) g, (byte) b);
public ColoredLitInstance color(int red, int green, int blue, int alpha) {
return color((byte) red, (byte) green, (byte) blue, (byte) alpha);
}
public ColoredLitInstance color(byte r, byte g, byte b) {
this.r = r;
this.g = g;
this.b = b;
public ColoredLitInstance color(int red, int green, int blue) {
return color((byte) red, (byte) green, (byte) blue);
}
public ColoredLitInstance color(byte red, byte green, byte blue, byte alpha) {
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
return this;
}
public ColoredLitInstance color(byte r, byte g, byte b, byte a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
public ColoredLitInstance color(byte red, byte green, byte blue) {
this.red = red;
this.green = green;
this.blue = blue;
return this;
}

View File

@ -17,15 +17,15 @@ import net.minecraft.client.renderer.LightTexture;
public interface FlatLit extends Instance {
/**
* Set the packed light value for this instance.
* @param packedLight Packed block and sky light per {@link LightTexture#pack(int, int)}
* @param packedLight the packed light per {@link LightTexture#pack(int, int)}
* @return {@code this} for chaining
*/
FlatLit light(int packedLight);
/**
* Set the block and sky light values for this instance.
* @param blockLight Block light value
* @param skyLight Sky light value
* @param blockLight the block light value
* @param skyLight the sky light value
* @return {@code this} for chaining
*/
default FlatLit light(int blockLight, int skyLight) {

View File

@ -20,10 +20,10 @@ public final class InstanceTypes {
.matrix("normal", FloatRepr.FLOAT, 3)
.build())
.writer((ptr, instance) -> {
MemoryUtil.memPutByte(ptr, instance.r);
MemoryUtil.memPutByte(ptr + 1, instance.g);
MemoryUtil.memPutByte(ptr + 2, instance.b);
MemoryUtil.memPutByte(ptr + 3, instance.a);
MemoryUtil.memPutByte(ptr, instance.red);
MemoryUtil.memPutByte(ptr + 1, instance.green);
MemoryUtil.memPutByte(ptr + 2, instance.blue);
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model);
@ -43,10 +43,10 @@ public final class InstanceTypes {
.vector("rotation", FloatRepr.FLOAT, 4)
.build())
.writer((ptr, instance) -> {
MemoryUtil.memPutByte(ptr, instance.r);
MemoryUtil.memPutByte(ptr + 1, instance.g);
MemoryUtil.memPutByte(ptr + 2, instance.b);
MemoryUtil.memPutByte(ptr + 3, instance.a);
MemoryUtil.memPutByte(ptr, instance.red);
MemoryUtil.memPutByte(ptr + 1, instance.green);
MemoryUtil.memPutByte(ptr + 2, instance.blue);
MemoryUtil.memPutByte(ptr + 3, instance.alpha);
ExtraMemoryOps.put2x16(ptr + 4, instance.overlay);
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
MemoryUtil.memPutFloat(ptr + 12, instance.posX);

View File

@ -1,7 +1,8 @@
package dev.engine_room.flywheel.lib.instance;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.joml.Quaternionfc;
import org.joml.Vector3fc;
import dev.engine_room.flywheel.api.instance.InstanceHandle;
import dev.engine_room.flywheel.api.instance.InstanceType;
@ -22,72 +23,83 @@ public class OrientedInstance extends ColoredLitInstance implements Rotate<Orien
super(type, handle);
}
public OrientedInstance setPosition(float x, float y, float z) {
public OrientedInstance position(float x, float y, float z) {
posX = x;
posY = y;
posZ = z;
return this;
}
public OrientedInstance setPosition(Vector3f pos) {
return setPosition(pos.x(), pos.y(), pos.z());
public OrientedInstance position(Vector3fc pos) {
return position(pos.x(), pos.y(), pos.z());
}
public OrientedInstance setPosition(Vec3i pos) {
return setPosition(pos.getX(), pos.getY(), pos.getZ());
public OrientedInstance position(Vec3i pos) {
return position(pos.getX(), pos.getY(), pos.getZ());
}
public OrientedInstance setPosition(Vec3 pos) {
return setPosition((float) pos.x(), (float) pos.y(), (float) pos.z());
public OrientedInstance position(Vec3 pos) {
return position((float) pos.x(), (float) pos.y(), (float) pos.z());
}
public OrientedInstance resetPosition() {
return setPosition(0, 0, 0);
public OrientedInstance zeroPosition() {
return position(0, 0, 0);
}
public OrientedInstance nudgePosition(float x, float y, float z) {
public OrientedInstance translatePosition(float x, float y, float z) {
posX += x;
posY += y;
posZ += z;
return this;
}
public OrientedInstance setPivot(float x, float y, float z) {
public OrientedInstance pivot(float x, float y, float z) {
pivotX = x;
pivotY = y;
pivotZ = z;
return this;
}
public OrientedInstance setPivot(Vector3f pos) {
return setPivot(pos.x(), pos.y(), pos.z());
public OrientedInstance pivot(Vector3fc pos) {
return pivot(pos.x(), pos.y(), pos.z());
}
public OrientedInstance setPivot(Vec3i pos) {
return setPivot(pos.getX(), pos.getY(), pos.getZ());
public OrientedInstance pivot(Vec3i pos) {
return pivot(pos.getX(), pos.getY(), pos.getZ());
}
public OrientedInstance setPivot(Vec3 pos) {
return setPivot((float) pos.x(), (float) pos.y(), (float) pos.z());
public OrientedInstance pivot(Vec3 pos) {
return pivot((float) pos.x(), (float) pos.y(), (float) pos.z());
}
public OrientedInstance setRotation(Quaternionf q) {
public OrientedInstance centerPivot() {
return pivot(0.5f, 0.5f, 0.5f);
}
public OrientedInstance translatePivot(float x, float y, float z) {
pivotX += x;
pivotY += y;
pivotZ += z;
return this;
}
public OrientedInstance rotation(Quaternionfc q) {
rotation.set(q);
return this;
}
public OrientedInstance setRotation(float x, float y, float z, float w) {
public OrientedInstance rotation(float x, float y, float z, float w) {
rotation.set(x, y, z, w);
return this;
}
public OrientedInstance resetRotation() {
public OrientedInstance identityRotation() {
rotation.identity();
return this;
}
@Override
public OrientedInstance rotate(Quaternionf quaternion) {
public OrientedInstance rotate(Quaternionfc quaternion) {
rotation.mul(quaternion);
return this;
}

View File

@ -4,14 +4,13 @@ import dev.engine_room.flywheel.api.instance.InstanceHandle;
import dev.engine_room.flywheel.api.instance.InstanceType;
public class ShadowInstance extends AbstractInstance {
public float x, y, z;
public float entityX, entityZ;
public float sizeX, sizeZ;
public float alpha;
public float radius;
public ShadowInstance(InstanceType<?> type, InstanceHandle handle) {
public ShadowInstance(InstanceType<? extends ShadowInstance> type, InstanceHandle handle) {
super(type, handle);
}
}

View File

@ -9,7 +9,7 @@ import dev.engine_room.flywheel.api.instance.InstanceWriter;
import dev.engine_room.flywheel.api.layout.Layout;
import net.minecraft.resources.ResourceLocation;
public class SimpleInstanceType<I extends Instance> implements InstanceType<I> {
public final class SimpleInstanceType<I extends Instance> implements InstanceType<I> {
private final Factory<I> factory;
private final Layout layout;
private final InstanceWriter<I> writer;
@ -58,7 +58,7 @@ public class SimpleInstanceType<I extends Instance> implements InstanceType<I> {
I create(InstanceType<I> type, InstanceHandle handle);
}
public static class Builder<I extends Instance> {
public static final class Builder<I extends Instance> {
private final Factory<I> factory;
private Layout layout;
private InstanceWriter<I> writer;

View File

@ -1,8 +1,10 @@
package dev.engine_room.flywheel.lib.instance;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Matrix4fc;
import org.joml.Quaternionfc;
import com.mojang.blaze3d.vertex.PoseStack;
@ -12,7 +14,6 @@ import dev.engine_room.flywheel.lib.transform.Transform;
import net.minecraft.util.Mth;
public class TransformedInstance extends ColoredLitInstance implements Transform<TransformedInstance> {
public final Matrix4f model = new Matrix4f();
public final Matrix3f normal = new Matrix3f();
@ -21,21 +22,34 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
}
@Override
public TransformedInstance mulPose(Matrix4f pose) {
public TransformedInstance mulPose(Matrix4fc pose) {
this.model.mul(pose);
return this;
}
@Override
public TransformedInstance mulNormal(Matrix3f normal) {
public TransformedInstance mulNormal(Matrix3fc normal) {
this.normal.mul(normal);
return this;
}
@Override
public TransformedInstance rotateAround(Quaternionf quaternion, float x, float y, float z) {
this.model.rotateAround(quaternion, x, y, z);
this.normal.rotate(quaternion);
public TransformedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) {
model.rotateAround(quaternion, x, y, z);
normal.rotate(quaternion);
return this;
}
@Override
public TransformedInstance translate(float x, float y, float z) {
model.translate(x, y, z);
return this;
}
@Override
public TransformedInstance rotate(Quaternionfc quaternion) {
model.rotate(quaternion);
normal.rotate(quaternion);
return this;
}
@ -59,16 +73,9 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
return this;
}
@Override
public TransformedInstance rotate(Quaternionf quaternion) {
model.rotate(quaternion);
normal.rotate(quaternion);
return this;
}
@Override
public TransformedInstance translate(double x, double y, double z) {
model.translate((float) x, (float) y, (float) z);
public TransformedInstance setTransform(PoseStack.Pose pose) {
model.set(pose.pose());
normal.set(pose.normal());
return this;
}
@ -76,9 +83,9 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
return setTransform(stack.last());
}
public TransformedInstance setTransform(PoseStack.Pose pose) {
this.model.set(pose.pose());
this.normal.set(pose.normal());
public TransformedInstance setIdentityTransform() {
model.identity();
normal.identity();
return this;
}
@ -89,15 +96,9 @@ public class TransformedInstance extends ColoredLitInstance implements Transform
* This will allow the GPU to quickly discard all geometry for this instance, effectively "turning it off".
* </p>
*/
public TransformedInstance setEmptyTransform() {
public TransformedInstance setZeroTransform() {
model.zero();
normal.zero();
return this;
}
public TransformedInstance loadIdentity() {
model.identity();
normal.identity();
return this;
}
}

View File

@ -1,6 +1,7 @@
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;
@ -9,13 +10,18 @@ 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;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
public interface FlwLibXplat {
FlwLibXplat INSTANCE = DependencyInjection.load(FlwLibXplat.class, "dev.engine_room.flywheel.impl.FlwLibXplatImpl");
@UnknownNullability
BakedModel getBakedModel(ModelManager modelManager, ResourceLocation location);
BlockRenderDispatcher createVanillaBlockRenderDispatcher();
BakedModelBuilder createBakedModelBuilder(BakedModel bakedModel);

View File

@ -5,7 +5,7 @@ import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.CutoutShader;
public class CutoutShaders {
public final class CutoutShaders {
/**
* Do not discard any fragments based on alpha.
*/

View File

@ -5,7 +5,7 @@ import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.FogShader;
public class FogShaders {
public final class FogShaders {
public static final FogShader NONE = FogShader.REGISTRY.registerAndGet(new SimpleFogShader(Flywheel.rl("fog/none.glsl")));
public static final FogShader LINEAR = FogShader.REGISTRY.registerAndGet(new SimpleFogShader(Flywheel.rl("fog/linear.glsl")));
public static final FogShader LINEAR_FADE = FogShader.REGISTRY.registerAndGet(new SimpleFogShader(Flywheel.rl("fog/linear_fade.glsl")));

View File

@ -2,73 +2,50 @@ package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.material.Transparency;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.resources.ResourceLocation;
public final class Materials {
private static final ResourceLocation MINECART_LOCATION = new ResourceLocation("textures/entity/minecart.png");
public static final Material CHUNK_SOLID_SHADED = SimpleMaterial.builder()
public static final Material SOLID_BLOCK = SimpleMaterial.builder()
.build();
public static final Material CHUNK_SOLID_UNSHADED = SimpleMaterial.builder()
public static final Material SOLID_UNSHADED_BLOCK = SimpleMaterial.builder()
.diffuse(false)
.build();
public static final Material CHUNK_CUTOUT_MIPPED_SHADED = SimpleMaterial.builder()
public static final Material CUTOUT_MIPPED_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.HALF)
.build();
public static final Material CHUNK_CUTOUT_MIPPED_UNSHADED = SimpleMaterial.builder()
public static final Material CUTOUT_MIPPED_UNSHADED_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.HALF)
.diffuse(false)
.build();
public static final Material CHUNK_CUTOUT_SHADED = SimpleMaterial.builder()
public static final Material CUTOUT_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.mipmap(false)
.build();
public static final Material CHUNK_CUTOUT_UNSHADED = SimpleMaterial.builder()
public static final Material CUTOUT_UNSHADED_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.mipmap(false)
.diffuse(false)
.build();
public static final Material CHUNK_TRANSLUCENT_SHADED = SimpleMaterial.builder()
public static final Material TRANSLUCENT_BLOCK = SimpleMaterial.builder()
.transparency(Transparency.TRANSLUCENT)
.build();
public static final Material CHUNK_TRANSLUCENT_UNSHADED = SimpleMaterial.builder()
public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builder()
.transparency(Transparency.TRANSLUCENT)
.diffuse(false)
.build();
public static final Material CHUNK_TRIPWIRE_SHADED = SimpleMaterial.builder()
public static final Material TRIPWIRE_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.transparency(Transparency.TRANSLUCENT)
.build();
public static final Material CHUNK_TRIPWIRE_UNSHADED = SimpleMaterial.builder()
public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.transparency(Transparency.TRANSLUCENT)
.diffuse(false)
.build();
public static final Material CHEST = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.texture(Sheets.CHEST_SHEET)
.mipmap(false)
.build();
public static final Material SHULKER = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.texture(Sheets.SHULKER_SHEET)
.mipmap(false)
.backfaceCulling(false)
.build();
public static final Material BELL = SimpleMaterial.builder()
.mipmap(false)
.build();
public static final Material MINECART = SimpleMaterial.builder()
.texture(MINECART_LOCATION)
.mipmap(false)
.build();
private Materials() {
}
}

View File

@ -3,5 +3,5 @@ package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import net.minecraft.resources.ResourceLocation;
public record SimpleMaterialShaders(ResourceLocation vertexShader, ResourceLocation fragmentShader) implements MaterialShaders {
public record SimpleMaterialShaders(ResourceLocation vertexSource, ResourceLocation fragmentSource) implements MaterialShaders {
}

View File

@ -0,0 +1,36 @@
package dev.engine_room.flywheel.lib.math;
import net.minecraft.util.Mth;
public final class DataPacker {
private DataPacker() {
}
/**
* Pack a float as an unsigned, normalized byte.
*/
public static byte packNormU8(float f) {
return (byte) (int) (Mth.clamp(f, 0.0f, 1.0f) * 255);
}
/**
* Unpack an unsigned, normalized byte to a float.
*/
public static float unpackNormU8(byte b) {
return (float) (Byte.toUnsignedInt(b)) / 255f;
}
/**
* Pack a float as a signed, normalized byte.
*/
public static byte packNormI8(float f) {
return (byte) (Mth.clamp(f, -1.0f, 1.0f) * 127);
}
/**
* Unpack a signed, normalized byte to a float.
*/
public static float unpackNormI8(byte b) {
return (float) b / 127f;
}
}

View File

@ -2,10 +2,8 @@ package dev.engine_room.flywheel.lib.math;
import static org.joml.Math.fma;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
public final class MatrixMath {
private MatrixMath() {
@ -34,131 +32,4 @@ public final class MatrixMath {
public static float transformNormalZ(Matrix3f matrix, float x, float y, float z) {
return fma(matrix.m02(), x, fma(matrix.m12(), y, matrix.m22() * z));
}
/**
* Extracts the greatest scale factor across all axes from the given matrix.
*
* @param matrix The matrix to extract the scale from.
* @return The greatest scale factor across all axes.
*/
public static float extractScale(Matrix4f matrix) {
float scaleSqrX = matrix.m00() * matrix.m00() + matrix.m01() * matrix.m01() + matrix.m02() * matrix.m02();
float scaleSqrY = matrix.m10() * matrix.m10() + matrix.m11() * matrix.m11() + matrix.m12() * matrix.m12();
float scaleSqrZ = matrix.m20() * matrix.m20() + matrix.m21() * matrix.m21() + matrix.m22() * matrix.m22();
return Math.sqrt(Math.max(Math.max(scaleSqrX, scaleSqrY), scaleSqrZ));
}
/**
* Writes the frustum planes of the given projection matrix to the given buffer.<p>
* Uses a different format that is friendly towards an optimized instruction-parallel
* implementation of sphere-frustum intersection.<p>
* The format is as follows:<p>
* {@code vec4(nxX, pxX, nyX, pyX)}<br>
* {@code vec4(nxY, pxY, nyY, pyY)}<br>
* {@code vec4(nxZ, pxZ, nyZ, pyZ)}<br>
* {@code vec4(nxW, pxW, nyW, pyW)}<br>
* {@code vec2(nzX, pzX)}<br>
* {@code vec2(nzY, pzY)}<br>
* {@code vec2(nzZ, pzZ)}<br>
* {@code vec2(nzW, pzW)}<br>
* <p>
* Writes 96 bytes to the buffer.
*
* @param ptr The buffer to write the planes to.
* @param m The projection matrix to compute the frustum planes for.
*/
public static void writePackedFrustumPlanes(long ptr, Matrix4f m) {
float nxX, nxY, nxZ, nxW;
float pxX, pxY, pxZ, pxW;
float nyX, nyY, nyZ, nyW;
float pyX, pyY, pyZ, pyW;
float nzX, nzY, nzZ, nzW;
float pzX, pzY, pzZ, pzW;
float invl;
nxX = m.m03() + m.m00();
nxY = m.m13() + m.m10();
nxZ = m.m23() + m.m20();
nxW = m.m33() + m.m30();
invl = Math.invsqrt(nxX * nxX + nxY * nxY + nxZ * nxZ);
nxX *= invl;
nxY *= invl;
nxZ *= invl;
nxW *= invl;
pxX = m.m03() - m.m00();
pxY = m.m13() - m.m10();
pxZ = m.m23() - m.m20();
pxW = m.m33() - m.m30();
invl = Math.invsqrt(pxX * pxX + pxY * pxY + pxZ * pxZ);
pxX *= invl;
pxY *= invl;
pxZ *= invl;
pxW *= invl;
nyX = m.m03() + m.m01();
nyY = m.m13() + m.m11();
nyZ = m.m23() + m.m21();
nyW = m.m33() + m.m31();
invl = Math.invsqrt(nyX * nyX + nyY * nyY + nyZ * nyZ);
nyX *= invl;
nyY *= invl;
nyZ *= invl;
nyW *= invl;
pyX = m.m03() - m.m01();
pyY = m.m13() - m.m11();
pyZ = m.m23() - m.m21();
pyW = m.m33() - m.m31();
invl = Math.invsqrt(pyX * pyX + pyY * pyY + pyZ * pyZ);
pyX *= invl;
pyY *= invl;
pyZ *= invl;
pyW *= invl;
nzX = m.m03() + m.m02();
nzY = m.m13() + m.m12();
nzZ = m.m23() + m.m22();
nzW = m.m33() + m.m32();
invl = Math.invsqrt(nzX * nzX + nzY * nzY + nzZ * nzZ);
nzX *= invl;
nzY *= invl;
nzZ *= invl;
nzW *= invl;
pzX = m.m03() - m.m02();
pzY = m.m13() - m.m12();
pzZ = m.m23() - m.m22();
pzW = m.m33() - m.m32();
invl = Math.invsqrt(pzX * pzX + pzY * pzY + pzZ * pzZ);
pzX *= invl;
pzY *= invl;
pzZ *= invl;
pzW *= invl;
MemoryUtil.memPutFloat(ptr, nxX);
MemoryUtil.memPutFloat(ptr + 4, pxX);
MemoryUtil.memPutFloat(ptr + 8, nyX);
MemoryUtil.memPutFloat(ptr + 12, pyX);
MemoryUtil.memPutFloat(ptr + 16, nxY);
MemoryUtil.memPutFloat(ptr + 20, pxY);
MemoryUtil.memPutFloat(ptr + 24, nyY);
MemoryUtil.memPutFloat(ptr + 28, pyY);
MemoryUtil.memPutFloat(ptr + 32, nxZ);
MemoryUtil.memPutFloat(ptr + 36, pxZ);
MemoryUtil.memPutFloat(ptr + 40, nyZ);
MemoryUtil.memPutFloat(ptr + 44, pyZ);
MemoryUtil.memPutFloat(ptr + 48, nxW);
MemoryUtil.memPutFloat(ptr + 52, pxW);
MemoryUtil.memPutFloat(ptr + 56, nyW);
MemoryUtil.memPutFloat(ptr + 60, pyW);
MemoryUtil.memPutFloat(ptr + 64, nzX);
MemoryUtil.memPutFloat(ptr + 68, pzX);
MemoryUtil.memPutFloat(ptr + 72, nzY);
MemoryUtil.memPutFloat(ptr + 76, pzY);
MemoryUtil.memPutFloat(ptr + 80, nzZ);
MemoryUtil.memPutFloat(ptr + 84, pzZ);
MemoryUtil.memPutFloat(ptr + 88, nzW);
MemoryUtil.memPutFloat(ptr + 92, pzW);
}
}

View File

@ -29,47 +29,6 @@ public final class MoreMath {
return (numerator + denominator - 1) / denominator;
}
public static int numDigits(int number) {
// cursed but allegedly the fastest algorithm, taken from https://www.baeldung.com/java-number-of-digits-in-int
if (number < 100000) {
if (number < 100) {
if (number < 10) {
return 1;
} else {
return 2;
}
} else {
if (number < 1000) {
return 3;
} else {
if (number < 10000) {
return 4;
} else {
return 5;
}
}
}
} else {
if (number < 10000000) {
if (number < 1000000) {
return 6;
} else {
return 7;
}
} else {
if (number < 100000000) {
return 8;
} else {
if (number < 1000000000) {
return 9;
} else {
return 10;
}
}
}
}
}
public static long ceilLong(double d) {
return (long) Math.ceil(d);
}

View File

@ -1,34 +0,0 @@
package dev.engine_room.flywheel.lib.math;
public final class RenderMath {
private RenderMath() {
}
/**
* Convert a signed byte into a signed, normalized float.
*/
public static float f(byte b) {
return b / 127f;
}
/**
* Convert a signed, normalized float into a signed byte.
*/
public static byte nb(float f) {
return (byte) (f * 127);
}
/**
* Convert an unsigned byte into an unsigned, normalized float.
*/
public static float uf(byte b) {
return (float) (Byte.toUnsignedInt(b)) / 255f;
}
/**
* Convert an unsigned, normalized float into an unsigned byte.
*/
public static byte unb(float f) {
return (byte) (int) (f * 255);
}
}

View File

@ -18,13 +18,21 @@ non-sealed abstract class AbstractMemoryBlockImpl implements MemoryBlock {
this.size = size;
}
void assertAllocated() {
if (freed) {
throw new IllegalStateException("Operation called on freed MemoryBlock!");
}
}
@Override
public long ptr() {
assertAllocated();
return ptr;
}
@Override
public long size() {
assertAllocated();
return size;
}
@ -35,27 +43,32 @@ non-sealed abstract class AbstractMemoryBlockImpl implements MemoryBlock {
@Override
public void copyTo(MemoryBlock block) {
assertAllocated();
long bytes = Math.min(size, block.size());
copyTo(block.ptr(), bytes);
}
@Override
public void copyTo(long ptr, long bytes) {
assertAllocated();
MemoryUtil.memCopy(this.ptr, ptr, bytes);
}
@Override
public void copyTo(long ptr) {
assertAllocated();
copyTo(ptr, size);
}
@Override
public void clear() {
assertAllocated();
MemoryUtil.memSet(ptr, 0, size);
}
@Override
public ByteBuffer asBuffer() {
assertAllocated();
int intSize = (int) size;
if (intSize != size) {
throw new UnsupportedOperationException("Cannot create buffer with long capacity!");
@ -64,12 +77,13 @@ non-sealed abstract class AbstractMemoryBlockImpl implements MemoryBlock {
}
void freeInner() {
FlwMemoryTracker._freeCPUMemory(size);
FlwMemoryTracker._freeCpuMemory(size);
freed = true;
}
@Override
public void free() {
assertAllocated();
FlwMemoryTracker.free(ptr);
freeInner();
}

View File

@ -31,8 +31,9 @@ class DebugMemoryBlockImpl extends AbstractMemoryBlockImpl {
@Override
public MemoryBlock realloc(long size) {
assertAllocated();
MemoryBlock block = new DebugMemoryBlockImpl(FlwMemoryTracker.realloc(ptr, size), size, cleaner, 1);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
freeInner();
return block;
}
@ -54,13 +55,13 @@ class DebugMemoryBlockImpl extends AbstractMemoryBlockImpl {
static MemoryBlock malloc(long size) {
MemoryBlock block = new DebugMemoryBlockImpl(FlwMemoryTracker.malloc(size), size, CLEANER, 2);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
static MemoryBlock calloc(long num, long size) {
MemoryBlock block = new DebugMemoryBlockImpl(FlwMemoryTracker.calloc(num, size), num * size, CLEANER, 2);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
@ -94,7 +95,7 @@ class DebugMemoryBlockImpl extends AbstractMemoryBlockImpl {
FlwLibLink.INSTANCE.getLogger().warn(builder.toString());
FlwMemoryTracker.free(ptr);
FlwMemoryTracker._freeCPUMemory(size);
FlwMemoryTracker._freeCpuMemory(size);
}
}
}

View File

@ -7,8 +7,6 @@ import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.lib.util.StringUtil;
public final class FlwMemoryTracker {
public static final boolean DEBUG_MEMORY_SAFETY = System.getProperty("flw.debugMemorySafety") != null;
private static final AtomicLong CPU_MEMORY = new AtomicLong(0);
private static final AtomicLong GPU_MEMORY = new AtomicLong(0);
@ -32,38 +30,38 @@ public final class FlwMemoryTracker {
}
public static long realloc(long ptr, long size) {
ptr = MemoryUtil.nmemRealloc(ptr, size);
if (ptr == MemoryUtil.NULL) {
long newPtr = MemoryUtil.nmemRealloc(ptr, size);
if (newPtr == MemoryUtil.NULL) {
throw new OutOfMemoryError("Failed to reallocate " + size + " bytes for address " + StringUtil.formatAddress(ptr));
}
return ptr;
return newPtr;
}
public static void free(long ptr) {
MemoryUtil.nmemFree(ptr);
}
public static void _allocCPUMemory(long size) {
public static void _allocCpuMemory(long size) {
CPU_MEMORY.getAndAdd(size);
}
public static void _freeCPUMemory(long size) {
public static void _freeCpuMemory(long size) {
CPU_MEMORY.getAndAdd(-size);
}
public static void _allocGPUMemory(long size) {
public static void _allocGpuMemory(long size) {
GPU_MEMORY.getAndAdd(size);
}
public static void _freeGPUMemory(long size) {
public static void _freeGpuMemory(long size) {
GPU_MEMORY.getAndAdd(-size);
}
public static long getCPUMemory() {
public static long getCpuMemory() {
return CPU_MEMORY.get();
}
public static long getGPUMemory() {
public static long getGpuMemory() {
return GPU_MEMORY.get();
}
}

View File

@ -26,7 +26,7 @@ public sealed interface MemoryBlock permits AbstractMemoryBlockImpl {
void free();
static MemoryBlock malloc(long size) {
if (FlwMemoryTracker.DEBUG_MEMORY_SAFETY) {
if (MemoryBlockImpl.DEBUG_MEMORY_SAFETY) {
return DebugMemoryBlockImpl.malloc(size);
} else {
return MemoryBlockImpl.malloc(size);
@ -38,7 +38,7 @@ public sealed interface MemoryBlock permits AbstractMemoryBlockImpl {
}
static MemoryBlock calloc(long num, long size) {
if (FlwMemoryTracker.DEBUG_MEMORY_SAFETY) {
if (MemoryBlockImpl.DEBUG_MEMORY_SAFETY) {
return DebugMemoryBlockImpl.calloc(num, size);
} else {
return MemoryBlockImpl.calloc(num, size);

View File

@ -1,6 +1,8 @@
package dev.engine_room.flywheel.lib.memory;
class MemoryBlockImpl extends AbstractMemoryBlockImpl {
static final boolean DEBUG_MEMORY_SAFETY = System.getProperty("flw.debugMemorySafety") != null;
MemoryBlockImpl(long ptr, long size) {
super(ptr, size);
}
@ -12,21 +14,22 @@ class MemoryBlockImpl extends AbstractMemoryBlockImpl {
@Override
public MemoryBlock realloc(long size) {
assertAllocated();
MemoryBlock block = new MemoryBlockImpl(FlwMemoryTracker.realloc(ptr, size), size);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
freeInner();
return block;
}
static MemoryBlock malloc(long size) {
MemoryBlock block = new MemoryBlockImpl(FlwMemoryTracker.malloc(size), size);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
static MemoryBlock calloc(long num, long size) {
MemoryBlock block = new MemoryBlockImpl(FlwMemoryTracker.calloc(num, size), num * size);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
}

View File

@ -28,21 +28,22 @@ class TrackedMemoryBlockImpl extends AbstractMemoryBlockImpl {
@Override
public MemoryBlock realloc(long size) {
assertAllocated();
MemoryBlock block = new TrackedMemoryBlockImpl(FlwMemoryTracker.realloc(ptr, size), size, cleaner);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
freeInner();
return block;
}
static MemoryBlock malloc(long size) {
MemoryBlock block = new TrackedMemoryBlockImpl(FlwMemoryTracker.malloc(size), size, CLEANER);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
static MemoryBlock calloc(long num, long size) {
MemoryBlock block = new TrackedMemoryBlockImpl(FlwMemoryTracker.calloc(num, size), num * size, CLEANER);
FlwMemoryTracker._allocCPUMemory(block.size());
FlwMemoryTracker._allocCpuMemory(block.size());
return block;
}
@ -61,7 +62,7 @@ class TrackedMemoryBlockImpl extends AbstractMemoryBlockImpl {
public void run() {
if (!freed) {
FlwMemoryTracker.free(ptr);
FlwMemoryTracker._freeCPUMemory(size);
FlwMemoryTracker._freeCpuMemory(size);
}
}
}

View File

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.lib.model;
import org.jetbrains.annotations.UnknownNullability;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil;
@ -9,32 +10,32 @@ import dev.engine_room.flywheel.api.model.IndexSequence;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
import dev.engine_room.flywheel.api.vertex.VertexView;
import dev.engine_room.flywheel.api.vertex.VertexList;
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
import dev.engine_room.flywheel.lib.material.StandardMaterialShaders;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.vertex.FullVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
public class LineModelBuilder {
public static final Material MATERIAL = SimpleMaterial.builder()
public final class LineModelBuilder {
private static final Material MATERIAL = SimpleMaterial.builder()
.shaders(StandardMaterialShaders.LINE)
.backfaceCulling(false)
.diffuse(false)
.build();
private final VertexView vertices;
private MemoryBlock block;
@UnknownNullability
private VertexView vertexView;
@UnknownNullability
private MemoryBlock data;
private int vertexCount = 0;
private LineModelBuilder(int segmentCount) {
this.vertices = new FullVertexView();
this.block = MemoryBlock.malloc(segmentCount * 4 * FullVertexView.STRIDE);
vertices.ptr(block.ptr());
}
public static LineModelBuilder withCapacity(int segmentCount) {
return new LineModelBuilder(segmentCount);
public LineModelBuilder(int segmentCount) {
vertexView = new FullVertexView();
data = MemoryBlock.mallocTracked(segmentCount * 4 * vertexView.stride());
vertexView.ptr(data.ptr());
}
public LineModelBuilder line(float x1, float y1, float z1, float x2, float y2, float z2) {
@ -50,53 +51,61 @@ public class LineModelBuilder {
float normalZ = dz / length;
for (int i = 0; i < 2; i++) {
vertices.x(vertexCount + i, x1);
vertices.y(vertexCount + i, y1);
vertices.z(vertexCount + i, z1);
vertexView.x(vertexCount + i, x1);
vertexView.y(vertexCount + i, y1);
vertexView.z(vertexCount + i, z1);
vertices.x(vertexCount + 2 + i, x2);
vertices.y(vertexCount + 2 + i, y2);
vertices.z(vertexCount + 2 + i, z2);
vertexView.x(vertexCount + 2 + i, x2);
vertexView.y(vertexCount + 2 + i, y2);
vertexView.z(vertexCount + 2 + i, z2);
}
for (int i = 0; i < 4; i++) {
vertices.r(vertexCount + i, 1);
vertices.g(vertexCount + i, 1);
vertices.b(vertexCount + i, 1);
vertices.a(vertexCount + i, 1);
vertices.u(vertexCount + i, 0);
vertices.v(vertexCount + i, 0);
vertices.light(vertexCount + i, LightTexture.FULL_BRIGHT);
vertices.normalX(vertexCount + i, normalX);
vertices.normalY(vertexCount + i, normalY);
vertices.normalZ(vertexCount + i, normalZ);
vertexView.r(vertexCount + i, 1);
vertexView.g(vertexCount + i, 1);
vertexView.b(vertexCount + i, 1);
vertexView.a(vertexCount + i, 1);
vertexView.u(vertexCount + i, 0);
vertexView.v(vertexCount + i, 0);
vertexView.overlay(vertexCount + i, OverlayTexture.NO_OVERLAY);
vertexView.light(vertexCount + i, LightTexture.FULL_BRIGHT);
vertexView.normalX(vertexCount + i, normalX);
vertexView.normalY(vertexCount + i, normalY);
vertexView.normalZ(vertexCount + i, normalZ);
}
vertexCount += 4;
return this;
}
public Model build() {
vertices.vertexCount(vertexCount);
vertexView.vertexCount(vertexCount);
var boundingSphere = ModelUtil.computeBoundingSphere(vertices);
var boundingSphere = ModelUtil.computeBoundingSphere(vertexView);
boundingSphere.w += 0.1f; // make the bounding sphere a little bigger to account for line width
var mesh = new LineMesh(vertexView, boundingSphere);
var model = new SingleMeshModel(mesh, MATERIAL);
var mesh = new LineMesh(vertexCount, vertices, block, boundingSphere);
vertexView = null;
data = null;
vertexCount = 0;
return new SingleMeshModel(mesh, MATERIAL);
return model;
}
private void ensureCapacity(int vertexCount) {
if (vertexCount * FullVertexView.STRIDE > block.size()) {
this.block = block.realloc(vertexCount * FullVertexView.STRIDE);
vertices.ptr(block.ptr());
if (data == null) {
vertexView = new FullVertexView();
data = MemoryBlock.mallocTracked(vertexCount * vertexView.stride());
vertexView.ptr(data.ptr());
} else if (vertexCount * vertexView.stride() > data.size()) {
data = data.realloc(vertexCount * vertexView.stride());
vertexView.ptr(data.ptr());
}
}
public static class LineMesh implements Mesh {
public static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> {
private static class LineMesh implements Mesh {
private static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> {
int numVertices = 2 * count / 3;
int baseVertex = 0;
while (baseVertex < numVertices) {
@ -113,26 +122,22 @@ public class LineModelBuilder {
ptr += 24;
}
};
private final int vertexCount;
private final VertexView vertexView;
private final MemoryBlock data;
private final VertexList vertexList;
private final Vector4f boundingSphere;
public LineMesh(int vertexCount, VertexView vertexView, MemoryBlock data, Vector4f boundingSphere) {
this.vertexCount = vertexCount;
this.vertexView = vertexView;
this.data = data;
public LineMesh(VertexList vertexList, Vector4f boundingSphere) {
this.vertexList = vertexList;
this.boundingSphere = boundingSphere;
}
@Override
public int vertexCount() {
return vertexCount;
return vertexList.vertexCount();
}
@Override
public void write(MutableVertexList vertexList) {
vertexView.writeAll(vertexList);
vertexList.writeAll(vertexList);
}
@Override
@ -142,17 +147,12 @@ public class LineModelBuilder {
@Override
public int indexCount() {
return vertexCount / 2 * 3;
return vertexCount() / 2 * 3;
}
@Override
public Vector4fc boundingSphere() {
return boundingSphere;
}
@Override
public void delete() {
data.free();
}
}
}

View File

@ -1,17 +1,17 @@
package dev.engine_room.flywheel.lib.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public class ModelCache<T> {
private static final List<ModelCache<?>> ALL = new ArrayList<>();
public final class ModelCache<T> {
private static final Set<ModelCache<?>> ALL = FlwUtil.createWeakHashSet();
private final Function<T, Model> factory;
private final Map<T, Model> map = new ConcurrentHashMap<>();
@ -28,7 +28,6 @@ public class ModelCache<T> {
}
public void clear() {
map.values().forEach(Model::delete);
map.clear();
}

View File

@ -1,16 +1,16 @@
package dev.engine_room.flywheel.lib.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.lib.util.FlwUtil;
public class ModelHolder {
private static final List<ModelHolder> ALL = new ArrayList<>();
public final class ModelHolder {
private static final Set<ModelHolder> ALL = FlwUtil.createWeakHashSet();
private final Supplier<Model> factory;
@Nullable
private volatile Model model;
@ -45,7 +45,6 @@ public class ModelHolder {
synchronized (this) {
model = this.model;
if (model != null) {
model.delete();
this.model = null;
}
}

View File

@ -31,19 +31,19 @@ public final class ModelUtil {
@Nullable
public static Material getMaterial(RenderType chunkRenderType, boolean shaded) {
if (chunkRenderType == RenderType.solid()) {
return shaded ? Materials.CHUNK_SOLID_SHADED : Materials.CHUNK_SOLID_UNSHADED;
return shaded ? Materials.SOLID_BLOCK : Materials.SOLID_UNSHADED_BLOCK;
}
if (chunkRenderType == RenderType.cutoutMipped()) {
return shaded ? Materials.CHUNK_CUTOUT_MIPPED_SHADED : Materials.CHUNK_CUTOUT_MIPPED_UNSHADED;
return shaded ? Materials.CUTOUT_MIPPED_BLOCK : Materials.CUTOUT_MIPPED_UNSHADED_BLOCK;
}
if (chunkRenderType == RenderType.cutout()) {
return shaded ? Materials.CHUNK_CUTOUT_SHADED : Materials.CHUNK_CUTOUT_UNSHADED;
return shaded ? Materials.CUTOUT_BLOCK : Materials.CUTOUT_UNSHADED_BLOCK;
}
if (chunkRenderType == RenderType.translucent()) {
return shaded ? Materials.CHUNK_TRANSLUCENT_SHADED : Materials.CHUNK_TRANSLUCENT_UNSHADED;
return shaded ? Materials.TRANSLUCENT_BLOCK : Materials.TRANSLUCENT_UNSHADED_BLOCK;
}
if (chunkRenderType == RenderType.tripwire()) {
return shaded ? Materials.CHUNK_TRIPWIRE_SHADED : Materials.CHUNK_TRIPWIRE_UNSHADED;
return shaded ? Materials.TRIPWIRE_BLOCK : Materials.TRIPWIRE_UNSHADED_BLOCK;
}
return null;
}

View File

@ -4,7 +4,7 @@ import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.model.IndexSequence;
public class QuadIndexSequence implements IndexSequence {
public final class QuadIndexSequence implements IndexSequence {
public static final QuadIndexSequence INSTANCE = new QuadIndexSequence();
private QuadIndexSequence() {

View File

@ -1,65 +0,0 @@
package dev.engine_room.flywheel.lib.model;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
import dev.engine_room.flywheel.api.vertex.VertexView;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
public class SimpleMesh implements QuadMesh {
private final int vertexCount;
private final VertexView vertexView;
private final Vector4f boundingSphere;
private final MemoryBlock data;
@Nullable
private final String descriptor;
public SimpleMesh(VertexView vertexView, MemoryBlock data, @Nullable String descriptor) {
this.vertexView = vertexView;
this.data = data;
this.descriptor = descriptor;
int bytes = (int) data.size();
int stride = (int) this.vertexView.stride();
if (bytes % stride != 0) {
throw new IllegalArgumentException("MemoryBlock contains non-whole amount of vertices!");
}
vertexCount = bytes / stride;
this.vertexView.ptr(data.ptr());
this.vertexView.vertexCount(vertexCount);
boundingSphere = ModelUtil.computeBoundingSphere(vertexView);
}
public SimpleMesh(VertexView vertexView, MemoryBlock data) {
this(vertexView, data, null);
}
@Override
public int vertexCount() {
return vertexCount;
}
@Override
public void write(MutableVertexList dst) {
vertexView.writeAll(dst);
}
@Override
public Vector4fc boundingSphere() {
return boundingSphere;
}
@Override
public void delete() {
data.free();
}
@Override
public String toString() {
return "SimpleMesh{" + "vertexCount=" + vertexCount + ",descriptor={" + descriptor + "}" + "}";
}
}

View File

@ -24,11 +24,4 @@ public class SimpleModel implements Model {
public Vector4fc boundingSphere() {
return boundingSphere;
}
@Override
public void delete() {
for (ConfiguredMesh mesh : meshes) {
mesh.mesh().delete();
}
}
}

View File

@ -0,0 +1,45 @@
package dev.engine_room.flywheel.lib.model;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import dev.engine_room.flywheel.api.vertex.MutableVertexList;
import dev.engine_room.flywheel.api.vertex.VertexList;
public final class SimpleQuadMesh implements QuadMesh {
private final VertexList vertexList;
private final Vector4f boundingSphere;
@Nullable
private final String descriptor;
public SimpleQuadMesh(VertexList vertexList, @Nullable String descriptor) {
this.vertexList = vertexList;
boundingSphere = ModelUtil.computeBoundingSphere(vertexList);
this.descriptor = descriptor;
}
public SimpleQuadMesh(VertexList vertexList) {
this(vertexList, null);
}
@Override
public int vertexCount() {
return vertexList.vertexCount();
}
@Override
public void write(MutableVertexList dst) {
vertexList.writeAll(dst);
}
@Override
public Vector4fc boundingSphere() {
return boundingSphere;
}
@Override
public String toString() {
return "SimpleQuadMesh{" + "vertexCount=" + vertexCount() + ",descriptor={" + descriptor + "}" + "}";
}
}

View File

@ -28,9 +28,4 @@ public class SingleMeshModel implements Model {
public Vector4fc boundingSphere() {
return mesh.boundingSphere();
}
@Override
public void delete() {
mesh.delete();
}
}

View File

@ -7,16 +7,16 @@ import org.lwjgl.system.MemoryUtil;
import com.mojang.blaze3d.vertex.BufferBuilder;
import dev.engine_room.flywheel.api.vertex.VertexView;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.SimpleMesh;
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
import dev.engine_room.flywheel.lib.vertex.NoOverlayVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
final class MeshHelper {
private MeshHelper() {
}
public static SimpleMesh blockVerticesToMesh(BufferBuilder.RenderedBuffer buffer, @Nullable String meshDescriptor) {
public static SimpleQuadMesh blockVerticesToMesh(BufferBuilder.RenderedBuffer buffer, @Nullable String meshDescriptor) {
BufferBuilder.DrawState drawState = buffer.drawState();
int vertexCount = drawState.vertexCount();
long srcStride = drawState.format().getVertexSize();
@ -25,7 +25,7 @@ final class MeshHelper {
long dstStride = vertexView.stride();
ByteBuffer src = buffer.vertexBuffer();
MemoryBlock dst = MemoryBlock.malloc((long) vertexCount * dstStride);
MemoryBlock dst = MemoryBlock.mallocTracked((long) vertexCount * dstStride);
long srcPtr = MemoryUtil.memAddress(src);
long dstPtr = dst.ptr();
// The first 31 bytes of each vertex in a block vertex buffer are guaranteed to contain the same data in the
@ -42,6 +42,6 @@ final class MeshHelper {
vertexView.ptr(dstPtr);
vertexView.vertexCount(vertexCount);
return new SimpleMesh(vertexView, dst, meshDescriptor);
return new SimpleQuadMesh(vertexView, meshDescriptor);
}
}

View File

@ -0,0 +1,57 @@
package dev.engine_room.flywheel.lib.model.baked;
import java.util.function.ToIntFunction;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class OriginBlockAndTintGetter extends VirtualBlockGetter {
@Nullable
protected BlockEntity originBlockEntity;
protected BlockState originBlockState = Blocks.AIR.defaultBlockState();
public OriginBlockAndTintGetter(ToIntFunction<BlockPos> blockLightFunc, ToIntFunction<BlockPos> skyLightFunc) {
super(blockLightFunc, skyLightFunc);
}
public void originBlockEntity(@Nullable BlockEntity blockEntity) {
originBlockEntity = blockEntity;
}
public void originBlockState(BlockState state) {
originBlockState = state;
}
@Override
@Nullable
public BlockEntity getBlockEntity(BlockPos pos) {
if (pos.equals(BlockPos.ZERO)) {
return originBlockEntity;
}
return null;
}
@Override
public BlockState getBlockState(BlockPos pos) {
if (pos.equals(BlockPos.ZERO)) {
return originBlockState;
}
return Blocks.AIR.defaultBlockState();
}
@Override
public int getHeight() {
return 1;
}
@Override
public int getMinBuildHeight() {
return 0;
}
}

View File

@ -1,8 +1,13 @@
package dev.engine_room.flywheel.lib.model.baked;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import org.jetbrains.annotations.UnknownNullability;
import com.google.common.collect.MapMaker;
import dev.engine_room.flywheel.lib.internal.FlwLibXplat;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.resources.ResourceLocation;
@ -10,44 +15,35 @@ import net.minecraft.resources.ResourceLocation;
* A helper class for loading and accessing JSON models not directly used by any blocks or items.
* <br>
* Creating a PartialModel will make Minecraft automatically load the associated modelLocation.
* PartialModels must be initialized before the initial resource reload, otherwise an error will be thrown.
* It is recommended to do this in the client mod initializer on Fabric and the mod class constructor on Forge.
* <br>
* Once Minecraft has finished baking all models, all PartialModels will have their bakedModel fields populated.
*/
public class PartialModel {
static final List<PartialModel> ALL = new ArrayList<>();
static boolean tooLate = false;
public final class PartialModel {
static final ConcurrentMap<ResourceLocation, PartialModel> ALL = new MapMaker().weakValues().makeMap();
static boolean populateOnInit = false;
protected final ResourceLocation modelLocation;
protected BakedModel bakedModel;
public PartialModel(ResourceLocation modelLocation) {
if (tooLate) {
throw new RuntimeException("Attempted to create PartialModel with location '" + modelLocation + "' after start of initial resource reload!");
}
private final ResourceLocation modelLocation;
@UnknownNullability
BakedModel bakedModel;
private PartialModel(ResourceLocation modelLocation) {
this.modelLocation = modelLocation;
synchronized (ALL) {
ALL.add(this);
if (populateOnInit) {
bakedModel = FlwLibXplat.INSTANCE.getBakedModel(Minecraft.getInstance().getModelManager(), modelLocation);
}
}
public ResourceLocation getLocation() {
return modelLocation;
}
public String getName() {
return getLocation()
.toString();
public static PartialModel of(ResourceLocation modelLocation) {
return ALL.computeIfAbsent(modelLocation, PartialModel::new);
}
@UnknownNullability
public BakedModel get() {
return bakedModel;
}
protected void set(BakedModel bakedModel) {
this.bakedModel = bakedModel;
public ResourceLocation modelLocation() {
return modelLocation;
}
}

View File

@ -7,8 +7,9 @@ import com.mojang.blaze3d.vertex.PoseStack;
import dev.engine_room.flywheel.api.model.Mesh;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.SimpleMesh;
import dev.engine_room.flywheel.lib.model.SimpleQuadMesh;
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.model.geom.ModelLayerLocation;
@ -29,10 +30,14 @@ public final class ModelPartConverter {
poseStack = objects.identityPoseStack;
}
VertexWriter vertexWriter = objects.vertexWriter;
vertexWriter.setTextureMapper(textureMapper);
modelPart.render(poseStack, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY);
MemoryBlock data = vertexWriter.copyDataAndReset();
return new SimpleMesh(new PosTexNormalVertexView(), data, "source=ModelPartConverter");
VertexView vertexView = new PosTexNormalVertexView();
vertexView.load(data);
return new SimpleQuadMesh(vertexView, "source=ModelPartConverter");
}
public static Mesh convert(ModelLayerLocation layer, @Nullable TextureAtlasSprite sprite, String... childPath) {

View File

@ -6,7 +6,7 @@ import org.lwjgl.system.MemoryUtil;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dev.engine_room.flywheel.lib.math.RenderMath;
import dev.engine_room.flywheel.lib.math.DataPacker;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import dev.engine_room.flywheel.lib.model.part.ModelPartConverter.TextureMapper;
import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView;
@ -85,9 +85,9 @@ class VertexWriter implements VertexConsumer {
public VertexConsumer normal(float x, float y, float z) {
if (!filledNormal) {
long ptr = vertexPtr();
MemoryUtil.memPutByte(ptr + 20, RenderMath.nb(x));
MemoryUtil.memPutByte(ptr + 21, RenderMath.nb(y));
MemoryUtil.memPutByte(ptr + 22, RenderMath.nb(z));
MemoryUtil.memPutByte(ptr + 20, DataPacker.packNormI8(x));
MemoryUtil.memPutByte(ptr + 21, DataPacker.packNormI8(y));
MemoryUtil.memPutByte(ptr + 22, DataPacker.packNormI8(z));
filledNormal = true;
}
return this;
@ -124,7 +124,7 @@ class VertexWriter implements VertexConsumer {
}
public MemoryBlock copyDataAndReset() {
MemoryBlock dataCopy = MemoryBlock.malloc(vertexCount * STRIDE);
MemoryBlock dataCopy = MemoryBlock.mallocTracked(vertexCount * STRIDE);
data.copyTo(dataCopy);
vertexCount = 0;

View File

@ -4,7 +4,7 @@ import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
public record BarrierPlan<C>(Plan<C> first, Plan<C> second) implements SimplyComposedPlan<C> {
public static <C> Plan<C> of(Plan<C> first, Plan<C> second) {
public static <C> BarrierPlan<C> of(Plan<C> first, Plan<C> second) {
return new BarrierPlan<>(first, second);
}
@ -12,5 +12,4 @@ public record BarrierPlan<C>(Plan<C> first, Plan<C> second) implements SimplyCom
public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) {
first.execute(taskExecutor, context, () -> second.execute(taskExecutor, context, onCompletion));
}
}

View File

@ -9,6 +9,9 @@ import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.lib.math.MoreMath;
public final class Distribute {
private Distribute() {
}
/**
* Distribute the given list of tasks across the threads of the task executor.
*
@ -184,7 +187,4 @@ public final class Distribute {
public static int sliceSize(TaskExecutor taskExecutor, int totalSize, int denominator) {
return MoreMath.ceilingDiv(totalSize, taskExecutor.threadCount() * denominator);
}
private Distribute() {
}
}

View File

@ -14,11 +14,11 @@ import dev.engine_room.flywheel.lib.task.functional.SupplierWithContext;
*/
public record DynamicNestedPlan<C>(
SupplierWithContext<C, Collection<? extends Plan<C>>> plans) implements SimplyComposedPlan<C> {
public static <C> Plan<C> of(SupplierWithContext.Ignored<C, Collection<? extends Plan<C>>> supplier) {
public static <C> DynamicNestedPlan<C> of(SupplierWithContext.Ignored<C, Collection<? extends Plan<C>>> supplier) {
return new DynamicNestedPlan<>(supplier);
}
public static <C> Plan<C> of(SupplierWithContext<C, Collection<? extends Plan<C>>> supplier) {
public static <C> DynamicNestedPlan<C> of(SupplierWithContext<C, Collection<? extends Plan<C>>> supplier) {
return new DynamicNestedPlan<>(supplier);
}

View File

@ -2,7 +2,6 @@ package dev.engine_room.flywheel.lib.task;
import java.util.List;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.lib.task.functional.ConsumerWithContext;
import dev.engine_room.flywheel.lib.task.functional.SupplierWithContext;
@ -19,19 +18,19 @@ import dev.engine_room.flywheel.lib.task.functional.SupplierWithContext;
*/
public record ForEachPlan<T, C>(SupplierWithContext<C, List<T>> listSupplier,
ConsumerWithContext<T, C> action) implements SimplyComposedPlan<C> {
public static <T, C> Plan<C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext<T, C> forEach) {
public static <T, C> ForEachPlan<T, C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext<T, C> forEach) {
return new ForEachPlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext.Ignored<T, C> forEach) {
public static <T, C> ForEachPlan<T, C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext.Ignored<T, C> forEach) {
return new ForEachPlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext<T, C> forEach) {
public static <T, C> ForEachPlan<T, C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext<T, C> forEach) {
return new ForEachPlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext.Ignored<T, C> forEach) {
public static <T, C> ForEachPlan<T, C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext.Ignored<T, C> forEach) {
return new ForEachPlan<>(iterable, forEach);
}

View File

@ -2,7 +2,6 @@ package dev.engine_room.flywheel.lib.task;
import java.util.List;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.lib.task.functional.ConsumerWithContext;
import dev.engine_room.flywheel.lib.task.functional.SupplierWithContext;
@ -19,19 +18,19 @@ import dev.engine_room.flywheel.lib.task.functional.SupplierWithContext;
*/
public record ForEachSlicePlan<T, C>(SupplierWithContext<C, List<T>> listSupplier,
ConsumerWithContext<List<T>, C> action) implements SimplyComposedPlan<C> {
public static <T, C> Plan<C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext<List<T>, C> forEach) {
public static <T, C> ForEachSlicePlan<T, C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext<List<T>, C> forEach) {
return new ForEachSlicePlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext.Ignored<List<T>, C> forEach) {
public static <T, C> ForEachSlicePlan<T, C> of(SupplierWithContext<C, List<T>> iterable, ConsumerWithContext.Ignored<List<T>, C> forEach) {
return new ForEachSlicePlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext<List<T>, C> forEach) {
public static <T, C> ForEachSlicePlan<T, C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext<List<T>, C> forEach) {
return new ForEachSlicePlan<>(iterable, forEach);
}
public static <T, C> Plan<C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext.Ignored<List<T>, C> forEach) {
public static <T, C> ForEachSlicePlan<T, C> of(SupplierWithContext.Ignored<C, List<T>> iterable, ConsumerWithContext.Ignored<List<T>, C> forEach) {
return new ForEachSlicePlan<>(iterable, forEach);
}

View File

@ -30,7 +30,7 @@ public record IfElsePlan<C>(BooleanSupplierWithContext<C> condition, Plan<C> onT
}
}
public static class Builder<C> {
public static final class Builder<C> {
private final BooleanSupplierWithContext<C> condition;
private Plan<C> onTrue = UnitPlan.of();
private Plan<C> onFalse = UnitPlan.of();

Some files were not shown because too many files have changed in this diff Show More