Compare commits

...
18 Commits
Author SHA1 Message Date
James b6a4497fb3 Redirected Util.checkGLError to respect disableErrorChecks (#2110) 2026-09-18 22:33:17 -07:00
Eclipse-Sol 939e31461a More Cloud Optimizations (#2114) 2026-09-18 21:53:50 -07:00
Eclipse-Sol 02f0b0fca1 Fix mipmaps bleeding (#2112) 2026-09-17 20:29:53 -07:00
Nick Smith b7d439e6b1 Added abutting block detection and processing (#2068) 2026-09-16 16:09:40 -07:00
KAMKEEL c36647af34 Hex Text and other Font Mod Compat System (#1972) 2026-09-16 16:01:16 -07:00
Eclipse-Sol f787801a0e Fix borking modded mobs with broken eyes (#2108) 2026-09-16 16:00:09 -07:00
Algent f192793395 Fix microblock transparency in the hotbar (#2107) 2026-09-16 15:48:26 -07:00
Jason Mitchell 3876f7e693 Instance more things (#2104)
* Flyby: pin the start origin and fly just above the route's terrain

* Instancing: particles, dropped items, entity cubes + shadow batching and some SDL-GPU fixes
2026-09-16 14:56:24 -07:00
danyadev 94ee37bfed fix resize handling on non-main thread for lwjgl2 (#2106) 2026-09-16 13:59:15 -07:00
Eclipse-Sol 26c161f0e8 Improve upon Texture Filtering Work (#2096) 2026-09-15 16:00:43 -07:00
Jason Mitchell 75639d3b73 Flyby improvements (#2100) 2026-09-14 19:32:31 -07:00
Jason Mitchell a07cecb879 Fix some GLSM bypasses (#2099) 2026-09-14 14:22:19 -07:00
kurrycat 81b82748ce SDL: flush vertex attribs + deferred texture uploads before readback (#2095) 2026-09-13 11:00:20 -07:00
Arvid JunehagandMartin Robertz 8d0c90c423 Updated the list of blocks to get rotated (#2094)
Co-authored-by: Martin Robertz <[email protected]>
2026-09-13 01:33:21 +00:00
kurrycat 8ce6a2f8c6 Fix cloud elevation setting (#2093) 2026-09-12 14:31:26 -07:00
DarkShadow44 2aa4ac7509 DH: Fix shaderpack fading (#2091) 2026-09-12 09:43:28 -07:00
DarkShadow44 8f98151049 Remove superflous glVertexAttribPointer call (#2090) 2026-09-12 09:16:27 -07:00
Eclipse-Sol 2b70318327 Fix issues with Schematica (#2088) 2026-09-09 17:51:39 -07:00
267 changed files with 17926 additions and 2884 deletions

No files matched your search

+2
View File
@@ -22,6 +22,8 @@ indent_size = 2
trim_trailing_whitespace = false
[*.java]
max_line_length = 240
ij_java_wrap_long_lines = false
ij_java_use_single_class_imports = true
ij_java_class_count_to_use_import_on_demand = 999
ij_java_names_count_to_use_import_on_demand = 999
+1
View File
@@ -44,5 +44,6 @@ tracy-client/src/main/resources/natives/
tracy-client/angelica/
dist/tracy-natives/
*.tracy
*.jfr
tracy-client/angelica/
+2
View File
@@ -101,3 +101,5 @@ Targets GL 3.3+ core profile
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
* RGSS Terrain Texture Filtering
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
+7 -1
View File
@@ -103,6 +103,11 @@ repositories {
mavenLocal()
}
val spirvTestRuntime by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
@@ -133,6 +138,7 @@ dependencies {
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3)
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
spirvTestRuntime(libs.lwjgl3.opengl)
// @Lwjgl3Aware annotation
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
@@ -309,7 +315,7 @@ val spirvTest by tasks.registering(Test::class) {
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
}.plus(spirvTestRuntime)
filter {
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
@@ -53,6 +53,9 @@ public final class SystemProperties {
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -61,6 +64,7 @@ public final class SystemProperties {
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
private static final boolean DEBUG_TOOLING_PROP = Boolean.getBoolean("angelica.debug.tooling");
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
@@ -76,6 +80,10 @@ public final class SystemProperties {
return DUMP_SHADERS_PROP || isDeobf();
}
public static boolean debugTooling() {
return DEBUG_TOOLING_PROP || isDeobf();
}
public static Path shaderDumpDir(String phase) {
return dumpShaders() ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
}
@@ -105,6 +113,11 @@ public final class SystemProperties {
FATAL
}
public enum FlybyPacing {
UNCAPPED,
CONFIGURED
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
@@ -27,6 +27,7 @@ import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -101,6 +102,7 @@ class GLContextState {
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
public final TextureUnitArray textures = new TextureUnitArray();
public final ImageUnitArray imageUnits = new ImageUnitArray();
public final SamplerUnitArray samplerUnits = new SamplerUnitArray();
public final BlendStateStack blendState = new BlendStateStack();
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
@@ -10,8 +10,10 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
@@ -52,6 +54,7 @@ import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -91,6 +94,7 @@ import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
@@ -424,11 +428,7 @@ public class GLStateManager {
public static boolean wideLineEmulationActive = false;
public static boolean lineStippleActive = false;
public static boolean instancedFfpDrawActive = false;
public static Instancing ffpInstancing = Instancing.NONE;
private static final MethodHandle MAT4_STACK_CURR_DEPTH;
@@ -1054,6 +1054,8 @@ public class GLStateManager {
case GL30.GL_DRAW_FRAMEBUFFER_BINDING -> glCtx.drawFramebuffer;
case GL30.GL_READ_FRAMEBUFFER_BINDING -> glCtx.readFramebuffer;
case GL33.GL_SAMPLER_BINDING -> glCtx.samplerUnits.get(glCtx.activeTextureUnit.getValue());
default -> switch (pname) {
case GL11.GL_FOG_MODE -> glCtx.fogState.getFogMode();
case GL11.GL_LINE_STIPPLE_PATTERN -> glCtx.lineState.getStipplePattern() & 0xFFFF;
@@ -1306,6 +1308,29 @@ public class GLStateManager {
}
}
public static void glEnablei(int cap, int index) {
RENDER_BACKEND.enablei(cap, index);
markIndexedCapUnknown(cap);
}
public static void glDisablei(int cap, int index) {
RENDER_BACKEND.disablei(cap, index);
markIndexedCapUnknown(cap);
}
private static void markIndexedCapUnknown(int cap) {
if (cap == GL11.GL_BLEND) {
ctx().blendMode.setUnknownState();
} else if (cap == GL11.GL_SCISSOR_TEST) {
ctx().scissorTest.setUnknownState();
}
}
public static void glBlendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buf, srcRGB, dstRGB, srcAlpha, dstAlpha);
ctx().blendState.setFuncUnknownState();
}
public static void enableBlend() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -1401,8 +1426,11 @@ public class GLStateManager {
}
public static void endForeignDraw() {
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0) {
GLSMHooks.resolvePendingProgram();
if (GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
}
}
}
@@ -1451,6 +1479,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
if (GLSMConfig.hudCacheOverride) {
glCtx.blendState.setAll(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
@@ -1467,8 +1496,10 @@ public class GLStateManager {
|| glCtx.blendState.getSrcRgb() != srcFactor
|| glCtx.blendState.getDstRgb() != dstFactor
|| glCtx.blendState.getSrcAlpha() != srcFactor
|| glCtx.blendState.getDstAlpha() != dstFactor) {
|| glCtx.blendState.getDstAlpha() != dstFactor
|| glCtx.blendState.isFuncUnknown()) {
glCtx.blendState.setAll(srcFactor, dstFactor, srcFactor, dstFactor);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFunc(srcFactor, dstFactor);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
@@ -1531,8 +1562,9 @@ public class GLStateManager {
}
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha) {
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha || glCtx.blendState.isFuncUnknown()) {
glCtx.blendState.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcRgb;
@@ -3546,8 +3578,7 @@ public class GLStateManager {
public static void updateDisplay(boolean processMessages) throws LWJGLException {
if (Thread.currentThread() != MainThread) {
swapBuffers();
if (processMessages) pumpDisplayMessages();
RENDER_BACKEND.updateDisplayFromWorkerThread(processMessages);
return;
}
if (processMessages) pumpDisplayMessages();
@@ -3756,6 +3787,7 @@ public class GLStateManager {
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
@@ -3779,6 +3811,12 @@ public class GLStateManager {
applyPolygonOffset(glCtx);
RENDER_BACKEND.cullFace(glCtx.polygonState.getCullFaceMode());
RENDER_BACKEND.frontFace(glCtx.polygonState.getFrontFace());
final SamplerUnitArray samplerUnits = glCtx.samplerUnits;
for (int i = 0; i < samplerUnits.size(); i++) {
final int sampler = samplerUnits.get(i);
if (sampler != 0) RENDER_BACKEND.bindSampler(i, sampler);
}
}
private static void applyPolygonOffset(GLContextState glCtx) {
@@ -3803,13 +3841,18 @@ public class GLStateManager {
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
}
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
if (glCtx.restoreBlendChanged) {
final BlendStateStack blend = glCtx.blendState;
if (glCtx.restoreBlendChanged || blend.isFuncUnknown()) {
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
blend.clearFuncUnknownState();
}
if (glCtx.restoreBlendChanged) {
RENDER_BACKEND.blendEquationSeparate(blend.getEquationRgb(), blend.getEquationAlpha());
RENDER_BACKEND.blendColor(
blend.getBlendColorR(), blend.getBlendColorG(), blend.getBlendColorB(), blend.getBlendColorA());
}
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
}
if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) {
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
@@ -4019,6 +4062,14 @@ public class GLStateManager {
}
}
public static void setTextureMatrix(int unit, Matrix4fc m) {
if (isCachingEnabled()) {
final GLContextState glCtx = ctx();
glCtx.textures.getTextureUnitMatrix(unit).set(m);
glCtx.texMatrixGeneration++;
}
}
public static void setProjectionMatrix(Matrix4fc m) {
final GLContextState glCtx = ctx();
if (isCachingEnabled()) {
@@ -4721,8 +4772,61 @@ public class GLStateManager {
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
public static int glGenSamplers() { return RENDER_BACKEND.genSamplers(); }
public static void glDeleteSamplers(int sampler) { RENDER_BACKEND.deleteSamplers(sampler); }
public static void glBindSampler(int unit, int sampler) { RENDER_BACKEND.bindSampler(unit, sampler); }
public static void glGenSamplers(IntBuffer samplers) {
for (int i = samplers.position(); i < samplers.limit(); i++) samplers.put(i, RENDER_BACKEND.genSamplers());
}
public static void glGenSamplers(int[] samplers) {
for (int i = 0; i < samplers.length; i++) samplers[i] = RENDER_BACKEND.genSamplers();
}
public static void glDeleteSamplers(int sampler) {
if (sampler == 0) return;
ctx().samplerUnits.unbindEverywhere(sampler);
RENDER_BACKEND.deleteSamplers(sampler);
}
public static void glDeleteSamplers(IntBuffer samplers) {
for (int i = samplers.position(); i < samplers.limit(); i++) glDeleteSamplers(samplers.get(i));
}
public static void glDeleteSamplers(int[] samplers) {
for (int sampler : samplers) glDeleteSamplers(sampler);
}
public static void glBindSampler(int unit, int sampler) {
final SamplerUnitArray units = ctx().samplerUnits;
if (isCachingEnabled() && unit >= 0 && unit < units.size() && units.get(unit) == sampler) return;
if (!units.set(unit, sampler)) {
warnOnce("samplerUnitRange", "glBindSampler unit {} exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS {}", unit, units.size());
}
RENDER_BACKEND.bindSampler(unit, sampler);
}
public static void glBindSamplers(int first, int count, IntBuffer samplers) {
if (samplers == null) {
for (int i = 0; i < count; i++) glBindSampler(first + i, 0);
return;
}
if (samplers.remaining() < count) {
throw new IllegalArgumentException("Number of remaining buffer elements is " + samplers.remaining() + ", must be at least " + count);
}
final int base = samplers.position();
for (int i = 0; i < count; i++) glBindSampler(first + i, samplers.get(base + i));
}
public static void glBindSamplers(int first, IntBuffer samplers) {
if (samplers != null) glBindSamplers(first, samplers.remaining(), samplers);
}
public static void glBindSamplers(int first, int[] samplers) {
if (samplers == null) return;
for (int i = 0; i < samplers.length; i++) glBindSampler(first + i, samplers[i]);
}
public static int getSamplerBinding(int unit) { return ctx().samplerUnits.get(unit); }
public static int getSamplerUnitCount() { return ctx().samplerUnits.size(); }
public static void glSamplerParameteri(int sampler, int pname, int param) { RENDER_BACKEND.samplerParameteri(sampler, pname, param); }
public static void glSamplerParameterf(int sampler, int pname, float param) { RENDER_BACKEND.samplerParameterf(sampler, pname, param); }
@@ -5294,6 +5398,11 @@ public class GLStateManager {
return RENDER_BACKEND.getError();
}
public static void checkGLError() {
final int error = glGetError();
if (error != GL11.GL_NO_ERROR) throw new org.lwjgl.opengl.OpenGLException(error);
}
public static String glGetString(int pname) {
if (!RENDER_BACKEND.hasContext()) return "no valid GL/render context";
return RENDER_BACKEND.getString(pname);
@@ -6173,6 +6282,10 @@ public class GLStateManager {
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { onBufferMapRange(target, access); return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static long glMapBufferRangeAddress(int target, long offset, long length, int access) {
onBufferMapRange(target, access);
return RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
}
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
@@ -49,6 +49,7 @@ public class RenderSystem {
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
@Getter private static int maxCombinedTextureImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@@ -64,9 +65,6 @@ public class RenderSystem {
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
private RenderSystem() {}
@@ -158,7 +156,7 @@ public class RenderSystem {
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
@@ -486,17 +484,15 @@ public class RenderSystem {
}
public static void disableBufferBlend(int buffer) {
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
GLStateManager.glDisablei(GL11.GL_BLEND, buffer);
}
public static void enableBufferBlend(int buffer) {
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
GLStateManager.glEnablei(GL11.GL_BLEND, buffer);
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
GLStateManager.glBlendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
}
public static void bindTextureToUnit(int unit, int texture) {
@@ -628,7 +624,7 @@ public class RenderSystem {
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
return GLStateManager.glGenSamplers();
}
public static void destroySampler(int sampler) {
@@ -638,22 +634,25 @@ public class RenderSystem {
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
GLStateManager.glSamplerParameteri(sampler, pname, param);
}
public static void samplerParameterf(int sampler, int pname, float param) {
if (!supportsSamplerObjects || sampler == 0) return;
GLStateManager.glSamplerParameterf(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
RENDER_BACKEND.bindSampler(unit, sampler);
samplers[unit] = sampler;
GLStateManager.glBindSampler(unit, sampler);
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
RENDER_BACKEND.bindSampler(i, 0);
samplers[i] = 0;
final int count = GLStateManager.getSamplerUnitCount();
for (int i = 0; i < count; i++) {
if (GLStateManager.getSamplerBinding(i) != 0) {
GLStateManager.glBindSampler(i, 0);
}
}
}
@@ -134,6 +134,12 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return caps != null && caps.GL_EXT_texture_filter_anisotropic;
}
@Override
public void updateDisplayFromWorkerThread(boolean processMessages) {
if (processMessages) pumpDisplayMessages();
Display.update(false);
}
@Override
public void flush() {GL11.glFlush();}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL20;
@@ -251,9 +252,16 @@ public abstract class RenderBackend {
public boolean handleSwapBuffers() { return false; }
public void onRenderThreadReleased(Thread thread) {}
public void updateDisplayFromWorkerThread(boolean processMessages) throws LWJGLException {
GLStateManager.swapBuffers();
if (processMessages) {
pumpDisplayMessages();
}
}
public String getTransferDebugInfo() { return null; }
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) { return false; }
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
public void endVoxelizationBatch(long pass) {}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
public final class CubeInstancedAttribs {
public static final int LOC_LIGHTMAP_SCALE = InstancedAttribs.LOC_LIGHTMAP;
public static final int LOC_CUBE_TEX = 15;
public static final int OFFSET_LIGHTMAP_SCALE = InstancedAttribs.HEAD_SIZE;
public static final int OFFSET_CUBE_TEX = OFFSET_LIGHTMAP_SCALE + 16;
public static final int STRIDE = OFFSET_CUBE_TEX + 16;
public static void pointInstanceAttribs(long base) {
InstancedAttribs.pointHead(base, STRIDE);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP_SCALE, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP_SCALE);
GLStateManager.glVertexAttribPointer(LOC_CUBE_TEX, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CUBE_TEX);
}
static void enableInstanceArrays() {
InstancedAttribs.enableHeadArrays();
InstancedAttribs.enableInstanced(LOC_CUBE_TEX);
}
private CubeInstancedAttribs() {}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.ffp;
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;
public CubeParams {
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
}
public static CubeParams of(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 (texWidth <= 0.0f || texHeight <= 0.0f) return null;
if (sizeX < 0 || sizeY < 0 || sizeZ < 0) return null;
if (mirror && sizeX == 0) return null;
final float minX = x - inflate;
final float minY = y - inflate;
final float minZ = z - inflate;
final float spanX = ((x + sizeX) + inflate) - minX;
final float spanY = ((y + sizeY) + inflate) - minY;
final float spanZ = ((z + sizeZ) + inflate) - minZ;
if (spanX <= MIN_SPAN || spanY <= MIN_SPAN || spanZ <= MIN_SPAN) return null;
return new CubeParams(minX, minY, minZ, spanX, spanY, spanZ, texU / texWidth, texV / texHeight, (mirror ? -sizeX : sizeX) / texWidth, sizeY / texHeight, sizeZ / texWidth, sizeZ / texHeight);
}
public void writeRows(long ptr, float[] mv, int off, float scale) {
final float tx = minX * scale;
final float ty = minY * scale;
final float tz = minZ * scale;
final float sx = spanX * scale;
final float sy = spanY * scale;
final float sz = spanZ * scale;
for (int r = 0; r < 3; r++) {
final float c0 = mv[off + r];
final float c1 = mv[off + 4 + r];
final float c2 = mv[off + 8 + r];
final long row = ptr + InstancedAttribs.OFFSET_ROWS + r * 16L;
memPutFloat(row, c0 * sx);
memPutFloat(row + 4, c1 * sy);
memPutFloat(row + 8, c2 * sz);
memPutFloat(row + 12, mv[off + 12 + r] + c0 * tx + c1 * ty + c2 * tz);
}
}
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);
}
}
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (13) + unit 0 (47 bits at offset 13)
* long[0]: global bits (14) + unit 0 (47 bits at offset 14)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,7 +63,7 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 13;
private static final int GLOBAL_BITS = 14;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -72,6 +72,7 @@ public final class FragmentKey {
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int BIT_COLOR_SUM = 12; // 1 bit
private static final int BIT_OVERLAY_INSTANCED = 13;
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -123,7 +124,9 @@ public final class FragmentKey {
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} else if (GLStateManager.getOverlayA() != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
@@ -245,6 +248,7 @@ public final class FragmentKey {
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -378,10 +382,7 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), colorSum(), overlayEnabled(), nrEnabledUnits()));
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -35,6 +35,9 @@ public final class FragmentShaderGenerator {
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.overlayInstanced()) {
sb.append("in vec4 v_Overlay;\n");
}
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
@@ -116,8 +119,11 @@ public final class FragmentShaderGenerator {
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
if (key.overlayInstanced()) {
sb.append(" color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);\n");
} else if (key.overlayEnabled()) {
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -1,18 +1,84 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 5;
public static final int LOC_MATRIX_COL1 = 6;
public static final int LOC_MATRIX_COL2 = 7;
public static final int LOC_MATRIX_COL3 = 8;
public static final int LOC_COLOR = 9;
public static final int LOC_ROW0 = 5;
public static final int LOC_ROW1 = 6;
public static final int LOC_ROW2 = 7;
public static final int LOC_COLOR = 8;
public static final int LOC_OVERLAY = 9;
public static final int LOC_LIGHTMAP = 10;
public static final int LOC_ENTITY = 11;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
public static final int OFFSET_LIGHTMAP = 68;
public static final int STRIDE = 80;
public static final int OFFSET_ROWS = 0;
public static final int OFFSET_COLOR = 48;
public static final int OFFSET_OVERLAY = 52;
public static final int OFFSET_ENTITY = 56;
public static final int HEAD_SIZE = 64;
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
public static final int STRIDE = 72;
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
writeRows(ptr, colMajor, off);
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
}
private static void writeRows(long ptr, float[] colMajor, int off) {
for (int r = 0; r < 3; r++) {
final long row = ptr + OFFSET_ROWS + r * 16L;
memPutFloat(row, colMajor[off + r]);
memPutFloat(row + 4, colMajor[off + 4 + r]);
memPutFloat(row + 8, colMajor[off + 8 + r]);
memPutFloat(row + 12, colMajor[off + 12 + r]);
}
}
public static void writeTail(long ptr, int colorABGR, int overlayABGR, long entityInfo) {
memPutInt(ptr + OFFSET_COLOR, colorABGR);
memPutInt(ptr + OFFSET_OVERLAY, overlayABGR);
memPutLong(ptr + OFFSET_ENTITY, entityInfo);
}
public static void writeLightmap(long ptr, int packedLight) {
memPutFloat(ptr, packedLight & 0xFFFF);
memPutFloat(ptr + 4, (packedLight >>> 16) & 0xFFFF);
}
public static long packEntityInfo(int entity, int blockEntity, int item) {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
pointHead(base, STRIDE);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
public static void enableHeadArrays() {
for (int loc = LOC_ROW0; loc <= LOC_ENTITY; loc++) {
enableInstanced(loc);
}
}
static void enableInstanced(int loc) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribDivisor(loc, 1);
}
private InstancedAttribs() {}
}
@@ -0,0 +1,84 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import java.util.ArrayList;
public final class InstancedGlslHelpers {
private InstancedGlslHelpers() {}
public static String mat4FromRows(String row0, String row1, String row2) {
return "mat4("
+ "vec4(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x, 0.0), "
+ "vec4(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y, 0.0), "
+ "vec4(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z, 0.0), "
+ "vec4(" + row0 + ".w, " + row1 + ".w, " + row2 + ".w, 1.0))";
}
public static String mat3FromRows(String row0, String row1, String row2) {
return "mat3("
+ "normalize(vec3(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x)), "
+ "normalize(vec3(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y)), "
+ "normalize(vec3(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z)))";
}
public static String particlePos(String centerHalf, String offset) {
return "vec4(" + centerHalf + ".xyz + " + offset + " * " + centerHalf + ".w, 1.0)";
}
public static String particleUv(String uv, String corner) {
return "vec4("
+ "mix(" + uv + ".x, " + uv + ".z, " + corner + ".x), "
+ "mix(" + uv + ".y, " + uv + ".w, " + corner + ".y), "
+ "0.0, 1.0)";
}
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
+ "vec2 " + prefix + "cubeU = " + prefix + "cubeMidU + (1.0 - 2.0 * " + prefix + "cubeMirror) * " + delta + ".xy;" + sep
+ "vec2 " + prefix + "cubeV = " + mid + ".zw + " + delta + ".zw;";
}
public static String cubeUv(String tex, String lightScale, String uA, String uZ, String vA, String vZ) {
return "vec4("
+ tex + ".x + " + uA + " * abs(" + tex + ".z) + " + uZ + " * " + lightScale + ".z, "
+ tex + ".y + " + vA + " * " + tex + ".w + " + vZ + " * " + lightScale + ".w, "
+ "0.0, 1.0)";
}
public static String[] attributeDecls(String prefix, Instancing kind) {
final ArrayList<String> decls = new ArrayList<>(9);
switch (kind) {
case NONE -> {}
case TEMPLATE -> addHead(decls, prefix, "vec2", "InstLightmap");
case CUBE -> {
decls.add(decl(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), "vec4", prefix + "CubeMid"));
decls.add(decl(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), "vec4", prefix + "CubeDelta"));
addHead(decls, prefix, "vec4", "InstLightmapScale");
decls.add(decl(CubeInstancedAttribs.LOC_CUBE_TEX, "vec4", prefix + "CubeTex"));
}
case PARTICLE -> {
decls.add(decl(ParticleInstancedAttribs.LOC_CENTER_HALF, "vec4", prefix + "InstCenterHalf"));
decls.add(decl(ParticleInstancedAttribs.LOC_UV, "vec4", prefix + "InstUv"));
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
}
}
return decls.toArray(String[]::new);
}
private static void addHead(ArrayList<String> decls, String prefix, String lightmapType, String lightmapName) {
decls.add(decl(InstancedAttribs.LOC_ROW0, "vec4", prefix + "InstRow0"));
decls.add(decl(InstancedAttribs.LOC_ROW1, "vec4", prefix + "InstRow1"));
decls.add(decl(InstancedAttribs.LOC_ROW2, "vec4", prefix + "InstRow2"));
decls.add(decl(InstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(InstancedAttribs.LOC_OVERLAY, "vec4", prefix + "InstOverlay"));
decls.add(decl(InstancedAttribs.LOC_LIGHTMAP, lightmapType, prefix + lightmapName));
}
private static String decl(int loc, String type, String name) {
return "layout(location = " + loc + ") in " + type + " " + name + ";";
}
}
@@ -0,0 +1,20 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced");
public static final Instancing[] VALUES = values();
public final String variantSuffix;
Instancing(String variantSuffix) {
this.variantSuffix = variantSuffix;
}
public boolean hasInstanceHead() {
return this == TEMPLATE || this == CUBE;
}
}
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleInstancedAttribs {
public static final int LOC_CENTER_HALF = 5;
public static final int LOC_UV = 6;
public static final int LOC_COLOR = InstancedAttribs.LOC_COLOR;
public static final int LOC_LIGHTMAP = InstancedAttribs.LOC_LIGHTMAP;
public static final int OFFSET_CENTER_HALF = 0;
public static final int OFFSET_UV = 16;
public static final int OFFSET_COLOR = 32;
public static final int OFFSET_LIGHTMAP = 36;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_CENTER_HALF, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CENTER_HALF);
GLStateManager.glVertexAttribPointer(LOC_UV, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_UV);
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_CENTER_HALF);
InstancedAttribs.enableInstanced(LOC_UV);
InstancedAttribs.enableInstanced(LOC_COLOR);
InstancedAttribs.enableInstanced(LOC_LIGHTMAP);
}
public static void writeInstance(long ptr, float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR, int packedLight) {
memPutFloat(ptr + OFFSET_CENTER_HALF, cx);
memPutFloat(ptr + OFFSET_CENTER_HALF + 4, cy);
memPutFloat(ptr + OFFSET_CENTER_HALF + 8, cz);
memPutFloat(ptr + OFFSET_CENTER_HALF + 12, half);
memPutFloat(ptr + OFFSET_UV, u0);
memPutFloat(ptr + OFFSET_UV + 4, v0);
memPutFloat(ptr + OFFSET_UV + 8, u1);
memPutFloat(ptr + OFFSET_UV + 12, v1);
memPutInt(ptr + OFFSET_COLOR, colorABGR);
InstancedAttribs.writeLightmap(ptr + OFFSET_LIGHTMAP, packedLight);
}
private ParticleInstancedAttribs() {}
}
@@ -0,0 +1,160 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final int VERTEX_STRIDE = 24;
private static final int OFFSET_OFFSET = 0;
private static final int OFFSET_CORNER = 12;
private static final int OFFSET_TANGENT = 20;
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
private static int vao;
private static int vbo;
private static ByteBuffer staging;
private static boolean written;
private static float lastRotX, lastRotXZ, lastRotZ, lastRotYZ, lastRotXY;
private ParticleQuadMesh() {}
public static float cornerA(int corner) {
return CORNER_A[corner];
}
public static float cornerB(int corner) {
return CORNER_B[corner];
}
public static float offsetX(float a, float b, float rotX, float rotYZ) {
return a * rotX + b * rotYZ;
}
public static float offsetY(float b, float rotXZ) {
return b * rotXZ;
}
public static float offsetZ(float a, float b, float rotZ, float rotXY) {
return a * rotZ + b * rotXY;
}
public static int vao() {
if (vao == 0) build();
return vao;
}
public static void update(float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
if (vao == 0) build();
if (written && rotX == lastRotX && rotXZ == lastRotXZ && rotZ == lastRotZ && rotYZ == lastRotYZ && rotXY == lastRotXY) {
return;
}
lastRotX = rotX;
lastRotXZ = rotXZ;
lastRotZ = rotZ;
lastRotYZ = rotYZ;
lastRotXY = rotXY;
written = true;
write(memAddress0(staging), rotX, rotXZ, rotZ, rotYZ, rotXY);
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
staging.position(0).limit(VERTEX_COUNT * VERTEX_STRIDE);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, staging);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void write(long dst, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
final int tangent = packTangent(rotX, 0.0f, rotZ);
long ptr = dst;
for (int i = 0; i < VERTEX_COUNT; i++) {
final float a = cornerA(i);
final float b = cornerB(i);
memPutFloat(ptr + OFFSET_OFFSET, offsetX(a, b, rotX, rotYZ));
memPutFloat(ptr + OFFSET_OFFSET + 4, offsetY(b, rotXZ));
memPutFloat(ptr + OFFSET_OFFSET + 8, offsetZ(a, b, rotZ, rotXY));
memPutFloat(ptr + OFFSET_CORNER, (a + 1.0f) * 0.5f);
memPutFloat(ptr + OFFSET_CORNER + 4, (b + 1.0f) * 0.5f);
memPutInt(ptr + OFFSET_TANGENT, tangent);
ptr += VERTEX_STRIDE;
}
}
private static int packTangent(float x, float y, float z) {
final float lenSq = x * x + y * y + z * z;
final float nx, ny, nz;
if (lenSq < 1.0e-12f) {
nx = 1.0f;
ny = 0.0f;
nz = 0.0f;
} else {
final float inv = (float) (1.0 / Math.sqrt(lenSq));
nx = x * inv;
ny = y * inv;
nz = z * inv;
}
return NormI8.pack(nx, ny, nz) | (127 << 24);
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) VERTEX_COUNT * VERTEX_STRIDE, GL15.GL_DYNAMIC_DRAW);
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_OFFSET);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 2, GL11.GL_FLOAT, false, OFFSET_CORNER);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
ParticleInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
if (staging == null) staging = memAlloc(VERTEX_COUNT * VERTEX_STRIDE);
written = false;
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
if (staging != null) {
memFree(staging);
staging = null;
}
written = false;
}
}
@@ -97,6 +97,8 @@ public final class ShaderManager {
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
final VertexKey vk = VertexKey.fromPacked(vkPacked);
VertexShaderGenerator.generate(vk);
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
@@ -127,6 +129,7 @@ public final class ShaderManager {
}
public void preDraw() {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
@@ -0,0 +1,161 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class UnitCubeMesh {
public static final int VERTEX_FLAGS = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT;
public static final int FACE_COUNT = 6;
public static final int VERTEX_COUNT = 24;
public static final int VERTEX_STRIDE = 64;
public static final int OFFSET_POSITION = 0;
private static final int OFFSET_COLOR = 12;
public static final int OFFSET_MID = 16;
public static final int OFFSET_DELTA = 32;
public static final int OFFSET_NORMAL = 48;
public static final int OFFSET_TANGENT = 60;
public static final float[][] CORNERS = {
{ 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 },
{ 0, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 },
};
public record Span(int a, int z) {
public int texels(int sizeA, int sizeZ) {
return a * sizeA + z * sizeZ;
}
}
public record Net(Span uLow, Span uHigh, Span vLow, Span vHigh) {
public Span u(int vertex) {
return vertex == 0 || vertex == 3 ? uHigh : uLow;
}
public Span v(int vertex) {
return vertex >= 2 ? vHigh : vLow;
}
}
public record Face(int[] corners, float[] normal, float[] tangent, Net net) {}
public static final Face[] FACES = {
new Face(new int[] { 5, 1, 2, 6 }, new float[] { 1, 0, 0 }, new float[] { 0, 0, 1, 1 }, new Net(new Span(1, 1), new Span(1, 2), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 0, 4, 7, 3 }, new float[] { -1, 0, 0 }, new float[] { 0, 0, -1, 1 }, new Net(new Span(0, 0), new Span(0, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 5, 4, 0, 1 }, new float[] { 0, -1, 0 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 0), new Span(0, 1))),
new Face(new int[] { 2, 3, 7, 6 }, new float[] { 0, 1, 0 }, new float[] { 1, 0, 0, -1 }, new Net(new Span(1, 1), new Span(2, 1), new Span(0, 1), new Span(0, 0))),
new Face(new int[] { 1, 0, 3, 2 }, new float[] { 0, 0, -1 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 4, 5, 6, 7 }, new float[] { 0, 0, 1 }, new float[] { -1, 0, 0, 1 }, new Net(new Span(1, 2), new Span(2, 2), new Span(0, 1), new Span(1, 1))),
};
private static int vao;
private static int vbo;
private UnitCubeMesh() {}
public static void write(long dst) {
long ptr = dst;
for (int f = 0; f < FACE_COUNT; f++) {
final Face face = FACES[f];
final Net net = face.net();
final float midUA = (net.uLow().a() + net.uHigh().a()) * 0.5f;
final float midUZ = (net.uLow().z() + net.uHigh().z()) * 0.5f;
final float midVA = (net.vLow().a() + net.vHigh().a()) * 0.5f;
final float midVZ = (net.vLow().z() + net.vHigh().z()) * 0.5f;
for (int c = 0; c < 4; c++) {
final float[] corner = CORNERS[face.corners()[c]];
final Span u = net.u(c);
final Span v = net.v(c);
memPutFloat(ptr + OFFSET_POSITION, corner[0]);
memPutFloat(ptr + OFFSET_POSITION + 4, corner[1]);
memPutFloat(ptr + OFFSET_POSITION + 8, corner[2]);
memPutInt(ptr + OFFSET_COLOR, 0xFFFFFFFF);
memPutFloat(ptr + OFFSET_MID, midUA);
memPutFloat(ptr + OFFSET_MID + 4, midUZ);
memPutFloat(ptr + OFFSET_MID + 8, midVA);
memPutFloat(ptr + OFFSET_MID + 12, midVZ);
memPutFloat(ptr + OFFSET_DELTA, u.a() - midUA);
memPutFloat(ptr + OFFSET_DELTA + 4, u.z() - midUZ);
memPutFloat(ptr + OFFSET_DELTA + 8, v.a() - midVA);
memPutFloat(ptr + OFFSET_DELTA + 12, v.z() - midVZ);
memPutFloat(ptr + OFFSET_NORMAL, face.normal()[0]);
memPutFloat(ptr + OFFSET_NORMAL + 4, face.normal()[1]);
memPutFloat(ptr + OFFSET_NORMAL + 8, face.normal()[2]);
final float[] tangent = face.tangent();
memPutInt(ptr + OFFSET_TANGENT, packTangent(tangent[0], tangent[1], tangent[2], tangent[3]));
ptr += VERTEX_STRIDE;
}
}
}
private static int packTangent(float x, float y, float z, float w) {
return NormI8.pack(x, y, z) | (((int) (w * 127.0f) & 0xFF) << 24);
}
public static int vao() {
if (vao == 0) build();
return vao;
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final int bytes = VERTEX_COUNT * VERTEX_STRIDE;
final ByteBuffer data = memAlloc(bytes);
try {
write(memAddress0(data));
data.position(0).limit(bytes);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
} finally {
memFree(data);
}
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_POSITION);
attrib(VertexFormatElement.Usage.COLOR.getAttributeLocation(), 4, GL11.GL_UNSIGNED_BYTE, true, OFFSET_COLOR);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_MID);
attrib(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_DELTA);
attrib(VertexFormatElement.Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_NORMAL);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
CubeInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -49,7 +49,7 @@ public final class VertexKey {
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCED = BIT_LINE_STIPPLE + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -97,7 +97,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -226,13 +226,15 @@ public final class VertexKey {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
static long withInstancing(long packed, Instancing kind) {
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
@@ -255,12 +257,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
packed, lightingEnabled(), lightEnabled(0), lightEnabled(1),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
}
}
@@ -55,31 +55,43 @@ public final class VertexShaderGenerator {
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
final Instancing kind = key.instancing();
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (key.hasVertexTexCoord()) {
if (kind == Instancing.PARTICLE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
} else {
if (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (kind != Instancing.CUBE) {
if (key.hasVertexTexCoord()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
}
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
if (key.instancedDraw()) {
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
sb.append(d).append('\n');
}
sb.append('\n');
}
private static final String PARTICLE_UV = InstancedGlslHelpers.particleUv("a_InstUv", "a_TexCoord0");
private static String unit0Source(VertexKey key) {
return switch (key.instancing()) {
case CUBE -> "cubeUv";
case PARTICLE -> PARTICLE_UV;
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
};
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
@@ -93,6 +105,10 @@ public final class VertexShaderGenerator {
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.instancing().hasInstanceHead()) {
sb.append("out vec4 v_Overlay;\n");
sb.append("invariant gl_Position;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
@@ -116,11 +132,16 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
if (key.instancedDraw()) {
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\n");
if (key.instancing() == Instancing.PARTICLE) {
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
} else {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
}
if (key.instancing().hasInstanceHead()) {
sb.append(" mat4 instMV = ").append(InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2")).append(";\n");
sb.append(" vec4 eyePos = instMV * pos4;\n");
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
sb.append(" v_Overlay = a_InstOverlay;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
@@ -134,8 +155,8 @@ public final class VertexShaderGenerator {
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
if (key.instancedDraw()) {
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
if (key.instancing().hasInstanceHead()) {
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append('\n');
return;
@@ -156,9 +177,13 @@ public final class VertexShaderGenerator {
if (key.colorMaterialEnabled()) {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
if (key.instancing() == Instancing.PARTICLE) {
sb.append("a_InstColor");
} else {
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancing().hasInstanceHead()) {
sb.append(" * a_InstColor");
}
}
sb.append(";\n");
@@ -255,22 +280,27 @@ public final class VertexShaderGenerator {
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else {
sb.append(" v_Color = ").append(src).append(";\n");
switch (key.instancing()) {
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
}
sb.append('\n');
}
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
final boolean cube = key.instancing() == Instancing.CUBE;
final boolean unit0FromCube = cube && key.unitTexCoordEnabled(0) && !key.texGenEnabled();
final boolean unit23FromCube = cube && key.unit23UvFromUnit0() && (key.unitTexCoordEnabled(2) || key.unitTexCoordEnabled(3));
if (unit0FromCube || unit23FromCube) {
emitCubeTexCoord(sb);
}
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
final String src = unit0Source(key);
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
@@ -279,20 +309,17 @@ public final class VertexShaderGenerator {
}
if (key.lightmapEnabled()) {
if (key.instancedDraw()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
} else if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
final String lightmap = switch (key.instancing()) {
case PARTICLE, TEMPLATE -> "a_InstLightmap";
case CUBE -> "a_InstLightmapScale.xy";
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
};
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
final String src = key.unit23UvFromUnit0() ? unit0Source(key) : "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
@@ -301,6 +328,12 @@ public final class VertexShaderGenerator {
}
}
private static void emitCubeTexCoord(StringBuilder sb) {
sb.append(" // Cube texture net\n");
sb.append(" ").append(InstancedGlslHelpers.cubePrelude("", "a_CubeMid", "a_CubeDelta", "a_Normal", "a_CubeTex", "\n ")).append('\n');
sb.append(" vec4 cubeUv = ").append(InstancedGlslHelpers.cubeUv("a_CubeTex", "a_InstLightmapScale", "cubeU.x", "cubeU.y", "cubeV.x", "cubeV.y")).append(";\n\n");
}
/**
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
import net.minecraftforge.eventbus.api.bus.EventBus;
@@ -14,6 +15,12 @@ public final class GLSMHooks {
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
public static PendingProgramSelection pendingProgramSelection;
public static void resolvePendingProgram() {
final PendingProgramSelection p = pendingProgramSelection;
if (p != null && !DisplayListManager.isRecording()) p.resolvePendingProgram();
}
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface PendingProgramSelection {
void resolvePendingProgram();
}
@@ -164,6 +164,8 @@ public class GLSMRedirector {
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
private static final String ARBSamplerObjects = "org/lwjgl/opengl/ARBSamplerObjects";
private static final String ARBMultiBind = "org/lwjgl/opengl/ARBMultiBind";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -555,6 +557,7 @@ public class GLSMRedirector {
.add("glMultiDrawElementsIndirect");
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glBindSamplers")
.add("glClearTexImage");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
@@ -767,6 +770,17 @@ public class GLSMRedirector {
.add("glMapBufferRange")
.add("glFlushMappedBufferRange")
);
methodRedirects.put(ARBSamplerObjects, RedirectMap.newMap()
.add("glGenSamplers")
.add("glDeleteSamplers")
.add("glIsSampler")
.add("glBindSampler")
.add("glSamplerParameteri")
.add("glSamplerParameterf")
);
methodRedirects.put(ARBMultiBind, RedirectMap.newMap()
.add("glBindSamplers")
);
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
@@ -796,6 +810,8 @@ public class GLSMRedirector {
.add("gluBuild2DMipmaps")
.add("gluErrorString")
);
// Must honor disableErrorChecks like glGetError, or it throws on errors those checks left latched
methodRedirects.put("org/lwjgl/opengl/Util", RedirectMap.newMap().add("checkGLError"));
// Quadric type replacements
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
@@ -12,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
protected int pointer;
private boolean funcUnknown;
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
public BlendStateStack() {
@@ -44,6 +46,18 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
return this;
}
public void setFuncUnknownState() {
funcUnknown = true;
}
public boolean isFuncUnknown() {
return funcUnknown;
}
public void clearFuncUnknownState() {
funcUnknown = false;
}
public BlendState readEffective(BlendState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
public class SamplerUnitArray {
private static final int MIN_UNITS = 16;
private int[] samplers;
public int get(int unit) {
final int[] local = samplers;
return local != null && unit >= 0 && unit < local.length ? local[unit] : 0;
}
public boolean set(int unit, int sampler) {
if (unit < 0) return false;
int[] local = samplers;
if (local == null) {
local = new int[Math.max(MIN_UNITS, RenderSystem.getMaxCombinedTextureImageUnits())];
samplers = local;
}
if (unit >= local.length) return false;
local[unit] = sampler;
return true;
}
public void unbindEverywhere(int sampler) {
final int[] local = samplers;
if (local == null) return;
for (int i = 0; i < local.length; i++) {
if (local[i] == sampler) local[i] = 0;
}
}
public int size() {
final int[] local = samplers;
return local == null ? 0 : local.length;
}
}
@@ -3,7 +3,8 @@ package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
@@ -11,7 +12,6 @@ import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Persistent-mapped fence-reclaimed ring over any buffer target. Callers reserve a range and
@@ -28,7 +28,8 @@ final class GlStreamingRing {
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
private final ObjectArrayFIFOQueue<FencedRegion> fenceQueue = new ObjectArrayFIFOQueue<>();
private final LongArrayFIFOQueue fenceIds = new LongArrayFIFOQueue();
private final IntArrayFIFOQueue fenceBytes = new IntArrayFIFOQueue();
private int writePos;
private int remaining;
private int pendingBytes;
@@ -51,13 +52,13 @@ final class GlStreamingRing {
static GlStreamingRing create(int target, int capacity, boolean forceOrphan) {
if (forceOrphan || !RenderSystem.supportsBufferStorage()) return null;
final int id = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(target, id);
final int id = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(target, id);
final int storageFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
RenderSystem.bufferStorage(target, capacity, storageFlags);
final int mapFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(target, 0, capacity, mapFlags);
RENDER_BACKEND.bindBuffer(target, 0);
final ByteBuffer mapped = GLStateManager.glMapBufferRange(target, 0, capacity, mapFlags);
GLStateManager.glBindBuffer(target, 0);
if (mapped == null) {
GLStateManager.glDeleteBuffers(id);
GLStateManager.LOGGER.warn("Persistent map failed for streaming ring (target=0x{}, {} bytes)", Integer.toHexString(target), capacity);
@@ -92,7 +93,7 @@ final class GlStreamingRing {
private boolean ensureRemaining(int needed) {
while (remaining < needed) {
if (fenceQueue.isEmpty()) {
if (fenceIds.isEmpty()) {
if (pendingBytes == 0) return false;
fence();
}
@@ -103,7 +104,8 @@ final class GlStreamingRing {
private void fence() {
if (pendingBytes > 0) {
fenceQueue.enqueue(new FencedRegion(RENDER_BACKEND.fenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes));
fenceIds.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
fenceBytes.enqueue(pendingBytes);
pendingBytes = 0;
fencesIssued++;
}
@@ -114,30 +116,32 @@ final class GlStreamingRing {
}
private void reclaim() {
while (!fenceQueue.isEmpty()) {
final FencedRegion region = fenceQueue.first();
if (!signaled(RENDER_BACKEND.clientWaitSync(region.fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(region.fenceId);
fenceQueue.dequeue();
remaining += region.bytes;
while (!fenceIds.isEmpty()) {
final long fenceId = fenceIds.firstLong();
if (!signaled(GLStateManager.glClientWaitSync(fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(fenceId);
fenceIds.dequeueLong();
remaining += fenceBytes.dequeueInt();
}
}
private void syncOldest() {
final FencedRegion region = fenceQueue.dequeue();
final long fenceId = fenceIds.dequeueLong();
final int bytes = fenceBytes.dequeueInt();
forcedReclaims++;
final long deadline = System.nanoTime() + SYNC_WAIT_TOTAL_NANOS;
long remainingNanos = SYNC_WAIT_TOTAL_NANOS;
int status;
do {
status = RENDER_BACKEND.clientWaitSync(region.fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, Math.min(SYNC_WAIT_SLICE_NANOS, remainingNanos));
status = GLStateManager.glClientWaitSync(fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT,
Math.min(SYNC_WAIT_SLICE_NANOS, remainingNanos));
remainingNanos = deadline - System.nanoTime();
} while (status == GL32.GL_TIMEOUT_EXPIRED && remainingNanos > 0);
if (!signaled(status)) {
GLStateManager.LOGGER.warn("Streaming ring fence wait did not signal (status=0x{}); reclaiming anyway", Integer.toHexString(status));
}
GLStateManager.glDeleteSync(region.fenceId);
remaining += region.bytes;
GLStateManager.glDeleteSync(fenceId);
remaining += bytes;
reclaim();
}
@@ -156,17 +160,16 @@ final class GlStreamingRing {
int forcedReclaims() { return forcedReclaims; }
void destroy() {
while (!fenceQueue.isEmpty()) {
GLStateManager.glDeleteSync(fenceQueue.dequeue().fenceId);
while (!fenceIds.isEmpty()) {
GLStateManager.glDeleteSync(fenceIds.dequeueLong());
}
fenceBytes.clear();
if (bufferId != 0) {
RENDER_BACKEND.bindBuffer(target, bufferId);
RENDER_BACKEND.unmapBuffer(target);
RENDER_BACKEND.bindBuffer(target, 0);
GLStateManager.glBindBuffer(target, bufferId);
GLStateManager.glUnmapBuffer(target);
GLStateManager.glBindBuffer(target, 0);
GLStateManager.glDeleteBuffers(bufferId);
bufferId = 0;
}
}
private record FencedRegion(long fenceId, int bytes) {}
}
@@ -5,8 +5,6 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Streaming buffer using the classic orphan pattern.
*/
@@ -16,19 +14,19 @@ public final class OrphanStreamingBuffer {
private int capacity;
public OrphanStreamingBuffer() {
this.bufferId = RENDER_BACKEND.genBuffers();
this.bufferId = GLStateManager.glGenBuffers();
}
public void upload(ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
void upload(StreamingUploader.UploadStrategy strategy, ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(strategy, data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public int getCapacity() {
@@ -7,7 +7,6 @@ import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
@@ -28,30 +27,30 @@ public final class StreamingUploader {
public static int upload(UploadStrategy strategy, ByteBuffer data, int capacity) {
switch (strategy) {
case BUFFER_DATA -> {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
case BUFFER_SUB_DATA -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
return capacity;
}
case MAP_BUFFER_RANGE -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
final int dataSize = data.remaining();
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
final long dst = GLStateManager.glMapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
if (dst == 0L) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
memCopy(memAddress0(data), dst, dataSize);
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
GLStateManager.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity;
}
default -> throw new UnsupportedOperationException();
@@ -10,7 +10,6 @@ import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class UniformRingBuffer {
@@ -35,10 +34,10 @@ public final class UniformRingBuffer {
this.stride = (blockSize + OFFSET_ALIGNMENT - 1) & -OFFSET_ALIGNMENT;
this.ring = GlStreamingRing.create(GL31.GL_UNIFORM_BUFFER, capacity, forceOrphan);
if (ring == null) {
orphanBufferId = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
orphanBufferId = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
}
}
@@ -56,20 +55,20 @@ public final class UniformRingBuffer {
}
private int writeOrphan(long srcAddress) {
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
int offset = writePos;
if (offset + stride > capacity) {
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
offset = 0;
orphanWraps++;
}
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL31.GL_UNIFORM_BUFFER, offset, blockSize, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
final long dst = GLStateManager.glMapBufferRangeAddress(GL31.GL_UNIFORM_BUFFER, offset, blockSize, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
if (dst == 0L) {
throw new IllegalStateException("mapBufferRange failed on uniform ring (offset=" + offset + ")");
}
memCopy(srcAddress, dst, blockSize);
RENDER_BACKEND.unmapBuffer(GL31.GL_UNIFORM_BUFFER);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
GLStateManager.glUnmapBuffer(GL31.GL_UNIFORM_BUFFER);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
writePos = offset + stride;
return offset;
}
@@ -0,0 +1,121 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class GLSM_BlendFuncUnknownState_GLTest {
private static final int VANILLA_SRC_RGB = GL11.GL_SRC_ALPHA;
private static final int VANILLA_DST_RGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
private static final int VANILLA_SRC_ALPHA = GL11.GL_ONE;
private static final int VANILLA_DST_ALPHA = GL11.GL_ZERO;
@BeforeEach
void requireBufferBlending() {
assumeTrue(RenderSystem.supportsBufferBlending());
drainGlErrors();
}
@AfterEach
void restoreDefaultBlendFunc() {
if (!RenderSystem.supportsBufferBlending()) return;
GLStateManager.tryBlendFuncSeparate(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void seedVanillaSeparate() {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
private static void overrideBufferZero() {
RenderSystem.blendFuncSeparatei(0, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
assertBufferBlend(0, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
}
private static void assertBufferBlend(int buffer, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GL30.glGetInteger(GL14.GL_BLEND_SRC_RGB, buffer), "buffer " + buffer + " src rgb");
assertEquals(dstRgb, GL30.glGetInteger(GL14.GL_BLEND_DST_RGB, buffer), "buffer " + buffer + " dst rgb");
assertEquals(srcAlpha, GL30.glGetInteger(GL14.GL_BLEND_SRC_ALPHA, buffer), "buffer " + buffer + " src alpha");
assertEquals(dstAlpha, GL30.glGetInteger(GL14.GL_BLEND_DST_ALPHA, buffer), "buffer " + buffer + " dst alpha");
}
private static void assertVanillaOnBothBuffers() {
for (int buffer = 0; buffer < 2; buffer++) {
assertBufferBlend(buffer, VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void tryBlendFuncSeparateForwardsAfterIndexedOverride() {
seedVanillaSeparate();
overrideBufferZero();
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
assertVanillaOnBothBuffers();
}
@Test
void glBlendFuncForwardsAfterIndexedOverride() {
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
overrideBufferZero();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
for (int buffer = 0; buffer < 2; buffer++) {
assertBufferBlend(buffer, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
}
@Test
void popAttribForwardsAfterIndexedOverride() {
seedVanillaSeparate();
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
try {
overrideBufferZero();
} finally {
GLStateManager.glPopAttrib();
}
assertVanillaOnBothBuffers();
}
@Test
void displayListReplayForwardsAfterIndexedOverride() {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
GLStateManager.glEndList();
seedVanillaSeparate();
overrideBufferZero();
GLStateManager.glCallList(list);
assertVanillaOnBothBuffers();
} finally {
GLStateManager.glDeleteLists(list, 1);
}
}
}
@@ -0,0 +1,235 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class GLSM_SamplerBinding_GLTest {
private static final int UNITS = 4;
@BeforeEach
void requireSamplerObjects() {
assumeTrue(RenderSystem.supportsSamplerObjects());
drainGlErrors();
}
@AfterEach
void unbindTestUnits() {
if (!RenderSystem.supportsSamplerObjects()) return;
for (int unit = 0; unit < UNITS; unit++) {
GLStateManager.glBindSampler(unit, 0);
GL33.glBindSampler(unit, 0);
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static int driverBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL33.GL_SAMPLER_BINDING);
GL13.glActiveTexture(saved);
return binding;
}
private static void assertUnits(String context, int... expected) {
for (int unit = 0; unit < expected.length; unit++) {
assertEquals(expected[unit], GLStateManager.getSamplerBinding(unit), context + ": cache unit " + unit);
assertEquals(expected[unit], driverBinding(unit), context + ": driver unit " + unit);
}
}
private static IntBuffer ints(int... values) {
final IntBuffer buf = ByteBuffer.allocateDirect(values.length * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
buf.put(values).flip();
return buf;
}
@Test
void renderSystemRebindAfterGlsmUnbindReachesDriver() {
final int sampler = RenderSystem.genSampler();
try {
RenderSystem.bindSamplerToUnit(0, sampler);
GLStateManager.glBindSampler(0, 0);
assertEquals(0, driverBinding(0), "setup: GLSM unbind must reach the driver");
RenderSystem.bindSamplerToUnit(0, sampler);
assertEquals(sampler, driverBinding(0), "RenderSystem skipped a bind the driver no longer holds");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void deleteClearsEveryUnitAndTheNextBindReachesDriver() {
final int sampler = RenderSystem.genSampler();
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glBindSampler(3, sampler);
GLStateManager.glDeleteSamplers(sampler);
assertUnits("after delete", 0, 0, 0, 0);
final int next = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, next);
assertEquals(next, driverBinding(1), "bind after delete never reached the driver");
} finally {
RenderSystem.destroySampler(next);
}
}
@Test
void samplerBindingQueryAnswersForTheActiveUnit() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(GL11.glGetInteger(GL33.GL_SAMPLER_BINDING),
GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
assertEquals(sampler, GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(GL11.glGetInteger(GL33.GL_SAMPLER_BINDING),
GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void unbindAllSamplersClearsUnitsBoundThroughGlsm() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glBindSampler(3, sampler);
RenderSystem.unbindAllSamplers();
assertEquals(0, driverBinding(1), "unit 1 still bound on the driver");
assertEquals(0, driverBinding(3), "unit 3 still bound on the driver");
assertEquals(0, GLStateManager.getSamplerBinding(1));
assertEquals(0, GLStateManager.getSamplerBinding(3));
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void replayForwardsCachedSamplerBindings() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(2, sampler);
GL33.glBindSampler(2, 0);
assertEquals(0, driverBinding(2), "setup failed to diverge the driver");
GLStateManager.replayStateToBackend();
assertEquals(sampler, driverBinding(2), "replay dropped a cached sampler binding");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void intBufferGenFillsFromPositionWithBindableSamplers() {
final IntBuffer buf = ints(-1, 0, 0);
buf.position(1);
GLStateManager.glGenSamplers(buf);
final int a = buf.get(1);
final int b = buf.get(2);
try {
assertEquals(-1, buf.get(0), "slot before position was overwritten");
assertEquals(1, buf.position(), "gen must not move the buffer position");
assertNotEquals(0, a);
assertNotEquals(0, b);
assertNotEquals(a, b);
GLStateManager.glBindSampler(0, a);
GLStateManager.glBindSampler(1, b);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "generated name rejected by the driver");
assertEquals(a, driverBinding(0));
assertEquals(b, driverBinding(1));
} finally {
GLStateManager.glDeleteSamplers(a);
GLStateManager.glDeleteSamplers(b);
}
}
@Test
void bindSamplersFormsBindFromPosition() {
final int a = RenderSystem.genSampler();
final int b = RenderSystem.genSampler();
try {
final IntBuffer buf = ints(-1, a, b);
buf.position(1);
GLStateManager.glBindSamplers(1, 2, buf);
assertEquals(1, buf.position(), "count form must not move the buffer position");
assertUnits("count form", 0, a, b, 0);
GLStateManager.glBindSampler(1, 0);
GLStateManager.glBindSampler(2, 0);
buf.position(1);
GLStateManager.glBindSamplers(1, buf);
assertEquals(1, buf.position(), "remaining form must not move the buffer position");
assertUnits("remaining form", 0, a, b, 0);
GLStateManager.glBindSamplers(1, (IntBuffer) null);
assertUnits("null remaining form", 0, a, b, 0);
GLStateManager.glBindSamplers(2, new int[]{a, b});
assertUnits("int[] form", 0, a, a, b);
} finally {
RenderSystem.destroySampler(a);
RenderSystem.destroySampler(b);
}
}
@Test
void bindSamplersNullUnbindsRangeAndShortBufferThrows() {
final int prior = RenderSystem.genSampler();
final int next = RenderSystem.genSampler();
try {
for (int unit = 0; unit < UNITS; unit++) GLStateManager.glBindSampler(unit, prior);
GLStateManager.glBindSamplers(1, 2, (IntBuffer) null);
assertUnits("null count form", prior, 0, 0, prior);
for (int unit = 0; unit < UNITS; unit++) GLStateManager.glBindSampler(unit, prior);
final IntBuffer buf = ints(next, next);
assertThrows(IllegalArgumentException.class, () -> GLStateManager.glBindSamplers(0, UNITS, buf));
assertEquals(0, buf.position());
assertUnits("short buffer", prior, prior, prior, prior);
} finally {
RenderSystem.destroySampler(prior);
RenderSystem.destroySampler(next);
}
}
}
@@ -1,8 +1,12 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
@@ -12,6 +16,7 @@ import org.lwjgl.util.spvc.SpvcReflectedResource;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -33,6 +38,47 @@ class FFPUniformBlockSpirvLayoutTest {
assertBlockLayout(FragmentShaderGenerator.generate(fk), Shaderc.shaderc_fragment_shader);
}
static Stream<Arguments> instancedKeys() {
return Stream.of(
Arguments.of("cube-lightmap-texmat", cubeKey(true, true, false)),
Arguments.of("cube-texgen", cubeKey(false, false, true)),
Arguments.of("particle-fog-texmat", particleFogTexMatKey())
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("instancedKeys")
void instancedVariantCompilesToSpirv(String label, VertexKey key) {
final String glsl = VertexShaderGenerator.generate(key);
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, Shaderc.shaderc_vertex_shader, label, SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
MemoryUtil.memFree(result.spirv());
}
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
if (unit0TextureMatrix || texGen) bits |= bit("BIT_UNIT_TEXMAT_BASE");
if (texGen) {
bits |= (long) VertexKey.TG_OBJ_LINEAR << position("BIT_TEXGEN_S");
bits |= (long) VertexKey.TG_OBJ_LINEAR << position("BIT_TEXGEN_T");
}
return VertexKey.fromPacked(VertexKey.withInstancing(bits, Instancing.CUBE));
}
private static VertexKey particleFogTexMatKey() {
final long bits = bit("BIT_UNIT_TEX_BASE") | 1L << (position("BIT_UNIT_TEX_BASE") + 1) | bit("BIT_UNIT_TEXMAT_BASE") | bit("BIT_FOG");
return VertexKey.fromPacked(VertexKey.withInstancing(bits, Instancing.PARTICLE));
}
private static long bit(String name) {
return 1L << position(name);
}
private static int position(String name) {
return Reflect.getStatic(VertexKey.class, name);
}
private static void assertBlockLayout(String glsl, int shaderKind) {
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "ffp-layout-test", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
@@ -2,14 +2,23 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PendingProgramSelection;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.NORMAL_BIT;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -22,41 +31,203 @@ class InstancedFFPDrawGLTest {
private static final int TEMPLATE_STRIDE = 32;
private static final int INSTANCE_STRIDE = InstancedAttribs.STRIDE;
private static final int PARITY_SIZE = 256;
private static final int INTEGER_WIDTH = 300;
private static final int INTEGER_HEIGHT = 100;
private static final List<String> log = new ArrayList<>();
private static final PendingProgramSelection LOGGING_SELECTION = () -> log.add("resolve");
private static final class LoggingBlendHandler implements DeferredBlendHandler {
@Override
public boolean isBlendLocked() {
return false;
}
@Override
public boolean isOverrideHeld() {
return false;
}
@Override
public void deferBlendModeToggle(boolean enabled) {}
@Override
public void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {}
@Override
public void flushDeferredBlend() {
log.add("flush");
}
}
private static boolean capturing;
private int vao, templateVbo, instanceVbo;
@BeforeAll
static void registerForeignDrawEndListener() {
capturing = true;
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
if (capturing) log.add("listener");
});
}
@AfterAll
static void stopCapturing() {
capturing = false;
}
@AfterEach
void cleanup() {
GLStateManager.instancedFfpDrawActive = false;
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
GLStateManager.glBindVertexArray(0);
GLSMHooks.pendingProgramSelection = null;
GLSMHooks.blendHandler = null;
log.clear();
FfpFixture.IntegerInstances.delete();
FfpFixture.resetFfpState();
CubeParityFixture.disableDirectionalLight();
CubeParityFixture.deleteResources();
ParticleParityFixture.deleteResources();
if (templateVbo != 0) { GLStateManager.glDeleteBuffers(templateVbo); templateVbo = 0; }
if (instanceVbo != 0) { GLStateManager.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
}
@Test
void vertexKeyCarriesInstancedBit() {
GLStateManager.instancedFfpDrawActive = false;
final long plain = VertexKey.packFromState(true, true, false, false, 0);
GLStateManager.instancedFfpDrawActive = true;
final long instanced = VertexKey.packFromState(true, true, false, false, 0);
GLStateManager.instancedFfpDrawActive = false;
void drawArraysResolvesBeforeFlushDeferredBlend() {
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
GLSMHooks.blendHandler = new LoggingBlendHandler();
assertFalse(VertexKey.fromPacked(plain).instancedDraw());
assertTrue(VertexKey.fromPacked(instanced).instancedDraw());
assertEquals(1, Long.bitCount(plain ^ instanced), "only the instanced bit may differ");
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 0);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
assertEquals(List.of("resolve", "flush"), log);
}
@Test
void resolveIsSuppressedWhileDisplayListIsRecording() {
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLSMHooks.resolvePendingProgram();
assertTrue(log.isEmpty());
GLStateManager.glEndList();
GLSMHooks.resolvePendingProgram();
assertEquals(List.of("resolve"), log);
}
@Test
void nestedForeignDrawsResolveOnceAtOutermostEndBeforeListeners() {
GLSMHooks.pendingProgramSelection = LOGGING_SELECTION;
GLStateManager.beginForeignDraw();
GLStateManager.beginForeignDraw();
GLStateManager.endForeignDraw();
assertTrue(log.isEmpty());
GLStateManager.endForeignDraw();
assertEquals(List.of("resolve", "listener"), log);
}
@Test
void unitCubeMatchesBakedQuadsWithLightmap() {
assertCubeParity(true, false);
}
@Test
void unitCubeMatchesBakedQuadsWhenLit() {
assertCubeParity(false, true);
}
private void assertCubeParity(boolean lightmap, boolean lit) {
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
sm.activate();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
FfpFixture.clear();
CubeParityFixture.drawReference(specs, lightmap);
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
FfpFixture.clear();
CubeParityFixture.drawInstanced(specs);
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
CubeParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
}
@Test
void particleUnit0TextureMatrixMatchesReference() {
final ParticleParityFixture.Rotation rot = ParticleParityFixture.Rotation.of(35f, -12f, false);
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
sm.activate();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
FfpFixture.clear();
ParticleParityFixture.drawReference(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
FfpFixture.clear();
ParticleParityFixture.drawInstanced(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
ParticleParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
}
@Test
void entityAttribSelectsColorPerInstance() {
GLStateManager.glViewport(0, 0, INTEGER_WIDTH, INTEGER_HEIGHT);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
FfpFixture.IntegerInstances.build(0.66f);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
FfpFixture.IntegerInstances.draw();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced ivec4 draw must not raise a GL error");
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
assertPixel(249, 50, 0, 0, 255, "entity 42 must render blue");
}
private static void assertPixel(int x, int y, int r, int g, int b, String label) {
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
final int gotR = pixel.get(0) & 0xFF;
final int gotG = pixel.get(1) & 0xFF;
final int gotB = pixel.get(2) & 0xFF;
assertEquals(r, gotR, () -> label + ": red channel, got (" + gotR + "," + gotG + "," + gotB + ")");
assertEquals(g, gotG, () -> label + ": green channel, got (" + gotR + "," + gotG + "," + gotB + ")");
assertEquals(b, gotB, () -> label + ": blue channel, got (" + gotR + "," + gotG + "," + gotB + ")");
}
@Test
void instancedSourceUsesInstanceAttribs() {
GLStateManager.instancedFfpDrawActive = true;
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
GLStateManager.instancedFfpDrawActive = false;
GLStateManager.ffpInstancing = Instancing.NONE;
final String source = VertexShaderGenerator.generate(key);
assertTrue(source.contains("a_InstCol0"), "instance matrix attribs declared");
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
assertTrue(source.contains("mat4 instMV"), "instance matrix used as modelview");
assertTrue(source.contains("a_InstColor"), "instance color multiplier used");
assertTrue(source.contains("a_InstLightmap"), "instance lightmap coord used");
@@ -74,7 +245,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.disableCull();
buildTemplate();
buildInstances();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
@@ -83,14 +254,12 @@ class InstancedFFPDrawGLTest {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.instancedFfpDrawActive = true;
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.instancedFfpDrawActive = false;
drawInstances();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced draw must not raise a GL error");
final int[] left = readPixel(200, 300);
final int[] right = readPixel(600, 300);
final int[] left = FfpFixture.readPixel(200, 300);
final int[] right = FfpFixture.readPixel(600, 300);
assertTrue(left[0] > 200 && left[1] < 50, "left instance red, got " + left[0] + "," + left[1] + "," + left[2]);
assertTrue(right[1] > 200 && right[0] < 50, "right instance green, got " + right[0] + "," + right[1] + "," + right[2]);
}
@@ -106,7 +275,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.disableCull();
buildTemplate();
buildInstancesWithAlpha();
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
@@ -117,14 +286,12 @@ class InstancedFFPDrawGLTest {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.instancedFfpDrawActive = true;
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.instancedFfpDrawActive = false;
drawInstances();
GLStateManager.disableAlphaTest();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
final int[] left = readPixel(200, 300);
final int[] right = readPixel(600, 300);
final int[] left = FfpFixture.readPixel(200, 300);
final int[] right = FfpFixture.readPixel(600, 300);
assertTrue(left[0] > 200, "opaque instance drawn, got " + left[0]);
assertTrue(right[0] < 20 && right[1] < 20, "alpha 0.05 instance discarded, got " + right[0] + "," + right[1]);
}
@@ -141,7 +308,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.disableCull();
buildTemplate();
buildInstances();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
@@ -150,48 +317,71 @@ class InstancedFFPDrawGLTest {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GLStateManager.instancedFfpDrawActive = true;
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
drawInstances();
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.glColor4f(1f, 1f, 1f, 1f);
rewriteInstanceColors(0x800000FF, 0x8000FF00);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.instancedFfpDrawActive = false;
uploadInstances(0x800000FF, 0, 0x8000FF00, 0);
drawInstances();
GLStateManager.disableBlend();
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.disableDepthTest();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
final int[] left = readPixel(200, 300);
final int[] background = readPixel(400, 550);
final int[] left = FfpFixture.readPixel(200, 300);
final int[] background = FfpFixture.readPixel(400, 550);
assertTrue(left[0] > 200, "overlay blended over base, got " + left[0]);
assertTrue(background[0] < 20 && background[1] < 20, "overlay must not draw off-base, got " + background[0]);
}
private void rewriteInstanceColors(int leftABGR, int rightABGR) {
final ByteBuffer patch = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
putInstance(patch, -0.5f, leftABGR);
putInstance(patch, 0.5f, rightABGR);
patch.flip();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, patch, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
@Test
void perInstanceOverlayMixesOnlyTintedInstances() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.setOverlayColor(0f, 1f, 0f, 1f);
drawInstances();
GLStateManager.setOverlayColor(0f, 0f, 0f, 0f);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced overlay draw must not raise a GL error");
final int[] left = FfpFixture.readPixel(200, 300);
final int[] right = FfpFixture.readPixel(600, 300);
assertTrue(left[0] > 200 && left[1] > 200 && left[2] > 200, "left instance overlay alpha 0 keeps its base color, got " + left[0] + "," + left[1] + "," + left[2]);
assertTrue(right[0] > 200 && right[1] < 50 && right[2] < 50, "right instance is mixed to its own opaque red overlay, got " + right[0] + "," + right[1] + "," + right[2]);
}
private void buildInstancesWithAlpha() {
private void uploadInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
putInstance(instances, -0.5f, 0xFF0000FF);
putInstance(instances, 0.5f, 0x0D0000FF);
putInstance(instances, -0.5f, leftColor, leftOverlay);
putInstance(instances, 0.5f, rightColor, rightOverlay);
instances.flip();
instanceVbo = GLStateManager.glGenBuffers();
final boolean first = instanceVbo == 0;
if (first) instanceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
pointAndEnableInstanceAttribs();
if (first) {
pointAndEnableInstanceAttribs();
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
@@ -225,47 +415,23 @@ class InstancedFFPDrawGLTest {
buf.position(base + TEMPLATE_STRIDE);
}
private void buildInstances() {
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
putInstance(instances, -0.5f, 0xFF0000FF);
putInstance(instances, 0.5f, 0xFF00FF00);
instances.flip();
instanceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
pointAndEnableInstanceAttribs();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
private static void pointAndEnableInstanceAttribs() {
for (int c = 0; c < 4; c++) {
final int loc = InstancedAttribs.LOC_MATRIX_COL0 + c;
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, 4, GL11.GL_FLOAT, false, INSTANCE_STRIDE, c * 16L);
GLStateManager.glVertexAttribDivisor(loc, 1);
}
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR);
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, INSTANCE_STRIDE, InstancedAttribs.OFFSET_COLOR);
GLStateManager.glVertexAttribDivisor(InstancedAttribs.LOC_COLOR, 1);
GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_LIGHTMAP);
GLStateManager.glVertexAttribPointer(InstancedAttribs.LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, INSTANCE_STRIDE, InstancedAttribs.OFFSET_LIGHTMAP);
GLStateManager.glVertexAttribDivisor(InstancedAttribs.LOC_LIGHTMAP, 1);
InstancedAttribs.enableHeadArrays();
InstancedAttribs.pointTemplate(0L);
}
private void putInstance(ByteBuffer buf, float translateX, int colorABGR) {
private static void putInstance(ByteBuffer buf, float translateX, int colorABGR, int overlayABGR) {
final int base = buf.position();
buf.putFloat(base, 1f).putFloat(base + 20, 1f).putFloat(base + 40, 1f).putFloat(base + 60, 1f);
buf.putFloat(base + 48, translateX);
buf.putInt(base + InstancedAttribs.OFFSET_COLOR, colorABGR);
buf.putFloat(base + InstancedAttribs.OFFSET_LIGHTMAP, 240f);
buf.putFloat(base + InstancedAttribs.OFFSET_LIGHTMAP + 4, 240f);
final long ptr = memAddress0(buf) + base;
InstancedAttribs.writeHead(ptr, FfpFixture.translationMatrix(translateX), 0, colorABGR, overlayABGR, 0L);
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP, 240f);
memPutFloat(ptr + InstancedAttribs.OFFSET_LIGHTMAP + 4, 240f);
buf.position(base + INSTANCE_STRIDE);
}
private static int[] readPixel(int x, int y) {
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GL11.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
return new int[] { pixel.get(0) & 0xFF, pixel.get(1) & 0xFF, pixel.get(2) & 0xFF };
private static void drawInstances() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.ffpInstancing = Instancing.NONE;
}
}
@@ -17,6 +17,8 @@ import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Set;
@@ -274,6 +276,11 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void utilCheckGLErrorIsRedirected() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
}
@Test
void indexedGettersHaveRealTargets() throws Exception {
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, getClass().getClassLoader());
@@ -300,6 +307,74 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty(), owner + "." + name + " should not be recorded");
}
private static boolean glsmDeclares(String name, String desc) throws ClassNotFoundException {
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, GLSMRedirectorTest.class.getClassLoader());
for (Method m : glsm.getDeclaredMethods()) {
final int mods = m.getModifiers();
if (!Modifier.isPublic(mods) || !Modifier.isStatic(mods) || !m.getName().equals(name)) continue;
if (Type.getMethodDescriptor(m).equals(desc)) return true;
}
return false;
}
private static void assertRedirectedToDeclaredTarget(String[][] cases) throws ClassNotFoundException {
for (boolean aware : new boolean[]{false, true}) {
for (String[] c : cases) {
final String call = c[0] + "." + c[1] + c[2] + (aware ? " aware" : "");
final ClassNode cn = makeClassWithMethodCall(c[0], c[1], c[2]);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, aware), call);
final MethodInsnNode target = firstCall(cn);
assertEquals(GLSM, target.owner, call);
assertEquals(c[1], target.name, call);
assertEquals(c[2], target.desc, call);
assertTrue(glsmDeclares(target.name, target.desc), () -> "GLStateManager lacks the target of " + call);
}
}
assertTrue(unmappedSeen().isEmpty(), () -> "unexpected reports: " + unmappedSeen());
}
@Test
void samplerGenDeleteOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL33", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "([I)V"},
});
}
@Test
void bindSamplersHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44C", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(I[I)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(I[I)V"},
});
}
@Test
void arbSamplerObjectsHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glIsSampler", "(I)Z"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glBindSampler", "(II)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameteri", "(III)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameterf", "(IIF)V"},
});
}
@Test
void descKeyedSyncRedirectsOnlyLongForm() {
assertRedirected("org/lwjgl/opengl/GL32C", "glFenceSync", "(II)J", "glFenceSync");
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader.UploadStrategy;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
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.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class StreamingBindingCacheGLTest {
private static ByteBuffer filled(int bytes) {
final ByteBuffer data = memAlloc(bytes);
for (int i = 0; i < bytes; i++) {
data.put(i, (byte) i);
}
data.position(0).limit(bytes);
return data;
}
private static int glsmUniformBufferBinding() {
final Object ctx = Reflect.invokeStatic(GLStateManager.class, "ctx", new Class<?>[0]);
final Int2IntMap bound = Reflect.get(ctx, "boundOtherBuffers");
return bound.get(GL31.GL_UNIFORM_BUFFER);
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphanUploadLeavesTheArrayBufferCacheMatchingTheDriver(UploadStrategy strategy) {
final int other = GLStateManager.glGenBuffers();
final OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
final ByteBuffer data = filled(64);
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
buf.upload(strategy, data);
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "first upload respecifies the buffer");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
buf.upload(strategy, data);
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "second upload takes the in-capacity path");
} finally {
memFree(data);
buf.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
@Test
void uniformRingOrphanPathLeavesTheUniformBufferCacheMatchingTheDriver() {
final int other = GLStateManager.glGenBuffers();
final ByteBuffer src = filled(64);
UniformRingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, other);
ring = new UniformRingBuffer(1024, 64, true);
assertFalse(ring.isPersistent());
assertEquals(GL11.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING), glsmUniformBufferBinding(), "constructor allocation");
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, other);
ring.writeBlock(src);
assertEquals(GL11.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING), glsmUniformBufferBinding(), "writeBlock");
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
memFree(src);
}
}
@Test
void persistentRingMarksItsOwnBufferAndLeavesTheCacheMatchingTheDriver() {
assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
final int other = GLStateManager.glGenBuffers();
PersistentStreamingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
ring = PersistentStreamingBuffer.createOrNull(4096, false);
assertNotNull(ring);
final IntSet persistent = Reflect.getStatic(FfpExtendedAttribs.class, "persistentSources");
assertTrue(persistent.contains(ring.getBufferId()), "the ring buffer is the persistent source");
assertFalse(persistent.contains(other), "the previously bound buffer is not persistent");
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO());
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
}
@@ -0,0 +1,308 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.function.IntFunction;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
public final class CubeParityFixture {
private static final int TEX_WIDTH = 64;
private static final int TEX_HEIGHT = 32;
private static final float SCALE = 0.0625f;
private static final short BRIGHTNESS = 240;
public record Spec(int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate, boolean mirror) {}
private static final float[] MODELVIEW = {
1.25f, 0.0f, 0.0f, 0.0f,
0.0f, 1.25f, 0.0f, 0.0f,
0.375f, 0.25f, -0.25f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
private static final float[] MODELVIEW_CONFORMAL = {
1.0f, 0.0f, 0.75f, 0.0f,
0.0f, 1.25f, 0.0f, 0.0f,
-0.75f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
private static float[] modelView = MODELVIEW;
private static int netTexture;
private static int lightmapTexture;
private CubeParityFixture() {}
public static Spec[] specs() {
return new Spec[] {
new Spec(0, 0, -4, -6, -2, 8, 12, 4, 0.0f, false),
new Spec(16, 8, -3, 1, -3, 6, 4, 6, 0.25f, true),
};
}
private static void setupCamera(boolean conformal) {
modelView = conformal ? MODELVIEW_CONFORMAL : MODELVIEW;
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
final FloatBuffer mv = BufferUtils.createFloatBuffer(16);
mv.put(modelView).flip();
GLStateManager.glLoadMatrix(mv);
}
public static void setupScene(boolean lightmap, boolean lit) {
GLStateManager.glDisable(GL11.GL_FOG);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glDepthMask(true);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
GLStateManager.glCullFace(GL11.GL_BACK);
setupCamera(lit);
if (lit) {
enableDirectionalLight();
} else {
GLStateManager.glDisable(GL11.GL_LIGHTING);
}
netTexture = createNetTexture();
if (lightmap) {
lightmapTexture = createNetTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glScalef(0.00390625f, 0.00390625f, 0.00390625f);
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, netTexture);
}
}
private static void enableDirectionalLight() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_NORMALIZE);
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, fill(buf, 0.2f, 0.2f, 0.2f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, fill(buf, 0.375f, 0.75f, 0.5f, 0.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, fill(buf, 1.0f, 1.0f, 1.0f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, fill(buf, 0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, fill(buf, 0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
}
public static void disableDirectionalLight() {
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_NORMALIZE);
GLStateManager.glDisable(GL11.GL_LIGHTING);
}
private static FloatBuffer fill(FloatBuffer buf, float x, float y, float z, float w) {
buf.clear();
buf.put(x).put(y).put(z).put(w).flip();
return buf;
}
private static int createNetTexture() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
final ByteBuffer texels = BufferUtils.createByteBuffer(TEX_WIDTH * TEX_HEIGHT * 4);
for (int y = 0; y < TEX_HEIGHT; y++) {
for (int x = 0; x < TEX_WIDTH; x++) {
texels.put((byte) (16 + x * 3));
texels.put((byte) (16 + y * 7));
texels.put((byte) (255 - x * 3));
texels.put((byte) 255);
}
}
texels.flip();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_WIDTH, TEX_HEIGHT, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
return id;
}
private static final int REF_STRIDE = 44;
private static int referenceVao;
private static int referenceVbo;
private static ByteBuffer buildQuadData(Spec[] specs) {
final ByteBuffer data = BufferUtils.createByteBuffer(specs.length * 24 * REF_STRIDE);
for (Spec spec : specs) {
final float[] corners = corners(spec);
for (int face = 0; face < UnitCubeMesh.FACE_COUNT; face++) {
final float[] uv = faceRect(spec, face);
final float[] normal = UnitCubeMesh.FACES[face].normal();
for (int corner = 0; corner < 4; corner++) {
final int source = spec.mirror() ? 3 - corner : corner;
final int index = UnitCubeMesh.FACES[face].corners()[source];
final int base = data.position();
data.putFloat(base, corners[index * 3] * SCALE);
data.putFloat(base + 4, corners[index * 3 + 1] * SCALE);
data.putFloat(base + 8, corners[index * 3 + 2] * SCALE);
data.putInt(base + 12, 0xFFFFFFFF);
data.putFloat(base + 16, uv[(source == 0 || source == 3) ? 2 : 0]);
data.putFloat(base + 20, uv[source >= 2 ? 3 : 1]);
data.putFloat(base + 24, BRIGHTNESS);
data.putFloat(base + 28, BRIGHTNESS);
data.putFloat(base + 32, spec.mirror() ? -normal[0] : normal[0]);
data.putFloat(base + 36, normal[1]);
data.putFloat(base + 40, normal[2]);
data.position(base + REF_STRIDE);
}
}
}
data.flip();
return data;
}
private static int uploadQuads(int vbo, Spec[] specs, boolean lightmap) {
final ByteBuffer data = buildQuadData(specs);
if (vbo == 0) vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
FfpFixture.attrib(1, 4, GL11.GL_UNSIGNED_BYTE, true, REF_STRIDE, 12);
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 16);
if (lightmap) {
FfpFixture.attrib(3, 2, GL11.GL_FLOAT, false, REF_STRIDE, 24);
} else {
GLStateManager.glDisableVertexAttribArray(3);
}
FfpFixture.attrib(4, 3, GL11.GL_FLOAT, false, REF_STRIDE, 32);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vbo;
}
public static void drawReference(Spec[] specs, boolean lightmap) {
if (referenceVao == 0) referenceVao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(referenceVao);
referenceVbo = uploadQuads(referenceVbo, specs, lightmap);
VAOManager.setCurrentVertexFlags(UnitCubeMesh.VERTEX_FLAGS | (lightmap ? VertexFlags.BRIGHTNESS_BIT : 0));
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, specs.length * 24);
GLStateManager.glBindVertexArray(0);
}
private static float[] corners(Spec spec) {
final float minX = spec.x() - spec.inflate();
final float minY = spec.y() - spec.inflate();
final float minZ = spec.z() - spec.inflate();
final float maxX = (spec.x() + spec.sizeX()) + spec.inflate();
final float maxY = (spec.y() + spec.sizeY()) + spec.inflate();
final float maxZ = (spec.z() + spec.sizeZ()) + spec.inflate();
final float lowX = spec.mirror() ? maxX : minX;
final float highX = spec.mirror() ? minX : maxX;
final float[] out = new float[UnitCubeMesh.CORNERS.length * 3];
for (int index = 0; index < UnitCubeMesh.CORNERS.length; index++) {
final float[] corner = UnitCubeMesh.CORNERS[index];
out[index * 3] = corner[0] > 0f ? highX : lowX;
out[index * 3 + 1] = corner[1] > 0f ? maxY : minY;
out[index * 3 + 2] = corner[2] > 0f ? maxZ : minZ;
}
return out;
}
private static float[] faceRect(Spec spec, int face) {
final int i = spec.texU();
final int j = spec.texV();
final int k = spec.sizeX();
final int l = spec.sizeY();
final int m = spec.sizeZ();
final int[] rect = switch (face) {
case 0 -> new int[] { i + m + k, j + m, i + m + k + m, j + m + l };
case 1 -> new int[] { i, j + m, i + m, j + m + l };
case 2 -> new int[] { i + m, j, i + m + k, j + m };
case 3 -> new int[] { i + m + k, j + m, i + m + k + k, j };
case 4 -> new int[] { i + m, j + m, i + m + k, j + m + l };
default -> new int[] { i + m + k + m, j + m, i + m + k + m + k, j + m + l };
};
return new float[] {
rect[0] / (float) TEX_WIDTH, rect[1] / (float) TEX_HEIGHT,
rect[2] / (float) TEX_WIDTH, rect[3] / (float) TEX_HEIGHT,
};
}
private static int instanceVbo;
public static void drawInstanced(Spec[] specs) {
final ByteBuffer records = BufferUtils.createByteBuffer(specs.length * CubeInstancedAttribs.STRIDE);
final long records0 = memAddress0(records);
for (int index = 0; index < specs.length; index++) {
final Spec spec = specs[index];
final CubeParams cube = CubeParams.of(TEX_WIDTH, TEX_HEIGHT, spec.mirror(), spec.texU(), spec.texV(), spec.x(), spec.y(), spec.z(), spec.sizeX(), spec.sizeY(), spec.sizeZ(), spec.inflate());
if (cube == null) {
throw new AssertionError("spec " + index + " is not cube instanceable");
}
final long ptr = records0 + (long) index * CubeInstancedAttribs.STRIDE;
cube.writeRows(ptr, modelView, 0, SCALE);
InstancedAttribs.writeTail(ptr, 0xFFFFFFFF, 0, 0L);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, BRIGHTNESS);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 4, BRIGHTNESS);
cube.writeTexture(ptr);
}
records.position(0).limit(specs.length * CubeInstancedAttribs.STRIDE);
GLStateManager.glBindVertexArray(UnitCubeMesh.vao());
VAOManager.setCurrentVertexFlags(UnitCubeMesh.VERTEX_FLAGS);
if (instanceVbo == 0) instanceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
CubeInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.CUBE;
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, specs.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.glBindVertexArray(0);
}
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
FfpFixture.assertPixelParity("instanced cubes", reference, instanced, size, background, 8, 1, describe);
}
public static void deleteResources() {
if (instanceVbo != 0) {
GLStateManager.glDeleteBuffers(instanceVbo);
instanceVbo = 0;
}
if (referenceVbo != 0) {
GLStateManager.glDeleteBuffers(referenceVbo);
referenceVbo = 0;
}
if (referenceVao != 0) {
GLStateManager.glDeleteVertexArrays(referenceVao);
referenceVao = 0;
}
if (netTexture != 0) {
GLStateManager.glDeleteTextures(netTexture);
netTexture = 0;
}
if (lightmapTexture != 0) {
GLStateManager.glDeleteTextures(lightmapTexture);
lightmapTexture = 0;
}
UnitCubeMesh.delete();
}
}
@@ -0,0 +1,272 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.util.function.IntFunction;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public final class FfpFixture {
private FfpFixture() {}
public static final class IntegerInstances {
private static final int INSTANCES = 3;
private static final String VS_HEAD = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 5) in vec4 a_InstRow0;
layout(location = 6) in vec4 a_InstRow1;
layout(location = 7) in vec4 a_InstRow2;
layout(location = 11) in ivec4 iris_Entity;
flat out vec3 v_Color;
void main() {
""";
private static final String VS_TAIL = """
gl_Position = instMV * vec4(a_Position, 1.0);
if (iris_Entity.x == -1) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (iris_Entity.x == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
else { v_Color = vec3(0.0, 0.0, 1.0); }
}
""";
private static final String VS = VS_HEAD + " mat4 instMV = " + InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2") + ";\n" + VS_TAIL;
private static final String FS = """
#version 330 core
flat in vec3 v_Color;
out vec4 fragColor;
void main() { fragColor = vec4(v_Color, 1.0); }
""";
private static int program;
private static int vao;
private static int templateVbo;
private static int instanceVbo;
private IntegerInstances() {}
public static void build(float spacing) {
program = buildProgram();
GLStateManager.glUseProgram(program);
buildTemplate();
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 42 });
}
public static void draw() {
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, INSTANCES);
}
public static void delete() {
GLStateManager.glUseProgram(0);
if (program != 0) { GLStateManager.glDeleteProgram(program); program = 0; }
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (templateVbo != 0) { GLStateManager.glDeleteBuffers(templateVbo); templateVbo = 0; }
if (instanceVbo != 0) { GLStateManager.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
GLStateManager.glBindVertexArray(0);
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
}
private static void buildTemplate() {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final ByteBuffer quad = BufferUtils.createByteBuffer(4 * 12);
putVertex(quad, -0.15f, -0.8f);
putVertex(quad, 0.15f, -0.8f);
putVertex(quad, 0.15f, 0.8f);
putVertex(quad, -0.15f, 0.8f);
quad.flip();
templateVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, templateVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, quad, GL15.GL_STATIC_DRAW);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
private static void putVertex(ByteBuffer buf, float x, float y) {
final int base = buf.position();
buf.putFloat(base, x).putFloat(base + 4, y).putFloat(base + 8, 0f);
buf.position(base + 12);
}
private static void buildInstances(float[] translateX, int[] entities) {
final ByteBuffer instances = BufferUtils.createByteBuffer(entities.length * InstancedAttribs.STRIDE);
final long base0 = memAddress0(instances);
for (int i = 0; i < entities.length; i++) {
final long ptr = base0 + (long) i * InstancedAttribs.STRIDE;
InstancedAttribs.writeHead(ptr, translationMatrix(translateX[i]), 0, 0xFFFFFFFF, 0, InstancedAttribs.packEntityInfo(entities[i], 0, 0));
}
instances.position(0).limit(entities.length * InstancedAttribs.STRIDE);
instanceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, instances, GL15.GL_STATIC_DRAW);
InstancedAttribs.enableHeadArrays();
InstancedAttribs.pointTemplate(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
private static int buildProgram() {
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
final int p = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(p, vs);
GLStateManager.glAttachShader(p, fs);
GLStateManager.glLinkProgram(p);
assertNotEquals(0, GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "test program must link: " + GLStateManager.glGetProgramInfoLog(p, 4096));
GLStateManager.glDeleteShader(vs);
GLStateManager.glDeleteShader(fs);
return p;
}
private static int compile(int type, String source) {
final int shader = GLStateManager.glCreateShader(type);
GLStateManager.glShaderSource(shader, source);
GLStateManager.glCompileShader(shader);
if (GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == 0) {
fail("test shader failed to compile:\n" + GLStateManager.glGetShaderInfoLog(shader, 4096) + "\n\n" + source);
}
return shader;
}
}
public static void attrib(int loc, int size, int type, boolean normalized, int stride, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, stride, offset);
}
public static void clear() {
GLStateManager.glClearColor(0f, 0f, 0f, 0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
public static int[] readRegion(int size) {
final ByteBuffer pixels = BufferUtils.createByteBuffer(size * size * 4);
GLStateManager.glReadPixels(0, 0, size, size, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
final int[] out = new int[size * size];
for (int i = 0; i < out.length; i++) {
out[i] = pixels.getInt(i * 4);
}
return out;
}
public static int[] readPixel(int x, int y) {
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GLStateManager.glReadPixels(x, y, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
return new int[] { pixel.get(0) & 0xFF, pixel.get(1) & 0xFF, pixel.get(2) & 0xFF, pixel.get(3) & 0xFF };
}
public static int solidTexture(int r, int g, int b, int a) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
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);
final ByteBuffer texels = BufferUtils.createByteBuffer(2 * 2 * 4);
for (int i = 0; i < 4; i++) {
texels.put((byte) r).put((byte) g).put((byte) b).put((byte) a);
}
texels.flip();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
return id;
}
public static float[] translationMatrix(float x) {
return new float[] {
1f, 0f, 0f, 0f,
0f, 1f, 0f, 0f,
0f, 0f, 1f, 0f,
x, 0f, 0f, 1f,
};
}
public static void assertPixelParity(String subject, int[] reference, int[] candidate, int size, int background, int minCoverageDivisor, int channelTolerance, IntFunction<String> describe) {
int covered = 0;
for (int pixel : reference) {
if (pixel != background) covered++;
}
final int coveredPixels = covered;
assertTrue(coveredPixels > reference.length / minCoverageDivisor, () -> "reference draw covered only " + coveredPixels + " of " + reference.length + " pixels");
int mismatches = 0;
int firstMismatch = -1;
for (int i = 0; i < reference.length; i++) {
if (!withinTolerance(reference[i], candidate[i], channelTolerance)) {
mismatches++;
if (firstMismatch < 0) firstMismatch = i;
}
}
final int index = firstMismatch;
final int differing = mismatches;
assertEquals(0, mismatches, () -> subject + " differ from baked quads in " + differing + " of " + reference.length + " pixels, first at (" + (index % size) + "," + (index / size) + "): reference " + describe.apply(reference[index]) + " instanced " + describe.apply(candidate[index]));
}
private static boolean withinTolerance(int a, int b, int tolerance) {
if (tolerance == 0) return a == b;
for (int shift = 0; shift <= 24; shift += 8) {
final int ca = (a >> shift) & 0xFF;
final int cb = (b >> shift) & 0xFF;
if (Math.abs(ca - cb) > tolerance) return false;
}
return true;
}
public static void assertOverlayFootprint(int[] base, int[] overlay, int background, String overlayName) {
int covered = 0;
for (int i = 0; i < base.length; i++) {
final int index = i;
if (base[i] != background) {
covered++;
assertTrue(base[i] != overlay[i], () -> "pixel " + index + " covered by the base draw did not change under the " + overlayName + ": 0x" + Integer.toHexString(overlay[index]));
} else {
assertEquals(base[i], overlay[i], () -> "pixel " + index + " outside the base footprint must not be touched by the " + overlayName);
}
}
final int coveredPixels = covered;
assertTrue(coveredPixels > base.length / 8, "base draw covered only " + coveredPixels + " of " + base.length + " pixels");
}
public static void resetFfpState() {
GLStateManager.ffpInstancing = Instancing.NONE;
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
GLStateManager.glBindVertexArray(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDepthMask(true);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
}
}
@@ -0,0 +1,216 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.function.IntFunction;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
public final class ParticleParityFixture {
public record Particle(float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR) {}
public record Rotation(float x, float xz, float z, float yz, float xy) {
public static Rotation of(float yawDeg, float pitchDeg, boolean thirdPersonFront) {
final float sign = thirdPersonFront ? -1f : 1f;
final float yaw = (float) Math.toRadians(yawDeg);
final float pitch = (float) Math.toRadians(pitchDeg);
final float rotX = (float) Math.cos(yaw) * sign;
final float rotZ = (float) Math.sin(yaw) * sign;
final float rotYZ = -rotZ * (float) Math.sin(pitch) * sign;
final float rotXY = rotX * (float) Math.sin(pitch) * sign;
final float rotXZ = (float) Math.cos(pitch);
return new Rotation(rotX, rotXZ, rotZ, rotYZ, rotXY);
}
public float offsetX(float a, float b) {
return a * x + b * yz;
}
public float offsetY(float b) {
return b * xz;
}
public float offsetZ(float a, float b) {
return a * z + b * xy;
}
}
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
private static final int REF_STRIDE = 24;
private static int referenceVao;
private static int referenceVbo;
private static int instanceVbo;
private static int checkerTexture;
private ParticleParityFixture() {}
public static float cornerA(int corner) {
return CORNER_A[corner];
}
public static float cornerB(int corner) {
return CORNER_B[corner];
}
public static Particle[] particles() {
return new Particle[] {
new Particle(-0.75f, 0.6f, 0f, 0.10f, 0f, 0f, 1f, 1f, 0xFF0000FF),
new Particle(-0.45f, -0.55f, 0f, 0.16f, 0f, 0f, 1f, 1f, 0xFF00FF00),
new Particle(-0.15f, 0.2f, 0f, 0.07f, 0f, 0f, 1f, 1f, 0xFFFF0000),
new Particle(0.1f, 0.75f, 0f, -0.12f, 0f, 0f, 1f, 1f, 0xFF00FFFF),
new Particle(0.35f, -0.3f, 0f, 0.20f, 0f, 0f, 1f, 1f, 0xFFFF00FF),
new Particle(0.6f, 0.45f, 0f, -0.08f, 0f, 0f, 1f, 1f, 0xFFFFFF00),
new Particle(0.8f, -0.7f, 0f, 0.13f, 0f, 0f, 1f, 1f, 0xFFFFFFFF),
new Particle(-0.8f, -0.1f, 0f, 0.09f, 0f, 0f, 1f, 1f, 0xFF0080FF),
};
}
private static int createCheckerTexture() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
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);
final ByteBuffer texels = BufferUtils.createByteBuffer(2 * 2 * 4);
putTexel(texels, 255, 32, 32);
putTexel(texels, 32, 255, 32);
putTexel(texels, 32, 32, 255);
putTexel(texels, 255, 255, 32);
texels.flip();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
return id;
}
private static void putTexel(ByteBuffer buf, int r, int g, int b) {
buf.put((byte) r).put((byte) g).put((byte) b).put((byte) 255);
}
public static void setupScene(boolean textureMatrix) {
GLStateManager.glDisable(GL11.GL_FOG);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
checkerTexture = createCheckerTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, checkerTexture);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
if (textureMatrix) {
GLStateManager.glTranslatef(0.1f, 0.1f, 0.0f);
GLStateManager.glScalef(0.8f, 0.8f, 1.0f);
}
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
private static ByteBuffer buildReferenceData(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
final ByteBuffer data = BufferUtils.createByteBuffer(particles.length * 4 * REF_STRIDE);
for (Particle p : particles) {
for (int i = 0; i < 4; i++) {
final float a = cornerA(i);
final float b = cornerB(i);
final float ox = a * rotX + b * rotYZ;
final float oy = b * rotXZ;
final float oz = a * rotZ + b * rotXY;
final float u = a < 0f ? p.u0() : p.u1();
final float v = b < 0f ? p.v0() : p.v1();
final int base = data.position();
data.putFloat(base, p.cx() + ox * p.half());
data.putFloat(base + 4, p.cy() + oy * p.half());
data.putFloat(base + 8, p.cz() + oz * p.half());
data.putInt(base + 12, p.colorABGR());
data.putFloat(base + 16, u);
data.putFloat(base + 20, v);
data.position(base + REF_STRIDE);
}
}
data.flip();
return data;
}
public static void drawReference(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
final ByteBuffer data = buildReferenceData(particles, rotX, rotXZ, rotZ, rotYZ, rotXY);
if (referenceVao == 0) referenceVao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(referenceVao);
if (referenceVbo == 0) referenceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, referenceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
FfpFixture.attrib(1, 4, GL11.GL_UNSIGNED_BYTE, true, REF_STRIDE, 12);
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 16);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT);
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, particles.length * 4);
GLStateManager.glBindVertexArray(0);
}
public static void drawInstanced(Particle[] particles, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
ParticleQuadMesh.update(rotX, rotXZ, rotZ, rotYZ, rotXY);
final ByteBuffer records = BufferUtils.createByteBuffer(particles.length * ParticleInstancedAttribs.STRIDE);
final long records0 = memAddress0(records);
for (int i = 0; i < particles.length; i++) {
final Particle p = particles[i];
final long ptr = records0 + (long) i * ParticleInstancedAttribs.STRIDE;
ParticleInstancedAttribs.writeInstance(ptr, p.cx(), p.cy(), p.cz(), p.half(), p.u0(), p.v0(), p.u1(), p.v1(), p.colorABGR(), 0);
}
records.position(0).limit(particles.length * ParticleInstancedAttribs.STRIDE);
GLStateManager.glBindVertexArray(ParticleQuadMesh.vao());
VAOManager.setCurrentVertexFlags(ParticleQuadMesh.VERTEX_FLAGS);
if (instanceVbo == 0) instanceVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
ParticleInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.PARTICLE;
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, particles.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.glBindVertexArray(0);
}
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
FfpFixture.assertPixelParity("instanced particles", reference, instanced, size, background, 16, 1, describe);
}
public static void deleteResources() {
if (instanceVbo != 0) {
GLStateManager.glDeleteBuffers(instanceVbo);
instanceVbo = 0;
}
if (referenceVbo != 0) {
GLStateManager.glDeleteBuffers(referenceVbo);
referenceVbo = 0;
}
if (referenceVao != 0) {
GLStateManager.glDeleteVertexArrays(referenceVao);
referenceVao = 0;
}
if (checkerTexture != 0) {
GLStateManager.glDeleteTextures(checkerTexture);
checkerTexture = 0;
}
ParticleQuadMesh.delete();
}
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.testutil;
import com.gtnewhorizon.gtnhlib.reflect.Fields;
import com.gtnewhorizon.gtnhlib.reflect.Fields.LookupType;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -99,6 +100,17 @@ public final class Reflect {
}
}
@SuppressWarnings("unchecked")
public static <T> T allocate(Class<T> type) {
try {
final Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (T) ((Unsafe) f.get(null)).allocateInstance(type);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
private static Method resolve(Class<?> owner, String name, Class<?>[] signature) throws NoSuchMethodException {
for (Class<?> c = owner; c != null; c = c.getSuperclass()) {
try {
+9 -6
View File
@@ -23,12 +23,15 @@ repositories {
}
}
val forwardedRunProperties = System.getProperties().stringPropertyNames().filter { it.startsWith("angelica.") || it.startsWith("quickPlay") }
configure<MinecraftExtension> {
extraRunJvmArguments.set(extraRunJvmArguments.get().filterNot { arg -> forwardedRunProperties.any { arg.startsWith("-D$it=") } })
}
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
for ((key, value) in System.getProperties()) {
val name = key.toString()
if (name.startsWith("angelica.") || name.startsWith("quickPlay")) {
jvmArgs("-D$name=$value")
}
for (name in forwardedRunProperties) {
jvmArgs("-D$name=${System.getProperty(name)}")
}
}
@@ -38,7 +41,7 @@ val lwjglDebug = project.extra.has("lwjglDebug") && project.extra["lwjglDebug"]
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && name != "runClient" && isMacOs) {
jvmArgs("-XstartOnFirstThread")
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
if ("angelica.sdlgpu.encoderAssertions" !in forwardedRunProperties) jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
if (lwjglDebug) {
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
environment("METAL_DEBUG_ERROR_MODE", "0")
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Usage: scripts/flyby-bench.sh <name> ffp|pack [flyby-capture.sh args...]
# Env: FLYBY_SCENE, FLYBY_ORIGIN, TIME_OF_DAY, QUICKPLAY_WORLD, TRACY_CAPTURE, SDLGPU, RENDER_DISTANCE.
# Forces particles:0 for the run and restores it after exit; the game rewrites options.txt on quit.
# A worktree needs tracy-client/src/main/resources/natives copied in.
set -euo pipefail
NAME="${1:?usage: flyby-bench.sh <name> ffp|pack [extra gradle args...]}"
MODE="${2:?usage: flyby-bench.sh <name> ffp|pack [extra gradle args...]}"
shift 2
OUT_DIR="tmp/bench"
OPTIONS="run/client/options.txt"
SHADERS="run/client/config/shaders.properties"
mkdir -p "$OUT_DIR"
PARTICLES_BEFORE="$(grep -E '^particles:' "$OPTIONS" | cut -d: -f2)"
SHADERS_BEFORE="$(grep -E '^enableShaders=' "$SHADERS" | cut -d= -f2)"
if [ -n "${RENDER_DISTANCE:-}" ]; then RENDER_DISTANCE_BEFORE="$(grep -E '^renderDistance:' "$OPTIONS" | cut -d: -f2)"; fi
restore() {
sed -i '' "s/^particles:.*/particles:${PARTICLES_BEFORE}/" "$OPTIONS"
sed -i '' "s/^enableShaders=.*/enableShaders=${SHADERS_BEFORE}/" "$SHADERS"
if [ -n "${RENDER_DISTANCE:-}" ]; then
sed -i '' "s/^renderDistance:.*/renderDistance:${RENDER_DISTANCE_BEFORE}/" "$OPTIONS"
fi
}
trap restore EXIT
sed -i '' 's/^particles:.*/particles:0/' "$OPTIONS"
case "$MODE" in
ffp) sed -i '' 's/^enableShaders=.*/enableShaders=false/' "$SHADERS" ;;
pack) sed -i '' 's/^enableShaders=.*/enableShaders=true/' "$SHADERS" ;;
*) echo "error: mode must be ffp or pack" >&2; exit 1 ;;
esac
if [ -n "${RENDER_DISTANCE:-}" ]; then
sed -i '' "s/^renderDistance:.*/renderDistance:${RENDER_DISTANCE}/" "$OPTIONS"
fi
export FLYBY_SCENE="${FLYBY_SCENE:-scripts/flyby-scenes/entities-x8.txt}"
export QUICKPLAY_WORLD="${QUICKPLAY_WORLD:-Flat World - No Mods}"
if [ "$QUICKPLAY_WORLD" = "Flat World - No Mods" ]; then export FLYBY_ORIGIN="${FLYBY_ORIGIN-1509.5,-924.5,0}"; fi
export TRACY_CAPTURE="${TRACY_CAPTURE:-$HOME/dev/mc/tracy/capture/build/tracy-capture}"
export SDLGPU="${SDLGPU:-1}"
echo "=== $NAME ($MODE) start $(date '+%H:%M:%S')"
scripts/flyby-capture.sh "$OUT_DIR/$NAME.tracy" circuit 128 -Dangelica.flyby.timeOfDay="${TIME_OF_DAY:-6000}" "$@" >| "$OUT_DIR/$NAME.console.log" 2>&1
echo "=== $NAME exit=$? $(date '+%H:%M:%S'), $(grep -c 'did not execute' "$OUT_DIR/$NAME.console.log" || true) failed scene lines"
+29 -3
View File
@@ -11,8 +11,11 @@
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
#
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
# FLYBY_ORIGIN - x,z[,yaw] flight start. Unset or empty starts where the player is.
# QUICKPLAY_WORLD - save to auto-load (default: latest).
# SDLGPU=1 - launch on the SDL-GPU backend.
# FLYBY_SCENE - server command file run at flyby start.
# SDLGPU=0|1 - force OpenGL (0) or SDL-GPU (1); unset uses the build default.
#
# Requires tracy-capture on PATH (TRACY_CAPTURE=/path/to/tracy-capture).
# QuickPlay loads QUICKPLAY_WORLD on launch; the run starts once the world is up.
@@ -33,8 +36,30 @@ WARMUP="${FLYBY_WARMUP:-400}"
TRACY_CAPTURE="${TRACY_CAPTURE:-tracy-capture}"
BACKEND_ARGS=()
if [ "${SDLGPU:-0}" = "1" ]; then
if [ "${SDLGPU:-}" = "1" ]; then
BACKEND_ARGS+=(-Dangelica.sdlgpu.enable=true)
elif [ "${SDLGPU:-}" = "0" ]; then
BACKEND_ARGS+=(-Dangelica.sdlgpu.enable=false)
fi
if [ -n "${FLYBY_PACING:-}" ]; then
BACKEND_ARGS+=(-Dangelica.flyby.pacing="$FLYBY_PACING")
fi
if [ -n "${FLYBY_ORIGIN:-}" ]; then
BACKEND_ARGS+=(-Dangelica.flyby.origin="$FLYBY_ORIGIN")
fi
SCENE_ABS=""
SCENE_ARGS=()
if [ -n "${FLYBY_SCENE:-}" ]; then
case "$FLYBY_SCENE" in
/*) SCENE_ABS="$FLYBY_SCENE" ;;
*) SCENE_ABS="$PWD/$FLYBY_SCENE" ;;
esac
if [ ! -f "$SCENE_ABS" ]; then
echo "error: FLYBY_SCENE file not found: $SCENE_ABS" >&2
exit 1
fi
SCENE_ARGS+=(-Dangelica.flyby.commands="$SCENE_ABS")
fi
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
@@ -68,7 +93,7 @@ cleanup() {
}
trap cleanup EXIT
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default})"
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default} scene=${SCENE_ABS:-none})"
./gradlew runClient25 --console=plain \
-Dangelica.flyby.route="$ROUTE" \
@@ -79,6 +104,7 @@ echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default})
-Dangelica.flyby.exitWhenDone=true \
-DquickPlaySingleplayer="${QUICKPLAY_WORLD:-latest}" \
${BACKEND_ARGS[@]+"${BACKEND_ARGS[@]}"} \
${SCENE_ARGS[@]+"${SCENE_ARGS[@]}"} \
"$@"
echo "==> game exited, finalising capture"
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
import csv
import io
import os
import subprocess
import sys
CSVEXPORT = os.environ.get("TRACY_CSVEXPORT", os.path.expanduser("~/dev/mc/tracy/csvexport/build/tracy-csvexport"))
PLOTS = [
"gl.drawCalls", "gl.listPlaybacks", "gl.streamDraws", "entitiesRendered", "blockEntitiesRendered", "tesr.modelParts", "tesr.retainedDraws", "tesr.cacheMisses", "tesr.instancedDraws", "tesr.instancedInstances", "tesr.cubeInstances",
"tesr.streamedInstances", "tesr.texMatrixRuns", "tesr.liveFallbacks", "tesr.bail.foreignProgram",
"tesr.bail.material", "tesr.bail.texture", "entity.shadowQuads", "entity.shadowDraws", "items.instanced", "items.fallback", "items.glintInstanced",
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
"sdl.acquireWaitUs",
]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass"]
CLOCK = "gl.drawCalls"
def run(args):
out = subprocess.run([CSVEXPORT] + args, capture_output=True, text=True)
if out.returncode != 0:
sys.exit(out.stderr.strip() or f"csvexport failed: {' '.join(args)}")
return list(csv.DictReader(io.StringIO(out.stdout)))
def windows(path):
marks = []
for row in run(["-m", path]):
name = row["MessageName"]
if name.startswith("flyby leg ") or name.startswith("flyby turn ") or name.startswith("flyby end"):
marks.append((int(row["total_ns"]), name))
marks.sort()
result = {}
for i, (t, name) in enumerate(marks):
if not name.startswith("flyby leg "):
continue
leg = int(name.split()[-1])
if leg in result:
continue
end = marks[i + 1][0] if i + 1 < len(marks) else None
result[leg] = (t, end)
return result
def inside(t, window):
start, end = window
return t >= start and (end is None or t < end)
def stats(values):
if not values:
return None
s = sorted(values)
n = len(s)
return (sum(s) / n, s[n // 2], s[min(n - 1, int(n * 0.99))], s[-1], n)
def analyze(path):
legs = windows(path)
plots = {name: {leg: [] for leg in legs} for name in PLOTS + ["Frame"]}
last_clock = None
for row in run(["-u", "-p", "-f", "zzz-no-such-zone", path]):
name = row["name"]
if name not in plots:
continue
t = int(row["ns_since_start"])
if name == CLOCK:
if last_clock is not None:
for leg, window in legs.items():
if inside(t, window):
plots["Frame"][leg].append((t - last_clock) / 1e6)
break
last_clock = t
for leg, window in legs.items():
if inside(t, window):
plots[name][leg].append(float(row["value"]))
break
zones = {name: {leg: [] for leg in legs} for name in ZONES}
for name in ZONES:
for row in run(["-u", "-c", "-f", name, path]):
if row["name"] != name:
continue
t = int(row["ns_since_start"])
for leg, window in legs.items():
if inside(t, window):
zones[name][leg].append(int(row["exec_time_ns"]))
break
return legs, plots, zones
def fmt(v, digits=1):
if v is None:
return "-"
if isinstance(v, float):
return f"{v:.{digits}f}"
return str(v)
def main(paths):
labels = [os.path.splitext(os.path.basename(p))[0] for p in paths]
data = [analyze(p) for p in paths]
all_legs = sorted({leg for legs, _, _ in data for leg in legs})
width = max(12, max(len(l) for l in labels) + 2)
for leg in all_legs:
print(f"\n== leg {leg}")
print("metric".ljust(28) + "".join(l.rjust(width) for l in labels))
def line(metric, cells):
print(metric.ljust(28) + "".join(fmt(c).rjust(width) for c in cells))
frame = [stats(plots["Frame"].get(leg, [])) for _, plots, _ in data]
line("frames", [f[4] if f else None for f in frame])
line("frame avg ms", [f[0] if f else None for f in frame])
line("frame p50 ms", [f[1] if f else None for f in frame])
line("frame p99 ms", [f[2] if f else None for f in frame])
line("frame max ms", [f[3] if f else None for f in frame])
busy = []
for (_, plots, _), f in zip(data, frame):
acquire = plots["sdl.acquireWaitUs"].get(leg, [])
gpu = plots["sdl.gpuWaitUs"].get(leg, [])
if f and acquire:
busy.append(f[0] - sum(acquire) / len(acquire) / 1000.0 - (sum(gpu) / len(gpu) / 1000.0 if gpu else 0.0))
else:
busy.append(None)
line("busy avg ms", busy)
for name in PLOTS:
per = []
for _, plots, _ in data:
v = plots[name].get(leg, [])
per.append(sum(v) / len(v) if v else None)
if all(c is None for c in per):
continue
line(name + " /frame", per)
for name in ZONES:
per = []
for legs, _, zones in data:
v = zones[name].get(leg, [])
frames = len(plots_frames(data, legs, leg))
per.append((sum(v) / 1000.0 / frames) if v and frames else None)
if all(c is None for c in per):
continue
line(name + " us/frame", per)
def plots_frames(data, legs, leg):
for l, plots, _ in data:
if l is legs:
return plots["Frame"].get(leg, [])
return []
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit("usage: flyby-compare.py <capture.tracy> [more captures...]")
main(sys.argv[1:])
+881
View File
@@ -0,0 +1,881 @@
# entities.txt at 8x density. Summons are relative to the flight start; start facing south (yaw 0).
# gamerules
difficulty 1
gamerule doMobSpawning false
gamerule doFireTick false
gamerule mobGriefing false
# hurt-flash + glint zombies, x=-5 leg
summon Zombie ~-17 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-17 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-14 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-11 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-8 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-2 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~1 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~4 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
# pigzombies, x=6 leg
summon PigZombie ~-6 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-6 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~-3 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~ ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~3 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~9 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~12 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~15 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
# passive mobs, grouped by species
summon Cow ~-17 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-2 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~1 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~4 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-6 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~-3 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~ ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~3 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~9 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~12 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~15 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-21 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-18 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-15 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-12 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-6 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-3 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~ ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~-2 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~1 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~4 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~7 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~13 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~16 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~19 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# villagers
summon Villager ~-17 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-17 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-17 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-17 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-17 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-17 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-14 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-11 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-8 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-2 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~1 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~4 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# dropped items, x=2 leg, 12 item types cycled twice
summon Item ~-10 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-4 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-1 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~5 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~8 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~11 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
# fire strip
setblock ~4 ~ ~110 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~4 ~1 ~110 minecraft:fire
setblock ~-4 ~ ~114 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~-4 ~1 ~114 minecraft:fire
setblock ~4 ~ ~118 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~4 ~1 ~118 minecraft:fire
setblock ~-4 ~ ~122 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~-4 ~1 ~122 minecraft:fire
# slowed cows, z=133 leg
summon Cow ~-22 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-19 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-16 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-13 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-10 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-7 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-4 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-1 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-26 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-23 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-20 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-11 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-8 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-30 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-27 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-24 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-21 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-18 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-15 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-12 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-9 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-34 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-31 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-28 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-25 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-22 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-19 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-16 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-13 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-38 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-35 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-32 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-29 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-26 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-23 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-20 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-17 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-42 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-39 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-36 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-33 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-30 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-27 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-24 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-21 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-46 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-43 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-40 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-37 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-34 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-31 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-28 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-25 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-50 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-47 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-44 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-41 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-38 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-35 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-32 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-29 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-54 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-51 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-48 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-45 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-42 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-39 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-36 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-33 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-58 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-55 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-52 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-49 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-46 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-43 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-40 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-37 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-62 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-59 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-56 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-53 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-50 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-47 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-44 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-41 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-66 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-63 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-60 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-57 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-54 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-51 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-48 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-45 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-70 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-67 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-64 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-61 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-58 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-55 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-52 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-49 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-74 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-71 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-68 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-65 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-62 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-59 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-56 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-53 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-78 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-75 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-72 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-69 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-66 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-63 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-60 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-57 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-82 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-79 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-76 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-73 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-70 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-67 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-64 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-61 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
# plain zombies, z=123 leg
summon Zombie ~-92 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-89 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-86 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-83 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-80 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-77 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-74 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-71 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-96 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-93 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-90 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-87 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-84 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-81 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-78 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-75 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-100 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-97 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-94 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-91 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-88 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-85 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-82 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-79 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-104 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-101 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-98 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-95 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-92 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-89 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-86 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-83 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-108 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-105 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-102 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-99 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-96 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-93 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-90 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-87 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-112 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-109 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-106 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-103 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-100 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-97 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-94 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-91 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-116 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-113 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-110 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-107 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-104 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-101 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-98 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-95 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-120 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-117 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-114 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-111 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-108 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-105 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-102 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-99 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# dropped items, z=126 leg, 12 item types once
summon Item ~-28 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-25 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-22 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-19 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-16 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-13 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-10 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-7 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-32 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-29 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-26 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-23 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-20 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-17 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-14 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-11 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-36 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-33 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-30 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-27 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-24 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-21 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-18 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-15 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-40 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-37 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-34 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-31 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-28 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-25 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-22 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-19 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-44 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-41 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-38 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-35 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-32 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-29 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-26 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-23 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-48 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-45 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-42 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-39 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-36 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-33 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-30 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-27 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-52 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-49 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-46 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-43 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-40 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-37 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-34 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-31 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-56 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-53 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-50 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-47 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-44 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-41 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-38 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-35 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-60 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-57 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-54 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-51 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-48 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-45 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-42 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-39 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-64 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-61 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-58 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-55 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-52 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-49 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-46 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-43 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-68 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-65 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-62 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-59 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-56 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-53 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-50 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-47 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-72 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-69 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-66 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-63 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-60 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-57 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-54 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-51 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
+139
View File
@@ -0,0 +1,139 @@
# Summons are relative to the flight start; start facing south (yaw 0).
# gamerules
difficulty 1
gamerule doMobSpawning false
gamerule doFireTick false
gamerule mobGriefing false
# hurt-flash + glint zombies, x=-5 leg
summon Zombie ~-5 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon Zombie ~-5 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:19b,Amplifier:0b,Duration:1000000},{Id:10b,Amplifier:1b,Duration:1000000}],Equipment:[{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},{id:313s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:312s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:311s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:310s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
# pigzombies, x=6 leg
summon PigZombie ~6 ~ ~10 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~14 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~18 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~22 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~26 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~30 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~34 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
summon PigZombie ~6 ~ ~38 {PersistenceRequired:1b,HealF:200.0f,Attributes:[{Name:"generic.maxHealth",Base:200.0d},{Name:"generic.movementSpeed",Base:0.0d}],Equipment:[{id:283s,Count:1b},{id:317s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:316s,Count:1b},{id:315s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}},{id:314s,Count:1b,tag:{ench:[{id:0s,lvl:1s}]}}],ForgeData:{flyby:1b}}
# passive mobs, grouped by species
summon Cow ~-5 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Cow ~-5 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Pig ~6 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Sheep ~-9 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~44 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~48 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~52 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~56 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~60 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Chicken ~10 ~ ~64 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# villagers
summon Villager ~-5 ~ ~70 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~74 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~78 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~82 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~86 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Villager ~-5 ~ ~90 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# dropped items, x=2 leg, 12 item types cycled twice
summon Item ~2 ~ ~60 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~62 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~64 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~66 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~68 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~70 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~72 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~74 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~76 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~78 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~80 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~82 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~84 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~86 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~88 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~90 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~92 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~94 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~96 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~98 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~100 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~102 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~104 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~2 ~ ~106 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
# fire strip
setblock ~4 ~ ~110 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~4 ~1 ~110 minecraft:fire
setblock ~-4 ~ ~114 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~-4 ~1 ~114 minecraft:fire
setblock ~4 ~ ~118 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~4 ~1 ~118 minecraft:fire
setblock ~-4 ~ ~122 minecraft:dispenser 0 replace {CustomName:"flyby"}
setblock ~-4 ~1 ~122 minecraft:fire
# slowed cows, z=133 leg
summon Cow ~-10 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-14 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-18 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-22 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-26 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-30 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-34 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-38 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-42 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-46 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-50 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-54 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-58 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-62 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-66 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
summon Cow ~-70 ~ ~133 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ActiveEffects:[{Id:1b,Amplifier:0b,Duration:1000000}],ForgeData:{flyby:1b}}
# plain zombies, z=123 leg
summon Zombie ~-80 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-84 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-88 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-92 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-96 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-100 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-104 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
summon Zombie ~-108 ~ ~123 {PersistenceRequired:1b,Attributes:[{Name:"generic.movementSpeed",Base:0.0d}],ForgeData:{flyby:1b}}
# dropped items, z=126 leg, 12 item types once
summon Item ~-16 ~ ~126 {Item:{id:276s,Count:1b,tag:{ench:[{id:16s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-20 ~ ~126 {Item:{id:264s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-24 ~ ~126 {Item:{id:265s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-28 ~ ~126 {Item:{id:261s,Count:1b,tag:{ench:[{id:48s,lvl:1s}]}},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-32 ~ ~126 {Item:{id:322s,Count:1b,Damage:1s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-36 ~ ~126 {Item:{id:403s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-40 ~ ~126 {Item:{id:373s,Count:1b,Damage:8193s},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-44 ~ ~126 {Item:{id:299s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-48 ~ ~126 {Item:{id:1s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-52 ~ ~126 {Item:{id:20s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-56 ~ ~126 {Item:{id:50s,Count:1b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
summon Item ~-60 ~ ~126 {Item:{id:4s,Count:64b},Age:-32768s,Lifespan:2147483647,Owner:"flyby",ForgeData:{flyby:1b}}
@@ -75,39 +75,58 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public int getPointerSize() { return 8; } // 64-bit
@Override public int glGenBuffers() { return BackendManager.RENDER_BACKEND.genBuffers(); }
@Override public void glDeleteBuffers(int buffer) { BackendManager.RENDER_BACKEND.deleteBuffers(buffer); }
@Override public int glGenBuffers() { return GLStateManager.glGenBuffers(); }
@Override public void glDeleteBuffers(int buffer) { GLStateManager.glDeleteBuffers(buffer); }
@Override public void glBindBuffer(int target, int buffer) { GLStateManager.glBindBuffer(target, buffer); }
@Override public void glBufferData(int target, long size, int usage) { BackendManager.RENDER_BACKEND.bufferData(target, size, usage); }
@Override public void glBufferData(int target, ByteBuffer data, int usage) { BackendManager.RENDER_BACKEND.bufferData(target, data, usage); }
@Override public void glBufferData(int target, long size, int usage) {
GLStateManager.glBufferData(target, size, usage);
}
@Override public void glBufferData(int target, ByteBuffer data, int usage) {
GLStateManager.glBufferData(target, data, usage);
}
@Override public void glBufferData(int target, long size, long data, int usage) {
if (data != 0) {
BackendManager.RENDER_BACKEND.bufferData(target, MemoryUtil.memByteBuffer(data, (int) size), usage);
GLStateManager.nglBufferData(target, size, data, usage);
} else {
BackendManager.RENDER_BACKEND.bufferData(target, size, usage);
GLStateManager.glBufferData(target, size, usage);
}
}
@Override public void glBufferStorage(int target, long size, int flags) { BackendManager.RENDER_BACKEND.bufferStorage(target, size, flags); }
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
BackendManager.RENDER_BACKEND.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
@Override public void glBufferStorage(int target, long size, int flags) {
GLStateManager.glBufferStorage(target, size, flags);
}
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
GLStateManager.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override public void glDrawArrays(int mode, int first, int count) {
GLStateManager.glDrawArrays(mode, first, count);
}
@Override public void glDrawArrays(int mode, int first, int count) { BackendManager.RENDER_BACKEND.drawArrays(mode, first, count); }
@Override public void glDrawBuffers(int[] buffers) {
try (var stack = MemoryStack.stackPush()) {
BackendManager.RENDER_BACKEND.drawBuffers(stack.ints(buffers));
GLStateManager.glDrawBuffers(stack.ints(buffers));
}
}
@Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override public void glTexParameteri(int target, int pname, int param) { GLStateManager.glTexParameteri(target, pname, param); }
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) { return BackendManager.RENDER_BACKEND.mapBufferRange(target, offset, length, flags); }
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) {
return GLStateManager.glMapBufferRange(target, offset, length, flags);
}
@Override public long nglMapBuffer(int target, int access) { return 0; }
@Override public ByteBuffer glMapBuffer(int target, int access) { return BackendManager.RENDER_BACKEND.mapBuffer(target, access); }
@Override public void glUnmapBuffer(int target) { BackendManager.RENDER_BACKEND.unmapBuffer(target); }
@Override public void glFlushMappedBufferRange(int target, long offset, long length) { BackendManager.RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
@Override public void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { BackendManager.RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
@Override public void glBindBufferBase(int target, int index, int buffer) { BackendManager.RENDER_BACKEND.bindBufferBase(target, index, buffer); }
@Override public ByteBuffer glMapBuffer(int target, int access) {
return GLStateManager.glMapBuffer(target, access);
}
@Override public void glUnmapBuffer(int target) { GLStateManager.glUnmapBuffer(target); }
@Override public void glFlushMappedBufferRange(int target, long offset, long length) {
GLStateManager.glFlushMappedBufferRange(target, offset, length);
}
@Override public void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset,
long size) {
GLStateManager.glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
}
@Override public void glBindBufferBase(int target, int index, int buffer) {
GLStateManager.glBindBufferBase(target, index, buffer);
}
@Override public int glGenVertexArrays() { return GLStateManager.glGenVertexArrays(); }
@Override public void glDeleteVertexArrays(int array) { GLStateManager.glDeleteVertexArrays(array); }
@@ -117,63 +136,80 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public void glEnableVertexAttribArray(int index) { GLStateManager.glEnableVertexAttribArray(index); }
@Override public void glVertexAttribDivisor(int index, int divisor) { GLStateManager.glVertexAttribDivisor(index, divisor); }
@Override public int glCreateShader(int type) { return BackendManager.RENDER_BACKEND.createShader(type); }
@Override public void glShaderSource(int shader, CharSequence source) { BackendManager.RENDER_BACKEND.shaderSource(shader, source); }
@Override public void glShaderSourceSafe(int shader, CharSequence source) { BackendManager.RENDER_BACKEND.shaderSource(shader, source); }
@Override public void glCompileShader(int shader) { BackendManager.RENDER_BACKEND.compileShader(shader); }
@Override public String glGetShaderInfoLog(int shader, int maxLength) { return BackendManager.RENDER_BACKEND.getShaderInfoLog(shader, maxLength); }
@Override public int glGetShaderi(int shader, int pname) { return BackendManager.RENDER_BACKEND.getShaderi(shader, pname); }
@Override public void glDeleteShader(int shader) { BackendManager.RENDER_BACKEND.deleteShader(shader); }
@Override public int glCreateShader(int type) { return GLStateManager.glCreateShader(type); }
@Override public void glShaderSource(int shader, CharSequence source) {
GLStateManager.glShaderSource(shader, source);
}
@Override public void glShaderSourceSafe(int shader, CharSequence source) {
GLStateManager.glShaderSource(shader, source);
}
@Override public void glCompileShader(int shader) { GLStateManager.glCompileShader(shader); }
@Override public String glGetShaderInfoLog(int shader, int maxLength) {
return GLStateManager.glGetShaderInfoLog(shader, maxLength);
}
@Override public int glGetShaderi(int shader, int pname) { return GLStateManager.glGetShaderi(shader, pname); }
@Override public void glDeleteShader(int shader) { GLStateManager.glDeleteShader(shader); }
@Override public int glCreateProgram() { return BackendManager.RENDER_BACKEND.createProgram(); }
@Override public void glAttachShader(int program, int shader) { BackendManager.RENDER_BACKEND.attachShader(program, shader); }
@Override public void glLinkProgram(int program) { BackendManager.RENDER_BACKEND.linkProgram(program); }
@Override public String glGetProgramInfoLog(int program, int maxLength) { return BackendManager.RENDER_BACKEND.getProgramInfoLog(program, maxLength); }
@Override public int glGetProgrami(int program, int pname) { return BackendManager.RENDER_BACKEND.getProgrami(program, pname); }
@Override public int glCreateProgram() { return GLStateManager.glCreateProgram(); }
@Override public void glAttachShader(int program, int shader) { GLStateManager.glAttachShader(program, shader); }
@Override public void glLinkProgram(int program) { GLStateManager.glLinkProgram(program); }
@Override public String glGetProgramInfoLog(int program, int maxLength) {
return GLStateManager.glGetProgramInfoLog(program, maxLength);
}
@Override public int glGetProgrami(int program, int pname) { return GLStateManager.glGetProgrami(program, pname); }
@Override public void glUseProgram(int program) { GLStateManager.glUseProgram(program); }
@Override public void glDeleteProgram(int program) { BackendManager.RENDER_BACKEND.deleteProgram(program); }
@Override public void glBindAttribLocation(int program, int index, CharSequence name) { BackendManager.RENDER_BACKEND.bindAttribLocation(program, index, name); }
@Override public void glDeleteProgram(int program) { GLStateManager.glDeleteProgram(program); }
@Override public void glBindAttribLocation(int program, int index, CharSequence name) {
GLStateManager.glBindAttribLocation(program, index, name);
}
@Override public void glBindFragDataLocation(int program, int colorNumber, CharSequence name) {}
@Override public int glGetUniformLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getUniformLocation(program, name); }
@Override public int glGetUniformLocation(int program, CharSequence name) {
return GLStateManager.glGetUniformLocation(program, name);
}
@Override public int glGetUniformBlockIndex(int program, CharSequence name) { return -1; /* TODO */ }
@Override public void glUniformBlockBinding(int program, int blockIndex, int blockBinding) { /* TODO */ }
@Override public void glUniform1f(int location, float v0) { BackendManager.RENDER_BACKEND.uniform1f(location, v0); }
@Override public void glUniform1i(int location, int v0) { BackendManager.RENDER_BACKEND.uniform1i(location, v0); }
@Override public void glUniform1fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform1fv(location, value); }
@Override public void glUniform2i(int location, int v0, int v1) { BackendManager.RENDER_BACKEND.uniform2i(location, v0, v1); }
@Override public void glUniform3f(int location, float v0, float v1, float v2) { BackendManager.RENDER_BACKEND.uniform3f(location, v0, v1, v2); }
@Override public void glUniform3fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform3(location, value); }
@Override public void glUniform3fv(int location, float[] value) {
try (var stack = MemoryStack.stackPush()) {
final FloatBuffer buf = stack.floats(value);
BackendManager.RENDER_BACKEND.uniform3(location, buf);
}
@Override public void glUniform1f(int location, float v0) { GLStateManager.glUniform1f(location, v0); }
@Override public void glUniform1i(int location, int v0) { GLStateManager.glUniform1i(location, v0); }
@Override public void glUniform1fv(int location, FloatBuffer value) { GLStateManager.glUniform1(location, value); }
@Override public void glUniform2i(int location, int v0, int v1) { GLStateManager.glUniform2i(location, v0, v1); }
@Override public void glUniform3f(int location, float v0, float v1, float v2) {
GLStateManager.glUniform3f(location, v0, v1, v2);
}
@Override public void glUniform4fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform4(location, value); }
@Override public void glUniform4fv(int location, float[] value) {
try (var stack = MemoryStack.stackPush()) {
final FloatBuffer buf = stack.floats(value);
BackendManager.RENDER_BACKEND.uniform4(location, buf);
}
@Override public void glUniform3fv(int location, FloatBuffer value) { GLStateManager.glUniform3(location, value); }
@Override public void glUniform3fv(int location, float[] value) { GLStateManager.glUniform3(location, value); }
@Override public void glUniform4fv(int location, FloatBuffer value) { GLStateManager.glUniform4(location, value); }
@Override public void glUniform4fv(int location, float[] value) { GLStateManager.glUniform4(location, value); }
@Override public void glUniformMatrix3fv(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix3(location, transpose, value);
}
@Override public void glUniformMatrix4fv(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix4(location, transpose, value);
}
@Override public void glUniformMatrix3fv(int location, boolean transpose, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniformMatrix3(location, transpose, value); }
@Override public void glUniformMatrix4fv(int location, boolean transpose, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniformMatrix4(location, transpose, value); }
@Override public void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) { BackendManager.RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex); }
@Override public void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) { BackendManager.RENDER_BACKEND.multiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex); }
@Override public void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) { BackendManager.RENDER_BACKEND.multiDrawElementsIndirect(mode, type, indirect, drawcount, stride); }
@Override public void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
GLStateManager.glDrawElementsBaseVertex(mode, count, type, indices, basevertex);
}
@Override public void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount,
long pBaseVertex) {
GLStateManager.glMultiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
}
@Override public void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GLStateManager.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@Override public long glFenceSync(int condition, int flags) { return BackendManager.RENDER_BACKEND.fenceSync(condition, flags); }
@Override public int glClientWaitSync(long sync, int flags, long timeout) { return BackendManager.RENDER_BACKEND.clientWaitSync(sync, flags, timeout); }
@Override public long glFenceSync(int condition, int flags) { return GLStateManager.glFenceSync(condition, flags); }
@Override public int glClientWaitSync(long sync, int flags, long timeout) {
return GLStateManager.glClientWaitSync(sync, flags, timeout);
}
@Override public int glGetSynci(long sync, int pname, IntBuffer length) {
if (length != null && length.remaining() > 0) length.put(0, 1);
return ((SDLGPURenderBackend) BackendManager.RENDER_BACKEND).getSyncStatus(sync);
}
@Override public void glWaitSync(long sync, int flags, long timeout) {
((SDLGPURenderBackend) BackendManager.RENDER_BACKEND).waitSync(sync, flags, timeout);
GLStateManager.glWaitSync(sync, flags, timeout);
}
@Override public void glDeleteSync(long sync) { BackendManager.RENDER_BACKEND.deleteSync(sync); }
@Override public void glDeleteSync(long sync) { GLStateManager.glDeleteSync(sync); }
private long[] queryElapsed = new long[64];
private int activeQueryId = -1;
@@ -185,7 +221,7 @@ public final class SDLGPULWJGLService implements LWJGLService {
}
}
@Override public int glGenQueries() { return BackendManager.RENDER_BACKEND.genQueries(); }
@Override public int glGenQueries() { return GLStateManager.glGenQueries(); }
@Override public void glDeleteQueries(int query) { }
@Override public void glBeginQuery(int target, int id) {
ensureQueryCapacity(id);
@@ -204,29 +240,42 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public int setupDebugCallback(DebugMessageHandler handler) { return 0; }
@Override public void disableDebugCallback() { }
@Override public void glObjectLabel(int identifier, int name, CharSequence label) { BackendManager.RENDER_BACKEND.objectLabel(identifier, name, label); }
@Override public void glPushDebugGroup(int source, int id, CharSequence message) { BackendManager.RENDER_BACKEND.pushDebugGroup(source, id, message); }
@Override public void glPopDebugGroup() { BackendManager.RENDER_BACKEND.popDebugGroup(); }
@Override public int glGenTextures() { return BackendManager.RENDER_BACKEND.genTextures(); }
@Override public void glGenTextures(int[] textures) {
for (int i = 0; i < textures.length; i++) textures[i] = BackendManager.RENDER_BACKEND.genTextures();
@Override public void glObjectLabel(int identifier, int name, CharSequence label) {
GLStateManager.glObjectLabel(identifier, name, label);
}
@Override public void glDeleteTextures(int texture) { BackendManager.RENDER_BACKEND.deleteTextures(texture); }
@Override public void glPushDebugGroup(int source, int id, CharSequence message) {
GLStateManager.glPushDebugGroup(source, id, message);
}
@Override public void glPopDebugGroup() { GLStateManager.glPopDebugGroup(); }
@Override public int glGenTextures() { return GLStateManager.glGenTextures(); }
@Override public void glGenTextures(int[] textures) {
for (int i = 0; i < textures.length; i++) textures[i] = GLStateManager.glGenTextures();
}
@Override public void glDeleteTextures(int texture) { GLStateManager.glDeleteTextures(texture); }
@Override public void glDeleteTextures(int[] textures) {
for (int t : textures) BackendManager.RENDER_BACKEND.deleteTextures(t);
for (int t : textures) GLStateManager.glDeleteTextures(t);
}
@Override public void glBindTexture(int target, int texture) { GLStateManager.glBindTexture(target, texture); }
@Override public void glActiveTexture(int texture) { GLStateManager.glActiveTexture(texture); }
@Override public int glGetTexLevelParameteri(int target, int level, int pname) { return BackendManager.RENDER_BACKEND.getTexLevelParameteri(target, level, pname); }
@Override public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) { BackendManager.RENDER_BACKEND.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); }
@Override public void glPixelStorei(int pname, int param) { BackendManager.RENDER_BACKEND.pixelStorei(pname, param); }
@Override public int glGetTexLevelParameteri(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width,
int height) {
GLStateManager.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
@Override public void glPixelStorei(int pname, int param) { GLStateManager.glPixelStorei(pname, param); }
@Override public int glGenFramebuffers() { return BackendManager.RENDER_BACKEND.genFramebuffers(); }
@Override public void glDeleteFramebuffers(int framebuffer) { BackendManager.RENDER_BACKEND.deleteFramebuffers(framebuffer); }
@Override public int glGenFramebuffers() { return GLStateManager.glGenFramebuffers(); }
@Override public void glDeleteFramebuffers(int framebuffer) { GLStateManager.glDeleteFramebuffers(framebuffer); }
@Override public void glBindFramebuffer(int target, int framebuffer) { GLStateManager.glBindFramebuffer(target, framebuffer); }
@Override public int glCheckFramebufferStatus(int target) { return BackendManager.RENDER_BACKEND.checkFramebufferStatus(target); }
@Override public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) { BackendManager.RENDER_BACKEND.framebufferTexture2D(target, attachment, textarget, texture, level); }
@Override public int glCheckFramebufferStatus(int target) {
return GLStateManager.glCheckFramebufferStatus(target);
}
@Override public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override public void glEnable(int cap) { GLStateManager.glEnable(cap); }
@Override public void glDisable(int cap) { GLStateManager.glDisable(cap); }
@@ -236,20 +285,22 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public void glDepthMask(boolean flag) { GLStateManager.glDepthMask(flag); }
@Override public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) { GLStateManager.glColorMask(red, green, blue, alpha); }
@Override public void glViewport(int x, int y, int width, int height) { GLStateManager.glViewport(x, y, width, height); }
@Override public void glClear(int mask) { BackendManager.RENDER_BACKEND.clear(mask); }
@Override public void glClearColor(float red, float green, float blue, float alpha) { BackendManager.RENDER_BACKEND.clearColor(red, green, blue, alpha); }
@Override public int glGetError() { return BackendManager.RENDER_BACKEND.getError(); }
@Override public void glMatrixMode(int mode) { /* Handled by GLSM FFP emulation */ }
@Override public void glLoadMatrixf(FloatBuffer m) { /* Handled by GLSM FFP emulation */ }
@Override public int glGetInteger(int pname) { return BackendManager.RENDER_BACKEND.getInteger(pname); }
@Override public void glGetIntegerv(int pname, int[] params) {
if (params.length > 0) params[0] = BackendManager.RENDER_BACKEND.getInteger(pname);
@Override public void glClear(int mask) { GLStateManager.glClear(mask); }
@Override public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override public int glGetError() { return GLStateManager.glGetError(); }
@Override public void glMatrixMode(int mode) { GLStateManager.glMatrixMode(mode); }
@Override public void glLoadMatrixf(FloatBuffer m) { GLStateManager.glLoadMatrix(m); }
@Override public int glGetInteger(int pname) { return GLStateManager.glGetInteger(pname); }
@Override public void glGetIntegerv(int pname, int[] params) { GLStateManager.glGetInteger(pname, params); }
@Override public boolean glGetBoolean(int pname) { return GLStateManager.glGetBoolean(pname); }
@Override public String glGetString(int pname) { return GLStateManager.glGetString(pname); }
@Override public int glGetAttribLocation(int program, CharSequence name) {
return GLStateManager.glGetAttribLocation(program, name);
}
@Override public boolean glGetBoolean(int pname) { return BackendManager.RENDER_BACKEND.getBoolean(pname); }
@Override public String glGetString(int pname) { return BackendManager.RENDER_BACKEND.getString(pname); }
@Override public int glGetAttribLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getAttribLocation(program, name); }
@Override public MemoryStack stackPush() {
org.lwjgl.system.MemoryStack.stackPush();
@@ -629,7 +629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
private void midFrameFenceFlush() {
private void flushAndSubmitMidFrame() {
final ContextState st = s();
drainDeferredPersistentRegions(st);
pipelineApplier.flushAttribRingChunk(st);
@@ -638,6 +638,10 @@ public class SDLGPURenderBackend extends RenderBackend {
transferThread.awaitSubmittedUpTo(st.frameHighestEnqueuedSeq);
}
frameManager.submitMidFrame();
}
private void midFrameFenceFlush() {
flushAndSubmitMidFrame();
fenceTracker.resolvePendingFences();
}
@@ -756,8 +760,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState cs = s();
if (cap == GL11.GL_BLEND) {
if (index < 0 || index >= ContextState.MAX_COLOR_ATTACHMENTS) return;
if (cs.pipeline.blendEnabledPerAttachment[index] != on) {
cs.pipeline.blendEnabledPerAttachment[index] = on;
if (cs.pipeline.blendEnabledPerDrawBuffer[index] != on) {
cs.pipeline.blendEnabledPerDrawBuffer[index] = on;
cs.pipeline.markOutputDirty();
}
return;
@@ -771,8 +775,8 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_BLEND -> {
boolean changed = false;
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (cs.pipeline.blendEnabledPerAttachment[i] != on) {
cs.pipeline.blendEnabledPerAttachment[i] = on;
if (cs.pipeline.blendEnabledPerDrawBuffer[i] != on) {
cs.pipeline.blendEnabledPerDrawBuffer[i] = on;
changed = true;
}
}
@@ -801,21 +805,12 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
final ContextState cs = s();
final int sc = FormatMap.mapBlendFactor(srcRGB);
final int dc = FormatMap.mapBlendFactor(dstRGB);
final int sa = FormatMap.mapBlendFactor(srcAlpha);
final int da = FormatMap.mapBlendFactor(dstAlpha);
if (sc == cs.pipeline.srcColorFactor && dc == cs.pipeline.dstColorFactor && sa == cs.pipeline.srcAlphaFactor && da == cs.pipeline.dstAlphaFactor) return;
cs.pipeline.srcColorFactor = sc;
cs.pipeline.dstColorFactor = dc;
cs.pipeline.srcAlphaFactor = sa;
cs.pipeline.dstAlphaFactor = da;
cs.pipeline.markOutputDirty();
s().pipeline.setBlendFactors(FormatMap.mapBlendFactor(srcRGB), FormatMap.mapBlendFactor(dstRGB), FormatMap.mapBlendFactor(srcAlpha), FormatMap.mapBlendFactor(dstAlpha));
}
@Override public void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
if (buf < 0 || buf >= ContextState.MAX_COLOR_ATTACHMENTS) return;
s().pipeline.setBlendFactors(buf, FormatMap.mapBlendFactor(srcRGB), FormatMap.mapBlendFactor(dstRGB), FormatMap.mapBlendFactor(srcAlpha), FormatMap.mapBlendFactor(dstAlpha));
}
@Override public void blendEquation(int mode) {
@@ -1016,6 +1011,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void drawArrays(int mode, int first, int count) {
if (count <= 0) return;
final FrameState f = frameManager.frame();
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
final ContextState st = s();
@@ -1295,14 +1291,26 @@ public class SDLGPURenderBackend extends RenderBackend {
return isProxyTarget(target) ? 0 : st.boundTextures[st.activeTextureUnit];
}
private int requestedMipLevels(int glId) {
return Math.max(1, resourceManager.getOrCreateTexSamplerState(glId).maxLevel + 1);
}
private int fullMipLevels(int glId, int width, int height) {
final int full = PixelOps.defaultMipLevels(width, height);
final int maxLevel = resourceManager.getOrCreateTexSamplerState(glId).maxLevel;
return maxLevel >= 0 ? Math.min(full, maxLevel + 1) : full;
}
private boolean growForLevel(int glId, int level) {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
final int wanted = meta != null
? Math.max(level + 1, fullMipLevels(glId, meta.width(), meta.height()))
: level + 1;
return resourceManager.ensureTextureLevels(glId, wanted);
}
private void ensureLevel0Storage(ContextState st, int glId, int target, int internalFormat, int width, int height, int depth, int format, boolean mipmapped) {
final int numLevels;
if (mipmapped) {
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
} else {
numLevels = 1;
}
final int numLevels = mipmapped ? requestedMipLevels(glId) : 1;
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
final int targetSdlFormat = bgraSwizzle
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
@@ -1326,7 +1334,7 @@ public class SDLGPURenderBackend extends RenderBackend {
&& m.width() == w
&& m.height() == h
&& m.depth() == d
&& m.levels() == levels
&& m.levels() >= levels
&& m.glTarget() == target
&& m.glFormat() == glFormat
&& m.sdlFormat() == sdlFormat;
@@ -1343,6 +1351,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
} else if (!growForLevel(glId, level)) {
return;
}
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, 0, 0, width, height, level, format, type);
@@ -1413,6 +1423,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
} else if (!growForLevel(glId, level)) {
return;
}
if (st.boundPixelUnpackBuffer == 0) return;
@@ -1463,6 +1475,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (level == 0) {
ensureLevel0Storage(cs, destGlId, target, internalFormat, width, height, 1, GL11.GL_NONE, true);
} else if (!growForLevel(destGlId, level)) {
return;
}
textureOps.copyTexSubImageImpl(cs, destGlId, level, 0, 0, x, y, width, height);
@@ -1567,6 +1581,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL12.GL_TEXTURE_MAX_LOD -> ss.maxLod = (float) param;
case GL14.GL_TEXTURE_COMPARE_MODE -> ss.compareMode = param;
case GL14.GL_TEXTURE_COMPARE_FUNC -> ss.compareFunc = param;
case EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> ss.maxAnisotropy = param;
default -> { return; }
}
ss.sdlSampler = 0;
@@ -1751,6 +1766,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private void applyDrawBuffersFromIntBuffer(FboState fbo, IntBuffer bufs, boolean isBound) {
final int count = bufs.remaining();
if (count > ContextState.MAX_COLOR_ATTACHMENTS) return;
final int basePos = bufs.position();
if (fbo.drawBuffers.length == count) {
boolean same = true;
@@ -1836,6 +1852,7 @@ public class SDLGPURenderBackend extends RenderBackend {
pixels.position(0);
pixels.limit(start + h * stride);
pixels.position(start);
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, pixels);
pixels.position(0);
pixels.limit(baseLimit);
@@ -1844,6 +1861,7 @@ public class SDLGPURenderBackend extends RenderBackend {
} else {
final ByteBuffer staging = MemoryUtil.memAlloc(w * h * 4);
try {
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, staging);
staging.rewind();
PixelOps.postProcessReadback(staging, w, h, format, srcSdlFormat, fromFbo0);
@@ -1875,6 +1893,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final long texHandle = resourceManager.getTextureHandle(glId);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return;
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, 0, 0, meta.width(), meta.height(), level, pixels);
pixels.rewind();
PixelOps.postProcessReadback(pixels, meta.width(), meta.height(), format, meta.sdlFormat(), false);
@@ -2011,10 +2030,8 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
cs.pipeline.programId = program;
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
cs.pipeline.shaderInputMask = prog.vertexInputMask;
cs.pipeline.shaderInputVecSize = prog.vertexInputVecSize;
cs.pipeline.shaderInputBaseType = prog.vertexInputBaseType;
cs.pipeline.shaderInputName = prog.vertexInputName;
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
cs.pipeline.markInputDirty();
cs.pipeline.markShaderDirty();
}
@Override public void useProgram(int program) {
@@ -2064,10 +2081,7 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
cs.pipeline.programId = program;
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
cs.pipeline.shaderInputMask = prog.vertexInputMask;
cs.pipeline.shaderInputVecSize = prog.vertexInputVecSize;
cs.pipeline.shaderInputBaseType = prog.vertexInputBaseType;
cs.pipeline.shaderInputName = prog.vertexInputName;
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
cs.pipeline.markShaderDirty();
}
@@ -2275,6 +2289,7 @@ public class SDLGPURenderBackend extends RenderBackend {
LOG.warn("uniform1i: loc={} val={} -- NO program bound", location, v0);
}
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 1);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0);
pipelineApplier.putUniform(st, location, a);
}
@@ -2282,6 +2297,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 1);
if (a == null) return;
a[0] = v0;
pipelineApplier.putUniform(st, location, a);
}
@@ -2289,6 +2305,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 2);
if (a == null) return;
a[0] = v0; a[1] = v1;
pipelineApplier.putUniform(st, location, a);
}
@@ -2296,6 +2313,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 2);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1);
pipelineApplier.putUniform(st, location, a);
}
@@ -2303,6 +2321,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 3);
if (a == null) return;
a[0] = v0; a[1] = v1; a[2] = v2;
pipelineApplier.putUniform(st, location, a);
}
@@ -2310,6 +2329,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 3);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1); a[2] = Float.intBitsToFloat(v2);
pipelineApplier.putUniform(st, location, a);
}
@@ -2317,6 +2337,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 4);
if (a == null) return;
a[0] = v0; a[1] = v1; a[2] = v2; a[3] = v3;
pipelineApplier.putUniform(st, location, a);
}
@@ -2324,6 +2345,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 4);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1);
a[2] = Float.intBitsToFloat(v2); a[3] = Float.intBitsToFloat(v3);
pipelineApplier.putUniform(st, location, a);
@@ -2345,6 +2367,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int n = value.remaining();
final int pos = value.position();
final float[] v = pipelineApplier.reuseOrAlloc(st, location, n);
if (v == null) return;
for (int i = 0; i < n; i++) v[i] = Float.intBitsToFloat(value.get(pos + i));
pipelineApplier.putUniform(st, location, v);
}
@@ -2354,8 +2377,16 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void uniform4(int location, FloatBuffer value) {
pipelineApplier.putUniformFv(s(), location, value);
}
@Override public void uniform3fv(int location, float[] values) { pipelineApplier.putUniform(s(), location, values); }
@Override public void uniform4fv(int location, float[] values) { pipelineApplier.putUniform(s(), location, values); }
@Override public void uniform3fv(int location, float[] values) { storeFloatUniform(location, values); }
@Override public void uniform4fv(int location, float[] values) { storeFloatUniform(location, values); }
private void storeFloatUniform(int location, float[] values) {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, values.length);
if (a == null) return;
System.arraycopy(values, 0, a, 0, values.length);
pipelineApplier.putUniform(st, location, a);
}
@Override public void uniformMatrix2(int location, boolean transpose, FloatBuffer value) {
pipelineApplier.storeMatrix(s(), location, transpose, value, 2);
}
@@ -2398,16 +2429,62 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public int genBuffers() { return resourceManager.genBuffer(); }
@Override public void deleteBuffers(int buffer) { dropMappingFor(buffer); readbackShadows.release(buffer); resourceManager.deleteBuffer(buffer); }
@Override public void deleteBuffers(int buffer) { deleteBuffer(buffer); }
@Override public void deleteBuffers(IntBuffer buffers) {
for (int i = 0; i < buffers.remaining(); i++) {
final int buffer = buffers.get(buffers.position() + i);
dropMappingFor(buffer);
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
deleteBuffer(buffers.get(buffers.position() + i));
}
}
private void deleteBuffer(int buffer) {
dropMappingFor(buffer);
if (buffer != 0) unbindDeletedBuffer(s(), buffer);
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
}
private static void unbindDeletedBuffer(ContextState st, int buffer) {
final ContextState.VAOState vao = st.currentVao;
boolean vaoChanged = false;
for (int i = 0; i < ContextState.MAX_VERTEX_ATTRIBS; i++) {
if (vao.bindingBuffer[i] == buffer) {
vao.bindingBuffer[i] = 0;
vaoChanged = true;
}
if (vao.attribVBO[i] == buffer) vao.attribVBO[i] = 0;
}
if (vao.elementBuffer == buffer) {
vao.elementBuffer = 0;
vaoChanged = true;
}
if (vaoChanged) st.bumpAttribStateGen();
if (st.boundArrayBuffer == buffer) st.boundArrayBuffer = 0;
if (st.boundIndirectBuffer == buffer) st.boundIndirectBuffer = 0;
if (st.boundDispatchIndirectBuffer == buffer) st.boundDispatchIndirectBuffer = 0;
if (st.boundCopyReadBuffer == buffer) st.boundCopyReadBuffer = 0;
if (st.boundCopyWriteBuffer == buffer) st.boundCopyWriteBuffer = 0;
if (st.boundUniformBuffer == buffer) st.boundUniformBuffer = 0;
if (st.boundSSBO == buffer) st.boundSSBO = 0;
if (st.boundPixelPackBuffer == buffer) st.boundPixelPackBuffer = 0;
if (st.boundPixelUnpackBuffer == buffer) st.boundPixelUnpackBuffer = 0;
boolean uboChanged = false;
boolean ssboChanged = false;
for (int i = 0; i < ContextState.MAX_INDEXED_BUFFERS; i++) {
if (st.boundUboByIndex[i] == buffer) {
st.boundUboByIndex[i] = 0;
st.uboRangeOffset[i] = 0;
st.uboRangeSize[i] = 0;
uboChanged = true;
}
if (st.boundSsboByIndex[i] == buffer) {
st.boundSsboByIndex[i] = 0;
ssboChanged = true;
}
}
if (uboChanged) st.uboRangeGen++;
if (ssboChanged) st.ssboBindGen++;
}
private static void dropMappingFor(int buffer) {
final ContextState st = s();
if (buffer == 0 || st.mappedBufferGlId != buffer || st.mappedStagingBuffer == null) return;
@@ -3264,32 +3341,31 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.refreshTextureReferences(glId);
}
@Override public void generateTextureMipmap(int texture) {
final long texHandle = resourceManager.getTextureHandle(texture);
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null || meta.levels() <= 1) return;
if (resourceManager.getTextureHandle(texture) == 0 || frameManager.getCommandBuffer() == 0) return;
ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null) return;
// The texture was created with only the levels GL had defined, so grow it to the chain glGenerateMipmap fills.
final int wanted = fullMipLevels(texture, meta.width(), meta.height());
if (wanted > meta.levels()) {
if (!resourceManager.ensureTextureLevels(texture, wanted)) return;
meta = resourceManager.getTextureMeta(texture);
if (meta == null) return;
}
if (meta.levels() <= 1) return;
// SDL asserts if any pass (render or copy) is active when generating mipmaps.
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive();
frameManager.noteMipGen();
textureOps.clearPendingMipGen(texture);
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), texHandle);
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), resourceManager.getTextureHandle(texture));
}
@Override public void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (texture == 0) return;
final ContextState st = s();
if (level == 0) {
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(texture);
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
textureOps.releaseTextureForRealloc(st, texture);
if (format == GL12.GL_BGRA && (internalformat == GL11.GL_RGBA || internalformat == GL11.GL_RGBA8)) {
resourceManager.createTextureWithSdlFormat(texture, target,
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
internalformat, width, height, 1, numLevels);
} else {
resourceManager.createTexture(texture, target, internalformat, width, height, 1, numLevels);
}
resourceManager.refreshTextureReferences(texture);
ensureLevel0Storage(st, texture, target, internalformat, width, height, 1, format, true);
} else if (!growForLevel(texture, level)) {
return;
}
textureOps.uploadTextureRegion(st, texture, resourceManager.getTextureMeta(texture), resourceManager.getTextureHandle(texture), pixels, 0, 0, width, height, level, format, type);
}
@@ -3554,6 +3630,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_MAX_TEXTURE_SIZE -> MAX_TEXTURE_SIZE;
case GL12.GL_MAX_3D_TEXTURE_SIZE -> MAX_3D_TEXTURE_SIZE;
case GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE -> MAX_CUBE_MAP_TEXTURE_SIZE;
case EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT -> (int) MAX_ANISOTROPY;
default -> -1;
};
}
@@ -3563,6 +3640,8 @@ public class SDLGPURenderBackend extends RenderBackend {
static final int MAX_TEXTURE_SIZE = 16384;
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
static final int MAX_3D_TEXTURE_SIZE = 2048;
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
static final float MAX_ANISOTROPY = 16.0f;
private static int unknownGetInteger(int pname) {
synchronized (UNKNOWN_GET_INTEGER_SEEN) {
@@ -3577,11 +3656,22 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void getInteger(int pname, IntBuffer params) {
if (params.remaining() > 0) params.put(params.position(), getInteger(pname));
}
private static float unknownGetFloat(int pname) {
synchronized (UNKNOWN_GET_FLOAT_SEEN) {
if (UNKNOWN_GET_FLOAT_SEEN.add(pname)) {
LOG.warn("getFloat: unhandled pname 0x{}, returning 0; callers reading this as a device limit will treat the feature as unavailable", Integer.toHexString(pname));
}
}
return 0.0f;
}
private static final IntOpenHashSet UNKNOWN_GET_FLOAT_SEEN = new IntOpenHashSet();
@Override public float getFloat(int pname) {
return switch (pname) {
case GL11.GL_LINE_WIDTH -> 1.0f;
case GL11.GL_POINT_SIZE -> 1.0f;
default -> 0.0f;
case EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT -> MAX_ANISOTROPY;
default -> unknownGetFloat(pname);
};
}
@Override public void getFloat(int pname, FloatBuffer params) {
@@ -3599,7 +3689,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState cs = s();
return switch (pname) {
case GL11.GL_DEPTH_TEST -> cs.pipeline.depthTestEnabled;
case GL11.GL_BLEND -> cs.pipeline.blendEnabledPerAttachment[0];
case GL11.GL_BLEND -> cs.pipeline.blendEnabledPerDrawBuffer[0];
case GL11.GL_CULL_FACE -> cs.pipeline.cullEnabled;
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
@@ -3841,11 +3931,11 @@ public class SDLGPURenderBackend extends RenderBackend {
private int voxLocStart = -1;
private int voxLocCount = -1;
@Override public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) {
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS || vertexBufferGlId == 0) return false;
@Override public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) {
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS) return false;
final ContextState st = s();
if (st.boundProgram == 0) return false;
bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, ssboBinding, vertexBufferGlId);
if (st.boundSsboByIndex[ssboBinding] == 0) return false;
final int loc = shaderManager.getUniformLocation(st.boundProgram, "u_RegionOffset");
if (loc >= 0) GLStateManager.glUniform3f(loc, x, y, z);
if (openPass != 0) voxelizationDispatcher.rebindVertexBuffer(st, openPass);
@@ -3880,6 +3970,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
if (count <= 0 || primcount <= 0) return;
if (!frameManager.isFrameActive()) return;
final ContextState st = s();
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
@@ -109,6 +109,7 @@ public final class DrawDispatch {
}
public void issueIndexedDraw(ContextState st, long rp, int ebo, int indexType, int count, int instances, int firstIndex, int baseVertex) {
if (count <= 0 || instances <= 0) return;
final EBOSplitScanner.EboSplit[] full;
if (st.primitiveRestartEnabled) {
full = resourceManager.getOrScanSplits(ebo, indexType, st.primitiveRestartSentinel);
@@ -104,9 +104,9 @@ public final class FFPTrace {
.append("/front=").append(p.frontFace).append("/all=").append(p.cullAll);
sb.append(" depth=").append(p.depthTestEnabled).append("/func=").append(p.depthCompareOp)
.append("/write=").append(p.depthWriteEnabled);
sb.append(" blend=").append(p.blendEnabledPerAttachment[0])
.append("/src=").append(p.srcColorFactor).append("/dst=").append(p.dstColorFactor)
.append("/srcA=").append(p.srcAlphaFactor).append("/dstA=").append(p.dstAlphaFactor)
sb.append(" blend=").append(p.blendEnabledPerDrawBuffer[0])
.append("/src=").append(p.srcColorFactors[0]).append("/dst=").append(p.dstColorFactors[0])
.append("/srcA=").append(p.srcAlphaFactors[0]).append("/dstA=").append(p.dstAlphaFactors[0])
.append("/op=").append(p.colorBlendOp).append('/').append(p.alphaBlendOp);
sb.append(" colorMask=0x").append(Integer.toHexString(p.colorWriteMask));
sb.append(" primitiveType=").append(p.primitiveType);
@@ -69,8 +69,11 @@ public final class PipelineApplier {
private final IntOpenHashSet dummyVboZeroWarned = new IntOpenHashSet();
private final IntOpenHashSet staleFboWarned = new IntOpenHashSet();
private final LongOpenHashSet bogusScissorWarned = new LongOpenHashSet();
private final LongOpenHashSet clampedScissorWarned = new LongOpenHashSet();
private final IntOpenHashSet pushUniformsNullCbWarned = new IntOpenHashSet();
private final int[] scissorRect = new int[4];
public PipelineApplier(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, PipelineStore pipelineStore, FBOClearTracker fboClearTracker, PersistentBufferSync persistentSync, SamplerBinder samplerBinder, StorageTextureBinder storageTextureBinder, StorageBufferBinder storageBufferBinder) {
this.frameManager = frameManager;
this.resourceManager = resourceManager;
@@ -461,27 +464,40 @@ public final class PipelineApplier {
}
if (st.scissorDirty) {
final long addr = st.cachedScissor.address();
final int sx, sy, sw, sh;
if (st.scissorEnabled) {
sx = st.scissorX;
sy = renderingToFbo ? st.scissorY : (fbo0Height(st) - st.scissorY - st.scissorH);
sw = st.scissorW;
sh = st.scissorH;
MemoryAccess.putInt(addr + SDL_Rect.X, sx);
MemoryAccess.putInt(addr + SDL_Rect.Y, sy);
MemoryAccess.putInt(addr + SDL_Rect.W, sw);
MemoryAccess.putInt(addr + SDL_Rect.H, sh);
if ((sw <= 0 || sh <= 0) && bogusScissorWarned.add(Hashing.packHiLo(sw, sh))) {
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
}
} else {
final int rectY = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
MemoryAccess.putInt(addr + SDL_Rect.X, (int) st.viewportX);
MemoryAccess.putInt(addr + SDL_Rect.Y, rectY);
MemoryAccess.putInt(addr + SDL_Rect.W, (int) st.viewportW);
MemoryAccess.putInt(addr + SDL_Rect.H, (int) st.viewportH);
sx = (int) st.viewportX;
sy = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
sw = (int) st.viewportW;
sh = (int) st.viewportH;
}
final boolean haveFboSize = renderingToFbo && fboState != null;
final int targetW = haveFboSize ? fboState.width : frameManager.getFbo0Width();
final int targetH = haveFboSize ? fboState.height : frameManager.getFbo0Height();
final boolean nonEmpty = ScissorClamp.clamp(sx, sy, sw, sh, targetW, targetH, scissorRect);
final int cx = scissorRect[ScissorClamp.X];
final int cy = scissorRect[ScissorClamp.Y];
final int cw = scissorRect[ScissorClamp.W];
final int ch = scissorRect[ScissorClamp.H];
if ((cx != sx || cy != sy || cw != sw || ch != sh) && clampedScissorWarned.add(Hashing.packHiLo(st.boundProgram, st.boundFboId))) {
LOG.warn("applyPipelineAndState: scissor (x={} y={} w={} h={}) is outside the {}x{} target - clamped to (x={} y={} w={} h={}); boundProgram={} boundFbo={} scissorEn={}", sx, sy, sw, sh, targetW, targetH, cx, cy, cw, ch, st.boundProgram, st.boundFboId, st.scissorEnabled);
}
if (!nonEmpty) return false;
final long addr = st.cachedScissor.address();
MemoryAccess.putInt(addr + SDL_Rect.X, cx);
MemoryAccess.putInt(addr + SDL_Rect.Y, cy);
MemoryAccess.putInt(addr + SDL_Rect.W, cw);
MemoryAccess.putInt(addr + SDL_Rect.H, ch);
SDL_SetGPUScissor(rp, st.cachedScissor);
st.scissorDirty = false;
}
@@ -535,7 +551,7 @@ public final class PipelineApplier {
final long elemAddr = st.vboBindingsAddr + (long) i * bindingSize;
if (vboHandle != 0) {
MemoryAccess.putAddress(elemAddr + SDL_GPUBufferBinding.BUFFER, vboHandle);
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, (int) vao.bindingOffset[b]);
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, PipelineCache.vertexOffsetBase(vao.bindingOffset[b]));
} else {
MemoryAccess.putAddress(elemAddr + SDL_GPUBufferBinding.BUFFER, dummyVBO);
MemoryAccess.putInt(elemAddr + SDL_GPUBufferBinding.OFFSET, i * 16);
@@ -677,10 +693,9 @@ public final class PipelineApplier {
public float[] reuseOrAlloc(ContextState st, int location, int length) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog != null && location >= 0 && location < prog.uniformSlotCount) {
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
}
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
return new float[length];
}
@@ -828,11 +843,13 @@ public final class PipelineApplier {
final int count = n / floatsPerMatrix;
if (count == 0) {
final float[] v = reuseOrAlloc(st, location, n);
if (v == null) return;
value.get(value.position(), v);
putUniform(st, location, v);
return;
}
final float[] out = reuseOrAlloc(st, location, count * floatsPerMatrix);
if (out == null) return;
MatrixMarshal.marshalMatrixToColumnMajor(value, value.position(), count, size, transpose, out, 0);
putUniform(st, location, out);
}
@@ -59,11 +59,11 @@ public final class PipelineCache {
public int primitiveType = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
public final boolean[] blendEnabledPerAttachment = new boolean[ContextState.MAX_COLOR_ATTACHMENTS];
public int srcColorFactor = SDL_GPU_BLENDFACTOR_ONE;
public int dstColorFactor = SDL_GPU_BLENDFACTOR_ZERO;
public int srcAlphaFactor = SDL_GPU_BLENDFACTOR_ONE;
public int dstAlphaFactor = SDL_GPU_BLENDFACTOR_ZERO;
public final boolean[] blendEnabledPerDrawBuffer = new boolean[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] srcColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] dstColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] srcAlphaFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] dstAlphaFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public int colorBlendOp = SDL_GPU_BLENDOP_ADD;
public int alphaBlendOp = SDL_GPU_BLENDOP_ADD;
@@ -160,15 +160,35 @@ public final class PipelineCache {
return true;
}
private int attachmentForSlot(int slot) {
final int[] db = drawBuffers;
if (db == null || slot < 0 || slot >= db.length) return 0;
final int idx = db[slot];
return (idx >= 0 && idx < ContextState.MAX_COLOR_ATTACHMENTS) ? idx : 0;
private boolean blendEnabledForSlot(int slot) {
return slot >= 0 && slot < ContextState.MAX_COLOR_ATTACHMENTS && blendEnabledPerDrawBuffer[slot];
}
private boolean blendEnabledForSlot(int slot) {
return blendEnabledPerAttachment[attachmentForSlot(slot)];
public boolean setBlendFactors(int sc, int dc, int sa, int da) {
boolean changed = false;
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (srcColorFactors[i] != sc || dstColorFactors[i] != dc || srcAlphaFactors[i] != sa || dstAlphaFactors[i] != da) {
srcColorFactors[i] = sc;
dstColorFactors[i] = dc;
srcAlphaFactors[i] = sa;
dstAlphaFactors[i] = da;
changed = true;
}
}
if (changed) markOutputDirty();
return changed;
}
public boolean setBlendFactors(int drawBuffer, int sc, int dc, int sa, int da) {
if (srcColorFactors[drawBuffer] == sc && dstColorFactors[drawBuffer] == dc && srcAlphaFactors[drawBuffer] == sa && dstAlphaFactors[drawBuffer] == da) {
return false;
}
srcColorFactors[drawBuffer] = sc;
dstColorFactors[drawBuffer] = dc;
srcAlphaFactors[drawBuffer] = sa;
dstAlphaFactors[drawBuffer] = da;
markOutputDirty();
return true;
}
public int maxAttribs;
@@ -183,6 +203,10 @@ public final class PipelineCache {
public PipelineCache() {
this.maxAttribs = ContextState.MAX_VERTEX_ATTRIBS;
Arrays.fill(srcColorFactors, SDL_GPU_BLENDFACTOR_ONE);
Arrays.fill(dstColorFactors, SDL_GPU_BLENDFACTOR_ZERO);
Arrays.fill(srcAlphaFactors, SDL_GPU_BLENDFACTOR_ONE);
Arrays.fill(dstAlphaFactors, SDL_GPU_BLENDFACTOR_ZERO);
}
long lastKey() {
@@ -199,6 +223,16 @@ public final class PipelineCache {
return true;
}
public void setVertexInputs(int mask, int[] vecSize, int[] baseType, String[] names) {
if (shaderInputMask != mask || !Arrays.equals(shaderInputVecSize, vecSize) || !Arrays.equals(shaderInputBaseType, baseType)) {
markInputDirty();
}
shaderInputMask = mask;
shaderInputVecSize = vecSize;
shaderInputBaseType = baseType;
shaderInputName = names;
}
public void markOutputDirty() { outputDirty = true; lastKey = 0L; }
public void markShaderDirty() { shaderDirty = true; lastKey = 0L; }
@@ -296,9 +330,11 @@ public final class PipelineCache {
| (((long) blendBits & 0xFFFFFFFFL) << 32);
long h = OUTPUT_SEED;
h = Hashing.fmix64(h, bits);
h = Hashing.fmix64(h, Hashing.packHiLo(primitiveType, srcColorFactor));
h = Hashing.fmix64(h, Hashing.packHiLo(dstColorFactor, srcAlphaFactor));
h = Hashing.fmix64(h, Hashing.packHiLo(dstAlphaFactor, colorBlendOp));
h = Hashing.fmix64(h, Hashing.packHiLo(primitiveType, colorBlendOp));
for (int i = 0; i < ctfLen; i++) {
h = Hashing.fmix64(h, Hashing.packHiLo(srcColorFactors[i], dstColorFactors[i]));
h = Hashing.fmix64(h, Hashing.packHiLo(srcAlphaFactors[i], dstAlphaFactors[i]));
}
h = Hashing.fmix64(h, Hashing.packHiLo(alphaBlendOp, depthCompareOp));
h = Hashing.fmix64(h, Hashing.packHiLo(stencilFrontCompareOp, stencilFrontFailOp));
h = Hashing.fmix64(h, Hashing.packHiLo(stencilFrontDepthFailOp, stencilFrontPassOp));
@@ -342,6 +378,7 @@ public final class PipelineCache {
| (((long) vao.attribType[i] & 0xFFFFL) << 16)
| ((vao.attribNormalized[i] ? 1L : 0L) << 32)
| ((vao.attribIsInteger[i] ? 1L : 0L) << 33)
| (((long) vertexOffsetMisalign(vao.bindingOffset[b]) & 3L) << 34)
| (((long) b & 0xFF) << 40)
| (((long) vao.attribStride[i] & 0xFFFFL) << 48);
final long w1 =
@@ -390,11 +427,11 @@ public final class PipelineCache {
desc.format(colorTargetFormats()[i]);
desc.blend_state()
.enable_blend(blendEnabledForSlot(i))
.src_color_blendfactor(srcColorFactor)
.dst_color_blendfactor(dstColorFactor)
.src_color_blendfactor(srcColorFactors[i])
.dst_color_blendfactor(dstColorFactors[i])
.color_blend_op(colorBlendOp)
.src_alpha_blendfactor(srcAlphaFactor)
.dst_alpha_blendfactor(dstAlphaFactor)
.src_alpha_blendfactor(srcAlphaFactors[i])
.dst_alpha_blendfactor(dstAlphaFactors[i])
.alpha_blend_op(alphaBlendOp)
.color_write_mask((byte) colorWriteMask)
.enable_color_write_mask(true);
@@ -504,8 +541,10 @@ public final class PipelineCache {
.append(" backCmp=").append(stencilBackCompareOp).append('\n');
}
sb.append(" Blend: enabled=").append(blendEnabledForSlot(0))
.append(" srcColor=").append(srcColorFactor).append(" dstColor=").append(dstColorFactor).append(" colorOp=").append(colorBlendOp)
.append(" srcAlpha=").append(srcAlphaFactor).append(" dstAlpha=").append(dstAlphaFactor).append(" alphaOp=").append(alphaBlendOp)
.append(" srcColor=").append(srcColorFactors[0]).append(" dstColor=").append(dstColorFactors[0])
.append(" colorOp=").append(colorBlendOp)
.append(" srcAlpha=").append(srcAlphaFactors[0]).append(" dstAlpha=").append(dstAlphaFactors[0])
.append(" alphaOp=").append(alphaBlendOp)
.append(" mask=0x").append(Integer.toHexString(colorWriteMask)).append('\n');
sb.append(" Rasterizer: cull=").append(getEffectiveCullMode())
.append(" front=").append(effectiveFrontFace())
@@ -624,6 +663,16 @@ public final class PipelineCache {
private static final int GL_TYPE_BASE = GL11.GL_BYTE;
static final int INVALID_FMT = -1;
public static final int VERTEX_BUFFER_OFFSET_ALIGN = 4;
public static int vertexOffsetBase(long bindingOffset) {
return (int) (bindingOffset & ~(VERTEX_BUFFER_OFFSET_ALIGN - 1));
}
public static int vertexOffsetMisalign(long bindingOffset) {
return (int) (bindingOffset & (VERTEX_BUFFER_OFFSET_ALIGN - 1));
}
private static final int[][] VTX_FMT = buildVertexFormatTable();
private static int[] fmtSame(int s1, int s2, int s3, int s4) {
@@ -801,7 +850,7 @@ public final class PipelineCache {
final int boundStride = vao.bindingStride[b];
final int stride = boundStride != 0 ? boundStride : (vao.attribStride[i] != 0 ? vao.attribStride[i] : vao.attribSize[i] * GLTypes.sizeBytes(vao.attribType[i]));
final int divisor = vao.bindingDivisor[b];
final int relOff = vao.attribRelativeOffset[i];
final int relOff = vao.attribRelativeOffset[i] + vertexOffsetMisalign(vao.bindingOffset[b]);
final int attribAlign = GLTypes.sizeBytes(vao.attribType[i]);
if (attribAlign > 0 && (stride % attribAlign) != 0) {
LOG.warn("[PipelineCache] unaligned vertex stride: location={} stride={} type=0x{} size={} -- attribAddress will not satisfy format alignment; fix producer", i, stride, Integer.toHexString(vao.attribType[i]), vao.attribSize[i]);
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
public final class ScissorClamp {
public static final int X = 0;
public static final int Y = 1;
public static final int W = 2;
public static final int H = 3;
private ScissorClamp() {}
public static boolean clamp(int x, int y, int w, int h, int targetW, int targetH, int[] out) {
if (targetW <= 0 || targetH <= 0) {
out[X] = x;
out[Y] = y;
out[W] = w;
out[H] = h;
return w > 0 && h > 0;
}
final int x0 = Math.max(x, 0);
final int y0 = Math.max(y, 0);
final int x1 = (int) Math.min((long) x + w, targetW);
final int y1 = (int) Math.min((long) y + h, targetH);
out[X] = x0;
out[Y] = y0;
out[W] = Math.max(x1 - x0, 0);
out[H] = Math.max(y1 - y0, 0);
return out[W] > 0 && out[H] > 0;
}
}
@@ -1,8 +1,9 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.sdl.SDLError;
@@ -86,13 +87,14 @@ public final class ReadbackShadows {
}
public void release(int glId) {
final LongArrayList doomed = new LongArrayList();
for (long k : slots.keySet()) {
if ((int) (k >>> 32) == glId) doomed.add(k);
}
for (int i = 0; i < doomed.size(); i++) {
final Slot slot = slots.remove(doomed.getLong(i));
if (slot != null) releaseSlot(slot);
if (slots.isEmpty()) return;
final ObjectIterator<Long2ObjectMap.Entry<Slot>> it = slots.long2ObjectEntrySet().fastIterator();
while (it.hasNext()) {
final Long2ObjectMap.Entry<Slot> entry = it.next();
if ((int) (entry.getLongKey() >>> 32) == glId) {
releaseSlot(entry.getValue());
it.remove();
}
}
}
@@ -513,7 +513,7 @@ public final class ResourceManager {
final int newUsage = reconcileUsageAndWarn(glId, meta.usage | requiredUsage);
if ((meta.usage & newUsage) == newUsage) return oldHandle;
final long newHandle = createTexture(glId, meta.glTarget, meta.glFormat, meta.width, meta.height, meta.depth, meta.levels, newUsage);
final long newHandle = createTextureWithSdlFormat(glId, meta.glTarget, meta.sdlFormat, meta.glFormat, meta.width, meta.height, meta.depth, meta.levels, newUsage);
if (newHandle == 0) return oldHandle;
if (oldHandle != 0 && !copyAllMips(oldHandle, newHandle, meta.glTarget, meta.width, meta.height, meta.depth, meta.levels)) {
LOG.warn("[ResourceManager] texture {} usage promote 0x{} -> 0x{} had no copy pass available; restoring the populated texture and skipping the promote. Image load/store on it will not work this frame.", glId, Integer.toHexString(meta.usage), Integer.toHexString(newUsage));
@@ -530,6 +530,41 @@ public final class ResourceManager {
return newHandle;
}
public boolean ensureTextureLevels(int glId, int levels) {
final TextureMeta meta;
final long oldHandle;
if (fastRead()) {
meta = textureMetas.get(glId);
if (meta == null) return false;
if (meta.levels >= levels) return true;
oldHandle = textureHandles.get(glId);
} else {
rLock.lock();
try {
meta = textureMetas.get(glId);
if (meta == null) return false;
if (meta.levels >= levels) return true;
oldHandle = textureHandles.get(glId);
} finally {
rLock.unlock();
}
}
final long newHandle = createTextureWithSdlFormat(glId, meta.glTarget, meta.sdlFormat, meta.glFormat, meta.width, meta.height, meta.depth, levels, meta.usage);
if (newHandle == 0) return false;
if (oldHandle != 0 && !copyAllMips(oldHandle, newHandle, meta.glTarget, meta.width, meta.height, meta.depth, meta.levels)) {
LOG.warn("[ResourceManager] texture {} mip grow {} -> {} had no copy pass available; keeping the populated texture. Levels past {} stay undefined this frame.", glId, meta.levels, levels, meta.levels - 1);
releaseTextureDeferred(newHandle);
restoreTextureHandle(glId, oldHandle, meta);
return false;
}
releaseTextureDeferred(oldHandle);
refreshTextureReferences(glId);
LOG.debug("Recreated texture {} with levels {} -> {} ({}x{})", glId, meta.levels, levels, meta.width, meta.height);
return true;
}
private boolean copyAllMips(long srcTex, long dstTex, int glTarget, int width, int height, int depth, int levels) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return false;
@@ -81,6 +81,7 @@ public final class TextureOps {
case GL12.GL_TEXTURE_MAX_LOD -> ss.maxLod = (float) param;
case GL14.GL_TEXTURE_COMPARE_MODE -> ss.compareMode = param;
case GL14.GL_TEXTURE_COMPARE_FUNC -> ss.compareFunc = param;
case EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> ss.maxAnisotropy = param;
default -> { return; }
}
ss.sdlSampler = 0;
@@ -101,9 +102,9 @@ public final class TextureOps {
st.samplerBindGen++;
}
/// requires a flush + submit to not read stale data
public void readbackTexture(long texHandle, int x, int y, int w, int h, int level, ByteBuffer output) {
if (texHandle == 0 || output == null) return;
frameManager.submitMidFrame();
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return;
@@ -31,6 +31,8 @@ public final class PerFrameBlockInjector {
private PerFrameBlockInjector() {}
public static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
if (normalize(perFrame) == null && normalize(perPass) == null) return source;
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
@@ -26,6 +26,8 @@ public final class SamplerStripper {
private SamplerStripper() {}
public static String stripUnused(String source) {
if (!source.contains("sampler")) return source;
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
@@ -88,8 +89,14 @@ public final class ShaderManager {
private static final int PREWARM_CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<PrewarmKey, PrewarmEntry> PREWARM_CACHE = new Object2ObjectLinkedOpenHashMap<>();
private record TransformKey(String source, int glShaderType, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {}
private record TransformEntry(String source, Set<String> boolUniforms) {}
private static final int TRANSFORM_CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<TransformKey, TransformEntry> TRANSFORM_CACHE = new Object2ObjectLinkedOpenHashMap<>();
public static void clearPrewarmCache() {
synchronized (PREWARM_CACHE) { PREWARM_CACHE.clear(); }
synchronized (TRANSFORM_CACHE) { TRANSFORM_CACHE.clear(); }
}
private static PrewarmHit lookupPrewarm(String src, int glShaderType) {
@@ -133,24 +140,35 @@ public final class ShaderManager {
return;
}
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, obj.type, "shader" + shader, true);
obj.boolUniforms = pre != null ? pre.boolUniforms() : Set.of();
String src = pre != null ? pre.rewrittenSource() : raw;
if (obj.isVertex()) {
src = ClipZRemap.injectGLToVulkanClipZ(src);
final TransformKey transformKey = new TransformKey(raw, obj.type, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
TransformEntry transformed;
synchronized (TRANSFORM_CACHE) {
transformed = TRANSFORM_CACHE.getAndMoveToFirst(transformKey);
}
src = SamplerStripper.stripUnused(src);
if (obj.type == GL20.GL_VERTEX_SHADER || obj.type == GL20.GL_FRAGMENT_SHADER) {
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
if (transformed == null) {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, obj.type, "shader" + shader, true);
String src = pre != null ? pre.rewrittenSource() : raw;
if (obj.isVertex()) {
src = ClipZRemap.injectGLToVulkanClipZ(src);
}
src = SamplerStripper.stripUnused(src);
if (obj.type == GL20.GL_VERTEX_SHADER || obj.type == GL20.GL_FRAGMENT_SHADER) {
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
}
transformed = new TransformEntry(src, pre != null ? pre.boolUniforms() : Set.of());
synchronized (TRANSFORM_CACHE) {
TRANSFORM_CACHE.putAndMoveToFirst(transformKey, transformed);
while (TRANSFORM_CACHE.size() > TRANSFORM_CACHE_MAX) TRANSFORM_CACHE.removeLast();
}
}
obj.source = src;
obj.boolUniforms = transformed.boolUniforms();
obj.source = transformed.source();
final ShaderWorkSubmitter submitter = GLSMHooks.shaderWorkSubmitter;
if (submitter != null) {
final int shaderKind = shaderKindFor(obj);
final int glType = obj.type;
final String finalSrc = src;
final String finalSrc = obj.source;
obj.spirvFuture = submitter.submit(() -> {
final SpirvCompiler.Result r = SpirvCompiler.compile(finalSrc, shaderKind, "shader" + shader, SpirvCompiler.Options.vulkanForced460Core());
if (r.spirv() != null && (glType == GL20.GL_VERTEX_SHADER || glType == GL20.GL_FRAGMENT_SHADER)) {
@@ -0,0 +1,159 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
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.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.system.MemoryUtil;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class BufferDeleteUnbindTest {
private static final int BUFFER = 7001;
private static final int OTHER = 7002;
private static final int UBO_INDEX = 3;
private static final int SSBO_INDEX = 5;
private static final int VERTEX_BINDING = 2;
private static final int ATTRIB = 4;
private static SDLGPURenderBackend backend;
private ContextState st;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
backend.bindBufferRange(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, BUFFER, 16L, 64L);
backend.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_INDEX, BUFFER);
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, BUFFER);
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, BUFFER);
backend.bindVertexBuffer(VERTEX_BINDING, BUFFER, 8L, 12);
backend.bindBuffer(GL15.GL_ARRAY_BUFFER, BUFFER);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, BUFFER);
backend.bindBuffer(GL31.GL_COPY_READ_BUFFER, BUFFER);
backend.bindBuffer(GL31.GL_COPY_WRITE_BUFFER, BUFFER);
backend.bindBuffer(GL30.GL_PIXEL_PACK_BUFFER, BUFFER);
backend.bindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, BUFFER);
st.currentVao.attribVBO[ATTRIB] = BUFFER;
}
@AfterEach
void tearDown() {
backend.bindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, 0);
backend.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_INDEX, 0);
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 0);
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
backend.bindVertexBuffer(VERTEX_BINDING, 0, 0L, 0);
backend.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 0);
backend.bindBuffer(GL31.GL_COPY_READ_BUFFER, 0);
backend.bindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0);
backend.bindBuffer(GL30.GL_PIXEL_PACK_BUFFER, 0);
backend.bindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, 0);
st.currentVao.attribVBO[ATTRIB] = 0;
}
private void assertAllCleared() {
assertEquals(0, st.boundUboByIndex[UBO_INDEX]);
assertEquals(0, st.uboRangeOffset[UBO_INDEX]);
assertEquals(0, st.uboRangeSize[UBO_INDEX]);
assertEquals(0, st.boundUniformBuffer);
assertEquals(0, st.boundSsboByIndex[SSBO_INDEX]);
assertEquals(0, st.boundSSBO);
assertEquals(0, st.boundIndirectBuffer);
assertEquals(0, st.currentVao.elementBuffer);
assertEquals(0, st.currentVao.bindingBuffer[VERTEX_BINDING]);
assertEquals(0, st.currentVao.attribVBO[ATTRIB]);
assertEquals(0, st.boundArrayBuffer);
assertEquals(0, st.boundDispatchIndirectBuffer);
assertEquals(0, st.boundCopyReadBuffer);
assertEquals(0, st.boundCopyWriteBuffer);
assertEquals(0, st.boundPixelPackBuffer);
assertEquals(0, st.boundPixelUnpackBuffer);
}
private void assertAllBound() {
assertEquals(BUFFER, st.boundUboByIndex[UBO_INDEX]);
assertEquals(16, st.uboRangeOffset[UBO_INDEX]);
assertEquals(64, st.uboRangeSize[UBO_INDEX]);
assertEquals(BUFFER, st.boundUniformBuffer);
assertEquals(BUFFER, st.boundSsboByIndex[SSBO_INDEX]);
assertEquals(BUFFER, st.boundSSBO);
assertEquals(BUFFER, st.boundIndirectBuffer);
assertEquals(BUFFER, st.currentVao.elementBuffer);
assertEquals(BUFFER, st.currentVao.bindingBuffer[VERTEX_BINDING]);
assertEquals(BUFFER, st.currentVao.attribVBO[ATTRIB]);
assertEquals(BUFFER, st.boundArrayBuffer);
assertEquals(BUFFER, st.boundDispatchIndirectBuffer);
assertEquals(BUFFER, st.boundCopyReadBuffer);
assertEquals(BUFFER, st.boundCopyWriteBuffer);
assertEquals(BUFFER, st.boundPixelPackBuffer);
assertEquals(BUFFER, st.boundPixelUnpackBuffer);
}
@Test
void deleteClearsEveryBindingOfTheDeletedBuffer() {
assertAllBound();
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
backend.deleteBuffers(BUFFER);
assertAllCleared();
assertEquals(8L, st.currentVao.bindingOffset[VERTEX_BINDING]);
assertEquals(12, st.currentVao.bindingStride[VERTEX_BINDING]);
assertNotEquals(uboGen, st.uboRangeGen);
assertNotEquals(ssboGen, st.ssboBindGen);
assertNotEquals(attribGen, st.attribStateGen);
}
@Test
void intBufferDeleteClearsEveryBindingOfTheDeletedBuffer() {
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
final IntBuffer ids = MemoryUtil.memAllocInt(2);
try {
ids.put(0, OTHER).put(1, BUFFER);
backend.deleteBuffers(ids);
} finally {
MemoryUtil.memFree(ids);
}
assertAllCleared();
assertNotEquals(uboGen, st.uboRangeGen);
assertNotEquals(ssboGen, st.ssboBindGen);
assertNotEquals(attribGen, st.attribStateGen);
}
@Test
void deletingAnUnrelatedBufferChangesNothing() {
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
backend.deleteBuffers(OTHER);
backend.deleteBuffers(0);
assertAllBound();
assertEquals(uboGen, st.uboRangeGen);
assertEquals(ssboGen, st.ssboBindGen);
assertEquals(attribGen, st.attribStateGen);
}
}
@@ -0,0 +1,83 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
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.opengl.GL30;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
class DrawBuffersLimitTest {
private static final int BOUND_FBO = 901;
private static final int UNBOUND_FBO = 902;
private static SDLGPURenderBackend backend;
private static ResourceManager resourceManager;
private ContextState st;
private int savedFbo;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
resourceManager = Reflect.get(backend, "resourceManager");
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
savedFbo = st.boundFboId;
st.boundFboId = BOUND_FBO;
}
@AfterEach
void tearDown() {
st.boundFboId = savedFbo;
resourceManager.deleteFbo(BOUND_FBO);
resourceManager.deleteFbo(UNBOUND_FBO);
}
private static IntBuffer attachments(int count) {
final int[] bufs = new int[count];
for (int i = 0; i < count; i++) bufs[i] = GL30.GL_COLOR_ATTACHMENT0 + i;
return IntBuffer.wrap(bufs);
}
@Test
void namedDrawBuffersOverTheLimitAreIgnored() {
final FboState fbo = resourceManager.createFbo(UNBOUND_FBO);
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(2));
final int[] before = fbo.drawBuffers;
assertArrayEquals(new int[]{0, 1}, before, "a legal count must apply, or the refusal proves nothing");
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(ContextState.MAX_COLOR_ATTACHMENTS + 1));
assertSame(before, fbo.drawBuffers, "a count above MAX_DRAW_BUFFERS must leave the draw buffers untouched");
assertArrayEquals(new int[]{0, 1}, fbo.drawBuffers);
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(ContextState.MAX_COLOR_ATTACHMENTS));
assertEquals(ContextState.MAX_COLOR_ATTACHMENTS, fbo.drawBuffers.length, "exactly the limit is legal");
}
@Test
void boundDrawBuffersOverTheLimitAreIgnored() {
final FboState fbo = resourceManager.createFbo(BOUND_FBO);
assertSame(fbo, resourceManager.getFbo(st.boundFboId));
final int[] before = fbo.drawBuffers;
backend.drawBuffers(attachments(ContextState.MAX_COLOR_ATTACHMENTS + 1));
assertSame(before, fbo.drawBuffers, "a count above MAX_DRAW_BUFFERS must leave the draw buffers untouched");
assertArrayEquals(new int[]{0}, fbo.drawBuffers);
assertNull(fbo.cachedColorFormats, "an ignored call must not reach the pipeline color formats");
}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.sun.management.ThreadMXBean;
import java.lang.management.ManagementFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public final class SdlAsserts {
private static final int WARMUP_ROUNDS = 50_000;
private static final int MEASURED_ROUNDS = 100_000;
private static final long ALLOCATION_BUDGET_BYTES = 256L * 1024L;
private SdlAsserts() {}
public static void assertBlendFactors(PipelineCache p, int drawBuffer, int sc, int dc, int sa, int da, String when) {
assertEquals(sc, p.srcColorFactors[drawBuffer], when + ": srcColor of draw buffer " + drawBuffer);
assertEquals(dc, p.dstColorFactors[drawBuffer], when + ": dstColor of draw buffer " + drawBuffer);
assertEquals(sa, p.srcAlphaFactors[drawBuffer], when + ": srcAlpha of draw buffer " + drawBuffer);
assertEquals(da, p.dstAlphaFactors[drawBuffer], when + ": dstAlpha of draw buffer " + drawBuffer);
}
public static void assertAllocationFree(Runnable round, String what) {
assumeTrue(ManagementFactory.getThreadMXBean() instanceof ThreadMXBean);
final ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
assumeTrue(bean.isThreadAllocatedMemorySupported() && bean.isThreadAllocatedMemoryEnabled());
for (int i = 0; i < WARMUP_ROUNDS; i++) round.run();
final long before = bean.getCurrentThreadAllocatedBytes();
for (int i = 0; i < MEASURED_ROUNDS; i++) round.run();
final long allocated = bean.getCurrentThreadAllocatedBytes() - before;
assertTrue(allocated < ALLOCATION_BUDGET_BYTES, allocated + " bytes allocated across " + MEASURED_ROUNDS + " rounds of " + what);
}
}
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
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.system.MemoryUtil;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UniformStoreTest {
private static final int VEC4 = 0;
private static final int OUT_OF_RANGE = 7;
private static final float[] VALUES = {5f, 6f, 7f, 8f};
private static SDLGPURenderBackend backend;
private ContextState st;
private ShaderManager.ProgramObject prog;
private ShaderManager.ProgramObject savedProgramObj;
private int savedProgram;
private IntBuffer ints;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
savedProgramObj = st.boundProgramObj;
savedProgram = st.boundProgram;
prog = new ShaderManager.ProgramObject();
prog.linked = true;
prog.vertexUboSize = 16;
prog.nextUniformLocation = 1;
prog.vertexMemberInfo.put(VEC4, new ShaderManager.UniformMemberInfo(0, 16, 0, true, 4, 1, 0, 1));
prog.buildUniformSlotArrays();
st.boundProgramObj = prog;
st.boundProgram = 1;
ints = MemoryUtil.memAllocInt(4);
ints.put(0, 1).put(1, 2).put(2, 3).put(3, 4);
}
@AfterEach
void tearDown() {
MemoryUtil.memFree(ints);
st.releaseUniformStaging(prog);
st.boundProgramObj = savedProgramObj;
st.boundProgram = savedProgram;
}
private void writeAll(int location) {
backend.uniform4f(location, 1f, 2f, 3f, 4f);
backend.uniform4fv(location, VALUES);
backend.uniform4i(location, 1, 2, 3, 4);
backend.uniform4iv(location, ints);
}
private void dropRound() {
st.boundProgramObj = null;
writeAll(VEC4);
st.boundProgramObj = prog;
writeAll(OUT_OF_RANGE);
}
private void assertNothingStaged() {
final UniformStaging us = st.uniformStaging(prog);
assertNull(us.uniformDataBySlot[VEC4]);
assertEquals(0L, us.uniformValueHashBySlot[VEC4]);
assertFalse(us.vsUniformDirty);
}
private float[] staged(int location) {
return st.uniformStaging(prog).uniformDataBySlot[location];
}
@Test
void noProgramBoundStoresNothing() {
st.boundProgramObj = null;
assertDoesNotThrow(() -> writeAll(VEC4));
st.boundProgramObj = prog;
assertNothingStaged();
}
@Test
void outOfRangeLocationStoresNothing() {
assertEquals(1, prog.uniformSlotCount);
assertDoesNotThrow(() -> writeAll(OUT_OF_RANGE));
assertNothingStaged();
}
@Test
void droppedStoresDoNotAllocate() {
SdlAsserts.assertAllocationFree(this::dropRound, "dropped uniform stores");
assertNothingStaged();
}
@Test
void boundProgramWriteStillStores() {
backend.uniform4f(VEC4, 1f, 2f, 3f, 4f);
assertArrayEquals(new float[]{1f, 2f, 3f, 4f}, staged(VEC4));
backend.uniform4fv(VEC4, VALUES);
assertArrayEquals(VALUES, staged(VEC4));
backend.uniform4iv(VEC4, ints);
final float[] stagedValues = staged(VEC4);
for (int i = 0; i < 4; i++) {
assertEquals(i + 1, Float.floatToRawIntBits(stagedValues[i]));
}
assertTrue(st.uniformStaging(prog).vsUniformDirty);
}
@Test
void vectorWriteCopiesTheCallerArrayAndReusesTheSlot() {
final float[] first = {5f, 6f, 7f, 8f};
backend.uniform4fv(VEC4, first);
final float[] slot = staged(VEC4);
assertNotSame(first, slot, "the staging must not hold the caller's array");
assertArrayEquals(new float[]{5f, 6f, 7f, 8f}, slot);
final float[] second = {9f, 10f, 11f, 12f};
backend.uniform4fv(VEC4, second);
assertSame(slot, staged(VEC4), "same-length writes must reuse the slot array");
assertNotSame(second, staged(VEC4));
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, slot);
final float[] equalValues = second.clone();
backend.uniform4fv(VEC4, equalValues);
assertSame(slot, staged(VEC4), "an equal-valued caller array must not replace the slot array");
backend.uniform4f(VEC4, 1f, 2f, 3f, 4f);
assertArrayEquals(new float[]{5f, 6f, 7f, 8f}, first);
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, second);
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, equalValues);
assertArrayEquals(new float[]{1f, 2f, 3f, 4f}, slot);
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlDegenerateDrawTest {
private static final int RED = 0xFFFF0000;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void zeroCountDrawArraysLeavesTheFollowingDrawIntact() {
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 0);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), RED, "after a zero-count drawArrays");
}
@Test
void negativeCountDrawArraysLeavesTheFollowingDrawIntact() {
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, -3);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), RED, "after a negative-count drawArrays");
}
}
@@ -62,6 +62,13 @@ class GlsmSdlFbo0ViewportTest {
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
private static void bindFbo0WithFullViewport() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, FBO0_W, FBO0_H);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
private static int createCopyTarget() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
@@ -88,6 +95,33 @@ class GlsmSdlFbo0ViewportTest {
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
}
@Test
void scissorLargerThanFbo0StillCoversTheWholeTarget() {
bindFbo0WithFullViewport();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
try {
GLStateManager.glScissor(0, 0, FBO0_W + 16, FBO0_H + 16);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W, FBO0_H), RED, "inside an oversize scissor");
} finally {
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
}
@Test
void scissorRunningPastTheRightEdgeClipsToTheTarget() {
bindFbo0WithFullViewport();
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
try {
GLStateManager.glScissor(FBO0_W - 8, 0, 32, FBO0_H);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(FBO0_W - 8, 0, 8, FBO0_H), RED, "inside the clipped scissor");
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W - 8, FBO0_H), BLUE, "left of the scissor");
} finally {
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
}
@Test
void copyTexSubImage2DFromAPartialViewportKeepsGlOrientation() {
bindFbo0WithSmallViewport();
@@ -1,6 +1,11 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ParticleParityFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -40,6 +45,14 @@ class GlsmSdlFfpTest {
GlsmSdlHeadlessRig.endFrame();
}
@AfterEach
void cleanupInstancing() {
GLStateManager.ffpInstancing = Instancing.NONE;
CubeParityFixture.deleteResources();
ParticleParityFixture.deleteResources();
FfpFixture.IntegerInstances.delete();
}
private static int createLightmapTexture() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -146,9 +159,7 @@ class GlsmSdlFfpTest {
GLStateManager.glCallList(list);
assertTexelEverywhere("two units, glCallList");
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.beginFrameAndClear();
final IntBuffer lists = BufferUtils.createIntBuffer(1);
lists.put(0, list);
@@ -172,6 +183,54 @@ class GlsmSdlFfpTest {
assertTrue(red < ((TEXEL >> 16) & 0xFF), () -> "lit quad is not attenuated: " + describe(center));
}
@Test
void unitCubeMatchesBakedQuadsWithLightmap() {
CubeParityFixture.setupScene(true, false);
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
CubeParityFixture.drawReference(specs, true);
final int[] reference = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.beginFrameAndClear();
CubeParityFixture.drawInstanced(specs);
final int[] instanced = GlsmSdlHeadlessRig.readTarget();
CubeParityFixture.assertPixelParity(reference, instanced, SIZE, 0xFF000000, GlsmSdlHeadlessRig::describe);
}
@Test
void particleFirstPersonRotationMatchesReference() {
final ParticleParityFixture.Rotation rot = ParticleParityFixture.Rotation.of(35f, -12f, false);
ParticleParityFixture.setupScene(false);
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
ParticleParityFixture.drawReference(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
final int[] reference = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.beginFrameAndClear();
ParticleParityFixture.drawInstanced(particles, rot.x(), rot.xz(), rot.z(), rot.yz(), rot.xy());
final int[] instanced = GlsmSdlHeadlessRig.readTarget();
ParticleParityFixture.assertPixelParity(reference, instanced, SIZE, 0xFF000000, GlsmSdlHeadlessRig::describe);
}
@Test
void entityAttribSelectsColorPerInstance() {
FfpFixture.IntegerInstances.build(0.6f);
GlsmSdlHeadlessRig.beginFrameAndClear();
FfpFixture.IntegerInstances.draw();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 13, SIZE / 2, 0xFFFF0000, "entity -1 must render red");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 32, SIZE / 2, 0xFF00FF00, "entity 7 must render green");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 42 must render blue");
}
private static void enableGuiStandardItemLighting() {
GLStateManager.glPushMatrix();
GLStateManager.glRotatef(-30.0f, 0.0f, 1.0f, 0.0f);
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -85,6 +86,12 @@ public final class GlsmSdlHeadlessRig {
endFrame();
}
public static int glsmBufferBinding(int target) {
final Object ctx = Reflect.getStatic(GLStateManager.class, "primaryContext");
final Int2IntMap bound = Reflect.get(ctx, "boundOtherBuffers");
return bound.get(target);
}
private static int colorTargetSdlFormat() {
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
@@ -115,6 +122,12 @@ public final class GlsmSdlHeadlessRig {
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
public static void beginFrameAndClear() {
beginFrame();
bindTarget();
clearTo(0.0f, 0.0f, 0.0f, 1.0f);
}
public static void beginFrameAndReset() {
beginFrame();
resetState();
@@ -0,0 +1,59 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_SRC_ALPHA;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlIndexedBlendRestoreTest {
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static PipelineCache pipeline() {
final ContextState st = Reflect.invokeStatic(SDLGPURenderBackend.class, "s", new Class<?>[0]);
return st.pipeline;
}
private static void vanillaBlendFunc() {
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
@Test
void vanillaBlendFuncAfterIndexedOverrideReachesEveryDrawBuffer() {
try {
vanillaBlendFunc();
RenderSystem.blendFuncSeparatei(1, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
final PipelineCache p = pipeline();
SdlAsserts.assertBlendFactors(p, 1, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, "override");
SdlAsserts.assertBlendFactors(p, 0, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "override");
vanillaBlendFunc();
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(p, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "restore");
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "restore forward must clear the unknown flag");
} finally {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
}
@@ -0,0 +1,106 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlLWJGLServiceRoutingTest {
private static final int UBO_INDEX = 3;
private static SDLGPULWJGLService svc;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
svc = SDLGPULWJGLService.create();
}
private static Object glsmContext() {
return Reflect.getStatic(GLStateManager.class, "primaryContext");
}
private static int glsmUnpackAlignment() {
final PixelUnpackState state = Reflect.get(glsmContext(), "pixelUnpackState");
return state.alignment();
}
@Test
void deleteBuffersClearsTheGlsmArrayBufferBinding() {
final int buffer = svc.glGenBuffers();
svc.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
svc.glBufferData(GL15.GL_ARRAY_BUFFER, 64L, GL15.GL_STATIC_DRAW);
assertEquals(buffer, GLStateManager.getBoundVBO());
svc.glDeleteBuffers(buffer);
assertEquals(0, GLStateManager.getBoundVBO());
}
@Test
void bindBufferBaseUpdatesTheGlsmGenericBinding() {
final int buffer = svc.glGenBuffers();
svc.glBindBuffer(GL31.GL_UNIFORM_BUFFER, buffer);
svc.glBufferData(GL31.GL_UNIFORM_BUFFER, 64L, GL15.GL_DYNAMIC_DRAW);
svc.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
assertEquals(0, GlsmSdlHeadlessRig.glsmBufferBinding(GL31.GL_UNIFORM_BUFFER));
try {
svc.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, buffer);
assertEquals(buffer, GlsmSdlHeadlessRig.glsmBufferBinding(GL31.GL_UNIFORM_BUFFER));
} finally {
svc.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, 0);
svc.glDeleteBuffers(buffer);
}
}
@Test
void clearColorUpdatesTheGlsmCache() {
svc.glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
assertEquals(0.25f, GLStateManager.getClearColor().getRed());
assertEquals(0.5f, GLStateManager.getClearColor().getGreen());
assertEquals(0.75f, GLStateManager.getClearColor().getBlue());
}
@Test
void pixelStoreiUpdatesTheGlsmUnpackState() {
assertNotEquals(1, glsmUnpackAlignment());
try {
svc.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
assertEquals(1, glsmUnpackAlignment());
} finally {
svc.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
}
assertEquals(4, glsmUnpackAlignment());
}
@Test
void deleteFramebuffersClearsTheGlsmDrawFramebuffer() {
final int framebuffer = svc.glGenFramebuffers();
try {
svc.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
assertEquals(framebuffer, GLStateManager.getDrawFramebuffer());
svc.glDeleteFramebuffers(framebuffer);
assertEquals(0, GLStateManager.getDrawFramebuffer());
} finally {
GlsmSdlHeadlessRig.bindTarget();
}
}
}
@@ -0,0 +1,84 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlReadbackTest {
private static final int TEX_SIZE = 8;
private static final int R = 0x12;
private static final int G = 0x34;
private static final int B = 0x56;
private static final int A = 0xFF;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void getTexImageSeesDeferredTextureUpload() {
final int texture = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
final ByteBuffer src = solidTexture(TEX_SIZE, TEX_SIZE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_SIZE, TEX_SIZE, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
final ByteBuffer dst = BufferUtils.createByteBuffer(TEX_SIZE * TEX_SIZE * 4);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, dst);
assertPixels(dst, TEX_SIZE * TEX_SIZE);
GLStateManager.glDeleteTextures(texture);
}
@Test
void readPixelsSeesPriorDraw() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GLStateManager.glReadPixels(
GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
assertEquals(0xFF, pixel.get(0) & 0xFF);
assertEquals(0x00, pixel.get(1) & 0xFF);
assertEquals(0x00, pixel.get(2) & 0xFF);
assertEquals(0xFF, pixel.get(3) & 0xFF);
}
private static ByteBuffer solidTexture(int width, int height) {
final ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4);
for (int i = 0; i < width * height; i++) {
pixels.put((byte) R);
pixels.put((byte) G);
pixels.put((byte) B);
pixels.put((byte) A);
}
return pixels.flip();
}
private static void assertPixels(ByteBuffer pixels, int count) {
for (int i = 0; i < count; i++) {
assertEquals(R, pixels.get(i * 4) & 0xFF, "red at pixel " + i);
assertEquals(G, pixels.get(i * 4 + 1) & 0xFF, "green at pixel " + i);
assertEquals(B, pixels.get(i * 4 + 2) & 0xFF, "blue at pixel " + i);
assertEquals(A, pixels.get(i * 4 + 3) & 0xFF, "alpha at pixel " + i);
}
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlSamplerBindingTest {
private static final int UNIT = 3;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void glsmDeleteClearsBothCaches() {
final ContextState st = SdlTestRig.contextState();
final int sampler = GLStateManager.glGenSamplers();
GLStateManager.glBindSampler(UNIT, sampler);
assertEquals(sampler, st.boundSamplerObjects[UNIT], "SDL unit binding");
assertEquals(sampler, GLStateManager.getSamplerBinding(UNIT), "GLSM unit binding");
GLStateManager.glDeleteSamplers(sampler);
assertEquals(0, st.boundSamplerObjects[UNIT], "SDL must drop the deleted sampler");
assertEquals(0, GLStateManager.getSamplerBinding(UNIT), "GLSM must drop the deleted sampler");
}
@Test
void renderSystemRebindAfterGlsmUnbindReachesSdl() {
assertTrue(RenderSystem.supportsSamplerObjects(), "RenderSystem sampler calls would no-op and prove nothing");
final ContextState st = SdlTestRig.contextState();
final int sampler = RenderSystem.genSampler();
try {
RenderSystem.bindSamplerToUnit(UNIT, sampler);
GLStateManager.glBindSampler(UNIT, 0);
assertEquals(0, st.boundSamplerObjects[UNIT], "setup: GLSM unbind must reach SDL");
RenderSystem.bindSamplerToUnit(UNIT, sampler);
assertEquals(sampler, st.boundSamplerObjects[UNIT], "RenderSystem skipped a bind SDL no longer holds");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void unitCountMatchesTheSdlSamplerArray() {
GLStateManager.glBindSampler(0, 0);
assertEquals(SdlTestRig.contextState().boundSamplerObjects.length, GLStateManager.getSamplerUnitCount());
}
}
@@ -0,0 +1,48 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL15;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
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.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlStreamingRingTest {
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void ringCreationLeavesTheGlsmArrayBufferCacheMatchingSdl() {
assertTrue(RenderSystem.supportsBufferStorage(), "SDL-GPU must advertise buffer storage");
final int other = GLStateManager.glGenBuffers();
PersistentStreamingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
ring = PersistentStreamingBuffer.createOrNull(4096);
assertNotNull(ring);
final IntSet persistent = Reflect.getStatic(FfpExtendedAttribs.class, "persistentSources");
assertTrue(persistent.contains(ring.getBufferId()), "the ring buffer is the persistent source");
assertFalse(persistent.contains(other), "the previously bound buffer is not persistent");
assertEquals(RENDER_BACKEND.getInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "SDL ContextState.boundArrayBuffer must equal the GLSM cache after ring creation");
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
}
@@ -0,0 +1,137 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlUnalignedAttribTest {
private static final int STRIDE = 24;
private static final int FACTOR = 204;
private static final int DECOY = 64;
private static final String VERT = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 5) in float a_Factor;
out float v_Factor;
void main() {
gl_Position = vec4(a_Position, 1.0);
v_Factor = a_Factor;
}
""";
private static final String FRAG = """
#version 330 core
in float v_Factor;
out vec4 fragColor;
void main() {
fragColor = vec4(v_Factor, v_Factor, v_Factor, 1.0);
}
""";
private static int program;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static int buildProgram() {
if (program != 0) return program;
final int vs = GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER);
GLStateManager.glShaderSource(vs, VERT);
GLStateManager.glCompileShader(vs);
assertTrue(GLStateManager.glGetShaderi(vs, GL20.GL_COMPILE_STATUS) != 0,
() -> "vertex shader: " + GLStateManager.glGetShaderInfoLog(vs));
final int fs = GLStateManager.glCreateShader(GL20.GL_FRAGMENT_SHADER);
GLStateManager.glShaderSource(fs, FRAG);
GLStateManager.glCompileShader(fs);
assertTrue(GLStateManager.glGetShaderi(fs, GL20.GL_COMPILE_STATUS) != 0,
() -> "fragment shader: " + GLStateManager.glGetShaderInfoLog(fs));
final int p = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(p, vs);
GLStateManager.glAttachShader(p, fs);
GLStateManager.glLinkProgram(p);
assertTrue(GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS) != 0, "program did not link");
program = p;
return p;
}
private static int redChannelForFactorAt(int factorOffset, int decoyOffset) {
final int p = buildProgram();
final int vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final int vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final ByteBuffer data = MemoryUtil.memAlloc(3 * STRIDE);
try {
for (int i = 0; i < 3 * STRIDE; i++) data.put(i, (byte) 0);
final float[][] corners = {{-1.0f, -1.0f}, {3.0f, -1.0f}, {-1.0f, 3.0f}};
for (int v = 0; v < 3; v++) {
final int base = v * STRIDE;
data.putFloat(base, corners[v][0]);
data.putFloat(base + 4, corners[v][1]);
data.putFloat(base + 8, 0.0f);
if (decoyOffset >= 0) data.put(base + decoyOffset, (byte) DECOY);
data.put(base + factorOffset, (byte) FACTOR);
}
data.position(0).limit(3 * STRIDE);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
} finally {
MemoryUtil.memFree(data);
}
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
GLStateManager.glEnableVertexAttribArray(5);
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_UNSIGNED_BYTE, true, STRIDE, factorOffset);
GLStateManager.glUseProgram(p);
try {
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
} finally {
GLStateManager.glUseProgram(0);
}
final int[] pixels = GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return (pixels[0] >> 16) & 0xFF;
}
@Test
void byteAttribAtAnAlignedOffsetReadsItsOwnByte() {
assertEquals(FACTOR, redChannelForFactorAt(20, -1), 1);
}
@Test
void byteAttribAtAnUnalignedOffsetReadsItsOwnByte() {
assertEquals(FACTOR, redChannelForFactorAt(23, 20), 1);
}
@Test
void byteAttribAtEveryMisalignmentReadsItsOwnByte() {
for (int offset = 20; offset <= 23; offset++) {
final int decoy = offset == 20 ? -1 : 20;
final int got = redChannelForFactorAt(offset, decoy);
assertEquals(FACTOR, got, 1, "factor byte at offset " + offset + " read back as " + got);
}
}
}
@@ -0,0 +1,91 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL43;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlVoxelRegionBindTest {
private static final int BOUND_SLOT = 5;
private static final int EMPTY_SLOT = 6;
private static final int UNLINKED_PROGRAM = Integer.MAX_VALUE;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static int glsmGenericSsbo() {
return GlsmSdlHeadlessRig.glsmBufferBinding(GL43.GL_SHADER_STORAGE_BUFFER);
}
private static void unbind(int slot) {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, slot, 0);
}
@Test
void glsmBindBufferBaseReachesBothCaches() {
final ContextState st = SdlTestRig.contextState();
final int buf = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT], "SDL indexed SSBO slot");
assertEquals(buf, glsmGenericSsbo(), "GLSM generic SSBO binding");
} finally {
unbind(BOUND_SLOT);
GLStateManager.glDeleteBuffers(buf);
}
}
@Test
void regionHookRefusesWithoutProgramOrBoundSlotAndNeverRebinds() {
final ContextState st = SdlTestRig.contextState();
final int savedProgram = st.boundProgram;
final int buf = GLStateManager.glGenBuffers();
try {
unbind(EMPTY_SLOT);
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
st.boundProgram = 0;
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 0f, 0f, 0f), "no program bound");
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT], "refusal must leave the slot alone");
st.boundProgram = UNLINKED_PROGRAM;
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(EMPTY_SLOT, 0L, 0f, 0f, 0f), "slot has no buffer");
assertEquals(0, st.boundSsboByIndex[EMPTY_SLOT], "the hook must not bind a buffer itself");
assertTrue(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 1f, 2f, 3f), "a GLSM-bound slot with a program must be accepted, or the refusals prove nothing");
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT]);
assertEquals(buf, glsmGenericSsbo(), "GLSM and SDL must still agree after the hook");
} finally {
st.boundProgram = savedProgram;
unbind(BOUND_SLOT);
GLStateManager.glDeleteBuffers(buf);
}
}
@Test
void deletingTheRegionBufferClearsTheGlsmGenericBinding() {
final int buf = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
assertEquals(buf, glsmGenericSsbo());
GLStateManager.glDeleteBuffers(buf);
assertEquals(0, glsmGenericSsbo(), "delete must see the tracked SSBO binding");
} finally {
unbind(BOUND_SLOT);
}
}
}
@@ -0,0 +1,79 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ZERO;
class IndexedBlendStateTest {
private SDLGPURenderBackend backend;
private ContextState st;
@BeforeEach
void resetBlend() {
backend = new SDLGPURenderBackend();
st = SdlTestRig.contextState();
backend.blendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
backend.disable(GL11.GL_BLEND);
}
@AfterEach
void restoreBlend() {
backend.blendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
backend.disable(GL11.GL_BLEND);
}
@Test
void indexedBlendFuncChangesOnlyThatDrawBuffer() {
backend.blendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (i == 1) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, "indexed override");
} else {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "indexed override");
}
}
}
@Test
void globalBlendFuncSetsEveryDrawBuffer() {
backend.blendFuncSeparatei(2, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
backend.blendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "global set");
}
}
@Test
void indexedEnableSetsOnlyThatDrawBuffer() {
backend.enablei(GL11.GL_BLEND, 2);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
assertEquals(i == 2, st.pipeline.blendEnabledPerDrawBuffer[i], "blend enable of draw buffer " + i);
}
}
@Test
void outOfRangeIndexedBlendFuncIsIgnored() {
final int pastEnd = ContextState.MAX_COLOR_ATTACHMENTS;
backend.blendFuncSeparatei(pastEnd, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
backend.blendFuncSeparatei(-1, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "out of range ignored");
}
}
}
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
import static org.lwjgl.sdl.SDLGPU.*;
@@ -45,7 +46,7 @@ class PipelineCacheKeyTest {
void testBlendChangeProducesDifferentKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.blendEnabledPerAttachment[0] = true;
b.blendEnabledPerDrawBuffer[0] = true;
assertNotEquals(computeKey(a), computeKey(b), "Blend state change should produce different key");
}
@@ -105,12 +106,75 @@ class PipelineCacheKeyTest {
void testBlendFactorChangeProducesDifferentKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.srcColorFactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
b.dstColorFactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
b.setBlendFactors(SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertNotEquals(computeKey(a), computeKey(b), "Blend factor change should produce different key");
}
@Test
void perDrawBufferFactorOnLiveSlotChangesKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(0, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertNotEquals(computeKey(a), computeKey(b), "factor on the only color target must change the key");
}
@Test
void perDrawBufferFactorOnUnusedSlotKeepsKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(1, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertEquals(computeKey(a), computeKey(b), "factor on a draw buffer with no color target must keep the key");
}
@Test
void globalFactorsAfterIndexedSetMatchFreshCache() {
final PipelineCache fresh = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(0, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE);
b.setBlendFactors(3, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE);
b.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
final int[] fourTargets = new int[4];
Arrays.fill(fourTargets, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM);
fresh.setColorTargetFormats(fourTargets);
b.setColorTargetFormats(fourTargets);
assertEquals(computeKey(fresh), computeKey(b), "a global blend func must overwrite every indexed entry");
}
@Test
void blendFactorSettersReportNoChangeForIdenticalValues() {
final PipelineCache c = createCache();
assertFalse(c.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertFalse(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertTrue(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertFalse(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertTrue(c.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
}
@Test
void blendEnableIsIndexedByDrawBufferNotAttachment() {
final int[] twoTargets = {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT};
final PipelineCache base = createCache();
base.setColorTargetFormats(twoTargets);
base.setDrawBuffers(new int[]{3, 5});
final PipelineCache slot0 = createCache();
slot0.setColorTargetFormats(twoTargets);
slot0.setDrawBuffers(new int[]{3, 5});
slot0.blendEnabledPerDrawBuffer[0] = true;
final PipelineCache attachment3 = createCache();
attachment3.setColorTargetFormats(twoTargets);
attachment3.setDrawBuffers(new int[]{3, 5});
attachment3.blendEnabledPerDrawBuffer[3] = true;
assertNotEquals(computeKey(base), computeKey(slot0), "draw buffer 0 is the first color target");
assertEquals(computeKey(base), computeKey(attachment3), "index 3 is a draw buffer past the two targets");
}
@Test
void testColorWriteMaskChangeProducesDifferentKey() {
final PipelineCache a = createCache();
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -454,6 +455,47 @@ class PipelineCacheVertexInputTest {
assertFalse(c.markInputDirtyIfLivenessChanged(store, 42, 7), "swapping two live buffers must not force an input rehash");
}
@Test
void programInputSignatureChangeInvalidatesInputHash() {
final int[] vecSizesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
vecSizesA[0] = 3;
final int[] baseTypesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
baseTypesA[0] = Spvc.SPVC_BASETYPE_FP32;
final String[] namesA = new String[ContextState.MAX_VERTEX_ATTRIBS];
namesA[0] = "a_Pos";
final PipelineCache c = cache(0x1, vecSizesA, baseTypesA);
c.shaderInputName = namesA;
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
final long keyA = Reflect.invoke(c, "currentKey", new Class<?>[] { PipelineStore.class, ContextState.class }, store, cs);
final boolean dirtyAfterKeyA = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterKeyA, "currentKey must clear the dirty flag once recomputed");
c.setVertexInputs(0x1, vecSizesA, baseTypesA, namesA);
final boolean dirtyAfterSameSignature = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterSameSignature, "an identical signature must not force a rehash");
c.setVertexInputs(0x1, vecSizesA.clone(), baseTypesA.clone(), namesA.clone());
final boolean dirtyAfterEqualCopies = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterEqualCopies, "another program with an equal signature must not force a rehash");
final int[] vecSizesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
vecSizesB[0] = 3; vecSizesB[1] = 4;
final int[] baseTypesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
baseTypesB[0] = Spvc.SPVC_BASETYPE_FP32; baseTypesB[1] = Spvc.SPVC_BASETYPE_FP32;
final String[] namesB = new String[ContextState.MAX_VERTEX_ATTRIBS];
namesB[0] = "a_Pos"; namesB[1] = "a_Extra";
c.setVertexInputs(0x3, vecSizesB, baseTypesB, namesB);
final boolean dirtyAfterChangedSignature = Reflect.get(c, "inputDirty");
assertTrue(dirtyAfterChangedSignature, "a changed vertex-input signature must invalidate the cached input hash");
final long keyB = Reflect.invoke(c, "currentKey", new Class<?>[] { PipelineStore.class, ContextState.class }, store, cs);
assertTrue(keyA != keyB, "currentKey must reflect the new signature, not a stale cached input hash");
}
@Test
void bindingsArrayIsDenseIndexedBySlot() {
// D3D12 requirement: SDL's D3D12 backend reads vertex_buffer_descriptions[buffer_slot] as a flat array, so bindings.get(i).slot() must equal i
@@ -0,0 +1,68 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ScissorClampTest {
private final int[] out = new int[4];
@Test
void rectInsideTheTargetIsUnchanged() {
assertTrue(ScissorClamp.clamp(4, 8, 16, 32, 64, 96, out));
assertArrayEquals(new int[]{4, 8, 16, 32}, out);
}
@Test
void overhangPastTheRightAndBottomEdgesShrinks() {
assertTrue(ScissorClamp.clamp(0, 0, 1710, 960, 1708, 960, out));
assertArrayEquals(new int[]{0, 0, 1708, 960}, out);
assertTrue(ScissorClamp.clamp(60, 90, 16, 32, 64, 96, out));
assertArrayEquals(new int[]{60, 90, 4, 6}, out);
}
@Test
void negativeOriginShrinksTheRectInsteadOfShiftingIt() {
assertTrue(ScissorClamp.clamp(-6, -10, 16, 32, 64, 96, out));
assertArrayEquals(new int[]{0, 0, 10, 22}, out);
}
@Test
void rectStraddlingBothEdgesIsClampedToTheWholeTarget() {
assertTrue(ScissorClamp.clamp(-8, -8, 128, 128, 64, 96, out));
assertArrayEquals(new int[]{0, 0, 64, 96}, out);
}
@Test
void rectFullyOutsideTheTargetIsEmpty() {
assertFalse(ScissorClamp.clamp(64, 0, 16, 32, 64, 96, out));
assertArrayEquals(new int[]{64, 0, 0, 32}, out);
assertFalse(ScissorClamp.clamp(-40, 0, 16, 32, 64, 96, out));
assertArrayEquals(new int[]{0, 0, 0, 32}, out);
}
@Test
void degenerateSourceRectIsEmpty() {
assertFalse(ScissorClamp.clamp(0, 0, 0, 32, 64, 96, out));
assertFalse(ScissorClamp.clamp(0, 0, 16, -4, 64, 96, out));
}
@Test
void unknownTargetSizeLeavesTheRectAlone() {
assertTrue(ScissorClamp.clamp(-6, 4, 1710, 32, 0, 0, out));
assertArrayEquals(new int[]{-6, 4, 1710, 32}, out);
assertFalse(ScissorClamp.clamp(0, 0, 0, 32, 0, 96, out));
}
@Test
void largeRectDoesNotOverflow() {
assertTrue(ScissorClamp.clamp(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE, 64, 96, out));
assertArrayEquals(new int[]{0, 0, 64, 96}, out);
}
}
@@ -118,6 +118,8 @@ class CopyTexSubImageFromFbo0GpuTest {
}
private static ByteBuffer download(int destGlId) {
frameManager.submitMidFrame();
final ByteBuffer out = MemoryUtil.memAlloc(SIZE * SIZE * 4);
textureOps.readbackTexture(resourceManager.getTextureHandle(destGlId), 0, 0, SIZE, SIZE, 0, out);
out.rewind();
@@ -1,16 +1,22 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ReadbackShadowsTest {
private static final long SENTINEL_HANDLE = 0xBEEFL;
private static final int SLOTS_PER_BUFFER = 32;
private static final int SLOT_STRIDE = 64;
private static ReadbackShadows shadows() {
return new ReadbackShadows(null);
@@ -62,4 +68,39 @@ class ReadbackShadowsTest {
assertFalse(shadows.hasSlotsFor(7), "deleting a buffer must drop all of its ring slots");
assertTrue(shadows.hasSlotsFor(8), "deleting one buffer must not drop another's slots");
}
private static int slotCount(ReadbackShadows shadows) {
return Reflect.<Map<Long, Object>>get(shadows, "slots").size();
}
private static ReadbackShadows ringShadows(int... glIds) {
final ReadbackShadows shadows = shadows();
for (int glId : glIds) {
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
SdlReflect.putReadbackSlot(shadows, glId, (long) i * SLOT_STRIDE, 0L, SLOT_STRIDE, true);
}
}
return shadows;
}
@Test
void releaseRemovesExactlyTheSlotsOfItsBuffer() {
final ReadbackShadows shadows = ringShadows(1, 2, 3);
shadows.release(2);
assertFalse(shadows.hasSlotsFor(2));
assertEquals(2 * SLOTS_PER_BUFFER, slotCount(shadows));
for (int glId = 1; glId <= 3; glId += 2) {
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
assertTrue(SdlReflect.readbackSlotValid(shadows, glId, (long) i * SLOT_STRIDE));
}
}
}
@Test
void releaseWithNoSlotsDoesNotAllocate() {
final ReadbackShadows shadows = shadows();
SdlAsserts.assertAllocationFree(() -> shadows.release(7), "releases with no readback slots");
}
}
@@ -1,58 +0,0 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_SAMPLER;
import static org.lwjgl.system.MemoryUtil.memAlloc;
import static org.lwjgl.system.MemoryUtil.memFree;
@Timeout(60)
class ReadbackSubmitOrderGpuTest {
private static final int SIZE = 4;
@AfterAll
static void releaseDevice() {
SdlTestRig.releaseRealDevice();
}
@Test
void aReadbackSubmitsTheFramesWritesFirst() throws Exception {
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
final ResourceManager rm = rig.resourceManager;
final FrameManager fm = rig.frameManager;
final TextureOps textureOps = new TextureOps(rig.device, fm, rm, null);
final int glId = rm.genTexture();
final long texture = rm.createTexture(glId, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1, SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
assertNotEquals(0L, texture, "texture creation failed");
fm.beginFrame();
final FrameManager.FrameState f = fm.frame();
final long frameCb = f.commandBuffer;
assertNotEquals(0L, frameCb, "no frame command buffer to order against");
assertEquals(0, f.midFrameSubmitsThisFrame);
final ByteBuffer out = memAlloc(SIZE * SIZE * 4);
try {
textureOps.readbackTexture(texture, 0, 0, SIZE, SIZE, 0, out);
} finally {
memFree(out);
}
assertEquals(1, f.midFrameSubmitsThisFrame, "the frame command buffer must be submitted before the download buffer, or the readback sees stale contents");
assertNotEquals(0L, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
fm.endFrame();
}
}
@@ -14,6 +14,7 @@ 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.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.system.MemoryUtil.memFree;
@@ -80,6 +81,13 @@ class PerFrameBlockInjectorTest {
assertEquals(src, PerFrameBlockInjector.inject(src, new PerFrameUniformBlock(List.of()), null));
}
@Test
void noBlocksReturnsTheSourceInstance() {
final String src = "#version 330 core\nuniform float frameTimeCounter;\nvoid main() {}\n";
assertSame(src, PerFrameBlockInjector.inject(src, null, null));
assertSame(src, PerFrameBlockInjector.inject(src, new PerFrameUniformBlock(List.of()), new PerFrameUniformBlock(List.of())));
}
@Test
void memberShadowedByAPackDeclarationIsRenamedNotRedeclared() {
final String out = PerFrameBlockInjector.inject("""
@@ -23,22 +23,9 @@ class PrewarmSingleParseEquivalenceTest {
GLSMHooks.perPassUniformBlock = null;
}
private static String legacy(String source, int glShaderType) {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, "test", true);
String src = pre != null ? pre.rewrittenSource() : source;
if (glShaderType == GL20.GL_VERTEX_SHADER) {
src = ClipZRemap.injectGLToVulkanClipZ(src);
}
src = SamplerStripper.stripUnused(src);
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
}
return src;
}
private static void assertEquivalent(String label, String source, int glShaderType) {
GlslVulkanPreprocess.clearCache();
final String legacyOut = legacy(source, glShaderType);
final String legacyOut = ShaderTransformChain.run(source, glShaderType);
GlslVulkanPreprocess.clearCache();
final String parseOnceOut = ShaderManager.applyPrewarmTransforms(source, glShaderType);
assertEquals(legacyOut, parseOnceOut, "single-parse vs chained pipeline diverged for " + label);
@@ -46,7 +33,7 @@ class PrewarmSingleParseEquivalenceTest {
private static void assertEquivalentIgnoringWhitespace(String label, String source, int glShaderType) {
GlslVulkanPreprocess.clearCache();
final String legacyOut = legacy(source, glShaderType);
final String legacyOut = ShaderTransformChain.run(source, glShaderType);
GlslVulkanPreprocess.clearCache();
final String parseOnceOut = ShaderManager.applyPrewarmTransforms(source, glShaderType);
assertEquals(legacyOut.replaceAll("\\s+", " "), parseOnceOut.replaceAll("\\s+", " "), "single-parse vs chained pipeline diverged beyond whitespace for " + label);
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -118,13 +119,10 @@ class SDLGPUUniformDedupTest {
}
@Test
void noProgramBound_reuseOrAllocReturnsFresh_putIsNoOp() {
void noProgramBound_reuseOrAllocReturnsNull_putIsNoOp() {
st.boundProgramObj = null;
final float[] a = applier.reuseOrAlloc(st, 0, 4);
assertEquals(4, a.length);
final float[] b = applier.reuseOrAlloc(st, 0, 4);
assertNotSame(a, b, "no caching without bound program");
applier.putUniform(st, 0, a);
assertNull(applier.reuseOrAlloc(st, 0, 4), "no program means nowhere to store");
applier.putUniform(st, 0, new float[4]);
}
@Test
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SdlSamplerStripperPreprocessorTest {
@@ -104,6 +105,16 @@ class SdlSamplerStripperPreprocessorTest {
assertFalse(out.contains("uniform sampler2D u_S"), "a comment mention must not keep the decl; output:\n" + out);
}
@Test
void sourceWithoutSamplersReturnsTheSourceInstance() {
final String src = "#version 460 core\n"
+ "in vec3 v_Position;\n"
+ "void main() {\n"
+ " gl_Position = vec4(v_Position, 1.0);\n"
+ "}\n";
assertSame(src, SamplerStripper.stripUnused(src));
}
@Test
void samplerOnlyInMacroBody_isPreserved() {
final String src = "#version 460 core\n"
@@ -0,0 +1,116 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL20;
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.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ShaderSourceTransformCacheTest {
private static final String FRAG = """
#version 460 core
uniform sampler2D u_Unused;
uniform sampler2D u_Used;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = texture(u_Used, vec2(0.5));
}
""";
private static final String VERT = """
#version 460 core
layout(location = 0) in vec3 a_Position;
in vec2 a_TexCoord;
out vec2 v_TexCoord;
void main() {
v_TexCoord = a_TexCoord;
gl_Position = vec4(a_Position, 1.0);
}
""";
private static final String PFB_FRAG = """
#version 460 core
uniform float frameTimeCounter;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(frameTimeCounter);
}
""";
private static final String CLIP_Z = "gl_Position.z = gl_Position.z * 0.5 + gl_Position.w * 0.5;";
@BeforeEach
void reset() {
ShaderManager.clearPrewarmCache();
GlslVulkanPreprocess.clearCache();
}
@AfterEach
void clearBlocks() {
ShaderManager.clearPrewarmCache();
GlslVulkanPreprocess.clearCache();
GLSMHooks.perFrameUniformBlock = null;
GLSMHooks.perPassUniformBlock = null;
}
@Test
void repeatedSourceReusesTheChainedTransform() {
final String expected = ShaderTransformChain.run(FRAG, GL20.GL_FRAGMENT_SHADER);
GlslVulkanPreprocess.clearCache();
final ShaderManager sm = new ShaderManager(null);
final int a = sm.createShader(GL20.GL_FRAGMENT_SHADER);
final int b = sm.createShader(GL20.GL_FRAGMENT_SHADER);
sm.shaderSource(a, FRAG);
sm.shaderSource(b, FRAG);
assertEquals(expected, sm.getShaderSource(a));
assertSame(sm.getShaderSource(a), sm.getShaderSource(b));
}
@Test
void vertexSourceMatchesTheChainAndCarriesClipZ() {
final String expected = ShaderTransformChain.run(VERT, GL20.GL_VERTEX_SHADER);
GlslVulkanPreprocess.clearCache();
final ShaderManager sm = new ShaderManager(null);
final int v = sm.createShader(GL20.GL_VERTEX_SHADER);
sm.shaderSource(v, VERT);
assertEquals(expected, sm.getShaderSource(v));
assertTrue(sm.getShaderSource(v).contains(CLIP_Z));
}
@Test
void blockChangeMissesTheCache() {
GLSMHooks.perFrameUniformBlock = new PerFrameUniformBlock(List.of(new Member("frameTimeCounter", UniformType.FLOAT)));
final ShaderManager sm = new ShaderManager(null);
final int withBlock = sm.createShader(GL20.GL_FRAGMENT_SHADER);
sm.shaderSource(withBlock, PFB_FRAG);
assertTrue(sm.getShaderSource(withBlock).contains("AngelicaPerFrame"));
GLSMHooks.perFrameUniformBlock = null;
GlslVulkanPreprocess.clearCache();
final String expected = ShaderTransformChain.run(PFB_FRAG, GL20.GL_FRAGMENT_SHADER);
GlslVulkanPreprocess.clearCache();
final int withoutBlock = sm.createShader(GL20.GL_FRAGMENT_SHADER);
sm.shaderSource(withoutBlock, PFB_FRAG);
assertEquals(expected, sm.getShaderSource(withoutBlock));
assertFalse(sm.getShaderSource(withoutBlock).contains("AngelicaPerFrame"));
}
}
@@ -0,0 +1,23 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import org.lwjgl.opengl.GL20;
final class ShaderTransformChain {
private ShaderTransformChain() {}
static String run(String source, int glShaderType) {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, "test", true);
String src = pre != null ? pre.rewrittenSource() : source;
if (glShaderType == GL20.GL_VERTEX_SHADER) {
src = ClipZRemap.injectGLToVulkanClipZ(src);
}
src = SamplerStripper.stripUnused(src);
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
}
return src;
}
}
@@ -21,7 +21,7 @@ public final class TesrMaterial {
private static final ConcurrentHashMap<TesrMaterial, TesrMaterial> INTERNED = new ConcurrentHashMap<>();
public static final TesrMaterial CURRENT_STATE = intern(new TesrMaterial(false, 0, 0, 0, 0, false, 0, 0, false, false, false, false, false, 0, false, Transparency.OPAQUE, SpecialRender.NONE, null));
public static final TesrMaterial CURRENT_STATE = intern(new TesrMaterial(false, 0, 0, 0, 0, false, 0, 0, false, false, false, false, false, 0, false, false, Transparency.OPAQUE, SpecialRender.NONE, null));
private static TesrMaterial intern(TesrMaterial material) {
final TesrMaterial existing = INTERNED.putIfAbsent(material, material);
@@ -39,6 +39,7 @@ public final class TesrMaterial {
private final boolean isStream;
private final float cutoutAlpha;
private final boolean isDepthEqual;
private final boolean isUnfilteredAtlas;
private final Transparency transparency;
private final SpecialRender special;
private final TesrShader shader;
@@ -59,6 +60,7 @@ public final class TesrMaterial {
private boolean stream;
private float cutoutAlpha;
private boolean depthEqual;
private boolean unfilteredAtlas;
private Transparency transparency = Transparency.OPAQUE;
private SpecialRender special = SpecialRender.NONE;
private TesrShader shader;
@@ -119,6 +121,11 @@ public final class TesrMaterial {
return this;
}
public Builder unfilteredAtlas() {
this.unfilteredAtlas = true;
return this;
}
/** Standard alpha blending (glass, liquids). SRC_ALPHA, ONE_MINUS_SRC_ALPHA */
public Builder translucent() {
this.transparency = Transparency.TRANSLUCENT;
@@ -158,7 +165,7 @@ public final class TesrMaterial {
if (special != SpecialRender.NONE && shader != null) {
throw new IllegalStateException("special() and shader() are mutually exclusive");
}
return intern(new TesrMaterial(hasColor, red, green, blue, alpha, hasLightmap, lightmapX, lightmapY, noCull, unlit, noDepthWrite, depthOnly, stream, cutoutAlpha, depthEqual, transparency, special, shader));
return intern(new TesrMaterial(hasColor, red, green, blue, alpha, hasLightmap, lightmapX, lightmapY, noCull, unlit, noDepthWrite, depthOnly, stream, cutoutAlpha, depthEqual, unfilteredAtlas, transparency, special, shader));
}
}
}
@@ -1,5 +1,5 @@
/** TESR mesh cache/batch API (Render thread only) */
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "1")
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "2")
package com.gtnewhorizons.angelica.api.tesr;
import cpw.mods.fml.common.API;
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
@@ -392,13 +393,21 @@ public class BatchingFontRenderer {
idxWriterIndex += 6;
}
/** Draw layers; see {@link FontDrawCmd#layer}. */
private static final int LAYER_BACKGROUND = -1;
private static final int LAYER_DEFAULT = 0;
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
pushDrawCmd(startIdx, idxCount, texture, isUnicode, LAYER_DEFAULT);
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode, int layer) {
arenaOwner = this;
// Never coalesce into a command below the watermark - it belongs to a sealed segment.
if (batchCommands.size() > batchSealedEnd) {
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
if (prevEndVtx == startIdx && lastCmd.texture == texture) {
if (prevEndVtx == startIdx && lastCmd.texture == texture && lastCmd.layer == layer) {
// Coalesce into one
lastCmd.idxCount += idxCount;
return;
@@ -410,7 +419,7 @@ public class BatchingFontRenderer {
}
}
final FontDrawCmd cmd = batchCommandPool.pop();
cmd.reset(startIdx, idxCount, texture, isUnicode);
cmd.reset(startIdx, idxCount, texture, isUnicode, layer);
batchCommands.add(cmd);
}
@@ -420,8 +429,19 @@ public class BatchingFontRenderer {
public int idxCount;
public boolean isUnicode;
public ResourceLocation texture;
/**
* Sorted ahead of the texture, so lower layers draw first. Sorting by texture
* means submission order is not kept, so anything belonging under the glyphs
* needs a lower layer rather than an earlier push.
*/
public int layer;
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
reset(startVtx, vtxCount, texture, isUnicode, LAYER_DEFAULT);
}
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode, int layer) {
this.layer = layer;
this.startVtx = startVtx;
this.idxCount = vtxCount;
this.texture = texture;
@@ -456,9 +476,12 @@ public class BatchingFontRenderer {
+ ']';
}
public static final Comparator<FontDrawCmd> DRAW_ORDER_COMPARATOR = Comparator.comparing((FontDrawCmd fdc) -> fdc.texture,
Comparator.nullsLast(Comparator.comparing(ResourceLocation::getResourceDomain)
.thenComparing(ResourceLocation::getResourcePath))).thenComparing(fdc -> fdc.startVtx);
public static final Comparator<FontDrawCmd> DRAW_ORDER_COMPARATOR =
Comparator.comparingInt((FontDrawCmd fdc) -> fdc.layer)
.thenComparing(fdc -> fdc.texture,
Comparator.nullsLast(Comparator.comparing(ResourceLocation::getResourceDomain)
.thenComparing(ResourceLocation::getResourcePath)))
.thenComparing(fdc -> fdc.startVtx);
}
/**
@@ -1281,6 +1304,7 @@ public class BatchingFontRenderer {
boolean curWave = false;
boolean curDinnerbone = false;
boolean curGradient = false;
long curCustomEffects = 0L;
boolean curShadow = false;
boolean curShadowCustomColor = false;
int curShadowColorOverride = 0;
@@ -1331,6 +1355,16 @@ public class BatchingFontRenderer {
if (rgb != -1) {
curRainbow = false;
curGradient = false;
curCustomEffects = 0L;
// Styles normally carry through a hex colour; a text mod that owns
// this grammar can say otherwise, and the widths follow the same call.
if (FontRendering.hexColorResetsStyles()) {
curRandom = false;
curBold = false;
curStrikethrough = false;
curUnderline = false;
curItalic = false;
}
curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF);
curShadowColor = (curShadowColor & 0xFF000000) | ((rgb & 0xFCFCFC) >> 2);
charIdx += SECTION_X_PAYLOAD;
@@ -1346,6 +1380,7 @@ public class BatchingFontRenderer {
curItalic = false;
curRainbow = false;
curGradient = false;
curCustomEffects = 0L;
// wave/dinnerbone NOT reset — they're positional effects, independent of color
final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10);
@@ -1370,6 +1405,7 @@ public class BatchingFontRenderer {
} else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) {
curRainbow = true;
curGradient = false;
curCustomEffects = 0L;
rainbowCharIndex = 0;
} else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) {
curWave = !curWave;
@@ -1394,6 +1430,7 @@ public class BatchingFontRenderer {
if (color1 != -1 && color2 != -1) {
curGradient = true;
curRainbow = false;
curCustomEffects = 0L;
gradientStartRgb = color1;
gradientEndRgb = color2;
gradientCharIndex = 0;
@@ -1401,6 +1438,10 @@ public class BatchingFontRenderer {
gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f;
charIdx += GRADIENT_PAYLOAD;
}
} else if (FontEffectRegistry.isRegistered(fmtCode)) {
// Toggles, like wave and dinnerbone, so a span can be closed
// without a colour or §r. Those still clear it as before.
curCustomEffects ^= FontEffectRegistry.bit(fmtCode);
} else if (fmtCode == 'r') {
curRandom = false;
curBold = false;
@@ -1411,6 +1452,7 @@ public class BatchingFontRenderer {
curWave = false;
curDinnerbone = false;
curGradient = false;
curCustomEffects = 0L;
curShadow = false;
curShadowCustomColor = false;
curColor = color;
@@ -1434,19 +1476,23 @@ public class BatchingFontRenderer {
}
}
if (curRandom) {
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
}
// ASCII space, NBSP, NNBSP, decided before obfuscation as vanilla does.
final boolean whitespace = chr == ' ' || chr == '\u00A0' || chr == '\u202F';
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
// Asked of the provider that draws the glyph, so §k keeps to the font in use
// and to the width it was measured at, under a custom or unicode font too.
if (curRandom && !whitespace) {
chr = fontProvider.getRandomReplacement(chr);
}
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
visibleCharIndex++;
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
if (whitespace) {
curX += 4 * this.getWhitespaceScale() + (curBold ? 1 : 0);
continue;
}
@@ -1479,7 +1525,6 @@ public class BatchingFontRenderer {
final int shadowCopies = FontConfig.shadowCopies;
final int boldCopies = FontConfig.boldCopies;
final ResourceLocation texture = fontProvider.getTexture(chr);
final int idxId = idxWriterIndex;
// Wave: Y offset via sine wave
float renderY = heightNorth;
@@ -1488,27 +1533,55 @@ public class BatchingFontRenderer {
renderY += (float) Math.sin(visibleCharIndex * WAVE_FREQUENCY + time) * AngelicaConfig.waveAmplitude;
}
float renderX = curX;
int glyphColor = curColor;
int glyphShadowColor = curShadowColor;
int glyphBackground = 0;
if (curCustomEffects != 0L) {
long fx = curCustomEffects;
while (fx != 0L) {
final CustomGlyphEffect effect = FontEffectRegistry.get(Long.numberOfTrailingZeros(fx));
fx &= fx - 1L;
glyphColor = effect.transformColor(glyphColor, false, visibleCharIndex);
glyphShadowColor = effect.transformColor(glyphShadowColor, true, visibleCharIndex);
renderX += effect.offsetX(visibleCharIndex);
renderY += effect.offsetY(visibleCharIndex);
final int background = effect.backgroundColor(visibleCharIndex);
if (background != 0) glyphBackground = background;
}
}
// Untextured, with its own draw command, like the underline below.
if (glyphBackground != 0) {
final int bgIdx = idxWriterIndex;
pushUntexRect(renderX, renderY, glyphW, heightSouth, glyphBackground);
pushDrawCmd(bgIdx, 6, null, false, LAYER_BACKGROUND);
}
// After the background, so this command covers only what is counted below.
final int idxId = idxWriterIndex;
final boolean drawShadow = enableShadow || curShadow;
if (drawShadow) {
final int effectiveShadowColor = curShadowCustomColor
? ((curColor & 0xFF000000) | (curShadowColorOverride & 0x00FFFFFF))
: curShadowColor;
? ((glyphColor & 0xFF000000) | (curShadowColorOverride & 0x00FFFFFF))
: glyphShadowColor;
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(renderX + shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(renderX + 2.0f * shadowOffsetPart, renderY + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, effectiveShadowColor, uStart, vStart, uSz, vSz, curDinnerbone);
}
}
}
pushTexRect(curX, renderY, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(renderX, renderY, glyphW - 1.0f, heightSouth, itOff, glyphColor, uStart, vStart, uSz, vSz, curDinnerbone);
if (curBold) {
for (int n = 1; n <= boldCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
pushTexRect(curX + shadowOffsetPart, renderY, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz, curDinnerbone);
pushTexRect(renderX + shadowOffsetPart, renderY, glyphW - 1.0f, heightSouth, itOff, glyphColor, uStart, vStart, uSz, vSz, curDinnerbone);
}
}
@@ -1530,15 +1603,18 @@ public class BatchingFontRenderer {
underlineEndX = curX;
strikethroughEndX = curX;
if ((curRainbow || curGradient) && curUnderline && underlineStartX != underlineEndX) {
// Flushed per glyph while the colour moves, so the line under the text is the
// colour of the glyph above it rather than one flat run.
final boolean perGlyphColor = curRainbow || curGradient || curCustomEffects != 0L;
if (perGlyphColor && curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, glyphColor);
pushDrawCmd(ulIdx, 6, null, false);
underlineStartX = underlineEndX;
}
if ((curRainbow || curGradient) && curStrikethrough && strikethroughStartX != strikethroughEndX) {
if (perGlyphColor && curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int stIdx = idxWriterIndex;
pushUntexRect(strikethroughStartX, strikethroughY, strikethroughEndX - strikethroughStartX, glyphScaleY, curColor);
pushUntexRect(strikethroughStartX, strikethroughY, strikethroughEndX - strikethroughStartX, glyphScaleY, glyphColor);
pushDrawCmd(stIdx, 6, null, false);
strikethroughStartX = strikethroughEndX;
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.BooleanSupplier;
/**
* Utility for converting {@code &}-based shorthand into {@code §}-based
@@ -17,6 +21,8 @@ public final class ColorCodeUtils {
private ColorCodeUtils() {}
private static final Logger LOGGER = LogManager.getLogger("AngelicaFontEffects");
public static final char FORMATTING_CHAR = '§';
public static final char ESCAPED_AMPERSAND = '';
@@ -69,8 +75,26 @@ public final class ColorCodeUtils {
private static String lastConversionInput;
private static String lastConversionOutput;
private static BooleanSupplier conversionSuppressor;
/**
* Lets text mods that draw format codes as literal characters (e.g. inside edit GUIs)
* suspend {@code &} conversion for strings rendered while the supplier returns true.
*/
public static void setConversionSuppressor(BooleanSupplier suppressor) {
if (suppressor != null && conversionSuppressor != null && suppressor != conversionSuppressor) {
LOGGER.info("Replacing the & conversion suppressor; only the newest one is consulted");
}
conversionSuppressor = suppressor;
}
public static boolean isConversionSuppressed() {
final BooleanSupplier suppressor = conversionSuppressor;
return suppressor != null && suppressor.getAsBoolean();
}
public static String convertAmpersandToSectionX(String text) {
if (text == null || !AngelicaConfig.enableAmpersandConversion) return text;
if (text == null || !AngelicaConfig.enableAmpersandConversion || isConversionSuppressed()) return text;
if (text == lastConversionInput) return lastConversionOutput;
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.client.font;
/**
* A per-glyph text effect another mod can attach to an unused {@code §} format code through
* {@link FontEffectRegistry}. While the code is active the batched font renderer invokes the
* effect for every visible glyph it emits.
* <p>
* The format code toggles the effect, and any colour code ({@code §0}-{@code §f}, {@code §x},
* {@code §g}, {@code §q}) or {@code §r} clears it, as with the renderer's own colour-bound
* effects. Wave and dinnerbone are positional and leave it alone.
*/
public interface CustomGlyphEffect {
/**
* Transforms the colour a glyph is about to be drawn with. Called once for the main pass and
* once for the shadow pass (receiving the already-darkened shadow colour). Implementations
* should preserve the alpha channel of the input.
*/
int transformColor(int argb, boolean shadowPass, int glyphIndex);
/**
* Quad drawn behind the glyph as {@code 0xAARRGGBB}, or {@code 0} for none. Lets an
* effect mark a span without taking over the text colour. Where two active effects
* both draw one, the later format-code letter wins.
*/
default int backgroundColor(int glyphIndex) {
return 0;
}
/** Horizontal offset added to the glyph quad, in text-space pixels. */
default float offsetX(int glyphIndex) {
return 0.0f;
}
/** Vertical offset added to the glyph quad, in text-space pixels. */
default float offsetY(int glyphIndex) {
return 0.0f;
}
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.client.font;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Maps unused {@code §} format-code letters to {@link CustomGlyphEffect}s supplied by other mods
* (e.g. HexText's ignite and shake). Letters already used by vanilla formatting or the batched
* font renderer's own grammar cannot be claimed.
*/
public final class FontEffectRegistry {
private static final Logger LOGGER = LogManager.getLogger("AngelicaFontEffects");
/** Letters with a meaning in vanilla formatting or the batched renderer's grammar. */
private static final String RESERVED_CODES = "abcdefgklmnoqruvxz";
private static final CustomGlyphEffect[] EFFECTS = new CustomGlyphEffect[26];
private static volatile boolean anyRegistered = false;
private FontEffectRegistry() {}
/**
* Claims {@code code} (a-z, case-insensitive) for the given effect.
*
* @return false when the letter is reserved, already claimed, or invalid
*/
public static synchronized boolean register(char code, CustomGlyphEffect effect) {
final char lower = Character.toLowerCase(code);
if (lower < 'a' || lower > 'z' || RESERVED_CODES.indexOf(lower) != -1 || effect == null) {
LOGGER.warn("Rejected font effect registration for reserved or invalid code '{}'", code);
return false;
}
if (EFFECTS[lower - 'a'] != null) {
LOGGER.warn("Rejected font effect registration for already-claimed code '{}'", code);
return false;
}
EFFECTS[lower - 'a'] = effect;
anyRegistered = true;
return true;
}
public static boolean hasAny() {
return anyRegistered;
}
public static boolean isRegistered(char lowerCode) {
return anyRegistered && lowerCode >= 'a' && lowerCode <= 'z' && EFFECTS[lowerCode - 'a'] != null;
}
static long bit(char lowerCode) {
return 1L << (lowerCode - 'a');
}
static CustomGlyphEffect get(int bitIndex) {
return EFFECTS[bitIndex];
}
}
@@ -4,8 +4,12 @@ import net.minecraft.util.ResourceLocation;
public interface FontProvider {
/** Tries before {@link #getRandomReplacement} gives up and leaves the character alone. */
int RANDOM_GLYPH_TRIES = 64;
/**
* For use with §k. Should fetch a character of the same width as provided.
* For use with §k. Should fetch a character of the same width as provided, and one this
* provider can draw, since the provider is picked for the original character.
*/
char getRandomReplacement(char chr);
boolean isGlyphAvailable(char chr);
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import jss.util.RandomXoshiro256StarStar;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
@@ -31,6 +32,7 @@ public final class FontProviderCustom implements FontProvider {
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
private float currentFontQuality = FontConfig.customFontQuality;
@Setter
@@ -239,8 +241,29 @@ public final class FontProviderCustom implements FontProvider {
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
}
/**
* A random glyph of the same width, for {@code §k}. Kept inside the character's own
* atlas: atlases are rasterized on first use, so reaching outside one would build a
* new atlas mid-frame. Slots the font has no glyph for are skipped.
*/
@Override
public char getRandomReplacement(char chr) {
if (this.font == null) {
return chr;
}
final FontAtlas atlas = getAtlas(chr);
final GlyphData target = atlas.glyphData[chr % ATLAS_SIZE];
if (target == null) {
return chr;
}
final int atlasStart = (chr / ATLAS_SIZE) * ATLAS_SIZE;
for (int attempt = 0; attempt < RANDOM_GLYPH_TRIES; attempt++) {
final int slot = fontRandom.nextInt(ATLAS_SIZE);
final GlyphData candidate = atlas.glyphData[slot];
if (candidate != null && candidate.xAdvance == target.xAdvance) {
return (char) (atlasStart + slot);
}
}
return chr;
}
@@ -43,16 +43,27 @@ public final class FontProviderMC implements FontProvider {
return MCFONT_ASCII_MAP.containsKey(chr);
}
/**
* A random glyph of the same width, for {@code §k}. Only slots the reverse lookup
* agrees with are eligible: {@link #MCFONT_CHARS} repeats {@code U+0000} and the map
* keeps the last, so other slots would measure from a different one. Bounded, since
* a resource pack may ship widths where nothing matches.
*/
public char getRandomReplacement(char chr){
int lutIndex = lookupMcFontPosition(chr);
if (lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
return MCFONT_CHARS.charAt(lutIndex);
final int lutIndex = lookupMcFontPosition(chr);
if (lutIndex == -1 || this.charWidth == null || lutIndex >= this.charWidth.length) {
return chr;
}
final int targetWidth = this.charWidth[lutIndex];
for (int attempt = 0; attempt < RANDOM_GLYPH_TRIES; attempt++) {
final int candidate = fontRandom.nextInt(this.charWidth.length);
if (this.charWidth[candidate] != targetWidth) {
continue;
}
final char replacement = MCFONT_CHARS.charAt(candidate);
if (MCFONT_ASCII_MAP.get(replacement) == candidate) {
return replacement;
}
}
return chr;
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
@@ -34,6 +35,7 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
private final byte[] glyphWidth = new byte[65536];
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
@@ -53,8 +55,24 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
return true;
}
/**
* A random glyph of the same width, for {@code §k}. Kept inside the character's own
* 256-glyph page so the swap neither pulls in another page's texture nor splits the
* batch. Empty entries are left alone, and never swapped in for a glyph that draws.
*/
@Override
public char getRandomReplacement(char chr) {
if (this.glyphWidth[chr] == 0) {
return chr;
}
final int pageStart = (chr / 256) * 256;
final float targetWidth = getXAdvance(chr);
for (int attempt = 0; attempt < RANDOM_GLYPH_TRIES; attempt++) {
final char candidate = (char) (pageStart + fontRandom.nextInt(256));
if (this.glyphWidth[candidate] != 0 && getXAdvance(candidate) == targetWidth) {
return candidate;
}
}
return chr;
}
@@ -5,7 +5,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.ITessellatorInstance;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.renderer.Tessellator;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
@@ -28,8 +27,7 @@ class DeferredBatchTessellator extends DirectTessellator {
record DrawRange(long stateKey, int byteOffset, int byteLength, int vertexCount, int drawMode, int flags) {}
private final List<DrawRange> ranges = new ArrayList<>();
private final Matrix4f defaultModelview = new Matrix4f();
private final Matrix4f defaultModelviewInverse = new Matrix4f();
private final ModelViewDelta modelViewDelta = new ModelViewDelta();
private final Matrix4f deltaMatrix = new Matrix4f(); // reusable scratch
private final Vector3f scratch = new Vector3f();
private Matrix4fc currentTransform;
@@ -68,22 +66,14 @@ class DeferredBatchTessellator extends DirectTessellator {
/** Snapshot the current modelview as the "default" for this bracket. */
void snapshotDefaultModelview() {
defaultModelview.set(GLStateManager.getModelViewMatrix());
defaultModelview.invert(defaultModelviewInverse);
modelViewDelta.snapshot();
}
@Override
protected int interceptDraw(Tessellator tess) {
inIntercept = true;
try {
// Compute delta: if current MV differs from default, pre-transform vertices
final Matrix4f currentMV = GLStateManager.getModelViewMatrix();
if (!currentMV.equals(defaultModelview)) {
defaultModelviewInverse.mul(currentMV, deltaMatrix);
this.currentTransform = deltaMatrix;
} else {
this.currentTransform = null;
}
this.currentTransform = modelViewDelta.deltaOrNull(deltaMatrix);
final int byteOffsetBefore = bufferLimit();
final int vertCount = tess.vertexCount;
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff. Show more