Compare commits

..
5 Commits
55 changed files with 1176 additions and 282 deletions

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ on:
jobs:
build-and-test:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@05e7cd7ce0ea015acda8c10c1c39fb3a20a49e60
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
with:
disable-spotless-pr: true
java-distribution: temurin
+8
View File
@@ -170,7 +170,13 @@ val archScanAsm: Configuration by configurations.creating {
isCanBeResolved = true
}
val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
archScanAsm(libs.asm.tree.test)
archScanAsm(libs.asm.commons.test)
}
@@ -212,6 +218,8 @@ val glCoreTest by tasks.registering(Test::class) {
.plus(downgraded(downgradeRootMainClasses))
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
javaAgent(glsmRedirectAgent)
configureAngelicaJava8()
}
+8
View File
@@ -0,0 +1,8 @@
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.testing.Test
import org.gradle.process.CommandLineArgumentProvider
fun Test.javaAgent(jar: FileCollection) {
inputs.files(jar).withPropertyName("javaAgent")
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${jar.singleFile.path}") })
}
+17
View File
@@ -267,6 +267,22 @@ tasks.test {
configureGlsmJava8()
}
val redirectAgentJar by tasks.registering(Jar::class) {
archiveClassifier = "redirect-agent"
manifest {
attributes("Premain-Class" to "com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent")
}
}
val redirectAgent by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(redirectAgent.name, redirectAgentJar)
}
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
tasks.register<Test>(taskName) {
description = "Runs the $tag GL tests in their own JVM, so they get their own Display and GL profile."
@@ -275,6 +291,7 @@ val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
includeTags = setOf(tag)
}
configureGlsmJava8()
javaAgent(files(redirectAgentJar))
}
}
@@ -5644,6 +5644,9 @@ public class GLStateManager {
return true;
}
switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_TEXTURE_WRAP_S, GL11.GL_TEXTURE_WRAP_T -> {
return updateTexParameteriCache(info, pname, (int) param);
}
case EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> {
if (info.getMaxAnisotropy() == param && isCachingEnabled()) return false;
info.setMaxAnisotropy(param);
@@ -5891,7 +5894,10 @@ public class GLStateManager {
}
TextureInfoCache.INSTANCE.onTexStorage2D(target, internalFormat, width, height);
}
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) {
RENDER_BACKEND.clearTexImage(texture, level, format, type);
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
}
public static int glGenSamplers() {
requireContext("glGenSamplers");
@@ -6754,6 +6760,7 @@ public class GLStateManager {
throw DisplayListManager.unsupportedInList("glCopyTexImage2D");
}
RENDER_BACKEND.copyTexImage2D(target, level, internalFormat, x, y, width, height, border);
TextureInfoCache.INSTANCE.onCopyTexImage2D(target, level, internalFormat, width, height);
maybeGenerateMipmap(target, level);
}
@@ -7875,10 +7882,18 @@ public class GLStateManager {
}
public static void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
RENDER_BACKEND.framebufferTexture2D(target, attachment, textarget, texture, level);
}
public static void glFramebufferTexture(int target, int attachment, int texture, int level) { RENDER_BACKEND.framebufferTexture(target, attachment, texture, level); }
public static void glFramebufferTexture(int target, int attachment, int texture, int level) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
RENDER_BACKEND.framebufferTexture(target, attachment, texture, level);
}
public static void attachTextureForReading(int attachment, int texture) {
RENDER_BACKEND.framebufferTexture2D(GL30.GL_READ_FRAMEBUFFER, attachment, GL11.GL_TEXTURE_2D, texture, 0);
}
public static int glGenRenderbuffers() {
requireContext("glGenRenderbuffers");
@@ -8699,6 +8714,7 @@ public class GLStateManager {
binding.setBinding(texture, level, layered, layer, access, format);
if (texture != 0 && unit > glCtx.maxBoundImageUnit) glCtx.maxBoundImageUnit = unit;
}
if (access != GL15.GL_READ_ONLY) TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
}
@@ -8752,6 +8768,7 @@ public class GLStateManager {
int srcWidth, int srcHeight, int srcDepth) {
RENDER_BACKEND.copyImageSubData(srcName, srcTarget, srcLevel, srcX, srcY, srcZ,
dstName, dstTarget, dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
if (dstTarget != GL30.GL_RENDERBUFFER && dstLevel == 0) TextureInfoCache.INSTANCE.onContentsChanged(dstName);
}
public static void glDispatchCompute(int x, int y, int z) { RENDER_BACKEND.dispatchCompute(x, y, z); }
@@ -267,6 +267,7 @@ public class RenderSystem {
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
if (i == 0) TextureInfoCache.INSTANCE.onContentsChanged(destTexture);
}
public static void texParameteri(int texture, int target, int pname, int param) {
@@ -281,24 +282,28 @@ public class RenderSystem {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
@@ -358,6 +363,7 @@ public class RenderSystem {
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
@@ -5,6 +5,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
private static final float MIN_SPAN = 1.0e-4f;
private static final float UV_EPSILON = 1.0e-5f;
public CubeParams {
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
@@ -43,12 +44,25 @@ public record CubeParams(float minX, float minY, float minZ, float spanX, float
}
}
public void writeTexture(long ptr) {
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
public boolean uvInsideTexture() {
final float u1 = u0 + 2.0f * (Math.abs(uScaleX) + uScaleZ);
final float v1 = v0 + vScaleY + vScaleZ;
return u0 >= 0.0f && v0 >= 0.0f && u1 <= 1.0f + UV_EPSILON && v1 <= 1.0f + UV_EPSILON;
}
public static boolean allInsideTexture(CubeParams[] cubes) {
for (CubeParams cube : cubes) {
if (!cube.uvInsideTexture()) return false;
}
return true;
}
public void writeTexture(long ptr, float regionU, float regionV, float regionW, float regionH) {
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ * regionH);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, regionU + u0 * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, regionV + v0 * regionH);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY * regionH);
}
}
@@ -22,8 +22,8 @@ public class TextureInfo {
protected int width = -1;
protected int height = -1;
@Setter protected int minFilter = UNSET_INT;
@Setter protected int magFilter = UNSET_INT;
protected int minFilter = UNSET_INT;
protected int magFilter = UNSET_INT;
@Setter protected int wrapS = UNSET_INT;
@Setter protected int wrapT = UNSET_INT;
@Setter protected int maxLevel = UNSET_INT;
@@ -37,9 +37,12 @@ public class TextureInfo {
@Getter @Setter protected boolean generateMipmap = false;
@Getter protected int uploadGeneration;
@Getter protected boolean writtenOutsideUploads;
@Getter protected int changeStamp;
void bumpUploadGeneration() {
uploadGeneration++;
changeStamp++;
}
protected TextureInfo(int id) {
@@ -78,6 +81,16 @@ public class TextureInfo {
return height;
}
public void setMinFilter(int minFilter) {
this.minFilter = minFilter;
changeStamp++;
}
public void setMagFilter(int magFilter) {
this.magFilter = magFilter;
changeStamp++;
}
public int getMinFilter() {
if (minFilter == UNSET_INT) minFilter = queryParameteri(GL11.GL_TEXTURE_MIN_FILTER);
return minFilter;
@@ -14,7 +14,10 @@ import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntConsumer;
public class TextureInfoCache {
/**
@@ -28,10 +31,45 @@ public class TextureInfoCache {
private final Int2ObjectMap<TextureInfo> cache = new Int2ObjectOpenHashMap<>();
private final ReentrantLock lock = new ReentrantLock();
private final List<IntConsumer> deleteListeners = new ArrayList<>();
private int deleteCount;
private TextureInfoCache() {
}
public int deleteCount() {
return deleteCount;
}
public void addDeleteListener(IntConsumer listener) {
deleteListeners.add(listener);
}
public void onContentsChanged(int texture) {
if (texture <= 0) return;
final TextureInfo info = getInfo(texture);
if (info != null) info.bumpUploadGeneration();
}
public void onWrittenOutsideUploads(int texture) {
if (texture <= 0) return;
final TextureInfo info = getInfo(texture);
if (info == null || info.writtenOutsideUploads) return;
info.writtenOutsideUploads = true;
info.changeStamp++;
}
private void defineLevelZero(int texture, int target, int internalformat, int width, int height) {
final TextureInfo info = getInfo(texture);
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
public TextureInfo getInfo(int id) {
if (id < 0) return null;
@@ -48,48 +86,27 @@ public class TextureInfoCache {
}
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
if (isTrackedTarget(target) && level == 0) {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
if (isTrackedTarget(target) && level == 0) {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onCopyTexImage2D(int target, int level, int internalformat, int width, int height) {
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTextureImage2D(int texture, int target, int level, int internalformat, int width, int height) {
if (isTrackedTarget(target) && level == 0) defineLevelZero(texture, target, internalformat, width, height);
}
public void onTexStorage2D(int target, int internalformat, int width, int height) {
if (!isTrackedTarget(target)) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
if (isTrackedTarget(target)) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTexSubImage2D(int target, int level) {
if (!isTrackedTarget(target) || level != 0) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.bumpUploadGeneration();
if (isTrackedTarget(target) && level == 0) onContentsChanged(GLStateManager.getBoundTextureForServerState());
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
@@ -131,9 +148,11 @@ public class TextureInfoCache {
public void onDeleteTexture(int id) {
if (id < 0) return;
deleteCount++;
if (GLStateManager.isSplashComplete()) {
cache.remove(id);
for (IntConsumer listener : deleteListeners) listener.accept(id);
return;
}
@@ -18,7 +18,7 @@ public class GLSM_VAO_UnitTest {
@AfterEach
void resetClientArrayState() {
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLSMUtil.setClientState(GL11.GL_NORMAL_ARRAY, false);
}
@@ -66,7 +66,7 @@ public class GLSM_VAO_UnitTest {
int vao = UniversalVAO.genVertexArrays();
Assertions.assertTrue(vao > 0, "GL_VERTEX_ARRAY_BINDING - VAO Created");
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, vao, "GL_VERTEX_ARRAY_BINDING - Bound");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
@@ -91,7 +91,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Check if everything got reset to default
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, 0, "GL_ARRAY_BUFFER_BINDING - Unbound");
@@ -103,18 +103,18 @@ public class GLSM_VAO_UnitTest {
verifyState(GL11.GL_NORMAL_ARRAY, true, "GL_NORMAL_ARRAY - Reset");
// Check if the VAO states are still valid after re-binding it
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
verifyState(GL11.GL_VERTEX_ARRAY, vaoVertexEnabled, "GL_VERTEX_ARRAY - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_SIZE, vaoVertexSize, "GL_VERTEX_ARRAY_SIZE - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_TYPE, vaoVertexType, "GL_VERTEX_ARRAY_TYPE - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_STRIDE, vaoVertexStride, "GL_VERTEX_ARRAY_STRIDE - Preserved");
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glPopAttrib();
@@ -123,7 +123,7 @@ public class GLSM_VAO_UnitTest {
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, vbo, "GL_ARRAY_BUFFER_BINDING - Not affected by glPushAttrib");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
@@ -132,7 +132,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
@@ -145,7 +145,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -153,7 +153,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
@@ -166,7 +166,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -174,7 +174,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testFfpConventionalAppliesWithoutGenericAttribute() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
@@ -186,7 +186,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -198,12 +198,4 @@ public class GLSM_VAO_UnitTest {
verifyState(GL11.GL_COLOR_ARRAY, false, "GL_COLOR_ARRAY Default State");
verifyState(GL11.GL_INDEX_ARRAY, false, "GL_INDEX_ARRAY Default State");
}
private static void glBindVertexArray(int array) {
final GLContextState glCtx = GLStateManager.ctx();
if (array == 0) array = glCtx.defaultVAO;
glCtx.boundVAO = array;
glCtx.vaos.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -35,7 +35,6 @@ class WideLineClipGLTest {
@BeforeEach
void setUp() {
FfpFixture.routeVertexFormats(true);
savedEmulation = GLStateManager.wideLineEmulationEnabled;
GLStateManager.wideLineEmulationEnabled = true;
@@ -54,7 +53,6 @@ class WideLineClipGLTest {
@AfterEach
void tearDown() {
FfpFixture.routeVertexFormats(false);
GLStateManager.wideLineEmulationEnabled = savedEmulation;
GLStateManager.glLineWidth(1.0f);
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_ERR">
<Console name="Console" target="SYSTEM_ERR" follow="true">
<PatternLayout pattern="[%-5level] %logger{36} - %msg%n"/>
</Console>
</Appenders>
@@ -28,6 +28,7 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
GlsmRedirectAgent.requireInstalled();
if (startFailure != null) {
throw new IllegalStateException("GLSMCoreExtension context setup failed earlier in this JVM", startFailure);
}
@@ -81,6 +82,7 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
public void afterEach(ExtensionContext context) {
final int error = GL11.glGetError();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
GlsmRedirectAgent.assertNoFailures();
assertEquals(GL11.GL_NO_ERROR, error, () -> "GL Error: 0x" + Integer.toHexString(error));
}
@@ -61,6 +61,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
GlsmRedirectAgent.requireInstalled();
if (startFailure != null) {
throw new IllegalStateException("GLSMExtension context setup failed earlier in this JVM", startFailure);
}
@@ -179,6 +180,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
fail("Test leaked an open display-list recorder! Missing glEndList.");
}
GlsmRedirectAgent.assertNoFailures();
final int error = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, error,
() -> "GL Error: 0x" + Integer.toHexString(error));
@@ -0,0 +1,76 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.loading.ClassDump;
import com.gtnewhorizons.angelica.glsm.loading.ModRedirector;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.fail;
public final class GlsmRedirectAgent {
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
private static final List<Throwable> FAILURES = new CopyOnWriteArrayList<>();
private static final AtomicInteger TRANSFORMED = new AtomicInteger();
private static boolean installed;
private GlsmRedirectAgent() {}
public static void premain(String args, Instrumentation instrumentation) {
install(instrumentation);
}
public static void install(Instrumentation instrumentation) {
final ModRedirector redirector = new ModRedirector(ClassDump.DISABLED);
addTransformer(instrumentation, (className, classfile) -> {
if (!className.startsWith(GTNHLIB)) return null;
final byte[] out = redirector.transform(className.replace('/', '.'), classfile, null);
if (out == classfile) return null;
TRANSFORMED.incrementAndGet();
return out;
});
installed = true;
}
public static void addTransformer(Instrumentation instrumentation, BiFunction<String, byte[], byte[]> transform) {
instrumentation.addTransformer(new Guarded(transform));
}
public static void requireInstalled() {
if (!installed) throw new IllegalStateException("GL tests need the GLSM redirect agent");
}
public static void assertNoFailures() {
if (!FAILURES.isEmpty()) fail("agent transform failures: " + FAILURES, FAILURES.get(0));
}
public static int transformedCount() {
return TRANSFORMED.get();
}
private static final class Guarded implements ClassFileTransformer {
private final BiFunction<String, byte[], byte[]> transform;
private Guarded(BiFunction<String, byte[], byte[]> transform) {
this.transform = transform;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
if (className == null) return null;
try {
return transform.apply(className, classfile);
} catch (Throwable t) {
FAILURES.add(new IllegalStateException("transforming " + className, t));
return null;
}
}
}
}
@@ -260,7 +260,7 @@ public final class CubeParityFixture {
InstancedAttribs.writeTail(ptr, 0xFFFFFFFF, 0, 0L);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, BRIGHTNESS);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 4, BRIGHTNESS);
cube.writeTexture(ptr);
cube.writeTexture(ptr, 0.0f, 0.0f, 1.0f, 1.0f);
}
records.position(0).limit(specs.length * CubeInstancedAttribs.STRIDE);
@@ -1,8 +1,5 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import org.lwjgl.BufferUtils;
@@ -136,27 +133,6 @@ public final class FfpFixture {
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, stride, offset);
}
private static boolean formatRoutingRegistered;
private static boolean routeFormats;
public static void routeVertexFormats(boolean enabled) {
routeFormats = enabled;
if (formatRoutingRegistered) return;
formatRoutingRegistered = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
if (!routeFormats) return false;
final int stride = format.getVertexSize();
int elementOffset = (int) offset;
for (VertexFormatElement element : format.elementsArray) {
final Usage usage = element.getUsage();
final boolean normalized = usage == Usage.COLOR || usage == Usage.NORMAL;
attrib(usage.getAttributeLocation(), element.getCount(), element.getType().getGlType(), normalized, stride, elementOffset);
elementOffset += element.getByteSize();
}
return true;
});
}
public static void clear() {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
+3 -3
View File
@@ -108,6 +108,7 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
dependencies {
"glsmSdlAgentCompileOnly"(project(":glsm"))
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
testImplementation(glsmSdlAgent.output)
}
@@ -155,9 +156,8 @@ val glsmSdlTest by tasks.registering(Test::class) {
useJUnitPlatform {
includeTags(glsmSdlTag)
}
dependsOn(tasks.named("glsmSdlStubsClasses"), glsmSdlRedirectAgentJar)
val agentJarPath = glsmSdlRedirectAgentJar.flatMap { it.archiveFile }.map { it.asFile.path }
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${agentJarPath.get()}") })
dependsOn(tasks.named("glsmSdlStubsClasses"))
javaAgent(files(glsmSdlRedirectAgentJar))
jvmArgs(
"-Dangelica.sdlgpu.enable=true",
"-Dangelica.sdlgpu.disablePresenterThread=true",
@@ -1,60 +1,15 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public final class GlsmSdlRedirectAgent {
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
private static final List<String> FAILURES = Collections.synchronizedList(new ArrayList<>());
private static final AtomicInteger TRANSFORMED_COUNT = new AtomicInteger();
private GlsmSdlRedirectAgent() {}
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new Redirector());
}
public static List<String> failures() {
return List.copyOf(FAILURES);
}
public static int transformedCount() {
return TRANSFORMED_COUNT.get();
}
private static final class Redirector implements ClassFileTransformer {
private final GLSMRedirector redirector = new GLSMRedirector();
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
if (className == null) return null;
try {
final byte[] observed = MovedSamplerProbe.instrument(className, classfile);
if (observed != null) return observed;
if (!className.startsWith(GTNHLIB) || !redirector.shouldTransform(classfile)) return null;
final ClassNode node = new ClassNode();
new ClassReader(classfile).accept(node, 0);
if (!redirector.transformClassNode(className.replace('/', '.'), node)) return null;
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
node.accept(writer);
TRANSFORMED_COUNT.incrementAndGet();
return writer.toByteArray();
} catch (Throwable t) {
FAILURES.add(className + ": " + t);
return null;
}
}
GlsmRedirectAgent.install(instrumentation);
GlsmRedirectAgent.addTransformer(instrumentation, MovedSamplerProbe::instrument);
}
}
@@ -1881,6 +1881,8 @@ public class SDLGPURenderBackend extends RenderBackend {
}
if (cs.boundFboId == framebuffer) return;
final FboState drawing = framebuffer != 0 ? resourceManager.getFbo(framebuffer) : null;
if (drawing != null && drawing.readOnlyColor) makeColorTargets(drawing);
final int oldFboId = cs.boundFboId;
final boolean structurallyIdentical = oldFboId != 0 && framebuffer != 0 && fboClearTracker.fbosHaveSameAttachments(cs, oldFboId, framebuffer);
cs.boundFboId = framebuffer;
@@ -1893,36 +1895,52 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.setColorTargetFormats(cachedSwapchainFormatArray);
cs.pipeline.setDrawBuffers(null);
cs.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
} else {
final FboState fbo = resourceManager.getFbo(framebuffer);
if (fbo != null && fbo.getColorTexture() != 0) {
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
cs.pipeline.setDepthTargetFormat(fbo.depthTexture != 0 ? fbo.depthFormat : 0);
}
} else if (drawing != null && drawing.getColorTexture() != 0) {
fboClearTracker.updatePipelineCacheColorFormats(cs, drawing);
cs.pipeline.setDepthTargetFormat(drawing.depthTexture != 0 ? drawing.depthFormat : 0);
}
cs.pipeline.markOutputDirty();
}
private void makeColorTargets(FboState fbo) {
for (int i = 0; i < fbo.colorAttachmentCount; i++) {
final int glId = fbo.colorGlIds[i];
if (glId == 0) continue;
fbo.colorTextures[i] = resourceManager.ensureTextureUsage(glId, SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
resourceManager.markFboAttachment(glId);
}
fbo.readOnlyColor = false;
fbo.targetsDirty = true;
}
@Override public void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
final ContextState cs = s();
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
final int fboId = target == GL30.GL_READ_FRAMEBUFFER ? cs.boundReadFboId : cs.boundFboId;
if (fboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(fboId);
if (fbo == null) return;
final boolean drawTarget = fboId == cs.boundFboId;
final int colorIdx = attachment - GL30.GL_COLOR_ATTACHMENT0;
if (colorIdx >= 0 && colorIdx < ContextState.MAX_COLOR_ATTACHMENTS) {
if (texture == 0) {
fbo.detachColor(colorIdx);
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
cs.pipeline.markOutputDirty();
if (drawTarget) {
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
cs.pipeline.markOutputDirty();
}
return;
}
final long handle = resourceManager.ensureTextureUsage(texture, SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
final long handle = resourceManager.ensureTextureUsage(texture, drawTarget ? SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET : SDL_GPU_TEXTUREUSAGE_SAMPLER);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
fbo.colorTextures[colorIdx] = handle;
fbo.colorGlIds[colorIdx] = texture;
fbo.colorLevels[colorIdx] = level;
resourceManager.markFboAttachment(texture);
if (drawTarget) {
resourceManager.markFboAttachment(texture);
} else {
fbo.readOnlyColor = true;
}
fbo.targetsDirty = true;
if (meta != null) {
fbo.colorFormats[colorIdx] = meta.sdlFormat();
@@ -1933,16 +1951,20 @@ public class SDLGPURenderBackend extends RenderBackend {
}
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
fbo.cachedFormatsDirty = true;
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
cs.pipeline.markOutputDirty();
if (drawTarget) {
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
cs.pipeline.markOutputDirty();
}
} else if (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT) {
final boolean stencilOnly = attachment == GL30.GL_STENCIL_ATTACHMENT;
if (texture == 0) {
if (stencilOnly && fbo.depthGlId != 0) return;
fbo.detachDepth();
cs.pipeline.hasDepthTarget = false;
cs.pipeline.depthTargetFormat = 0;
cs.pipeline.markOutputDirty();
if (drawTarget) {
cs.pipeline.hasDepthTarget = false;
cs.pipeline.depthTargetFormat = 0;
cs.pipeline.markOutputDirty();
}
return;
}
if (stencilOnly && fbo.depthGlId != 0 && fbo.depthGlId != texture) {
@@ -1962,9 +1984,11 @@ public class SDLGPURenderBackend extends RenderBackend {
if (meta != null) {
fbo.depthFormat = meta.sdlFormat();
}
cs.pipeline.hasDepthTarget = true;
cs.pipeline.depthTargetFormat = fbo.depthFormat;
cs.pipeline.markOutputDirty();
if (drawTarget) {
cs.pipeline.hasDepthTarget = true;
cs.pipeline.depthTargetFormat = fbo.depthFormat;
cs.pipeline.markOutputDirty();
}
}
}
@@ -13,6 +13,7 @@ public final class FboState {
public final int[] colorFormats = new int[MAX_COLOR_ATTACHMENTS];
public final int[] colorLevels = new int[MAX_COLOR_ATTACHMENTS];
public int colorAttachmentCount;
public boolean readOnlyColor;
public int[] drawBuffers = {0};
public int readBufferIndex;
@@ -175,6 +175,33 @@ class GlsmSdlCopyBlitTest {
GLStateManager.glDeleteTextures(destination);
}
@Test
void attachingToTheReadFramebufferLeavesTheDrawFramebufferAlone() {
final int source = GlsmSdlHeadlessRig.createSolidTexture(GREEN);
final int drawColor = GlsmSdlHeadlessRig.createSolidTexture(BLUE);
final int readFbo = GLStateManager.glGenFramebuffers();
final int drawFbo = GlsmSdlHeadlessRig.fboWithColor(drawColor, 0);
try {
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_READ_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, source, 0);
GLStateManager.glBlitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(drawColor, 0), GREEN, "the blit reads the read framebuffer's attachment");
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(drawColor, 0), RED, "the draw framebuffer kept its own attachment");
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(source, 0), GREEN, "the read attachment never became a draw target");
} finally {
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(drawFbo);
GLStateManager.glDeleteTextures(source);
GLStateManager.glDeleteTextures(drawColor);
}
}
static Stream<Arguments> levelBlits() {
return Stream.of(
Arguments.of("1:1 into level 0: reads the source attachment level", SIZE / 2, 0),
@@ -1,12 +1,11 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class GlsmSdlFrameExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback {
@Override
@@ -22,7 +21,6 @@ public final class GlsmSdlFrameExtension implements BeforeAllCallback, BeforeEac
@Override
public void afterEach(ExtensionContext context) {
GlsmSdlHeadlessRig.endFrame();
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
GlsmRedirectAgent.assertNoFailures();
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
@@ -71,9 +72,8 @@ public final class GlsmSdlHeadlessRig {
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
assertTrue(GlsmSdlRedirectAgent.transformedCount() > 0, "redirect agent transformed no classes");
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
assertTrue(GlsmRedirectAgent.transformedCount() > 0, "redirect agent transformed no classes");
GlsmRedirectAgent.assertNoFailures();
createTarget();
booted = true;
@@ -1,12 +1,12 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class GlsmSdlOutOfFrameExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback {
@@ -24,6 +24,6 @@ public final class GlsmSdlOutOfFrameExtension implements BeforeAllCallback, Befo
@Override
public void afterEach(ExtensionContext context) {
GlsmSdlHeadlessRig.endFrame();
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(), () -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
GlsmRedirectAgent.assertNoFailures();
}
}
@@ -194,7 +194,7 @@ public class FontStrategist {
// The splash font renderer can only bind its own ascii texture.
private static FontProvider findModernGlyph(BatchingFontRenderer me, char chr, boolean forceUnicode) {
if (!FontConfig.enableModernFont || forceUnicode || me.isSplash) { return null; }
if (!FontConfig.enableModernFont || forceUnicode || me.isSplash || !GLStateManager.isMainThread()) { return null; }
return FontProviderBitmap.find(chr);
}
@@ -163,6 +163,10 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableCubeInstancing;
@Config.Comment("Stitch a texture atlas between entity and block entity models so different textures draw together (requires cube instancing)")
@Config.DefaultBoolean(true)
public static boolean enableEntityTextureAtlas;
@Config.Comment("Skip the end-of-frame shader buffer copy by ping-ponging buffers. Disable if a shader pack misrenders.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -199,6 +199,7 @@ public enum Mixins implements IMixins {
"angelica.entity.MixinRendererLivingEntity_GlintClock",
"angelica.entity.MixinRendererLivingEntity_ModelPassDraws",
"angelica.entity.MixinRender_BatchEligibility",
"angelica.entity.MixinRender_EntityFireBatch",
"angelica.entity.MixinRender_NameTagDraws",
"angelica.entity.MixinRender_ShadowBatch",
"angelica.entity.MixinTextureManager",
@@ -15,6 +15,7 @@ import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
@@ -49,6 +50,7 @@ import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import com.gtnewhorizons.angelica.rendering.tesr.EntityTextureAtlas;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.config.SystemProperties;
@@ -65,6 +67,7 @@ import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
import com.gtnewhorizons.angelica.iris.ShaderWarmup;
@@ -208,6 +211,8 @@ public final class ClientProxy extends CommonProxy {
ItemRenderListManager.registerReloadListener();
BlockRenderListManager.registerReloadListener();
}
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(manager -> EntityTextureAtlas.INSTANCE.clear());
TextureInfoCache.INSTANCE.addDeleteListener(EntityTextureAtlas.INSTANCE::onTextureDeleted);
FontProviderBitmap.registerReloadListener();
// Debug tooling
@@ -488,7 +488,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
if (te != null) {
final NBTTagCompound teNbt = new NBTTagCompound();
te.writeToNBT(teNbt);
teBlockId = teMap.resolve(block, teNbt);
teBlockId = teMap.resolve(block, teNbt, metadata);
} else {
teBlockId = -1;
}
@@ -71,9 +71,18 @@ public final class BiomeVertexBlender {
return blender != null && blender.isActive();
}
public int deferBlockTint() {
public void arm() {
cache.armedBlender = this;
}
public void disarm() {
cache.armedBlender = null;
}
private boolean tryDefer(SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
if (this.type != type || blockX != x || blockY != y || blockZ != z) return false;
replacedBlockTint = true;
return 0xFFFFFF;
return true;
}
public boolean beginFace(RenderBlocks renderer, int face, double x, double y, double z, IIcon icon) {
@@ -129,7 +138,11 @@ public final class BiomeVertexBlender {
public static long smoothColor(IBlockAccess access, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
final SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache != null) return cache.getColor(type, x, y, z) & 0xFFFFFFFFL;
if (cache != null) {
final BiomeVertexBlender armed = cache.armedBlender;
if (armed != null && armed.tryDefer(type, x, y, z)) return 0xFFFFFFL;
return cache.getColor(type, x, y, z) & 0xFFFFFFFFL;
}
if (access instanceof WorldClientExtension ext) return ext.celeritas$getSmoothBiomeColorCache().getColor(type, x, y, z) & 0xFFFFFFFFL;
return -1L;
}
@@ -28,6 +28,7 @@ public class SmoothBiomeColorCache extends BiomeColorCache<BiomeGenBase, SmoothB
private final double[] temperatureNoise;
private NoiseGeneratorPerlin noiseSource;
private int minX, minZ, waterY;
BiomeVertexBlender armedBlender;
private static final ThreadLocal<SmoothBiomeColorCache> activeCache = new ThreadLocal<>();
private static final class Slice extends BoxBlur.ColorBuffer {
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.resources.IReloadableResourceManager;
import org.lwjgl.opengl.GL11;
@@ -41,6 +42,11 @@ public class BlockRenderListManager {
return itemFrameDisplayLists[variant];
}
public static boolean rendersPlainItem(RenderBlocks rb, Block block, float brightness) {
return !isISBRH(block.getRenderType()) && !rb.enableAO && rb.overrideBlockTexture == null && brightness == 1.0F && rb.useInventoryTint
&& rb.blockAccess == null && (rb.uvRotateEast | rb.uvRotateWest | rb.uvRotateSouth | rb.uvRotateNorth | rb.uvRotateTop | rb.uvRotateBottom) == 0;
}
public static int callOrStartCompiling(int cached) {
if (cached != 0) {
GLStateManager.glCallList(cached);
@@ -35,6 +35,7 @@ public final class DroppedItemInstancer {
private static final AngelicaTesrMeshCache.GtnhMeshBackend iconBackend = new AngelicaTesrMeshCache.GtnhMeshBackend();
private static BakedTransformCapture blockBackend;
private static boolean basePart;
private static int itemsDrawingBlocks;
private static final GlintCapture glintCapture = new GlintCapture();
private static boolean glintSeen;
private static final ItemPropCache.ItemProp glintProperties = new ItemPropCache.ItemProp();
@@ -199,6 +200,24 @@ public final class DroppedItemInstancer {
}
public static long block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas) {
final long part = itemBlock(stack, rb, block, meta, brightness, unfilteredAtlas);
if (part != SKIP) itemsDrawingBlocks++;
return part;
}
public static void endBlock(long part) {
if (part != SKIP) itemsDrawingBlocks--;
endPart(part);
}
public static long directBlock(RenderBlocks rb, Block block, int meta, float brightness) {
if (itemsDrawingBlocks != 0 || !ModelPartBatcher.INSTANCE.entitiesActive()) return RUN;
if (!BatchEligibility.batchingAllowed()) return GLStateManager.drawCalls;
final TesrMaterial material = EntityMaterials.itemFromCurrentState(false);
return material != null ? batchBlock(material, rb, block, meta, brightness) : GLStateManager.drawCalls;
}
private static long itemBlock(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas) {
final TesrMaterial material = material(stack, unfilteredAtlas);
if (material == null) {
return RUN;
@@ -246,6 +265,10 @@ public final class DroppedItemInstancer {
}
}
public static void forgetItemDraws() {
itemsDrawingBlocks = 0;
}
public static void clear() {
icons.clear();
blocks.clear();
@@ -307,10 +330,7 @@ public final class DroppedItemInstancer {
private static BailReason blockStateBail(RenderBlocks renderBlocks, Block block, float brightness) {
if (BlockRenderListManager.isISBRH(block.getRenderType())) return BailReason.ISBRH;
if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint
|| renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth
| renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE;
return null;
return BlockRenderListManager.rendersPlainItem(renderBlocks, block, brightness) ? null : BailReason.BLOCK_STATE;
}
private static final class IconMesh {
@@ -0,0 +1,332 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
/**
* Copies entity and block entity textures into a shared atlas, so model boxes with different textures land in one instanced draw.
*/
public final class EntityTextureAtlas {
public static final EntityTextureAtlas INSTANCE = new EntityTextureAtlas();
static final int PAGE_SIZE = 2048;
private static final int MAX_PAGES = 4;
private static final int MAX_EDGE = 512;
private static final int MAX_QUICK_REFRESHES = 8;
private static final long QUICK_REFRESH_MS = 1000L;
public static final class Slot {
static final Slot WHOLE_TEXTURE = new Slot();
final Page page;
final int width, height;
final int x, y;
final float u, v, w, h;
final Matrix4f uvMatrix;
Slot(Page page, int x, int y, int width, int height) {
this.page = page;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.u = (x + 1) / (float) PAGE_SIZE;
this.v = (y + 1) / (float) PAGE_SIZE;
this.w = width / (float) PAGE_SIZE;
this.h = height / (float) PAGE_SIZE;
this.uvMatrix = new Matrix4f().translation(u, v, 0.0f).scale(w, h, 1.0f);
}
private Slot() {
this.page = null;
this.x = this.y = this.width = this.height = 0;
this.u = this.v = 0.0f;
this.w = this.h = 1.0f;
this.uvMatrix = new Matrix4f();
}
}
private static final class Shelf {
final int y, height;
int x;
Shelf(int y, int height) {
this.y = y;
this.height = height;
}
}
static final class Page {
final ResourceLocation location;
final int texture;
final ObjectArrayList<Shelf> shelves = new ObjectArrayList<>();
int nextShelfY;
Page(ResourceLocation location, int texture) {
this.location = location;
this.texture = texture;
}
long place(int width, int height) {
Shelf best = null;
for (Shelf shelf : shelves) {
if (shelf.height >= height && PAGE_SIZE - shelf.x >= width && (best == null || shelf.height < best.height)) best = shelf;
}
if (best != null && best.height > 2 * height && PAGE_SIZE - nextShelfY >= height) best = null;
if (best == null) {
if (PAGE_SIZE - nextShelfY < height || width > PAGE_SIZE) return -1L;
best = new Shelf(nextShelfY, height);
shelves.add(best);
nextShelfY += height;
}
final long at = ((long) best.x << 32) | best.y;
best.x += width;
return at;
}
void empty() {
shelves.clear();
nextShelfY = 0;
}
}
private static final class PageTexture extends AbstractTexture {
PageTexture(int texture) {
this.glTextureId = texture;
}
@Override
public void loadTexture(IResourceManager resourceManager) {}
}
private static final class Entry {
final TextureInfo info;
int uploadGeneration;
int quickRefreshes;
long lastRefreshMs;
Slot slot;
int noRoomWidth = -1, noRoomHeight = -1;
Entry(TextureInfo info) {
this.info = info;
}
}
private final Int2ObjectOpenHashMap<Entry> entries = new Int2ObjectOpenHashMap<>();
private final ObjectArrayList<Page> pages = new ObjectArrayList<>();
private final ObjectArrayList<Slot> freeSlots = new ObjectArrayList<>();
private final ObjectArrayList<Slot> releasedThisPass = new ObjectArrayList<>();
private int readFbo, drawFbo;
private int drawFboPage;
private int lastTexture = -1;
private boolean lastPbrPipeline;
private TextureInfo lastInfo;
private int lastChangeStamp, lastDeleteCount, lastPbrHolderChanges;
private Slot lastSlot;
private EntityTextureAtlas() {}
Slot slotFor(int texture, boolean pbrPipeline) {
if (texture == lastTexture && pbrPipeline == lastPbrPipeline && lastInfo.getChangeStamp() == lastChangeStamp
&& TextureInfoCache.INSTANCE.deleteCount() == lastDeleteCount && PBRTextureManager.INSTANCE.holderChanges() == lastPbrHolderChanges) {
return lastSlot;
}
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texture);
if (info == null) return null;
lastSlot = lookUp(texture, info, pbrPipeline);
lastTexture = texture;
lastPbrPipeline = pbrPipeline;
lastInfo = info;
lastChangeStamp = info.getChangeStamp();
lastDeleteCount = TextureInfoCache.INSTANCE.deleteCount();
lastPbrHolderChanges = PBRTextureManager.INSTANCE.holderChanges();
return lastSlot;
}
private Slot lookUp(int texture, TextureInfo info, boolean pbrPipeline) {
Entry entry = entries.get(texture);
if (entry == null || entry.info != info) {
if (entry != null) release(entry);
entry = new Entry(info);
entries.put(texture, entry);
}
if (!eligible(texture, entry, pbrPipeline)) {
if (info.isWrittenOutsideUploads()) release(entry);
return null;
}
final int width = info.getWidth(), height = info.getHeight();
if (entry.slot != null && (entry.slot.width != width || entry.slot.height != height)) release(entry);
if (entry.slot == null) {
entry.slot = takeFreeSlot(width, height);
if (entry.slot == null && (width != entry.noRoomWidth || height != entry.noRoomHeight)) entry.slot = placeOnPage(width, height);
if (entry.slot == null) {
entry.noRoomWidth = width;
entry.noRoomHeight = height;
return null;
}
} else if (entry.uploadGeneration == info.getUploadGeneration()) {
return entry.slot;
} else if (recordRefresh(entry)) {
release(entry);
return null;
}
copy(texture, entry.slot);
entry.uploadGeneration = info.getUploadGeneration();
return entry.slot;
}
private static boolean recordRefresh(Entry entry) {
final long now = System.currentTimeMillis();
entry.quickRefreshes = now - entry.lastRefreshMs < QUICK_REFRESH_MS ? entry.quickRefreshes + 1 : 1;
entry.lastRefreshMs = now;
return entry.quickRefreshes > MAX_QUICK_REFRESHES;
}
private static boolean eligible(int texture, Entry entry, boolean pbrPipeline) {
final TextureInfo info = entry.info;
final int width = info.getWidth();
final int height = info.getHeight();
if (width <= 0 || height <= 0 || width > MAX_EDGE || height > MAX_EDGE) return false;
if (info.getTarget() != GL11.GL_TEXTURE_2D || info.isWrittenOutsideUploads() || entry.quickRefreshes > MAX_QUICK_REFRESHES) return false;
final int format = info.getInternalFormat();
if (format != GL11.GL_RGBA && format != GL11.GL_RGBA8) return false;
if (info.getMinFilter() != GL11.GL_NEAREST || info.getMagFilter() != GL11.GL_NEAREST) return false;
return !pbrPipeline || !PBRTextureManager.INSTANCE.hasLoadedPbrTextures(texture);
}
private void release(Entry entry) {
if (entry.slot != null) releasedThisPass.add(entry.slot);
entry.slot = null;
}
void recycleReleasedRegions() {
if (releasedThisPass.isEmpty()) return;
freeSlots.addAll(releasedThisPass);
releasedThisPass.clear();
}
public void onTextureDeleted(int texture) {
final Entry entry = entries.remove(texture);
if (entry != null) release(entry);
}
private Slot takeFreeSlot(int width, int height) {
for (int i = 0, n = freeSlots.size(); i < n; i++) {
final Slot free = freeSlots.get(i);
if (free.width == width && free.height == height) return freeSlots.remove(i);
}
return null;
}
private Slot placeOnPage(int width, int height) {
for (int i = 0, n = pages.size(); i <= n; i++) {
final Page page = i < n ? pages.get(i) : newPage();
if (page == null) return null;
final long at = page.place(width + 2, height + 2);
if (at >= 0) return new Slot(page, (int) (at >>> 32), (int) at, width, height);
}
return null;
}
private Page newPage() {
if (pages.size() == MAX_PAGES) return null;
final int previous = GLStateManager.getBoundTextureForServerState();
final int texture = GLStateManager.glGenTextures();
BatchStateGuard.suspend();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, PAGE_SIZE, PAGE_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
BatchStateGuard.resume();
}
final ResourceLocation location = new ResourceLocation("angelica", "entity_atlas_" + pages.size());
Minecraft.getMinecraft().getTextureManager().loadTexture(location, new PageTexture(texture));
final Page page = new Page(location, texture);
pages.add(page);
return page;
}
private void copy(int texture, Slot slot) {
if (readFbo == 0) {
readFbo = GLStateManager.glGenFramebuffers();
drawFbo = GLStateManager.glGenFramebuffers();
}
final int previousRead = GLStateManager.getReadFramebuffer();
final int previousDraw = GLStateManager.getDrawFramebuffer();
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
BatchStateGuard.suspend();
try {
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.attachTextureForReading(GL30.GL_COLOR_ATTACHMENT0, texture);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
assert slot.page != null;
if (drawFboPage != slot.page.texture) {
GLStateManager.glFramebufferTexture2D(GL30.GL_DRAW_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, slot.page.texture, 0);
drawFboPage = slot.page.texture;
}
final int w = slot.width, h = slot.height, x = slot.x + 1, y = slot.y + 1;
final int[] fromX = {0, 0, w - 1}, widths = {w, 1, 1}, toX = {x, x - 1, x + w};
final int[] fromY = {0, 0, h - 1}, heights = {h, 1, 1}, toY = {y, y - 1, y + h};
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) blit(fromX[col], fromY[row], widths[col], heights[row], toX[col], toY[row]);
}
} finally {
GLStateManager.attachTextureForReading(GL30.GL_COLOR_ATTACHMENT0, 0);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, previousRead);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, previousDraw);
if (scissor) GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
BatchStateGuard.resume();
}
}
private static void blit(int srcX, int srcY, int width, int height, int dstX, int dstY) {
GLStateManager.glBlitFramebuffer(srcX, srcY, srcX + width, srcY + height, dstX, dstY, dstX + width, dstY + height, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
}
public void clear() {
for (Page page : pages) page.empty();
entries.clear();
freeSlots.clear();
releasedThisPass.clear();
lastTexture = -1;
lastInfo = null;
lastSlot = null;
}
void freePages() {
if (pages.isEmpty() && readFbo == 0) return;
clear();
for (Page page : pages) GLStateManager.glDeleteTextures(page.texture);
pages.clear();
if (readFbo != 0) {
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(drawFbo);
readFbo = 0;
drawFbo = 0;
}
drawFboPage = 0;
}
}
@@ -178,12 +178,13 @@ final class InstancedTemplateRenderer {
final int light = data.lights.getInt(i);
final int overlay = data.overlays.getInt(i);
final long info = data.infos.getLong(i);
for (int p = 0, n = partCubes.length; p < n; p++) {
final CubeParams cube = partCubes[p];
final EntityTextureAtlas.Slot slot = data.atlasSlot(run, i);
final EntityTextureAtlas.Slot region = slot != null ? slot : EntityTextureAtlas.Slot.WHOLE_TEXTURE;
for (final CubeParams cube : partCubes) {
cube.writeRows(ptr, data.matrices, off, scale);
InstancedAttribs.writeTail(ptr, color, overlay, info);
InstancedAttribs.writeLightmap(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, light);
cube.writeTexture(ptr);
cube.writeTexture(ptr, region.u, region.v, region.w, region.h);
ptr += CubeInstancedAttribs.STRIDE;
}
}
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.UnitCubeMesh;
import net.minecraft.client.model.ModelBox;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.model.PositionTextureVertex;
import net.minecraft.client.model.TexturedQuad;
@@ -11,6 +13,10 @@ public final class ModelBoxCapture {
private ModelBoxCapture() {}
public static CubeParams capture(ModelBox box, TexturedQuad[] quads, ModelRenderer part, int texU, int texV, int sizeX, int sizeY, int sizeZ, float inflate) {
return capture(quads, part.textureWidth, part.textureHeight, part.mirror, texU, texV, box.posX1, box.posY1, box.posZ1, sizeX, sizeY, sizeZ, inflate);
}
public static CubeParams capture(TexturedQuad[] quads, float texWidth, float texHeight, boolean mirror, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate) {
if (quads == null || quads.length != UnitCubeMesh.FACE_COUNT) return null;
final CubeParams params = CubeParams.of(texWidth, texHeight, mirror, texU, texV, x, y, z, sizeX, sizeY, sizeZ, inflate);
@@ -14,6 +14,7 @@ import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIds;
@@ -103,6 +104,7 @@ public final class ModelPartBatcher {
private boolean texCaptured;
private boolean texIdentity;
private boolean active;
private int entityFireDepth;
private Mode mode = Mode.BLOCK_ENTITIES;
private boolean instancedThisCycle;
private DeferredWorldRenderingPipeline shaderPipeline;
@@ -141,6 +143,7 @@ public final class ModelPartBatcher {
private static final class PartTemplate {
TemplateBuffer template;
CubeParams[] cubes;
boolean cubesInsideTexture;
float scale;
long lastUsedMs;
boolean emptyVanillaPart;
@@ -248,6 +251,8 @@ public final class ModelPartBatcher {
if (entityPass && entityTranslucentsHeld) {
discardHeldEntities();
}
// Held entity translucents draw after deferred and can still sample regions released before them
if (!entityTranslucentsHeld) recycleAtlas();
active = true;
final long nowMs = System.currentTimeMillis();
mvCaptured = false;
@@ -350,6 +355,7 @@ public final class ModelPartBatcher {
if (instancedThisCycle) {
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
}
recycleAtlas();
AngelicaBufferSource.rebindPass();
} finally {
if (wrap) GbufferPrograms.endEntities();
@@ -367,6 +373,15 @@ public final class ModelPartBatcher {
private void discardHeldEntities() {
entityTranslucentsHeld = false;
entitySource.discard();
recycleAtlas();
}
private static void recycleAtlas() {
if (AngelicaConfig.enableEntityTextureAtlas) {
EntityTextureAtlas.INSTANCE.recycleReleasedRegions();
} else {
EntityTextureAtlas.INSTANCE.freePages();
}
}
public boolean isActive() {
@@ -514,7 +529,11 @@ public final class ModelPartBatcher {
}
public boolean entityPassActive() {
return active && mode == Mode.ENTITIES && !shadow;
return entitiesActive() && !shadow;
}
public boolean entitiesActive() {
return active && mode == Mode.ENTITIES;
}
public boolean isShadowPass() {
@@ -555,18 +574,18 @@ public final class ModelPartBatcher {
if (shadow && isDepthEqualDecalState()) {
return true;
}
return queueTemplate(entry.template, entry.cubes, scale, null, false);
return queueTemplate(entry.template, entry.cubes, entry.cubesInsideTexture, scale, null, false);
}
public boolean queueTemplate(TemplateBuffer template, TesrMaterial material) {
return queueTemplate(template, null, 1.0f, material, false);
return queueTemplate(template, null, false, 1.0f, material, false);
}
public boolean queueTemplate(TemplateBuffer template, TesrMaterial material, boolean afterGlint) {
return queueTemplate(template, null, 1.0f, material, afterGlint);
return queueTemplate(template, null, false, 1.0f, material, afterGlint);
}
private boolean queueTemplate(TemplateBuffer template, CubeParams[] cubes, float scale, TesrMaterial explicitMaterial, boolean afterGlint) {
private boolean queueTemplate(TemplateBuffer template, CubeParams[] cubes, boolean cubesInsideTexture, float scale, TesrMaterial explicitMaterial, boolean afterGlint) {
if (!active) {
bail(BailReason.INACTIVE);
return false;
@@ -613,7 +632,8 @@ public final class ModelPartBatcher {
final int glintSlot = material.special() == TesrMaterial.SpecialRender.GLINT ? lastBoundGlintSlot : ShaderGlint.NO_TINT;
final int cullCode = material.isNoCull() ? DrawState.DISABLED : DrawState.liveCull();
final boolean lit = DrawState.liveLit(material);
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, DrawState.liveLightmap(), afterGlint);
final EntityTextureAtlas.Slot atlasSlot = texMatrix == null && cubesInsideTexture ? atlasSlotFor(material) : null;
final RenderLayer layer = layerFor(atlasSlot != null ? Objects.requireNonNull(atlasSlot.page).location : texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, DrawState.liveLightmap(), afterGlint);
// Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id
final int entityId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
final Color4 color = GLStateManager.getColor();
@@ -621,11 +641,20 @@ public final class ModelPartBatcher {
final int overlayABGR = currentOverlay();
final long entityInfo = currentEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedItem());
final int packedLight = GLSMConfig.packedLastBrightness();
activeGroups.queue(template, cubes, scale, layer, material, currentModelView(), packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix);
activeGroups.queue(template, cubes, scale, layer, material, currentModelView(), packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix, atlasSlot);
parts++;
return true;
}
private EntityTextureAtlas.Slot atlasSlotFor(TesrMaterial material) {
if (!AngelicaConfig.enableEntityTextureAtlas) return null;
// Glint, overlay and custom shaders sample their own textures
if (material.special() != TesrMaterial.SpecialRender.NONE || material.shader() != null || material.isUnfilteredAtlas() || material == EntityMaterials.OVERLAY) return null;
if (!activeGroups.instancesCubes(material)) return null;
if (shaderPipeline != null && !shaderPipeline.hasInstancedVariant(Instancing.CUBE)) return null;
return EntityTextureAtlas.INSTANCE.slotFor(lastBoundGlId, shaderPipeline != null);
}
private enum TessellatorRoute { DRAW_NOW, DRAW_NOW_EXPECTED, QUEUE }
public static int drawTessellator(Tessellator tess) {
@@ -650,8 +679,13 @@ public final class ModelPartBatcher {
}
}
public void beginEntityFire() { entityFireDepth++; }
public void endEntityFire() { entityFireDepth--; }
private TessellatorRoute routeTessellatorDraw(Tessellator tess) {
if (!active || mode != Mode.BLOCK_ENTITIES || !GLStateManager.isMainThread() || !BatchEligibility.insideRenderer()) return TessellatorRoute.DRAW_NOW;
if (!active || !GLStateManager.isMainThread()) return TessellatorRoute.DRAW_NOW;
final boolean entityFire = entityFireDepth != 0 && mode == Mode.ENTITIES;
if (!entityFire && (mode != Mode.BLOCK_ENTITIES || !BatchEligibility.insideRenderer())) return TessellatorRoute.DRAW_NOW;
if (!tess.isDrawing || tess.drawMode != GL11.GL_QUADS || tess.vertexCount == 0 || (tess.vertexCount & 3) != 0) return TessellatorRoute.DRAW_NOW;
if (shaderPipeline != null && TessellatorEntityIds.hasIdChanges(tess)) return TessellatorRoute.DRAW_NOW;
if (DisplayListManager.isRecording() || !GLStateManager.glIsEnabled(GL11.GL_DEPTH_TEST) || !unitTexIdentity()) return TessellatorRoute.DRAW_NOW;
@@ -659,7 +693,7 @@ public final class ModelPartBatcher {
final TesrMaterial material = EntityMaterials.fromCurrentState(true);
if (material == null || material == EntityMaterials.OVERLAY) return TessellatorRoute.DRAW_NOW;
if (lastBoundLocation == null || GLStateManager.getBoundTextureForServerState() != lastBoundGlId) return TessellatorRoute.DRAW_NOW;
if (!BatchEligibility.batchingAllowed()) return TessellatorRoute.DRAW_NOW_EXPECTED;
if (!entityFire && !BatchEligibility.batchingAllowed()) return TessellatorRoute.DRAW_NOW_EXPECTED;
final PassOverride pass = shaderPipeline != null ? PassOverride.capture() : PassOverride.NONE;
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
@@ -667,7 +701,8 @@ public final class ModelPartBatcher {
final int cullCode = material.isNoCull() ? DrawState.DISABLED : DrawState.liveCull();
final RenderLayer layer = layerFor(lastBoundLocation, material, pass, offset ? polygon.getOffsetFactor() : 0.0f, offset ? polygon.getOffsetUnits() : 0.0f,
ShaderGlint.NO_TINT, cullCode, DrawState.liveLit(material), DrawState.liveLightmap());
final int groupId = groupId(shaderPipeline != null, pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
// Flames carry entity_flame, which some packs may voxelize as a light source in the shadow pass
final int groupId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
TessellatorStreamingDrawer.fillMissingAttributes(tess);
activeGroups.streamVertices(layer, groupId, tess.rawBuffer, tess.vertexCount, currentModelView());
return TessellatorRoute.QUEUE;
@@ -724,6 +759,7 @@ public final class ModelPartBatcher {
entry.scale = scale;
entry.template = null;
entry.cubes = null;
entry.cubesInsideTexture = false;
entry.emptyVanillaPart = false;
final List<ModelBox> boxes = part.cubeList;
if (builder != null) {
@@ -758,6 +794,7 @@ public final class ModelPartBatcher {
cubes[i] = params;
}
entry.cubes = cubes;
entry.cubesInsideTexture = CubeParams.allInsideTexture(cubes);
}
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit, boolean lightmap) {
@@ -794,6 +831,7 @@ public final class ModelPartBatcher {
}
public void clear() {
entityFireDepth = 0;
shadows.reset();
groups.clear();
entityGroups.clear();
@@ -94,6 +94,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
static final class InstanceColumns {
final ObjectArrayList<TemplateBuffer> templates = new ObjectArrayList<>();
final ObjectArrayList<CubeParams[]> cubes = new ObjectArrayList<>();
final ObjectArrayList<EntityTextureAtlas.Slot> atlasSlots = new ObjectArrayList<>();
boolean anyAtlasSlot;
final FloatArrayList scales = new FloatArrayList();
final IntArrayList lights = new IntArrayList();
final IntArrayList colors = new IntArrayList();
@@ -122,8 +124,18 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
size++;
}
void addCubes(TemplateBuffer templateOrNull, CubeParams[] cubeParams, float scale, Matrix4fc mv, int light, int color, int overlay, long info) {
EntityTextureAtlas.Slot atlasSlot(int i) {
return anyAtlasSlot ? atlasSlots.get(i) : null;
}
EntityTextureAtlas.Slot atlasSlot(TexRun run, int i) {
return run.source != null ? null : atlasSlot(i);
}
void addCubes(TemplateBuffer templateOrNull, CubeParams[] cubeParams, float scale, Matrix4fc mv, int light, int color, int overlay, long info, EntityTextureAtlas.Slot atlasSlot) {
cubes.add(cubeParams);
atlasSlots.add(atlasSlot);
anyAtlasSlot |= atlasSlot != null;
scales.add(scale);
add(templateOrNull, mv, light, color, overlay, info);
}
@@ -140,6 +152,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
if (src.cubes.size() == src.size) {
for (int i = start; i < end; i++) cubes.add(src.cubes.get(i));
atlasSlots.size(atlasSlots.size() + n);
scales.addElements(scales.size(), src.scales.elements(), start, n);
}
fill(lights, n, light);
@@ -158,6 +171,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
void clear() {
templates.clear();
cubes.clear();
atlasSlots.clear();
anyAtlasSlot = false;
scales.clear();
lights.clear();
colors.clear();
@@ -477,10 +492,14 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void queue(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix) {
queue(template, null, 1.0f, layer, material, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, id, texMatrix);
queue(template, null, 1.0f, layer, material, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, id, texMatrix, null);
}
void queue(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix) {
boolean instancesCubes(TesrMaterial material) {
return instanceableMaterial(material) && glintCapture == null;
}
void queue(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot) {
final Group group = groupFor(layer, material, id);
touch(group, layer);
if (group.stream) {
@@ -488,7 +507,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
final boolean capturingBase = glintCapture != null && glintCapture.capturesBase();
if (instanceable && cubes != null) {
final TexRun run = group.openOrExtendRun(group.cubeColumns, texMatrix, false, capturingBase && group.opaque);
group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo);
group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, atlasSlot);
run.last.end = group.cubeColumns.size;
run.parts++;
run.instances += cubes.length;
@@ -706,7 +725,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
if (group.cubeColumns.hasInstances()) {
if (cubeAvailable) {
need |= NEED_CUBE;
} else if (matrixAvailable) {
} else if (matrixAvailable && !group.cubeColumns.anyAtlasSlot) {
need |= NEED_MATRIX;
} else {
drawInstancesCpu(group, group.cubeColumns);
@@ -724,7 +743,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
final boolean fixedFunction = deferred == null && GLStateManager.getActiveProgram() == 0;
drawTemplateRuns(group.templateColumns, group.material.special() == TesrMaterial.SpecialRender.GLINT && fixedFunction, group.material == EntityMaterials.ITEM_GLINT);
}
if (!cubeAvailable && group.cubeColumns.hasInstances()) {
if (!cubeAvailable && group.cubeColumns.hasInstances() && !group.cubeColumns.anyAtlasSlot) {
drawTemplateRuns(group.cubeColumns, group.material == EntityMaterials.GLINT && deferred == null, false);
}
}
@@ -904,7 +923,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
System.arraycopy(data.matrices, i * 16, matScratch, 0, 16);
scratchMat.set(matScratch);
final int color = head.source != null ? head.color : data.colors.getInt(i);
ptr = VertexTransform.writeInstance(ptr, format, templates.get(i), scratchMat, scratchVec, color, data.lights.getInt(i), texMatrix);
final EntityTextureAtlas.Slot slot = data.atlasSlot(head, i);
ptr = VertexTransform.writeInstance(ptr, format, templates.get(i), scratchMat, scratchVec, color, data.lights.getInt(i), slot != null ? slot.uvMatrix : texMatrix);
}
fallbackScratch.position((int) (ptr - base));
fallbackScratch.flip();
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
import com.gtnewhorizons.angelica.rendering.particles.ParticleInstancer;
public final class TesrLifecycle {
@@ -10,6 +11,8 @@ public final class TesrLifecycle {
public static void reset() {
TesrBatchRenderer.INSTANCE.clearRetained();
ModelPartBatcher.INSTANCE.clear();
EntityTextureAtlas.INSTANCE.clear();
DroppedItemInstancer.forgetItemDraws();
ParticleInstancer.clear();
RenderLayer.clearInterningAndHooks();
@@ -103,7 +103,7 @@ public class BlockMaterialMapping {
nbtEntries.forEach((intId, entries) -> {
for (BlockEntry entry : entries) {
if (entry.hasNbtProperties()) {
map.addCondition(entry.id(), entry.nbtProperties(), intId);
map.addCondition(entry.id(), entry.nbtProperties(), intId, entry.metas());
}
}
});
@@ -121,7 +121,7 @@ public class BlockMaterialMapping {
return;
}
teMap.addCondition(block, entry.nbtProperties(), intId);
teMap.addCondition(block, entry.nbtProperties(), intId, entry.metas());
}
/**
@@ -13,6 +13,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Maps objects with specific NBT properties to shader IDs. Generic over the
@@ -34,7 +35,9 @@ public class NbtConditionalIdMap<K> {
boolean isExistenceOnly() { return expectedValue == null; }
}
record NbtCondition(PropertyMatcher[] matchers, int shaderId) {}
record NbtCondition(PropertyMatcher[] matchers, Set<Integer> metas, int shaderId) {
boolean isMetaMatch() { return metas != null && !metas.isEmpty(); }
}
private final Map<K, List<NbtCondition>> conditionsByKey;
@@ -47,7 +50,7 @@ public class NbtConditionalIdMap<K> {
* registries at registration time. Single-quoted values are kept as literals.
* Dot notation paths are pre-split here so we prevent allocations.
*/
public void addCondition(K key, Map<String, PropertiesTokenizer.NbtValue> nbtProperties, int shaderId) {
public void addCondition(K key, Map<String, PropertiesTokenizer.NbtValue> nbtProperties, int shaderId, Set<Integer> metas) {
final PropertyMatcher[] matchers = new PropertyMatcher[nbtProperties.size()];
int i = 0;
@@ -88,7 +91,7 @@ public class NbtConditionalIdMap<K> {
}
conditionsByKey.computeIfAbsent(key, k -> new ArrayList<>())
.add(new NbtCondition(matchers, shaderId));
.add(new NbtCondition(matchers, metas, shaderId));
}
public boolean hasConditions(K key) {
@@ -102,13 +105,16 @@ public class NbtConditionalIdMap<K> {
/**
* Resolves the shader ID by matching the provided NBT data.
*/
public int resolve(K key, NBTTagCompound nbt) {
public int resolve(K key, NBTTagCompound nbt, Integer meta) {
final List<NbtCondition> conditions = conditionsByKey.get(key);
if (conditions == null) {
return -1;
}
for (NbtCondition condition : conditions) {
if (meta != null && condition.isMetaMatch() && !condition.metas.contains(meta)) {
continue;
}
if (matchesNbt(nbt, condition.matchers())) {
return condition.shaderId;
}
@@ -117,6 +123,13 @@ public class NbtConditionalIdMap<K> {
return -1;
}
/**
* Resolves the shader ID by matching the provided NBT data.
*/
public int resolve(K key, NBTTagCompound nbt) {
return resolve(key, nbt, null);
}
private static boolean matchesNbt(NBTTagCompound nbt, PropertyMatcher[] matchers) {
for (PropertyMatcher matcher : matchers) {
if (matcher.isPathMatch()) {
@@ -2610,7 +2610,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
private static final ProgramId[] INSTANCED_PROGRAM_IDS = {
ProgramId.Block, ProgramId.BlockTrans, ProgramId.Entities, ProgramId.EntitiesTrans,
ProgramId.Shadow, ProgramId.ShadowWater, ProgramId.ArmorGlint
ProgramId.Shadow, ProgramId.ShadowWater, ProgramId.ArmorGlint, ProgramId.SpiderEyes
};
private static final ProgramId[] PARTICLE_PROGRAM_IDS = {
@@ -28,6 +28,7 @@ public class PBRTextureManager {
}
private final Int2ObjectMap<PBRTextureHolder> holders = new Int2ObjectOpenHashMap<>();
private int holderChanges;
private final PBRTextureConsumerImpl consumer = new PBRTextureConsumerImpl();
private NativeImageBackedSingleColorTexture defaultNormalTexture;
@@ -61,15 +62,24 @@ public class PBRTextureManager {
return holder;
}
public boolean hasLoadedPbrTextures(int id) {
return getHolder(id) != defaultHolder;
}
public PBRTextureHolder getOrLoadHolder(int id) {
PBRTextureHolder holder = holders.get(id);
if (holder == null) {
holder = loadHolder(id);
holders.put(id, holder);
holderChanges++;
}
return holder;
}
public int holderChanges() {
return holderChanges;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private PBRTextureHolder loadHolder(int id) {
AbstractTexture texture = TextureTracker.INSTANCE.getTexture(id);
@@ -96,6 +106,7 @@ public class PBRTextureManager {
PBRTextureHolder holder = holders.remove(id);
if (holder != null) {
closeHolder(holder);
holderChanges++;
}
}
@@ -106,6 +117,7 @@ public class PBRTextureManager {
}
}
holders.clear();
holderChanges++;
}
public void close() {
@@ -160,7 +160,7 @@ public abstract class MixinItemRenderer_Instanced {
@Surround.Finally("block")
private void angelica$endHeldBlock(@Surround.Carry long part) {
DroppedItemInstancer.endPart(part);
DroppedItemInstancer.endBlock(part);
}
@Redirect(
@@ -24,8 +24,9 @@ public abstract class MixinModelBox_CubeParams implements ModelBoxData {
@Inject(method = "<init>", at = @At("RETURN"))
private void angelica$captureCubeParams(ModelRenderer part, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate, CallbackInfo ci) {
//noinspection ConstantValue
if (((Object) this).getClass() != ModelBox.class) return;
this.angelica$cubeParams = ModelBoxCapture.capture(this.quadList, part.textureWidth, part.textureHeight, part.mirror, texU, texV, x, y, z, sizeX, sizeY, sizeZ, inflate);
this.angelica$cubeParams = ModelBoxCapture.capture((ModelBox) (Object) this, this.quadList, part, texU, texV, sizeX, sizeY, sizeZ, inflate);
}
@Override
@@ -116,6 +116,6 @@ public abstract class MixinRenderItem_Instanced {
@Surround.Finally("block")
private void angelica$endDroppedBlock(@Surround.Carry long part) {
DroppedItemInstancer.endPart(part);
DroppedItemInstancer.endBlock(part);
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(Render.class)
public class MixinRender_EntityFireBatch {
@Surround(method = "renderEntityOnFire")
private void angelica$batchEntityFire(Entity entity, double x, double y, double z, float partialTicks) {
ModelPartBatcher.INSTANCE.beginEntityFire();
}
@Surround.Finally
private void angelica$endEntityFire() {
ModelPartBatcher.INSTANCE.endEntityFire();
}
}
@@ -4,51 +4,28 @@ import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
// Innermost: shaders.MixinRenderBlocks_ItemId must also cover a cache hit
@Mixin(value = RenderBlocks.class, priority = 900)
public abstract class MixinRenderBlocks {
@Shadow
public boolean useInventoryTint;
@Shadow
public boolean enableAO;
@Shadow
public IIcon overrideBlockTexture;
@Shadow public IBlockAccess blockAccess;
@Shadow public int uvRotateEast;
@Shadow public int uvRotateWest;
@Shadow public int uvRotateSouth;
@Shadow public int uvRotateNorth;
@Shadow public int uvRotateTop;
@Shadow public int uvRotateBottom;
@Surround(method = "renderBlockAsItem")
private void angelica$cacheBlockItemRenderer(Block block, int meta, float brightness) {
final boolean uncacheable = BlockRenderListManager.isISBRH(block.getRenderType())
|| enableAO
|| this.overrideBlockTexture != null
|| brightness != 1.0F
|| !this.useInventoryTint
|| this.blockAccess != null
|| (uvRotateEast | uvRotateWest | uvRotateSouth | uvRotateNorth | uvRotateTop | uvRotateBottom) != 0
//noinspection DataFlowIssue
final RenderBlocks self = (RenderBlocks) (Object) this;
final boolean uncacheable = !BlockRenderListManager.rendersPlainItem(self, block, brightness)
|| GLStateManager.isRecordingDisplayList()
|| TessellatorManager.isCurrentlyCapturing()
|| TessellatorManager.shouldInterceptDraw(Tessellator.instance);
@Surround.Carry
final int list = uncacheable ? 0 : BlockRenderListManager.callOrStartCompiling(BlockRenderListManager.getDisplayList(block, meta));
final long part = uncacheable ? DroppedItemInstancer.RUN : DroppedItemInstancer.directBlock(self, block, meta, brightness);
@Surround.Carry
final int list = uncacheable ? 0 : part == DroppedItemInstancer.SKIP ? -1 : BlockRenderListManager.callOrStartCompiling(BlockRenderListManager.getDisplayList(block, meta));
@Surround.Skip
final boolean cached = list < 0;
}
@@ -62,4 +39,9 @@ public abstract class MixinRenderBlocks {
private void angelica$abortBlockItemRenderer(Throwable error, @Surround.Carry int list) {
BlockRenderListManager.abortCompiling(list);
}
@Surround.Finally
private void angelica$countDirectBlock(@Surround.Carry long part) {
DroppedItemInstancer.endPart(part);
}
}
@@ -26,6 +26,7 @@ public abstract class MixinRenderBlocks {
@Shadow public IBlockAccess blockAccess;
@Shadow public abstract boolean hasOverrideBlockTexture();
@Unique private final BiomeVertexBlender angelica$vertexBlender = new BiomeVertexBlender();
@Unique private boolean angelica$blending;
@Surround(method = {"renderStandardBlock", "renderBlockLiquid"}, id = "blendBlock")
private void angelica$blendBlock(Block block, int x, int y, int z) {
@@ -34,8 +35,10 @@ public abstract class MixinRenderBlocks {
BiomeBlendTessellator tessellator = null;
@Surround.Carry("previous")
BiomeVertexBlender previous = null;
@Surround.Carry
@Surround.Carry("blend")
boolean blend = false;
@Surround.Carry("wasBlending")
final boolean wasBlending = angelica$blending;
if (cache != null && ClientProxy.options().quality.biomeBlendMode == BiomeBlendMode.FANCY && SmoothBiomeColorCache.configuredRadius() != 0) {
tessellator = (BiomeBlendTessellator) TessellatorManager.get();
previous = tessellator.angelica$getBiomeBlender();
@@ -45,11 +48,13 @@ public abstract class MixinRenderBlocks {
if (blend) angelica$vertexBlender.setup(cache, type, x, y, z);
tessellator.angelica$setBiomeBlender(blend ? angelica$vertexBlender : null);
}
angelica$blending = blend;
}
@Surround.Finally("blendBlock")
private void angelica$endBlendBlock(@Surround.Carry("tessellator") BiomeBlendTessellator tessellator, @Surround.Carry("previous") BiomeVertexBlender previous,
@Surround.Carry boolean blend) {
@Surround.Carry("blend") boolean blend, @Surround.Carry("wasBlending") boolean wasBlending) {
angelica$blending = wasBlending;
if (tessellator == null) return;
tessellator.angelica$setBiomeBlender(previous);
if (blend) angelica$vertexBlender.clear();
@@ -66,13 +71,14 @@ public abstract class MixinRenderBlocks {
@Surround(method = {"renderStandardBlock", "renderBlockLiquid"}, id = "deferTint",
at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;colorMultiplier(Lnet/minecraft/world/IBlockAccess;III)I"))
private void angelica$deferTintEnter() {
@Surround.Skip
final boolean defer = ((BiomeBlendTessellator) TessellatorManager.get()).angelica$getBiomeBlender() == angelica$vertexBlender;
@Surround.Carry
final boolean armed = angelica$blending;
if (armed) angelica$vertexBlender.arm();
}
@Surround.Skipped("deferTint")
private int angelica$deferTint() {
return angelica$vertexBlender.deferBlockTint();
@Surround.Finally("deferTint")
private void angelica$deferTintExit(@Surround.Carry boolean armed) {
if (armed) angelica$vertexBlender.disarm();
}
@Surround(method = {"renderStandardBlockWithAmbientOcclusion", "renderStandardBlockWithAmbientOcclusionPartial", "renderStandardBlockWithColorMultiplier"}, id = "faceYNeg",
@@ -0,0 +1,235 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@GLCoreTest
class EntityTextureAtlasGLTest {
private static final int WIDTH = 4;
private static final int HEIGHT = 2;
private static final int PAGE = EntityTextureAtlas.PAGE_SIZE;
private final List<ITextureObject> pages = new ArrayList<>();
private Minecraft previous;
private int source, passColor, passFbo;
@BeforeAll
static void listenForDeletes() {
TextureInfoCache.INSTANCE.addDeleteListener(EntityTextureAtlas.INSTANCE::onTextureDeleted);
}
@BeforeEach
void setUp() {
previous = Minecraft.getMinecraft();
final Minecraft minecraft = mock(Minecraft.class);
final TextureManager textures = mock(TextureManager.class);
when(textures.loadTexture(any(ResourceLocation.class), any(ITextureObject.class))).thenAnswer(call -> pages.add(call.getArgument(1)));
when(minecraft.getTextureManager()).thenReturn(textures);
Reflect.setStatic(Minecraft.class, "theMinecraft", minecraft);
passColor = texture(GL11.GL_NEAREST, 0);
passFbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, passFbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, passColor, 0);
source = texture(GL11.GL_NEAREST, 0);
}
@AfterEach
void tearDown() {
EntityTextureAtlas.INSTANCE.freePages();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(passFbo);
GLStateManager.glDeleteTextures(source);
GLStateManager.glDeleteTextures(passColor);
Reflect.setStatic(Minecraft.class, "theMinecraft", previous);
}
private static int argb(int x, int y, int seed) {
return 0xFF000000 | (x * 50 + seed) << 16 | (y * 90 + seed) << 8 | 0x20;
}
private static int texture(int filter, int seed) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, WIDTH, HEIGHT, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels(seed));
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return id;
}
private static ByteBuffer texels(int seed) {
final ByteBuffer texels = BufferUtils.createByteBuffer(WIDTH * HEIGHT * 4);
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
final int c = argb(x, y, seed);
texels.put((byte) (c >> 16)).put((byte) (c >> 8)).put((byte) c).put((byte) (c >>> 24));
}
}
texels.flip();
return texels;
}
private void assertSlotHolds(EntityTextureAtlas.Slot slot, int seed) {
final ByteBuffer page = BufferUtils.createByteBuffer(PAGE * PAGE * 4);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, pages.get(0).getGlTextureId());
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, page);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
for (int y = -1; y <= HEIGHT; y++) {
for (int x = -1; x <= WIDTH; x++) {
final int at = ((slot.y + 1 + y) * PAGE + slot.x + 1 + x) * 4;
final int actual = (page.get(at + 3) & 0xFF) << 24 | (page.get(at) & 0xFF) << 16 | (page.get(at + 1) & 0xFF) << 8 | page.get(at + 2) & 0xFF;
final int expected = argb(Math.clamp(x, 0, WIDTH - 1), Math.clamp(y, 0, HEIGHT - 1), seed);
assertEquals(Integer.toHexString(expected), Integer.toHexString(actual), "page texel for source (" + x + ", " + y + ")");
}
}
}
@Test
void copiesTheTextureWithEdgeGuttersAndRestoresTheFramebuffers() {
final EntityTextureAtlas.Slot slot = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(slot);
assertEquals(passFbo, GLStateManager.getDrawFramebuffer(), "draw framebuffer restored");
assertEquals(passFbo, GLStateManager.getReadFramebuffer(), "read framebuffer restored");
assertEquals((slot.x + 1) / (float) PAGE, slot.u);
assertEquals(WIDTH / (float) PAGE, slot.w);
assertSlotHolds(slot, 0);
}
@Test
void reuploadingTheTextureRefreshesItsSlot() {
final EntityTextureAtlas.Slot slot = EntityTextureAtlas.INSTANCE.slotFor(source, false);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, source);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels(7));
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
assertSame(slot, EntityTextureAtlas.INSTANCE.slotFor(source, false));
assertSlotHolds(slot, 7);
}
@Test
void filteredTexturesKeepTheirOwnDraw() {
final int filtered = texture(GL11.GL_LINEAR, 0);
try {
assertNull(EntityTextureAtlas.INSTANCE.slotFor(filtered, false));
} finally {
GLStateManager.glDeleteTextures(filtered);
}
}
@Test
void switchingToLinearFilteringDropsTheSlot() {
final EntityTextureAtlas.Slot slot = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(slot);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, source);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
assertNull(EntityTextureAtlas.INSTANCE.slotFor(source, false), "integer filter change");
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
assertSame(slot, EntityTextureAtlas.INSTANCE.slotFor(source, false), "the kept slot comes back");
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
assertNull(EntityTextureAtlas.INSTANCE.slotFor(source, false), "float filter change");
}
@Test
void drawingIntoTheTextureDropsItsSlot() {
assertNotNull(EntityTextureAtlas.INSTANCE.slotFor(source, false));
final int target = GLStateManager.glGenFramebuffers();
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, source, 0);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, passFbo);
GLStateManager.glDeleteFramebuffers(target);
}
assertNull(EntityTextureAtlas.INSTANCE.slotFor(source, false));
}
@Test
void clearingKeepsThePagesAndRefillsThem() {
final EntityTextureAtlas.Slot before = EntityTextureAtlas.INSTANCE.slotFor(source, false);
EntityTextureAtlas.INSTANCE.clear();
final EntityTextureAtlas.Slot after = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(after);
assertNotNull(before);
assertSame(before.page, after.page, "a cleared atlas reuses its page instead of deleting and reallocating it");
assertEquals(1, pages.size(), "the page is registered with the texture manager once");
assertSlotHolds(after, 0);
}
@Test
void aDeletedTexturesRegionIsReusedOnlyFromTheNextPass() {
final EntityTextureAtlas.Slot first = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(first);
GLStateManager.glDeleteTextures(source);
final int samePass = texture(GL11.GL_NEAREST, 3);
try {
final EntityTextureAtlas.Slot taken = EntityTextureAtlas.INSTANCE.slotFor(samePass, false);
assertNotNull(taken);
assertFalse(taken.x == first.x && taken.y == first.y, "instances queued earlier in the pass still sample the old region");
EntityTextureAtlas.INSTANCE.recycleReleasedRegions();
source = texture(GL11.GL_NEAREST, 5);
final EntityTextureAtlas.Slot reused = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(reused);
assertEquals(first.x, reused.x);
assertEquals(first.y, reused.y);
assertSlotHolds(reused, 5);
} finally {
GLStateManager.glDeleteTextures(samePass);
}
}
@Test
void smallTexturesStillFitAfterALargeOneIsTurnedAway() {
final int large = 512;
final int[] filling = new int[4 * 9 + 1];
final int small = texture(GL11.GL_NEAREST, 0);
try {
for (int i = 0; i < filling.length; i++) filling[i] = blankTexture(large);
for (int i = 0; i < filling.length - 1; i++) assertNotNull(EntityTextureAtlas.INSTANCE.slotFor(filling[i], false), "large texture " + i);
assertNull(EntityTextureAtlas.INSTANCE.slotFor(filling[filling.length - 1], false), "no page has room for another 512");
assertNotNull(EntityTextureAtlas.INSTANCE.slotFor(small, false), "shelves still have room for a small texture");
} finally {
for (int id : filling) if (id != 0) GLStateManager.glDeleteTextures(id);
GLStateManager.glDeleteTextures(small);
}
}
private static int blankTexture(int size) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return id;
}
}
@@ -129,6 +129,21 @@ class ModelBoxCaptureTest {
return false;
}
@ParameterizedTest
@CsvSource({
"false, 0.0",
"true, 0.0",
"false, 0.5",
"true, 0.5",
})
void constructedBoxCapturesFromItsStoredCorner(boolean mirror, float inflate) {
final ModelRenderer part = part(64, 32, mirror);
final ModelBox box = new ModelBox(part, 40, 16, -3, -2, -2, 4, 12, 4, inflate);
final CubeParams fromBox = ModelBoxCapture.capture(box, quadsOf(box), part, 40, 16, 4, 12, 4, inflate);
assertEquals(ModelBoxCapture.capture(quadsOf(box), 64, 32, mirror, 40, 16, -3, -2, -2, 4, 12, 4, inflate), fromBox);
assertNotNull(fromBox, "mirrored and inflated boxes must verify");
}
@Test
void mutatedQuadListIsRejected() {
final ModelRenderer part = part(64, 32, false);
@@ -60,8 +60,7 @@ class RetainedTesrGroupsGLTest {
private static final float[] TRIANGLE = { -0.1f, -0.1f, 0f, 0.1f, -0.1f, 0f, 0f, 0.15f, 0f };
private static final ResourceLocation GLINT_TEXTURE = new ResourceLocation("angelica", "test/glint");
private static boolean overridesRegistered;
private static boolean skipClientArrays;
private static boolean probeRegistered;
private static Runnable onBufferSetup;
private AngelicaBufferSource source;
@@ -82,14 +81,13 @@ class RetainedTesrGroupsGLTest {
private CubeParams[] glintCubes;
@BeforeAll
static void registerClientArrayOverrides() {
if (overridesRegistered) return;
overridesRegistered = true;
static void registerBufferSetupProbe() {
if (probeRegistered) return;
probeRegistered = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
if (onBufferSetup != null) onBufferSetup.run();
return skipClientArrays;
return false;
});
VertexFormat.registerClearBufferStateOverride(format -> skipClientArrays);
}
@BeforeEach
@@ -119,7 +117,6 @@ class RetainedTesrGroupsGLTest {
@AfterEach
void cleanup() {
onBufferSetup = null;
skipClientArrays = false;
source.discard();
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
@@ -194,7 +191,7 @@ class RetainedTesrGroupsGLTest {
for (int id = 1; id <= 3; id++) {
final Matrix4f mv = new Matrix4f().translation(id, 0f, 0f);
groups.queue(template, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, null);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, null);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, null, null);
}
}
@@ -221,12 +218,8 @@ class RetainedTesrGroupsGLTest {
state.setCurrentBlockEntity(83);
state.setCurrentEntityColor(0, 1, 0, 0.5f);
draws.clear();
skipClientArrays = true;
assertEquals(GL11.GL_QUADS, template.drawMode);
groups.drawLayer(layer);
// FIXME each GL_QUADS fallback draw (VertexBuffer.draw inside MeshBuffer.render) raises this with the
// client arrays skipped; a GL_TRIANGLES template does not.
assertEquals(GL11.GL_INVALID_ENUM, GL11.glGetError());
assertEquals(3, draws.size(), "only differing uniform values split the CPU fallback");
for (int i = 0; i < 3; i++) {
assertArrayEquals(new long[] {45010 + i, 45020 + i, 45030 + i,
@@ -290,7 +283,7 @@ class RetainedTesrGroupsGLTest {
for (int id = 1; id <= 3; id++) {
final Matrix4f mv = new Matrix4f().translation(id, 0f, 0f);
groups.queue(template, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, texMatrices[id - 1]);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, null);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, RetainedTesrTestSupport.STREAM, mv, 0, -1, 0, 0L, id, null, null);
}
groups.drawLayer(layer);
@@ -330,7 +323,7 @@ class RetainedTesrGroupsGLTest {
for (int id = 1; id <= 3; id++) {
final Matrix4f mv = new Matrix4f().translation(id, 0f, 0f);
groups.queue(template, layer, EntityMaterials.GLINT, mv, 0, -1, 0, 0L, id, texMatrices[id - 1]);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, EntityMaterials.GLINT, mv, 0, -1, 0, 0L, id, texMatrices[id - 1]);
groups.queue(template, RetainedTesrTestSupport.cubes(1), 0.0625f, layer, EntityMaterials.GLINT, mv, 0, -1, 0, 0L, id, texMatrices[id - 1], null);
}
final RetainedTesrGroups.Group group = RetainedTesrTestSupport.onlyGroup(groups);
@@ -339,7 +332,6 @@ class RetainedTesrGroupsGLTest {
assertEquals(2, group.cubeColumns.runs.size(), "sanity: cube runs A,B,A merge into two heads");
assertNotNull(group.cubeColumns.runs.get(0).next, "sanity: the cube A head must carry a second segment");
skipClientArrays = true;
groups.drawLayer(layer);
assertEquals(0, pipeline.matrixBinds);
@@ -610,8 +602,8 @@ class RetainedTesrGroupsGLTest {
FfpFixture.clear();
bindBase();
final InstanceColumns cols = new InstanceColumns();
cols.addCubes(null, glintCubes, CUBE_SCALE, cubeE1, 0, 0xFFFFFFFF, 0, 0L);
cols.addCubes(null, glintCubes, CUBE_SCALE, cubeE2, 0, 0xFFFFFFFF, 0, 0L);
cols.addCubes(null, glintCubes, CUBE_SCALE, cubeE1, 0, 0xFFFFFFFF, 0, 0L, null);
cols.addCubes(null, glintCubes, CUBE_SCALE, cubeE2, 0, 0xFFFFFFFF, 0, 0L, null);
instanced.drawCubes(cols, run(2, 2 * glintCubes.length));
instanced.endFrame();
return FfpFixture.readRegion(SIZE);
@@ -619,7 +611,7 @@ class RetainedTesrGroupsGLTest {
private void drawCubeInstance(Matrix4f mv) {
final InstanceColumns cols = new InstanceColumns();
cols.addCubes(null, glintCubes, CUBE_SCALE, mv, 0, 0xFFFFFFFF, 0, 0L);
cols.addCubes(null, glintCubes, CUBE_SCALE, mv, 0, 0xFFFFFFFF, 0, 0L, null);
instanced.drawCubes(cols, run(1, glintCubes.length));
instanced.endFrame();
}
@@ -676,10 +668,10 @@ class RetainedTesrGroupsGLTest {
setGlintBlendState();
bindGlint();
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE1, 0, -1, 0, 0L, 1, glintMatrixA);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE1, 0, -1, 0, 0L, 1, glintMatrixB);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE2, 0, -1, 0, 0L, 2, glintMatrixA);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE2, 0, -1, 0, 0L, 2, glintMatrixB);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE1, 0, -1, 0, 0L, 1, glintMatrixA, null);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE1, 0, -1, 0, 0L, 1, glintMatrixB, null);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE2, 0, -1, 0, 0L, 2, glintMatrixA, null);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, cubeE2, 0, -1, 0, 0L, 2, glintMatrixB, null);
groups.drawLayer(layer);
instanced.endFrame();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "the merged candidate draw must not raise a GL error");
@@ -755,7 +747,7 @@ class RetainedTesrGroupsGLTest {
for (Matrix4f pose : new Matrix4f[] {cubeE1, cubeE2}) {
applyGlintTexMatrix(glintMatrixA);
assertTrue(capture.begin(groups));
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, pose, 0, -1, 0, 0L, 0, glintMatrixA);
groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, pose, 0, -1, 0, 0L, 0, glintMatrixA, null);
capture.end();
applyGlintTexMatrix(glintMatrixB);
assertTrue(capture.replay(groups));
@@ -11,6 +11,7 @@ import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.batchedentityrendering.impl.BufferSegment;
import net.coderbot.batchedentityrendering.impl.BufferSourceProbe;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -403,8 +404,8 @@ class RetainedTesrGroupsTest {
final Matrix4f mv = new Matrix4f().translation(1f, 2f, 3f);
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring));
groups.queue(template, cubes(3), 0.0625f, layer, STREAM, mv, 7, -1, 0, 0L, 5, null);
groups.queue(template, cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 7, -1, 0, 0L, 5, null);
groups.queue(template, cubes(3), 0.0625f, layer, STREAM, mv, 7, -1, 0, 0L, 5, null, null);
groups.queue(template, cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 7, -1, 0, 0L, 5, null, null);
assertTrue(groups.hasDraws(layer));
assertEquals(0, groups.streamedInstances);
@@ -416,8 +417,8 @@ class RetainedTesrGroupsTest {
final RetainedTesrGroups piped = new RetainedTesrGroups(source);
piped.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), new RetainedTesrTestSupport.CountingPipeline(false, false));
piped.queue(template, cubes(3), 0.0625f, layer, STREAM, mv, 7, -1, 0, 0L, 5, null);
piped.queue(template, cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 7, -1, 0, 0L, 5, null);
piped.queue(template, cubes(3), 0.0625f, layer, STREAM, mv, 7, -1, 0, 0L, 5, null, null);
piped.queue(template, cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 7, -1, 0, 0L, 5, null, null);
final RetainedTesrGroups.Group pipedGroup = onlyGroup(piped);
assertEquals(2, pipedGroup.cubeColumns.templates.size(), "a pipeline can fall back to the per-part template");
@@ -428,7 +429,7 @@ class RetainedTesrGroupsTest {
void cubePartsFallBackToTemplatesWithoutAnInstancedRenderer() {
groups.beginPass(new Matrix4f(), 0, 0, 0);
groups.queue(template(), cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null);
groups.queue(template(), cubes(2), 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null, null);
assertEquals(0, cubeInstances(groups));
assertEquals(1, groups.streamedInstances);
@@ -439,9 +440,9 @@ class RetainedTesrGroupsTest {
final CubeParams[] params = cubes(2);
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring));
groups.queue(template(), params, 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null);
groups.queue(template(), params, 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null, null);
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring));
groups.queue(template(), params, 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null);
groups.queue(template(), params, 0.0625f, layer, STREAM, new Matrix4f(), 0, -1, 0, 0L, 5, null, null);
assertEquals(2, cubeInstances(groups), "the previous frame's instances must not accumulate");
}
@@ -526,4 +527,17 @@ class RetainedTesrGroupsTest {
assertEquals(2, groups.groupCount(), "the last-group memo must compare material, not just layer and id, on every queue call");
}
@Test
void glintCopiesSampleTheirOwnTextureRatherThanTheBaseAtlasRegion() {
final EntityTextureAtlas.Page page = new EntityTextureAtlas.Page(new ResourceLocation("angelica", "test_page"), 0);
final EntityTextureAtlas.Slot slot = new EntityTextureAtlas.Slot(page, 0, 0, 4, 2);
final RetainedTesrGroups.InstanceColumns base = new RetainedTesrGroups.InstanceColumns();
base.addCubes(null, cubes(1), 0.0625f, new Matrix4f(), 0, -1, 0, 0L, slot);
final RetainedTesrGroups.InstanceColumns glint = new RetainedTesrGroups.InstanceColumns();
glint.appendRange(base, 0, 1, 0, -1, 0);
assertSame(slot, base.atlasSlot(0));
assertNull(glint.atlasSlot(0));
}
}
@@ -75,7 +75,7 @@ class EntityIdHelperTest {
for (int id : new int[] { 41, 42 }) {
settings.setEntityIds(key -> name.equals(key) ? 17 : -1);
final NbtConditionalIdMap<NamespacedId> nbt = new NbtConditionalIdMap<>();
nbt.addCondition(name, Collections.singletonMap("Data.Variant", new NbtValue("original", true)), id);
nbt.addCondition(name, Collections.singletonMap("Data.Variant", new NbtValue("original", true)), id, null);
settings.setEntityNbtMap(nbt);
assertEquals(id, identity.resolve(), "resolve the captured variant against the current pack");
assertEquals(17, EntityIdHelper.getEntityId(entity), "the live entity has changed variants");