Merge branch 'dev' into shader-pipeline

This commit is contained in:
Jozufozu 2021-08-05 00:53:29 -07:00
commit 9e699b7715
56 changed files with 1015 additions and 511 deletions

View file

@ -1,126 +1,20 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2021 Jozufozu
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
General Public License, supplemented by the additional permissions listed below.
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to
version 3 of the GNU General Public License.
"The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined
below.
An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on
the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by
the Library.
A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of
the Library with which the Combined Work was made is also called the "Linked Version".
The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding
any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not
on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application,
including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the
System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied
by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may
convey a copy of the modified version:
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not
supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful,
or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may
convey such object code under terms of your choice, provided that, if the incorporated material is not limited to
numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its
use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification
of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications,
if you also do each of the following:
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its
use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library
among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under
section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified
version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked
Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and
Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner
specified by section 6 of the GNU GPL for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the Library side by side in a single library together with
other library facilities that are not Applications and are not covered by this License, and convey such a combined
library under terms of your choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library
facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where
to find the accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time
to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new
problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain
numbered version of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and conditions either of that published version or of any
later version published by the Free Software Foundation. If the Library as you received it does not specify a version
number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License
ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General
Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for
you to choose that version for the Library.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -121,6 +121,12 @@ jar {
jar.finalizedBy('reobfJar')
javadoc {
source = [sourceSets.main.allJava]
// prevent java 8's strict doclint for javadocs from failing builds
options.addStringOption('Xdoclint:none', '-quiet')
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
archiveBaseName.set(project.archivesBaseName)
@ -128,8 +134,13 @@ task sourcesJar(type: Jar) {
archiveClassifier.set('sources')
}
task javadocJar(type: Jar, dependsOn: javadoc) {
from javadoc.destinationDir
archiveClassifier.set('javadoc')
}
artifacts {
archives jar, sourcesJar
archives jar, sourcesJar, javadocJar
}
publishing {
@ -138,6 +149,7 @@ publishing {
mavenJava(MavenPublication) {
artifact jar
artifact sourcesJar
artifact javadocJar
}
}

View file

@ -1,3 +1,42 @@
0.2.2:
Fixes
- Fix ConcurrentModificationException crash
- Fix NullPointer rendering create contraptions on older graphics cards
- Fix crash triggered when moving large distances in a single frame
0.2.1:
Fixes
- Potential fix for many issues caused by optimized chunk accesses
New
- Added config+command to disable chunk access optimization
0.2.0:
New
- Flywheel driven shulker box rendering
- Optimize chunk accesses by caching previous result
- Further optimize flywheel rendered objects through parallel updates
Changes
- Distant objects are update throttled according to the sequence of prime numbers, smoothing out updates
- Rename normalOverlay to debugNormals, make naming consistent across command and config
Fixes
- Fix issue causing modded banner patterns to all have missing textures
Technical/API
- Reorganize, simplify, and document everything in the MaterialManager tree
- MaterialManagers associate RenderStates with MaterialGroups
- Proper support for rendering in different layers (SOLID, CUTOUT, and TRANSPARENT)
- New methods in MaterialManager to accommodate these changes
- Deprecate old functions in MaterialManager in favor of new ones using MaterialGroups
- InstanceDatas can be transferred to other Instancers via "instance stealing"
- Abstraction for models, IModel
- Easier to use, and gives Flywheel more freedom to optimize
- Buffered models directly consume IModels
- Added BlockModel, renders a single block
- Added WorldModel, renders many blocks given a world instance
- Cuboids can be inverted across Y and Z, used by many vanilla models for some reason
- TransformStack scaling
- VecBuffer coloring
- Add more information to RenderLayerEvent and BeginFrameEvent
0.1.1:
New
- Flywheel driven chest and bell rendering, ~20x performance improvement in contrived cases

View file

@ -1,7 +1,7 @@
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
# mod version info
mod_version=0.2.0
mod_version=0.2.2
mc_update_version=1.16
minecraft_version=1.16.5
forge_version=36.1.66

View file

@ -43,6 +43,7 @@ public class Backend {
private Matrix4f projectionMatrix = new Matrix4f();
private boolean instancedArrays;
private boolean enabled;
public boolean chunkCachingEnabled;
private final List<IShaderContext<?>> contexts = new ArrayList<>();
private final Map<ResourceLocation, MaterialSpec<?>> materialRegistry = new HashMap<>();
@ -153,9 +154,11 @@ public class Backend {
enabled = FlwConfig.get()
.enabled() && !OptifineHandler.usingShaders();
chunkCachingEnabled = FlwConfig.get()
.chunkCaching();
}
public boolean canUseInstancing(World world) {
public boolean canUseInstancing(@Nullable World world) {
return canUseInstancing() && isFlywheelWorld(world);
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.gl.attrib;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -97,6 +97,14 @@ public class VecBuffer {
return this;
}
public VecBuffer putColor(byte r, byte g, byte b, byte a) {
internal.put(r);
internal.put(g);
internal.put(b);
internal.put(a);
return this;
}
public VecBuffer putVec3(float x, float y, float z) {
internal.putFloat(x);
internal.putFloat(y);

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.gl.buffer;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.gl;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.gl.shader;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -14,6 +14,10 @@ import com.jozufozu.flywheel.backend.instancing.tile.TileEntityInstance;
public interface IDynamicInstance extends IInstance {
/**
* Called every frame.
* <br>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside of this instance.
* <br>
* {@link Instancer}/{@link InstanceData} creation/acquisition is safe here.
*/
void beginFrame();

View file

@ -16,6 +16,15 @@ public interface IInstance {
void remove();
/**
* When an instance is reset, the instance is deleted and re-created.
*
* <p>
* This is used to handle things like block state changes.
* </p>
*
* @return true if this instance should be reset
*/
boolean shouldReset();
void update();

View file

@ -22,6 +22,10 @@ public interface ITickableInstance extends IInstance {
/**
* Called every tick.
* <br>
* <em>DISPATCHED IN PARALLEL</em>, don't attempt to mutate anything outside of this instance.
* <br>
* {@link Instancer}/{@link InstanceData} creation/acquisition is safe here.
*/
void tick();

View file

@ -1,26 +1,36 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.util.RenderMath;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArraySet;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3f;
public abstract class InstanceManager<T> implements MaterialManager.OriginShiftListener {
public final MaterialManager<?> materialManager;
protected final ArrayList<T> queuedAdditions;
protected final ConcurrentHashMap.KeySetView<T, Boolean> queuedUpdates;
private final Set<T> queuedAdditions;
private final Set<T> queuedUpdates;
protected final Map<T, IInstance> instances;
protected final Object2ObjectOpenHashMap<T, ITickableInstance> tickableInstances;
@ -31,8 +41,8 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
public InstanceManager(MaterialManager<?> materialManager) {
this.materialManager = materialManager;
this.queuedUpdates = ConcurrentHashMap.newKeySet(64);
this.queuedAdditions = new ArrayList<>(64);
this.queuedUpdates = new HashSet<>(64);
this.queuedAdditions = new HashSet<>(64);
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
@ -41,16 +51,41 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
materialManager.addListener(this);
}
/**
* Is the given object capable of being instanced at all?
*
* @return false if on object cannot be instanced.
*/
protected abstract boolean canInstance(T obj);
/**
* Is the given object currently capable of being instanced?
*
* <p>
* This won't be the case for TEs or entities that are outside of loaded chunks.
* </p>
*
* @return true if the object is currently capable of being instanced.
*/
protected abstract boolean canCreateInstance(T obj);
@Nullable
protected abstract IInstance createRaw(T obj);
protected abstract boolean canCreateInstance(T entity);
/**
* Ticks the InstanceManager.
*
* <p>
* {@link ITickableInstance}s get ticked.
* <br>
* Queued updates are processed.
* </p>
*/
public void tick(double cameraX, double cameraY, double cameraZ) {
tick++;
processQueuedUpdates();
// integer camera pos
// integer camera pos as a micro-optimization
int cX = (int) cameraX;
int cY = (int) cameraY;
int cZ = (int) cameraZ;
@ -72,12 +107,6 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
if ((tick % getUpdateDivisor(dX, dY, dZ)) == 0) instance.tick();
});
}
queuedUpdates.forEach(te -> {
queuedUpdates.remove(te);
update(te);
});
}
public void beginFrame(ActiveRenderInfo info) {
@ -114,13 +143,26 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
}
}
public synchronized void queueAdd(T obj) {
public void queueAdd(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
queuedAdditions.add(obj);
synchronized (queuedAdditions) {
queuedAdditions.add(obj);
}
}
/**
* Update the instance associated with an object.
*
* <p>
* By default this is the only hook an IInstance has to change its internal state. This is the lowest frequency
* update hook IInstance gets. For more frequent updates, see {@link ITickableInstance} and
* {@link IDynamicInstance}.
* </p>
*
* @param obj the object to update.
*/
public void update(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
@ -130,9 +172,11 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
if (instance != null) {
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
removeInternal(obj, instance);
createInternal(obj);
} else {
instance.update();
@ -141,11 +185,12 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
}
}
public synchronized void queueUpdate(T obj) {
public void queueUpdate(T obj) {
if (!Backend.getInstance()
.canUseInstancing()) return;
queuedUpdates.add(obj);
synchronized (queuedUpdates) {
queuedUpdates.add(obj);
}
}
public void onLightUpdate(T obj) {
@ -176,7 +221,6 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
tickableInstances.clear();
}
@SuppressWarnings("unchecked")
@Nullable
protected <I extends T> IInstance getInstance(I obj, boolean create) {
if (!Backend.getInstance()
@ -193,11 +237,30 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
}
}
protected synchronized void processQueuedAdditions() {
if (queuedAdditions.size() > 0) {
queuedAdditions.forEach(this::addInternal);
protected void processQueuedAdditions() {
ArrayList<T> queued;
synchronized (queuedAdditions) {
queued = new ArrayList<>(queuedAdditions);
queuedAdditions.clear();
}
if (queued.size() > 0) {
queued.forEach(this::addInternal);
}
}
protected void processQueuedUpdates() {
ArrayList<T> queued;
synchronized (queuedUpdates) {
queued = new ArrayList<>(queuedUpdates);
queuedUpdates.clear();
}
if (queued.size() > 0) {
queued.forEach(this::update);
}
}
protected boolean shouldFrameUpdate(BlockPos worldPos, float lookX, float lookY, float lookZ, int cX, int cY, int cZ) {
@ -220,7 +283,7 @@ public abstract class InstanceManager<T> implements MaterialManager.OriginShiftL
int i = (dSq / 2048);
return divisorSequence[Math.min(i, divisorSequence.length - 1)];
return divisorSequence[MathHelper.clamp(i, 0, divisorSequence.length - 1)];
}
protected void addInternal(T tile) {

View file

@ -0,0 +1,104 @@
package com.jozufozu.flywheel.backend.instancing;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.backend.state.RenderLayer;
import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.IWorld;
import net.minecraftforge.event.TickEvent;
/**
* A manager class for a single world where instancing is supported.
* <br>
* The material manager is shared between the different instance managers.
*/
public class InstanceWorld {
protected final MaterialManager<WorldProgram> materialManager;
protected final InstanceManager<Entity> entityInstanceManager;
protected final InstanceManager<TileEntity> tileEntityInstanceManager;
public InstanceWorld() {
materialManager = MaterialManager.builder(Contexts.WORLD)
.build();
entityInstanceManager = new EntityInstanceManager(materialManager);
tileEntityInstanceManager = new TileInstanceManager(materialManager);
}
public MaterialManager<WorldProgram> getMaterialManager() {
return materialManager;
}
public InstanceManager<Entity> getEntityInstanceManager() {
return entityInstanceManager;
}
public InstanceManager<TileEntity> getTileEntityInstanceManager() {
return tileEntityInstanceManager;
}
/**
* Free all acquired resources and invalidate this instance world.
*/
public void delete() {
materialManager.delete();
}
/**
* Instantiate all the necessary instances to render the given world.
*/
public void loadAll(ClientWorld world) {
world.blockEntityList.forEach(tileEntityInstanceManager::add);
world.entitiesForRendering()
.forEach(entityInstanceManager::add);
}
/**
* Get ready to render a frame:
* <br>
* Check and shift the origin coordinate.
* <br>
* Call {@link IDynamicInstance#beginFrame()} on all instances in this world.
*/
public void beginFrame(BeginFrameEvent event) {
materialManager.checkAndShiftOrigin(event.getInfo());
tileEntityInstanceManager.beginFrame(event.getInfo());
entityInstanceManager.beginFrame(event.getInfo());
}
/**
* Tick the renderers after the game has ticked:
* <br>
* Call {@link ITickableInstance#tick()} on all instances in this world.
*/
public void tick() {
Minecraft mc = Minecraft.getInstance();
Entity renderViewEntity = mc.cameraEntity != null ? mc.cameraEntity : mc.player;
if (renderViewEntity == null) return;
tileEntityInstanceManager.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
entityInstanceManager.tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
}
/**
* Draw the given layer.
*/
public void renderLayer(RenderLayerEvent event) {
event.type.setupRenderState();
materialManager.render(event.layer, event.viewProjection, event.camX, event.camY, event.camZ);
event.type.clearRenderState();
}
}

View file

@ -3,12 +3,7 @@ package com.jozufozu.flywheel.backend.instancing;
import javax.annotation.Nonnull;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.tile.TileInstanceManager;
import com.jozufozu.flywheel.backend.material.MaterialManager;
import com.jozufozu.flywheel.backend.state.RenderLayer;
import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.event.RenderLayerEvent;
@ -30,19 +25,18 @@ import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(Dist.CLIENT)
public class InstancedRenderDispatcher {
private static final WorldAttached<MaterialManager<WorldProgram>> materialManagers = new WorldAttached<>($ -> MaterialManager.builder(Contexts.WORLD).build());
private static final WorldAttached<InstanceManager<Entity>> entityInstanceManager = new WorldAttached<>(world -> new EntityInstanceManager(materialManagers.get(world)));
private static final WorldAttached<InstanceManager<TileEntity>> tileInstanceManager = new WorldAttached<>(world -> new TileInstanceManager(materialManagers.get(world)));
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>($ -> new InstanceWorld());
@Nonnull
public static InstanceManager<TileEntity> getTiles(IWorld world) {
return tileInstanceManager.get(world);
return instanceWorlds.get(world)
.getTileEntityInstanceManager();
}
@Nonnull
public static InstanceManager<Entity> getEntities(IWorld world) {
return entityInstanceManager.get(world);
return instanceWorlds.get(world)
.getEntityInstanceManager();
}
@SubscribeEvent
@ -55,12 +49,7 @@ public class InstancedRenderDispatcher {
ClientWorld world = mc.level;
AnimationTickHolder.tick();
Entity renderViewEntity = mc.cameraEntity != null ? mc.cameraEntity : mc.player;
if (renderViewEntity == null) return;
getTiles(world).tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
getEntities(world).tick(renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
instanceWorlds.get(world).tick();
}
public static void enqueueUpdate(TileEntity te) {
@ -73,29 +62,18 @@ public class InstancedRenderDispatcher {
@SubscribeEvent
public static void onBeginFrame(BeginFrameEvent event) {
materialManagers.get(event.getWorld())
.checkAndShiftOrigin(event.getInfo());
getTiles(event.getWorld()).beginFrame(event.getInfo());
getEntities(event.getWorld()).beginFrame(event.getInfo());
instanceWorlds.get(event.getWorld()).beginFrame(event);
}
@SubscribeEvent
public static void renderLayer(RenderLayerEvent event) {
if (event.layer == null) return;
ClientWorld world = event.getWorld();
if (!Backend.getInstance()
.canUseInstancing(world)) return;
RenderLayer renderLayer = RenderLayer.fromRenderType(event.type);
if (renderLayer == null) return;
event.type.setupRenderState();
materialManagers.get(world)
.render(renderLayer, event.viewProjection, event.camX, event.camY, event.camZ);
event.type.clearRenderState();
instanceWorlds.get(world).renderLayer(event);
}
@SubscribeEvent
@ -108,13 +86,8 @@ public class InstancedRenderDispatcher {
}
public static void loadAllInWorld(ClientWorld world) {
materialManagers.replace(world, MaterialManager::delete);
InstanceManager<TileEntity> tiles = tileInstanceManager.replace(world);
world.blockEntityList.forEach(tiles::add);
InstanceManager<Entity> entities = entityInstanceManager.replace(world);
world.entitiesForRendering()
.forEach(entities::add);
instanceWorlds.replace(world, InstanceWorld::delete)
.loadAll(world);
}
}

View file

@ -1,6 +1,5 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.function.Supplier;
@ -12,19 +11,33 @@ import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.backend.material.MaterialSpec;
import com.jozufozu.flywheel.backend.model.BufferedModel;
import com.jozufozu.flywheel.backend.model.IBufferedModel;
import com.jozufozu.flywheel.backend.model.IndexedModel;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.core.model.ModelUtil;
import com.jozufozu.flywheel.util.AttribUtil;
import net.minecraft.util.math.vector.Vector3i;
/**
* An instancer is how you interact with an instanced model.
* <p>
* Instanced models can have many copies, and on most systems it's very fast to draw all of the copies at once.
* There is no limit to how many copies an instanced model can have.
* Each copy is represented by an InstanceData object.
* </p>
* <p>
* When you call {@link #createInstance()} you are given an InstanceData object that you can manipulate however
* you want. The changes you make to the InstanceData object are automatically made visible, and persistent.
* Changing the position of your InstanceData object every frame means that that copy of the model will be in a
* different position in the world each frame. Setting the position of your InstanceData once and not touching it
* again means that your model will be in the same position in the world every frame. This persistence is useful
* because it means the properties of your model don't have to be re-evaluated every frame.
* </p>
*
* @param <D> the data that represents a copy of the instanced model.
*/
public class Instancer<D extends InstanceData> {
public final Supplier<Vector3i> originCoordinate;
protected final Supplier<IModel> gen;
protected BufferedModel model;
protected IBufferedModel model;
protected final VertexFormat instanceFormat;
protected final IInstanceFactory<D> factory;
@ -40,11 +53,30 @@ public class Instancer<D extends InstanceData> {
boolean anyToRemove;
boolean anyToUpdate;
public Instancer(Supplier<IModel> model, Supplier<Vector3i> originCoordinate, MaterialSpec<D> spec) {
public Instancer(Supplier<IModel> model, MaterialSpec<D> spec) {
this.gen = model;
this.factory = spec.getInstanceFactory();
this.instanceFormat = spec.getInstanceFormat();
this.originCoordinate = originCoordinate;
}
/**
* @return a handle to a new copy of this model.
*/
public D createInstance() {
return _add(factory.create(this));
}
/**
* Copy a data from another Instancer to this.
*
* This has the effect of swapping out one model for another.
* @param inOther the data associated with a different model.
*/
public void stealInstance(D inOther) {
if (inOther.owner == this) return;
inOther.owner.anyToRemove = true;
_add(inOther);
}
public void render() {
@ -59,19 +91,8 @@ public class Instancer<D extends InstanceData> {
vao.unbind();
}
public D createInstance() {
return _add(factory.create(this));
}
public void stealInstance(D inOther) {
if (inOther.owner == this) return;
inOther.owner.anyToRemove = true;
_add(inOther);
}
private void init() {
model = ModelUtil.getIndexedModel(gen.get());
model = new IndexedModel(gen.get());
initialized = true;
if (model.getVertexCount() <= 0)

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.entity;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.tile;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.loading;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -4,36 +4,31 @@ import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import com.jozufozu.flywheel.backend.instancing.Instancer;
import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.core.model.BlockModel;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.util.Pair;
import com.jozufozu.flywheel.util.RenderUtil;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.util.Direction;
import net.minecraft.util.math.vector.Vector3i;
/**
* A collection of Instancers that all have the same format.
* @param <D>
*/
public class InstanceMaterial<D extends InstanceData> {
protected final Supplier<Vector3i> originCoordinate;
protected final Cache<Object, Instancer<D>> models;
protected final MaterialSpec<D> spec;
private final VertexFormat modelFormat;
public InstanceMaterial(Supplier<Vector3i> renderer, MaterialSpec<D> spec) {
this.originCoordinate = renderer;
public InstanceMaterial(MaterialSpec<D> spec) {
this.spec = spec;
this.models = CacheBuilder.newBuilder()
@ -42,7 +37,37 @@ public class InstanceMaterial<D extends InstanceData> {
RenderWork.enqueue(instancer::delete);
})
.build();
modelFormat = this.spec.getModelFormat();
}
/**
* Get an instancer for the given model. Calling this method twice with the same key will return the same instancer.
*
* @param key An object that uniquely identifies the model.
* @param modelSupplier A factory that creates the IModel that you want to render.
* @return An instancer for the given model, capable of rendering many copies for little cost.
*/
public Instancer<D> model(Object key, Supplier<IModel> modelSupplier) {
try {
return models.get(key, () -> new Instancer<>(modelSupplier, spec));
} catch (ExecutionException e) {
throw new RuntimeException("error creating instancer", e);
}
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState) {
return model(partial, () -> new BlockModel(spec.getModelFormat(), partial.get(), referenceState));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir) {
return getModel(partial, referenceState, dir, RenderUtil.rotateToFace(dir));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir, Supplier<MatrixStack> modelTransform) {
return model(Pair.of(dir, partial), () -> new BlockModel(spec.getModelFormat(), partial.get(), referenceState, modelTransform.get()));
}
public Instancer<D> getModel(BlockState toRender) {
return model(toRender, () -> new BlockModel(spec.getModelFormat(), toRender));
}
public boolean nothingToRender() {
@ -72,43 +97,4 @@ public class InstanceMaterial<D extends InstanceData> {
}
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState) {
return model(partial, () -> buildModel(partial.get(), referenceState));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir) {
return getModel(partial, referenceState, dir, RenderUtil.rotateToFace(dir));
}
public Instancer<D> getModel(PartialModel partial, BlockState referenceState, Direction dir, Supplier<MatrixStack> modelTransform) {
return model(Pair.of(dir, partial), () -> buildModel(partial.get(), referenceState, modelTransform.get()));
}
public Instancer<D> getModel(BlockState toRender) {
return model(toRender, () -> buildModel(toRender));
}
public Instancer<D> model(Object key, Supplier<IModel> supplier) {
try {
return models.get(key, () -> new Instancer<>(supplier, originCoordinate, spec));
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
private IModel buildModel(BlockState renderedState) {
BlockRendererDispatcher dispatcher = Minecraft.getInstance()
.getBlockRenderer();
return buildModel(dispatcher.getBlockModel(renderedState), renderedState);
}
private IModel buildModel(IBakedModel model, BlockState renderedState) {
return buildModel(model, renderedState, new MatrixStack());
}
private IModel buildModel(IBakedModel model, BlockState referenceState, MatrixStack ms) {
return new BlockModel(modelFormat, model, referenceState, ms);
}
}

View file

@ -10,6 +10,12 @@ import com.jozufozu.flywheel.core.shader.WorldProgram;
import net.minecraft.util.math.vector.Matrix4f;
/**
* A group of materials all rendered with the same GL state.
*
* The children of a material group will all be rendered at the same time.
* No guarantees are made about the order of draw calls.
*/
public class MaterialGroup<P extends WorldProgram> {
protected final MaterialManager<P> owner;
@ -24,6 +30,17 @@ public class MaterialGroup<P extends WorldProgram> {
this.state = state;
}
/**
* Get the material as defined by the given {@link MaterialSpec spec}.
* @param spec The material you want to create instances with.
* @param <D> The type representing the per instance data.
* @return A
*/
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> material(MaterialSpec<D> spec) {
return (InstanceMaterial<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
}
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
for (MaterialRenderer<P> renderer : renderers) {
renderer.render(viewProjection, camX, camY, camZ);
@ -34,19 +51,6 @@ public class MaterialGroup<P extends WorldProgram> {
}
@SuppressWarnings("unchecked")
public <D extends InstanceData> InstanceMaterial<D> material(MaterialSpec<D> spec) {
return (InstanceMaterial<D>) materials.computeIfAbsent(spec, this::createInstanceMaterial);
}
private InstanceMaterial<?> createInstanceMaterial(MaterialSpec<?> type) {
InstanceMaterial<?> material = new InstanceMaterial<>(owner::getOriginCoordinate, type);
this.renderers.add(new MaterialRenderer<>(owner.getProgram(type.getProgramName()), material, this::setup));
return material;
}
public void clear() {
materials.values().forEach(InstanceMaterial::clear);
}
@ -58,4 +62,12 @@ public class MaterialGroup<P extends WorldProgram> {
materials.clear();
renderers.clear();
}
private InstanceMaterial<?> createInstanceMaterial(MaterialSpec<?> type) {
InstanceMaterial<?> material = new InstanceMaterial<>(type);
this.renderers.add(new MaterialRenderer<>(owner.getProgram(type.getProgramName()), material, this::setup));
return material;
}
}

View file

@ -60,9 +60,44 @@ public class MaterialManager<P extends WorldProgram> {
}
}
/**
* Get a material group that will render in the given layer with the given state.
*
* @param layer The {@link RenderLayer} you want to draw in.
* @param state The {@link IRenderState} you need to draw with.
* @return A material group whose children will
*/
public MaterialGroup<P> state(RenderLayer layer, IRenderState state) {
return layers.get(layer).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> solid(IRenderState state) {
return layers.get(RenderLayer.SOLID).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> cutout(IRenderState state) {
return layers.get(RenderLayer.CUTOUT).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> transparent(IRenderState state) {
return layers.get(RenderLayer.TRANSPARENT).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> defaultSolid() {
return solid(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
public MaterialGroup<P> defaultCutout() {
return cutout(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
public MaterialGroup<P> defaultTransparent() {
return transparent(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
/**
* Render every model for every material.
* @param layer Which vanilla {@link RenderType} is being drawn?
* @param layer Which of the 3 {@link RenderLayer render layers} is being drawn?
* @param viewProjection How do we get from camera space to clip space?
*/
public void render(RenderLayer layer, Matrix4f viewProjection, double camX, double camY, double camZ) {
@ -95,34 +130,6 @@ public class MaterialManager<P extends WorldProgram> {
}
}
public MaterialGroup<P> state(RenderLayer layer, IRenderState state) {
return layers.get(layer).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> solid(IRenderState state) {
return layers.get(RenderLayer.SOLID).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> cutout(IRenderState state) {
return layers.get(RenderLayer.CUTOUT).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> transparent(IRenderState state) {
return layers.get(RenderLayer.TRANSPARENT).computeIfAbsent(state, this::createGroup);
}
public MaterialGroup<P> defaultSolid() {
return solid(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
public MaterialGroup<P> defaultCutout() {
return cutout(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
public MaterialGroup<P> defaultTransparent() {
return transparent(TextureRenderState.get(PlayerContainer.BLOCK_ATLAS));
}
@Deprecated
public <D extends InstanceData> InstanceMaterial<D> getMaterial(MaterialSpec<D> materialType) {
return defaultCutout().material(materialType);
@ -155,6 +162,11 @@ public class MaterialManager<P extends WorldProgram> {
listeners.add(listener);
}
/**
* Maintain the integer origin coordinate to be within a certain distance from the camera in all directions.
*
* This prevents floating point precision issues at high coordinates.
*/
public void checkAndShiftOrigin(ActiveRenderInfo info) {
int cX = MathHelper.floor(info.getPosition().x);
int cY = MathHelper.floor(info.getPosition().y);

View file

@ -4,7 +4,6 @@ import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.instancing.IInstanceFactory;
import com.jozufozu.flywheel.backend.instancing.InstanceData;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
public class MaterialSpec<D extends InstanceData> {
@ -15,19 +14,13 @@ public class MaterialSpec<D extends InstanceData> {
private final VertexFormat modelFormat;
private final VertexFormat instanceFormat;
private final IInstanceFactory<D> instanceFactory;
private final ResourceLocation texture;
public MaterialSpec(ResourceLocation name, ResourceLocation programSpec, VertexFormat modelFormat, VertexFormat instanceFormat, IInstanceFactory<D> instanceFactory) {
this(name, programSpec, modelFormat, instanceFormat, PlayerContainer.BLOCK_ATLAS, instanceFactory);
}
public MaterialSpec(ResourceLocation name, ResourceLocation programSpec, VertexFormat modelFormat, VertexFormat instanceFormat, ResourceLocation texture, IInstanceFactory<D> instanceFactory) {
this.name = name;
this.programSpec = programSpec;
this.modelFormat = modelFormat;
this.instanceFormat = instanceFormat;
this.instanceFactory = instanceFactory;
this.texture = texture;
}
public ResourceLocation getProgramName() {

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.material;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -1,23 +1,23 @@
package com.jozufozu.flywheel.backend.model;
import java.util.function.Supplier;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.util.AttribUtil;
public class ArrayModelRenderer extends ModelRenderer {
protected GlVertexArray vao;
public ArrayModelRenderer(BufferedModel model) {
public ArrayModelRenderer(Supplier<IModel> model) {
super(model);
vao = new GlVertexArray();
vao.bind();
model.setupState();
vao.unbind();
model.clearState();
}
@Override
public void draw() {
if (!model.valid()) return;
if (!initialized) init();
if (!isValid()) return;
vao.bind();
@ -25,4 +25,27 @@ public class ArrayModelRenderer extends ModelRenderer {
vao.unbind();
}
@Override
protected void init() {
initialized = true;
IModel model = modelSupplier.get();
if (model.empty()) return;
this.model = new IndexedModel(model);
vao = new GlVertexArray();
vao.bind();
// bind the model's vbo to our vao
this.model.setupState();
AttribUtil.enableArrays(this.model.getAttributeCount());
vao.unbind();
this.model.clearState();
}
}

View file

@ -9,46 +9,45 @@ import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer;
import com.jozufozu.flywheel.core.model.IModel;
import com.jozufozu.flywheel.util.AttribUtil;
public class BufferedModel {
public class BufferedModel implements IBufferedModel {
protected final IModel model;
protected final GlPrimitive primitiveMode;
protected final ByteBuffer data;
protected final VertexFormat format;
protected final int vertexCount;
protected GlBuffer vbo;
protected boolean deleted;
public BufferedModel(GlPrimitive primitiveMode, VertexFormat format, ByteBuffer data, int vertices) {
public BufferedModel(GlPrimitive primitiveMode, IModel model) {
this.model = model;
this.primitiveMode = primitiveMode;
this.data = data;
this.format = format;
this.vertexCount = vertices;
vbo = new GlBuffer(GlBufferType.ARRAY_BUFFER);
vbo.bind();
// allocate the buffer on the gpu
vbo.alloc(this.data.capacity());
vbo.alloc(model.size());
// mirror it in system memory so we can write to it, and upload our model.
vbo.getBuffer(0, this.data.capacity())
.put(this.data)
.flush();
MappedBuffer buffer = vbo.getBuffer(0, model.size());
model.buffer(buffer);
buffer.flush();
vbo.unbind();
}
public VertexFormat getFormat() {
return format;
return model.format();
}
public int getVertexCount() {
return vertexCount;
return model.vertexCount();
}
public boolean valid() {
return vertexCount > 0 && !deleted;
return getVertexCount() > 0 && !deleted;
}
/**
@ -57,7 +56,7 @@ public class BufferedModel {
public void setupState() {
vbo.bind();
AttribUtil.enableArrays(getAttributeCount());
format.vertexAttribPointers(0);
getFormat().vertexAttribPointers(0);
}
public void clearState() {
@ -66,7 +65,7 @@ public class BufferedModel {
}
public void drawCall() {
glDrawArrays(primitiveMode.glEnum, 0, vertexCount);
glDrawArrays(primitiveMode.glEnum, 0, getVertexCount());
}
/**
@ -75,7 +74,7 @@ public class BufferedModel {
public void drawInstances(int instanceCount) {
if (!valid()) return;
Backend.getInstance().compat.drawInstanced.drawArraysInstanced(primitiveMode, 0, vertexCount, instanceCount);
Backend.getInstance().compat.drawInstanced.drawArraysInstanced(primitiveMode, 0, getVertexCount(), instanceCount);
}
public void delete() {
@ -84,10 +83,5 @@ public class BufferedModel {
deleted = true;
vbo.delete();
}
public int getAttributeCount() {
return format.getAttributeCount();
}
}

View file

@ -0,0 +1,32 @@
package com.jozufozu.flywheel.backend.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
public interface IBufferedModel {
VertexFormat getFormat();
int getVertexCount();
boolean valid();
/**
* The VBO/VAO should be bound externally.
*/
void setupState();
void clearState();
void drawCall();
/**
* Draws many instances of this model, assuming the appropriate state is already bound.
*/
void drawInstances(int instanceCount);
void delete();
default int getAttributeCount() {
return getFormat().getAttributeCount();
}
}

View file

@ -8,6 +8,7 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlPrimitive;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.model.IModel;
/**
* An indexed triangle model. Just what the driver ordered.
@ -18,15 +19,10 @@ public class IndexedModel extends BufferedModel {
protected ElementBuffer ebo;
public IndexedModel(VertexFormat modelFormat, ByteBuffer buf, int vertices, ElementBuffer ebo) {
super(GlPrimitive.TRIANGLES, modelFormat, buf, vertices);
public IndexedModel(IModel model) {
super(GlPrimitive.TRIANGLES, model);
this.ebo = ebo;
}
public static IndexedModel fromSequentialQuads(VertexFormat modelFormat, ByteBuffer quads, int vertices) {
return new IndexedModel(modelFormat, quads, vertices, QuadConverter.getInstance()
.quads2Tris(vertices / 4));
this.ebo = model.createEBO();
}
@Override
@ -48,13 +44,8 @@ public class IndexedModel extends BufferedModel {
@Override
public void drawInstances(int instanceCount) {
if (vertexCount <= 0 || deleted) return;
if (!valid()) return;
Backend.getInstance().compat.drawInstanced.drawElementsInstanced(primitiveMode, ebo.elementCount, ebo.eboIndexType, 0, instanceCount);
}
@Override
public void delete() {
super.delete();
}
}

View file

@ -1,18 +1,26 @@
package com.jozufozu.flywheel.backend.model;
import java.util.function.Supplier;
import com.jozufozu.flywheel.core.model.IModel;
public class ModelRenderer {
protected BufferedModel model;
protected Supplier<IModel> modelSupplier;
protected IBufferedModel model;
public ModelRenderer(BufferedModel model) {
this.model = model;
protected boolean initialized;
public ModelRenderer(Supplier<IModel> modelSupplier) {
this.modelSupplier = modelSupplier;
}
/**
* Renders this model, checking first if there is anything to render.
*/
public void draw() {
if (!model.valid()) return;
if (!initialized) init();
if (!isValid()) return;
model.setupState();
model.drawCall();
@ -20,6 +28,20 @@ public class ModelRenderer {
}
public void delete() {
model.delete();
if (model != null)
model.delete();
}
protected void init() {
initialized = true;
IModel model = modelSupplier.get();
if (model.empty()) return;
this.model = new IndexedModel(model);
}
protected boolean isValid() {
return model != null && model.valid();
}
}

View file

@ -4,9 +4,36 @@ import javax.annotation.Nullable;
import net.minecraft.client.renderer.RenderType;
/**
* The 3 discrete stages the world is rendered in.
*/
public enum RenderLayer {
/**
* Solid layer:<br>
*
* All polygons will entirely occlude everything behind them.
*
* <br><br>
* e.g. stone, dirt, solid blocks
*/
SOLID,
/**
* Cutout layer:<br>
*
* <em>Fragments</em> will either occlude or not occlude depending on the texture/material.
*
* <br><br>
* e.g. leaves, cobwebs, tall grass, saplings, glass
*/
CUTOUT,
/**
* Transparent layer:<br>
*
* Nothing is guaranteed to occlude and fragments blend their color with what's behind them.
*
* <br><br>
* e.g. stained glass, water
*/
TRANSPARENT,
;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.state;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -18,6 +18,7 @@ import net.minecraftforge.api.distmarker.OnlyIn;
public enum BooleanConfig {
ENGINE(() -> BooleanConfig::enabled),
NORMAL_OVERLAY(() -> BooleanConfig::normalOverlay),
CHUNK_CACHING(() -> BooleanConfig::chunkCaching),
;
final Supplier<Consumer<BooleanDirective>> receiver;
@ -71,6 +72,25 @@ public enum BooleanConfig {
player.displayClientMessage(text, false);
}
@OnlyIn(Dist.CLIENT)
private static void chunkCaching(BooleanDirective state) {
ClientPlayerEntity player = Minecraft.getInstance().player;
if (player == null || state == null) return;
if (state == BooleanDirective.DISPLAY) {
ITextComponent text = new StringTextComponent("Chunk caching is currently: ").append(boolToText(FlwConfig.get().client.chunkCaching.get()));
player.displayClientMessage(text, false);
return;
}
FlwConfig.get().client.chunkCaching.set(state.get());
ITextComponent text = boolToText(FlwConfig.get().client.chunkCaching.get()).append(new StringTextComponent(" chunk caching").withStyle(TextFormatting.WHITE));
player.displayClientMessage(text, false);
Backend.reloadWorldRenderers();
}
private static IFormattableTextComponent boolToText(boolean b) {
return b ? new StringTextComponent("enabled").withStyle(TextFormatting.DARK_GREEN) : new StringTextComponent("disabled").withStyle(TextFormatting.RED);
}

View file

@ -16,6 +16,8 @@ public class FlwCommands {
dispatcher.register(Commands.literal("flywheel")
.then(new BooleanConfigCommand("backend", BooleanConfig.ENGINE).register())
.then(new BooleanConfigCommand("debugNormals", BooleanConfig.NORMAL_OVERLAY).register()));
.then(new BooleanConfigCommand("debugNormals", BooleanConfig.NORMAL_OVERLAY).register())
.then(new BooleanConfigCommand("chunkCaching", BooleanConfig.CHUNK_CACHING).register())
);
}
}

View file

@ -34,12 +34,17 @@ public class FlwConfig {
return client.debugNormals.get();
}
public boolean chunkCaching() {
return client.chunkCaching.get();
}
public static void init() {
}
public static class ClientConfig {
public final BooleanValue enabled;
public final BooleanValue debugNormals;
public final BooleanValue chunkCaching;
public ClientConfig(ForgeConfigSpec.Builder builder) {
@ -48,6 +53,9 @@ public class FlwConfig {
debugNormals = builder.comment("Enable or disable a debug overlay that colors pixels by their normal")
.define("debugNormals", false);
chunkCaching = builder.comment("Cache chunk lookups to improve performance.")
.define("chunkCaching", true);
}
}
}

View file

@ -0,0 +1,16 @@
package com.jozufozu.flywheel.core;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.renderer.culling.ClippingHelper;
/**
* Used to capture the ClippingHelper from WorldRenderer#renderLevel
*/
public class Clipping {
/**
* Assigned in {@link com.jozufozu.flywheel.mixin.GlobalClippingHelperMixin this} mixin.
*/
public static ClippingHelper HELPER;
}

View file

@ -9,12 +9,21 @@ public class Formats {
.addAttributes(CommonAttributes.VEC3, CommonAttributes.NORMAL, CommonAttributes.UV)
.build();
public static final VertexFormat COLORED_LIT_MODEL = VertexFormat.builder()
.addAttributes(CommonAttributes.VEC3,
CommonAttributes.NORMAL,
CommonAttributes.UV,
CommonAttributes.RGBA,
CommonAttributes.LIGHT)
.build();
public static final VertexFormat TRANSFORMED = litInstance().addAttributes(MatrixAttributes.MAT4, MatrixAttributes.MAT3)
.build();
public static final VertexFormat ORIENTED = litInstance().addAttributes(CommonAttributes.VEC3, CommonAttributes.VEC3, CommonAttributes.QUATERNION)
.build();
public static VertexFormat.Builder litInstance() {
public static VertexFormat.Builder litInstance() {
return VertexFormat.builder()
.addAttributes(CommonAttributes.LIGHT, CommonAttributes.RGBA);
}

View file

@ -53,8 +53,6 @@ public class CrumblingRenderer {
INVALIDATOR = state.getSecond();
}
private static final RenderType crumblingLayer = ModelBakery.DESTROY_TYPES.get(0);
public static void renderBreaking(ClientWorld world, Matrix4f viewProjection, double cameraX, double cameraY, double cameraZ) {
if (!Backend.getInstance()
.canUseInstancing(world)) return;
@ -64,6 +62,7 @@ public class CrumblingRenderer {
if (activeStages.isEmpty()) return;
State state = STATE.get();
RenderType layer = ModelBakery.DESTROY_TYPES.get(0);
InstanceManager<TileEntity> renderer = state.instanceManager;
@ -71,7 +70,7 @@ public class CrumblingRenderer {
ActiveRenderInfo info = Minecraft.getInstance().gameRenderer.getMainCamera();
MaterialManager<CrumblingProgram> materials = state.materialManager;
crumblingLayer.setupRenderState();
layer.setupRenderState();
for (Int2ObjectMap.Entry<List<TileEntity>> stage : activeStages.int2ObjectEntrySet()) {
int i = stage.getIntKey();
@ -92,7 +91,7 @@ public class CrumblingRenderer {
}
crumblingLayer.clearRenderState();
layer.clearRenderState();
GlTextureUnit.T0.makeActive();
Texture breaking = textureManager.getTexture(ModelBakery.BREAKING_LOCATIONS.get(0));

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.core.crumbling;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -23,6 +23,9 @@ import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
/**
* A model of a single block.
*/
public class BlockModel implements IModel {
private static final MatrixStack IDENTITY = new MatrixStack();
@ -30,6 +33,12 @@ public class BlockModel implements IModel {
private final VertexFormat modelFormat;
public BlockModel(VertexFormat modelFormat, BlockState state) {
this(modelFormat, Minecraft.getInstance()
.getBlockRenderer()
.getBlockModel(state), state);
}
public BlockModel(VertexFormat modelFormat, IBakedModel model, BlockState referenceState) {
this(modelFormat, model, referenceState, IDENTITY);
}
@ -63,12 +72,6 @@ public class BlockModel implements IModel {
}
}
@Override
public ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertexCount() / 4);
}
public static BufferBuilder getBufferBuilder(IBakedModel model, BlockState referenceState, MatrixStack ms) {
Minecraft mc = Minecraft.getInstance();
BlockRendererDispatcher dispatcher = mc.getBlockRenderer();

View file

@ -3,9 +3,27 @@ package com.jozufozu.flywheel.core.model;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.backend.model.ElementBuffer;
import com.jozufozu.flywheel.core.QuadConverter;
/**
* A model that can be rendered by flywheel.
*
* <p>
* It is expected that the following assertion will not fail:
* </p>
*
* <pre>{@code
* IModel model = ...;
* VecBuffer into = ...;
*
* int initial = VecBuffer.unwrap().position();
*
* model.buffer(into);
*
* int final = VecBuffer.unwrap().position();
*
* assert model.size() == final - initial;
* }</pre>
*/
public interface IModel {
@ -14,13 +32,43 @@ public interface IModel {
*/
void buffer(VecBuffer buffer);
/**
* @return The number of vertices the model has.
*/
int vertexCount();
/**
* @return The format of this model's vertices
*/
VertexFormat format();
ElementBuffer createEBO();
/**
* Create an element buffer object that indexes the vertices of this model.
*
* <p>
* Very often models in minecraft are made up of sequential quads, which is a very predictable pattern.
* The default implementation accommodates this, however this can be overridden to change the behavior and
* support more complex models.
* </p>
* @return an element buffer object indexing this model's vertices.
*/
default ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertexCount() / 4);
}
/**
* The size in bytes that this model's data takes up.
*/
default int size() {
return vertexCount() * format().getStride();
}
/**
* Is there nothing to render?
* @return true if there are no vertices.
*/
default boolean empty() {
return vertexCount() == 0;
}
}

View file

@ -43,10 +43,4 @@ public class ModelPart implements IModel {
public VertexFormat format() {
return Formats.UNLIT_MODEL;
}
@Override
public ElementBuffer createEBO() {
return QuadConverter.getInstance()
.quads2Tris(vertices / 4);
}
}

View file

@ -1,21 +1,62 @@
package com.jozufozu.flywheel.core.model;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.backend.model.IndexedModel;
import java.util.Collection;
import java.util.Random;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.block.BlockRenderType;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockModelRenderer;
import net.minecraft.client.renderer.BlockModelShapes;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.gen.feature.template.Template;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.common.util.Lazy;
public class ModelUtil {
public static IndexedModel getIndexedModel(IModel blockModel) {
ByteBuffer vertices = ByteBuffer.allocate(blockModel.size());
vertices.order(ByteOrder.nativeOrder());
private static final Lazy<BlockModelRenderer> MODEL_RENDERER = Lazy.of(() -> new BlockModelRenderer(Minecraft.getInstance().getBlockColors()));
private static final Lazy<BlockModelShapes> BLOCK_MODELS = Lazy.of(() -> Minecraft.getInstance().getModelManager().getBlockModelShaper());
blockModel.buffer(new VecBuffer(vertices));
public static BufferBuilder getBufferBuilderFromTemplate(IBlockDisplayReader renderWorld, RenderType layer, Collection<Template.BlockInfo> blocks) {
MatrixStack ms = new MatrixStack();
Random random = new Random();
BufferBuilder builder = new BufferBuilder(DefaultVertexFormats.BLOCK.getIntegerSize());
builder.begin(GL_QUADS, DefaultVertexFormats.BLOCK);
((Buffer) vertices).rewind();
ForgeHooksClient.setRenderLayer(layer);
BlockModelRenderer.enableCaching();
for (Template.BlockInfo info : blocks) {
BlockState state = info.state;
return new IndexedModel(blockModel.format(), vertices, blockModel.vertexCount(), blockModel.createEBO());
if (state.getRenderShape() != BlockRenderType.MODEL)
continue;
if (!RenderTypeLookup.canRenderInLayer(state, layer))
continue;
BlockPos pos = info.pos;
ms.pushPose();
ms.translate(pos.getX(), pos.getY(), pos.getZ());
MODEL_RENDERER.get().renderModel(renderWorld, BLOCK_MODELS.get().getBlockModel(state), state, pos, ms, builder, true,
random, 42, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE);
ms.popPose();
}
BlockModelRenderer.clearCache();
ForgeHooksClient.setRenderLayer(null);
builder.end();
return builder;
}
}

View file

@ -0,0 +1,53 @@
package com.jozufozu.flywheel.core.model;
import java.util.Collection;
import com.jozufozu.flywheel.backend.gl.attrib.VertexFormat;
import com.jozufozu.flywheel.backend.gl.buffer.VecBuffer;
import com.jozufozu.flywheel.core.Formats;
import com.jozufozu.flywheel.util.BufferBuilderReader;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.IBlockDisplayReader;
import net.minecraft.world.gen.feature.template.Template;
public class WorldModel implements IModel {
private final BufferBuilderReader reader;
public WorldModel(IBlockDisplayReader renderWorld, RenderType layer, Collection<Template.BlockInfo> blocks) {
reader = new BufferBuilderReader(ModelUtil.getBufferBuilderFromTemplate(renderWorld, layer, blocks));
}
@Override
public void buffer(VecBuffer vertices) {
for (int i = 0; i < vertexCount(); i++) {
vertices.putVec3(reader.getX(i), reader.getY(i), reader.getZ(i));
vertices.putVec3(reader.getNX(i), reader.getNY(i), reader.getNZ(i));
vertices.putVec2(reader.getU(i), reader.getV(i));
vertices.putColor(reader.getR(i), reader.getG(i), reader.getB(i), reader.getA(i));
int light = reader.getLight(i);
byte block = (byte) (LightTexture.block(light) << 4);
byte sky = (byte) (LightTexture.sky(light) << 4);
vertices.putVec2(block, sky);
}
}
@Override
public int vertexCount() {
return reader.getVertexCount();
}
@Override
public VertexFormat format() {
return Formats.COLORED_LIT_MODEL;
}
}

View file

@ -5,6 +5,7 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.culling.ClippingHelper;
import net.minecraft.client.world.ClientWorld;
import net.minecraftforge.eventbus.api.Event;
@ -14,13 +15,15 @@ public class BeginFrameEvent extends Event {
private final ActiveRenderInfo info;
private final GameRenderer gameRenderer;
private final LightTexture lightTexture;
private final ClippingHelper clippingHelper;
public BeginFrameEvent(ClientWorld world, MatrixStack stack, ActiveRenderInfo info, GameRenderer gameRenderer, LightTexture lightTexture) {
public BeginFrameEvent(ClientWorld world, MatrixStack stack, ActiveRenderInfo info, GameRenderer gameRenderer, LightTexture lightTexture, ClippingHelper clippingHelper) {
this.world = world;
this.stack = stack;
this.info = info;
this.gameRenderer = gameRenderer;
this.lightTexture = lightTexture;
this.clippingHelper = clippingHelper;
}
public ClientWorld getWorld() {
@ -42,4 +45,8 @@ public class BeginFrameEvent extends Event {
public LightTexture getLightTexture() {
return lightTexture;
}
public ClippingHelper getClippingHelper() {
return clippingHelper;
}
}

View file

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.event;
import javax.annotation.Nullable;
import net.minecraft.client.world.ClientWorld;
import net.minecraftforge.eventbus.api.Event;
@ -10,6 +12,7 @@ public class ReloadRenderersEvent extends Event {
this.world = world;
}
@Nullable
public ClientWorld getWorld() {
return world;
}

View file

@ -1,6 +1,13 @@
package com.jozufozu.flywheel.event;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.state.RenderLayer;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeBuffers;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraftforge.eventbus.api.Event;
@ -8,18 +15,36 @@ import net.minecraftforge.eventbus.api.Event;
public class RenderLayerEvent extends Event {
private final ClientWorld world;
public final RenderType type;
public final MatrixStack stack;
public final Matrix4f viewProjection;
public final RenderTypeBuffers buffers;
public final double camX;
public final double camY;
public final double camZ;
public final RenderLayer layer;
public RenderLayerEvent(ClientWorld world, RenderType type, Matrix4f viewProjection, double camX, double camY, double camZ) {
public RenderLayerEvent(ClientWorld world, RenderType type, MatrixStack stack, RenderTypeBuffers buffers, double camX, double camY, double camZ) {
this.world = world;
this.type = type;
this.viewProjection = viewProjection;
this.stack = stack;
viewProjection = stack.last()
.pose()
.copy();
viewProjection.multiplyBackward(Backend.getInstance()
.getProjectionMatrix());
this.buffers = buffers;
this.camX = camX;
this.camY = camY;
this.camZ = camZ;
this.layer = RenderLayer.fromRenderType(type);
}
@Nullable
public RenderLayer getLayer() {
return layer;
}
public ClientWorld getWorld() {

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.event;
import javax.annotation.ParametersAreNonnullByDefault;
import mcp.MethodsReturnNonnullByDefault;

View file

@ -1,6 +1,8 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@ -8,7 +10,10 @@ import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.jozufozu.flywheel.backend.Backend;
import net.minecraft.client.multiplayer.ClientChunkProvider;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.PacketBuffer;
import net.minecraft.world.biome.BiomeContainer;
@ -20,6 +25,9 @@ import net.minecraft.world.chunk.IChunk;
@Mixin(ClientChunkProvider.class)
public abstract class FastChunkProviderMixin extends AbstractChunkProvider {
@Shadow
@Final
private ClientWorld level;
@Unique
private int lastX;
@Unique
@ -32,36 +40,52 @@ public abstract class FastChunkProviderMixin extends AbstractChunkProvider {
at = @At("HEAD"),
cancellable = true)
public void returnCachedChunk(int x, int z, ChunkStatus status, boolean create, CallbackInfoReturnable<IChunk> cir) {
if (status.isOrAfter(ChunkStatus.FULL) && lastChunk != null && x == lastX && z == lastZ) {
cir.setReturnValue(lastChunk);
if (Backend.getInstance().chunkCachingEnabled && status.isOrAfter(ChunkStatus.FULL)) {
synchronized (level) {
if (lastChunk != null && x == lastX && z == lastZ) {
cir.setReturnValue(lastChunk);
}
}
}
}
@Inject(method = "getChunk",
at = @At("RETURN"))
public void cacheChunk(int x, int z, ChunkStatus status, boolean create, CallbackInfoReturnable<IChunk> cir) {
if (status.isOrAfter(ChunkStatus.FULL)) {
lastChunk = cir.getReturnValue();
lastX = x;
lastZ = z;
if (Backend.getInstance().chunkCachingEnabled && status.isOrAfter(ChunkStatus.FULL)) {
synchronized (level) {
lastChunk = cir.getReturnValue();
lastX = x;
lastZ = z;
}
}
}
@Inject(method = "drop", at = @At("HEAD"))
public void invalidateOnDrop(int x, int z, CallbackInfo ci) {
if (x == lastX && z == lastZ)
lastChunk = null;
if (Backend.getInstance().chunkCachingEnabled) {
synchronized (level) {
if (x == lastX && z == lastZ) lastChunk = null;
}
}
}
@Inject(method = "replaceWithPacketData", at = @At("HEAD"))
public void invalidateOnPacket(int x, int z, BiomeContainer p_228313_3_, PacketBuffer p_228313_4_, CompoundNBT p_228313_5_, int p_228313_6_, boolean p_228313_7_, CallbackInfoReturnable<Chunk> cir) {
if (x == lastX && z == lastZ)
lastChunk = null;
if (Backend.getInstance().chunkCachingEnabled) {
synchronized (level) {
if (x == lastX && z == lastZ) lastChunk = null;
}
}
}
@Redirect(method = "isTickingChunk", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientChunkProvider;hasChunk(II)Z"))
public boolean redirectTicking(ClientChunkProvider clientChunkProvider, int x, int z) {
if (lastChunk != null && x == lastX && z == lastZ) return true;
if (Backend.getInstance().chunkCachingEnabled) {
synchronized (level) {
if (lastChunk != null && x == lastX && z == lastZ) return true;
}
}
return clientChunkProvider.hasChunk(x, z);
}

View file

@ -0,0 +1,20 @@
package com.jozufozu.flywheel.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.core.Clipping;
import net.minecraft.client.renderer.culling.ClippingHelper;
import net.minecraft.util.math.vector.Matrix4f;
@Mixin(ClippingHelper.class)
public class GlobalClippingHelperMixin {
@Inject(at = @At("TAIL"), method = "<init>")
private void init(Matrix4f p_i226026_1_, Matrix4f p_i226026_2_, CallbackInfo ci) {
Clipping.HELPER = (ClippingHelper) (Object) this;
}
}

View file

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.mixin;
import org.lwjgl.opengl.GL20;
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;
@ -10,6 +11,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.OptifineHandler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.Clipping;
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
@ -21,6 +23,7 @@ import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeBuffers;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.BlockPos;
@ -37,9 +40,13 @@ public class RenderHooksMixin {
@Shadow
private ClientWorld level;
@Shadow
@Final
private RenderTypeBuffers renderBuffers;
@Inject(at = @At(value = "INVOKE", target = "net.minecraft.client.renderer.WorldRenderer.compileChunksUntil(J)V"), method = "renderLevel")
private void setupFrame(MatrixStack stack, float p_228426_2_, long p_228426_3_, boolean p_228426_5_, ActiveRenderInfo info, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f p_228426_9_, CallbackInfo ci) {
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(level, stack, info, gameRenderer, lightTexture));
private void setupFrame(MatrixStack stack, float p_228426_2_, long p_228426_3_, boolean p_228426_5_, ActiveRenderInfo info, GameRenderer gameRenderer, LightTexture lightTexture, Matrix4f projection, CallbackInfo ci) {
MinecraftForge.EVENT_BUS.post(new BeginFrameEvent(level, stack, info, gameRenderer, lightTexture, Clipping.HELPER));
}
/**
@ -49,15 +56,14 @@ public class RenderHooksMixin {
*/
@Inject(at = @At("TAIL"), method = "renderChunkLayer")
private void renderLayer(RenderType type, MatrixStack stack, double camX, double camY, double camZ, CallbackInfo ci) {
Matrix4f view = stack.last()
.pose();
Matrix4f viewProjection = view.copy();
viewProjection.multiplyBackward(Backend.getInstance()
.getProjectionMatrix());
MinecraftForge.EVENT_BUS.post(new RenderLayerEvent(level, type, viewProjection, camX, camY, camZ));
RenderTypeBuffers renderBuffers = this.renderBuffers;
MinecraftForge.EVENT_BUS.post(new RenderLayerEvent(level, type, stack, renderBuffers, camX, camY, camZ));
if (!OptifineHandler.usingShaders()) GL20.glUseProgram(0);
renderBuffers.bufferSource().endBatch(type);
}
@Inject(at = @At("TAIL"), method = "allChanged")

View file

@ -36,6 +36,10 @@ public class Lazy<T> {
return Pair.of(lazy, killSwitch);
}
public static <T> Lazy<T> of(NonNullSupplier<T> factory) {
return new Lazy<>(factory);
}
public static class KillSwitch<T> {
private final Lazy<T> lazy;

View file

@ -2,7 +2,31 @@ package com.jozufozu.flywheel.util;
public class RenderMath {
/**
* Convert a signed, normalized floating point value into a normalized byte.
*/
public static byte nb(float f) {
return (byte) (f * 127);
}
/**
* Convert a signed byte into a normalized float.
*/
public static float f(byte b) {
return b / 127f;
}
/**
* Convert an unsigned byte into a normalized float.
*/
public static float uf(byte b) {
return (float) (Byte.toUnsignedInt(b)) / 255f;
}
/**
* Convert an unsigned, normalized float into an unsigned normalized byte.
*/
public static byte unb(float f) {
return (byte) Math.floor(f * 255);
}
}

View file

@ -1,95 +0,0 @@
package com.jozufozu.flywheel.util.transform;
import java.util.ArrayDeque;
import java.util.Deque;
import net.minecraft.util.math.vector.Quaternion;
public class QuaternionTransformStack implements TransformStack {
private final Deque<Transform> stack;
public QuaternionTransformStack() {
stack = new ArrayDeque<>();
stack.add(new Transform());
}
@Override
public TransformStack translate(double x, double y, double z) {
Transform peek = stack.peek();
double qx = peek.qx;
double qy = peek.qy;
double qz = peek.qz;
double qw = peek.qw;
peek.x += qw * x + qy * z - qz * y;
peek.y += qw * y - qx * z + qz * x;
peek.z += qw * z + qx * y - qy * x;
return this;
}
@Override
public TransformStack multiply(Quaternion quaternion) {
return this;
}
@Override
public TransformStack push() {
stack.push(stack.peek().copy());
return this;
}
@Override
public TransformStack scale(float factor) {
return this;
}
@Override
public TransformStack pop() {
if (stack.size() == 1) {
stack.peek().loadIdentity();
} else {
stack.pop();
}
return this;
}
private static class Transform {
public double qx;
public double qy;
public double qz;
public double qw;
public double x;
public double y;
public double z;
public Transform() {
qw = 1.0;
}
public void loadIdentity() {
x = y = z = 0.0;
qx = qy = qz = 0.0;
qw = 1.0;
}
public Transform copy() {
Transform transform = new Transform();
transform.qx = this.qx;
transform.qy = this.qy;
transform.qz = this.qz;
transform.qw = this.qw;
transform.x = this.x;
transform.y = this.y;
transform.z = this.z;
return transform;
}
}
}

View file

@ -1,7 +1,7 @@
modLoader = "javafml"
loaderVersion = "[36,)"
issueTrackerURL = "https://github.com/Jozufozu/Flywheel/issues"
license = "LGPLv3"
license = "MIT"
[[mods]]
modId = "flywheel"

View file

@ -18,7 +18,8 @@
"atlas.SheetDataAccessor",
"light.LightUpdateMixin",
"light.NetworkLightUpdateMixin",
"FastChunkProviderMixin"
"FastChunkProviderMixin",
"GlobalClippingHelperMixin"
],
"injectors": {
"defaultRequire": 0