mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 23:47:09 +01:00
Merge branch 'dev' into shader-pipeline
This commit is contained in:
commit
9e699b7715
56 changed files with 1015 additions and 511 deletions
140
LICENCE.md
140
LICENCE.md
|
@ -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.
|
||||
|
|
14
build.gradle
14
build.gradle
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.gl.attrib;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.gl.buffer;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.gl;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.gl.shader;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.instancing.entity;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.instancing.tile;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.loading;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.material;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
;
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.state;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
src/main/java/com/jozufozu/flywheel/core/Clipping.java
Normal file
16
src/main/java/com/jozufozu/flywheel/core/Clipping.java
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.core.crumbling;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.event;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
modLoader = "javafml"
|
||||
loaderVersion = "[36,)"
|
||||
issueTrackerURL = "https://github.com/Jozufozu/Flywheel/issues"
|
||||
license = "LGPLv3"
|
||||
license = "MIT"
|
||||
|
||||
[[mods]]
|
||||
modId = "flywheel"
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
"atlas.SheetDataAccessor",
|
||||
"light.LightUpdateMixin",
|
||||
"light.NetworkLightUpdateMixin",
|
||||
"FastChunkProviderMixin"
|
||||
"FastChunkProviderMixin",
|
||||
"GlobalClippingHelperMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 0
|
||||
|
|
Loading…
Reference in a new issue