quads, RenderType renderLayer, ItemStackRenderState.FoilType glintType, MeshView mesh);
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/ColorHelper.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/ColorHelper.java
new file mode 100644
index 000000000..80003422e
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/ColorHelper.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.helper;
+
+import java.nio.ByteOrder;
+
+/**
+ * Static routines of general utility for renderer implementations.
+ * Renderers are not required to use these helpers, but they were
+ * designed to be usable without the default renderer.
+ */
+public abstract class ColorHelper {
+ private ColorHelper() { }
+
+ private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+
+ /**
+ * Component-wise max.
+ */
+ public static int maxLight(int l0, int l1) {
+ if (l0 == 0) return l1;
+ if (l1 == 0) return l0;
+
+ return Math.max(l0 & 0xFFFF, l1 & 0xFFFF) | Math.max(l0 & 0xFFFF0000, l1 & 0xFFFF0000);
+ }
+
+ /** Component-wise multiply. Components need to be in same order in both inputs! */
+ public static int multiplyColor(int color1, int color2) {
+ if (color1 == -1) {
+ return color2;
+ } else if (color2 == -1) {
+ return color1;
+ }
+
+ final int alpha = ((color1 >>> 24) & 0xFF) * ((color2 >>> 24) & 0xFF) / 0xFF;
+ final int red = ((color1 >>> 16) & 0xFF) * ((color2 >>> 16) & 0xFF) / 0xFF;
+ final int green = ((color1 >>> 8) & 0xFF) * ((color2 >>> 8) & 0xFF) / 0xFF;
+ final int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF;
+
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ /** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */
+ public static int multiplyRGB(int color, float shade) {
+ final int alpha = ((color >>> 24) & 0xFF);
+ final int red = (int) (((color >>> 16) & 0xFF) * shade);
+ final int green = (int) (((color >>> 8) & 0xFF) * shade);
+ final int blue = (int) ((color & 0xFF) * shade);
+
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ /**
+ * Component-wise max.
+ */
+ public static int maxBrightness(int b0, int b1) {
+ if (b0 == 0) return b1;
+ if (b1 == 0) return b0;
+
+ return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000);
+ }
+
+ /*
+ Renderer color format: ARGB (0xAARRGGBB)
+ Vanilla color format (little endian): ABGR (0xAABBGGRR)
+ Vanilla color format (big endian): RGBA (0xRRGGBBAA)
+
+ Why does the vanilla color format change based on endianness?
+ See VertexConsumer#quad. Quad data is loaded as integers into
+ a native byte order buffer. Color is read directly from bytes
+ 12, 13, 14 of each vertex. A different byte order will yield
+ different results.
+
+ The renderer always uses ARGB because the API color methods
+ always consume and return ARGB. Vanilla block and item colors
+ also use ARGB.
+ */
+
+ /**
+ * Converts from ARGB color to ABGR color if little endian or RGBA color if big endian.
+ */
+ public static int toVanillaColor(int color) {
+ if (color == -1) {
+ return -1;
+ }
+
+ if (BIG_ENDIAN) {
+ // ARGB to RGBA
+ return ((color & 0x00FFFFFF) << 8) | ((color & 0xFF000000) >>> 24);
+ } else {
+ // ARGB to ABGR
+ return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16);
+ }
+ }
+
+ /**
+ * Converts to ARGB color from ABGR color if little endian or RGBA color if big endian.
+ */
+ public static int fromVanillaColor(int color) {
+ if (color == -1) {
+ return -1;
+ }
+
+ if (BIG_ENDIAN) {
+ // RGBA to ARGB
+ return ((color & 0xFFFFFF00) >>> 8) | ((color & 0x000000FF) << 24);
+ } else {
+ // ABGR to ARGB
+ return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16);
+ }
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/GeometryHelper.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/GeometryHelper.java
new file mode 100644
index 000000000..fc68442cd
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/GeometryHelper.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.helper;
+
+import static net.minecraft.util.Mth.equal;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.core.Direction;
+import net.minecraft.core.Direction.Axis;
+import net.minecraft.core.Direction.AxisDirection;
+import org.joml.Vector3fc;
+
+/**
+ * Static routines of general utility for renderer implementations.
+ * Renderers are not required to use these helpers, but they were
+ * designed to be usable without the default renderer.
+ */
+public abstract class GeometryHelper {
+ private GeometryHelper() { }
+
+ /** set when a quad touches all four corners of a unit cube. */
+ public static final int CUBIC_FLAG = 1;
+
+ /** set when a quad is parallel to (but not necessarily on) a its light face. */
+ public static final int AXIS_ALIGNED_FLAG = CUBIC_FLAG << 1;
+
+ /** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */
+ public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1;
+
+ /** how many bits quad header encoding should reserve for encoding geometry flags. */
+ public static final int FLAG_BIT_COUNT = 3;
+
+ private static final float EPS_MIN = 0.0001f;
+ private static final float EPS_MAX = 1.0f - EPS_MIN;
+
+ /**
+ * Analyzes the quad and returns a value with some combination
+ * of {@link #AXIS_ALIGNED_FLAG}, {@link #LIGHT_FACE_FLAG} and {@link #CUBIC_FLAG}.
+ * Intended use is to optimize lighting when the geometry is regular.
+ * Expects convex quads with all points co-planar.
+ */
+ public static int computeShapeFlags(QuadView quad) {
+ Direction lightFace = quad.lightFace();
+ int bits = 0;
+
+ if (isQuadParallelToFace(lightFace, quad)) {
+ bits |= AXIS_ALIGNED_FLAG;
+
+ if (isParallelQuadOnFace(lightFace, quad)) {
+ bits |= LIGHT_FACE_FLAG;
+ }
+ }
+
+ if (isQuadCubic(lightFace, quad)) {
+ bits |= CUBIC_FLAG;
+ }
+
+ return bits;
+ }
+
+ /**
+ * Returns true if quad is parallel to the given face.
+ * Does not validate quad winding order.
+ * Expects convex quads with all points co-planar.
+ */
+ public static boolean isQuadParallelToFace(Direction face, QuadView quad) {
+ int i = face.getAxis().ordinal();
+ final float val = quad.posByIndex(0, i);
+ return equal(val, quad.posByIndex(1, i)) && equal(val, quad.posByIndex(2, i)) && equal(val, quad.posByIndex(3, i));
+ }
+
+ /**
+ * True if quad - already known to be parallel to a face - is actually coplanar with it.
+ * For compatibility with vanilla resource packs, also true if quad is outside the face.
+ *
+ * Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)}
+ * for that purpose. Expects convex quads with all points co-planar.
+ */
+ public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) {
+ final float x = quad.posByIndex(0, lightFace.getAxis().ordinal());
+ return lightFace.getAxisDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN;
+ }
+
+ /**
+ * Returns true if quad is truly a quad (not a triangle) and fills a full block cross-section.
+ * If known to be true, allows use of a simpler/faster AO lighting algorithm.
+ *
+ *
Does not check if quad is actually coplanar with the light face, nor does it check that all
+ * quad vertices are coplanar with each other.
+ *
+ *
Expects convex quads with all points co-planar.
+ */
+ public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
+ int a, b;
+
+ switch (lightFace) {
+ case EAST:
+ case WEST:
+ a = 1;
+ b = 2;
+ break;
+ case UP:
+ case DOWN:
+ a = 0;
+ b = 2;
+ break;
+ case SOUTH:
+ case NORTH:
+ a = 1;
+ b = 0;
+ break;
+ default:
+ // handle WTF case
+ return false;
+ }
+
+ return confirmSquareCorners(a, b, quad);
+ }
+
+ /**
+ * Used by {@link #isQuadCubic(Direction, QuadView)}.
+ * True if quad touches all four corners of unit square.
+ *
+ *
For compatibility with resource packs that contain models with quads exceeding
+ * block boundaries, considers corners outside the block to be at the corners.
+ */
+ private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) {
+ int flags = 0;
+
+ for (int i = 0; i < 4; i++) {
+ final float a = quad.posByIndex(i, aCoordinate);
+ final float b = quad.posByIndex(i, bCoordinate);
+
+ if (a <= EPS_MIN) {
+ if (b <= EPS_MIN) {
+ flags |= 1;
+ } else if (b >= EPS_MAX) {
+ flags |= 2;
+ } else {
+ return false;
+ }
+ } else if (a >= EPS_MAX) {
+ if (b <= EPS_MIN) {
+ flags |= 4;
+ } else if (b >= EPS_MAX) {
+ flags |= 8;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return flags == 15;
+ }
+
+ /**
+ * Identifies the face to which the quad is most closely aligned.
+ * This mimics the value that {@link BakedQuad#getDirection()} returns, and is
+ * used in the vanilla renderer for all diffuse lighting.
+ *
+ *
Derived from the quad face normal and expects convex quads with all points co-planar.
+ */
+ public static Direction lightFace(QuadView quad) {
+ final Vector3fc normal = quad.faceNormal();
+ switch (GeometryHelper.longestAxis(normal)) {
+ case X:
+ return normal.x() > 0 ? Direction.EAST : Direction.WEST;
+
+ case Y:
+ return normal.y() > 0 ? Direction.UP : Direction.DOWN;
+
+ case Z:
+ return normal.z() > 0 ? Direction.SOUTH : Direction.NORTH;
+
+ default:
+ // handle WTF case
+ return Direction.UP;
+ }
+ }
+
+ /**
+ * Simple 4-way compare, doesn't handle NaN values.
+ */
+ public static float min(float a, float b, float c, float d) {
+ final float x = a < b ? a : b;
+ final float y = c < d ? c : d;
+ return x < y ? x : y;
+ }
+
+ /**
+ * Simple 4-way compare, doesn't handle NaN values.
+ */
+ public static float max(float a, float b, float c, float d) {
+ final float x = a > b ? a : b;
+ final float y = c > d ? c : d;
+ return x > y ? x : y;
+ }
+
+ /**
+ * @see #longestAxis(float, float, float)
+ */
+ public static Axis longestAxis(Vector3fc vec) {
+ return longestAxis(vec.x(), vec.y(), vec.z());
+ }
+
+ /**
+ * Identifies the largest (max absolute magnitude) component (X, Y, Z) in the given vector.
+ */
+ public static Axis longestAxis(float normalX, float normalY, float normalZ) {
+ Axis result = Axis.Y;
+ float longest = Math.abs(normalY);
+ float a = Math.abs(normalX);
+
+ if (a > longest) {
+ result = Axis.X;
+ longest = a;
+ }
+
+ return Math.abs(normalZ) > longest
+ ? Axis.Z : result;
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/NormalHelper.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/NormalHelper.java
new file mode 100644
index 000000000..8b8ebb1b0
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/NormalHelper.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.helper;
+
+import net.vulkanmod.render.model.quad.ModelQuadView;
+import net.vulkanmod.render.vertex.format.I32_SNorm;
+import org.jetbrains.annotations.NotNull;
+import org.joml.Vector3f;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.minecraft.core.Direction;
+import net.minecraft.core.Vec3i;
+import net.minecraft.util.Mth;
+
+/**
+ * Static routines of general utility for renderer implementations.
+ * Renderers are not required to use these helpers, but they were
+ * designed to be usable without the default renderer.
+ */
+public abstract class NormalHelper {
+ private NormalHelper() { }
+
+ private static final float PACK = 127.0f;
+ private static final float UNPACK = 1.0f / PACK;
+
+ /**
+ * Stores a normal plus an extra value as a quartet of signed bytes.
+ * This is the same normal format that vanilla rendering expects.
+ * The extra value is for use by shaders.
+ */
+ public static int packNormal(float x, float y, float z, float w) {
+ x = Mth.clamp(x, -1, 1);
+ y = Mth.clamp(y, -1, 1);
+ z = Mth.clamp(z, -1, 1);
+ w = Mth.clamp(w, -1, 1);
+
+ return ((int) (x * PACK) & 0xFF) | (((int) (y * PACK) & 0xFF) << 8) | (((int) (z * PACK) & 0xFF) << 16) | (((int) (w * PACK) & 0xFF) << 24);
+ }
+
+ /**
+ * Version of {@link #packNormal(float, float, float, float)} that accepts a vector type.
+ */
+ public static int packNormal(Vector3f normal, float w) {
+ return packNormal(normal.x(), normal.y(), normal.z(), w);
+ }
+
+ /**
+ * Like {@link #packNormal(float, float, float, float)}, but without a {@code w} value.
+ */
+ public static int packNormal(float x, float y, float z) {
+ x = Mth.clamp(x, -1, 1);
+ y = Mth.clamp(y, -1, 1);
+ z = Mth.clamp(z, -1, 1);
+
+ return ((int) (x * PACK) & 0xFF) | (((int) (y * PACK) & 0xFF) << 8) | (((int) (z * PACK) & 0xFF) << 16);
+ }
+
+ /**
+ * Like {@link #packNormal(Vector3f, float)}, but without a {@code w} value.
+ */
+ public static int packNormal(Vector3f normal) {
+ return packNormal(normal.x(), normal.y(), normal.z());
+ }
+
+ public static float unpackNormalX(int packedNormal) {
+ return ((byte) (packedNormal & 0xFF)) * UNPACK;
+ }
+
+ public static float unpackNormalY(int packedNormal) {
+ return ((byte) ((packedNormal >>> 8) & 0xFF)) * UNPACK;
+ }
+
+ public static float unpackNormalZ(int packedNormal) {
+ return ((byte) ((packedNormal >>> 16) & 0xFF)) * UNPACK;
+ }
+
+ public static float unpackNormalW(int packedNormal) {
+ return ((byte) ((packedNormal >>> 24) & 0xFF)) * UNPACK;
+ }
+
+ public static void unpackNormal(int packedNormal, Vector3f target) {
+ target.set(unpackNormalX(packedNormal), unpackNormalY(packedNormal), unpackNormalZ(packedNormal));
+ }
+
+ /**
+ * Computes the face normal of the given quad and saves it in the provided non-null vector.
+ * If {@link QuadView#nominalFace()} is set will optimize by confirming quad is parallel to that
+ * face and, if so, use the standard normal for that face direction.
+ *
+ *
Will work with triangles also. Assumes counter-clockwise winding order, which is the norm.
+ * Expects convex quads with all points co-planar.
+ */
+ public static void computeFaceNormal(@NotNull Vector3f saveTo, QuadView q) {
+ final Direction nominalFace = q.nominalFace();
+
+ if (nominalFace != null && GeometryHelper.isQuadParallelToFace(nominalFace, q)) {
+ Vec3i vec = nominalFace.getUnitVec3i();
+ saveTo.set(vec.getX(), vec.getY(), vec.getZ());
+ return;
+ }
+
+ final float x0 = q.x(0);
+ final float y0 = q.y(0);
+ final float z0 = q.z(0);
+ final float x1 = q.x(1);
+ final float y1 = q.y(1);
+ final float z1 = q.z(1);
+ final float x2 = q.x(2);
+ final float y2 = q.y(2);
+ final float z2 = q.z(2);
+ final float x3 = q.x(3);
+ final float y3 = q.y(3);
+ final float z3 = q.z(3);
+
+ final float dx0 = x2 - x0;
+ final float dy0 = y2 - y0;
+ final float dz0 = z2 - z0;
+ final float dx1 = x3 - x1;
+ final float dy1 = y3 - y1;
+ final float dz1 = z3 - z1;
+
+ float normX = dy0 * dz1 - dz0 * dy1;
+ float normY = dz0 * dx1 - dx0 * dz1;
+ float normZ = dx0 * dy1 - dy0 * dx1;
+
+ float l = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ);
+
+ if (l != 0) {
+ normX /= l;
+ normY /= l;
+ normZ /= l;
+ }
+
+ saveTo.set(normX, normY, normZ);
+ }
+
+ public static int computePackedNormal(ModelQuadView q) {
+ final float x0 = q.getX(0);
+ final float y0 = q.getY(0);
+ final float z0 = q.getZ(0);
+ final float x1 = q.getX(1);
+ final float y1 = q.getY(1);
+ final float z1 = q.getZ(1);
+ final float x2 = q.getX(2);
+ final float y2 = q.getY(2);
+ final float z2 = q.getZ(2);
+ final float x3 = q.getX(3);
+ final float y3 = q.getY(3);
+ final float z3 = q.getZ(3);
+
+ final float dx0 = x2 - x0;
+ final float dy0 = y2 - y0;
+ final float dz0 = z2 - z0;
+ final float dx1 = x3 - x1;
+ final float dy1 = y3 - y1;
+ final float dz1 = z3 - z1;
+
+ float normX = dy0 * dz1 - dz0 * dy1;
+ float normY = dz0 * dx1 - dx0 * dz1;
+ float normZ = dx0 * dy1 - dy0 * dx1;
+
+ float l = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ);
+
+ if (l != 0) {
+ normX /= l;
+ normY /= l;
+ normZ /= l;
+ }
+
+ return I32_SNorm.packNormal(normX, normY, normZ);
+ }
+
+ public static int packedNormalFromDirection(Direction direction) {
+ Vec3i normal = direction.getUnitVec3i();
+
+ return I32_SNorm.packNormal(normal.getX(), normal.getY(), normal.getZ());
+ }
+
+ public static void unpackNormalTo(int packedNormal, Vector3f normal) {
+ normal.set(I32_SNorm.unpackX(packedNormal), I32_SNorm.unpackY(packedNormal), I32_SNorm.unpackZ(packedNormal));
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/TextureHelper.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/TextureHelper.java
new file mode 100644
index 000000000..bc622cd92
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/helper/TextureHelper.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.helper;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.core.Direction;
+
+/**
+ * Handles most texture-baking use cases for model loaders and model libraries
+ * via {@link #bakeSprite(MutableQuadView, TextureAtlasSprite, int)}. Also used by the API
+ * itself to implement automatic block-breaking models for enhanced models.
+ */
+public class TextureHelper {
+ private TextureHelper() { }
+
+ private static final float NORMALIZER = 1f / 16f;
+
+ /**
+ * Bakes textures in the provided vertex data, handling UV locking,
+ * rotation, interpolation, etc. Textures must not be already baked.
+ */
+ public static void bakeSprite(MutableQuadView quad, TextureAtlasSprite sprite, int bakeFlags) {
+ if (quad.nominalFace() != null && (MutableQuadView.BAKE_LOCK_UV & bakeFlags) != 0) {
+ // Assigns normalized UV coordinates based on vertex positions
+ applyModifier(quad, UVLOCKERS[quad.nominalFace().get3DDataValue()]);
+ } else if ((MutableQuadView.BAKE_NORMALIZED & bakeFlags) == 0) { // flag is NOT set, UVs are assumed to not be normalized yet as is the default, normalize through dividing by 16
+ // Scales from 0-16 to 0-1
+ applyModifier(quad, (q, i) -> q.uv(i, q.u(i) * NORMALIZER, q.v(i) * NORMALIZER));
+ }
+
+ final int rotation = bakeFlags & 3;
+
+ if (rotation != 0) {
+ // Rotates texture around the center of sprite.
+ // Assumes normalized coordinates.
+ applyModifier(quad, ROTATIONS[rotation]);
+ }
+
+ if ((MutableQuadView.BAKE_FLIP_U & bakeFlags) != 0) {
+ // Inverts U coordinates. Assumes normalized (0-1) values.
+ applyModifier(quad, (q, i) -> q.uv(i, 1 - q.u(i), q.v(i)));
+ }
+
+ if ((MutableQuadView.BAKE_FLIP_V & bakeFlags) != 0) {
+ // Inverts V coordinates. Assumes normalized (0-1) values.
+ applyModifier(quad, (q, i) -> q.uv(i, q.u(i), 1 - q.v(i)));
+ }
+
+ interpolate(quad, sprite);
+ }
+
+ /**
+ * Faster than sprite method. Sprite computes span and normalizes inputs each call,
+ * so we'd have to denormalize before we called, only to have the sprite renormalize immediately.
+ */
+ private static void interpolate(MutableQuadView q, TextureAtlasSprite sprite) {
+ final float uMin = sprite.getU0();
+ final float uSpan = sprite.getU1() - uMin;
+ final float vMin = sprite.getV0();
+ final float vSpan = sprite.getV1() - vMin;
+
+ for (int i = 0; i < 4; i++) {
+ q.uv(i, uMin + q.u(i) * uSpan, vMin + q.v(i) * vSpan);
+ }
+ }
+
+ @FunctionalInterface
+ private interface VertexModifier {
+ void apply(MutableQuadView quad, int vertexIndex);
+ }
+
+ private static void applyModifier(MutableQuadView quad, VertexModifier modifier) {
+ for (int i = 0; i < 4; i++) {
+ modifier.apply(quad, i);
+ }
+ }
+
+ private static final VertexModifier[] ROTATIONS = new VertexModifier[] {
+ null,
+ (q, i) -> q.uv(i, q.v(i), 1 - q.u(i)), //90
+ (q, i) -> q.uv(i, 1 - q.u(i), 1 - q.v(i)), //180
+ (q, i) -> q.uv(i, 1 - q.v(i), q.u(i)) // 270
+ };
+
+ private static final VertexModifier[] UVLOCKERS = new VertexModifier[6];
+
+ static {
+ UVLOCKERS[Direction.EAST.get3DDataValue()] = (q, i) -> q.uv(i, 1 - q.z(i), 1 - q.y(i));
+ UVLOCKERS[Direction.WEST.get3DDataValue()] = (q, i) -> q.uv(i, q.z(i), 1 - q.y(i));
+ UVLOCKERS[Direction.NORTH.get3DDataValue()] = (q, i) -> q.uv(i, 1 - q.x(i), 1 - q.y(i));
+ UVLOCKERS[Direction.SOUTH.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), 1 - q.y(i));
+ UVLOCKERS[Direction.DOWN.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), 1 - q.z(i));
+ UVLOCKERS[Direction.UP.get3DDataValue()] = (q, i) -> q.uv(i, q.x(i), q.z(i));
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/EncodingFormat.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/EncodingFormat.java
new file mode 100644
index 000000000..a0de50146
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/EncodingFormat.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.mesh;
+
+import com.google.common.base.Preconditions;
+import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import com.mojang.blaze3d.vertex.VertexFormat;
+import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import org.apache.commons.lang3.ArrayUtils;
+import org.jetbrains.annotations.Nullable;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
+import net.vulkanmod.render.chunk.build.frapi.helper.GeometryHelper;
+import net.minecraft.core.Direction;
+import net.minecraft.util.Mth;
+
+/**
+ * Holds all the array offsets and bit-wise encoders/decoders for
+ * packing/unpacking quad data in an array of integers.
+ * All of this is implementation-specific - that's why it isn't a "helper" class.
+ */
+public final class EncodingFormat {
+ private EncodingFormat() { }
+
+ static final int HEADER_BITS = 0;
+ static final int HEADER_FACE_NORMAL = 1;
+ static final int HEADER_TINT_INDEX = 2;
+ static final int HEADER_TAG = 3;
+ public static final int HEADER_STRIDE = 4;
+
+ static final int VERTEX_X;
+ static final int VERTEX_Y;
+ static final int VERTEX_Z;
+ static final int VERTEX_COLOR;
+ static final int VERTEX_U;
+ static final int VERTEX_V;
+ static final int VERTEX_LIGHTMAP;
+ static final int VERTEX_NORMAL;
+ public static final int VERTEX_STRIDE;
+
+ public static final int QUAD_STRIDE;
+ public static final int QUAD_STRIDE_BYTES;
+ public static final int TOTAL_STRIDE;
+
+ static {
+ final VertexFormat format = DefaultVertexFormat.BLOCK;
+ VERTEX_X = HEADER_STRIDE + 0;
+ VERTEX_Y = HEADER_STRIDE + 1;
+ VERTEX_Z = HEADER_STRIDE + 2;
+ VERTEX_COLOR = HEADER_STRIDE + 3;
+ VERTEX_U = HEADER_STRIDE + 4;
+ VERTEX_V = VERTEX_U + 1;
+ VERTEX_LIGHTMAP = HEADER_STRIDE + 6;
+ VERTEX_NORMAL = HEADER_STRIDE + 7;
+ VERTEX_STRIDE = format.getVertexSize() / 4;
+ QUAD_STRIDE = VERTEX_STRIDE * 4;
+ QUAD_STRIDE_BYTES = QUAD_STRIDE * 4;
+ TOTAL_STRIDE = HEADER_STRIDE + QUAD_STRIDE;
+
+ Preconditions.checkState(VERTEX_STRIDE == QuadView.VANILLA_VERTEX_STRIDE, "Indigo vertex stride (%s) mismatched with rendering API (%s)", VERTEX_STRIDE, QuadView.VANILLA_VERTEX_STRIDE);
+ Preconditions.checkState(QUAD_STRIDE == QuadView.VANILLA_QUAD_STRIDE, "Indigo quad stride (%s) mismatched with rendering API (%s)", QUAD_STRIDE, QuadView.VANILLA_QUAD_STRIDE);
+ }
+
+ private static final int DIRECTION_COUNT = Direction.values().length;
+ private static final int NULLABLE_DIRECTION_COUNT = DIRECTION_COUNT + 1;
+
+ private static final @Nullable ChunkSectionLayer[] NULLABLE_BLOCK_RENDER_LAYERS = ArrayUtils.add(ChunkSectionLayer.values(), null);
+ private static final int NULLABLE_BLOCK_RENDER_LAYER_COUNT = NULLABLE_BLOCK_RENDER_LAYERS.length;
+ private static final TriState[] TRI_STATES = TriState.values();
+ private static final int TRI_STATE_COUNT = TRI_STATES.length;
+ private static final @Nullable ItemStackRenderState.FoilType[] NULLABLE_GLINTS = ArrayUtils.add(ItemStackRenderState.FoilType.values(), null);
+ private static final int NULLABLE_GLINT_COUNT = NULLABLE_GLINTS.length;
+ private static final ShadeMode[] SHADE_MODES = ShadeMode.values();
+ private static final int SHADE_MODE_COUNT = SHADE_MODES.length;
+
+ private static final int NULL_RENDER_LAYER_INDEX = NULLABLE_BLOCK_RENDER_LAYER_COUNT - 1;
+ private static final int NULL_GLINT_INDEX = NULLABLE_GLINT_COUNT - 1;
+
+ private static final int CULL_BIT_LENGTH = Mth.ceillog2(NULLABLE_DIRECTION_COUNT);
+ private static final int LIGHT_BIT_LENGTH = Mth.ceillog2(DIRECTION_COUNT);
+ private static final int NORMALS_BIT_LENGTH = 4;
+ private static final int GEOMETRY_BIT_LENGTH = GeometryHelper.FLAG_BIT_COUNT;
+ private static final int RENDER_LAYER_BIT_LENGTH = Mth.ceillog2(NULLABLE_BLOCK_RENDER_LAYER_COUNT);
+ private static final int EMISSIVE_BIT_LENGTH = 1;
+ private static final int DIFFUSE_BIT_LENGTH = 1;
+ private static final int AO_BIT_LENGTH = Mth.ceillog2(TRI_STATE_COUNT);
+ private static final int GLINT_BIT_LENGTH = Mth.ceillog2(NULLABLE_GLINT_COUNT);
+ private static final int SHADE_MODE_BIT_LENGTH = Mth.ceillog2(SHADE_MODE_COUNT);
+
+ private static final int CULL_BIT_OFFSET = 0;
+ private static final int LIGHT_BIT_OFFSET = CULL_BIT_OFFSET + CULL_BIT_LENGTH;
+ private static final int NORMALS_BIT_OFFSET = LIGHT_BIT_OFFSET + LIGHT_BIT_LENGTH;
+ private static final int GEOMETRY_BIT_OFFSET = NORMALS_BIT_OFFSET + NORMALS_BIT_LENGTH;
+ private static final int RENDER_LAYER_BIT_OFFSET = GEOMETRY_BIT_OFFSET + GEOMETRY_BIT_LENGTH;
+ private static final int EMISSIVE_BIT_OFFSET = RENDER_LAYER_BIT_OFFSET + RENDER_LAYER_BIT_LENGTH;
+ private static final int DIFFUSE_BIT_OFFSET = EMISSIVE_BIT_OFFSET + EMISSIVE_BIT_LENGTH;
+ private static final int AO_BIT_OFFSET = DIFFUSE_BIT_OFFSET + DIFFUSE_BIT_LENGTH;
+ private static final int GLINT_BIT_OFFSET = AO_BIT_OFFSET + AO_BIT_LENGTH;
+ private static final int SHADE_MODE_BIT_OFFSET = GLINT_BIT_OFFSET + GLINT_BIT_LENGTH;
+ private static final int TOTAL_BIT_LENGTH = SHADE_MODE_BIT_OFFSET + SHADE_MODE_BIT_LENGTH;
+
+ private static final int CULL_MASK = bitMask(CULL_BIT_LENGTH, CULL_BIT_OFFSET);
+ private static final int LIGHT_MASK = bitMask(LIGHT_BIT_LENGTH, LIGHT_BIT_OFFSET);
+ private static final int NORMALS_MASK = bitMask(NORMALS_BIT_LENGTH, NORMALS_BIT_OFFSET);
+ private static final int GEOMETRY_MASK = bitMask(GEOMETRY_BIT_LENGTH, GEOMETRY_BIT_OFFSET);
+ private static final int RENDER_LAYER_MASK = bitMask(RENDER_LAYER_BIT_LENGTH, RENDER_LAYER_BIT_OFFSET);
+ private static final int EMISSIVE_MASK = bitMask(EMISSIVE_BIT_LENGTH, EMISSIVE_BIT_OFFSET);
+ private static final int DIFFUSE_MASK = bitMask(DIFFUSE_BIT_LENGTH, DIFFUSE_BIT_OFFSET);
+ private static final int AO_MASK = bitMask(AO_BIT_LENGTH, AO_BIT_OFFSET);
+ private static final int GLINT_MASK = bitMask(GLINT_BIT_LENGTH, GLINT_BIT_OFFSET);
+ private static final int SHADE_MODE_MASK = bitMask(SHADE_MODE_BIT_LENGTH, SHADE_MODE_BIT_OFFSET);
+
+ static {
+ Preconditions.checkArgument(TOTAL_BIT_LENGTH <= 32, "Indigo header encoding bit count (%s) exceeds integer bit length)", TOTAL_STRIDE);
+ }
+
+ private static int bitMask(int bitLength, int bitOffset) {
+ return ((1 << bitLength) - 1) << bitOffset;
+ }
+
+ @Nullable
+ static Direction cullFace(int bits) {
+ return ModelHelper.faceFromIndex((bits & CULL_MASK) >>> CULL_BIT_OFFSET);
+ }
+
+ static int cullFace(int bits, @Nullable Direction face) {
+ return (bits & ~CULL_MASK) | (ModelHelper.toFaceIndex(face) << CULL_BIT_OFFSET);
+ }
+
+ static Direction lightFace(int bits) {
+ return ModelHelper.faceFromIndex((bits & LIGHT_MASK) >>> LIGHT_BIT_OFFSET);
+ }
+
+ static int lightFace(int bits, Direction face) {
+ return (bits & ~LIGHT_MASK) | (ModelHelper.toFaceIndex(face) << LIGHT_BIT_OFFSET);
+ }
+
+ /** indicate if vertex normal has been set - bits correspond to vertex ordinals. */
+ static int normalFlags(int bits) {
+ return (bits & NORMALS_MASK) >>> NORMALS_BIT_OFFSET;
+ }
+
+ static int normalFlags(int bits, int normalFlags) {
+ return (bits & ~NORMALS_MASK) | ((normalFlags << NORMALS_BIT_OFFSET) & NORMALS_MASK);
+ }
+
+ static int geometryFlags(int bits) {
+ return (bits & GEOMETRY_MASK) >>> GEOMETRY_BIT_OFFSET;
+ }
+
+ static int geometryFlags(int bits, int geometryFlags) {
+ return (bits & ~GEOMETRY_MASK) | ((geometryFlags << GEOMETRY_BIT_OFFSET) & GEOMETRY_MASK);
+ }
+
+ @Nullable
+ static ChunkSectionLayer renderLayer(int bits) {
+ return NULLABLE_BLOCK_RENDER_LAYERS[(bits & RENDER_LAYER_MASK) >>> RENDER_LAYER_BIT_OFFSET];
+ }
+
+ static int renderLayer(int bits, @Nullable ChunkSectionLayer renderLayer) {
+ int index = renderLayer == null ? NULL_RENDER_LAYER_INDEX : renderLayer.ordinal();
+ return (bits & ~RENDER_LAYER_MASK) | (index << RENDER_LAYER_BIT_OFFSET);
+ }
+
+ static boolean emissive(int bits) {
+ return (bits & EMISSIVE_MASK) != 0;
+ }
+
+ static int emissive(int bits, boolean emissive) {
+ return emissive ? (bits | EMISSIVE_MASK) : (bits & ~EMISSIVE_MASK);
+ }
+
+ static boolean diffuseShade(int bits) {
+ return (bits & DIFFUSE_MASK) != 0;
+ }
+
+ static int diffuseShade(int bits, boolean shade) {
+ return shade ? (bits | DIFFUSE_MASK) : (bits & ~DIFFUSE_MASK);
+ }
+
+ static TriState ambientOcclusion(int bits) {
+ return TRI_STATES[(bits & AO_MASK) >>> AO_BIT_OFFSET];
+ }
+
+ static int ambientOcclusion(int bits, TriState ao) {
+ return (bits & ~AO_MASK) | (ao.ordinal() << AO_BIT_OFFSET);
+ }
+
+ @Nullable
+ static ItemStackRenderState.FoilType glint(int bits) {
+ return NULLABLE_GLINTS[(bits & GLINT_MASK) >>> GLINT_BIT_OFFSET];
+ }
+
+ static int glint(int bits, @Nullable ItemStackRenderState.FoilType glint) {
+ int index = glint == null ? NULL_GLINT_INDEX : glint.ordinal();
+ return (bits & ~GLINT_MASK) | (index << GLINT_BIT_OFFSET);
+ }
+
+ static ShadeMode shadeMode(int bits) {
+ return SHADE_MODES[(bits & SHADE_MODE_MASK) >>> SHADE_MODE_BIT_OFFSET];
+ }
+
+ static int shadeMode(int bits, ShadeMode mode) {
+ return (bits & ~SHADE_MODE_MASK) | (mode.ordinal() << SHADE_MODE_BIT_OFFSET);
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MeshImpl.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MeshImpl.java
new file mode 100644
index 000000000..834f867b8
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MeshImpl.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.mesh;
+
+import java.util.function.Consumer;
+
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import org.jetbrains.annotations.Range;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+
+public class MeshImpl implements Mesh {
+ /** Used to satisfy external calls to {@link #forEach(Consumer)}. */
+ private static final ThreadLocal> CURSOR_POOLS = ThreadLocal.withInitial(ObjectArrayList::new);
+
+ int[] data;
+ int limit;
+
+ MeshImpl(int[] data) {
+ this.data = data;
+ limit = data.length;
+ }
+
+ MeshImpl() {
+ }
+
+ @Override
+ @Range(from = 0, to = Integer.MAX_VALUE)
+ public int size() {
+ return limit / EncodingFormat.TOTAL_STRIDE;
+ }
+
+ @Override
+ public void forEach(Consumer super QuadView> action) {
+ ObjectArrayList pool = CURSOR_POOLS.get();
+ QuadViewImpl cursor;
+
+ if (pool.isEmpty()) {
+ cursor = new QuadViewImpl();
+ } else {
+ cursor = pool.pop();
+ }
+
+ forEach(action, cursor);
+
+ pool.push(cursor);
+ }
+
+ /**
+ * The renderer can call this with its own cursor to avoid the performance hit of a
+ * thread-local lookup or to use a mutable cursor.
+ */
+ void forEach(Consumer super C> action, C cursor) {
+ final int limit = this.limit;
+ int index = 0;
+ cursor.data = data;
+
+ while (index < limit) {
+ cursor.baseIndex = index;
+ cursor.load();
+ action.accept(cursor);
+ index += EncodingFormat.TOTAL_STRIDE;
+ }
+
+ cursor.data = null;
+ }
+
+ // TODO: This could be optimized by checking if the emitter is that of a MutableMeshImpl and if
+ // it has no transforms, in which case the entire data array can be copied in bulk.
+ @Override
+ public void outputTo(QuadEmitter emitter) {
+ MutableQuadViewImpl e = (MutableQuadViewImpl) emitter;
+ final int[] data = this.data;
+ final int limit = this.limit;
+ int index = 0;
+
+ while (index < limit) {
+ System.arraycopy(data, index, e.data, e.baseIndex, EncodingFormat.TOTAL_STRIDE);
+ e.load();
+ e.transformAndEmit();
+ index += EncodingFormat.TOTAL_STRIDE;
+ }
+
+ e.clear();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableMeshImpl.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableMeshImpl.java
new file mode 100644
index 000000000..79ab94df6
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableMeshImpl.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.mesh;
+
+import java.util.function.Consumer;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableMesh;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+
+/**
+ * Our implementation of {@link MutableMesh}, mainly used for optimized mesh creation.
+ * Not much to it - mainly it just needs to grow the int[] array as quads are appended
+ * and maintain/provide a properly-configured {@link MutableQuadView} instance.
+ * All the encoding and other work is handled in the quad base classes.
+ * The one interesting bit is in {@link #emitter}.
+ */
+public class MutableMeshImpl extends MeshImpl implements MutableMesh {
+ private final MutableQuadViewImpl emitter = new MutableQuadViewImpl() {
+ @Override
+ protected void emitDirectly() {
+ // Necessary because the validity of geometry is not encoded; reading mesh data always
+ // uses QuadViewImpl#load(), which assumes valid geometry. Built immutable meshes
+ // should also have valid geometry for better performance.
+ computeGeometry();
+ limit += EncodingFormat.TOTAL_STRIDE;
+ ensureCapacity(EncodingFormat.TOTAL_STRIDE);
+ baseIndex = limit;
+ }
+ };
+
+ public MutableMeshImpl() {
+ data = new int[8 * EncodingFormat.TOTAL_STRIDE];
+ limit = 0;
+
+ ensureCapacity(EncodingFormat.TOTAL_STRIDE);
+ emitter.data = data;
+ emitter.baseIndex = limit;
+ emitter.clear();
+ }
+
+ private void ensureCapacity(int stride) {
+ if (stride > data.length - limit) {
+ final int[] bigger = new int[data.length * 2];
+ System.arraycopy(data, 0, bigger, 0, limit);
+ data = bigger;
+ emitter.data = data;
+ }
+ }
+
+ @Override
+ public QuadEmitter emitter() {
+ emitter.clear();
+ return emitter;
+ }
+
+ @Override
+ public void forEachMutable(Consumer super MutableQuadView> action) {
+ // emitDirectly will not be called by forEach, so just reuse the main emitter.
+ forEach(action, emitter);
+ emitter.data = data;
+ emitter.baseIndex = limit;
+ }
+
+ @Override
+ public Mesh immutableCopy() {
+ final int[] packed = new int[limit];
+ System.arraycopy(data, 0, packed, 0, limit);
+ return new MeshImpl(packed);
+ }
+
+ @Override
+ public void clear() {
+ limit = 0;
+ emitter.baseIndex = limit;
+ emitter.clear();
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableQuadViewImpl.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableQuadViewImpl.java
new file mode 100644
index 000000000..215db6398
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/MutableQuadViewImpl.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.mesh;
+
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.vulkanmod.render.model.quad.ModelQuadView;
+import org.jetbrains.annotations.Nullable;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadTransform;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
+import net.vulkanmod.render.chunk.build.frapi.helper.NormalHelper;
+import net.vulkanmod.render.chunk.build.frapi.helper.TextureHelper;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.core.Direction;
+
+import java.util.Objects;
+
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.*;
+
+/**
+ * Almost-concrete implementation of a mutable quad. The only missing part is {@link #emitDirectly()},
+ * because that depends on where/how it is used. (Mesh encoding vs. render-time transformation).
+ *
+ * In many cases an instance of this class is used as an "editor quad". The editor quad's
+ * {@link #emitDirectly()} method calls some other internal method that transforms the quad
+ * data and then buffers it. Transformations should be the same as they would be in a vanilla
+ * render - the editor is serving mainly as a way to access vertex data without magical
+ * numbers. It also allows for a consistent interface for those transformations.
+ */
+public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEmitter {
+ private static final QuadTransform NO_TRANSFORM = q -> true;
+
+ private static final int[] DEFAULT_QUAD_DATA = new int[EncodingFormat.TOTAL_STRIDE];
+
+ static {
+ MutableQuadViewImpl quad = new MutableQuadViewImpl() {
+ @Override
+ protected void emitDirectly() {
+ // This quad won't be emitted. It's only used to configure the default quad data.
+ }
+ };
+
+ // Start with all zeroes
+ quad.data = DEFAULT_QUAD_DATA;
+ // Apply non-zero defaults
+ quad.color(-1, -1, -1, -1);
+ quad.cullFace(null);
+ quad.renderLayer(null);
+ quad.diffuseShade(true);
+ quad.ambientOcclusion(TriState.DEFAULT);
+ quad.glint(null);
+ quad.tintIndex(-1);
+ quad.tintIndex(-1);
+ }
+
+ protected boolean hasTransform = false;
+ private QuadTransform activeTransform = NO_TRANSFORM;
+ private final ObjectArrayList transformStack = new ObjectArrayList<>();
+ private final QuadTransform stackTransform = q -> {
+ int i = transformStack.size() - 1;
+
+ while (i >= 0) {
+ if (!transformStack.get(i--).transform(q)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ public final void clear() {
+ System.arraycopy(DEFAULT_QUAD_DATA, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
+ isGeometryInvalid = true;
+ nominalFace = null;
+ }
+
+ @Override
+ public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) {
+ final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X;
+ data[index] = Float.floatToRawIntBits(x);
+ data[index + 1] = Float.floatToRawIntBits(y);
+ data[index + 2] = Float.floatToRawIntBits(z);
+ isGeometryInvalid = true;
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl color(int vertexIndex, int color) {
+ data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR] = color;
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl uv(int vertexIndex, float u, float v) {
+ final int i = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U;
+ data[i] = Float.floatToRawIntBits(u);
+ data[i + 1] = Float.floatToRawIntBits(v);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl spriteBake(TextureAtlasSprite sprite, int bakeFlags) {
+ TextureHelper.bakeSprite(this, sprite, bakeFlags);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) {
+ data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP] = lightmap;
+ return this;
+ }
+
+ protected void normalFlags(int flags) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS], flags);
+ }
+
+ @Override
+ public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
+ normalFlags(normalFlags() | (1 << vertexIndex));
+ data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormalHelper.packNormal(x, y, z);
+ return this;
+ }
+
+ /**
+ * Internal helper method. Copies face normals to vertex normals lacking one.
+ */
+ public final void populateMissingNormals() {
+ final int normalFlags = this.normalFlags();
+
+ if (normalFlags == 0b1111) return;
+
+ final int packedFaceNormal = packedFaceNormal();
+
+ for (int v = 0; v < 4; v++) {
+ if ((normalFlags & (1 << v)) == 0) {
+ data[baseIndex + v * VERTEX_STRIDE + VERTEX_NORMAL] = packedFaceNormal;
+ }
+ }
+
+ normalFlags(0b1111);
+ }
+
+ @Override
+ public final MutableQuadViewImpl nominalFace(@Nullable Direction face) {
+ nominalFace = face;
+ return this;
+ }
+
+ @Override
+ public final MutableQuadViewImpl cullFace(@Nullable Direction face) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(data[baseIndex + HEADER_BITS], face);
+ nominalFace(face);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl renderLayer(@Nullable ChunkSectionLayer renderLayer) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.renderLayer(data[baseIndex + HEADER_BITS], renderLayer);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl emissive(boolean emissive) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.emissive(data[baseIndex + HEADER_BITS], emissive);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl diffuseShade(boolean shade) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.diffuseShade(data[baseIndex + HEADER_BITS], shade);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl ambientOcclusion(TriState ao) {
+ Objects.requireNonNull(ao, "ambient occlusion TriState may not be null");
+ data[baseIndex + HEADER_BITS] = EncodingFormat.ambientOcclusion(data[baseIndex + HEADER_BITS], ao);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl glint(@Nullable ItemStackRenderState.FoilType glint) {
+ data[baseIndex + HEADER_BITS] = EncodingFormat.glint(data[baseIndex + HEADER_BITS], glint);
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl shadeMode(ShadeMode mode) {
+ Objects.requireNonNull(mode, "ShadeMode may not be null");
+ data[baseIndex + HEADER_BITS] = EncodingFormat.shadeMode(data[baseIndex + HEADER_BITS], mode);
+ return this;
+ }
+
+ @Override
+ public final MutableQuadViewImpl tintIndex(int tintIndex) {
+ data[baseIndex + HEADER_TINT_INDEX] = tintIndex;
+ return this;
+ }
+
+ @Override
+ public final MutableQuadViewImpl tag(int tag) {
+ data[baseIndex + HEADER_TAG] = tag;
+ return this;
+ }
+
+ @Override
+ public MutableQuadViewImpl copyFrom(QuadView quad) {
+ final QuadViewImpl q = (QuadViewImpl) quad;
+ System.arraycopy(q.data, q.baseIndex, data, baseIndex, EncodingFormat.TOTAL_STRIDE);
+ nominalFace = q.nominalFace;
+ isGeometryInvalid = q.isGeometryInvalid;
+
+ if (!isGeometryInvalid) {
+ faceNormal.set(q.faceNormal);
+ }
+
+ return this;
+ }
+
+ @Override
+ public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex) {
+ System.arraycopy(quadData, startIndex, data, baseIndex + HEADER_STRIDE, VANILLA_QUAD_STRIDE);
+ isGeometryInvalid = true;
+
+ int normalFlags = 0;
+ int colorIndex = baseIndex + VERTEX_COLOR;
+ int normalIndex = baseIndex + VERTEX_NORMAL;
+
+ for (int i = 0; i < 4; i++) {
+ data[colorIndex] = ColorHelper.fromVanillaColor(data[colorIndex]);
+
+ // Set normal flag if normal is not zero, ignoring W component
+ if ((data[normalIndex] & 0xFFFFFF) != 0) {
+ normalFlags |= 1 << i;
+ }
+
+ colorIndex += VERTEX_STRIDE;
+ normalIndex += VERTEX_STRIDE;
+ }
+
+ normalFlags(normalFlags);
+ return this;
+ }
+
+ @Override
+ public final MutableQuadViewImpl fromBakedQuad(BakedQuad quad) {
+ fromVanilla(quad.vertices(), 0);
+// data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace);
+ nominalFace(quad.direction());
+ diffuseShade(quad.shade());
+ tintIndex(quad.tintIndex());
+
+// tag(0);
+
+ // Copy data from BakedQuad instead of calculating properties
+ ModelQuadView quadView = (ModelQuadView) (Object) quad;
+ int normal = quadView.getNormal();
+ data[baseIndex + HEADER_FACE_NORMAL] = normal;
+ NormalHelper.unpackNormalTo(normal, faceNormal);
+
+ Direction lightFace = quadView.lightFace();
+ data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], lightFace);
+ data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], quadView.getFlags());
+
+ this.facing = quadView.getQuadFacing();
+ this.isGeometryInvalid = false;
+
+ int lightEmission = quad.lightEmission();
+
+ if (lightEmission > 0) {
+ for (int i = 0; i < 4; i++) {
+ lightmap(i, LightTexture.lightCoordsWithEmission(lightmap(i), lightEmission));
+ }
+ }
+
+ return this;
+ }
+
+ @Override
+ public void pushTransform(QuadTransform transform) {
+ if (transform == null) {
+ throw new NullPointerException("QuadTransform cannot be null!");
+ }
+
+ transformStack.push(transform);
+ hasTransform = true;
+
+ if (transformStack.size() == 1) {
+ activeTransform = transform;
+ } else if (transformStack.size() == 2) {
+ activeTransform = stackTransform;
+ }
+ }
+
+ @Override
+ public void popTransform() {
+ transformStack.pop();
+
+ if (transformStack.size() == 0) {
+ activeTransform = NO_TRANSFORM;
+ hasTransform = false;
+ } else if (transformStack.size() == 1) {
+ activeTransform = transformStack.get(0);
+ }
+ }
+
+ /**
+ * Emit the quad without applying transforms and without clearing the underlying data.
+ * Geometry is not guaranteed to be valid when called, but can be computed by calling {@link #computeGeometry()}.
+ */
+ protected abstract void emitDirectly();
+
+ /**
+ * Apply transforms and then if transforms return true, emit the quad without clearing the underlying data.
+ */
+ public final void transformAndEmit() {
+ if (activeTransform.transform(this)) {
+ emitDirectly();
+ }
+ }
+
+ @Override
+ public final MutableQuadViewImpl emit() {
+ transformAndEmit();
+ clear();
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/QuadViewImpl.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/QuadViewImpl.java
new file mode 100644
index 000000000..723e80792
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/mesh/QuadViewImpl.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.mesh;
+
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.HEADER_BITS;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.HEADER_TINT_INDEX;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.HEADER_FACE_NORMAL;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.HEADER_STRIDE;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.HEADER_TAG;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.QUAD_STRIDE;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_COLOR;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_LIGHTMAP;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_NORMAL;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_STRIDE;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_U;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_V;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_X;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_Y;
+import static net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat.VERTEX_Z;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
+import net.vulkanmod.render.model.quad.ModelQuadFlags;
+import net.vulkanmod.render.model.quad.ModelQuadView;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Vector2f;
+import org.joml.Vector3f;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
+import net.vulkanmod.render.chunk.build.frapi.helper.GeometryHelper;
+import net.vulkanmod.render.chunk.build.frapi.helper.NormalHelper;
+import net.minecraft.core.Direction;
+
+/**
+ * Base class for all quads / quad makers. Handles the ugly bits
+ * of maintaining and encoding the quad state.
+ */
+public class QuadViewImpl implements QuadView, ModelQuadView {
+ @Nullable
+ protected Direction nominalFace;
+ /** True when face normal, light face, or geometry flags may not match geometry. */
+ protected boolean isGeometryInvalid = true;
+ protected final Vector3f faceNormal = new Vector3f();
+
+ /** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */
+ protected int[] data;
+
+ /** Beginning of the quad. Also the header index. */
+ protected int baseIndex = 0;
+
+ protected QuadFacing facing;
+
+ /**
+ * Decodes necessary state from the backing data array.
+ * The encoded data must contain valid computed geometry.
+ */
+ public void load() {
+ isGeometryInvalid = false;
+ nominalFace = lightFace();
+ NormalHelper.unpackNormal(packedFaceNormal(), faceNormal);
+ facing = QuadFacing.fromNormal(faceNormal);
+ }
+
+ protected void computeGeometry() {
+ if (isGeometryInvalid) {
+ isGeometryInvalid = false;
+
+ NormalHelper.computeFaceNormal(faceNormal, this);
+ data[baseIndex + HEADER_FACE_NORMAL] = NormalHelper.packNormal(faceNormal);
+
+ // depends on face normal
+ Direction lightFace = GeometryHelper.lightFace(this);
+ data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], lightFace);
+
+ // depends on light face
+ data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], ModelQuadFlags.getQuadFlags(this, lightFace));
+
+ facing = QuadFacing.fromNormal(faceNormal);
+ }
+ }
+
+ /** gets flags used for lighting - lazily computed via {@link GeometryHelper#computeShapeFlags(QuadView)}. */
+ public int geometryFlags() {
+ computeGeometry();
+ return EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public float x(int vertexIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X]);
+ }
+
+ @Override
+ public float y(int vertexIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Y]);
+ }
+
+ @Override
+ public float z(int vertexIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Z]);
+ }
+
+ @Override
+ public float posByIndex(int vertexIndex, int coordinateIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X + coordinateIndex]);
+ }
+
+ @Override
+ public Vector3f copyPos(int vertexIndex, @Nullable Vector3f target) {
+ if (target == null) {
+ target = new Vector3f();
+ }
+
+ final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X;
+ target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1]), Float.intBitsToFloat(data[index + 2]));
+ return target;
+ }
+
+ @Override
+ public int color(int vertexIndex) {
+ return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR];
+ }
+
+ @Override
+ public float u(int vertexIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U]);
+ }
+
+ @Override
+ public float v(int vertexIndex) {
+ return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]);
+ }
+
+ @Override
+ public Vector2f copyUv(int vertexIndex, @Nullable Vector2f target) {
+ if (target == null) {
+ target = new Vector2f();
+ }
+
+ final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U;
+ target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1]));
+ return target;
+ }
+
+ @Override
+ public int lightmap(int vertexIndex) {
+ return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP];
+ }
+
+ public final int normalFlags() {
+ return EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public final boolean hasNormal(int vertexIndex) {
+ return (normalFlags() & (1 << vertexIndex)) != 0;
+ }
+
+ /** True if any vertex normal has been set. */
+ public final boolean hasVertexNormals() {
+ return normalFlags() != 0;
+ }
+
+ /** True if all vertex normals have been set. */
+ public final boolean hasAllVertexNormals() {
+ return (normalFlags() & 0b1111) == 0b1111;
+ }
+
+ protected final int normalIndex(int vertexIndex) {
+ return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL;
+ }
+
+ @Override
+ public final float normalX(int vertexIndex) {
+ return hasNormal(vertexIndex) ? NormalHelper.unpackNormalX(data[normalIndex(vertexIndex)]) : Float.NaN;
+ }
+
+ @Override
+ public final float normalY(int vertexIndex) {
+ return hasNormal(vertexIndex) ? NormalHelper.unpackNormalY(data[normalIndex(vertexIndex)]) : Float.NaN;
+ }
+
+ @Override
+ public final float normalZ(int vertexIndex) {
+ return hasNormal(vertexIndex) ? NormalHelper.unpackNormalZ(data[normalIndex(vertexIndex)]) : Float.NaN;
+ }
+
+ @Override
+ @Nullable
+ public final Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target) {
+ if (hasNormal(vertexIndex)) {
+ if (target == null) {
+ target = new Vector3f();
+ }
+
+ final int normal = data[normalIndex(vertexIndex)];
+ NormalHelper.unpackNormal(normal, target);
+ return target;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ @NotNull
+ public final Direction lightFace() {
+ computeGeometry();
+ return EncodingFormat.lightFace(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ @Nullable
+ public final Direction nominalFace() {
+ return nominalFace;
+ }
+
+ public final int packedFaceNormal() {
+ computeGeometry();
+ return data[baseIndex + HEADER_FACE_NORMAL];
+ }
+
+ @Override
+ public final Vector3f faceNormal() {
+ computeGeometry();
+ return faceNormal;
+ }
+
+ @Override
+ @Nullable
+ public final Direction cullFace() {
+ return EncodingFormat.cullFace(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ @Nullable
+ public ChunkSectionLayer renderLayer() {
+ return EncodingFormat.renderLayer(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public boolean emissive() {
+ return EncodingFormat.emissive(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public boolean diffuseShade() {
+ return EncodingFormat.diffuseShade(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public TriState ambientOcclusion() {
+ return EncodingFormat.ambientOcclusion(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ @Nullable
+ public ItemStackRenderState.FoilType glint() {
+ return EncodingFormat.glint(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public ShadeMode shadeMode() {
+ return EncodingFormat.shadeMode(data[baseIndex + HEADER_BITS]);
+ }
+
+ @Override
+ public final int tintIndex() {
+ return data[baseIndex + HEADER_TINT_INDEX];
+ }
+
+ @Override
+ public final int tag() {
+ return data[baseIndex + HEADER_TAG];
+ }
+
+ @Override
+ public final void toVanilla(int[] target, int targetIndex) {
+ System.arraycopy(data, baseIndex + HEADER_STRIDE, target, targetIndex, QUAD_STRIDE);
+
+ int colorIndex = targetIndex + VERTEX_COLOR - HEADER_STRIDE;
+
+ for (int i = 0; i < 4; i++) {
+ target[colorIndex] = ColorHelper.toVanillaColor(target[colorIndex]);
+ colorIndex += VANILLA_VERTEX_STRIDE;
+ }
+ }
+
+ @Override
+ public int getFlags() {
+ return geometryFlags();
+ }
+
+ @Override
+ public float getX(int idx) {
+ return this.x(idx);
+ }
+
+ @Override
+ public float getY(int idx) {
+ return this.y(idx);
+ }
+
+ @Override
+ public float getZ(int idx) {
+ return this.z(idx);
+ }
+
+ @Override
+ public int getColor(int idx) {
+ return this.color(idx);
+ }
+
+ @Override
+ public float getU(int idx) {
+ return this.u(idx);
+ }
+
+ @Override
+ public float getV(int idx) {
+ return this.v(idx);
+ }
+
+ @Override
+ public int getColorIndex() {
+ return this.tintIndex();
+ }
+
+ @Override
+ public Direction getFacingDirection() {
+ return this.lightFace();
+ }
+
+ @Override
+ public int getNormal() {
+ return packedFaceNormal();
+ }
+
+ @Override
+ public QuadFacing getQuadFacing() {
+ return this.facing;
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractBlockRenderContext.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractBlockRenderContext.java
new file mode 100644
index 000000000..9750c2f47
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractBlockRenderContext.java
@@ -0,0 +1,295 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import it.unimi.dsi.fastutil.objects.Object2ByteLinkedOpenHashMap;
+import net.fabricmc.fabric.api.renderer.v1.Renderer;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.color.block.BlockColor;
+import net.minecraft.client.color.block.BlockColors;
+import net.minecraft.client.renderer.ItemBlockRenderTypes;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.block.model.BlockModelPart;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.BlockAndTintGetter;
+import net.minecraft.world.level.BlockGetter;
+import net.minecraft.world.phys.shapes.BooleanOp;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import net.vulkanmod.interfaces.color.BlockColorsExtended;
+import net.vulkanmod.render.chunk.build.color.BlockColorRegistry;
+import net.vulkanmod.render.chunk.build.frapi.VulkanModRenderer;
+import net.vulkanmod.render.chunk.build.light.LightPipeline;
+import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
+import org.jetbrains.annotations.Nullable;
+import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
+import net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.level.block.state.BlockState;
+
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public abstract class AbstractBlockRenderContext extends AbstractRenderContext {
+ private static final Renderer RENDERER = VulkanModRenderer.INSTANCE;
+
+ protected final BlockColorRegistry blockColorRegistry;
+
+ private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
+ {
+ data = new int[EncodingFormat.TOTAL_STRIDE];
+ clear();
+ }
+
+ @Override
+ public void emitDirectly() {
+ renderQuad(this);
+ }
+
+// @Override
+// public void emitBlockQuads(QuadEmitter emitter, BakedModel model, BlockState state,
+// Supplier randomSupplier, Predicate<@Nullable Direction> cullTest) {
+// if (this.hasTransform) {
+// super.emitBlockQuads(emitter, model, state, randomSupplier, cullTest);
+// } else {
+// AbstractBlockRenderContext.this.emitVanillaBlockQuads(model, state, randomSupplier, cullTest);
+// }
+// }
+ };
+
+ protected BlockState blockState;
+ protected BlockPos blockPos;
+ protected BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos();
+ protected ChunkSectionLayer defaultLayer;
+
+ protected BlockAndTintGetter renderRegion;
+
+ protected final Object2ByteLinkedOpenHashMap occlusionCache = new Object2ByteLinkedOpenHashMap<>(2048, 0.25F) {
+ protected void rehash(int i) {
+ }
+ };
+
+ protected final QuadLightData quadLightData = new QuadLightData();
+ protected LightPipeline smoothLightPipeline;
+ protected LightPipeline flatLightPipeline;
+
+ protected boolean useAO;
+ protected boolean defaultAO;
+
+ protected RandomSource random;
+
+ protected boolean enableCulling = true;
+ protected int cullCompletionFlags;
+ protected int cullResultFlags;
+
+ protected AbstractBlockRenderContext() {
+ this.occlusionCache.defaultReturnValue((byte) 127);
+
+ BlockColors blockColors = Minecraft.getInstance().getBlockColors();
+ this.blockColorRegistry = BlockColorsExtended.from(blockColors).getColorResolverMap();
+ }
+
+ protected void setupLightPipelines(LightPipeline flatLightPipeline, LightPipeline smoothLightPipeline) {
+ this.flatLightPipeline = flatLightPipeline;
+ this.smoothLightPipeline = smoothLightPipeline;
+ }
+
+ public void prepareForWorld(BlockAndTintGetter blockView, boolean enableCulling) {
+ this.renderRegion = blockView;
+ this.enableCulling = enableCulling;
+ }
+
+ public void prepareForBlock(BlockState blockState, BlockPos blockPos, boolean modelAo) {
+ this.blockPos = blockPos;
+ this.blockState = blockState;
+ this.defaultLayer = ItemBlockRenderTypes.getChunkRenderType(blockState);
+
+ this.useAO = Minecraft.useAmbientOcclusion();
+ this.defaultAO = this.useAO && modelAo && blockState.getLightEmission() == 0;
+
+ this.cullCompletionFlags = 0;
+ this.cullResultFlags = 0;
+ }
+
+ public boolean isFaceCulled(@Nullable Direction face) {
+ return !this.shouldRenderFace(face);
+ }
+
+ public boolean shouldRenderFace(Direction face) {
+ if (face == null || !enableCulling) {
+ return true;
+ }
+
+ final int mask = 1 << face.get3DDataValue();
+
+ if ((cullCompletionFlags & mask) == 0) {
+ cullCompletionFlags |= mask;
+
+ if (this.faceNotOccluded(blockState, face)) {
+ cullResultFlags |= mask;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return (cullResultFlags & mask) != 0;
+ }
+ }
+
+ public boolean faceNotOccluded(BlockState blockState, Direction face) {
+ BlockGetter blockGetter = this.renderRegion;
+
+ BlockPos adjPos = tempPos.setWithOffset(blockPos, face);
+ BlockState adjBlockState = blockGetter.getBlockState(adjPos);
+
+ if (blockState.skipRendering(adjBlockState, face)) {
+ return false;
+ }
+
+ if (adjBlockState.canOcclude()) {
+ VoxelShape shape = blockState.getFaceOcclusionShape(face);
+
+ if (shape.isEmpty())
+ return true;
+
+ VoxelShape adjShape = adjBlockState.getFaceOcclusionShape(face.getOpposite());
+
+ if (adjShape.isEmpty())
+ return true;
+
+ if (shape == Shapes.block() && adjShape == Shapes.block()) {
+ return false;
+ }
+
+ ShapePairKey blockStatePairKey = new ShapePairKey(shape, adjShape);
+
+ byte b = occlusionCache.getAndMoveToFirst(blockStatePairKey);
+ if (b != 127) {
+ return b != 0;
+ } else {
+ boolean bl = Shapes.joinIsNotEmpty(shape, adjShape, BooleanOp.ONLY_FIRST);
+
+ if (occlusionCache.size() == 2048) {
+ occlusionCache.removeLastByte();
+ }
+
+ occlusionCache.putAndMoveToFirst(blockStatePairKey, (byte) (bl ? 1 : 0));
+ return bl;
+ }
+ }
+
+ return true;
+ }
+
+ public QuadEmitter getEmitter() {
+ editorQuad.clear();
+ return editorQuad;
+ }
+
+ @Override
+ protected void bufferQuad(MutableQuadViewImpl quadView) {
+ this.renderQuad(quadView);
+ }
+
+ protected abstract VertexConsumer getVertexConsumer(ChunkSectionLayer layer);
+
+ private void renderQuad(MutableQuadViewImpl quad) {
+ if (isFaceCulled(quad.cullFace())) {
+ return;
+ }
+
+ endRenderQuad(quad);
+ }
+
+ protected void endRenderQuad(MutableQuadViewImpl quad) {}
+
+ /** handles block color, common to all renders. */
+ protected void tintQuad(MutableQuadViewImpl quad) {
+ int tintIndex = quad.tintIndex();
+
+ if (tintIndex != -1) {
+ final int blockColor = getBlockColor(this.renderRegion, tintIndex);
+
+ for (int i = 0; i < 4; i++) {
+ quad.color(i, ColorHelper.multiplyColor(blockColor, quad.color(i)));
+ }
+ }
+ }
+
+ private int getBlockColor(BlockAndTintGetter region, int colorIndex) {
+ BlockColor blockColor = this.blockColorRegistry.getBlockColor(this.blockState.getBlock());
+
+ int color = blockColor != null ? blockColor.getColor(blockState, region, blockPos, colorIndex) : -1;
+ return 0xFF000000 | color;
+ }
+
+ protected void shadeQuad(MutableQuadViewImpl quad, LightPipeline lightPipeline, boolean emissive, boolean vanillaShade) {
+ QuadLightData data = this.quadLightData;
+
+ // TODO: enhanced AO
+ lightPipeline.calculate(quad, this.blockPos, data, quad.cullFace(), quad.lightFace(), quad.diffuseShade());
+
+ if (emissive) {
+ for (int i = 0; i < 4; i++) {
+ quad.color(i, ColorHelper.multiplyRGB(quad.color(i), data.br[i]));
+// quad.lightmap(i, LightTexture.FULL_BRIGHT);
+ data.lm[i] = LightTexture.FULL_BRIGHT;
+ }
+ } else {
+ for (int i = 0; i < 4; i++) {
+ quad.color(i, ColorHelper.multiplyRGB(quad.color(i), data.br[i]));
+// quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), data.lm[i]));
+ data.lm[i] = ColorHelper.maxBrightness(quad.lightmap(i), data.lm[i]);
+ }
+ }
+ }
+
+ public ChunkSectionLayer effectiveRenderLayer(@Nullable ChunkSectionLayer quadRenderLayer) {
+ return quadRenderLayer == null ? defaultLayer : quadRenderLayer;
+ }
+
+ public void emitVanillaBlockQuads(BlockStateModel model, @Nullable BlockState state, Supplier randomSupplier, Predicate cullTest) {
+ MutableQuadViewImpl quad = this.editorQuad;
+// final RenderMaterial defaultMaterial = state.getLightEmission() == 0 ? STANDARD_MATERIAL : NO_AO_MATERIAL;
+
+ for (int i = 0; i <= ModelHelper.NULL_FACE_ID; i++) {
+ final Direction cullFace = ModelHelper.faceFromIndex(i);
+
+ if (cullTest.test(cullFace)) {
+ // Skip entire quad list if possible.
+ continue;
+ }
+
+ final List parts = ((BlockStateModel) this).collectParts(random);
+ final int partCount = parts.size();
+
+ for (int j = 0; j < partCount; j++) {
+ parts.get(j).emitQuads(quad, cullTest);
+ }
+ }
+
+ }
+
+ // TODO move elsewhere
+ record ShapePairKey(VoxelShape first, VoxelShape second) {
+ public boolean equals(Object object) {
+ if (object instanceof ShapePairKey shapePairKey && this.first == shapePairKey.first && this.second == shapePairKey.second) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public int hashCode() {
+ return System.identityHashCode(this.first) * 31 + System.identityHashCode(this.second);
+ }
+ }
+
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractRenderContext.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractRenderContext.java
new file mode 100644
index 000000000..d98321876
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/AbstractRenderContext.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.vulkanmod.render.chunk.build.frapi.mesh.EncodingFormat;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import org.joml.Matrix3f;
+import org.joml.Matrix4f;
+import org.joml.Vector3f;
+import org.joml.Vector4f;
+
+
+public abstract class AbstractRenderContext {
+ private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() {
+ {
+ data = new int[EncodingFormat.TOTAL_STRIDE];
+ clear();
+ }
+
+ @Override
+ protected void emitDirectly() {
+ bufferQuad(this);
+ }
+ };
+
+ private final Vector4f posVec = new Vector4f();
+ private final Vector3f normalVec = new Vector3f();
+
+ protected PoseStack.Pose matrices;
+ protected int overlay;
+
+ protected QuadEmitter getEmitter() {
+ editorQuad.clear();
+ return editorQuad;
+ }
+
+ protected abstract void bufferQuad(MutableQuadViewImpl quadView);
+
+ /** final output step, common to all renders. */
+ protected void bufferQuad(MutableQuadViewImpl quad, VertexConsumer vertexConsumer) {
+ final Vector4f posVec = this.posVec;
+ final Vector3f normalVec = this.normalVec;
+ final PoseStack.Pose matrices = this.matrices;
+ final Matrix4f posMatrix = matrices.pose();
+ final boolean useNormals = quad.hasVertexNormals();
+
+ if (useNormals) {
+ quad.populateMissingNormals();
+ } else {
+ matrices.transformNormal(quad.faceNormal(), normalVec);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ posVec.set(quad.x(i), quad.y(i), quad.z(i), 1.0f);
+ posVec.mul(posMatrix);
+
+ if (useNormals) {
+ quad.copyNormal(i, normalVec);
+ matrices.transformNormal(normalVec, normalVec);
+ }
+
+ vertexConsumer.addVertex(posVec.x(), posVec.y(), posVec.z(), quad.color(i), quad.u(i), quad.v(i), overlay, quad.lightmap(i), normalVec.x(), normalVec.y(), normalVec.z());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/BlockRenderContext.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/BlockRenderContext.java
new file mode 100644
index 000000000..872671b4b
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/BlockRenderContext.java
@@ -0,0 +1,101 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
+import net.fabricmc.fabric.api.renderer.v1.render.BlockVertexConsumerProvider;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.client.renderer.ItemBlockRenderTypes;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.BlockAndTintGetter;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+import net.vulkanmod.Initializer;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import net.vulkanmod.render.chunk.build.light.LightMode;
+import net.vulkanmod.render.chunk.build.light.LightPipeline;
+import net.vulkanmod.render.chunk.build.light.data.ArrayLightDataCache;
+import net.vulkanmod.render.chunk.build.light.flat.FlatLightPipeline;
+import net.vulkanmod.render.chunk.build.light.smooth.NewSmoothLightPipeline;
+import net.vulkanmod.render.chunk.build.light.smooth.SmoothLightPipeline;
+
+/**
+ * Context for non-terrain block rendering.
+ */
+public class BlockRenderContext extends AbstractBlockRenderContext {
+ public static final ThreadLocal POOL = ThreadLocal.withInitial(BlockRenderContext::new);
+
+ private BlockVertexConsumerProvider vertexConsumers;
+ private ChunkSectionLayer defaultRenderLayer;
+
+ private final ArrayLightDataCache lightDataCache = new ArrayLightDataCache();
+
+ public BlockRenderContext() {
+ LightPipeline flatLightPipeline = new FlatLightPipeline(this.lightDataCache);
+
+ LightPipeline smoothLightPipeline;
+ if (Initializer.CONFIG.ambientOcclusion == LightMode.SUB_BLOCK) {
+ smoothLightPipeline = new NewSmoothLightPipeline(lightDataCache);
+ }
+ else {
+ smoothLightPipeline = new SmoothLightPipeline(lightDataCache);
+ }
+
+ this.setupLightPipelines(flatLightPipeline, smoothLightPipeline);
+
+ random = RandomSource.create();
+ }
+
+ public void render(BlockAndTintGetter blockView, BlockStateModel model, BlockState state, BlockPos pos, PoseStack matrixStack, BlockVertexConsumerProvider buffers, boolean cull, long seed, int overlay) {
+ Vec3 offset = state.getOffset(pos);
+ matrixStack.translate(offset.x, offset.y, offset.z);
+
+ this.blockPos = pos;
+ this.vertexConsumers = buffers;
+ this.defaultRenderLayer = ItemBlockRenderTypes.getChunkRenderType(state);
+ this.matrices = matrixStack.last();
+ this.overlay = overlay;
+ this.random.setSeed(seed);
+
+ this.lightDataCache.reset(blockView, pos);
+
+ this.prepareForWorld(blockView, cull);
+ this.prepareForBlock(state, pos, state.getLightEmission() == 0);
+
+ model.emitQuads(getEmitter(), blockView, pos, state, random, this::isFaceCulled);
+
+ this.vertexConsumers = null;
+ }
+
+ @Override
+ protected VertexConsumer getVertexConsumer(ChunkSectionLayer layer) {
+ return vertexConsumers.getBuffer(layer);
+ }
+
+ protected void endRenderQuad(MutableQuadViewImpl quad) {
+ final TriState aoMode = quad.ambientOcclusion();
+ final boolean ao = this.useAO && (aoMode == TriState.TRUE || (aoMode == TriState.DEFAULT && this.defaultAO));
+ final boolean emissive = quad.emissive();
+ final boolean vanillaShade = quad.shadeMode() == ShadeMode.VANILLA;
+ final ChunkSectionLayer quadRenderLayer = quad.renderLayer();
+ final ChunkSectionLayer renderLayer = quadRenderLayer == null ? defaultRenderLayer : quadRenderLayer;
+ final VertexConsumer vertexConsumer = getVertexConsumer(renderLayer);
+
+ LightPipeline lightPipeline = ao ? this.smoothLightPipeline : this.flatLightPipeline;
+
+ tintQuad(quad);
+ shadeQuad(quad, lightPipeline, emissive, vanillaShade);
+ copyLightData(quad);
+ bufferQuad(quad, vertexConsumer);
+ }
+
+ private void copyLightData(MutableQuadViewImpl quad) {
+ for (int i = 0; i < 4; i++) {
+ quad.lightmap(i, this.quadLightData.lm[i]);
+ }
+ }
+
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/ItemRenderContext.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/ItemRenderContext.java
new file mode 100644
index 000000000..3448ad0a9
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/ItemRenderContext.java
@@ -0,0 +1,176 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import com.mojang.math.MatrixUtil;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Supplier;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.MeshView;
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
+import net.fabricmc.fabric.api.renderer.v1.render.FabricLayerRenderState;
+import net.fabricmc.fabric.api.renderer.v1.render.RenderLayerHelper;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.vulkanmod.mixin.render.frapi.ItemRendererAccessor;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.Sheets;
+import net.minecraft.client.renderer.entity.ItemRenderer;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.item.ItemDisplayContext;
+import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import org.jetbrains.annotations.Nullable;
+
+
+/**
+ * Used during item buffering to support geometry added through {@link FabricLayerRenderState#emitter()}.
+ */
+public class ItemRenderContext extends AbstractRenderContext {
+ private static final int GLINT_COUNT = ItemStackRenderState.FoilType.values().length;
+
+ private ItemDisplayContext itemDisplayContext;
+ private PoseStack matrixStack;
+ private MultiBufferSource vertexConsumerProvider;
+ private int lightmap;
+ private int[] tints;
+
+ private RenderType defaultLayer;
+ private ItemStackRenderState.FoilType defaultGlint;
+ private boolean ignoreQuadGlint;
+
+ private PoseStack.Pose specialGlintEntry;
+ private final VertexConsumer[] vertexConsumerCache = new VertexConsumer[3 * GLINT_COUNT];
+
+ public void renderModel(ItemDisplayContext itemDisplayContext, PoseStack matrixStack, MultiBufferSource bufferSource, int lightmap, int overlay, int[] tints, List modelQuads, MeshView mesh, RenderType renderType, ItemStackRenderState.FoilType foilType, boolean ignoreQuadGlint) {
+ this.itemDisplayContext = itemDisplayContext;
+ this.matrixStack = matrixStack;
+ this.vertexConsumerProvider = bufferSource;
+ this.lightmap = lightmap;
+ this.overlay = overlay;
+ this.tints = tints;
+
+ defaultLayer = renderType;
+ defaultGlint = foilType;
+ this.ignoreQuadGlint = ignoreQuadGlint;
+
+ bufferQuads(modelQuads, mesh);
+
+ this.matrixStack = null;
+ this.vertexConsumerProvider = null;
+ this.tints = null;
+
+ specialGlintEntry = null;
+ Arrays.fill(vertexConsumerCache, null);
+ }
+
+ private void bufferQuads(List vanillaQuads, MeshView mesh) {
+ QuadEmitter emitter = getEmitter();
+
+ final int vanillaQuadCount = vanillaQuads.size();
+
+ for (int i = 0; i < vanillaQuadCount; i++) {
+ final BakedQuad q = vanillaQuads.get(i);
+ emitter.fromBakedQuad(q);
+ emitter.emit();
+ }
+
+ mesh.outputTo(emitter);
+ }
+
+ @Override
+ protected void bufferQuad(MutableQuadViewImpl quad) {
+ final VertexConsumer vertexConsumer = getVertexConsumer(quad.renderLayer(), quad.glint());
+
+ tintQuad(quad);
+ shadeQuad(quad, quad.emissive());
+ bufferQuad(quad, vertexConsumer);
+ }
+
+ private void tintQuad(MutableQuadViewImpl quad) {
+ int tintIndex = quad.tintIndex();
+
+ if (tintIndex != -1 && tintIndex < tints.length) {
+ final int tint = tints[tintIndex];
+
+ for (int i = 0; i < 4; i++) {
+ quad.color(i, ColorHelper.multiplyColor(tint, quad.color(i)));
+ }
+ }
+ }
+
+ private void shadeQuad(MutableQuadViewImpl quad, boolean emissive) {
+ if (emissive) {
+ for (int i = 0; i < 4; i++) {
+ quad.lightmap(i, LightTexture.FULL_BRIGHT);
+ }
+ } else {
+ final int lightmap = this.lightmap;
+
+ for (int i = 0; i < 4; i++) {
+ quad.lightmap(i, ColorHelper.maxBrightness(quad.lightmap(i), lightmap));
+ }
+ }
+ }
+
+ private VertexConsumer getVertexConsumer(@Nullable ChunkSectionLayer quadRenderLayer, @Nullable ItemStackRenderState.FoilType quadGlint) {
+ RenderType layer;
+ ItemStackRenderState.FoilType glint;
+
+ if (quadRenderLayer == null) {
+ layer = defaultLayer;
+ } else {
+ layer = RenderLayerHelper.getEntityBlockLayer(quadRenderLayer);
+ }
+
+ if (ignoreQuadGlint || quadGlint == null) {
+ glint = defaultGlint;
+ } else {
+ glint = quadGlint;
+ }
+
+ int cacheIndex;
+
+ if (layer == Sheets.translucentItemSheet()) {
+ cacheIndex = 0;
+ } else if (layer == Sheets.cutoutBlockSheet()) {
+ cacheIndex = GLINT_COUNT;
+ } else {
+ cacheIndex = 2 * GLINT_COUNT;
+ }
+
+ cacheIndex += glint.ordinal();
+ VertexConsumer vertexConsumer = vertexConsumerCache[cacheIndex];
+
+ if (vertexConsumer == null) {
+ vertexConsumer = createVertexConsumer(layer, glint);
+ vertexConsumerCache[cacheIndex] = vertexConsumer;
+ }
+
+ return vertexConsumer;
+ }
+
+ private VertexConsumer createVertexConsumer(RenderType layer, ItemStackRenderState.FoilType glint) {
+ if (glint == ItemStackRenderState.FoilType.SPECIAL) {
+ if (specialGlintEntry == null) {
+ specialGlintEntry = matrixStack.last().copy();
+
+ if (itemDisplayContext == ItemDisplayContext.GUI) {
+ MatrixUtil.mulComponentWise(specialGlintEntry.pose(), 0.5F);
+ } else if (itemDisplayContext.firstPerson()) {
+ MatrixUtil.mulComponentWise(specialGlintEntry.pose(), 0.75F);
+ }
+ }
+
+ return ItemRendererAccessor.getSpecialFoilBuffer(vertexConsumerProvider, layer, specialGlintEntry);
+ }
+
+ return ItemRenderer.getFoilBuffer(vertexConsumerProvider, layer, true, glint != ItemStackRenderState.FoilType.NONE);
+ }
+
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/MeshItemCommand.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/MeshItemCommand.java
new file mode 100644
index 000000000..52acc30b1
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/MeshItemCommand.java
@@ -0,0 +1,12 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import java.util.List;
+import net.fabricmc.fabric.api.renderer.v1.mesh.MeshView;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.minecraft.world.item.ItemDisplayContext;
+
+public record MeshItemCommand(PoseStack.Pose positionMatrix, ItemDisplayContext displayContext, int lightCoords, int overlayCoords, int outlineColor, int[] tintLayers, List quads, RenderType renderLayer, ItemStackRenderState.FoilType glintType, MeshView mesh) {
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/QuadToPosPipe.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/QuadToPosPipe.java
new file mode 100644
index 000000000..151d073c8
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/QuadToPosPipe.java
@@ -0,0 +1,26 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+
+import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
+import org.joml.Matrix4fc;
+import org.joml.Vector3f;
+import org.joml.Vector3fc;
+
+import java.util.function.Consumer;
+
+public class QuadToPosPipe implements Consumer {
+ private final Consumer posConsumer;
+ private final Vector3f vec;
+ public Matrix4fc matrix;
+
+ public QuadToPosPipe(Consumer posConsumer, Vector3f vec) {
+ this.posConsumer = posConsumer;
+ this.vec = vec;
+ }
+
+ @Override
+ public void accept(QuadView quad) {
+ for (int i = 0; i < 4; i++) {
+ posConsumer.accept(quad.copyPos(i, vec).mulPosition(matrix));
+ }
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/SimpleBlockRenderContext.java b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/SimpleBlockRenderContext.java
new file mode 100644
index 000000000..275738cfb
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/frapi/render/SimpleBlockRenderContext.java
@@ -0,0 +1,119 @@
+package net.vulkanmod.render.chunk.build.frapi.render;
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.fabricmc.fabric.api.renderer.v1.render.BlockVertexConsumerProvider;
+import net.minecraft.client.renderer.ItemBlockRenderTypes;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.client.renderer.MultiBufferSource;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.ARGB;
+import net.minecraft.util.Mth;
+import net.minecraft.util.RandomSource;
+import net.minecraft.world.level.BlockAndTintGetter;
+import net.minecraft.world.level.block.state.BlockState;
+import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import org.jetbrains.annotations.Nullable;
+
+public class SimpleBlockRenderContext extends AbstractRenderContext {
+ public static final ThreadLocal POOL = ThreadLocal.withInitial(SimpleBlockRenderContext::new);
+
+ private final RandomSource random = RandomSource.create();
+
+ private BlockVertexConsumerProvider vertexConsumers;
+ private ChunkSectionLayer defaultRenderLayer;
+ private float red;
+ private float green;
+ private float blue;
+ private int light;
+
+ @Nullable
+ private ChunkSectionLayer lastRenderLayer;
+ @Nullable
+ private VertexConsumer lastVertexConsumer;
+
+ @Override
+ protected void bufferQuad(MutableQuadViewImpl quad) {
+ final ChunkSectionLayer quadRenderLayer = quad.renderLayer();
+ final ChunkSectionLayer renderLayer = quadRenderLayer == null ? defaultRenderLayer : quadRenderLayer;
+ final VertexConsumer vertexConsumer;
+
+ if (renderLayer == lastRenderLayer) {
+ vertexConsumer = lastVertexConsumer;
+ } else {
+ lastVertexConsumer = vertexConsumer = vertexConsumers.getBuffer(renderLayer);
+ lastRenderLayer = renderLayer;
+ }
+
+ tintQuad(quad);
+ shadeQuad(quad, quad.emissive());
+ bufferQuad(quad, vertexConsumer);
+ }
+
+ private void tintQuad(MutableQuadViewImpl quad) {
+ if (quad.tintIndex() != -1) {
+ final float red = this.red;
+ final float green = this.green;
+ final float blue = this.blue;
+
+ for (int i = 0; i < 4; i++) {
+ quad.color(i, ARGB.scaleRGB(quad.color(i), red, green, blue));
+ }
+ }
+ }
+
+ private void shadeQuad(MutableQuadViewImpl quad, boolean emissive) {
+ if (emissive) {
+ for (int i = 0; i < 4; i++) {
+ quad.lightmap(i, LightTexture.FULL_BRIGHT);
+ }
+ } else {
+ final int light = this.light;
+
+ for (int i = 0; i < 4; i++) {
+ quad.lightmap(i, ColorHelper.maxLight(quad.lightmap(i), light));
+ }
+ }
+ }
+
+ public void bufferModel(PoseStack.Pose entry, BlockVertexConsumerProvider vertexConsumers, BlockStateModel model, float red, float green, float blue, int light, int overlay, BlockAndTintGetter blockView, BlockPos pos, BlockState state) {
+ matrices = entry;
+ this.overlay = overlay;
+
+ this.vertexConsumers = vertexConsumers;
+ this.defaultRenderLayer = ItemBlockRenderTypes.getChunkRenderType(state);
+ this.red = Mth.clamp(red, 0, 1);
+ this.green = Mth.clamp(green, 0, 1);
+ this.blue = Mth.clamp(blue, 0, 1);
+ this.light = light;
+
+ random.setSeed(42L);
+
+ model.emitQuads(getEmitter(), blockView, pos, state, random, cullFace -> false);
+
+ matrices = null;
+ this.vertexConsumers = null;
+ lastRenderLayer = null;
+ lastVertexConsumer = null;
+ }
+}
+
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/LightPipeline.java b/src/main/java/net/vulkanmod/render/chunk/build/light/LightPipeline.java
index ba0fa8a7b..69d3c184e 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/LightPipeline.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/LightPipeline.java
@@ -2,7 +2,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
-import net.vulkanmod.render.model.quad.QuadView;
+import net.vulkanmod.render.model.quad.ModelQuadView;
import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
/**
@@ -19,5 +19,5 @@ public interface LightPipeline {
* @param lightFace The light face of the quad
* @param shade True if the block is shaded by ambient occlusion
*/
- void calculate(QuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFace, boolean shade);
+ void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFace, boolean shade);
}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/data/ArrayLightDataCache.java b/src/main/java/net/vulkanmod/render/chunk/build/light/data/ArrayLightDataCache.java
index 1fbde68f7..3e23dcc5c 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/data/ArrayLightDataCache.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/data/ArrayLightDataCache.java
@@ -25,7 +25,7 @@ public ArrayLightDataCache() {
}
public void reset(BlockAndTintGetter blockAndTintGetter, int x, int y, int z) {
- this.world = blockAndTintGetter;
+ this.region = blockAndTintGetter;
this.xOffset = x - NEIGHBOR_BLOCK_RADIUS;
this.yOffset = y - NEIGHBOR_BLOCK_RADIUS;
@@ -34,6 +34,16 @@ public void reset(BlockAndTintGetter blockAndTintGetter, int x, int y, int z) {
Arrays.fill(this.light, 0);
}
+ public void reset(BlockAndTintGetter blockAndTintGetter, BlockPos origin) {
+ this.region = blockAndTintGetter;
+
+ this.xOffset = origin.getX() - NEIGHBOR_BLOCK_RADIUS;
+ this.yOffset = origin.getY() - NEIGHBOR_BLOCK_RADIUS;
+ this.zOffset = origin.getZ() - NEIGHBOR_BLOCK_RADIUS;
+
+ Arrays.fill(this.light, 0);
+ }
+
public void reset(SectionPos origin) {
this.xOffset = origin.minBlockX() - NEIGHBOR_BLOCK_RADIUS;
this.yOffset = origin.minBlockY() - NEIGHBOR_BLOCK_RADIUS;
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/data/LightDataAccess.java b/src/main/java/net/vulkanmod/render/chunk/build/light/data/LightDataAccess.java
index ff0ad86d4..d2e532738 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/data/LightDataAccess.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/data/LightDataAccess.java
@@ -5,7 +5,6 @@
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LightLayer;
-import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.vulkanmod.Initializer;
@@ -16,10 +15,10 @@
/**
* The light data cache is used to make accessing the light data and occlusion properties of blocks cheaper. The data
* for each block is stored as an integer with packed fields in order to work around the lack of value types in Java.
- *
+ *
* This code is not very pretty, but it does perform significantly faster than the vanilla implementation and has
* good cache locality.
- *
+ *
* Each integer contains the following fields:
* - BL: World block light, encoded as a 4-bit unsigned integer
* - SL: World sky light, encoded as a 4-bit unsigned integer
@@ -29,7 +28,7 @@
* - OP: Block opacity test, true if opaque
* - FO: Full cube opacity test, true if opaque full cube
* - FC: Full cube test, true if full cube
- *
+ *
* You can use the various static pack/unpack methods to extract these values in a usable format.
*/
public abstract class LightDataAccess {
@@ -45,7 +44,7 @@ public abstract class LightDataAccess {
private static final float AO_INV = 1.0f / 2048.0f;
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
- protected BlockAndTintGetter world;
+ protected BlockAndTintGetter region;
final boolean subBlockLighting;
@@ -55,14 +54,14 @@ protected LightDataAccess() {
public int get(int x, int y, int z, SimpleDirection d1, SimpleDirection d2) {
return this.get(x + d1.getStepX() + d2.getStepX(),
- y + d1.getStepY() + d2.getStepY(),
- z + d1.getStepZ() + d2.getStepZ());
+ y + d1.getStepY() + d2.getStepY(),
+ z + d1.getStepZ() + d2.getStepZ());
}
public int get(int x, int y, int z, SimpleDirection dir) {
return this.get(x + dir.getStepX(),
- y + dir.getStepY(),
- z + dir.getStepZ());
+ y + dir.getStepY(),
+ z + dir.getStepZ());
}
public int get(BlockPos pos, SimpleDirection dir) {
@@ -81,19 +80,18 @@ public int get(BlockPos pos) {
protected int compute(int x, int y, int z) {
BlockPos pos = this.pos.set(x, y, z);
+ BlockState state = region.getBlockState(pos);
- BlockState state = world.getBlockState(pos);
-
- boolean em = state.emissiveRendering(world, pos);
+ boolean em = state.emissiveRendering(region, pos);
boolean op;
- if(this.subBlockLighting)
+ if (this.subBlockLighting)
op = state.canOcclude();
else
- op = state.isViewBlocking(world, pos) && state.getLightBlock(world, pos) != 0;
+ op = state.isViewBlocking(region, pos) && state.getLightBlock() != 0;
- boolean fo = state.isSolidRender(world, pos);
- boolean fc = state.isCollisionShapeFullBlock(world, pos);
+ boolean fo = state.isSolidRender();
+ boolean fc = state.isCollisionShapeFullBlock(region, pos);
int lu = state.getLightEmission();
@@ -103,16 +101,25 @@ protected int compute(int x, int y, int z) {
if (fo && lu == 0) {
bl = 0;
sl = 0;
- } else {
- bl = world.getBrightness(LightLayer.BLOCK, pos);
- sl = world.getBrightness(LightLayer.SKY, pos);
+ }
+ else {
+ if (em) {
+ bl = region.getBrightness(LightLayer.BLOCK, pos);
+ sl = region.getBrightness(LightLayer.SKY, pos);
+ }
+ else {
+ int light = LevelRenderer.getLightColor(LevelRenderer.BrightnessGetter.DEFAULT, region, state, pos);
+ bl = LightTexture.block(light);
+ sl = LightTexture.sky(light);
+ }
}
// FIX: Do not apply AO from blocks that emit light
float ao;
if (lu == 0) {
- ao = state.getShadeBrightness(world, pos);
- } else {
+ ao = state.getShadeBrightness(region, pos);
+ }
+ else {
ao = 1.0f;
}
@@ -121,12 +128,12 @@ protected int compute(int x, int y, int z) {
bl = Math.max(bl, lu);
int crs = (fo || fc) && lu == 0 && useAo ? 0xFF : 0;
- if(!fo && op) {
- VoxelShape shape = state.getShape(world, pos);
- crs = ((VoxelShapeExtended)(shape)).getCornerOcclusion();
+ if (!fo && op) {
+ VoxelShape shape = state.getShape(region, pos);
+ crs = ((VoxelShapeExtended) (shape)).getCornerOcclusion();
}
- return packFC(fc) | packFO(fo) | packOP(op) | packEM(em) | packCO(crs) | packAO(ao) | packSL(sl) | packBL(bl);
+ return packFC(fc) | packFO(fo) | packOP(op) | packEM(em) | packCO(crs) | packAO(ao) | packSL(sl) | packBL(bl);
}
public static int packBL(int blockLight) {
@@ -215,7 +222,7 @@ public static int getEmissiveLightmap(int word) {
}
}
- public BlockAndTintGetter getWorld() {
- return this.world;
+ public BlockAndTintGetter getRegion() {
+ return this.region;
}
}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/flat/FlatLightPipeline.java b/src/main/java/net/vulkanmod/render/chunk/build/light/flat/FlatLightPipeline.java
index 2472ac4b8..b11f4e352 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/flat/FlatLightPipeline.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/flat/FlatLightPipeline.java
@@ -4,7 +4,7 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.vulkanmod.render.chunk.util.SimpleDirection;
-import net.vulkanmod.render.model.quad.QuadView;
+import net.vulkanmod.render.model.quad.ModelQuadView;
import net.vulkanmod.render.chunk.build.light.data.LightDataAccess;
import net.vulkanmod.render.chunk.build.light.LightPipeline;
import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
@@ -29,7 +29,7 @@ public FlatLightPipeline(LightDataAccess lightCache) {
}
@Override
- public void calculate(QuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFace, boolean shade) {
+ public void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFace, boolean shade) {
int lightmap;
// To match vanilla behavior, use the cull face if it exists/is available
@@ -47,7 +47,7 @@ public void calculate(QuadView quad, BlockPos pos, QuadLightData out, Direction
}
Arrays.fill(out.lm, lightmap);
- Arrays.fill(out.br, this.lightCache.getWorld().getShade(lightFace, shade));
+ Arrays.fill(out.br, this.lightCache.getRegion().getShade(lightFace, shade));
}
private int getLightmap(BlockPos pos, Direction face) {
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/NewSmoothLightPipeline.java b/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/NewSmoothLightPipeline.java
index 32cdabcb9..503d27ff9 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/NewSmoothLightPipeline.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/NewSmoothLightPipeline.java
@@ -4,7 +4,7 @@
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.vulkanmod.render.chunk.util.SimpleDirection;
-import net.vulkanmod.render.model.quad.QuadView;
+import net.vulkanmod.render.model.quad.ModelQuadView;
import net.vulkanmod.render.chunk.build.light.data.LightDataAccess;
import net.vulkanmod.render.chunk.build.light.LightPipeline;
import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
@@ -42,7 +42,7 @@ public NewSmoothLightPipeline(LightDataAccess cache) {
}
@Override
- public void calculate(QuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFaceO, boolean shade) {
+ public void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFaceO, boolean shade) {
this.updateCachedData(pos.asLong());
int flags = quad.getFlags();
@@ -85,7 +85,7 @@ private void applyAlignedFullFace(AoNeighborInfo neighborInfo, BlockPos pos, Sim
* Calculates the light data for a grid-aligned quad that does not cover the entire block volume's face.
* Flags: IS_ALIGNED, IS_PARTIAL
*/
- private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
// TODO stair lighting is inconsistent
// A solution might be an interpolation grid
// this.self.calculatePartialAlignedFace(this.lightCache, pos, dir);
@@ -104,13 +104,13 @@ private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, QuadView quad,
}
/**
- * This method is the same as {@link #applyNonParallelFace(AoNeighborInfo, QuadView, BlockPos, SimpleDirection,
+ * This method is the same as {@link #applyNonParallelFace(AoNeighborInfo, ModelQuadView, BlockPos, SimpleDirection,
* QuadLightData)} but with the check for a depth of approximately 0 removed. If the quad is parallel but not
* aligned, all of its vertices will have the same depth and this depth must be approximately greater than 0,
* meaning the check for 0 will always return false.
* Flags: !IS_ALIGNED, IS_PARALLEL
*/
- private void applyParallelFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyParallelFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
this.self.calculateSelfOcclusion(this.lightCache, pos, dir);
for (int i = 0; i < 4; i++) {
@@ -139,7 +139,7 @@ private void applyParallelFace(AoNeighborInfo neighborInfo, QuadView quad, Block
/**
* Flags: !IS_ALIGNED, !IS_PARALLEL
*/
- private void applyNonParallelFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyNonParallelFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
for (int i = 0; i < 4; i++) {
// Clamp the vertex positions to the block's boundaries to prevent weird errors in lighting
float cx = clamp(quad.getX(i));
@@ -229,7 +229,7 @@ private void applyInsetPartialFaceVertexSO(BlockPos pos, SimpleDirection dir, fl
}
private void applySidedBrightness(QuadLightData out, Direction face, boolean shade) {
- float brightness = this.lightCache.getWorld().getShade(face, shade);
+ float brightness = this.lightCache.getRegion().getShade(face, shade);
float[] br = out.br;
for (int i = 0; i < br.length; i++) {
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/SmoothLightPipeline.java b/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/SmoothLightPipeline.java
index 62993e312..154eb0518 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/SmoothLightPipeline.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/light/smooth/SmoothLightPipeline.java
@@ -4,7 +4,7 @@
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.vulkanmod.render.chunk.util.SimpleDirection;
-import net.vulkanmod.render.model.quad.QuadView;
+import net.vulkanmod.render.model.quad.ModelQuadView;
import net.vulkanmod.render.chunk.build.light.data.LightDataAccess;
import net.vulkanmod.render.chunk.build.light.LightPipeline;
import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
@@ -67,7 +67,7 @@ public SmoothLightPipeline(LightDataAccess cache) {
}
@Override
- public void calculate(QuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFaceO, boolean shade) {
+ public void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direction cullFace, Direction lightFaceO, boolean shade) {
this.updateCachedData(pos.asLong());
int flags = quad.getFlags();
@@ -110,7 +110,7 @@ private void applyAlignedFullFace(AoNeighborInfo neighborInfo, BlockPos pos, Sim
* Calculates the light data for a grid-aligned quad that does not cover the entire block volume's face.
* Flags: IS_ALIGNED, IS_PARTIAL
*/
- private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
for (int i = 0; i < 4; i++) {
// Clamp the vertex positions to the block's boundaries to prevent weird errors in lighting
float cx = clamp(quad.getX(i));
@@ -124,13 +124,13 @@ private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, QuadView quad,
}
/**
- * This method is the same as {@link #applyNonParallelFace(AoNeighborInfo, QuadView, BlockPos, SimpleDirection,
+ * This method is the same as {@link #applyNonParallelFace(AoNeighborInfo, ModelQuadView, BlockPos, SimpleDirection,
* QuadLightData)} but with the check for a depth of approximately 0 removed. If the quad is parallel but not
* aligned, all of its vertices will have the same depth and this depth must be approximately greater than 0,
* meaning the check for 0 will always return false.
* Flags: !IS_ALIGNED, IS_PARALLEL
*/
- private void applyParallelFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyParallelFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
for (int i = 0; i < 4; i++) {
// Clamp the vertex positions to the block's boundaries to prevent weird errors in lighting
float cx = clamp(quad.getX(i));
@@ -157,7 +157,7 @@ private void applyParallelFace(AoNeighborInfo neighborInfo, QuadView quad, Block
/**
* Flags: !IS_ALIGNED, !IS_PARALLEL
*/
- private void applyNonParallelFace(AoNeighborInfo neighborInfo, QuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
+ private void applyNonParallelFace(AoNeighborInfo neighborInfo, ModelQuadView quad, BlockPos pos, SimpleDirection dir, QuadLightData out) {
for (int i = 0; i < 4; i++) {
// Clamp the vertex positions to the block's boundaries to prevent weird errors in lighting
float cx = clamp(quad.getX(i));
@@ -220,7 +220,7 @@ private void applyInsetPartialFaceVertex(BlockPos pos, SimpleDirection dir, floa
}
private void applySidedBrightness(QuadLightData out, Direction face, boolean shade) {
- float brightness = this.lightCache.getWorld().getShade(face, shade);
+ float brightness = this.lightCache.getRegion().getShade(face, shade);
float[] br = out.br;
for (int i = 0; i < br.length; i++) {
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/renderer/BlockRenderer.java b/src/main/java/net/vulkanmod/render/chunk/build/renderer/BlockRenderer.java
new file mode 100644
index 000000000..0c3607e71
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/renderer/BlockRenderer.java
@@ -0,0 +1,151 @@
+package net.vulkanmod.render.chunk.build.renderer;
+
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
+import net.fabricmc.fabric.api.util.TriState;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ItemBlockRenderTypes;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
+import net.minecraft.client.renderer.chunk.ChunkSectionLayer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Vec3i;
+import net.minecraft.world.level.BlockAndTintGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.levelgen.SingleThreadedRandomSource;
+import net.minecraft.world.phys.Vec3;
+import net.vulkanmod.Initializer;
+import net.vulkanmod.render.chunk.build.frapi.mesh.MutableQuadViewImpl;
+import net.vulkanmod.render.chunk.build.frapi.render.AbstractBlockRenderContext;
+import net.vulkanmod.render.chunk.build.light.LightPipeline;
+import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
+import net.vulkanmod.render.chunk.build.thread.BuilderResources;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
+import net.vulkanmod.render.model.quad.QuadUtils;
+import net.vulkanmod.render.model.quad.ModelQuadView;
+import net.vulkanmod.render.vertex.TerrainBufferBuilder;
+import net.vulkanmod.render.vertex.TerrainBuilder;
+import net.vulkanmod.render.vertex.TerrainRenderType;
+import net.vulkanmod.render.vertex.format.I32_SNorm;
+import net.vulkanmod.vulkan.util.ColorUtil;
+import org.joml.Vector3f;
+
+public class BlockRenderer extends AbstractBlockRenderContext {
+ private Vector3f pos;
+
+ private BuilderResources resources;
+ private TerrainBuilder terrainBuilder;
+
+ final boolean backFaceCulling = Initializer.CONFIG.backFaceCulling;
+
+ private TerrainRenderType renderType;
+
+ public void setResources(BuilderResources resources) {
+ this.resources = resources;
+ }
+
+ public BlockRenderer(LightPipeline flatLightPipeline, LightPipeline smoothLightPipeline) {
+ super();
+ this.setupLightPipelines(flatLightPipeline, smoothLightPipeline);
+
+ this.random = new SingleThreadedRandomSource(42L);
+ }
+
+ public void renderBlock(BlockState blockState, BlockPos blockPos, Vector3f pos) {
+ this.pos = pos;
+ this.blockPos = blockPos;
+ this.blockState = blockState;
+ this.random.setSeed(blockState.getSeed(blockPos));
+
+ TerrainRenderType renderType = TerrainRenderType.get(ItemBlockRenderTypes.getChunkRenderType(blockState));
+ renderType = TerrainRenderType.getRemapped(renderType);
+ this.renderType = renderType;
+ this.terrainBuilder = this.resources.builderPack.builder(renderType);
+ this.terrainBuilder.setBlockAttributes(blockState);
+
+ BlockStateModel model = Minecraft.getInstance().getBlockRenderer().getBlockModel(blockState);
+
+ BlockAndTintGetter renderRegion = this.renderRegion;
+ Vec3 offset = blockState.getOffset(blockPos);
+ pos.add((float) offset.x, (float) offset.y, (float) offset.z);
+
+ this.prepareForBlock(blockState, blockPos, blockState.getLightEmission() == 0);
+
+ model.emitQuads(this.getEmitter(), renderRegion, blockPos, blockState, this.random, this::isFaceCulled);
+ }
+
+ @Override
+ protected VertexConsumer getVertexConsumer(ChunkSectionLayer layer) {
+ return null;
+ }
+
+ protected void endRenderQuad(MutableQuadViewImpl quad) {
+ final TriState aoMode = quad.ambientOcclusion();
+ final boolean ao = this.useAO && (aoMode == TriState.TRUE || (aoMode == TriState.DEFAULT && this.defaultAO));
+ final boolean emissive = quad.emissive();
+ final boolean vanillaShade = quad.shadeMode() == ShadeMode.VANILLA;
+
+ TerrainBuilder terrainBuilder = getBufferBuilder(quad.renderLayer());
+
+ LightPipeline lightPipeline = ao ? this.smoothLightPipeline : this.flatLightPipeline;
+
+ tintQuad(quad);
+ shadeQuad(quad, lightPipeline, emissive, vanillaShade);
+ bufferQuad(terrainBuilder, this.pos, quad, this.quadLightData);
+ }
+
+ private TerrainBuilder getBufferBuilder(ChunkSectionLayer layer) {
+ if (layer == null) {
+ return this.terrainBuilder;
+ } else {
+ TerrainRenderType renderType = TerrainRenderType.get(layer);
+ renderType = TerrainRenderType.getRemapped(renderType);
+ TerrainBuilder bufferBuilder = this.resources.builderPack.builder(renderType);
+ bufferBuilder.setBlockAttributes(this.blockState);
+
+ return bufferBuilder;
+ }
+ }
+
+ public void bufferQuad(TerrainBuilder terrainBuilder, Vector3f pos, ModelQuadView quad, QuadLightData quadLightData) {
+ QuadFacing quadFacing = quad.getQuadFacing();
+
+ if (renderType == TerrainRenderType.TRANSLUCENT || !this.backFaceCulling) {
+ quadFacing = QuadFacing.UNDEFINED;
+ }
+
+ TerrainBufferBuilder bufferBuilder = terrainBuilder.getBufferBuilder(quadFacing.ordinal());
+
+ int packedNormal = quad.getNormal();
+
+ float[] brightnessArr = quadLightData.br;
+ int[] lights = quadLightData.lm;
+
+ // Rotate triangles if needed to fix AO anisotropy
+ int idx = QuadUtils.getIterationStartIdx(brightnessArr, lights);
+
+ bufferBuilder.ensureCapacity();
+
+ for (byte i = 0; i < 4; ++i) {
+ final float x = pos.x() + quad.getX(idx);
+ final float y = pos.y() + quad.getY(idx);
+ final float z = pos.z() + quad.getZ(idx);
+
+ final int quadColor = quad.getColor(idx);
+
+ int color = ColorUtil.ARGB.toRGBA(quadColor);
+
+ final int light = lights[idx];
+ final float u = quad.getU(idx);
+ final float v = quad.getV(idx);
+
+ bufferBuilder.vertex(x, y, z, color, u, v, light, packedNormal);
+
+ idx = (idx + 1) & 0b11;
+ }
+
+ }
+
+}
+
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/renderer/DefaultFluidRenderers.java b/src/main/java/net/vulkanmod/render/chunk/build/renderer/DefaultFluidRenderers.java
new file mode 100644
index 000000000..0fbbe5415
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/build/renderer/DefaultFluidRenderers.java
@@ -0,0 +1,17 @@
+package net.vulkanmod.render.chunk.build.renderer;
+
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
+
+public abstract class DefaultFluidRenderers {
+
+ private static final ReferenceOpenHashSet set = new ReferenceOpenHashSet<>();
+
+ public static void add(FluidRenderHandler handler) {
+ set.add(handler);
+ }
+
+ public static boolean has(FluidRenderHandler handler) {
+ return set.contains(handler);
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/LiquidRenderer.java b/src/main/java/net/vulkanmod/render/chunk/build/renderer/FluidRenderer.java
similarity index 83%
rename from src/main/java/net/vulkanmod/render/chunk/build/LiquidRenderer.java
rename to src/main/java/net/vulkanmod/render/chunk/build/renderer/FluidRenderer.java
index 4ae8eca4a..f29acae11 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/LiquidRenderer.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/renderer/FluidRenderer.java
@@ -1,8 +1,11 @@
-package net.vulkanmod.render.chunk.build;
+package net.vulkanmod.render.chunk.build.renderer;
+import com.mojang.blaze3d.vertex.VertexConsumer;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
+import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRendering;
import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
@@ -20,16 +23,18 @@
import net.vulkanmod.render.chunk.build.light.LightPipeline;
import net.vulkanmod.render.chunk.build.light.data.QuadLightData;
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
import net.vulkanmod.render.chunk.util.Util;
import net.vulkanmod.render.model.quad.ModelQuad;
import net.vulkanmod.render.model.quad.ModelQuadFlags;
import net.vulkanmod.render.model.quad.QuadUtils;
import net.vulkanmod.render.vertex.TerrainBufferBuilder;
-import net.vulkanmod.render.vertex.VertexUtil;
+import net.vulkanmod.render.vertex.TerrainRenderType;
+import net.vulkanmod.render.vertex.format.I32_SNorm;
import net.vulkanmod.vulkan.util.ColorUtil;
import org.joml.Vector3f;
-public class LiquidRenderer {
+public class FluidRenderer implements FluidRendering.DefaultRenderer {
private static final float MAX_FLUID_HEIGHT = 0.8888889F;
private final BlockPos.MutableBlockPos mBlockPos = new BlockPos.MutableBlockPos();
@@ -38,21 +43,41 @@ public class LiquidRenderer {
BuilderResources resources;
+ private final LightPipeline smoothLightPipeline;
+ private final LightPipeline flatLightPipeline;
+
private final int[] quadColors = new int[4];
+ public FluidRenderer(LightPipeline flatLightPipeline, LightPipeline smoothLightPipeline) {
+ this.smoothLightPipeline = smoothLightPipeline;
+ this.flatLightPipeline = flatLightPipeline;
+ }
+
public void setResources(BuilderResources resources) {
this.resources = resources;
}
- public void renderLiquid(BlockState blockState, FluidState fluidState, BlockPos blockPos, TerrainBufferBuilder vertexConsumer) {
- tessellate(blockState, fluidState, blockPos, vertexConsumer);
+ public void renderLiquid(BlockState blockState, FluidState fluidState, BlockPos blockPos) {
+ FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getType());
+
+ TerrainRenderType renderType = TerrainRenderType.get(ItemBlockRenderTypes.getRenderLayer(fluidState));
+ renderType = TerrainRenderType.getRemapped(renderType);
+ TerrainBufferBuilder bufferBuilder = this.resources.builderPack.builder(renderType).getBufferBuilder(QuadFacing.UNDEFINED.ordinal());
+
+ // Fallback to water/lava in case there's no handler
+ if (handler == null) {
+ boolean isLava = fluidState.is(FluidTags.LAVA);
+ handler = FluidRenderHandlerRegistry.INSTANCE.get(isLava ? Fluids.LAVA : Fluids.WATER);
+ }
+
+ FluidRendering.render(handler, this.resources.getRegion(),blockPos, bufferBuilder, blockState, fluidState, this);
}
private boolean isFaceOccludedByState(BlockGetter blockGetter, float h, Direction direction, BlockPos blockPos, BlockState blockState) {
- mBlockPos.set(blockPos).offset(Direction.DOWN.getNormal());
+ mBlockPos.set(blockPos).offset(Direction.DOWN.getUnitVec3i());
if (blockState.canOcclude()) {
- VoxelShape occlusionShape = blockState.getOcclusionShape(blockGetter, mBlockPos);
+ VoxelShape occlusionShape = blockState.getOcclusionShape();
if (occlusionShape == Shapes.block()) {
return direction != Direction.UP;
@@ -61,7 +86,7 @@ private boolean isFaceOccludedByState(BlockGetter blockGetter, float h, Directio
}
VoxelShape voxelShape = Shapes.box(0.0, 0.0, 0.0, 1.0, h, 1.0);
- return Shapes.blockOccudes(voxelShape, occlusionShape, direction);
+ return Shapes.blockOccludes(voxelShape, occlusionShape, direction);
} else {
return false;
}
@@ -85,12 +110,14 @@ public BlockState getAdjBlockState(BlockAndTintGetter blockAndTintGetter, int x,
return blockAndTintGetter.getBlockState(mBlockPos);
}
- public void tessellate(BlockState blockState, FluidState fluidState, BlockPos blockPos, TerrainBufferBuilder vertexConsumer) {
- BlockAndTintGetter region = this.resources.region;
+ public void render(FluidRenderHandler handler, BlockAndTintGetter world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
+ render(handler, blockState, fluidState, pos, (TerrainBufferBuilder) vertexConsumer);
+ }
- final FluidRenderHandler handler = getFluidRenderHandler(fluidState);
- int color = handler.getFluidColor(region, blockPos, fluidState);
+ public void render(FluidRenderHandler handler, BlockState blockState, FluidState fluidState, BlockPos blockPos, TerrainBufferBuilder bufferBuilder) {
+ BlockAndTintGetter region = this.resources.getRegion();
+ int color = handler.getFluidColor(region, blockPos, fluidState);
TextureAtlasSprite[] sprites = handler.getFluidSprites(region, blockPos, fluidState);
float r = ColorUtil.ARGB.unpackR(color);
@@ -102,7 +129,7 @@ public void tessellate(BlockState blockState, FluidState fluidState, BlockPos bl
final int posZ = blockPos.getZ();
boolean useAO = blockState.getLightEmission() == 0 && Minecraft.useAmbientOcclusion();
- LightPipeline lightPipeline = useAO ? this.resources.smoothLightPipeline : this.resources.flatLightPipeline;
+ LightPipeline lightPipeline = useAO ? this.smoothLightPipeline : this.flatLightPipeline;
BlockState downState = getAdjBlockState(region, posX, posY, posZ, Direction.DOWN);
BlockState upState = getAdjBlockState(region, posX, posY, posZ, Direction.UP);
@@ -137,14 +164,14 @@ public void tessellate(BlockState blockState, FluidState fluidState, BlockPos bl
seHeight = 1.0F;
swHeight = 1.0F;
} else {
- float s = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.NORTH.getNormal()), northState);
- float t = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.SOUTH.getNormal()), southState);
- float u = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.EAST.getNormal()), eastState);
- float v = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.WEST.getNormal()), westState);
- neHeight = this.calculateAverageHeight(region, fluid, height, s, u, mBlockPos.set(blockPos).offset(Direction.NORTH.getNormal()).offset(Direction.EAST.getNormal()));
- nwHeight = this.calculateAverageHeight(region, fluid, height, s, v, mBlockPos.set(blockPos).offset(Direction.NORTH.getNormal()).offset(Direction.WEST.getNormal()));
- seHeight = this.calculateAverageHeight(region, fluid, height, t, u, mBlockPos.set(blockPos).offset(Direction.SOUTH.getNormal()).offset(Direction.EAST.getNormal()));
- swHeight = this.calculateAverageHeight(region, fluid, height, t, v, mBlockPos.set(blockPos).offset(Direction.SOUTH.getNormal()).offset(Direction.WEST.getNormal()));
+ float s = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.NORTH.getUnitVec3i()), northState);
+ float t = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.SOUTH.getUnitVec3i()), southState);
+ float u = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.EAST.getUnitVec3i()), eastState);
+ float v = this.getHeight(region, fluid, mBlockPos.set(blockPos).offset(Direction.WEST.getUnitVec3i()), westState);
+ neHeight = this.calculateAverageHeight(region, fluid, height, s, u, mBlockPos.set(blockPos).offset(Direction.NORTH.getUnitVec3i()).offset(Direction.EAST.getUnitVec3i()));
+ nwHeight = this.calculateAverageHeight(region, fluid, height, s, v, mBlockPos.set(blockPos).offset(Direction.NORTH.getUnitVec3i()).offset(Direction.WEST.getUnitVec3i()));
+ seHeight = this.calculateAverageHeight(region, fluid, height, t, u, mBlockPos.set(blockPos).offset(Direction.SOUTH.getUnitVec3i()).offset(Direction.EAST.getUnitVec3i()));
+ swHeight = this.calculateAverageHeight(region, fluid, height, t, v, mBlockPos.set(blockPos).offset(Direction.SOUTH.getUnitVec3i()).offset(Direction.WEST.getUnitVec3i()));
}
float x0 = (posX & 15);
@@ -214,10 +241,10 @@ public void tessellate(BlockState blockState, FluidState fluidState, BlockPos bl
updateQuad(this.modelQuad, blockPos, lightPipeline, Direction.UP);
updateColor(r, g, b, brightness);
- putQuad(modelQuad, vertexConsumer, x0, y0, z0, false);
+ putQuad(modelQuad, bufferBuilder, x0, y0, z0, false);
if (fluidState.shouldRenderBackwardUpFace(region, blockPos.above())) {
- putQuad(modelQuad, vertexConsumer, x0, y0, z0, true);
+ putQuad(modelQuad, bufferBuilder, x0, y0, z0, true);
}
}
@@ -240,8 +267,7 @@ public void tessellate(BlockState blockState, FluidState fluidState, BlockPos bl
updateQuad(this.modelQuad, blockPos, lightPipeline, Direction.DOWN);
updateColor(r, g, b, brightness);
- putQuad(modelQuad, vertexConsumer, x0, y0, z0, false);
-
+ putQuad(modelQuad, bufferBuilder, x0, y0, z0, false);
}
modelQuad.setFlags(ModelQuadFlags.IS_PARALLEL | ModelQuadFlags.IS_ALIGNED);
@@ -347,26 +373,15 @@ public void tessellate(BlockState blockState, FluidState fluidState, BlockPos bl
updateQuad(this.modelQuad, blockPos, lightPipeline, direction);
updateColor(r, g, b, brightness);
- putQuad(modelQuad, vertexConsumer, x0, y0, z0, false);
+ putQuad(modelQuad, bufferBuilder, x0, y0, z0, false);
if (!isOverlay) {
- putQuad(modelQuad, vertexConsumer, x0, y0, z0, true);
+ putQuad(modelQuad, bufferBuilder, x0, y0, z0, true);
}
}
}
- private static FluidRenderHandler getFluidRenderHandler(FluidState fluidState) {
- FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getType());
-
- // Fallback to water in case no handler was found
- if (handler == null) {
- handler = FluidRenderHandlerRegistry.INSTANCE.get(Fluids.WATER);
- }
-
- return handler;
- }
-
private float calculateAverageHeight(BlockAndTintGetter blockAndTintGetter, Fluid fluid, float f, float g, float h, BlockPos blockPos) {
if (!(h >= 1.0F) && !(g >= 1.0F)) {
float[] fs = new float[2];
@@ -407,7 +422,7 @@ private float getHeight(BlockAndTintGetter blockAndTintGetter, Fluid fluid, Bloc
private float getHeight(BlockAndTintGetter blockAndTintGetter, Fluid fluid, BlockPos blockPos, BlockState adjBlockState) {
FluidState adjFluidState = adjBlockState.getFluidState();
if (fluid.isSame(adjFluidState.getType())) {
- BlockState blockState2 = blockAndTintGetter.getBlockState(blockPos.offset(Direction.UP.getNormal()));
+ BlockState blockState2 = blockAndTintGetter.getBlockState(blockPos.offset(Direction.UP.getUnitVec3i()));
return fluid.isSame(blockState2.getFluidState().getType()) ? 1.0F : adjFluidState.getOwnHeight();
} else {
return !adjBlockState.isSolid() ? 0.0F : -1.0f;
@@ -420,7 +435,7 @@ private int calculateNormal(ModelQuad quad) {
.cross(quad.getX(3), quad.getY(3), quad.getZ(3));
normal.normalize();
- return VertexUtil.packNormal(normal.x(), normal.y(), normal.z());
+ return I32_SNorm.packNormal(normal.x(), normal.y(), normal.z());
}
private void putQuad(ModelQuad quad, TerrainBufferBuilder bufferBuilder, float xOffset, float yOffset, float zOffset, boolean flip) {
@@ -455,11 +470,8 @@ private void setVertex(ModelQuad quad, int i, float x, float y, float z, float u
quad.setV(i, v);
}
- private void updateQuad(ModelQuad quad, BlockPos blockPos,
- LightPipeline lightPipeline, Direction dir) {
-
+ private void updateQuad(ModelQuad quad, BlockPos blockPos, LightPipeline lightPipeline, Direction dir) {
lightPipeline.calculate(quad, blockPos, resources.quadLightData, null, dir, false);
-
}
private void updateColor(float r, float g, float b, float brightness) {
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/task/BuildTask.java b/src/main/java/net/vulkanmod/render/chunk/build/task/BuildTask.java
index 6c104faa9..0da19e2e1 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/task/BuildTask.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/task/BuildTask.java
@@ -1,8 +1,8 @@
package net.vulkanmod.render.chunk.build.task;
import net.minecraft.client.Minecraft;
-import net.minecraft.client.renderer.ItemBlockRenderTypes;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
+import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState;
import net.minecraft.client.renderer.chunk.VisGraph;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.RenderShape;
@@ -13,13 +13,14 @@
import net.vulkanmod.Initializer;
import net.vulkanmod.render.chunk.RenderSection;
import net.vulkanmod.render.chunk.WorldRenderer;
-import net.vulkanmod.render.chunk.build.BlockRenderer;
-import net.vulkanmod.render.chunk.build.LiquidRenderer;
+import net.vulkanmod.render.chunk.build.renderer.BlockRenderer;
+import net.vulkanmod.render.chunk.build.renderer.FluidRenderer;
import net.vulkanmod.render.chunk.build.RenderRegion;
import net.vulkanmod.render.chunk.build.UploadBuffer;
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
import net.vulkanmod.render.chunk.build.thread.ThreadBuilderPack;
-import net.vulkanmod.render.vertex.TerrainBufferBuilder;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
+import net.vulkanmod.render.vertex.TerrainBuilder;
import net.vulkanmod.render.vertex.TerrainRenderType;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
@@ -94,7 +95,7 @@ private CompileResult compile(float camX, float camY, float camZ, BuilderResourc
BlockRenderer blockRenderer = builderResources.blockRenderer;
- LiquidRenderer liquidRenderer = builderResources.liquidRenderer;
+ FluidRenderer fluidRenderer = builderResources.fluidRenderer;
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
@@ -104,7 +105,7 @@ private CompileResult compile(float camX, float camY, float camZ, BuilderResourc
blockPos.set(section.xOffset() + x, section.yOffset() + y, section.zOffset() + z);
BlockState blockState = this.region.getBlockState(blockPos);
- if (blockState.isSolidRender(this.region, blockPos)) {
+ if (blockState.isSolidRender()) {
visGraph.setOpaque(blockPos);
}
@@ -116,44 +117,34 @@ private CompileResult compile(float camX, float camY, float camZ, BuilderResourc
}
FluidState fluidState = blockState.getFluidState();
- TerrainRenderType renderType;
- TerrainBufferBuilder bufferBuilder;
if (!fluidState.isEmpty()) {
- renderType = TerrainRenderType.get(ItemBlockRenderTypes.getRenderLayer(fluidState));
-
- bufferBuilder = getBufferBuilder(bufferBuilders, renderType);
- bufferBuilder.setBlockAttributes(blockState);
-
- liquidRenderer.renderLiquid(blockState, fluidState, blockPos, bufferBuilder);
+ fluidRenderer.renderLiquid(blockState, fluidState, blockPos);
}
if (blockState.getRenderShape() == RenderShape.MODEL) {
- renderType = TerrainRenderType.get(ItemBlockRenderTypes.getChunkRenderType(blockState));
-
- bufferBuilder = getBufferBuilder(bufferBuilders, renderType);
- bufferBuilder.setBlockAttributes(blockState);
-
pos.set(blockPos.getX() & 15, blockPos.getY() & 15, blockPos.getZ() & 15);
- blockRenderer.renderBlock(blockState, blockPos, pos, bufferBuilder);
+ blockRenderer.renderBlock(blockState, blockPos, pos);
}
}
}
}
- TerrainBufferBuilder translucentBufferBuilder = bufferBuilders.builder(TerrainRenderType.TRANSLUCENT);
- if (!translucentBufferBuilder.isCurrentBatchEmpty()) {
- translucentBufferBuilder.setupQuadSortingPoints();
- translucentBufferBuilder.setupQuadSorting(camX - (float) startBlockPos.getX(), camY - (float) startBlockPos.getY(), camZ - (float) startBlockPos.getZ());
- compileResult.transparencyState = translucentBufferBuilder.getSortState();
+ TerrainBuilder trasnlucentTerrainBuilder = bufferBuilders.builder(TerrainRenderType.TRANSLUCENT);
+ if (trasnlucentTerrainBuilder.getBufferBuilder(QuadFacing.UNDEFINED.ordinal()).getVertices() > 0) {
+ trasnlucentTerrainBuilder.setupQuadSortingPoints();
+ trasnlucentTerrainBuilder.setupQuadSorting(camX - (float) startBlockPos.getX(), camY - (float) startBlockPos.getY(), camZ - (float) startBlockPos.getZ());
+ compileResult.transparencyState = trasnlucentTerrainBuilder.getSortState();
}
for (TerrainRenderType renderType : TerrainRenderType.VALUES) {
- TerrainBufferBuilder.RenderedBuffer renderedBuffer = bufferBuilders.builder(renderType).end();
- if (renderedBuffer != null) {
- UploadBuffer uploadBuffer = new UploadBuffer(renderedBuffer);
- compileResult.renderedLayers.put(renderType, uploadBuffer);
- renderedBuffer.release();
- }
+ TerrainBuilder builder = bufferBuilders.builder(renderType);
+
+ TerrainBuilder.DrawState drawState = builder.endDrawing();
+
+ UploadBuffer uploadBuffer = new UploadBuffer(builder, drawState);
+ compileResult.renderedLayers.put(renderType, uploadBuffer);
+
+ builder.clear();
}
compileResult.visibilitySet = visGraph.resolve();
@@ -163,12 +154,12 @@ private CompileResult compile(float camX, float camY, float camZ, BuilderResourc
private void setupBufferBuilders(ThreadBuilderPack builderPack) {
for (TerrainRenderType renderType : TerrainRenderType.VALUES) {
- TerrainBufferBuilder bufferBuilder = builderPack.builder(renderType);
+ TerrainBuilder bufferBuilder = builderPack.builder(renderType);
bufferBuilder.begin();
}
}
- private TerrainBufferBuilder getBufferBuilder(ThreadBuilderPack bufferBuilders, TerrainRenderType renderType) {
+ private TerrainBuilder getTerrainBuilder(ThreadBuilderPack bufferBuilders, TerrainRenderType renderType) {
renderType = compactRenderTypes(renderType);
return bufferBuilders.builder(renderType);
}
@@ -191,10 +182,10 @@ private TerrainRenderType compactRenderTypes(TerrainRenderType renderType) {
}
private void handleBlockEntity(CompileResult compileResult, E blockEntity) {
- BlockEntityRenderer blockEntityRenderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(blockEntity);
+ BlockEntityRenderer blockEntityRenderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(blockEntity);
if (blockEntityRenderer != null) {
compileResult.blockEntities.add(blockEntity);
- if (blockEntityRenderer.shouldRenderOffScreen(blockEntity)) {
+ if (blockEntityRenderer.shouldRenderOffScreen()) {
compileResult.globalBlockEntities.add(blockEntity);
}
}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/task/ChunkTask.java b/src/main/java/net/vulkanmod/render/chunk/build/task/ChunkTask.java
index 13d416da6..c1acc33df 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/task/ChunkTask.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/task/ChunkTask.java
@@ -2,7 +2,6 @@
import net.vulkanmod.render.chunk.RenderSection;
import net.vulkanmod.render.chunk.build.RenderRegion;
-import net.vulkanmod.render.chunk.build.TaskDispatcher;
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/task/CompiledSection.java b/src/main/java/net/vulkanmod/render/chunk/build/task/CompiledSection.java
index c93de3a3c..e233ab0a2 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/task/CompiledSection.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/task/CompiledSection.java
@@ -3,7 +3,6 @@
import com.google.common.collect.Lists;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.vulkanmod.render.vertex.QuadSorter;
-import net.vulkanmod.render.vertex.TerrainBufferBuilder;
import org.jetbrains.annotations.Nullable;
import java.util.List;
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/task/SortTransparencyTask.java b/src/main/java/net/vulkanmod/render/chunk/build/task/SortTransparencyTask.java
index 5f804c1a5..f6d1a72d6 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/task/SortTransparencyTask.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/task/SortTransparencyTask.java
@@ -7,7 +7,7 @@
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
import net.vulkanmod.render.chunk.build.thread.ThreadBuilderPack;
import net.vulkanmod.render.vertex.QuadSorter;
-import net.vulkanmod.render.vertex.TerrainBufferBuilder;
+import net.vulkanmod.render.vertex.TerrainBuilder;
import net.vulkanmod.render.vertex.TerrainRenderType;
public class SortTransparencyTask extends ChunkTask {
@@ -35,21 +35,24 @@ public Result runTask(BuilderResources context) {
CompiledSection compiledSection = this.section.getCompiledSection();
QuadSorter.SortState transparencyState = compiledSection.transparencyState;
- TerrainBufferBuilder bufferBuilder = builderPack.builder(TerrainRenderType.TRANSLUCENT);
+ TerrainBuilder bufferBuilder = builderPack.builder(TerrainRenderType.TRANSLUCENT);
bufferBuilder.begin();
bufferBuilder.restoreSortState(transparencyState);
bufferBuilder.setupQuadSorting(x - (float) this.section.xOffset(), y - (float) this.section.yOffset(), z - (float) this.section.zOffset());
- TerrainBufferBuilder.RenderedBuffer renderedBuffer = bufferBuilder.end();
+ TerrainBuilder.DrawState drawState = bufferBuilder.endDrawing();
CompileResult compileResult = new CompileResult(this.section, false);
- UploadBuffer uploadBuffer = new UploadBuffer(renderedBuffer);
+ UploadBuffer uploadBuffer = new UploadBuffer(bufferBuilder, drawState);
compileResult.renderedLayers.put(TerrainRenderType.TRANSLUCENT, uploadBuffer);
- renderedBuffer.release();
+
+ bufferBuilder.reset();
if (this.cancelled.get()) {
+ compileResult.renderedLayers.values().forEach(UploadBuffer::release);
return Result.CANCELLED;
}
+
taskDispatcher.scheduleSectionUpdate(compileResult);
return Result.SUCCESSFUL;
}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/TaskDispatcher.java b/src/main/java/net/vulkanmod/render/chunk/build/task/TaskDispatcher.java
similarity index 80%
rename from src/main/java/net/vulkanmod/render/chunk/build/TaskDispatcher.java
rename to src/main/java/net/vulkanmod/render/chunk/build/task/TaskDispatcher.java
index ae43f3544..e78146d83 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/TaskDispatcher.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/task/TaskDispatcher.java
@@ -1,4 +1,4 @@
-package net.vulkanmod.render.chunk.build;
+package net.vulkanmod.render.chunk.build.task;
import com.google.common.collect.Queues;
import net.vulkanmod.render.chunk.ChunkArea;
@@ -6,12 +6,10 @@
import net.vulkanmod.render.chunk.RenderSection;
import net.vulkanmod.render.chunk.WorldRenderer;
import net.vulkanmod.render.chunk.buffer.DrawBuffers;
-import net.vulkanmod.render.chunk.build.task.ChunkTask;
-import net.vulkanmod.render.chunk.build.task.CompileResult;
-import net.vulkanmod.render.chunk.build.thread.ThreadBuilderPack;
+import net.vulkanmod.render.chunk.build.UploadBuffer;
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
+import net.vulkanmod.render.chunk.build.thread.ThreadBuilderPack;
import net.vulkanmod.render.vertex.TerrainRenderType;
-
import org.jetbrains.annotations.Nullable;
import java.util.Queue;
@@ -39,25 +37,30 @@ public void createThreads() {
}
public void createThreads(int n) {
- if(!this.stopThreads) {
+ if (!this.stopThreads) {
this.stopThreads();
}
this.stopThreads = false;
- if(this.resources != null) {
+ if (this.resources != null) {
for (BuilderResources resources : this.resources) {
- resources.clear();
+ resources.free();
}
}
+ // Auto select thread count
+ if (n == 0) {
+ n = Math.max((Runtime.getRuntime().availableProcessors() - 1) / 2, 1);
+ }
+
this.threads = new Thread[n];
this.resources = new BuilderResources[n];
for (int i = 0; i < n; i++) {
BuilderResources builderResources = new BuilderResources();
Thread thread = new Thread(() -> runTaskThread(builderResources),
- "Builder-" + i);
+ "Builder-" + i);
thread.setPriority(Thread.NORM_PRIORITY);
this.threads[i] = thread;
@@ -67,10 +70,10 @@ public void createThreads(int n) {
}
private void runTaskThread(BuilderResources builderResources) {
- while(!this.stopThreads) {
+ while (!this.stopThreads) {
ChunkTask task = this.pollTask();
- if(task == null)
+ if (task == null)
synchronized (this) {
try {
this.idleThreads++;
@@ -81,7 +84,7 @@ private void runTaskThread(BuilderResources builderResources) {
this.idleThreads--;
}
- if(task == null)
+ if (task == null)
continue;
task.runTask(builderResources);
@@ -89,12 +92,13 @@ private void runTaskThread(BuilderResources builderResources) {
}
public void schedule(ChunkTask chunkTask) {
- if(chunkTask == null)
+ if (chunkTask == null)
return;
if (chunkTask.highPriority) {
this.highPriorityTasks.offer(chunkTask);
- } else {
+ }
+ else {
this.lowPriorityTasks.offer(chunkTask);
}
@@ -107,14 +111,14 @@ public void schedule(ChunkTask chunkTask) {
private ChunkTask pollTask() {
ChunkTask task = this.highPriorityTasks.poll();
- if(task == null)
+ if (task == null)
task = this.lowPriorityTasks.poll();
return task;
}
public void stopThreads() {
- if(this.stopThreads)
+ if (this.stopThreads)
return;
this.stopThreads = true;
@@ -136,7 +140,7 @@ public void stopThreads() {
public boolean updateSections() {
CompileResult result;
boolean flag = false;
- while((result = this.compileResults.poll()) != null) {
+ while ((result = this.compileResults.poll()) != null) {
flag = true;
doSectionUpdate(result);
}
@@ -155,18 +159,22 @@ private void doSectionUpdate(CompileResult compileResult) {
// Check if area has been dismissed before uploading
ChunkAreaManager chunkAreaManager = WorldRenderer.getInstance().getChunkAreaManager();
- if (chunkAreaManager.getChunkArea(renderArea.index) != renderArea)
+ if (chunkAreaManager.getChunkArea(renderArea.index) != renderArea) {
+ compileResult.renderedLayers.values()
+ .forEach(UploadBuffer::release);
return;
+ }
- if(compileResult.fullUpdate) {
+ if (compileResult.fullUpdate) {
var renderLayers = compileResult.renderedLayers;
- for(TerrainRenderType renderType : TerrainRenderType.VALUES) {
+ for (TerrainRenderType renderType : TerrainRenderType.VALUES) {
UploadBuffer uploadBuffer = renderLayers.get(renderType);
- if(uploadBuffer != null) {
+ if (uploadBuffer != null) {
drawBuffers.upload(section, uploadBuffer, renderType);
- } else {
- section.getDrawParameters(renderType).reset(renderArea, renderType);
+ }
+ else {
+ section.resetDrawParameters(renderType);
}
}
@@ -178,17 +186,19 @@ private void doSectionUpdate(CompileResult compileResult) {
}
}
- public boolean isIdle() { return this.idleThreads == this.threads.length && this.compileResults.isEmpty(); }
+ public boolean isIdle() {
+ return this.idleThreads == this.threads.length && this.compileResults.isEmpty();
+ }
public void clearBatchQueue() {
- while(!this.highPriorityTasks.isEmpty()) {
+ while (!this.highPriorityTasks.isEmpty()) {
ChunkTask chunkTask = this.highPriorityTasks.poll();
if (chunkTask != null) {
chunkTask.cancel();
}
}
- while(!this.lowPriorityTasks.isEmpty()) {
+ while (!this.lowPriorityTasks.isEmpty()) {
ChunkTask chunkTask = this.lowPriorityTasks.poll();
if (chunkTask != null) {
chunkTask.cancel();
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/thread/BuilderResources.java b/src/main/java/net/vulkanmod/render/chunk/build/thread/BuilderResources.java
index 41a05af95..83418108e 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/thread/BuilderResources.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/thread/BuilderResources.java
@@ -2,10 +2,10 @@
import net.vulkanmod.Initializer;
import net.vulkanmod.render.chunk.RenderSection;
-import net.vulkanmod.render.chunk.build.BlockRenderer;
-import net.vulkanmod.render.chunk.build.LiquidRenderer;
+import net.vulkanmod.render.chunk.build.renderer.BlockRenderer;
+import net.vulkanmod.render.chunk.build.renderer.FluidRenderer;
import net.vulkanmod.render.chunk.build.RenderRegion;
-import net.vulkanmod.render.chunk.build.TintCache;
+import net.vulkanmod.render.chunk.build.color.TintCache;
import net.vulkanmod.render.chunk.build.light.LightMode;
import net.vulkanmod.render.chunk.build.light.LightPipeline;
import net.vulkanmod.render.chunk.build.light.data.ArrayLightDataCache;
@@ -16,41 +16,45 @@
public class BuilderResources {
public final ThreadBuilderPack builderPack = new ThreadBuilderPack();
- public final BlockRenderer blockRenderer = new BlockRenderer();
- public final LiquidRenderer liquidRenderer = new LiquidRenderer();
-
public final TintCache tintCache = new TintCache();
- public RenderRegion region;
+ public final BlockRenderer blockRenderer;
+ public final FluidRenderer fluidRenderer;
public final ArrayLightDataCache lightDataCache = new ArrayLightDataCache();
public final QuadLightData quadLightData = new QuadLightData();
- public final LightPipeline smoothLightPipeline;
- public final LightPipeline flatLightPipeline;
+ private RenderRegion region;
private int totalBuildTime = 0, buildCount = 0;
public BuilderResources() {
- this.flatLightPipeline = new FlatLightPipeline(lightDataCache);
+ LightPipeline flatLightPipeline = new FlatLightPipeline(this.lightDataCache);
+
+ LightPipeline smoothLightPipeline;
+ if (Initializer.CONFIG.ambientOcclusion == LightMode.SUB_BLOCK) {
+ smoothLightPipeline = new NewSmoothLightPipeline(lightDataCache);
+ }
+ else {
+ smoothLightPipeline = new SmoothLightPipeline(lightDataCache);
+ }
+
+ this.blockRenderer = new BlockRenderer(flatLightPipeline, smoothLightPipeline);
+ this.fluidRenderer = new FluidRenderer(flatLightPipeline, smoothLightPipeline);
- if(Initializer.CONFIG.ambientOcclusion == LightMode.SUB_BLOCK)
- this.smoothLightPipeline = new NewSmoothLightPipeline(lightDataCache);
- else
- this.smoothLightPipeline = new SmoothLightPipeline(lightDataCache);
+ this.blockRenderer.setResources(this);
+ this.fluidRenderer.setResources(this);
}
public void update(RenderRegion region, RenderSection renderSection) {
this.region = region;
+ this.blockRenderer.prepareForWorld(region, true);
- lightDataCache.reset(region, renderSection.xOffset(), renderSection.yOffset(), renderSection.zOffset());
-
- blockRenderer.setResources(this);
- liquidRenderer.setResources(this);
+ this.lightDataCache.reset(region, renderSection.xOffset(), renderSection.yOffset(), renderSection.zOffset());
}
- public void clear() {
- builderPack.clearAll();
+ public void free() {
+ builderPack.freeAll();
}
public void updateBuildStats(int buildTime) {
@@ -58,6 +62,10 @@ public void updateBuildStats(int buildTime) {
this.totalBuildTime += buildTime;
}
+ public RenderRegion getRegion() {
+ return region;
+ }
+
public int getTotalBuildTime() {
return totalBuildTime;
}
diff --git a/src/main/java/net/vulkanmod/render/chunk/build/thread/ThreadBuilderPack.java b/src/main/java/net/vulkanmod/render/chunk/build/thread/ThreadBuilderPack.java
index 5517ce4ea..86ca915a6 100644
--- a/src/main/java/net/vulkanmod/render/chunk/build/thread/ThreadBuilderPack.java
+++ b/src/main/java/net/vulkanmod/render/chunk/build/thread/ThreadBuilderPack.java
@@ -1,7 +1,11 @@
package net.vulkanmod.render.chunk.build.thread;
-import net.vulkanmod.render.vertex.TerrainBufferBuilder;
+import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import net.vulkanmod.render.PipelineManager;
+import net.vulkanmod.render.vertex.CustomVertexFormat;
+import net.vulkanmod.render.vertex.TerrainBuilder;
import net.vulkanmod.render.vertex.TerrainRenderType;
+import net.vulkanmod.render.vertex.VertexBuilder;
import java.util.Arrays;
import java.util.EnumMap;
@@ -9,20 +13,27 @@
import java.util.function.Function;
public class ThreadBuilderPack {
- private static Function terrainBuilderConstructor;
+ private static Function terrainBuilderConstructor;
public static void defaultTerrainBuilderConstructor() {
- terrainBuilderConstructor = renderType -> new TerrainBufferBuilder(TerrainRenderType.getRenderType(renderType).bufferSize());
+ terrainBuilderConstructor = renderType -> {
+ int size = TerrainRenderType.getLayer(renderType)
+ .bufferSize() / DefaultVertexFormat.BLOCK.getVertexSize();
+
+ boolean compressedFormat = PipelineManager.terrainVertexFormat == CustomVertexFormat.COMPRESSED_TERRAIN;
+ VertexBuilder vertexBuilder = compressedFormat ? new VertexBuilder.CompressedVertexBuilder() : new VertexBuilder.DefaultVertexBuilder();
+ return new TerrainBuilder(size, vertexBuilder);
+ };
}
- public static void setTerrainBuilderConstructor(Function constructor) {
+ public static void setTerrainBuilderConstructor(Function constructor) {
terrainBuilderConstructor = constructor;
}
- private final Map builders;
+ private final Map builders;
public ThreadBuilderPack() {
- var map = new EnumMap(TerrainRenderType.class);
+ var map = new EnumMap(TerrainRenderType.class);
Arrays.stream(TerrainRenderType.values()).forEach(
terrainRenderType -> map.put(terrainRenderType,
terrainBuilderConstructor.apply(terrainRenderType))
@@ -30,12 +41,12 @@ public ThreadBuilderPack() {
builders = map;
}
- public TerrainBufferBuilder builder(TerrainRenderType renderType) {
+ public TerrainBuilder builder(TerrainRenderType renderType) {
return this.builders.get(renderType);
}
- public void clearAll() {
- this.builders.values().forEach(TerrainBufferBuilder::clear);
+ public void freeAll() {
+ this.builders.values().forEach(TerrainBuilder::free);
}
}
diff --git a/src/main/java/net/vulkanmod/render/chunk/cull/QuadFacing.java b/src/main/java/net/vulkanmod/render/chunk/cull/QuadFacing.java
new file mode 100644
index 000000000..292b69fd1
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/chunk/cull/QuadFacing.java
@@ -0,0 +1,66 @@
+package net.vulkanmod.render.chunk.cull;
+
+import net.minecraft.core.Direction;
+import net.minecraft.util.Mth;
+import net.vulkanmod.render.vertex.format.I32_SNorm;
+import org.joml.Vector3f;
+
+public enum QuadFacing {
+ X_POS,
+ Y_POS,
+ Z_POS,
+ X_NEG,
+ Z_NEG,
+ UNDEFINED,
+ Y_NEG;
+
+ public static final QuadFacing[] VALUES = QuadFacing.values();
+ public static final int COUNT = VALUES.length;
+
+ public static QuadFacing fromDirection(Direction direction) {
+ return switch (direction) {
+ case DOWN -> Y_NEG;
+ case UP -> Y_POS;
+ case NORTH -> Z_NEG;
+ case SOUTH -> Z_POS;
+ case WEST -> X_NEG;
+ case EAST -> X_POS;
+ };
+ }
+
+ public static QuadFacing fromNormal(int packedNormal) {
+ final float x = I32_SNorm.unpackX(packedNormal);
+ final float y = I32_SNorm.unpackY(packedNormal);
+ final float z = I32_SNorm.unpackZ(packedNormal);
+
+ return fromNormal(x, y, z);
+ }
+
+ public static QuadFacing fromNormal(Vector3f normal) {
+ return fromNormal(normal.x(), normal.y(), normal.z());
+ }
+
+ public static QuadFacing fromNormal(float x, float y, float z) {
+ final float absX = Math.abs(x);
+ final float absY = Math.abs(y);
+ final float absZ = Math.abs(z);
+
+ float sum = absX + absY + absZ;
+
+ if (Mth.equal(sum, 1.0f)) {
+ if (Mth.equal(absX, 1.0f)) {
+ return x > 0.0f ? QuadFacing.X_POS : QuadFacing.X_NEG;
+ }
+
+ if (Mth.equal(absY, 1.0f)) {
+ return y > 0.0f ? QuadFacing.Y_POS : QuadFacing.Y_NEG;
+ }
+
+ if (Mth.equal(absZ, 1.0f)) {
+ return z > 0.0f ? QuadFacing.Z_POS : QuadFacing.Z_NEG;
+ }
+ }
+
+ return QuadFacing.UNDEFINED;
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/chunk/graph/SectionGraph.java b/src/main/java/net/vulkanmod/render/chunk/graph/SectionGraph.java
index f3758feda..58b2d156e 100644
--- a/src/main/java/net/vulkanmod/render/chunk/graph/SectionGraph.java
+++ b/src/main/java/net/vulkanmod/render/chunk/graph/SectionGraph.java
@@ -7,13 +7,14 @@
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.util.Mth;
+import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.vulkanmod.Initializer;
import net.vulkanmod.interfaces.FrustumMixed;
import net.vulkanmod.render.chunk.*;
import net.vulkanmod.render.chunk.build.RenderRegionBuilder;
-import net.vulkanmod.render.chunk.build.TaskDispatcher;
+import net.vulkanmod.render.chunk.build.task.TaskDispatcher;
import net.vulkanmod.render.chunk.frustum.VFrustum;
import net.vulkanmod.render.chunk.util.AreaSetQueue;
import net.vulkanmod.render.chunk.util.ResettableQueue;
@@ -55,13 +56,14 @@ public SectionGraph(Level level, SectionGrid sectionGrid, TaskDispatcher taskDis
public void update(Camera camera, Frustum frustum, boolean spectator) {
Profiler profiler = Profiler.getMainProfiler();
+ ProfilerFiller mcProfiler = net.minecraft.util.profiling.Profiler.get();
BlockPos blockpos = camera.getBlockPosition();
- this.minecraft.getProfiler().popPush("update");
+ mcProfiler.popPush("update");
boolean flag = this.minecraft.smartCull;
- if (spectator && this.level.getBlockState(blockpos).isSolidRender(this.level, blockpos)) {
+ if (spectator && this.level.getBlockState(blockpos).isSolidRender()) {
flag = false;
}
@@ -70,7 +72,7 @@ public void update(Camera camera, Frustum frustum, boolean spectator) {
this.sectionGrid.updateFrustumVisibility(this.frustum);
profiler.pop();
- this.minecraft.getProfiler().push("partial_update");
+ mcProfiler.push("partial_update");
this.initUpdate();
this.initializeQueueForFullUpdate(camera);
@@ -82,7 +84,7 @@ public void update(Camera camera, Frustum frustum, boolean spectator) {
this.scheduleRebuilds();
- this.minecraft.getProfiler().pop();
+ mcProfiler.pop();
}
private void initializeQueueForFullUpdate(Camera camera) {
@@ -91,8 +93,8 @@ private void initializeQueueForFullUpdate(Camera camera) {
RenderSection renderSection = this.sectionGrid.getSectionAtBlockPos(blockpos);
if (renderSection == null) {
- boolean flag = blockpos.getY() > this.level.getMinBuildHeight();
- int y = flag ? this.level.getMaxBuildHeight() - 8 : this.level.getMinBuildHeight() + 8;
+ boolean flag = blockpos.getY() > this.level.getMinY();
+ int y = flag ? this.level.getMaxY() - 8 : this.level.getMinY() + 8;
int x = Mth.floor(vec3.x / 16.0D) * 16;
int z = Mth.floor(vec3.z / 16.0D) * 16;
diff --git a/src/main/java/net/vulkanmod/render/chunk/util/CircularIntList.java b/src/main/java/net/vulkanmod/render/chunk/util/CircularIntList.java
index e81145a91..d8964eaa9 100644
--- a/src/main/java/net/vulkanmod/render/chunk/util/CircularIntList.java
+++ b/src/main/java/net/vulkanmod/render/chunk/util/CircularIntList.java
@@ -1,78 +1,67 @@
package net.vulkanmod.render.chunk.util;
import org.apache.commons.lang3.Validate;
-import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Spliterator;
-import java.util.function.Consumer;
public class CircularIntList {
- private int[] list;
- private final int startIndex;
+ private final int size;
+ private final int[] list;
+ private int startIndex;
- private int[] previous;
- private int[] next;
+ private final OwnIterator iterator;
+ private final RangeIterator rangeIterator;
- private OwnIterator iterator;
+ public CircularIntList(int size) {
+ this.size = size;
+ this.list = new int[size + 2];
- public CircularIntList(int size, int startIndex) {
- this.startIndex = startIndex;
-
- this.generateList(size);
+ this.iterator = new OwnIterator();
+ this.rangeIterator = new RangeIterator();
}
- private void generateList(int size) {
- int[] list = new int[size];
+ public void updateStartIdx(int startIndex) {
+ int[] list = this.list;
+ this.startIndex = startIndex;
- this.previous = new int[size];
- this.next = new int[size];
+ list[0] = -1;
+ list[size + 1] = -1;
- int k = 0;
+ int k = 1;
for(int i = startIndex; i < size; ++i) {
list[k] = i;
-
++k;
}
for(int i = 0; i < startIndex; ++i) {
list[k] = i;
++k;
}
-
- this.previous[0] = -1;
- System.arraycopy(list, 0, this.previous, 1, size - 1);
-
- this.next[size - 1] = -1;
- System.arraycopy(list, 1, this.next, 0, size - 1);
-
- this.list = list;
}
public int getNext(int i) {
- return this.next[i];
+ return this.list[i + 1];
}
public int getPrevious(int i) {
- return this.previous[i];
+ return this.list[i - 1];
}
public OwnIterator iterator() {
- return new OwnIterator();
+ return this.iterator;
}
- public RangeIterator rangeIterator(int startIndex, int endIndex) {
- return new RangeIterator(startIndex, endIndex);
+ public RangeIterator getRangeIterator(int startIndex, int endIndex) {
+ this.rangeIterator.update(startIndex, endIndex);
+ return this.rangeIterator;
}
- public void restartIterator() {
- this.iterator.restart();
+ public RangeIterator createRangeIterator() {
+ return new RangeIterator();
}
public class OwnIterator implements Iterator {
- private int currentIndex = -1;
- private final int maxIndex = list.length - 1;
+ private int currentIndex = 0;
+ private final int maxIndex = size;
@Override
public boolean hasNext() {
@@ -90,38 +79,32 @@ public int getCurrentIndex() {
}
public void restart() {
- this.currentIndex = -1;
+ this.currentIndex = 0;
}
}
public class RangeIterator implements Iterator {
private int currentIndex;
- private final int startIndex;
- private final int maxIndex;
+ private int startIndex;
+ private int endIndex;
- public RangeIterator(int startIndex, int endIndex) {
- this.startIndex = startIndex;
- this.maxIndex = endIndex;
- Validate.isTrue(this.maxIndex < list.length, "Beyond max size");
+ public void update(int startIndex, int endIndex) {
+ Validate.isTrue(endIndex < list.length, "Beyond max size");
+ this.startIndex = startIndex + 1;
+ this.endIndex = endIndex + 1;
this.restart();
}
@Override
public boolean hasNext() {
- return currentIndex < maxIndex;
+ return currentIndex < endIndex;
}
@Override
public Integer next() {
currentIndex++;
- try {
- return list[currentIndex];
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException();
- }
-
+ return list[currentIndex];
}
public int getCurrentIndex() {
diff --git a/src/main/java/net/vulkanmod/render/chunk/util/Util.java b/src/main/java/net/vulkanmod/render/chunk/util/Util.java
index c37ddd0ff..caa063410 100644
--- a/src/main/java/net/vulkanmod/render/chunk/util/Util.java
+++ b/src/main/java/net/vulkanmod/render/chunk/util/Util.java
@@ -44,11 +44,11 @@ public static int flooredLog(int v) {
return log;
}
- public static int align(int i, int alignment) {
+ public static long align(long l, int alignment) {
if (alignment == 0)
- return i;
+ return l;
- int r = i % alignment;
- return r != 0 ? i + alignment - r : i;
+ long r = l % alignment;
+ return r != 0 ? l + alignment - r : l;
}
}
diff --git a/src/main/java/net/vulkanmod/render/engine/EGlProgram.java b/src/main/java/net/vulkanmod/render/engine/EGlProgram.java
new file mode 100644
index 000000000..11613d4f3
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/EGlProgram.java
@@ -0,0 +1,86 @@
+package net.vulkanmod.render.engine;
+
+import com.google.common.collect.Sets;
+import com.mojang.blaze3d.opengl.*;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.logging.LogUtils;
+import net.vulkanmod.vulkan.shader.Pipeline;
+import net.vulkanmod.vulkan.shader.descriptor.UBO;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import java.util.*;
+
+public class EGlProgram {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public static Set BUILT_IN_UNIFORMS = Sets.newHashSet("Projection", "Lighting", "Fog", "Globals");
+ public static EGlProgram INVALID_PROGRAM = new EGlProgram(-1, "invalid");
+ private final Map uniformsByName = new HashMap();
+ private final int programId;
+ private final String debugLabel;
+
+ public EGlProgram(int i, String string) {
+ this.programId = i;
+ this.debugLabel = string;
+ }
+
+ public void setupUniforms(Pipeline pipeline, List uniformDescriptions, List samplers) {
+ int i = 0;
+ int j = 0;
+
+ for (RenderPipeline.UniformDescription uniformDescription : uniformDescriptions) {
+ String name = uniformDescription.name();
+
+ Uniform uniform = switch (uniformDescription.type()) {
+ case UNIFORM_BUFFER -> {
+ UBO ubo = pipeline.getUBO(name);
+
+ if (ubo == null) {
+ yield null;
+ }
+
+ int binding = ubo.binding;
+ yield new Uniform.Ubo(binding);
+ }
+ case TEXEL_BUFFER -> {
+ int binding = i++;
+ yield new Uniform.Utb(binding, 0, Objects.requireNonNull(uniformDescription.textureFormat()));
+ }
+ };
+
+ this.uniformsByName.put(name, uniform);
+ }
+
+ for (String samplerName : samplers) {
+ var imageDescriptor = pipeline.getImageDescriptor(samplerName);
+ int binding = imageDescriptor.getBinding();
+ int imageIdx = imageDescriptor.imageIdx;
+ this.uniformsByName.put(samplerName, new Uniform.Sampler(binding, imageIdx));
+ }
+
+ }
+
+ @Nullable
+ public Uniform getUniform(String string) {
+ RenderSystem.assertOnRenderThread();
+ return this.uniformsByName.get(string);
+ }
+
+ public int getProgramId() {
+ return this.programId;
+ }
+
+ public String toString() {
+ return this.debugLabel;
+ }
+
+ public String getDebugLabel() {
+ return this.debugLabel;
+ }
+
+ public Map getUniforms() {
+ return this.uniformsByName;
+ }
+
+}
diff --git a/src/main/java/net/vulkanmod/render/engine/VkCommandEncoder.java b/src/main/java/net/vulkanmod/render/engine/VkCommandEncoder.java
new file mode 100644
index 000000000..94d1e64e5
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkCommandEncoder.java
@@ -0,0 +1,909 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.buffers.GpuBuffer;
+import com.mojang.blaze3d.buffers.GpuBufferSlice;
+import com.mojang.blaze3d.buffers.GpuFence;
+import com.mojang.blaze3d.opengl.*;
+import com.mojang.blaze3d.pipeline.BlendFunction;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.platform.DepthTestFunction;
+import com.mojang.blaze3d.platform.NativeImage;
+import com.mojang.blaze3d.systems.CommandEncoder;
+import com.mojang.blaze3d.systems.RenderPass;
+import com.mojang.blaze3d.textures.GpuTexture;
+import com.mojang.blaze3d.textures.GpuTextureView;
+import com.mojang.blaze3d.vertex.VertexFormat;
+import com.mojang.logging.LogUtils;
+import net.minecraft.client.Minecraft;
+import net.minecraft.util.ARGB;
+import net.vulkanmod.gl.VkGlFramebuffer;
+import net.vulkanmod.gl.VkGlTexture;
+import net.vulkanmod.interfaces.shader.ExtendedRenderPipeline;
+import net.vulkanmod.vulkan.Renderer;
+import net.vulkanmod.vulkan.Synchronization;
+import net.vulkanmod.vulkan.VRenderSystem;
+import net.vulkanmod.vulkan.Vulkan;
+import net.vulkanmod.vulkan.device.DeviceManager;
+import net.vulkanmod.vulkan.framebuffer.Framebuffer;
+import net.vulkanmod.vulkan.memory.buffer.StagingBuffer;
+import net.vulkanmod.vulkan.queue.GraphicsQueue;
+import net.vulkanmod.vulkan.shader.GraphicsPipeline;
+import net.vulkanmod.vulkan.shader.Pipeline;
+import net.vulkanmod.vulkan.shader.descriptor.ImageDescriptor;
+import net.vulkanmod.vulkan.shader.descriptor.UBO;
+import net.vulkanmod.vulkan.texture.ImageUtil;
+import net.vulkanmod.vulkan.texture.VTextureSelector;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.opengl.*;
+import org.lwjgl.system.MemoryStack;
+import org.lwjgl.system.MemoryUtil;
+import org.lwjgl.vulkan.*;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+import static org.lwjgl.system.MemoryStack.stackPush;
+import static org.lwjgl.vulkan.VK10.*;
+
+public class VkCommandEncoder implements CommandEncoder {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private final VkGpuDevice device;
+
+ @Nullable
+ private RenderPipeline lastPipeline;
+ private boolean inRenderPass;
+
+ @Nullable
+ private EGlProgram lastProgram;
+
+ private int framebufferId = VkGlFramebuffer.genFramebufferId();
+
+ protected VkCommandEncoder(VkGpuDevice glDevice) {
+ this.device = glDevice;
+ }
+
+ @Override
+ public RenderPass createRenderPass(Supplier supplier, GpuTextureView gpuTexture, OptionalInt optionalInt) {
+ return this.createRenderPass(supplier, gpuTexture, optionalInt, null, OptionalDouble.empty());
+ }
+
+ @Override
+ public RenderPass createRenderPass(Supplier supplier, GpuTextureView colorTexture, OptionalInt optionalInt, @Nullable GpuTextureView depthTexture, OptionalDouble optionalDouble) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before creating a new one!");
+ } else {
+ if (optionalDouble.isPresent() && depthTexture == null) {
+ LOGGER.warn("Depth clear value was provided but no depth texture is being used");
+ }
+
+ if (Minecraft.getInstance().getMainRenderTarget().getColorTexture() == colorTexture.texture()) {
+ Renderer.getInstance().getMainPass().rebindMainTarget();
+
+ int j = 0;
+ if (optionalInt.isPresent()) {
+ int k = optionalInt.getAsInt();
+ GL11.glClearColor(ARGB.redFloat(k), ARGB.greenFloat(k), ARGB.blueFloat(k), ARGB.alphaFloat(k));
+ j |= 16384;
+ }
+
+ if (depthTexture != null && optionalDouble.isPresent()) {
+ GL11.glClearDepth(optionalDouble.getAsDouble());
+ j |= 256;
+ }
+
+ if (j != 0) {
+ GlStateManager._disableScissorTest();
+ GlStateManager._depthMask(true);
+ GlStateManager._colorMask(true, true, true, true);
+ GlStateManager._clear(j);
+ }
+
+ return new VkRenderPass(this, depthTexture != null);
+ }
+
+ if (colorTexture.isClosed()) {
+ throw new IllegalStateException("Color texture is closed");
+ } else if (depthTexture != null && depthTexture.isClosed()) {
+ throw new IllegalStateException("Depth texture is closed");
+ } else {
+ this.inRenderPass = true;
+ GpuTexture depthTexture1 = depthTexture != null ? depthTexture.texture() : null;
+ VkFbo fbo = ((VkGpuTexture)colorTexture.texture()).getFbo(depthTexture1);
+ fbo.bind();
+
+ int j = 0;
+ if (optionalInt.isPresent()) {
+ int k = optionalInt.getAsInt();
+ GL11.glClearColor(ARGB.redFloat(k), ARGB.greenFloat(k), ARGB.blueFloat(k), ARGB.alphaFloat(k));
+ j |= 16384;
+ }
+
+ if (depthTexture != null && optionalDouble.isPresent()) {
+ GL11.glClearDepth(optionalDouble.getAsDouble());
+ j |= 256;
+ }
+
+ if (j != 0) {
+ GlStateManager._disableScissorTest();
+ GlStateManager._depthMask(true);
+ GlStateManager._colorMask(true, true, true, true);
+ GlStateManager._clear(j);
+ }
+
+ GlStateManager._viewport(0, 0, colorTexture.getWidth(0), colorTexture.getHeight(0));
+ this.lastPipeline = null;
+ return new VkRenderPass(this, depthTexture != null);
+ }
+ }
+
+ }
+
+ @Override
+ public void clearColorTexture(GpuTexture colorAttachment, int clearColor) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before creating a new one!");
+ }
+ else if (Renderer.isRecording()) {
+ if (Minecraft.getInstance().getMainRenderTarget().getColorTexture() == colorAttachment) {
+ Renderer.getInstance().getMainPass().rebindMainTarget();
+
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+ Renderer.clearAttachments(0x4000);
+ }
+ else {
+ VkGpuTexture vkGpuTexture = (VkGpuTexture) colorAttachment;
+ VkGlFramebuffer.bindFramebuffer(GL30.GL_FRAMEBUFFER, framebufferId);
+ VkGlFramebuffer.framebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, vkGpuTexture.glId(), 0);
+
+ VkGlFramebuffer.beginRendering(VkGlFramebuffer.getFramebuffer(framebufferId));
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+ Renderer.clearAttachments(0x4000);
+ Renderer.getInstance().endRenderPass();
+
+ VkFbo fbo = ((VkGpuTexture)colorAttachment).getFbo(null);
+
+ ((VkGpuTexture) colorAttachment).setClearColor(clearColor);
+
+ Framebuffer boundFramebuffer = Renderer.getInstance().getBoundFramebuffer();
+ if (boundFramebuffer != null && boundFramebuffer.getColorAttachment() == ((VkGpuTexture) colorAttachment).getVulkanImage()) {
+ fbo.clearAttachments();
+ }
+ }
+ }
+ else {
+ GraphicsQueue graphicsQueue = DeviceManager.getGraphicsQueue();
+ var commandBuffer = graphicsQueue.getCommandBuffer();
+ VkGpuTexture vkGpuTexture = (VkGpuTexture) colorAttachment;
+
+ VkGlFramebuffer glFramebuffer = VkGlFramebuffer.getFramebuffer(this.framebufferId);
+ glFramebuffer.setAttachmentTexture(GL30.GL_COLOR_ATTACHMENT0, vkGpuTexture.glId());
+ glFramebuffer.create();
+
+ Framebuffer framebuffer = glFramebuffer.getFramebuffer();
+ var renderPass = glFramebuffer.getRenderPass();
+ try (MemoryStack stack = stackPush()) {
+ framebuffer.beginRenderPass(commandBuffer.handle, renderPass, stack);
+ }
+
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+ Renderer.clearAttachments(commandBuffer.handle, 0x4000, 0, 0, framebuffer.getWidth(), framebuffer.getHeight());
+ renderPass.endRenderPass(commandBuffer.handle);
+
+ long fence = graphicsQueue.submitCommands(commandBuffer);
+ Synchronization.waitFence(fence);
+ }
+ }
+
+ @Override
+ public void clearColorAndDepthTextures(GpuTexture colorAttachment, int clearColor, GpuTexture depthAttachment, double clearDepth) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before creating a new one!");
+ }
+ else {
+ if (Minecraft.getInstance().getMainRenderTarget().getColorTexture() == colorAttachment) {
+ Renderer.getInstance().getMainPass().rebindMainTarget();
+
+ VRenderSystem.clearDepth(clearDepth);
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+ Renderer.clearAttachments(0x4100);
+ }
+ else {
+ VkFbo fbo = ((VkGpuTexture)colorAttachment).getFbo(depthAttachment);
+
+ ((VkGpuTexture) colorAttachment).setClearColor(clearColor);
+ ((VkGpuTexture) depthAttachment).setDepthClearValue((float) clearDepth);
+
+ Framebuffer boundFramebuffer = Renderer.getInstance().getBoundFramebuffer();
+ if (boundFramebuffer != null && boundFramebuffer.getColorAttachment() == ((VkGpuTexture) colorAttachment).getVulkanImage()
+ && boundFramebuffer.getDepthAttachment() == ((VkGpuTexture) depthAttachment).getVulkanImage())
+ {
+ fbo.clearAttachments();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void clearColorAndDepthTextures(GpuTexture colorAttachment, int clearColor, GpuTexture depthAttachment, double clearDepth, int x0, int y0, int width, int height) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before creating a new one!");
+ } else {
+ VRenderSystem.clearDepth(clearDepth);
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+
+ int framebufferHeight = colorAttachment.getHeight(0);
+ y0 = framebufferHeight - height - y0;
+
+ Framebuffer boundFramebuffer = Renderer.getInstance().getBoundFramebuffer();
+ if (boundFramebuffer != null && boundFramebuffer.getColorAttachment() == ((VkGpuTexture) colorAttachment).getVulkanImage()
+ && boundFramebuffer.getDepthAttachment() == ((VkGpuTexture) depthAttachment).getVulkanImage())
+ {
+ Renderer.clearAttachments(0x4100, x0, y0, width, height);
+ }
+ else {
+ // TODO
+// throw new IllegalStateException();
+ }
+ }
+ }
+
+ @Override
+ public void clearDepthTexture(GpuTexture depthAttachment, double clearDepth) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before creating a new one!");
+ }
+ else {
+ Framebuffer boundFramebuffer = Renderer.getInstance().getBoundFramebuffer();
+ if (boundFramebuffer != null && boundFramebuffer.getDepthAttachment() == ((VkGpuTexture) depthAttachment).getVulkanImage()) {
+ VRenderSystem.clearDepth(clearDepth);
+ Renderer.clearAttachments(0x100);
+ }
+ else {
+ ((VkGpuTexture) depthAttachment).setDepthClearValue((float) clearDepth);
+ }
+ }
+ }
+
+ @Override
+ public void writeToBuffer(GpuBufferSlice gpuBufferSlice, ByteBuffer byteBuffer) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else {
+ VkGpuBuffer vkGpuBuffer = (VkGpuBuffer) gpuBufferSlice.buffer();
+ if (vkGpuBuffer.closed) {
+ throw new IllegalStateException("Buffer already closed");
+ }
+ else {
+ int size = byteBuffer.remaining();
+ if (size + gpuBufferSlice.offset() > vkGpuBuffer.size()) {
+ throw new IllegalArgumentException(
+ "Cannot write more data than this buffer can hold (attempting to write " + size + " bytes at offset " + gpuBufferSlice.offset() + " to " + gpuBufferSlice.length() + " slice size)"
+ );
+ } else {
+ int dstOffset = gpuBufferSlice.offset();
+
+ var commandBuffer = Renderer.getInstance().getTransferCb();
+
+ StagingBuffer stagingBuffer = Vulkan.getStagingBuffer();
+ stagingBuffer.copyBuffer(size, byteBuffer);
+
+ long srcOffset = stagingBuffer.getOffset();
+
+ try (MemoryStack stack = MemoryStack.stackPush()) {
+ if (!commandBuffer.isRecording()) {
+ commandBuffer.begin(stack);
+ }
+
+ VkBufferCopy.Buffer copyRegion = VkBufferCopy.calloc(1, stack);
+ copyRegion.size(size);
+ copyRegion.srcOffset(srcOffset);
+ copyRegion.dstOffset(dstOffset);
+
+ vkCmdCopyBuffer(commandBuffer.handle, stagingBuffer.getId(), vkGpuBuffer.buffer.getId(), copyRegion);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public GpuBuffer.MappedView mapBuffer(GpuBuffer gpuBuffer, boolean readable, boolean writable) {
+ return this.mapBuffer(gpuBuffer.slice(), readable, writable);
+ }
+
+ @Override
+ public GpuBuffer.MappedView mapBuffer(GpuBufferSlice gpuBufferSlice, boolean readable, boolean writable) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else {
+ VkGpuBuffer gpuBuffer = (VkGpuBuffer)(gpuBufferSlice.buffer());
+ if (gpuBuffer.closed) {
+ throw new IllegalStateException("Buffer already closed");
+ } else if (!readable && !writable) {
+ throw new IllegalArgumentException("At least read or write must be true");
+ } else if (readable && (gpuBuffer.usage() & 1) == 0) {
+ throw new IllegalStateException("Buffer is not readable");
+ } else if (writable && (gpuBuffer.usage() & 2) == 0) {
+ throw new IllegalStateException("Buffer is not writable");
+ } else if (gpuBufferSlice.offset() + gpuBufferSlice.length() > gpuBuffer.size()) {
+ throw new IllegalArgumentException(
+ "Cannot map more data than this buffer can hold (attempting to map "
+ + gpuBufferSlice.length()
+ + " bytes at offset "
+ + gpuBufferSlice.offset()
+ + " from "
+ + gpuBuffer.size()
+ + " size buffer)"
+ );
+ } else {
+ int i = 0;
+ if (readable) {
+ i |= 1;
+ }
+
+ if (writable) {
+ i |= 34;
+ }
+
+ ByteBuffer byteBuffer = MemoryUtil.memByteBuffer(gpuBuffer.getBuffer().getDataPtr() + gpuBufferSlice.offset(), gpuBufferSlice.length());
+ return new VkGpuBuffer.MappedView(0, byteBuffer);
+ }
+ }
+ }
+
+ public void copyToBuffer(GpuBufferSlice gpuBufferSlice, GpuBufferSlice gpuBufferSlice2) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else {
+ VkGpuBuffer vkGpuBuffer = (VkGpuBuffer) gpuBufferSlice.buffer();
+ if (vkGpuBuffer.closed) {
+ throw new IllegalStateException("Source buffer already closed");
+ } else if ((vkGpuBuffer.usage() & 8) == 0) {
+ throw new IllegalStateException("Source buffer needs USAGE_COPY_DST to be a destination for a copy");
+ } else {
+ VkGpuBuffer vkGpuBuffer2 = (VkGpuBuffer) gpuBufferSlice2.buffer();
+ if (vkGpuBuffer2.closed) {
+ throw new IllegalStateException("Target buffer already closed");
+ } else if ((vkGpuBuffer2.usage() & 8) == 0) {
+ throw new IllegalStateException("Target buffer needs USAGE_COPY_DST to be a destination for a copy");
+ } else if (gpuBufferSlice.length() != gpuBufferSlice2.length()) {
+ int var6 = gpuBufferSlice.length();
+ throw new IllegalArgumentException("Cannot copy from slice of size " + var6 + " to slice of size " + gpuBufferSlice2.length() + ", they must be equal");
+ } else if (gpuBufferSlice.offset() + gpuBufferSlice.length() > vkGpuBuffer.size()) {
+ int var5 = gpuBufferSlice.length();
+ throw new IllegalArgumentException("Cannot copy more data than the source buffer holds (attempting to copy " + var5 + " bytes at offset " + gpuBufferSlice.offset() + " from " + vkGpuBuffer.size() + " size buffer)");
+ } else if (gpuBufferSlice2.offset() + gpuBufferSlice2.length() > vkGpuBuffer2.size()) {
+ int var10002 = gpuBufferSlice2.length();
+ throw new IllegalArgumentException("Cannot copy more data than the target buffer can hold (attempting to copy " + var10002 + " bytes at offset " + gpuBufferSlice2.offset() + " to " + vkGpuBuffer2.size() + " size buffer)");
+ } else {
+// this.device.directStateAccess().copyBufferSubData(vkGpuBuffer.handle, vkGpuBuffer2.handle, gpuBufferSlice.offset(), gpuBufferSlice2.offset(), gpuBufferSlice.length());
+// vkGpuBuffer.buffer.copyBuffer(byteBuffer, byteBuffer.remaining(), gpuBufferSlice.offset());
+
+ // TODO
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void writeToTexture(GpuTexture gpuTexture, NativeImage nativeImage) {
+ int i = gpuTexture.getWidth(0);
+ int j = gpuTexture.getHeight(0);
+ if (nativeImage.getWidth() != i || nativeImage.getHeight() != j) {
+ throw new IllegalArgumentException(
+ "Cannot replace texture of size " + i + "x" + j + " with image of size " + nativeImage.getWidth() + "x" + nativeImage.getHeight()
+ );
+ } else if (gpuTexture.isClosed()) {
+ throw new IllegalStateException("Destination texture is closed");
+ } else {
+ this.writeToTexture(gpuTexture, nativeImage, 0, 0, 0, 0, i, j, 0, 0);
+ }
+ }
+
+ @Override
+ public void writeToTexture(GpuTexture gpuTexture, NativeImage nativeImage, int level, int arrayLayer, int xOffset, int yOffset, int width, int height, int unpackSkipPixels, int unpackSkipRows) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else if (level >= 0 && level < gpuTexture.getMipLevels()) {
+ if (unpackSkipPixels + width > nativeImage.getWidth() || unpackSkipRows + height > nativeImage.getHeight()) {
+ throw new IllegalArgumentException(
+ "Copy source ("
+ + nativeImage.getWidth()
+ + "x"
+ + nativeImage.getHeight()
+ + ") is not large enough to read a rectangle of "
+ + width
+ + "x"
+ + height
+ + " from "
+ + unpackSkipPixels
+ + "x"
+ + unpackSkipRows
+ );
+ } else if (xOffset + width > gpuTexture.getWidth(level) || yOffset + height > gpuTexture.getHeight(level)) {
+ throw new IllegalArgumentException(
+ "Dest texture (" + width + "x" + height + ") is not large enough to write a rectangle of " + width + "x" + height + " at " + xOffset + "x" + yOffset + " (at mip level " + level + ")"
+ );
+ } else if (gpuTexture.isClosed()) {
+ throw new IllegalStateException("Destination texture is closed");
+ } else {
+ VTextureSelector.setActiveTexture(0);
+ var glTexture = VkGlTexture.getTexture(((GlTexture) gpuTexture).glId());
+// VTextureSelector.bindTexture(((VkGpuTexture) gpuTexture).getVulkanImage());
+ VTextureSelector.bindTexture(glTexture.getVulkanImage());
+ VTextureSelector.uploadSubTexture(level, arrayLayer, width, height, xOffset, yOffset, unpackSkipRows, unpackSkipPixels, nativeImage.getWidth(), nativeImage.getPointer());
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid mipLevel " + level + ", must be >= 0 and < " + gpuTexture.getMipLevels());
+ }
+ }
+
+ @Override
+ public void writeToTexture(GpuTexture gpuTexture, ByteBuffer byteBuffer, NativeImage.Format format, int level, int j, int xOffset, int yOffset, int width, int height) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else if (level >= 0 && level < gpuTexture.getMipLevels()) {
+ if (width * height * format.components() > byteBuffer.remaining()) {
+ throw new IllegalArgumentException(
+ "Copy would overrun the source buffer (remaining length of " + byteBuffer.remaining() + ", but copy is " + width + "x" + height + " of format " + format + ")"
+ );
+ } else if (xOffset + width > gpuTexture.getWidth(level) || yOffset + height > gpuTexture.getHeight(level)) {
+ throw new IllegalArgumentException(
+ "Dest texture ("
+ + gpuTexture.getWidth(level)
+ + "x"
+ + gpuTexture.getHeight(level)
+ + ") is not large enough to write a rectangle of "
+ + width
+ + "x"
+ + height
+ + " at "
+ + xOffset
+ + "x"
+ + yOffset
+ );
+ } else if (gpuTexture.isClosed()) {
+ throw new IllegalStateException("Destination texture is closed");
+ } else if ((gpuTexture.usage() & 1) == 0) {
+ throw new IllegalStateException("Color texture must have USAGE_COPY_DST to be a destination for a write");
+ } else if (j >= gpuTexture.getDepthOrLayers()) {
+ throw new UnsupportedOperationException("Depth or layer is out of range, must be >= 0 and < " + gpuTexture.getDepthOrLayers());
+ }
+ else {
+ GlStateManager._bindTexture(((VkGpuTexture)gpuTexture).id);
+
+ GlStateManager._pixelStore(3314, width);
+ GlStateManager._pixelStore(3316, 0);
+ GlStateManager._pixelStore(3315, 0);
+ GlStateManager._pixelStore(3317, format.components());
+ GlStateManager._texSubImage2D(3553, level, xOffset, yOffset, width, height, GlConst.toGl(format), 5121, byteBuffer);
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid mipLevel, must be >= 0 and < " + gpuTexture.getMipLevels());
+ }
+ }
+
+ @Override
+ public void copyTextureToBuffer(GpuTexture gpuTexture, GpuBuffer gpuBuffer, int i, Runnable runnable, int j) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else {
+ this.copyTextureToBuffer(gpuTexture, gpuBuffer, i, runnable, j, 0, 0, gpuTexture.getWidth(j), gpuTexture.getHeight(j));
+ }
+ }
+
+ @Override
+ public void copyTextureToBuffer(GpuTexture gpuTexture, GpuBuffer gpuBuffer, int dstOffset, Runnable runnable, int mipLevel, int xOffset, int yOffset, int width, int height) {
+ VkGpuBuffer vkGpuBuffer = (VkGpuBuffer) gpuBuffer;
+ VkGpuTexture vkGpuTexture = (VkGpuTexture) gpuTexture;
+
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else if (mipLevel >= 0 && mipLevel < gpuTexture.getMipLevels()) {
+ if (gpuTexture.getWidth(mipLevel) * gpuTexture.getHeight(mipLevel) * vkGpuTexture.getVulkanImage().formatSize + dstOffset > gpuBuffer.size()) {
+ throw new IllegalArgumentException(
+ "Buffer of size "
+ + gpuBuffer.size()
+ + " is not large enough to hold "
+ + width
+ + "x"
+ + height
+ + " pixels ("
+ + vkGpuTexture.getVulkanImage().formatSize
+ + " bytes each) starting from offset "
+ + dstOffset
+ );
+ }
+ else if (xOffset + width > gpuTexture.getWidth(mipLevel) || yOffset + height > gpuTexture.getHeight(mipLevel)) {
+ throw new IllegalArgumentException(
+ "Copy source texture ("
+ + gpuTexture.getWidth(mipLevel)
+ + "x"
+ + gpuTexture.getHeight(mipLevel)
+ + ") is not large enough to read a rectangle of "
+ + width
+ + "x"
+ + height
+ + " from "
+ + xOffset
+ + ","
+ + yOffset
+ );
+ } else if (gpuTexture.isClosed()) {
+ throw new IllegalStateException("Source texture is closed");
+ } else if (gpuBuffer.isClosed()) {
+ throw new IllegalStateException("Destination buffer is closed");
+ } else {
+ ImageUtil.copyImageToBuffer(vkGpuTexture.getVulkanImage(), vkGpuBuffer.getBuffer(), mipLevel, width, height, xOffset, yOffset, dstOffset, width, height);
+
+ runnable.run();
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid mipLevel " + mipLevel + ", must be >= 0 and < " + gpuTexture.getMipLevels());
+ }
+ }
+
+ @Override
+ public void copyTextureToTexture(GpuTexture gpuTexture, GpuTexture gpuTexture2, int mipLevel, int j, int k, int l, int m, int n, int o) {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else if (mipLevel >= 0 && mipLevel < gpuTexture.getMipLevels() && mipLevel < gpuTexture2.getMipLevels()) {
+ if (j + n > gpuTexture2.getWidth(mipLevel) || k + o > gpuTexture2.getHeight(mipLevel)) {
+ throw new IllegalArgumentException(
+ "Dest texture ("
+ + gpuTexture2.getWidth(mipLevel)
+ + "x"
+ + gpuTexture2.getHeight(mipLevel)
+ + ") is not large enough to write a rectangle of "
+ + n
+ + "x"
+ + o
+ + " at "
+ + j
+ + "x"
+ + k
+ );
+ } else if (l + n > gpuTexture.getWidth(mipLevel) || m + o > gpuTexture.getHeight(mipLevel)) {
+ throw new IllegalArgumentException(
+ "Source texture ("
+ + gpuTexture.getWidth(mipLevel)
+ + "x"
+ + gpuTexture.getHeight(mipLevel)
+ + ") is not large enough to read a rectangle of "
+ + n
+ + "x"
+ + o
+ + " at "
+ + l
+ + "x"
+ + m
+ );
+ } else if (gpuTexture.isClosed()) {
+ throw new IllegalStateException("Source texture is closed");
+ } else if (gpuTexture2.isClosed()) {
+ throw new IllegalStateException("Destination texture is closed");
+ } else {
+ // TODO implement
+ }
+ } else {
+ throw new IllegalArgumentException("Invalid mipLevel " + mipLevel + ", must be >= 0 and < " + gpuTexture.getMipLevels() + " and < " + gpuTexture2.getMipLevels());
+ }
+ }
+
+ @Override
+ public GpuFence createFence() {
+ if (this.inRenderPass) {
+ throw new IllegalStateException("Close the existing render pass before performing additional commands");
+ } else {
+// throw new UnsupportedOperationException();
+ // TODO
+ return new GpuFence() {
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public boolean awaitCompletion(long l) {
+ return true;
+ }
+ };
+ }
+ }
+
+ @Override
+ public void presentTexture(GpuTextureView gpuTexture) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void executeDrawMultiple(
+ VkRenderPass renderPass,
+ Collection> collection,
+ @Nullable GpuBuffer gpuBuffer,
+ @Nullable VertexFormat.IndexType indexType,
+ Collection collection2,
+ T object
+ ) {
+ if (this.trySetup(renderPass)) {
+ if (indexType == null) {
+ indexType = VertexFormat.IndexType.SHORT;
+ }
+
+ Pipeline pipeline = ExtendedRenderPipeline.of(renderPass.getPipeline()).getPipeline();
+
+ for (RenderPass.Draw draw : collection) {
+ VertexFormat.IndexType indexType2 = draw.indexType() == null ? indexType : draw.indexType();
+ renderPass.setIndexBuffer(draw.indexBuffer() == null ? gpuBuffer : draw.indexBuffer(), indexType2);
+ renderPass.setVertexBuffer(draw.slot(), draw.vertexBuffer());
+
+ if (GlRenderPass.VALIDATION) {
+ if (renderPass.indexBuffer == null) {
+ throw new IllegalStateException("Missing index buffer");
+ }
+
+ if (renderPass.indexBuffer.isClosed()) {
+ throw new IllegalStateException("Index buffer has been closed!");
+ }
+
+ if (renderPass.vertexBuffers[0] == null) {
+ throw new IllegalStateException("Missing vertex buffer at slot 0");
+ }
+
+ if (renderPass.vertexBuffers[0].isClosed()) {
+ throw new IllegalStateException("Vertex buffer at slot 0 has been closed!");
+ }
+ }
+
+ BiConsumer biConsumer = draw.uniformUploaderConsumer();
+ if (biConsumer != null) {
+ biConsumer.accept(object, (string, gpuBufferSlice) -> {
+ EGlProgram glProgram = ExtendedRenderPipeline.of(renderPass.pipeline).getProgram();
+ if (glProgram.getUniform(string) instanceof Uniform.Ubo ubo) {
+
+ int blockBinding;
+ try {
+ blockBinding = ubo.blockBinding();
+ } catch (Throwable var7) {
+ throw new MatchException(var7.toString(), var7);
+ }
+
+ // TODO
+// GL32.glBindBufferRange(35345, blockBinding, ((GlBuffer)gpuBufferSlice.buffer()).handle, (long)gpuBufferSlice.offset(), (long)gpuBufferSlice.length());
+ }
+ });
+
+ Renderer.getInstance().uploadAndBindUBOs(pipeline);
+ }
+
+ this.drawFromBuffers(renderPass, 0, draw.firstIndex(), draw.indexCount(), indexType2, renderPass.pipeline, 1);
+ }
+ }
+ }
+
+ protected void executeDraw(VkRenderPass renderPass, int i, int j, int k, @Nullable VertexFormat.IndexType indexType, int l) {
+ if (this.trySetup(renderPass)) {
+ if (GlRenderPass.VALIDATION) {
+ if (indexType != null) {
+ if (renderPass.indexBuffer == null) {
+ throw new IllegalStateException("Missing index buffer");
+ }
+
+ if (renderPass.indexBuffer.isClosed()) {
+ throw new IllegalStateException("Index buffer has been closed!");
+ }
+ }
+
+ if (renderPass.vertexBuffers[0] == null) {
+ throw new IllegalStateException("Missing vertex buffer at slot 0");
+ }
+
+ if (renderPass.vertexBuffers[0].isClosed()) {
+ throw new IllegalStateException("Vertex buffer at slot 0 has been closed!");
+ }
+ }
+
+ this.drawFromBuffers(renderPass, i, j, k, indexType, renderPass.pipeline, l);
+ }
+ }
+
+ public void drawFromBuffers(VkRenderPass renderPass, int vertexOffset, int firstIndex, int vertexCount,
+ @Nullable VertexFormat.IndexType indexType, RenderPipeline renderPipeline, int instanceCount)
+ {
+ if (instanceCount < 1) {
+ instanceCount = 1;
+ }
+ if (vertexOffset < 0) {
+ vertexOffset = 0;
+ }
+
+ VkCommandBuffer vkCommandBuffer = Renderer.getCommandBuffer();
+ VkGpuBuffer vertexBuffer = (VkGpuBuffer)renderPass.vertexBuffers[0];
+ try (MemoryStack stack = stackPush()) {
+ if (vertexBuffer != null) {
+ VK11.vkCmdBindVertexBuffers(vkCommandBuffer, 0, stack.longs(vertexBuffer.buffer.getId()), stack.longs(0));
+ }
+
+ if (renderPass.indexBuffer != null) {
+ VkGpuBuffer indexBuffer = (VkGpuBuffer)renderPass.indexBuffer;
+
+ int vkIndexType = switch (indexType) {
+ case SHORT -> VK_INDEX_TYPE_UINT16;
+ case INT -> VK_INDEX_TYPE_UINT32;
+ };
+
+ VK11.vkCmdBindIndexBuffer(vkCommandBuffer, indexBuffer.buffer.getId(), 0, vkIndexType);
+ VK11.vkCmdDrawIndexed(vkCommandBuffer, vertexCount, instanceCount, firstIndex, vertexOffset, 0);
+ }
+ else {
+ var autoIndexBuffer = Renderer.getDrawer().getAutoIndexBuffer(renderPipeline.getVertexFormatMode(), vertexCount);
+ if (autoIndexBuffer != null) {
+ int indexCount = autoIndexBuffer.getIndexCount(vertexCount);
+ VK11.vkCmdBindIndexBuffer(vkCommandBuffer, autoIndexBuffer.getIndexBuffer().getId(), 0, autoIndexBuffer.getIndexBuffer().indexType.value);
+ VK11.vkCmdDrawIndexed(vkCommandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, 0);
+ }
+ else {
+ VK11.vkCmdDraw(vkCommandBuffer, vertexCount, instanceCount, vertexOffset, 0);
+ }
+ }
+ }
+ }
+
+ public boolean trySetup(VkRenderPass renderPass) {
+ if (VkRenderPass.VALIDATION) {
+ if (renderPass.pipeline == null) {
+ throw new IllegalStateException("Can't draw without a render pipeline");
+ }
+
+ for (RenderPipeline.UniformDescription uniformDescription : renderPass.pipeline.getUniforms()) {
+ Object object = renderPass.uniforms.get(uniformDescription.name());
+ if (object == null && !GlProgram.BUILT_IN_UNIFORMS.contains(uniformDescription.name())) {
+ throw new IllegalStateException("Missing uniform " + uniformDescription.name() + " (should be " + uniformDescription.type() + ")");
+ }
+ }
+
+ }
+
+ applyPipelineState(renderPass.pipeline);
+ setupUniforms(renderPass);
+
+ if (renderPass.isScissorEnabled()) {
+ GlStateManager._enableScissorTest();
+ GlStateManager._scissorBox(
+ renderPass.getScissorX(), renderPass.getScissorY(), renderPass.getScissorWidth(), renderPass.getScissorHeight()
+ );
+ } else {
+ GlStateManager._disableScissorTest();
+ }
+
+ return bindPipeline(renderPass.pipeline);
+ }
+
+ public void setupUniforms(VkRenderPass renderPass) {
+ RenderPipeline renderPipeline = renderPass.pipeline;
+ EGlProgram glProgram = ExtendedRenderPipeline.of(renderPass.pipeline).getProgram();
+ Pipeline pipeline = ExtendedRenderPipeline.of(renderPass.pipeline).getPipeline();
+
+ for (UBO ubo : pipeline.getBuffers()) {
+ String uniformName = ubo.name;
+ Uniform uniform = glProgram.getUniform(uniformName);
+
+ GpuBufferSlice gpuBufferSlice = renderPass.uniforms.get(uniformName);
+
+ // In case uniform buffer is not set, fallback to global buffer
+ if (gpuBufferSlice == null) {
+ ubo.setUseGlobalBuffer(true);
+ ubo.setUpdate(true);
+ continue;
+ }
+
+ VkGpuBuffer gpuBuffer = (VkGpuBuffer) gpuBufferSlice.buffer();
+
+ assert ubo != null;
+ ubo.setUseGlobalBuffer(false);
+ ubo.getBufferSlice().set(gpuBuffer.buffer, gpuBufferSlice.offset(), gpuBufferSlice.length());
+ }
+
+ for (ImageDescriptor imageDescriptor : pipeline.getImageDescriptors()) {
+ String uniformName = imageDescriptor.name;
+ int samplerIndex = imageDescriptor.imageIdx;
+
+ VkTextureView textureView = (VkTextureView) renderPass.samplers.get(uniformName);
+ if (textureView == null) {
+ continue;
+ }
+
+ VkGpuTexture gpuTexture = textureView.texture();
+ if (gpuTexture.isClosed()) {
+ continue;
+ }
+
+ GlStateManager._activeTexture(33984 + samplerIndex);
+ GlStateManager._bindTexture(gpuTexture.id);
+
+ GlStateManager._texParameter(GL11.GL_TEXTURE_2D, 33084, textureView.baseMipLevel());
+ GlStateManager._texParameter(GL11.GL_TEXTURE_2D, 33085, textureView.baseMipLevel() + textureView.mipLevels() - 1);
+ gpuTexture.flushModeChanges();
+ }
+
+ }
+
+ public boolean bindPipeline(RenderPipeline renderPipeline) {
+ Pipeline pipeline = ExtendedRenderPipeline.of(renderPipeline).getPipeline();
+
+ if (pipeline == null) {
+ return false;
+ }
+
+ Renderer renderer = Renderer.getInstance();
+ renderer.bindGraphicsPipeline((GraphicsPipeline) pipeline);
+// VTextureSelector.bindShaderTextures(pipeline);
+
+ renderer.uploadAndBindUBOs(pipeline);
+
+ return true;
+ }
+
+ public void applyPipelineState(RenderPipeline renderPipeline) {
+ if (this.lastPipeline != renderPipeline) {
+ this.lastPipeline = renderPipeline;
+ if (renderPipeline.getDepthTestFunction() != DepthTestFunction.NO_DEPTH_TEST) {
+ GlStateManager._enableDepthTest();
+ GlStateManager._depthFunc(GlConst.toGl(renderPipeline.getDepthTestFunction()));
+ } else {
+ GlStateManager._disableDepthTest();
+ }
+
+ if (renderPipeline.isCull()) {
+ GlStateManager._enableCull();
+ } else {
+ GlStateManager._disableCull();
+ }
+
+ if (renderPipeline.getBlendFunction().isPresent()) {
+ GlStateManager._enableBlend();
+ BlendFunction blendFunction = renderPipeline.getBlendFunction().get();
+ GlStateManager._blendFuncSeparate(
+ GlConst.toGl(blendFunction.sourceColor()),
+ GlConst.toGl(blendFunction.destColor()),
+ GlConst.toGl(blendFunction.sourceAlpha()),
+ GlConst.toGl(blendFunction.destAlpha())
+ );
+ } else {
+ GlStateManager._disableBlend();
+ }
+
+ GlStateManager._polygonMode(1032, GlConst.toGl(renderPipeline.getPolygonMode()));
+ GlStateManager._depthMask(renderPipeline.isWriteDepth());
+ GlStateManager._colorMask(renderPipeline.isWriteColor(), renderPipeline.isWriteColor(), renderPipeline.isWriteColor(), renderPipeline.isWriteAlpha());
+ if (renderPipeline.getDepthBiasConstant() == 0.0F && renderPipeline.getDepthBiasScaleFactor() == 0.0F) {
+ GlStateManager._disablePolygonOffset();
+ } else {
+ GlStateManager._polygonOffset(renderPipeline.getDepthBiasScaleFactor(), renderPipeline.getDepthBiasConstant());
+ GlStateManager._enablePolygonOffset();
+ }
+
+ switch (renderPipeline.getColorLogic()) {
+ case NONE:
+ GlStateManager._disableColorLogicOp();
+ break;
+ case OR_REVERSE:
+ GlStateManager._enableColorLogicOp();
+ GlStateManager._logicOp(5387);
+ }
+
+ VRenderSystem.setPrimitiveTopologyGL(GlConst.toGl(renderPipeline.getVertexFormatMode()));
+ }
+ }
+
+ public void finishRenderPass() {
+ this.inRenderPass = false;
+ }
+
+ protected VkGpuDevice getDevice() {
+ return this.device;
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/engine/VkDebugLabel.java b/src/main/java/net/vulkanmod/render/engine/VkDebugLabel.java
new file mode 100644
index 000000000..60ac40491
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkDebugLabel.java
@@ -0,0 +1,40 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.opengl.*;
+import com.mojang.logging.LogUtils;
+import java.util.Set;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import org.slf4j.Logger;
+
+@Environment(EnvType.CLIENT)
+public class VkDebugLabel {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ public void applyLabel(VkGpuBuffer glBuffer) {
+ }
+
+ public void applyLabel(VkGpuTexture glTexture) {
+ }
+
+ public void applyLabel(GlShaderModule glShaderModule) {
+ }
+
+ public void applyLabel(GlProgram glProgram) {
+ }
+
+ public void applyLabel(VertexArrayCache.VertexArray vertexArray) {
+ }
+
+ public static VkDebugLabel create(boolean bl, Set set) {
+ return new VkDebugLabel();
+ }
+
+ public boolean exists() {
+ return true;
+ }
+
+
+}
+
diff --git a/src/main/java/net/vulkanmod/render/engine/VkFbo.java b/src/main/java/net/vulkanmod/render/engine/VkFbo.java
new file mode 100644
index 000000000..1b61c1f49
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkFbo.java
@@ -0,0 +1,69 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.opengl.GlStateManager;
+import net.minecraft.util.ARGB;
+import net.vulkanmod.gl.VkGlFramebuffer;
+import net.vulkanmod.vulkan.Renderer;
+import net.vulkanmod.vulkan.VRenderSystem;
+import org.lwjgl.opengl.GL33;
+
+public class VkFbo {
+ final int glId;
+ final VkGpuTexture colorAttachment;
+ final VkGpuTexture depthAttachment;
+
+ protected VkFbo(VkGpuTexture colorAttachment, VkGpuTexture depthAttachment) {
+ this.glId = GlStateManager.glGenFramebuffers();
+ this.colorAttachment = colorAttachment;
+ this.depthAttachment = depthAttachment;
+
+ // Direct access
+ VkGlFramebuffer fbo = VkGlFramebuffer.getFramebuffer(this.glId);
+
+ fbo.setAttachmentTexture(GL33.GL_COLOR_ATTACHMENT0, colorAttachment.id);
+ if (depthAttachment != null) {
+ fbo.setAttachmentTexture(GL33.GL_DEPTH_ATTACHMENT, depthAttachment.id);
+ }
+ }
+
+ public void bind() {
+ VkGlFramebuffer.bindFramebuffer(GL33.GL_FRAMEBUFFER, this.glId);
+ clearAttachments();
+ }
+
+ protected void clearAttachments() {
+ int clear = 0;
+ float clearDepth;
+ int clearColor;
+
+ if (colorAttachment.needsClear()) {
+ clear |= 0x4000;
+ clearColor = colorAttachment.clearColor;
+
+ VRenderSystem.setClearColor(ARGB.redFloat(clearColor), ARGB.greenFloat(clearColor), ARGB.blueFloat(clearColor), ARGB.alphaFloat(clearColor));
+
+ colorAttachment.needsClear = false;
+ }
+
+ if (depthAttachment != null && depthAttachment.needsClear()) {
+ clear |= 0x100;
+ clearDepth = depthAttachment.depthClearValue;
+
+ VRenderSystem.clearDepth(clearDepth);
+
+ depthAttachment.needsClear = false;
+ }
+
+ if (clear != 0) {
+ Renderer.clearAttachments(clear);
+ }
+ }
+
+ protected void close() {
+ VkGlFramebuffer.deleteFramebuffer(this.glId);
+ }
+
+ public boolean needsClear() {
+ return this.colorAttachment.needsClear() || (this.depthAttachment != null && this.depthAttachment.needsClear());
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/engine/VkGpuBuffer.java b/src/main/java/net/vulkanmod/render/engine/VkGpuBuffer.java
new file mode 100644
index 000000000..1c672841e
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkGpuBuffer.java
@@ -0,0 +1,111 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.buffers.GpuBuffer;
+
+import java.nio.ByteBuffer;
+import java.util.function.Supplier;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.vulkanmod.vulkan.memory.MemoryManager;
+import net.vulkanmod.vulkan.memory.MemoryType;
+import net.vulkanmod.vulkan.memory.MemoryTypes;
+import net.vulkanmod.vulkan.memory.buffer.Buffer;
+import org.jetbrains.annotations.Nullable;
+
+import static org.lwjgl.vulkan.VK10.*;
+
+@Environment(EnvType.CLIENT)
+public class VkGpuBuffer extends GpuBuffer {
+ protected boolean closed;
+ @Nullable protected final Supplier label;
+
+ Buffer buffer;
+
+ protected VkGpuBuffer(VkDebugLabel glDebugLabel, @Nullable Supplier supplier, int usage, int size) {
+ super(usage, size);
+ this.label = supplier;
+
+ int vkUsage = 0;
+ if ((usage & GpuBuffer.USAGE_COPY_SRC) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+ }
+ if ((usage & GpuBuffer.USAGE_COPY_DST) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+ }
+ if ((usage & GpuBuffer.USAGE_VERTEX) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+ }
+ if ((usage & GpuBuffer.USAGE_INDEX) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+ }
+ if ((usage & GpuBuffer.USAGE_UNIFORM) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ }
+ if ((usage & GpuBuffer.USAGE_UNIFORM_TEXEL_BUFFER) != 0) {
+ vkUsage |= VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT;
+ }
+
+ boolean mappable = (usage & GpuBuffer.USAGE_MAP_READ) != 0 |
+ (usage & GpuBuffer.USAGE_MAP_WRITE) != 0 |
+ (usage & GpuBuffer.USAGE_HINT_CLIENT_STORAGE) != 0;
+
+ MemoryType memoryType = mappable ? MemoryTypes.HOST_MEM : MemoryTypes.GPU_MEM;
+
+ this.buffer = new Buffer(vkUsage, memoryType);
+ this.buffer.createBuffer(this.size());
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.closed;
+ }
+
+ @Override
+ public void close() {
+ if (!this.closed) {
+ this.closed = true;
+
+ MemoryManager.getInstance().addToFreeable(this.buffer);
+ }
+ }
+
+ public Buffer getBuffer() {
+ return buffer;
+ }
+
+ public static int bufferUsageToGlEnum(int i) {
+ boolean stream = (i & 4) != 0;
+ // Draw
+ if ((i & 2) != 0) {
+ return stream ? 35040 : 35044;
+ }
+ // Read
+ else if ((i & 1) != 0) {
+ return stream ? 35041 : 35045;
+ } else {
+ return 35044;
+ }
+ }
+
+ @Environment(EnvType.CLIENT)
+ public static class MappedView implements GpuBuffer.MappedView {
+ private final int target;
+ private final ByteBuffer data;
+
+ protected MappedView(int i, ByteBuffer byteBuffer) {
+ this.target = i;
+ this.data = byteBuffer;
+ }
+
+ @Override
+ public ByteBuffer data() {
+ return this.data;
+ }
+
+ @Override
+ public void close() {
+// GlStateManager._glUnmapBuffer(this.target);
+ }
+ }
+}
+
diff --git a/src/main/java/net/vulkanmod/render/engine/VkGpuDevice.java b/src/main/java/net/vulkanmod/render/engine/VkGpuDevice.java
new file mode 100644
index 000000000..6d5ec560d
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkGpuDevice.java
@@ -0,0 +1,404 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.buffers.GpuBuffer;
+import com.mojang.blaze3d.opengl.*;
+import com.mojang.blaze3d.pipeline.CompiledRenderPipeline;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.preprocessor.GlslPreprocessor;
+import com.mojang.blaze3d.shaders.ShaderType;
+import com.mojang.blaze3d.systems.CommandEncoder;
+import com.mojang.blaze3d.systems.GpuDevice;
+import com.mojang.blaze3d.textures.GpuTexture;
+import com.mojang.blaze3d.textures.GpuTextureView;
+import com.mojang.blaze3d.textures.TextureFormat;
+import com.mojang.logging.LogUtils;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.client.renderer.ShaderDefines;
+import net.minecraft.resources.ResourceLocation;
+import net.vulkanmod.Initializer;
+import net.vulkanmod.gl.VkGlTexture;
+import net.vulkanmod.interfaces.shader.ExtendedRenderPipeline;
+import net.vulkanmod.render.shader.ShaderLoadUtil;
+import net.vulkanmod.vulkan.VRenderSystem;
+import net.vulkanmod.vulkan.Vulkan;
+import net.vulkanmod.vulkan.device.DeviceManager;
+import net.vulkanmod.vulkan.shader.GraphicsPipeline;
+import net.vulkanmod.vulkan.shader.Pipeline;
+import net.vulkanmod.vulkan.shader.converter.GLSLParser;
+import net.vulkanmod.vulkan.shader.converter.Lexer;
+import net.vulkanmod.vulkan.shader.descriptor.UBO;
+import net.vulkanmod.vulkan.texture.VulkanImage;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.vulkan.VK10;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
+
+@SuppressWarnings("NullableProblems")
+public class VkGpuDevice implements GpuDevice {
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ private final VkCommandEncoder encoder;
+ private final VkDebugLabel debugLabels;
+ private final int maxSupportedTextureSize;
+ private final int uniformOffsetAlignment;
+ private final BiFunction defaultShaderSource;
+ private final Map pipelineCache = new IdentityHashMap<>();
+ private final Map shaderCache = new HashMap<>();
+ private final Set enabledExtensions = new HashSet<>();
+
+ private final Map shaderSrcCache = new HashMap<>();
+
+ public VkGpuDevice(long l, int i, boolean bl, BiFunction shaderSource, boolean bl2) {
+ this.debugLabels = VkDebugLabel.create(bl2, this.enabledExtensions);
+ this.maxSupportedTextureSize = VRenderSystem.maxSupportedTextureSize();
+ this.uniformOffsetAlignment = (int) DeviceManager.deviceProperties.limits().minUniformBufferOffsetAlignment();
+ this.defaultShaderSource = shaderSource;
+
+ this.encoder = new VkCommandEncoder(this);
+ }
+
+ public VkDebugLabel debugLabels() {
+ return this.debugLabels;
+ }
+
+ @Override
+ public CommandEncoder createCommandEncoder() {
+ return this.encoder;
+ }
+
+ @Override
+ public GpuTexture createTexture(@Nullable Supplier supplier, int usage, TextureFormat textureFormat, int width, int height, int layers, int mipLevels) {
+ return this.createTexture(this.debugLabels.exists() && supplier != null ? supplier.get() : null, usage, textureFormat, width, height, layers, mipLevels);
+ }
+
+ @Override
+ public GpuTexture createTexture(@Nullable String string, int usage, TextureFormat textureFormat, int width, int height, int layers, int mipLevels) {
+ if (mipLevels < 1) {
+ throw new IllegalArgumentException("mipLevels must be at least 1");
+ } else {
+ int id = VkGlTexture.genTextureId();
+ if (string == null) {
+ string = String.valueOf(id);
+ }
+
+ int format = VkGpuTexture.vkFormat(textureFormat);
+ int viewType = VkGpuTexture.vkImageViewType(usage);
+ boolean depthFormat = VulkanImage.isDepthFormat(format);
+ int attachmentUsage = depthFormat ? VK10.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ VulkanImage texture = VulkanImage.builder(width, height)
+ .setName(string)
+ .setFormat(format)
+ .setArrayLayers(layers)
+ .setMipLevels(mipLevels)
+ .addUsage(attachmentUsage)
+ .setViewType(viewType)
+ .createVulkanImage();
+
+ VkGlTexture vGlTexture = VkGlTexture.getTexture(id);
+ vGlTexture.setVulkanImage(texture);
+ VkGlTexture.bindTexture(id);
+
+ VkGpuTexture glTexture = new VkGpuTexture(usage, string, textureFormat, width, height, layers, mipLevels, id, vGlTexture);
+ this.debugLabels.applyLabel(glTexture);
+ return glTexture;
+ }
+ }
+
+ public VkGpuTexture gpuTextureFromVulkanImage(VulkanImage image) {
+ int id = VkGlTexture.genTextureId();
+ VkGlTexture glTexture = VkGlTexture.getTexture(id);
+ glTexture.setVulkanImage(image);
+ TextureFormat textureFormat = VkGpuTexture.textureFormat(image.format);
+ VkGpuTexture gpuTexture = new VkGpuTexture(0, image.name, textureFormat, image.width, image.height, 1, image.mipLevels, id, glTexture);
+ this.debugLabels.applyLabel(gpuTexture);
+ return gpuTexture;
+ }
+
+ @Override
+ public GpuTextureView createTextureView(GpuTexture gpuTexture) {
+ return this.createTextureView(gpuTexture, 0, gpuTexture.getMipLevels());
+ }
+
+ @Override
+ public GpuTextureView createTextureView(GpuTexture gpuTexture, int startLevel, int levels) {
+ if (gpuTexture.isClosed()) {
+ throw new IllegalArgumentException("Can't create texture view with closed texture");
+ } else if (startLevel >= 0 && startLevel + levels <= gpuTexture.getMipLevels()) {
+
+ // Try to convert gpuTexture to VkGpuTexture in case it's not
+ if (gpuTexture.getClass() != VkGpuTexture.class) {
+ gpuTexture = VkGpuTexture.fromGlTexture((GlTexture) gpuTexture);
+ }
+
+ return new VkTextureView((VkGpuTexture) gpuTexture, startLevel, levels);
+ } else {
+ throw new IllegalArgumentException(
+ levels + " mip levels starting from " + startLevel + " would be out of range for texture with only " + gpuTexture.getMipLevels() + " mip levels"
+ );
+ }
+ }
+
+ @Override
+ public GpuBuffer createBuffer(@Nullable Supplier supplier, int usage, int size) {
+ if (size <= 0) {
+ throw new IllegalArgumentException("Buffer size must be greater than zero");
+ } else {
+ return new VkGpuBuffer(this.debugLabels, supplier, usage, size);
+ }
+ }
+
+ @Override
+ public GpuBuffer createBuffer(@Nullable Supplier supplier, int usage, ByteBuffer byteBuffer) {
+ if (!byteBuffer.hasRemaining()) {
+ throw new IllegalArgumentException("Buffer source must not be empty");
+ } else {
+ VkGpuBuffer glBuffer = new VkGpuBuffer(this.debugLabels, supplier, usage, byteBuffer.remaining());
+ this.encoder.writeToBuffer(glBuffer.slice(), byteBuffer);
+ return glBuffer;
+ }
+ }
+
+ @Override
+ public String getImplementationInformation() {
+ return "Vulkan " + Vulkan.getDevice().vkVersion + ", " + Vulkan.getDevice().vendorIdString;
+ }
+
+ @Override
+ public List getLastDebugMessages() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean isDebuggingEnabled() {
+ return false;
+ }
+
+ @Override
+ public String getRenderer() {
+ return DeviceManager.device.deviceName;
+ }
+
+ @Override
+ public String getVendor() {
+ return Vulkan.getDevice().vendorIdString;
+ }
+
+ @Override
+ public String getBackendName() {
+ return "Vulkan";
+ }
+
+ @Override
+ public String getVersion() {
+ return Vulkan.getDevice().vkVersion;
+ }
+
+ private static int getMaxSupportedTextureSize() {
+ int i = GlStateManager._getInteger(3379);
+
+ for (int j = Math.max(32768, i); j >= 1024; j >>= 1) {
+ GlStateManager._texImage2D(32868, 0, 6408, j, j, 0, 6408, 5121, null);
+ int k = GlStateManager._getTexLevelParameter(32868, 0, 4096);
+ if (k != 0) {
+ return j;
+ }
+ }
+
+ int jx = Math.max(i, 1024);
+ LOGGER.info("Failed to determine maximum texture size by probing, trying GL_MAX_TEXTURE_SIZE = {}", jx);
+ return jx;
+ }
+
+ @Override
+ public int getMaxTextureSize() {
+ return this.maxSupportedTextureSize;
+ }
+
+ @Override
+ public int getUniformOffsetAlignment() {
+ return this.uniformOffsetAlignment;
+ }
+
+ @Override
+ public void clearPipelineCache() {
+ for (GlRenderPipeline glRenderPipeline : this.pipelineCache.values()) {
+ if (glRenderPipeline.program() != GlProgram.INVALID_PROGRAM) {
+ glRenderPipeline.program().close();
+ }
+ }
+
+ this.pipelineCache.clear();
+
+ for (GlShaderModule glShaderModule : this.shaderCache.values()) {
+ if (glShaderModule != GlShaderModule.INVALID_SHADER) {
+ glShaderModule.close();
+ }
+ }
+
+ this.shaderCache.clear();
+ }
+
+ @Override
+ public List getEnabledExtensions() {
+ return new ArrayList(this.enabledExtensions);
+ }
+
+ @Override
+ public void close() {
+ this.clearPipelineCache();
+ }
+
+ protected GlShaderModule getOrCompileShader(
+ ResourceLocation resourceLocation, ShaderType shaderType, ShaderDefines shaderDefines, BiFunction biFunction
+ ) {
+ ShaderCompilationKey shaderCompilationKey = new ShaderCompilationKey(resourceLocation, shaderType, shaderDefines);
+ return this.shaderCache.computeIfAbsent(shaderCompilationKey, shaderCompilationKey2 -> this.compileShader(shaderCompilationKey, biFunction));
+ }
+
+ protected String getCachedShaderSrc(ResourceLocation resourceLocation, ShaderType shaderType, ShaderDefines shaderDefines, BiFunction shaderSourceGetter) {
+ ShaderCompilationKey shaderCompilationKey = new ShaderCompilationKey(resourceLocation, shaderType, shaderDefines);
+
+ return this.shaderSrcCache.computeIfAbsent(shaderCompilationKey, compilationKey -> {
+ String shaderExtension = switch (shaderType) {
+ case VERTEX -> ".vsh";
+ case FRAGMENT -> ".fsh";
+ };
+
+ String shaderName = resourceLocation.getPath() + shaderExtension;
+
+ if (ShaderLoadUtil.REMAPPED_SHADERS.contains(shaderName)) {
+ String src = ShaderLoadUtil.getShaderSource(resourceLocation, shaderType);
+
+ if (src == null) {
+ throw new RuntimeException("shader: (%s) not found.");
+ }
+
+ return src;
+ }
+
+ return shaderSourceGetter.apply(compilationKey.id, compilationKey.type);
+ });
+ }
+
+ public CompiledRenderPipeline precompilePipeline(RenderPipeline renderPipeline, @Nullable BiFunction shaderSourceGetter) {
+ shaderSourceGetter = shaderSourceGetter == null ? this.defaultShaderSource : shaderSourceGetter;
+ compilePipeline(renderPipeline, shaderSourceGetter);
+
+ return new VkRenderPipeline(renderPipeline);
+ }
+
+ public void compilePipeline(RenderPipeline renderPipeline) {
+ this.compilePipeline(renderPipeline, this.defaultShaderSource);
+ }
+
+ private GlShaderModule compileShader(ShaderCompilationKey shaderCompilationKey, BiFunction biFunction) {
+ String string = biFunction.apply(shaderCompilationKey.id, shaderCompilationKey.type);
+ if (string == null) {
+ LOGGER.error("Couldn't find source for {} shader ({})", shaderCompilationKey.type, shaderCompilationKey.id);
+ return GlShaderModule.INVALID_SHADER;
+ } else {
+ String string2 = GlslPreprocessor.injectDefines(string, shaderCompilationKey.defines);
+ int i = GlStateManager.glCreateShader(GlConst.toGl(shaderCompilationKey.type));
+ GlStateManager.glShaderSource(i, string2);
+ GlStateManager.glCompileShader(i);
+ if (GlStateManager.glGetShaderi(i, 35713) == 0) {
+ String string3 = StringUtils.trim(GlStateManager.glGetShaderInfoLog(i, 32768));
+ LOGGER.error("Couldn't compile {} shader ({}): {}", shaderCompilationKey.type.getName(), shaderCompilationKey.id, string3);
+ return GlShaderModule.INVALID_SHADER;
+ } else {
+ GlShaderModule glShaderModule = new GlShaderModule(i, shaderCompilationKey.id, shaderCompilationKey.type);
+ this.debugLabels.applyLabel(glShaderModule);
+ return glShaderModule;
+ }
+ }
+ }
+
+ private void compilePipeline(RenderPipeline renderPipeline, BiFunction shaderSrcGetter) {
+ String locationPath = renderPipeline.getLocation().getPath();
+
+ String configName;
+ if (locationPath.contains("core")) {
+ configName = locationPath.split("/")[1];
+ } else {
+ configName = locationPath;
+ }
+
+ Pipeline.Builder builder = new Pipeline.Builder(renderPipeline.getVertexFormat(), configName);
+ GraphicsPipeline pipeline;
+ ExtendedRenderPipeline extPipeline = ExtendedRenderPipeline.of(renderPipeline);
+
+ ResourceLocation vertexShaderLocation = renderPipeline.getVertexShader();
+ ResourceLocation fragmentShaderLocation = renderPipeline.getFragmentShader();
+
+ ShaderDefines shaderDefines = renderPipeline.getShaderDefines();
+
+ String vshSrc = this.getCachedShaderSrc(vertexShaderLocation, ShaderType.VERTEX, shaderDefines, shaderSrcGetter);
+ String fshSrc = this.getCachedShaderSrc(fragmentShaderLocation, ShaderType.FRAGMENT, shaderDefines, shaderSrcGetter);
+
+ vshSrc = GlslPreprocessor.injectDefines(vshSrc, shaderDefines);
+ fshSrc = GlslPreprocessor.injectDefines(fshSrc, shaderDefines);
+
+ Lexer lexer = new Lexer(vshSrc);
+ GLSLParser parser = new GLSLParser();
+ parser.setVertexFormat(renderPipeline.getVertexFormat());
+
+ try {
+ parser.parse(lexer, GLSLParser.Stage.VERTEX);
+
+ lexer = new Lexer(fshSrc);
+ parser.parse(lexer, GLSLParser.Stage.FRAGMENT);
+ } catch (Exception e) {
+ throw new RuntimeException("Caught exception while parsing: %s".formatted(renderPipeline.toString()), e);
+ }
+
+ UBO[] ubos = parser.createUBOs();
+
+ String vshProcessed = parser.getOutput(GLSLParser.Stage.VERTEX);
+ String fshProcessed = parser.getOutput(GLSLParser.Stage.FRAGMENT);
+
+ builder.setUniforms(List.of(ubos), parser.getSamplerList());
+ builder.compileShaders(configName, vshProcessed, fshProcessed);
+
+ try {
+ pipeline = builder.createGraphicsPipeline();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("Exception while compiling pipeline %s".formatted(renderPipeline));
+ }
+
+ EGlProgram eGlProgram = new EGlProgram(1, configName);
+ eGlProgram.setupUniforms(pipeline, renderPipeline.getUniforms(), renderPipeline.getSamplers());
+ extPipeline.setProgram(eGlProgram);
+
+ extPipeline.setPipeline(pipeline);
+ }
+
+ @Environment(EnvType.CLIENT)
+ record ShaderCompilationKey(ResourceLocation id, ShaderType type, ShaderDefines defines) {
+
+ public String toString() {
+ String string = this.id + " (" + this.type + ")";
+ return !this.defines.isEmpty() ? string + " with " + this.defines : string;
+ }
+ }
+
+ private static class VkRenderPipeline implements CompiledRenderPipeline {
+ final RenderPipeline renderPipeline;
+
+ public VkRenderPipeline(RenderPipeline renderPipeline) {
+ this.renderPipeline = renderPipeline;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/engine/VkGpuTexture.java b/src/main/java/net/vulkanmod/render/engine/VkGpuTexture.java
new file mode 100644
index 000000000..142769f0c
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkGpuTexture.java
@@ -0,0 +1,165 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.opengl.GlStateManager;
+import com.mojang.blaze3d.opengl.GlTexture;
+import com.mojang.blaze3d.textures.AddressMode;
+import com.mojang.blaze3d.textures.FilterMode;
+import com.mojang.blaze3d.textures.GpuTexture;
+import com.mojang.blaze3d.textures.TextureFormat;
+import it.unimi.dsi.fastutil.ints.*;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.vulkanmod.gl.VkGlTexture;
+import net.vulkanmod.vulkan.texture.SamplerManager;
+import net.vulkanmod.vulkan.texture.VulkanImage;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.vulkan.VK10;
+
+@Environment(EnvType.CLIENT)
+public class VkGpuTexture extends GlTexture {
+ private static final Reference2ReferenceOpenHashMap glToVkMap = new Reference2ReferenceOpenHashMap<>();
+
+ protected VkGlTexture glTexture;
+ protected final int id;
+ private final Int2ReferenceMap fboCache = new Int2ReferenceOpenHashMap<>();
+ protected boolean closed;
+ protected boolean modesDirty = true;
+
+ boolean needsClear = false;
+ int clearColor = 0;
+ float depthClearValue = 1.0f;
+
+ protected VkGpuTexture(int usage, String string, TextureFormat textureFormat, int width, int height, int layers, int mipLevel, int id, VkGlTexture glTexture) {
+ super(usage, string, textureFormat, width, height, layers, mipLevel, id);
+ this.id = id;
+ this.glTexture = glTexture;
+ }
+
+ @Override
+ public void close() {
+ if (!this.closed) {
+ this.closed = true;
+ GlStateManager._deleteTexture(this.id);
+
+ for (VkFbo fbo : this.fboCache.values()) {
+ fbo.close();
+ }
+ }
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.closed;
+ }
+
+ public void flushModeChanges() {
+ if (this.modesDirty) {
+ int maxLod = this.useMipmaps ? this.getMipLevels() - 1 : 0;
+
+ int magFilterVk = magFilter == FilterMode.LINEAR ? VK10.VK_FILTER_LINEAR : VK10.VK_FILTER_NEAREST;
+ int minFilterVk = minFilter == FilterMode.LINEAR ? VK10.VK_FILTER_LINEAR : VK10.VK_FILTER_NEAREST;
+
+ int addressModeUVk = this.addressModeU == AddressMode.REPEAT ? VK10.VK_SAMPLER_ADDRESS_MODE_REPEAT : VK10.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+ int addressModeVVk = this.addressModeV == AddressMode.REPEAT ? VK10.VK_SAMPLER_ADDRESS_MODE_REPEAT : VK10.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
+
+ long sampler = SamplerManager.getSampler(addressModeUVk, addressModeVVk,
+ minFilterVk, magFilterVk, VK10.VK_SAMPLER_MIPMAP_MODE_LINEAR,
+ maxLod, false, 0, -1);
+
+ glTexture.getVulkanImage().setSampler(sampler);
+
+ this.modesDirty = false;
+ }
+ }
+
+ public int glId() {
+ return this.id;
+ }
+
+ @Override
+ public void setAddressMode(AddressMode addressMode, AddressMode addressMode2) {
+ super.setAddressMode(addressMode, addressMode2);
+ this.modesDirty = true;
+ }
+
+ @Override
+ public void setTextureFilter(FilterMode filterMode, FilterMode filterMode2, boolean bl) {
+ super.setTextureFilter(filterMode, filterMode2, bl);
+ this.modesDirty = true;
+ }
+
+ @Override
+ public void setUseMipmaps(boolean bl) {
+ super.setUseMipmaps(bl);
+ this.modesDirty = true;
+ }
+
+ public void setClearColor(int clearColor) {
+ this.needsClear = true;
+ this.clearColor = clearColor;
+ }
+
+ public void setDepthClearValue(float depthClearValue) {
+ this.needsClear = true;
+ this.depthClearValue = depthClearValue;
+ }
+
+ public boolean needsClear() {
+ return needsClear;
+ }
+
+ public VkFbo getFbo(@Nullable GpuTexture depthAttachment) {
+ int depthAttachmentId = depthAttachment == null ? 0 : ((VkGpuTexture)depthAttachment).id;
+ return this.fboCache.computeIfAbsent(depthAttachmentId, j -> new VkFbo(this, (VkGpuTexture) depthAttachment));
+ }
+
+ public VulkanImage getVulkanImage() {
+ return glTexture.getVulkanImage();
+ }
+
+ public static VkGpuTexture fromGlTexture(GlTexture glTexture) {
+ return glToVkMap.computeIfAbsent(glTexture, glTexture1 -> {
+ var name = glTexture.getLabel();
+ int id = glTexture.glId();
+ VkGlTexture vglTexture = VkGlTexture.getTexture(id);
+ VkGpuTexture gpuTexture = new VkGpuTexture(0, name, glTexture.getFormat(),
+ glTexture.getWidth(0), glTexture.getHeight(0),
+ 1, glTexture.getMipLevels(),
+ glTexture.glId(), vglTexture);
+
+ return gpuTexture;
+ });
+ }
+
+ public static TextureFormat textureFormat(int format) {
+ return switch (format) {
+ case VK10.VK_FORMAT_R8G8B8A8_UNORM, VK10.VK_FORMAT_B8G8R8A8_UNORM, VK10.VK_FORMAT_R8G8B8A8_SRGB -> TextureFormat.RGBA8;
+ case VK10.VK_FORMAT_R8_UNORM -> TextureFormat.RED8;
+ case VK10.VK_FORMAT_D32_SFLOAT -> TextureFormat.DEPTH32;
+ default -> null;
+ };
+ }
+
+ public static int vkFormat(TextureFormat textureFormat) {
+ return switch (textureFormat) {
+ case RGBA8 -> VK10.VK_FORMAT_R8G8B8A8_UNORM;
+ case RED8 -> VK10.VK_FORMAT_R8_UNORM;
+ case RED8I -> VK10.VK_FORMAT_R8_SINT;
+ case DEPTH32 -> VK10.VK_FORMAT_D32_SFLOAT;
+ };
+ }
+
+ public static int vkImageViewType(int usage) {
+ int viewType;
+ if ((usage & GpuTexture.USAGE_CUBEMAP_COMPATIBLE) != 0) {
+ viewType = VK10.VK_IMAGE_VIEW_TYPE_CUBE;
+ }
+ else {
+ viewType = VK10.VK_IMAGE_VIEW_TYPE_2D;
+ }
+
+ return viewType;
+ }
+}
+
diff --git a/src/main/java/net/vulkanmod/render/engine/VkRenderPass.java b/src/main/java/net/vulkanmod/render/engine/VkRenderPass.java
new file mode 100644
index 000000000..8d9c039bc
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkRenderPass.java
@@ -0,0 +1,212 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.buffers.GpuBuffer;
+import com.mojang.blaze3d.buffers.GpuBufferSlice;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.systems.RenderPass;
+import com.mojang.blaze3d.systems.ScissorState;
+import com.mojang.blaze3d.textures.GpuTexture;
+import com.mojang.blaze3d.textures.GpuTextureView;
+import com.mojang.blaze3d.vertex.VertexFormat;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.SharedConstants;
+import net.vulkanmod.interfaces.shader.ExtendedRenderPipeline;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Matrix4f;
+
+@Environment(EnvType.CLIENT)
+public class VkRenderPass implements RenderPass {
+ protected static final int MAX_VERTEX_BUFFERS = 1;
+ public static final boolean VALIDATION = SharedConstants.IS_RUNNING_IN_IDE;
+ private final VkCommandEncoder encoder;
+ private final boolean hasDepthTexture;
+ private boolean closed;
+ @Nullable
+ protected RenderPipeline pipeline;
+ protected final GpuBuffer[] vertexBuffers = new GpuBuffer[1];
+ @Nullable
+ protected GpuBuffer indexBuffer;
+ protected VertexFormat.IndexType indexType = VertexFormat.IndexType.INT;
+ private final ScissorState scissorState = new ScissorState();
+ protected final HashMap uniforms = new HashMap<>();
+ protected final HashMap samplers = new HashMap<>();
+ protected final Set dirtyUniforms = new HashSet<>();
+ protected int pushedDebugGroups;
+
+ public VkRenderPass(VkCommandEncoder commandEncoder, boolean bl) {
+ this.encoder = commandEncoder;
+ this.hasDepthTexture = bl;
+ }
+
+ public boolean hasDepthTexture() {
+ return this.hasDepthTexture;
+ }
+
+ @Override
+ public void pushDebugGroup(Supplier supplier) {
+ if (this.closed) {
+ throw new IllegalStateException("Can't use a closed render pass");
+ } else {
+ this.pushedDebugGroups++;
+// this.encoder.getDevice().debugLabels().pushDebugGroup(supplier);
+ }
+ }
+
+ @Override
+ public void popDebugGroup() {
+ if (this.closed) {
+ throw new IllegalStateException("Can't use a closed render pass");
+ } else if (this.pushedDebugGroups == 0) {
+ throw new IllegalStateException("Can't pop more debug groups than was pushed!");
+ } else {
+ this.pushedDebugGroups--;
+// this.encoder.getDevice().debugLabels().popDebugGroup();
+ }
+ }
+
+ @Override
+ public void setPipeline(RenderPipeline renderPipeline) {
+ if (this.pipeline == null || this.pipeline != renderPipeline) {
+ this.dirtyUniforms.addAll(this.uniforms.keySet());
+ }
+
+
+ this.pipeline = renderPipeline;
+
+ if (ExtendedRenderPipeline.of(renderPipeline).getPipeline() == null) {
+ this.encoder.getDevice().compilePipeline(renderPipeline);
+ }
+ }
+
+ @Override
+ public void bindSampler(String string, @Nullable GpuTextureView gpuTextureView) {
+ if (gpuTextureView == null) {
+ this.samplers.remove(string);
+ } else {
+ this.samplers.put(string, gpuTextureView);
+ }
+
+ this.dirtyUniforms.add(string);
+ }
+
+ @Override
+ public void setUniform(String string, GpuBuffer gpuBuffer) {
+ this.uniforms.put(string, gpuBuffer.slice());
+ this.dirtyUniforms.add(string);
+ }
+
+ @Override
+ public void setUniform(String string, GpuBufferSlice gpuBufferSlice) {
+ int i = this.encoder.getDevice().getUniformOffsetAlignment();
+ if (gpuBufferSlice.offset() % i > 0) {
+ throw new IllegalArgumentException("Uniform buffer offset must be aligned to " + i);
+ } else {
+ this.uniforms.put(string, gpuBufferSlice);
+ this.dirtyUniforms.add(string);
+ }
+ }
+
+ @Override
+ public void enableScissor(int i, int j, int k, int l) {
+ this.scissorState.enable(i, j, k, l);
+ }
+
+ @Override
+ public void disableScissor() {
+ this.scissorState.disable();
+ }
+
+ public boolean isScissorEnabled() {
+ return this.scissorState.enabled();
+ }
+
+ public int getScissorX() {
+ return this.scissorState.x();
+ }
+
+ public int getScissorY() {
+ return this.scissorState.y();
+ }
+
+ public int getScissorWidth() {
+ return this.scissorState.width();
+ }
+
+ public int getScissorHeight() {
+ return this.scissorState.height();
+ }
+
+ public ScissorState getScissorState() { return this.scissorState; }
+
+ @Override
+ public void setVertexBuffer(int i, GpuBuffer gpuBuffer) {
+ if (i >= 0 && i < 1) {
+ this.vertexBuffers[i] = gpuBuffer;
+ } else {
+ throw new IllegalArgumentException("Vertex buffer slot is out of range: " + i);
+ }
+ }
+
+ @Override
+ public void setIndexBuffer(@Nullable GpuBuffer gpuBuffer, VertexFormat.IndexType indexType) {
+ this.indexBuffer = gpuBuffer;
+ this.indexType = indexType;
+ }
+
+ @Override
+ public void drawIndexed(int i, int j, int k, int l) {
+ if (this.closed) {
+ throw new IllegalStateException("Can't use a closed render pass");
+ } else {
+ this.encoder.executeDraw(this, i, j, k, this.indexType, l);
+ }
+ }
+
+ @Override
+ public void drawMultipleIndexed(
+ Collection> collection,
+ @Nullable GpuBuffer gpuBuffer,
+ @Nullable VertexFormat.IndexType indexType,
+ Collection collection2,
+ T object
+ ) {
+ if (this.closed) {
+ throw new IllegalStateException("Can't use a closed render pass");
+ } else {
+ this.encoder.executeDrawMultiple(this, collection, gpuBuffer, indexType, collection2, object);
+ }
+ }
+
+ @Override
+ public void draw(int i, int j) {
+ if (this.closed) {
+ throw new IllegalStateException("Can't use a closed render pass");
+ } else {
+ this.encoder.executeDraw(this, i, 0, j, null, 1);
+ }
+ }
+
+ @Override
+ public void close() {
+ if (!this.closed) {
+ if (this.pushedDebugGroups > 0) {
+ throw new IllegalStateException("Render pass had debug groups left open!");
+ }
+
+ this.closed = true;
+ this.encoder.finishRenderPass();
+ }
+ }
+
+ public @Nullable RenderPipeline getPipeline() {
+ return pipeline;
+ }
+}
+
diff --git a/src/main/java/net/vulkanmod/render/engine/VkTextureView.java b/src/main/java/net/vulkanmod/render/engine/VkTextureView.java
new file mode 100644
index 000000000..8ad3c6b79
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/engine/VkTextureView.java
@@ -0,0 +1,29 @@
+package net.vulkanmod.render.engine;
+
+import com.mojang.blaze3d.textures.GpuTextureView;
+
+public class VkTextureView extends GpuTextureView {
+ private boolean closed;
+
+ protected VkTextureView(VkGpuTexture gpuTexture, int i, int j) {
+ super(gpuTexture, i, j);
+ gpuTexture.addViews();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return this.closed;
+ }
+
+ @Override
+ public void close() {
+ if (!this.closed) {
+ this.closed = true;
+ this.texture().removeViews();
+ }
+ }
+
+ public VkGpuTexture texture() {
+ return (VkGpuTexture) super.texture();
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/gui/GuiBatchRenderer.java b/src/main/java/net/vulkanmod/render/gui/GuiBatchRenderer.java
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/main/java/net/vulkanmod/render/model/CubeModel.java b/src/main/java/net/vulkanmod/render/model/CubeModel.java
index da988f39d..ef5bb5548 100644
--- a/src/main/java/net/vulkanmod/render/model/CubeModel.java
+++ b/src/main/java/net/vulkanmod/render/model/CubeModel.java
@@ -1,15 +1,15 @@
package net.vulkanmod.render.model;
-import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.core.Direction;
import org.joml.Matrix4f;
import org.joml.Vector3f;
+import org.joml.Vector3fc;
import java.util.Set;
public class CubeModel {
- private ModelPart.Polygon[] polygons = new ModelPart.Polygon[6];
+ private Polygon[] polygons;
public float minX;
public float minY;
public float minZ;
@@ -17,99 +17,165 @@ public class CubeModel {
public float maxY;
public float maxZ;
- Vector3f[] vertices;
- Vector3f[] transformed = new Vector3f[8];
-
- public void setVertices(int i, int j, float f, float g, float h, float k, float l, float m, float n, float o, float p, boolean bl, float q, float r, Set set) {
- this.minX = f;
- this.minY = g;
- this.minZ = h;
- this.maxX = f + k;
- this.maxY = g + l;
- this.maxZ = h + m;
- this.polygons = new ModelPart.Polygon[set.size()];
+ Vertex[] vertices;
+
+ public void setVertices(int u, int v, float minX, float minY, float minZ, float dimX, float dimY, float dimZ, float growX, float growY, float growZ, boolean mirror, float uTexScale, float vTexScale, Set set) {
+ this.minX = minX;
+ this.minY = minY;
+ this.minZ = minZ;
+ this.maxX = minX + dimX;
+ this.maxY = minY + dimY;
+ this.maxZ = minZ + dimZ;
+ this.polygons = new Polygon[set.size()];
float s = maxX;
float t = maxY;
- float u = maxZ;
- f -= n;
- g -= o;
- h -= p;
- s += n;
- t += o;
- u += p;
- if (bl) {
- float v = s;
- s = f;
- f = v;
- }
-
- this.vertices = new Vector3f[]{
- new Vector3f(f, g, h),
- new Vector3f(s, g, h),
- new Vector3f(s, t, h),
- new Vector3f(f, t, h),
- new Vector3f(f, g, u),
- new Vector3f(s, g, u),
- new Vector3f(s, t, u),
- new Vector3f(f, t, u)
+ float u1 = maxZ;
+ minX -= growX;
+ minY -= growY;
+ minZ -= growZ;
+ s += growX;
+ t += growY;
+ u1 += growZ;
+ if (mirror) {
+ float v1 = s;
+ s = minX;
+ minX = v1;
+ }
+
+ this.vertices = new Vertex[]{
+ new Vertex(minX, minY, minZ, 0.0F, 0.0F),
+ new Vertex(s, minY, minZ, 0.0F, 8.0F),
+ new Vertex(s, t, minZ, 8.0F, 8.0F),
+ new Vertex(minX, t, minZ, 8.0F, 0.0F),
+ new Vertex(minX, minY, u1, 0.0F, 0.0F),
+ new Vertex(s, minY, u1, 0.0F, 8.0F),
+ new Vertex(s, t, u1, 8.0F, 8.0F),
+ new Vertex(minX, t, u1, 8.0F, 0.0F)
};
- for (int i1 = 0; i1 < 8; i1++) {
- //pre-divide all vertices once
- this.vertices[i1].div(16.0f);
- this.transformed[i1] = new Vector3f(0.0f);
- }
-
- ModelPart.Vertex vertex1 = new ModelPart.Vertex(transformed[0], 0.0F, 0.0F);
- ModelPart.Vertex vertex2 = new ModelPart.Vertex(transformed[1], 0.0F, 8.0F);
- ModelPart.Vertex vertex3 = new ModelPart.Vertex(transformed[2], 8.0F, 8.0F);
- ModelPart.Vertex vertex4 = new ModelPart.Vertex(transformed[3], 8.0F, 0.0F);
- ModelPart.Vertex vertex5 = new ModelPart.Vertex(transformed[4], 0.0F, 0.0F);
- ModelPart.Vertex vertex6 = new ModelPart.Vertex(transformed[5], 0.0F, 8.0F);
- ModelPart.Vertex vertex7 = new ModelPart.Vertex(transformed[6], 8.0F, 8.0F);
- ModelPart.Vertex vertex8 = new ModelPart.Vertex(transformed[7], 8.0F, 0.0F);
-
- float w = (float)i;
- float x = (float)i + m;
- float y = (float)i + m + k;
- float z = (float)i + m + k + k;
- float aa = (float)i + m + k + m;
- float ab = (float)i + m + k + m + k;
- float ac = (float)j;
- float ad = (float)j + m;
- float ae = (float)j + m + l;
+ float w = (float)u;
+ float x = (float)u + dimZ;
+ float y = (float)u + dimZ + dimX;
+ float z = (float)u + dimZ + dimX + dimX;
+ float aa = (float)u + dimZ + dimX + dimZ;
+ float ab = (float)u + dimZ + dimX + dimZ + dimX;
+ float ac = (float)v;
+ float ad = (float)v + dimZ;
+ float ae = (float)v + dimZ + dimY;
+
+ Vertex vertex1 = this.vertices[0];
+ Vertex vertex2 = this.vertices[1];
+ Vertex vertex3 = this.vertices[2];
+ Vertex vertex4 = this.vertices[3];
+ Vertex vertex5 = this.vertices[4];
+ Vertex vertex6 = this.vertices[5];
+ Vertex vertex7 = this.vertices[6];
+ Vertex vertex8 = this.vertices[7];
+
int idx = 0;
if (set.contains(Direction.DOWN)) {
- this.polygons[idx++] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex6, vertex5, vertex1, vertex2}, x, ac, y, ad, q, r, bl, Direction.DOWN);
+ this.polygons[idx++] = new Polygon(new Vertex[]{vertex6, vertex5, vertex1, vertex2}, x, ac, y, ad, uTexScale, vTexScale, mirror, Direction.DOWN);
}
if (set.contains(Direction.UP)) {
- this.polygons[idx++] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex3, vertex4, vertex8, vertex7}, y, ad, z, ac, q, r, bl, Direction.UP);
+ this.polygons[idx++] = new Polygon(new Vertex[]{vertex3, vertex4, vertex8, vertex7}, y, ad, z, ac, uTexScale, vTexScale, mirror, Direction.UP);
}
if (set.contains(Direction.WEST)) {
- this.polygons[idx++] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex1, vertex5, vertex8, vertex4}, w, ad, x, ae, q, r, bl, Direction.WEST);
+ this.polygons[idx++] = new Polygon(new Vertex[]{vertex1, vertex5, vertex8, vertex4}, w, ad, x, ae, uTexScale, vTexScale, mirror, Direction.WEST);
}
if (set.contains(Direction.NORTH)) {
- this.polygons[idx++] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex2, vertex1, vertex4, vertex3}, x, ad, y, ae, q, r, bl, Direction.NORTH);
+ this.polygons[idx++] = new Polygon(new Vertex[]{vertex2, vertex1, vertex4, vertex3}, x, ad, y, ae, uTexScale, vTexScale, mirror, Direction.NORTH);
}
if (set.contains(Direction.EAST)) {
- this.polygons[idx++] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex6, vertex2, vertex3, vertex7}, y, ad, aa, ae, q, r, bl, Direction.EAST);
+ this.polygons[idx++] = new Polygon(new Vertex[]{vertex6, vertex2, vertex3, vertex7}, y, ad, aa, ae, uTexScale, vTexScale, mirror, Direction.EAST);
}
if (set.contains(Direction.SOUTH)) {
- this.polygons[idx] = new ModelPart.Polygon(new ModelPart.Vertex[]{vertex5, vertex6, vertex7, vertex8}, aa, ad, ab, ae, q, r, bl, Direction.SOUTH);
+ this.polygons[idx] = new Polygon(new Vertex[]{vertex5, vertex6, vertex7, vertex8}, aa, ad, ab, ae, uTexScale, vTexScale, mirror, Direction.SOUTH);
}
}
public void transformVertices(Matrix4f matrix) {
- //Transform original vertices and store them
- for(int i = 0; i < 8; ++i) {
- this.vertices[i].mulPosition(matrix, this.transformed[i]);
+ // Transform original vertices and store them
+ for (int i = 0; i < 8; ++i) {
+ Vertex vertex = this.vertices[i];
+ vertex.pos.mulPosition(matrix, vertex.transformed);
+ }
+ }
+
+ public Polygon[] getPolygons() {
+ return this.polygons;
+ }
+
+ public record Polygon(Vertex[] vertices, Vector3fc normal) {
+
+ public Polygon(Vertex[] vertices, float u0, float v0, float u1, float v1, float uSize, float vSize, boolean mirror, Direction direction) {
+ this(vertices, (mirror ? mirrorFacing(direction) : direction).getUnitVec3f());
+
+ // This will force NaN if uSize or vSize are 0
+ float l = 0.0F / uSize;
+ float m = 0.0F / vSize;
+
+ vertices[0] = vertices[0].remap(u1 / uSize - l, v0 / vSize + m);
+ vertices[1] = vertices[1].remap(u0 / uSize + l, v0 / vSize + m);
+ vertices[2] = vertices[2].remap(u0 / uSize + l, v1 / vSize - m);
+ vertices[3] = vertices[3].remap(u1 / uSize - l, v1 / vSize - m);
+
+ if (mirror) {
+ int n = vertices.length;
+
+ for (int o = 0; o < n / 2; o++) {
+ Vertex vertex = vertices[o];
+ vertices[o] = vertices[n - 1 - o];
+ vertices[n - 1 - o] = vertex;
+ }
+ }
+ }
+
+ private static Direction mirrorFacing(Direction direction) {
+ return direction.getAxis() == Direction.Axis.X ? direction.getOpposite() : direction;
+ }
+ }
+
+ public static class Vertex {
+ private static final float SCALE_FACTOR = 16.0F;
+
+ final Vector3f pos;
+ final Vector3f transformed;
+ float u, v;
+
+ public Vertex(float x, float y, float z, float u, float v) {
+ this.pos = new Vector3f(x / SCALE_FACTOR, y / SCALE_FACTOR, z / SCALE_FACTOR);
+ this.transformed = new Vector3f();
+ this.u = u;
+ this.v = v;
+ }
+
+ public Vertex(Vector3f pos, Vector3f transformed, float u, float v) {
+ this.pos = pos;
+ this.transformed = transformed;
+ this.u = u;
+ this.v = v;
+ }
+
+ Vertex remap(float u, float v) {
+ return new Vertex(this.pos, this.transformed, u, v);
+ }
+
+ public Vector3f pos() {
+ return transformed;
+ }
+
+ public float u() {
+ return u;
+ }
+
+ public float v() {
+ return v;
}
}
- public ModelPart.Polygon[] getPolygons() { return this.polygons; }
}
diff --git a/src/main/java/net/vulkanmod/render/model/quad/ModelQuad.java b/src/main/java/net/vulkanmod/render/model/quad/ModelQuad.java
index efeebc0bd..d87df233d 100644
--- a/src/main/java/net/vulkanmod/render/model/quad/ModelQuad.java
+++ b/src/main/java/net/vulkanmod/render/model/quad/ModelQuad.java
@@ -2,10 +2,18 @@
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.core.Direction;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
-public class ModelQuad implements QuadView {
+/**
+ * Only used by FluidRenderer
+ */
+public class ModelQuad implements ModelQuadView {
public static final int VERTEX_SIZE = 8;
+ public static int vertexOffset(int vertexIndex) {
+ return vertexIndex * VERTEX_SIZE;
+ }
+
private final int[] data = new int[4 * VERTEX_SIZE];
Direction direction;
@@ -58,6 +66,21 @@ public Direction getFacingDirection() {
return this.direction;
}
+ @Override
+ public Direction lightFace() {
+ return this.direction;
+ }
+
+ @Override
+ public QuadFacing getQuadFacing() {
+ return QuadFacing.UNDEFINED;
+ }
+
+ @Override
+ public int getNormal() {
+ return 0;
+ }
+
public float setX(int idx, float f) {
return this.data[vertexOffset(idx)] = Float.floatToRawIntBits(f);
}
@@ -88,8 +111,4 @@ public void setFlags(int f) {
public void setSprite(TextureAtlasSprite sprite) {
this.sprite = sprite;
}
-
- private static int vertexOffset(int vertexIndex) {
- return vertexIndex * VERTEX_SIZE;
- }
}
diff --git a/src/main/java/net/vulkanmod/render/model/quad/ModelQuadFlags.java b/src/main/java/net/vulkanmod/render/model/quad/ModelQuadFlags.java
index 9016cb4aa..bb1dc0cca 100644
--- a/src/main/java/net/vulkanmod/render/model/quad/ModelQuadFlags.java
+++ b/src/main/java/net/vulkanmod/render/model/quad/ModelQuadFlags.java
@@ -2,8 +2,6 @@
import net.minecraft.core.Direction;
-import static net.vulkanmod.render.model.quad.ModelQuad.VERTEX_SIZE;
-
public class ModelQuadFlags {
/**
* Indicates that the quad does not fully cover the given face for the model.
@@ -25,7 +23,7 @@ public static boolean contains(int flags, int mask) {
return (flags & mask) != 0;
}
- public static int getQuadFlags(int[] vertices, Direction face) {
+ public static int getQuadFlags(ModelQuadView quad, Direction face) {
float minX = 32.0F;
float minY = 32.0F;
float minZ = 32.0F;
@@ -35,9 +33,9 @@ public static int getQuadFlags(int[] vertices, Direction face) {
float maxZ = -32.0F;
for (int i = 0; i < 4; ++i) {
- float x = Float.intBitsToFloat(vertices[i * VERTEX_SIZE]);
- float y = Float.intBitsToFloat(vertices[i * VERTEX_SIZE + 1]);
- float z = Float.intBitsToFloat(vertices[i * VERTEX_SIZE + 2]);
+ float x = quad.getX(i);
+ float y = quad.getY(i);
+ float z = quad.getZ(i);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
diff --git a/src/main/java/net/vulkanmod/render/model/quad/QuadView.java b/src/main/java/net/vulkanmod/render/model/quad/ModelQuadView.java
similarity index 71%
rename from src/main/java/net/vulkanmod/render/model/quad/QuadView.java
rename to src/main/java/net/vulkanmod/render/model/quad/ModelQuadView.java
index 883c4b315..ac791d9a5 100644
--- a/src/main/java/net/vulkanmod/render/model/quad/QuadView.java
+++ b/src/main/java/net/vulkanmod/render/model/quad/ModelQuadView.java
@@ -1,8 +1,9 @@
package net.vulkanmod.render.model.quad;
import net.minecraft.core.Direction;
+import net.vulkanmod.render.chunk.cull.QuadFacing;
-public interface QuadView {
+public interface ModelQuadView {
int getFlags();
@@ -22,6 +23,12 @@ public interface QuadView {
Direction getFacingDirection();
+ Direction lightFace();
+
+ QuadFacing getQuadFacing();
+
+ int getNormal();
+
default boolean isTinted() {
return this.getColorIndex() != -1;
}
diff --git a/src/main/java/net/vulkanmod/render/profiling/DebugEntryMemoryStats.java b/src/main/java/net/vulkanmod/render/profiling/DebugEntryMemoryStats.java
new file mode 100644
index 000000000..7742cc0e1
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/profiling/DebugEntryMemoryStats.java
@@ -0,0 +1,28 @@
+package net.vulkanmod.render.profiling;
+
+import net.minecraft.client.gui.components.debug.DebugScreenDisplayer;
+import net.minecraft.client.gui.components.debug.DebugScreenEntry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.vulkanmod.render.chunk.WorldRenderer;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class DebugEntryMemoryStats implements DebugScreenEntry {
+ private static final ResourceLocation GROUP = ResourceLocation.withDefaultNamespace("vk_memory");
+
+ @Override
+ public void display(DebugScreenDisplayer debugScreenDisplayer, @Nullable Level level,
+ @Nullable LevelChunk levelChunk, @Nullable LevelChunk levelChunk2) {
+ var chunkAreaManager = WorldRenderer.getInstance().getChunkAreaManager();
+
+ if (chunkAreaManager != null) {
+ debugScreenDisplayer.addToGroup(
+ GROUP,
+ List.of(chunkAreaManager.getStats())
+ );
+ }
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/profiling/Profiler.java b/src/main/java/net/vulkanmod/render/profiling/Profiler.java
index a5a34318c..ccfe5cb59 100644
--- a/src/main/java/net/vulkanmod/render/profiling/Profiler.java
+++ b/src/main/java/net/vulkanmod/render/profiling/Profiler.java
@@ -37,6 +37,7 @@ public static void setActive(boolean b) {
ObjectArrayList nodeStack = new ObjectArrayList<>();
ObjectArrayList nodes = new ObjectArrayList<>();
+ ObjectArrayList currentFrameNodes = new ObjectArrayList<>();
Object2ReferenceOpenHashMap nodeMap = new Object2ReferenceOpenHashMap<>();
Node mainNode;
@@ -67,7 +68,7 @@ public void push(String s) {
node.children.clear();
if (node.parent == selectedNode)
- nodes.add(node);
+ currentFrameNodes.add(node);
currentNode = node;
@@ -118,7 +119,10 @@ public void start() {
pushNodeStack(mainNode);
- nodes.clear();
+ var t = nodes;
+ nodes = currentFrameNodes;
+ currentFrameNodes = t;
+ currentFrameNodes.clear();
}
public void end() {
diff --git a/src/main/java/net/vulkanmod/render/profiling/ProfilerOverlay.java b/src/main/java/net/vulkanmod/render/profiling/ProfilerOverlay.java
index d75fcdbd4..ed85d1a97 100644
--- a/src/main/java/net/vulkanmod/render/profiling/ProfilerOverlay.java
+++ b/src/main/java/net/vulkanmod/render/profiling/ProfilerOverlay.java
@@ -1,17 +1,17 @@
package net.vulkanmod.render.profiling;
import com.google.common.base.Strings;
-import com.mojang.blaze3d.systems.RenderSystem;
-import com.mojang.blaze3d.vertex.DefaultVertexFormat;
-import com.mojang.blaze3d.vertex.VertexFormat;
+import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
-import net.vulkanmod.config.gui.GuiRenderer;
+import net.vulkanmod.Initializer;
+import net.vulkanmod.config.gui.render.GuiRenderer;
import net.vulkanmod.render.chunk.WorldRenderer;
import net.vulkanmod.render.chunk.build.task.ChunkTask;
import net.vulkanmod.render.chunk.build.thread.BuilderResources;
+import net.vulkanmod.vulkan.VRenderSystem;
import net.vulkanmod.vulkan.memory.MemoryManager;
import net.vulkanmod.vulkan.util.ColorUtil;
@@ -56,7 +56,6 @@ public static void onKeyPress(int key) {
public void render(GuiGraphics guiGraphics) {
GuiRenderer.guiGraphics = guiGraphics;
- GuiRenderer.pose = guiGraphics.pose();
List infoList = this.buildInfo();
@@ -67,8 +66,7 @@ public void render(GuiGraphics guiGraphics) {
Objects.requireNonNull(this.font);
- RenderSystem.enableBlend();
- GuiRenderer.beginBatch(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
+ VRenderSystem.enableBlend();
for (int i = 0; i < infoList.size(); ++i) {
String line = infoList.get(i);
@@ -82,8 +80,7 @@ public void render(GuiGraphics guiGraphics) {
}
}
- GuiRenderer.endBatch();
- RenderSystem.disableBlend();
+ VRenderSystem.disableBlend();
for (int i = 0; i < infoList.size(); ++i) {
String line = infoList.get(i);
@@ -101,16 +98,26 @@ private List buildInfo() {
List list = new ArrayList<>();
list.add("");
list.add("Profiler");
+ list.add("Version: %s %s ".formatted(Initializer.getVersion(), SharedConstants.getCurrentVersion().name()));
this.updateResults();
- if (lastResults == null)
+ if (lastResults == null) {
return list;
+ }
+
+ var partialResults = lastResults.getPartialResults();
+ if (partialResults.size() < 2) {
+ return list;
+ }
int fps = Math.round(1000.0f / frametime);
list.add(String.format("FPS: %d Frametime: %.3f", fps, frametime));
list.add("");
+ list.add(String.format("CPU fence wait time: %.3f", partialResults.get(1).value));
+ list.add("");
+
for (Profiler.Result result : lastResults.getPartialResults()) {
list.add(String.format("%s: %.3f", result.name, result.value));
}
@@ -123,8 +130,9 @@ private List buildInfo() {
list.add("");
list.add(String.format("Build time: %.0fms", BuildTimeProfiler.getDeltaTime()));
- if (ChunkTask.BENCH)
+ if (ChunkTask.BENCH) {
list.add(buildStats);
+ }
return list;
}
diff --git a/src/main/java/net/vulkanmod/render/shader/CustomRenderPipelines.java b/src/main/java/net/vulkanmod/render/shader/CustomRenderPipelines.java
new file mode 100644
index 000000000..07ca57f3c
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/shader/CustomRenderPipelines.java
@@ -0,0 +1,31 @@
+package net.vulkanmod.render.shader;
+
+import com.mojang.blaze3d.pipeline.BlendFunction;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.platform.DepthTestFunction;
+import com.mojang.blaze3d.vertex.DefaultVertexFormat;
+import com.mojang.blaze3d.vertex.VertexFormat;
+import net.minecraft.client.renderer.RenderPipelines;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CustomRenderPipelines {
+
+ public static final List pipelines = new ArrayList<>();
+
+ public static final RenderPipeline.Snippet GUI_TRIANGLES_SNIPPET = RenderPipeline.builder(RenderPipelines.MATRICES_PROJECTION_SNIPPET)
+ .withVertexShader("core/gui")
+ .withFragmentShader("core/gui")
+ .withBlend(BlendFunction.TRANSLUCENT)
+ .withVertexFormat(DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.TRIANGLES)
+ .withDepthTestFunction(DepthTestFunction.NO_DEPTH_TEST)
+ .buildSnippet();
+
+ public static final RenderPipeline GUI_TRIANGLES = register(RenderPipeline.builder(GUI_TRIANGLES_SNIPPET).withLocation("pipeline/gui").build());
+
+ static RenderPipeline register(RenderPipeline pipeline) {
+ pipelines.add(pipeline);
+ return pipeline;
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/shader/ShaderLoadUtil.java b/src/main/java/net/vulkanmod/render/shader/ShaderLoadUtil.java
new file mode 100644
index 000000000..0a224062f
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/shader/ShaderLoadUtil.java
@@ -0,0 +1,259 @@
+package net.vulkanmod.render.shader;
+
+import com.google.common.collect.Sets;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.blaze3d.shaders.ShaderType;
+import net.minecraft.resources.ResourceLocation;
+import net.vulkanmod.vulkan.shader.Pipeline;
+import net.vulkanmod.vulkan.shader.SPIRVUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+
+public abstract class ShaderLoadUtil {
+
+ public static final String RESOURCES_PATH = SPIRVUtils.class.getResource("/assets/vulkanmod").toExternalForm();
+ public static final String SHADERS_PATH = "%s/shaders/".formatted(RESOURCES_PATH);
+
+ public static final Set REMAPPED_SHADERS = Sets.newHashSet("core/screenquad.vsh","core/rendertype_item_entity_translucent_cull.vsh");
+
+ public static String resolveShaderPath(String path) {
+ return resolveShaderPath(SHADERS_PATH, path);
+ }
+
+ public static String resolveShaderPath(String shaderPath, String path) {
+ return "%s%s".formatted(shaderPath, path);
+ }
+
+ public static void loadShaders(Pipeline.Builder pipelineBuilder, JsonObject config, String configName, String path) {
+ String vertexShader = config.has("vertex") ? config.get("vertex").getAsString() : configName;
+ String fragmentShader = config.has("fragment") ? config.get("fragment").getAsString() : configName;
+
+ if (vertexShader == null) {
+ vertexShader = configName;
+ }
+ if (fragmentShader == null) {
+ fragmentShader = configName;
+ }
+
+ vertexShader = removeNameSpace(vertexShader);
+ fragmentShader = removeNameSpace(fragmentShader);
+
+ vertexShader = getFileName(vertexShader);
+ fragmentShader = getFileName(fragmentShader);
+
+ loadShader(pipelineBuilder, configName, path, vertexShader, SPIRVUtils.ShaderKind.VERTEX_SHADER);
+ loadShader(pipelineBuilder, configName, path, fragmentShader, SPIRVUtils.ShaderKind.FRAGMENT_SHADER);
+ }
+
+ public static void loadShader(Pipeline.Builder pipelineBuilder, String configName, String path, SPIRVUtils.ShaderKind type) {
+ String[] splitPath = splitPath(path);
+ String shaderName = splitPath[1];
+ String subPath = splitPath[0];
+
+ loadShader(pipelineBuilder, configName, subPath, shaderName, type);
+ }
+
+ public static void loadShader(Pipeline.Builder pipelineBuilder, String configName, String path, String shaderName, SPIRVUtils.ShaderKind type) {
+ String source = getShaderSource(path, configName, shaderName, type);
+
+ SPIRVUtils.SPIRV spirv = SPIRVUtils.compileShader(shaderName, source, type);
+
+ switch (type) {
+ case VERTEX_SHADER -> pipelineBuilder.setVertShaderSPIRV(spirv);
+ case FRAGMENT_SHADER -> pipelineBuilder.setFragShaderSPIRV(spirv);
+ }
+ }
+
+ public static String getConfigFilePath(String path, String rendertype) {
+ String basePath = "%s/shaders/%s".formatted(RESOURCES_PATH, path);
+ String configPath = "%s/%s/%s.json".formatted(basePath, rendertype, rendertype);
+
+ Path filePath;
+ try {
+ filePath = FileSystems.getDefault().getPath(configPath);
+
+ if (!Files.exists(filePath)) {
+ configPath = "%s/%s.json".formatted(basePath, rendertype);
+ filePath = FileSystems.getDefault().getPath(configPath);
+ }
+
+ if (!Files.exists(filePath)) {
+ return null;
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+
+ return filePath.toString();
+ }
+
+ public static JsonObject getJsonConfig(String path, String rendertype) {
+ // Check for external shader
+ if (rendertype.contains(String.valueOf(ResourceLocation.NAMESPACE_SEPARATOR))) {
+ return null;
+ }
+
+ String basePath = path;
+ String configPath = "%s/%s/%s.json".formatted(basePath, rendertype, rendertype);
+
+ InputStream stream;
+ try {
+ stream = getInputStream(configPath);
+
+ if (stream == null) {
+ configPath = "%s/%s.json".formatted(basePath, rendertype);
+ stream = getInputStream(configPath);
+ }
+
+ if (stream == null) {
+ return null;
+ }
+
+ JsonElement jsonElement = JsonParser.parseReader(new BufferedReader(new InputStreamReader(stream)));
+ stream.close();
+
+ return (JsonObject) jsonElement;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public static String getShaderSource(ResourceLocation resourceLocation, ShaderType type) {
+ String shaderExtension = switch (type) {
+ case VERTEX -> ".vsh";
+ case FRAGMENT -> ".fsh";
+ };
+
+ String path = resourceLocation.getPath();
+ String[] splitPath = splitPath(path);
+ String shaderName = "%s%s".formatted(splitPath[1], shaderExtension);
+ String shaderFile = "%s/shaders/%s/%s".formatted(RESOURCES_PATH, path, shaderName);
+
+ InputStream stream;
+ try {
+ stream = getInputStream(shaderFile);
+
+ if (stream == null) {
+ return null;
+ }
+
+ String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
+ stream.close();
+
+ return source;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String getShaderSource(String path, ShaderType type) {
+ String shaderExtension = switch (type) {
+ case VERTEX -> ".vsh";
+ case FRAGMENT -> ".fsh";
+ };
+
+ String[] splitPath = splitPath(path);
+ String shaderName = "%s%s".formatted(splitPath[1], shaderExtension);
+
+ String shaderFile = "%s/shaders/%s/%s".formatted(RESOURCES_PATH, path, shaderName);
+
+ InputStream stream;
+ try {
+ stream = getInputStream(shaderFile);
+ String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
+ stream.close();
+
+ return source;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String getShaderSource(String path, String configName, String shaderName, SPIRVUtils.ShaderKind type) {
+ String shaderExtension = switch (type) {
+ case VERTEX_SHADER -> ".vsh";
+ case FRAGMENT_SHADER -> ".fsh";
+ case COMPUTE_SHADER -> ".comp";
+ default -> throw new UnsupportedOperationException("shader type %s unsupported");
+ };
+
+ String basePath = path;
+
+ String shaderPath = "/%s/%s".formatted(configName, configName);
+ String shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
+
+ InputStream stream;
+ try {
+ stream = getInputStream(shaderFile);
+
+ if (stream == null) {
+ shaderPath = "/%s".formatted(shaderName);
+ shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
+ stream = getInputStream(shaderFile);
+ }
+
+ if (stream == null) {
+ shaderPath = "/%s/%s".formatted(configName, shaderName);
+ shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
+ stream = getInputStream(shaderFile);
+ }
+
+ if (stream == null) {
+ shaderPath = "/%s/%s".formatted(shaderName, shaderName);
+ shaderFile = "%s%s%s".formatted(basePath, shaderPath, shaderExtension);
+ stream = getInputStream(shaderFile);
+ }
+
+ if (stream == null) {
+ return null;
+ }
+
+ String source = IOUtils.toString(new BufferedReader(new InputStreamReader(stream)));
+ stream.close();
+
+ return source;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String getFileName(String path) {
+ int idx = path.lastIndexOf('/');
+ return idx > -1 ? path.substring(idx + 1) : path;
+ }
+
+ public static String removeNameSpace(String path) {
+ int idx = path.indexOf(':');
+ return idx > -1 ? path.substring(idx + 1) : path;
+ }
+
+ public static String[] splitPath(String path) {
+ int idx = path.lastIndexOf('/');
+
+ return new String[] {path.substring(0, idx), path.substring(idx + 1)};
+ }
+
+ public static InputStream getInputStream(String path) {
+ try {
+ var path1 = Paths.get(new URI(path));
+
+ if (!Files.exists(path1))
+ return null;
+
+ return Files.newInputStream(path1);
+ } catch (URISyntaxException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/net/vulkanmod/render/sky/CloudRenderer.java b/src/main/java/net/vulkanmod/render/sky/CloudRenderer.java
new file mode 100644
index 000000000..60943f029
--- /dev/null
+++ b/src/main/java/net/vulkanmod/render/sky/CloudRenderer.java
@@ -0,0 +1,407 @@
+package net.vulkanmod.render.sky;
+
+import com.mojang.blaze3d.opengl.GlStateManager;
+import com.mojang.blaze3d.platform.NativeImage;
+import com.mojang.blaze3d.systems.RenderSystem;
+import com.mojang.blaze3d.vertex.*;
+import net.minecraft.client.CloudStatus;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.server.packs.resources.ResourceManager;
+import net.minecraft.util.Mth;
+import net.minecraft.world.phys.Vec3;
+import net.vulkanmod.render.PipelineManager;
+import net.vulkanmod.render.VBO;
+import net.vulkanmod.vulkan.Renderer;
+import net.vulkanmod.vulkan.VRenderSystem;
+import net.vulkanmod.vulkan.shader.GraphicsPipeline;
+import net.vulkanmod.vulkan.util.ColorUtil;
+import org.apache.commons.lang3.Validate;
+import org.joml.Matrix4fStack;
+import org.lwjgl.opengl.GL11;
+
+import java.io.IOException;
+import java.util.Optional;
+
+public class CloudRenderer {
+ private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png");
+
+ private static final int DIR_NEG_Y_BIT = 1 << 0;
+ private static final int DIR_POS_Y_BIT = 1 << 1;
+ private static final int DIR_NEG_X_BIT = 1 << 2;
+ private static final int DIR_POS_X_BIT = 1 << 3;
+ private static final int DIR_NEG_Z_BIT = 1 << 4;
+ private static final int DIR_POS_Z_BIT = 1 << 5;
+
+ private static final byte Y_BELOW_CLOUDS = 0;
+ private static final byte Y_ABOVE_CLOUDS = 1;
+ private static final byte Y_INSIDE_CLOUDS = 2;
+
+ private static final int CELL_WIDTH = 12;
+ private static final int CELL_HEIGHT = 4;
+
+ private CloudGrid cloudGrid;
+
+ private int prevCloudX;
+ private int prevCloudZ;
+ private byte prevCloudY;
+
+ private CloudStatus prevCloudsType;
+
+ private boolean generateClouds;
+ private VBO cloudBuffer;
+
+ public CloudRenderer() {
+ loadTexture();
+ }
+
+ public void loadTexture() {
+ this.cloudGrid = createCloudGrid(TEXTURE_LOCATION);
+ }
+
+ public void renderClouds(ClientLevel level, float ticks, float partialTicks, double camX, double camY, double camZ) {
+ Optional