Compare commits

..
11 Commits
253 changed files with 14246 additions and 3867 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
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$REPO_ROOT/umbra"
gradle_args=(:compileJava :compileMixinJava :test --no-daemon --console=plain --stacktrace)
if [[ -n "${UMBRA_GRADLE_ARGS:-}" ]]; then
read -ra extra_args <<< "$UMBRA_GRADLE_ARGS"
gradle_args+=("${extra_args[@]}")
fi
sh ./gradlew "${gradle_args[@]}"
+25
View File
@@ -0,0 +1,25 @@
name: Umbra build
on:
pull_request:
branches: [ master, main ]
push:
branches: [ master, main ]
jobs:
umbra-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-java@v5
with:
distribution: zulu
java-version: |
21
25
- uses: gradle/actions/setup-gradle@v5
with:
validate-wrappers: true
- run: .github/scripts/umbra-build.sh
+2
View File
@@ -1,5 +1,6 @@
.gradle
.kotlin/
.lwjgl/
.settings
/.idea/
/.vs/
@@ -43,5 +44,6 @@ tracy-client/src/main/resources/natives/
tracy-client/angelica/
dist/tracy-natives/
*.tracy
*.jfr
tracy-client/angelica/
+4 -82
View File
@@ -1,19 +1,16 @@
import com.modrinth.minotaur.TaskModrinthUpload
import net.darkhax.curseforgegradle.TaskPublishCurseForge
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
val rgp = false
extra["lwjglDebug"] = lwjglDebug
minecraft {
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
@@ -24,24 +21,13 @@ minecraft {
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
// TODO: Remove once GTNHGradle includes shaderc/spvc in the default lwjgl3Bindings list
lwjgl3Bindings.addAll("shaderc", "spvc")
lwjgl3Version = libs.versions.lwjgl3.get()
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
}
// Forward -Dangelica.* from the gradle invocation to the client
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
for ((key, value) in System.getProperties()) {
val name = key.toString()
if (name.startsWith("angelica.")) {
jvmArgs("-D$name=$value")
}
}
}
apply(from = "gradle/angelica-run-common.gradle.kts")
if (gpuHud) {
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
@@ -57,47 +43,12 @@ if (gpuHud) {
configurations.all {
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
resolutionStrategy.dependencySubstitution {
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
}
}
// lwjgl3ify pulls a pinned lwjgl 3.4.2 snapshot transitively from here.
repositories {
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
content { includeGroup("org.lwjgl") }
}
}
val osName = System.getProperty("os.name").lowercase()
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && name != "runClient") {
if (isMacOs) {
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
jvmArgs("-XstartOnFirstThread")
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
if (lwjglDebug) {
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
environment("METAL_DEBUG_ERROR_MODE", "0")
environment("MTL_SHADER_VALIDATION", "1")
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
environment("MTL_DEBUG_LAYER", "1")
environment("MallocScribble", "1")
environment("MallocPreScribble", "1")
environment("MallocGuardEdges", "1")
environment("MallocStackLogging", "1")
environment("MallocStackLoggingNoCompact", "1")
}
}
}
}
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
@@ -126,14 +77,6 @@ tasks.withType<JavaExec>().configureEach {
}
}
tasks.processResources {
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to projectVersion)
}
}
tasks.register<Copy>("copyDependencies") {
group = "Angelica"
description = "Collect dependencies into the testDependencies folder"
@@ -206,6 +149,7 @@ fun Test.configureAngelicaJava8() {
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
}
@@ -233,29 +177,7 @@ val glCoreTest by tasks.registering(Test::class) {
tasks.test { finalizedBy(glCoreTest) }
tasks.shadowJar {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mergeServiceFiles()
minimize {
exclude(project(rootProject.path))
exclude(project(":glsm"))
exclude(project(":lwjgl3-backend"))
exclude(project(":sdl-gpu"))
exclude(dependency("org.taumc:.*:.*"))
exclude(dependency("org.antlr:.*:.*"))
}
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
}
apply(from = "gradle/angelica-shadow-common.gradle.kts")
tasks.withType<TaskPublishCurseForge>().configureEach {
uploadArtifacts.forEach { it.addGameVersion("Client") }
+4 -4
View File
@@ -52,8 +52,8 @@ configurations.configureEach {
}
DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -104,7 +104,6 @@ DependencyHandlerScope.of(dependencies).apply {
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
embedOnly(libs.celeritas.common) { isTransitive = false }
embedOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
@@ -177,7 +176,7 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
compileOnly(libs.distant.horizons.api)
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
@@ -208,5 +207,6 @@ afterEvaluate {
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
testImplementation(libs.distant.horizons.api)
}
}
+22 -1
View File
@@ -46,6 +46,27 @@ tasks.withType<Jar>().configureEach {
exclude("net/minecraft/**")
}
val stubsElements by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class.java, Category.LIBRARY))
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
outgoing {
capability("${project.group}:glsm-stubs:${project.version}")
}
}
artifacts {
add(stubsElements.name, sourceSets["stubs"].java.classesDirectory) {
type = "java-classes-directory"
builtBy(tasks.named("stubsClasses"))
}
}
repositories {
maven {
name = "Mojang"
@@ -114,6 +135,7 @@ dependencies {
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
// @Lwjgl3Aware annotation
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
compileOnly(libs.lombok) { isTransitive = false }
annotationProcessor(libs.lombok)
@@ -140,7 +162,6 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testImplementation(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
testRuntimeOnly(libs.commons.lang3)
}
@@ -52,6 +52,9 @@ public final class SystemProperties {
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
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 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");
@@ -60,19 +63,26 @@ 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");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
public static final String KEY_CELERITAS_LWJGL_SERVICE = "celeritas.lwjglService";
public static boolean dumpShaders() {
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;
}
@@ -102,6 +112,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));
@@ -564,7 +564,7 @@ public final class GLDebug {
/**
* Overload that lets you gate the String concat behind the debugState
*/
public static void pushGroup(String prefix, int value) {
public static void pushGroup(String prefix, long value) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(prefix + value);
}
@@ -52,6 +52,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 +92,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;
@@ -493,7 +495,9 @@ public class GLStateManager {
public static void initialize(GLSMInitConfig config) {
initConfig = config;
preInit(config.getDisplayWidth(), config.getDisplayHeight());
final int width = config.getDisplayWidth() > 0 ? config.getDisplayWidth() : Display.getWidth();
final int height = config.getDisplayHeight() > 0 ? config.getDisplayHeight() : Display.getHeight();
preInit(width, height);
init(config.getPostInitCallback());
}
@@ -1052,6 +1056,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;
@@ -1304,6 +1310,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) {
@@ -1449,6 +1478,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;
@@ -1465,8 +1495,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;
@@ -1529,8 +1561,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;
@@ -2003,6 +2036,7 @@ public class GLStateManager {
}
public static void glBindTexture(int target, int texture) {
if (TextureInfoCache.isProxyTarget(target)) return;
final GLContextState glCtx = ctx();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3534,6 +3568,9 @@ public class GLStateManager {
Display.swapBuffers();
}
private static boolean firstClearPending;
private static long firstClearBlockNanos;
public static void updateDisplay() throws LWJGLException {
updateDisplay(true);
}
@@ -3541,17 +3578,19 @@ public class GLStateManager {
public static void updateDisplay(boolean processMessages) throws LWJGLException {
if (Thread.currentThread() != MainThread) {
swapBuffers();
if (processMessages) Display.processMessages();
if (processMessages) pumpDisplayMessages();
return;
}
if (processMessages) pumpDisplayMessages();
if (!RENDER_BACKEND.wantsDisplayUpdateGateTiming()) {
Display.update(processMessages);
Display.update(false);
return;
}
final long start = System.nanoTime();
Display.update(processMessages);
Display.update(false);
final long end = System.nanoTime();
RENDER_BACKEND.recordFrameGate(end - start, end);
RENDER_BACKEND.recordGate(end - start, end);
firstClearPending = true;
}
/**
@@ -3748,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);
@@ -3771,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) {
@@ -3795,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);
@@ -3902,9 +3953,26 @@ public class GLStateManager {
return;
}
}
if (firstClearPending && Thread.currentThread() == MainThread && getDrawFramebuffer() == 0) {
firstClearPending = false;
final long start = System.nanoTime();
RENDER_BACKEND.clear(mask);
final long end = System.nanoTime();
if (end - start > 0) {
firstClearBlockNanos += end - start;
RENDER_BACKEND.recordGate(end - start, end);
}
return;
}
RENDER_BACKEND.clear(mask);
}
public static long takeFirstClearBlockNanos() {
final long value = firstClearBlockNanos;
firstClearBlockNanos = 0L;
return value;
}
public static void glPushAttrib(int mask) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -4554,6 +4622,11 @@ public class GLStateManager {
};
}
private static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0 || level >= Integer.SIZE - 1) return 1;
return Math.max(base >> level, 1);
}
public static int glGetTexLevelParameteri(int target, int level, int pname) {
if (target != GL11.GL_TEXTURE_2D || !isCachingEnabled()) {
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
@@ -4568,8 +4641,8 @@ public class GLStateManager {
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
}
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> Math.max(info.getWidth() >> level, 1);
case GL11.GL_TEXTURE_HEIGHT -> Math.max(info.getHeight() >> level, 1);
case GL11.GL_TEXTURE_WIDTH -> mipLevelSize(info.getWidth(), level);
case GL11.GL_TEXTURE_HEIGHT -> mipLevelSize(info.getHeight(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
@@ -4691,8 +4764,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); }
@@ -5246,20 +5372,8 @@ public class GLStateManager {
return RENDER_BACKEND.getEffectiveVSyncMode();
}
public static int getDisplayRefreshRateHz() {
return RENDER_BACKEND.getDisplayRefreshRateHz();
}
public static boolean gateAnchorsNextFrameStart() {
return RENDER_BACKEND.gateAnchorsNextFrameStart();
}
public static long lastFrameGateNanos() {
return RENDER_BACKEND.lastFrameGateNanos();
}
public static long lastFrameGateEndNanos() {
return RENDER_BACKEND.lastFrameGateEndNanos();
public static void setPresentSuppressed(boolean suppressed) {
RENDER_BACKEND.setPresentSuppressed(suppressed);
}
public static boolean hasSwapchainBackpressure() {
@@ -5267,7 +5381,7 @@ public class GLStateManager {
}
public static void pumpDisplayMessages() {
Display.processMessages();
RENDER_BACKEND.pumpDisplayMessages();
}
public static int glGetError() {
@@ -6155,6 +6269,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); }
@@ -9,7 +9,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.mitchej123.lwjgl.LWJGLServiceProvider;
import org.taumc.celeritas.lwjgl.LWJGLServiceProvider;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@@ -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,20 @@ 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 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);
}
}
}
@@ -40,7 +40,7 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
/**
* LWJGL2 GL implementation of {@link RenderBackend}.
@@ -122,11 +122,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
@Override
protected int queryDisplayRefreshRateHz() {
protected long queryRefreshPeriodNanos() {
final DisplayMode mode = Display.getDisplayMode();
if (mode != null && mode.getFrequency() > 0) return mode.getFrequency();
if (mode != null && mode.getFrequency() > 0) return periodFromIntegerHz(mode.getFrequency());
final DisplayMode desktop = Display.getDesktopDisplayMode();
return desktop == null ? 0 : desktop.getFrequency();
return desktop == null ? 0L : periodFromIntegerHz(desktop.getFrequency());
}
@Override
@@ -0,0 +1,112 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
public final class MainThreadPump {
private static final Logger LOGGER = LogManager.getLogger("MainThreadPump");
private static final Tracy.ZoneId Z_DISPLAY_MESSAGES = Tracy.zoneId("displayMessages", Tracy.COLOR_CLIENT);
private static final long PARK_MILLIS = 20L;
private static final long STALL_WARN_NANOS = 5_000_000_000L;
public static void pollInput() {
Keyboard.poll();
Mouse.poll();
}
private final Executor mainExecutor;
private final BooleanSupplier isMainThread;
private final Runnable pumpBody;
private final Runnable pollBody;
private final Runnable task = this::runPump;
private final Object lock = new Object();
private boolean done;
private Throwable failure;
private ClassLoader callerLoader;
public MainThreadPump(Executor mainExecutor, BooleanSupplier isMainThread, Runnable pumpBody, Runnable pollBody) {
this.mainExecutor = mainExecutor;
this.isMainThread = isMainThread;
this.pumpBody = pumpBody;
this.pollBody = pollBody;
}
public void pumpMessages() {
Tracy.beginZone(Z_DISPLAY_MESSAGES);
try {
if (isMainThread.getAsBoolean()) {
pumpBody.run();
} else {
synchronized (lock) {
runPumpOnMainThread();
}
}
pollBody.run();
} finally {
Tracy.endZone();
}
}
private void runPumpOnMainThread() {
final Thread self = Thread.currentThread();
callerLoader = self.getContextClassLoader();
failure = null;
done = false;
mainExecutor.execute(task);
boolean interrupted = false;
boolean stallReported = false;
final long stallAt = System.nanoTime() + STALL_WARN_NANOS;
while (!done) {
try {
lock.wait(PARK_MILLIS);
} catch (InterruptedException e) {
interrupted = true;
}
if (!done && !stallReported && System.nanoTime() - stallAt >= 0L) {
stallReported = true;
LOGGER.warn("Display message pump has been waiting on the RFB main thread for over {} ms", STALL_WARN_NANOS / 1_000_000L);
}
}
if (interrupted) self.interrupt();
final Throwable t = failure;
if (t != null) {
throw asUnchecked(t);
}
}
private void runPump() {
final Thread self = Thread.currentThread();
final ClassLoader saved = self.getContextClassLoader();
try {
self.setContextClassLoader(callerLoader);
pumpBody.run();
} catch (Throwable t) {
failure = t;
} finally {
try {
self.setContextClassLoader(saved);
} finally {
synchronized (lock) {
done = true;
lock.notifyAll();
}
}
}
}
private static RuntimeException asUnchecked(Throwable t) {
if (t instanceof RuntimeException runtime) return runtime;
if (t instanceof Error error) throw error;
return new RuntimeException(t);
}
}
@@ -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.opengl.Display;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@@ -12,6 +13,7 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Predicate;
/**
@@ -72,7 +74,9 @@ public abstract class RenderBackend {
public final void setVSyncMode(VSyncMode preferred) {
setVSyncPreference(preferred);
publishVSyncMode(applyVSyncMode(preferred));
final VSyncMode mode = applyVSyncMode(preferred);
vsyncHonored = applyHonored();
publishVSyncMode(mode);
}
public final void setVSyncPreference(VSyncMode preferred) {
@@ -90,50 +94,146 @@ public abstract class RenderBackend {
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
protected boolean applyHonored() { return true; }
private boolean vsyncHonored = true;
public final boolean vsyncHonored() { return vsyncHonored; }
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
protected final void publishVSyncMode(VSyncMode mode) { effectiveVSyncMode = mode; }
protected final void publishVSyncMode(VSyncMode mode) {
effectiveVSyncMode = mode;
bumpDisplayGeneration();
}
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
private int displayGeneration;
public final int displayGeneration() { return displayGeneration; }
protected final void bumpDisplayGeneration() { displayGeneration++; }
private static final long REFRESH_POLL_NANOS = 1_000_000_000L;
private static final long MIN_PLAUSIBLE_PERIOD_NANOS = 1_000_000L;
private static final long MAX_PLAUSIBLE_PERIOD_NANOS = 50_000_000L;
private long refreshPollNanos;
private int cachedRefreshHz;
private long cachedRefreshPeriodNanos;
public final int getDisplayRefreshRateHz() {
public final long refreshPeriodNanos() {
final long now = System.nanoTime();
if (refreshPollNanos == 0L || now - refreshPollNanos >= REFRESH_POLL_NANOS) {
refreshPollNanos = now;
cachedRefreshHz = queryDisplayRefreshRateHz();
final long fresh = plausiblePeriodNanos(queryRefreshPeriodNanos());
if (fresh != cachedRefreshPeriodNanos) bumpDisplayGeneration();
cachedRefreshPeriodNanos = fresh;
}
return cachedRefreshHz;
return cachedRefreshPeriodNanos;
}
protected int queryDisplayRefreshRateHz() { return 0; }
protected long queryRefreshPeriodNanos() { return 0L; }
public static int refreshHzFrom(int numerator, int denominator, float fallbackHz) {
if (numerator > 0 && denominator > 0) return (numerator + denominator - 1) / denominator;
return fallbackHz > 0.0f ? (int) Math.ceil(fallbackHz) : 0;
static long plausiblePeriodNanos(long periodNanos) {
return (periodNanos >= MIN_PLAUSIBLE_PERIOD_NANOS && periodNanos <= MAX_PLAUSIBLE_PERIOD_NANOS) ? periodNanos : 0L;
}
public static long periodFromRational(int numerator, int denominator, float fallbackHz) {
if (numerator > 0 && denominator > 0) {
final long scaledDenominator = 1_000_000_000L * denominator;
return (scaledDenominator + numerator / 2) / numerator;
}
return fallbackHz > 0.0f ? Math.round(1_000_000_000.0 / fallbackHz) : 0L;
}
private static final int[] TRUNCATED_NTSC_HZ = {23, 29, 59, 71, 119, 143, 239};
private static boolean isTruncatedNtscRate(int hz) {
for (int candidate : TRUNCATED_NTSC_HZ) {
if (candidate == hz) return true;
}
return false;
}
public static long periodFromIntegerHz(int hz) {
if (hz <= 0) return 0L;
if (isTruncatedNtscRate(hz)) {
final long denominator = (hz + 1) * 1000L;
return Math.round(1_000_000_000.0 * 1001 / denominator);
}
return Math.round(1_000_000_000.0 / hz);
}
public static int hzFromPeriod(long periodNanos) {
return periodNanos <= 0L ? 0 : (int) Math.round(1_000_000_000.0 / periodNanos);
}
public boolean hasSwapchainBackpressure() { return false; }
private long frameGateNanos;
private long frameGateEndNanos;
public static final class GateSample {
public long durationNanos;
public long endNanos;
public long gpuWaitNanos;
}
public boolean gateAnchorsNextFrameStart() { return false; }
static final class GateRecorder {
private long gateDurationNanos;
private long gateEndNanos;
private long gpuWaitNanos;
synchronized void recordGate(long durationNanos, long endNanos) {
if (gateEndNanos != 0L && gateDurationNanos >= durationNanos) return;
gateDurationNanos = durationNanos;
gateEndNanos = endNanos;
}
synchronized void recordGpuWait(long nanos) {
gpuWaitNanos += nanos;
}
synchronized void readGateSample(GateSample out) {
final long end = gateEndNanos;
out.durationNanos = end == 0L ? 0L : gateDurationNanos;
out.endNanos = end;
out.gpuWaitNanos = gpuWaitNanos;
gpuWaitNanos = 0L;
gateEndNanos = 0L;
}
}
private final GateRecorder gateRecorder = new GateRecorder();
public boolean wantsDisplayUpdateGateTiming() { return true; }
public void recordFrameGate(long durationNanos, long endNanos) {
frameGateNanos = durationNanos;
frameGateEndNanos = endNanos;
public final void recordGate(long durationNanos, long endNanos) {
gateRecorder.recordGate(durationNanos, endNanos);
}
public long lastFrameGateNanos() { return frameGateNanos; }
public final void recordGpuWait(long nanos) {
gateRecorder.recordGpuWait(nanos);
}
public long lastFrameGateEndNanos() { return frameGateEndNanos; }
public final void readGateSample(GateSample out) {
gateRecorder.readGateSample(out);
}
public void awaitPresent() {}
public void parkNanos(long nanos) {
LockSupport.parkNanos(nanos);
}
public void pumpDisplayMessages() {
Display.processMessages();
}
private boolean presentSuppressed;
public final void setPresentSuppressed(boolean suppressed) { presentSuppressed = suppressed; }
public final boolean isPresentSuppressed() { return presentSuppressed; }
public void onFrameBegin() {
if (GLStateManager.takeStateSeedPending()) {
@@ -144,7 +244,7 @@ public abstract class RenderBackend {
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
public void onPreSwapchainInvalidatingChange(Object change) {}
public void onPreSwapchainInvalidatingChange(Object change) { bumpDisplayGeneration(); }
public boolean handleMakeCurrent(Object drawable) { return false; }
public boolean handleReleaseContext(Object drawable) { return false; }
@@ -153,7 +253,7 @@ public abstract class RenderBackend {
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,65 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class FrameHooks {
private FrameHooks() {}
private static boolean gameLoopStarted;
private static long frameCounter;
private static boolean frameGroupActive;
public static void bootstrapFirstFrame() {
DisplayListManager.abortIfLeaked();
if (!gameLoopStarted) {
gameLoopStarted = true;
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
}
public static void frameEnd() {
popFrameGroup();
RENDER_BACKEND.onFrameEnd();
}
public static void frameBegin() {
if (gameLoopStarted) {
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
}
public static void shutdown() {
BackendManager.shutdown();
}
public static void bindSplashVao() {
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
}
public static void splashFinished() {
GLStateManager.markSplashComplete("SplashProgress.finish");
}
private static void pushFrameGroup() {
CaptureGate.refresh();
GLDebug.pushGroup("frame:", frameCounter);
frameGroupActive = true;
}
private static void popFrameGroup() {
if (frameGroupActive) {
GLDebug.popGroup();
frameGroupActive = false;
frameCounter++;
}
}
}
@@ -1,7 +1,9 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import lombok.Getter;
import java.util.function.Consumer;
@@ -14,7 +16,7 @@ public final class GLSMInitConfig {
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@Getter private final boolean DSAEnabled;
private final boolean dsaEnabled;
@Getter private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) {
@@ -26,7 +28,11 @@ public final class GLSMInitConfig {
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.DSAEnabled = builder.enableDSA;
this.dsaEnabled = builder.dsaEnabled;
}
public boolean isDSAEnabled() {
return dsaEnabled;
}
public static Builder builder() {
@@ -34,14 +40,14 @@ public final class GLSMInitConfig {
}
public static final class Builder {
private boolean lwjglDebug = false;
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private boolean enableDSA = false;
private boolean dsaEnabled = true;
private boolean noErrorChecks;
private Builder() {}
@@ -83,7 +89,7 @@ public final class GLSMInitConfig {
}
public Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
this.dsaEnabled = enableDSA;
return this;
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int savedFilterTextureId;
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
}
public static void clearSavedFilterTexture() {
savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
} else {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
}
}
public static void restoreAniso(float aniso) {
final int id = savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
}
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
public final class ClassDump {
public static final ClassDump DISABLED = new ClassDump(false);
private final boolean enabled;
public ClassDump(boolean enabled) {
this.enabled = enabled;
}
public void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
if (!enabled) return;
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
}
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
if (!enabled) return;
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
}
}
@@ -1,5 +1,10 @@
package com.gtnewhorizons.angelica.glsm.loading;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.Logger;
import javax.swing.JOptionPane;
import java.awt.GraphicsEnvironment;
import java.util.List;
/**
@@ -15,6 +20,10 @@ public final class DependencyVerifier {
*/
public record Check(String resourcePath, String errorMessage) {}
public interface FatalDialog {
void show(String title, String message);
}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
@@ -28,5 +37,39 @@ public final class DependencyVerifier {
}
}
public static List<Check> gtnhLibChecks(String productName) {
return List.of(
new Check("/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class", "Missing dependency: " + productName + " requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new Check("/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
new Check("/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib"));
}
public static void verifyOrHalt(Class<?> anchor, String productName, List<Check> checks, Logger logger, FatalDialog sdlDialog) {
final String message;
try {
verify(anchor, checks);
return;
} catch (RuntimeException ex) {
message = ex.getMessage();
}
final String title = productName + " - Missing Dependency";
System.err.println("FATAL: " + message);
try {
logger.fatal(message);
} catch (Throwable ignored) {}
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
try {
if (lwjgl3ifyMajor >= 3) {
sdlDialog.show(title, message);
} else if (!GraphicsEnvironment.isHeadless()) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
} catch (Throwable ignored) {}
Runtime.getRuntime().halt(1);
}
private DependencyVerifier() {}
}
@@ -1,54 +1,8 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class EarlyRedirectorCore {
private final GLSMRedirector core;
private final String[] targets;
private final String[] exclusions;
public final class EarlyRedirectorCore extends ModRedirector {
public EarlyRedirectorCore(String... extraExclusions) {
this.core = new GLSMRedirector();
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
if (extraExclusions != null && extraExclusions.length > 0) {
Collections.addAll(excl, extraExclusions);
}
this.exclusions = excl.toArray(new String[0]);
}
public byte[] transform(String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
if (!startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, exclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (core.transformClassNode(transformedName, cn)) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String p : prefixes) {
if (name.startsWith(p)) return true;
}
return false;
super(ClassDump.DISABLED, EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone(), extraExclusions);
}
}
@@ -91,9 +91,8 @@ public final class EcosystemNarrowRules {
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
"com.gtnewhorizons.angelica.sdlgpu",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
@@ -0,0 +1,19 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
public final class Lwjgl3ifyExclusions {
public static void apply() {
final var handle = SharedConfig.getRfbTransformers().stream()
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
.findFirst()
.orElse(null);
if (handle == null) return;
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
handle.exclusions().add(exclusion);
}
}
private Lwjgl3ifyExclusions() {}
}
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ModRedirector {
private final GLSMRedirector core = new GLSMRedirector();
private final ClassDump dump;
private final String[] targets;
private final String[] allExclusions;
public ModRedirector(ClassDump dump, String... extraExclusions) {
this(dump, (String[]) null, extraExclusions);
}
public ModRedirector(ClassDump dump, String[] targets, String[] extraExclusions) {
this.dump = dump;
this.targets = targets;
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
Collections.addAll(exclusions, extraExclusions);
this.allExclusions = exclusions.toArray(new String[0]);
}
public ClassDump getDump() {
return dump;
}
public String[] getTransformerExclusions() {
return allExclusions;
}
public boolean shouldTransform(byte[] basicClass) {
return core.shouldTransform(basicClass);
}
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
return core.transformClassNode(transformedName, cn, lwjgl3Aware);
}
public byte[] transform(String transformedName, byte[] basicClass, Object transformer) {
if (basicClass == null) return null;
if (targets != null && !startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, allExclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (!core.transformClassNode(transformedName, cn)) {
return basicClass;
}
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
dump.dumpClass(transformedName, basicClass, bytes, transformer);
return bytes;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String prefix : prefixes) {
if (name.startsWith(prefix)) return true;
}
return false;
}
}
@@ -0,0 +1,76 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public abstract class RfbModRedirector implements RfbClassTransformer {
private static final Attributes.Name LWJGL3_AWARE = new Attributes.Name("Lwjgl3ify-Aware");
private final String id;
private final ModRedirector inner;
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
this.id = id;
this.inner = inner;
}
@Override
public @NotNull String id() {
return id;
}
private static boolean isLwjgl3Aware(@Nullable Manifest manifest) {
return manifest != null && "true".equals(manifest.getMainAttributes().getValue(LWJGL3_AWARE));
}
@Override
public @NotNull String @Nullable [] sortAfter() {
return new String[] {"*", "mixin:mixin"};
}
@Override
public @NotNull String @Nullable [] sortBefore() {
return new String[] {"lwjgl3ify:redirect"};
}
@Override
public @NotNull String @Nullable [] additionalExclusions() {
return inner.getTransformerExclusions();
}
@Override
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
if (!classNode.isPresent()) {
return false;
}
return inner.shouldTransform(classNode.getOriginalBytes());
}
@Override
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
final boolean changed = inner.transformClassNode(className, classNode.getNode(), isLwjgl3Aware(manifest));
if (changed) {
classNode.computeMaxs();
inner.getDump().dumpRFBClass(className, classNode, this);
}
return changed;
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
}
}
@@ -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()
@@ -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;
}
@@ -9,6 +9,9 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import java.nio.Buffer;
import java.util.concurrent.locks.ReentrantLock;
@@ -101,6 +104,22 @@ public class TextureInfoCache {
};
}
public static boolean isProxyTarget(int target) {
return switch (target) {
case GL11.GL_PROXY_TEXTURE_1D,
GL11.GL_PROXY_TEXTURE_2D,
GL12.GL_PROXY_TEXTURE_3D,
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
GL31.GL_PROXY_TEXTURE_RECTANGLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
default -> false;
};
}
private static boolean isTrackedTarget(int target) {
return target == GL11.GL_TEXTURE_2D
|| target == GL11.GL_TEXTURE_1D
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.texture;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
public final class TextureTargets {
private TextureTargets() {}
public static boolean isProxyTarget(int target) {
return switch (target) {
case GL11.GL_PROXY_TEXTURE_1D,
GL11.GL_PROXY_TEXTURE_2D,
GL12.GL_PROXY_TEXTURE_3D,
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
GL31.GL_PROXY_TEXTURE_RECTANGLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
default -> false;
};
}
}
@@ -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);
}
}
}
@@ -42,6 +42,28 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
}
}
@Test
void proxyTargetBindIsNeitherForwardedNorCached() {
drainErrors();
final int t2d = GL11.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE5);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL11.GL_PROXY_TEXTURE_2D, 0);
GLStateManager.glBindTexture(GL12.GL_PROXY_TEXTURE_3D, t2d);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "a proxy target must never reach the driver");
assertEquals(t2d, GLStateManager.getBoundTextureForServerState(5), "a proxy bind must not disturb the cache");
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D));
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
@Test
void popAttribRestoresRecordedTarget() {
drainErrors();
@@ -22,6 +22,21 @@ public class GLSM_TextureUploadGeneration_GLTest {
return texId;
}
@Test
void aLevelBeyondTheShiftWidthClampsToOne() {
final int texId = allocate(64);
try {
assertEquals(64, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 6, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 31, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 32, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 64, GL11.GL_TEXTURE_HEIGHT));
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(texId);
}
}
@Test
void anUploadLeavesANonZeroGeneration() {
final int texId = allocate(4);
@@ -44,22 +44,48 @@ class BackendPolicyTest {
}
@Test
void refreshPrefersTheExactRational() {
assertEquals(144, RenderBackend.refreshHzFrom(144, 1, 0.0f));
assertEquals(60, RenderBackend.refreshHzFrom(60000, 1001, 0.0f));
void periodPrefersTheExactRational() {
assertEquals(16_683_333L, RenderBackend.periodFromRational(60000, 1001, 0.0f));
assertEquals(6_944_444L, RenderBackend.periodFromRational(144, 1, 0.0f));
}
@Test
void refreshRoundsUp() {
assertEquals(144, RenderBackend.refreshHzFrom(0, 0, 143.98f));
assertEquals(60, RenderBackend.refreshHzFrom(0, 0, 59.94f));
assertEquals(60, RenderBackend.refreshHzFrom(0, 0, 60.0f));
void periodFallsBackToTheFloatRate() {
assertEquals(16_683_350L, RenderBackend.periodFromRational(0, 0, 59.94f));
assertEquals(16_666_667L, RenderBackend.periodFromRational(0, 0, 60.0f));
}
@Test
void refreshIsZeroWhenUnknown() {
assertEquals(0, RenderBackend.refreshHzFrom(0, 0, 0.0f));
assertEquals(0, RenderBackend.refreshHzFrom(-1, -1, -1.0f));
assertEquals(0, RenderBackend.refreshHzFrom(144, 0, 0.0f));
void periodIsZeroWhenUnknown() {
assertEquals(0L, RenderBackend.periodFromRational(0, 0, 0.0f));
assertEquals(0L, RenderBackend.periodFromRational(-1, -1, -1.0f));
assertEquals(0L, RenderBackend.periodFromRational(144, 0, 0.0f));
}
@Test
void integerNtscRatesAreAdjusted() {
assertEquals(16_683_333L, RenderBackend.periodFromIntegerHz(59));
assertEquals(6_951_389L, RenderBackend.periodFromIntegerHz(143));
assertEquals(16_666_667L, RenderBackend.periodFromIntegerHz(60));
assertEquals(6_944_444L, RenderBackend.periodFromIntegerHz(144));
assertEquals(0L, RenderBackend.periodFromIntegerHz(0));
assertEquals(0L, RenderBackend.periodFromIntegerHz(-60));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
}
@Test
void hzFromPeriodRoundsToNearest() {
assertEquals(60, RenderBackend.hzFromPeriod(16_666_667L));
assertEquals(144, RenderBackend.hzFromPeriod(6_944_444L));
assertEquals(0, RenderBackend.hzFromPeriod(0L));
assertEquals(0, RenderBackend.hzFromPeriod(-1L));
}
@Test
void plausiblePeriodFilterKeeps20To1000Hz() {
assertEquals(0L, RenderBackend.plausiblePeriodNanos(999_000L));
assertEquals(1_000_000L, RenderBackend.plausiblePeriodNanos(1_000_000L));
assertEquals(50_000_000L, RenderBackend.plausiblePeriodNanos(50_000_000L));
assertEquals(0L, RenderBackend.plausiblePeriodNanos(51_000_000L));
}
}
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GateRecorderTest {
@Test
void theLongestBlockWithinAFrameWinsRegardlessOfOrder() {
final RenderBackend.GateRecorder longestFirst = new RenderBackend.GateRecorder();
longestFirst.recordGate(5_000_000L, 100L);
longestFirst.recordGate(1_000_000L, 200L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
longestFirst.readGateSample(out);
assertEquals(5_000_000L, out.durationNanos);
assertEquals(100L, out.endNanos);
final RenderBackend.GateRecorder shortestFirst = new RenderBackend.GateRecorder();
shortestFirst.recordGate(1_000_000L, 10L);
shortestFirst.recordGate(4_000_000L, 20L);
shortestFirst.readGateSample(out);
assertEquals(4_000_000L, out.durationNanos);
assertEquals(20L, out.endNanos);
}
@Test
void aReadConsumesTheSample() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGate(2_000_000L, 50L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
recorder.readGateSample(out);
assertEquals(0L, out.endNanos);
assertEquals(0L, out.durationNanos);
}
@Test
void gpuWaitAccumulatesAndResetsOnRead() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGate(1_000_000L, 10L);
recorder.recordGpuWait(300L);
recorder.recordGpuWait(700L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
assertEquals(1_000L, out.gpuWaitNanos);
recorder.recordGate(1_000_000L, 11L);
recorder.readGateSample(out);
assertEquals(0L, out.gpuWaitNanos);
}
@Test
void gpuWaitIsDrainedOnASampleFreeRead() {
final RenderBackend.GateRecorder recorder = new RenderBackend.GateRecorder();
recorder.recordGpuWait(450L);
final RenderBackend.GateSample out = new RenderBackend.GateSample();
recorder.readGateSample(out);
assertEquals(0L, out.endNanos);
assertEquals(450L, out.gpuWaitNanos);
recorder.readGateSample(out);
assertEquals(0L, out.gpuWaitNanos);
}
}
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Timeout(10)
class MainThreadPumpTest {
private static final class DeferredExecutor implements Executor {
final Deque<Runnable> tasks = new ConcurrentLinkedDeque<>();
@Override
public void execute(Runnable command) {
tasks.add(command);
}
void runNext() {
tasks.removeFirst().run();
}
void awaitTask() {
while (tasks.isEmpty()) {
Thread.onSpinWait();
}
}
}
private static final class InlineExecutor implements Executor {
final List<Runnable> submitted = Collections.synchronizedList(new ArrayList<>());
@Override
public void execute(Runnable command) {
submitted.add(command);
command.run();
}
}
private static void awaitWaiting(Thread t) {
while (t.getState() != Thread.State.WAITING && t.getState() != Thread.State.TIMED_WAITING) {
assertTrue(t.isAlive(), "thread died instead of blocking");
Thread.onSpinWait();
}
}
private static MainThreadPump pump(Executor executor, Runnable pumpBody, Runnable pollBody) {
return new MainThreadPump(executor, () -> false, pumpBody, pollBody);
}
@Test
void repeatedPumpsSubmitTheSameRunnable() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger pumps = new AtomicInteger();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = pump(executor, pumps::incrementAndGet, polls::incrementAndGet);
for (int i = 0; i < 16; i++) {
p.pumpMessages();
}
assertEquals(16, executor.submitted.size());
assertEquals(16, pumps.get());
assertEquals(16, polls.get());
final Runnable first = executor.submitted.get(0);
for (Runnable r : executor.submitted) {
assertSame(first, r, "pump must not allocate a new task per call");
}
}
@Test
void pumpBlocksUntilTheMainThreadRunsTheTask() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = pump(executor, () -> {}, polls::incrementAndGet);
final Thread caller = new Thread(p::pumpMessages, "pump-caller");
caller.start();
awaitWaiting(caller);
executor.awaitTask();
assertEquals(1, executor.tasks.size());
assertEquals(0, polls.get());
executor.runNext();
caller.join();
assertEquals(1, polls.get());
}
@Test
void aThrowingBodyPropagatesToTheCallerAndDoesNotLeaveItParked() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger polls = new AtomicInteger();
final AtomicInteger pumps = new AtomicInteger();
final MainThreadPump p = pump(executor, () -> {
if (pumps.incrementAndGet() == 1) throw new IllegalStateException("boom");
}, polls::incrementAndGet);
final IllegalStateException thrown = assertThrows(IllegalStateException.class, p::pumpMessages);
assertEquals("boom", thrown.getMessage());
assertEquals(0, polls.get(), "poll must not run when the pump body failed");
p.pumpMessages();
assertEquals(1, polls.get(), "a failed pump must not poison the next one");
}
@Test
void mainThreadCallersRunInlineWithoutTheExecutor() {
final InlineExecutor executor = new InlineExecutor();
final AtomicInteger pumps = new AtomicInteger();
final AtomicInteger polls = new AtomicInteger();
final MainThreadPump p = new MainThreadPump(executor, () -> true, pumps::incrementAndGet,
polls::incrementAndGet);
p.pumpMessages();
assertTrue(executor.submitted.isEmpty(), "the main thread must not hop through the executor");
assertEquals(1, pumps.get());
assertEquals(1, polls.get());
}
@Test
void anInterruptedCallerCompletesAndKeepsItsInterruptFlag() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final MainThreadPump p = pump(executor, () -> {}, () -> {});
final AtomicInteger stillInterrupted = new AtomicInteger(-1);
final Thread caller = new Thread(() -> {
p.pumpMessages();
stillInterrupted.set(Thread.currentThread().isInterrupted() ? 1 : 0);
}, "pump-interrupted-caller");
caller.start();
awaitWaiting(caller);
executor.awaitTask();
caller.interrupt();
assertFalse(executor.tasks.isEmpty());
executor.runNext();
caller.join();
assertEquals(1, stillInterrupted.get());
}
}
@@ -0,0 +1,15 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GLSMInitConfigDefaultsTest {
@Test
void emptyBuilderLeavesDsaToTheProbe() {
assertTrue(GLSMInitConfig.builder().build().isDSAEnabled());
assertFalse(GLSMInitConfig.builder().enableDSA(false).build().isDSAEnabled());
}
}
@@ -0,0 +1,127 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 ModRedirectorTest {
private static final String EXTRA_EXCLUSION = "com.example.excluded";
private static ClassNode emptyClass() {
final ClassNode cn = new ClassNode();
cn.version = Opcodes.V1_8;
cn.access = Opcodes.ACC_PUBLIC;
cn.name = "com/example/TestClass";
cn.superName = "java/lang/Object";
cn.methods = new ArrayList<>();
final MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "test", "()V", null, null);
mn.instructions.add(new InsnNode(Opcodes.RETURN));
cn.methods.add(mn);
return cn;
}
private static byte[] bytesWithoutGlCall() {
return toBytes(emptyClass());
}
private static byte[] bytesWithGlCall() {
final ClassNode cn = emptyClass();
final MethodNode mn = cn.methods.get(0);
mn.instructions.clear();
mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false));
mn.instructions.add(new InsnNode(Opcodes.RETURN));
return toBytes(cn);
}
private static byte[] toBytes(ClassNode cn) {
final ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
return cw.toByteArray();
}
private static ModRedirector redirector() {
return new ModRedirector(ClassDump.DISABLED, EXTRA_EXCLUSION);
}
private static void assertStandardExclusions(String[] actual) {
final List<String> exclusions = Arrays.asList(actual);
final String[] coreExclusions = new GLSMRedirector().getCoreExclusions();
for (String coreExclusion : coreExclusions) {
assertTrue(exclusions.contains(coreExclusion), coreExclusion);
}
assertTrue(exclusions.contains("com.gtnewhorizons.angelica.lwjgl3."));
assertTrue(exclusions.contains(EXTRA_EXCLUSION));
assertEquals(coreExclusions.length + 2, exclusions.size(), exclusions.toString());
}
@Test
void exclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(redirector().getTransformerExclusions());
}
@Test
void earlyRedirectorExclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions());
}
@Test
void nullInputReturnsNull() {
assertNull(redirector().transform("com.example.TestClass", null, this));
}
@Test
void excludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform(EXTRA_EXCLUSION + ".TestClass", original, this));
}
@Test
void coreExcludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform("org.lwjgl.opengl.TestClass", original, this));
}
@Test
void unchangedClassReturnsOriginalArray() {
final byte[] original = bytesWithoutGlCall();
assertSame(original, redirector().transform("com.example.TestClass", original, this));
}
@Test
void redirectedClassReturnsNewArray() {
final byte[] original = bytesWithGlCall();
assertNotSame(original, redirector().transform("com.example.TestClass", original, this));
}
@Test
void earlyRedirectorSkipsClassesOutsideItsTargets() {
final byte[] original = bytesWithGlCall();
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
assertSame(original, early.transform("com.example.TestClass", original, this));
}
@Test
void earlyRedirectorTransformsItsTargets() {
final byte[] original = bytesWithGlCall();
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
final String target = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS[0] + "TestClass";
assertNotSame(original, early.transform(target, original, this));
}
}
@@ -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;
@@ -300,6 +302,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);
}
}
}
@@ -39,7 +39,12 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
GLSMExtension.setMainThread(Thread.currentThread());
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(800, 600)
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("test");
@@ -69,6 +69,9 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
displayMode = new DisplayMode(800, 600);
final GLSMInitConfig config = GLSMInitConfig.builder()
.displaySize(displayMode.getWidth(), displayMode.getHeight())
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build();
setMainThread(Thread.currentThread());
+71
View File
@@ -0,0 +1,71 @@
import com.gtnewhorizons.retrofuturagradle.MinecraftExtension
import org.gradle.accessors.dm.LibrariesForLibs
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
val libs = the<LibrariesForLibs>()
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
}
}
repositories {
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
content { includeGroup("org.lwjgl") }
}
}
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 (name in forwardedRunProperties) {
jvmArgs("-D$name=${System.getProperty(name)}")
}
}
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
val lwjglDebug = project.extra.has("lwjglDebug") && project.extra["lwjglDebug"] as Boolean
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && name != "runClient" && isMacOs) {
jvmArgs("-XstartOnFirstThread")
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")
environment("MTL_SHADER_VALIDATION", "1")
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
environment("MTL_DEBUG_LAYER", "1")
environment("MallocScribble", "1")
environment("MallocPreScribble", "1")
environment("MallocGuardEdges", "1")
environment("MallocStackLogging", "1")
environment("MallocStackLoggingNoCompact", "1")
}
}
}
configure<MinecraftExtension> {
lwjgl3Bindings.addAll("shaderc", "spvc")
lwjgl3Version = libs.versions.lwjgl3.get()
}
tasks.named<Copy>("processResources") {
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to projectVersion)
}
}
+28
View File
@@ -0,0 +1,28 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
val embedOnly: Configuration by configurations
tasks.named<ShadowJar>("shadowJar") {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mergeServiceFiles()
minimize {
exclude(project(rootProject.path))
exclude(project(":glsm"))
exclude(project(":lwjgl3-backend"))
exclude(project(":sdl-gpu"))
exclude(dependency("org.taumc:.*:.*"))
exclude(dependency("org.embeddedt.celeritas:.*:.*"))
exclude(dependency("org.antlr:.*:.*"))
}
relocate("org.taumc.celeritas", "org.taumc.celeritas")
relocate("org.embeddedt", "org.embeddedt")
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
}
+4 -3
View File
@@ -6,10 +6,10 @@ log4j = "2.0-beta9"
asmCompile = "5.0.3"
# End Frozen
lwjgl3 = "3.4.2"
lwjgl3 = "3.4.2-SNAPSHOT"
lwjgl3ify = "3.0.31"
celeritas = "2.5.10-GTNH"
celeritas = "2.5.11-GTNH"
gtnhlib = "0.11.36"
eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
@@ -30,10 +30,10 @@ asmBuild = "9.7"
mockito = "3.12.4"
archunit = "1.4.1"
ant = "1.8.2"
distantHorizonsApi = "5.1.0"
[libraries]
celeritas-common = { module = "org.embeddedt.celeritas:celeritas-common", version.ref = "celeritas" }
celeritas-lwjgl2-service = { module = "org.embeddedt.celeritas:celeritas-lwjgl2-service", version.ref = "celeritas" }
eventbus = { module = "net.minecraftforge:eventbus", version.ref = "eventbus" }
glsl-transformation-lib = { module = "org.taumc:glsl-transformation-lib", version.ref = "glslTransformLib" }
antlr4-runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr4Runtime" }
@@ -58,6 +58,7 @@ junit-platform-reporting = { module = "org.junit.platform:junit-platform-reporti
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" }
ant = { module = "org.apache.ant:ant", version.ref = "ant" }
distant-horizons-api = { module = "maven.modrinth:DistantHorizonsApi", version.ref = "distantHorizonsApi" }
asm-tree-compile = { module = "org.ow2.asm:asm-tree", version.ref = "asmCompile" }
asm-tree-test = { module = "org.ow2.asm:asm-tree", version.ref = "asmTest" }
asm-build = { module = "org.ow2.asm:asm", version.ref = "asmBuild" }
+1
View File
@@ -61,6 +61,7 @@ dependencies {
api(project(":glsm"))
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
compileOnly(libs.celeritas.common) { isTransitive = false }
// LWJGL3
@@ -4,9 +4,13 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLESCaps;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.DebugMessageHandler;
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjglx.Lwjgl3ifyEventLoop;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.ARBClearTexture;
@@ -36,7 +40,9 @@ import org.lwjgl.opengl.GLDebugMessageCallback;
import org.lwjgl.sdl.SDL_DisplayMode;
import org.lwjgl.sdl.SDL_DropEvent;
import org.lwjgl.sdl.SDL_EventFilter;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.sdl.SDLTimer;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack;
@@ -71,6 +77,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
private SDL_EventFilter dropEventFilter;
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
private volatile boolean watchingDrops;
private boolean swapIntervalHonored = true;
@Override
public void init() {
@@ -173,19 +180,50 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
final boolean enabled = preferred.tearFree();
SDLVideo.SDL_GL_SetSwapInterval(enabled ? 1 : 0);
return enabled ? VSyncMode.ON : VSyncMode.OFF;
final int want = preferred.tearFree() ? 1 : 0;
final boolean setOk = SDLVideo.SDL_GL_SetSwapInterval(want);
final String setError = setOk ? null : SDLError.SDL_GetError();
int got = want;
try (MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer readback = stack.callocInt(1);
if (SDLVideo.SDL_GL_GetSwapInterval(readback)) got = readback.get(0);
}
if (!setOk) {
LOGGER.warn("SDL_GL_SetSwapInterval({}) failed: {}", want, setError);
} else if (got != want && !(want == 1 && got == -1)) {
LOGGER.warn("Requested swap interval {} but the driver reports {}", want, got);
}
swapIntervalHonored = want == 0 || got != 0;
return want != 0 && swapIntervalHonored ? VSyncMode.ON : VSyncMode.OFF;
}
@Override
protected int queryDisplayRefreshRateHz() {
protected boolean applyHonored() {
return swapIntervalHonored;
}
@Override
protected long queryRefreshPeriodNanos() {
final long window = Display.getWindow();
final int displayId = window == 0 ? SDLVideo.SDL_GetPrimaryDisplay() : SDLVideo.SDL_GetDisplayForWindow(window);
if (displayId == 0) return 0;
if (displayId == 0) return 0L;
final SDL_DisplayMode mode = SDLVideo.SDL_GetCurrentDisplayMode(displayId);
if (mode == null) return 0;
return refreshHzFrom(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
if (mode == null) return 0L;
return periodFromRational(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
}
@Override
public void parkNanos(long nanos) {
SDLTimer.SDL_DelayNS(nanos);
}
private static final class Pump {
static final MainThreadPump INSTANCE = new MainThreadPump(MainStartOnFirstThread.instance(), MainThreadExec::isMainThread, Lwjgl3ifyEventLoop::pumpEvents, MainThreadPump::pollInput);
}
@Override
public void pumpDisplayMessages() {
Pump.INSTANCE.pumpMessages();
}
@Override
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Usage: scripts/flyby-bench.sh <name> ffp|pack [flyby-capture.sh args...]
# Env: FLYBY_SCENE, 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}"
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"
+44 -7
View File
@@ -5,25 +5,59 @@
# * Game waits for the tracy connection, runs the route, returns to the original coordinates and exits.
# * tracy-capture finalizes
#
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length]
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]
# route - straight | pan | circuit | static
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
# 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_SPEED - blocks per tick. (0.5 ~= creative flight).
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
# QUICKPLAY_WORLD - save to auto-load (default: latest).
# 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).
# Launch the game to the main menu and load your benchmark save; the run starts once the world is up.
# QuickPlay loads QUICKPLAY_WORLD on launch; the run starts once the world is up.
#
# Ensure the chunks in the route have been generated before capturing
set -euo pipefail
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length]}"
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]}"
ROUTE="${2:-straight}"
LENGTH="${3:-0}"
if [ $# -ge 3 ]; then
shift 3
else
shift $#
fi
WARMUP="${FLYBY_WARMUP:-400}"
TRACY_CAPTURE="${TRACY_CAPTURE:-tracy-capture}"
BACKEND_ARGS=()
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
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
cat >&2 <<'EOF'
error: tracy-capture not found.
@@ -55,8 +89,7 @@ cleanup() {
}
trap cleanup EXIT
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default})"
echo " load your benchmark save once the main menu appears"
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default} scene=${SCENE_ABS:-none})"
./gradlew runClient25 --console=plain \
-Dangelica.flyby.route="$ROUTE" \
@@ -64,7 +97,11 @@ echo " load your benchmark save once the main menu appears"
-Dangelica.flyby.speed="${FLYBY_SPEED:-0}" \
-Dangelica.flyby.warmupTicks="$WARMUP" \
-Dangelica.flyby.waitForTracy=true \
-Dangelica.flyby.exitWhenDone=true
-Dangelica.flyby.exitWhenDone=true \
-DquickPlaySingleplayer="${QUICKPLAY_WORLD:-latest}" \
${BACKEND_ARGS[@]+"${BACKEND_ARGS[@]}"} \
${SCENE_ARGS[@]+"${SCENE_ARGS[@]}"} \
"$@"
echo "==> game exited, finalising capture"
cleanup
+156
View File
@@ -0,0 +1,156 @@
#!/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.streamedInstances", "tesr.liveFallbacks", "tesr.bail.hurtFlash", "tesr.bail.foreignProgram",
"tesr.bail.material", "tesr.bail.texture", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
"sdl.acquireWaitUs",
]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "tesrOpaque"]
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 player; 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 player; 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}}
+66 -1
View File
@@ -77,6 +77,9 @@ dependencies {
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(testFixtures(project(":glsm")))
testImplementation(project(":glsm")) {
capabilities { requireCapability("${project.group}:glsm-stubs") }
}
testImplementation(libs.celeritas.common) { isTransitive = false }
testImplementation(libs.lwjgl3.opengl)
// GL.<clinit> loads liblwjgl_opengl.so. Test scope only: a root-level runtimeOnly collides with lwjgl3Bindings.
@@ -84,15 +87,77 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
testRuntimeOnly(libs.commons.lang3)
testRuntimeOnly(libs.guava)
testImplementation(libs.asm.tree.test)
}
val glsmSdlTag = "glsm-sdl"
val glsmSdlStubs = sourceSets.create("glsmSdlStubs") {
java.srcDir("src/glsmSdlStubs/java")
}
val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
java.srcDir("src/glsmSdlAgent/java")
}
dependencies {
"glsmSdlAgentCompileOnly"(project(":glsm"))
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
testImplementation(glsmSdlAgent.output)
}
tasks.test {
useJUnitPlatform()
useJUnitPlatform {
excludeTags(glsmSdlTag)
}
if (System.getProperty("os.name").startsWith("Mac")) {
jvmArgs("-XstartOnFirstThread")
}
}
val glsmSdlRedirectAgentJar by tasks.registering(Jar::class) {
archiveClassifier = "glsm-sdl-redirect-agent"
from(glsmSdlAgent.output)
manifest {
attributes("Premain-Class" to "com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlRedirectAgent")
}
}
val glsmSdlTest by tasks.registering(Test::class) {
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmSdlStubs.output.classesDirs.plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
)
useJUnitPlatform {
includeTags(glsmSdlTag)
}
dependsOn(tasks.named("glsmSdlStubsClasses"), glsmSdlRedirectAgentJar)
val agentJarPath = glsmSdlRedirectAgentJar.flatMap { it.archiveFile }.map { it.asFile.path }
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${agentJarPath.get()}") })
jvmArgs(
"-Dangelica.sdlgpu.enable=true",
"-Dangelica.sdlgpu.disablePresenterThread=true",
"-Dceleritas.lwjglService=com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService",
)
if (System.getProperty("os.name").startsWith("Mac")) {
jvmArgs("-XstartOnFirstThread")
}
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
mustRunAfter(tasks.test)
}
tasks.check { dependsOn(glsmSdlTest) }
tasks.named<JavaCompile>("compileJava") {
val classesDir = destinationDirectory
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public final class GlsmSdlRedirectAgent {
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
private static final List<String> FAILURES = Collections.synchronizedList(new ArrayList<>());
private static final AtomicInteger TRANSFORMED_COUNT = new AtomicInteger();
private GlsmSdlRedirectAgent() {}
public static void premain(String args, Instrumentation instrumentation) {
instrumentation.addTransformer(new Redirector());
}
public static List<String> failures() {
return List.copyOf(FAILURES);
}
public static int transformedCount() {
return TRANSFORMED_COUNT.get();
}
private static final class Redirector implements ClassFileTransformer {
private final GLSMRedirector redirector = new GLSMRedirector();
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
if (className == null || !className.startsWith(GTNHLIB)) return null;
try {
if (!redirector.shouldTransform(classfile)) return null;
final ClassNode node = new ClassNode();
new ClassReader(classfile).accept(node, 0);
if (!redirector.transformClassNode(className.replace('/', '.'), node)) return null;
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
node.accept(writer);
TRANSFORMED_COUNT.incrementAndGet();
return writer.toByteArray();
} catch (Throwable t) {
FAILURES.add(className + ": " + t);
return null;
}
}
}
}
@@ -0,0 +1,10 @@
package org.lwjgl;
public final class Sys {
private Sys() {}
public static boolean is64Bit() {
return !"32".equals(System.getProperty("sun.arch.data.model"));
}
}
@@ -0,0 +1,31 @@
package org.lwjgl.opengl;
public class ContextCapabilities {
public boolean OpenGL30 = true;
public boolean OpenGL31 = true;
public boolean OpenGL32 = true;
public boolean OpenGL33 = true;
public boolean OpenGL40 = true;
public boolean OpenGL42 = true;
public boolean OpenGL43 = true;
public boolean OpenGL44 = true;
public boolean OpenGL45 = true;
public boolean GL_ARB_buffer_storage = true;
public boolean GL_ARB_clear_texture = true;
public boolean GL_ARB_compute_shader = true;
public boolean GL_ARB_direct_state_access = false;
public boolean GL_ARB_draw_buffers_blend = false;
public boolean GL_ARB_multi_draw_indirect = false;
public boolean GL_ARB_sampler_objects = true;
public boolean GL_ARB_shader_image_load_store = true;
public boolean GL_ARB_shader_storage_buffer_object = true;
public boolean GL_ARB_tessellation_shader = false;
public boolean GL_EXT_direct_state_access = false;
public boolean GL_EXT_gpu_shader4 = true;
public boolean GL_EXT_shader_image_load_store = true;
public boolean GL_NVX_gpu_memory_info = false;
}
@@ -0,0 +1,8 @@
package org.lwjgl.opengl;
public interface Drawable {
void makeCurrent();
void releaseContext();
}
@@ -0,0 +1,12 @@
package org.lwjgl.opengl;
public final class GLContext {
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
private GLContext() {}
public static ContextCapabilities getCapabilities() {
return CAPABILITIES;
}
}
@@ -150,6 +150,7 @@ public final class SDLGPUGate {
try {
device().claimWindow(ctx.window());
engaged = true;
SDLGPULWJGLService.bind();
BackendManager.RENDER_BACKEND.onPostWindowCreate(ctx.window());
} catch (Throwable t) {
initFailure = t;
@@ -158,9 +159,10 @@ public final class SDLGPUGate {
}
public static void fallBackToGL() {
if (SDLGPULWJGLService.isOfferedAsAvailable()) {
if (SDLGPULWJGLService.isConstructed()) {
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
}
SDLGPULWJGLService.unbind();
disarmed = true;
engaged = false;
deviceReady = false;
@@ -5,10 +5,10 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.sdlgpu.device.MemoryStackWrapper;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import com.mitchej123.lwjgl.DebugMessageHandler;
import com.mitchej123.lwjgl.GLExtension;
import com.mitchej123.lwjgl.LWJGLService;
import com.mitchej123.lwjgl.MemoryStack;
import org.taumc.celeritas.lwjgl.DebugMessageHandler;
import org.taumc.celeritas.lwjgl.GLExtension;
import org.taumc.celeritas.lwjgl.LWJGLService;
import org.taumc.celeritas.lwjgl.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.nio.Buffer;
@@ -17,19 +17,32 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
public final class SDLGPULWJGLService extends LWJGLService {
public final class SDLGPULWJGLService implements LWJGLService {
private static volatile boolean offeredAsAvailable;
private static final ThreadLocal<MemoryStackWrapper> STACK =
ThreadLocal.withInitial(() -> new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackGet()));
public static boolean isOfferedAsAvailable() { return offeredAsAvailable; }
private static volatile boolean constructed;
@Override public int getPriority() {
if (!SystemProperties.USE_SDL_GPU || !SDLGPUGate.isSDLGPUAvailable() || !SDLGPUGate.isEngaged()) {
return PRIORITY_UNAVAILABLE;
public static SDLGPULWJGLService create() {
if (!SDLGPUGate.isEngaged()) {
throw new IllegalStateException("SDL GPU is not engaged; " + SystemProperties.KEY_CELERITAS_LWJGL_SERVICE + " must not name " + SDLGPULWJGLService.class.getName());
}
constructed = true;
return new SDLGPULWJGLService();
}
public static boolean isConstructed() { return constructed; }
static void bind() {
System.setProperty(SystemProperties.KEY_CELERITAS_LWJGL_SERVICE, SDLGPULWJGLService.class.getName());
}
static void unbind() {
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
if (SDLGPULWJGLService.class.getName().equals(System.getProperty(key))) {
System.clearProperty(key);
}
offeredAsAvailable = true;
return 200;
}
@Override public boolean isOpenGLVersionSupported(int major, int minor) {
@@ -47,7 +60,6 @@ public final class SDLGPULWJGLService extends LWJGLService {
ARB_uniform_buffer_object,
ARB_vertex_array_object,
ARB_shader_storage_buffer_object,
ARB_instanced_arrays,
KHR_debug
-> true;
case ARB_timer_query,
@@ -63,30 +75,58 @@ public final class SDLGPULWJGLService extends 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 glBufferSubData(int target, long offset, ByteBuffer data) { BackendManager.RENDER_BACKEND.bufferSubData(target, offset, data); }
@Override public void glBufferSubData(int target, long offset, long size, long data) {
BackendManager.RENDER_BACKEND.bufferSubData(target, offset, MemoryUtil.memByteBuffer(data, (int) size));
@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 glDrawBuffers(int[] buffers) {
try (var stack = MemoryStack.stackPush()) {
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 GLStateManager.glMapBufferRange(target, offset, length, flags);
}
@Override public void glBufferStorage(int target, long size, int flags) { BackendManager.RENDER_BACKEND.bufferStorage(target, size, flags); }
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) { return BackendManager.RENDER_BACKEND.mapBufferRange(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); }
@@ -96,63 +136,80 @@ public final class SDLGPULWJGLService extends 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;
@@ -164,7 +221,7 @@ public final class SDLGPULWJGLService extends 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);
@@ -183,29 +240,42 @@ public final class SDLGPULWJGLService extends 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); }
@@ -215,23 +285,26 @@ public final class SDLGPULWJGLService extends 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() {
return new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackPush());
org.lwjgl.system.MemoryStack.stackPush();
return STACK.get();
}
@Override public long nmemAlloc(long size) { return MemoryUtil.nmemAlloc(size); }
File diff suppressed because it is too large. Load diff
@@ -335,7 +335,7 @@ public final class Device {
}
vsyncMode = candidate;
LOG.info("SDL present mode {} (preferred={}), refresh {}Hz, {}, video={}, framesInFlight={}, supported: vsync={} immediate={} mailbox={}",
candidate, preferred, getDisplayRefreshRateHz(),
candidate, preferred, RenderBackend.hzFromPeriod(getDisplayRefreshPeriodNanos()),
(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0 ? "fullscreen" : "windowed",
SDL_GetCurrentVideoDriver(), FRAMES_IN_FLIGHT,
supportsVSyncMode(VSyncMode.ON), supportsVSyncMode(VSyncMode.OFF), supportsVSyncMode(VSyncMode.MAILBOX));
@@ -376,13 +376,13 @@ public final class Device {
};
}
public int getDisplayRefreshRateHz() {
public long getDisplayRefreshPeriodNanos() {
final long window = Display.getWindow();
final int displayId = window == 0 ? SDL_GetPrimaryDisplay() : SDL_GetDisplayForWindow(window);
if (displayId == 0) return 0;
if (displayId == 0) return 0L;
final SDL_DisplayMode mode = SDL_GetCurrentDisplayMode(displayId);
if (mode == null) return 0;
return RenderBackend.refreshHzFrom(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
if (mode == null) return 0L;
return RenderBackend.periodFromRational(mode.refresh_rate_numerator(), mode.refresh_rate_denominator(), mode.refresh_rate());
}
public int getSwapchainTextureFormat() {
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.device;
import com.mitchej123.lwjgl.MemoryStack;
import org.taumc.celeritas.lwjgl.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -54,6 +54,28 @@ public final class ContextState {
public final PixelStoreState pixelStore = new PixelStoreState();
public static final class ProxyTextureState {
public int target;
public int width;
public int height;
public int depth;
public int internalFormat;
public void accept(int target, int width, int height, int depth, int internalFormat) {
this.target = target;
this.width = width;
this.height = height;
this.depth = depth;
this.internalFormat = internalFormat;
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
}
public final ProxyTextureState proxyTexture = new ProxyTextureState();
public static final class VAOState {
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
public int attribEnabledMask;
@@ -211,6 +233,7 @@ public final class ContextState {
public int boundArrayBuffer;
public int boundIndirectBuffer;
public int boundDispatchIndirectBuffer;
public int boundUniformBuffer;
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
@@ -226,6 +249,7 @@ public final class ContextState {
case GL30.GL_PIXEL_UNPACK_BUFFER -> boundPixelUnpackBuffer;
case GL30.GL_PIXEL_PACK_BUFFER -> boundPixelPackBuffer;
case GL40.GL_DRAW_INDIRECT_BUFFER -> boundIndirectBuffer;
case GL43.GL_DISPATCH_INDIRECT_BUFFER -> boundDispatchIndirectBuffer;
case GL31.GL_COPY_READ_BUFFER -> boundCopyReadBuffer;
case GL31.GL_COPY_WRITE_BUFFER -> boundCopyWriteBuffer;
case GL43.GL_SHADER_STORAGE_BUFFER -> boundSSBO;
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
@@ -43,6 +45,7 @@ public final class FrameManager {
private static final Tracy.ZoneId Z_SDL_SUBMIT = Tracy.zoneId("sdlSubmit", Tracy.COLOR_SWAP);
private static final Tracy.ZoneId Z_SDL_ACQUIRE_WAIT = Tracy.zoneId("sdlAcquireWait", Tracy.COLOR_SWAP);
private static final Tracy.ZoneId Z_SDL_GPU_WAIT = Tracy.zoneId("sdlGpuWait", Tracy.COLOR_SWAP);
private final Device device;
private ResourceManager resourceManager;
@@ -83,8 +86,7 @@ public final class FrameManager {
public int batchBreakSamplerThisFrame;
public int stateAppliesThisFrame;
public long acquireWaitNanosThisFrame;
public volatile long lastFrameGateNanos;
public volatile long lastFrameGateEndNanos;
public long gpuWaitNanosThisFrame;
public int midFrameSubmitsThisFrame;
public int mipGensThisFrame;
public int mipGenSubmitsThisFrame;
@@ -597,6 +599,7 @@ public final class FrameManager {
f.stateAppliesThisFrame = 0;
f.acquireWaitNanosThisFrame = 0;
f.mipGensThisFrame = 0;
f.gpuWaitNanosThisFrame = 0;
f.mipGenSubmitsThisFrame = 0;
f.uploadFlushSubmitsThisFrame = 0;
f.blitsThisFrame = 0;
@@ -773,7 +776,10 @@ public final class FrameManager {
}
public int getSwapchainFormat() {
return device.getSwapchainTextureFormat();
if (device.getClaimedWindow() != 0L) return device.getSwapchainTextureFormat();
if (resourceManager == null || !finalTarget.isReady()) return 0;
final FboState fbo = resourceManager.getFbo(finalTarget.fboId());
return fbo != null ? fbo.getColorFormat() : 0;
}
public boolean isFrameActive() {
@@ -873,14 +879,6 @@ public final class FrameManager {
registeredFrames.clear();
}
public long lastFrameGateNanos() {
return frame().lastFrameGateNanos;
}
public long lastFrameGateEndNanos() {
return frame().lastFrameGateEndNanos;
}
private volatile Presenter presenter;
public void setPresenter(Presenter presenter) {
@@ -894,6 +892,7 @@ public final class FrameManager {
}
public void presentFinalTarget() {
if (BackendManager.RENDER_BACKEND.isPresentSuppressed()) return;
if (!finalTarget.hasContent()) return;
present(finalTarget.colorTexture(), finalTarget.width(), finalTarget.height(), SDL_FLIP_NONE, false);
}
@@ -916,16 +915,13 @@ public final class FrameManager {
}
private final AtomicLong windowAcquireWaitNanos = new AtomicLong();
private final AtomicLong windowGpuWaitNanos = new AtomicLong();
private final AtomicInteger windowPresentSkips = new AtomicInteger();
private void drainWindowPresentCounters(FrameState f) {
if (f == windowFrame) return;
final long acquire = windowAcquireWaitNanos.getAndSet(0L);
if (acquire != 0L) {
f.acquireWaitNanosThisFrame += acquire;
f.lastFrameGateNanos = windowFrame.lastFrameGateNanos;
f.lastFrameGateEndNanos = windowFrame.lastFrameGateEndNanos;
}
f.acquireWaitNanosThisFrame += windowAcquireWaitNanos.getAndSet(0L);
f.gpuWaitNanosThisFrame += windowGpuWaitNanos.getAndSet(0L);
f.presentSkipsThisFrame += windowPresentSkips.getAndSet(0);
}
@@ -934,6 +930,11 @@ public final class FrameManager {
else f.acquireWaitNanosThisFrame += waitNanos;
}
private void noteGpuWait(FrameState f, long waitNanos) {
if (f == windowFrame) windowGpuWaitNanos.addAndGet(waitNanos);
else f.gpuWaitNanosThisFrame += waitNanos;
}
private void notePresentSkip(FrameState f) {
if (f == windowFrame) windowPresentSkips.incrementAndGet();
else f.presentSkipsThisFrame++;
@@ -962,13 +963,31 @@ public final class FrameManager {
final int w;
final int h;
final boolean callOk;
final boolean swapchainReady;
Tracy.beginZone(Z_SDL_GPU_WAIT);
try {
swapchainReady = SDL_WaitForGPUSwapchain(device.getDevice(), window);
} finally {
Tracy.endZone();
}
final long t1 = System.nanoTime();
BackendManager.RENDER_BACKEND.recordGpuWait(t1 - t0);
noteGpuWait(f, t1 - t0);
if (!swapchainReady) {
SDL_CancelGPUCommandBuffer(cb);
f.swapchainUnavailable = true;
notePresentSkip(f);
return false;
}
try (var stack = stackPush()) {
final PointerBuffer pTexture = stack.pointers(0);
final IntBuffer pWidth = stack.ints(0);
final IntBuffer pHeight = stack.ints(0);
Tracy.beginZone(Z_SDL_ACQUIRE_WAIT);
try {
callOk = SDL_WaitAndAcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
callOk = SDL_AcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
} finally {
Tracy.endZone();
}
@@ -981,9 +1000,6 @@ public final class FrameManager {
throw re;
}
final long tEnd = System.nanoTime();
f.lastFrameGateNanos = tEnd - t0;
f.lastFrameGateEndNanos = tEnd;
noteAcquireWait(f, tEnd - t0);
if (!callOk || tex == 0) {
SDL_CancelGPUCommandBuffer(cb);
@@ -992,6 +1008,9 @@ public final class FrameManager {
return false;
}
BackendManager.RENDER_BACKEND.recordGate(tEnd - t0, tEnd);
noteAcquireWait(f, tEnd - t1);
try (MemoryStack stack = stackPush()) {
final long info = stack.ncalloc(SDL_GPUBlitInfo.ALIGNOF, 1, SDL_GPUBlitInfo.SIZEOF);
writeBlitRegion(info + SDL_GPUBlitInfo.SOURCE, srcTexture, srcW, srcH);
@@ -8,23 +8,34 @@ public final class Presenter {
private final FrameManager frameManager;
private final Executor windowThreadExecutor;
private final Semaphore pending = new Semaphore(1);
private final Runnable task;
private long srcTexture;
private int srcW;
private int srcH;
private int flipMode;
public Presenter(FrameManager frameManager, Executor windowThreadExecutor) {
this.frameManager = frameManager;
this.windowThreadExecutor = windowThreadExecutor;
this.task = () -> {
try {
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
} finally {
pending.release();
}
};
}
public void requestPresent(long srcTexture, int srcW, int srcH, int flipMode) {
pending.acquireUninterruptibly();
this.srcTexture = srcTexture;
this.srcW = srcW;
this.srcH = srcH;
this.flipMode = flipMode;
boolean submitted = false;
try {
windowThreadExecutor.execute(() -> {
try {
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
} finally {
pending.release();
}
});
windowThreadExecutor.execute(task);
submitted = true;
} finally {
if (!submitted) pending.release();
@@ -37,6 +37,7 @@ final class SdlFramePlots {
private static final long PLOT_PER_PASS_BLOCK_FLUSHES = Tracy.plotHandle("sdl.perPassBlockFlushes");
private static final long PLOT_PER_PASS_BLOCK_BYTES = Tracy.plotHandle("sdl.perPassBlockBytes", TracyBackend.PLOT_FORMAT_MEMORY);
private static final long PLOT_ACQUIRE_WAIT_US = Tracy.plotHandle("sdl.acquireWaitUs");
private static final long PLOT_GPU_WAIT_US = Tracy.plotHandle("sdl.gpuWaitUs");
private static final long PLOT_PRESENT_TO_PRESENT_US = Tracy.plotHandle("sdl.presentToPresentUs");
private static final long PLOT_FRAMES_IN_FLIGHT = Tracy.plotHandle("sdl.framesInFlight");
private static final long PLOT_MIP_GENS = Tracy.plotHandle("sdl.mipGens");
@@ -97,6 +98,7 @@ final class SdlFramePlots {
Tracy.plotInt(PLOT_PER_PASS_BLOCK_FLUSHES, f.uniformBlockFlushesThisFrame[ShaderManager.BLOCK_PER_PASS]);
Tracy.plotInt(PLOT_PER_PASS_BLOCK_BYTES, f.uniformBlockBytesThisFrame[ShaderManager.BLOCK_PER_PASS]);
Tracy.plotInt(PLOT_ACQUIRE_WAIT_US, f.acquireWaitNanosThisFrame / 1000);
Tracy.plotInt(PLOT_GPU_WAIT_US, f.gpuWaitNanosThisFrame / 1000);
final long nowNanos = System.nanoTime();
if (f.lastEndFrameNanos != 0) Tracy.plotInt(PLOT_PRESENT_TO_PRESENT_US, (nowNanos - f.lastEndFrameNanos) / 1000);
f.lastEndFrameNanos = nowNanos;
@@ -131,7 +131,7 @@ public final class DrawDispatch {
}
}
public void drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
public boolean drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
final int numTriangles = count - 2;
final boolean use32bit = (first + count - 1) > 65535;
final int bytesPerIndex = use32bit ? 4 : 2;
@@ -180,13 +180,14 @@ public final class DrawDispatch {
setPrimitiveTypeForDraw(st, SDL_GPU_PRIMITIVETYPE_TRIANGLELIST);
pipelineApplier.ensureRenderPass(st);
if (!frameManager.isRenderPassActive()) return;
if (!pipelineApplier.applyPipelineAndState(st)) return;
if (!frameManager.isRenderPassActive()) return false;
if (!pipelineApplier.applyPipelineAndState(st)) return false;
final int elementSize = use32bit ? SDL_GPU_INDEXELEMENTSIZE_32BIT : SDL_GPU_INDEXELEMENTSIZE_16BIT;
final long rp = frameManager.getRenderPass();
bindIndexBufferIfChanged(st, rp, st.fanIndexBuffer, elementSize, uploadOffset);
SDL_DrawGPUIndexedPrimitives(rp, numTriangles * 3, 1, 0, 0, 0);
return true;
}
private long createFanIndexBuffer(int capacity) {
@@ -0,0 +1,211 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlock;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
public final class FFPTrace {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final String PREFIX = "[FFPTrace] ";
private static final int MAX_KEYS = 512;
private final ResourceManager resourceManager;
private final LongOpenHashSet seenKeys = new LongOpenHashSet();
private final StringBuilder sb = new StringBuilder(1024);
private int drawsThisFrame;
private boolean capReported;
public FFPTrace(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
public synchronized void trace(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
drawsThisFrame++;
final long key = key(st);
if (seenKeys.contains(key)) return;
if (seenKeys.size() >= MAX_KEYS) {
if (!capReported) {
capReported = true;
LOG.info("{}key cap {} reached; no further distinct draw states will be logged", PREFIX, MAX_KEYS);
}
return;
}
seenKeys.add(key);
LOG.info(format(st, kind, mode, count, indexType, first, baseVertex));
}
public synchronized void frameEnd(FrameManager.FrameState f) {
LOG.info("{}frame {} end: draws={} dropped={}", PREFIX, f.frameNumber, drawsThisFrame, f.droppedDrawsThisFrame);
drawsThisFrame = 0;
}
private long key(ContextState st) {
final PipelineCache p = st.pipeline;
long h = p.lastKey();
h = Hashing.fmix64(h, st.boundFboId);
h = Hashing.fmix64(h, Hashing.packHiLo(st.boundTextures[0], st.boundTextures[1]));
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorEnabled ? 1 : 0, 0));
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportX), Float.floatToRawIntBits(st.viewportY)));
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportW), Float.floatToRawIntBits(st.viewportH)));
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorX, st.scissorY));
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorW, st.scissorH));
return h;
}
private String format(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
final PipelineCache p = st.pipeline;
final ContextState.VAOState vao = st.currentVao;
final ShaderManager.ProgramObject prog = st.boundProgramObj;
sb.setLength(0);
sb.append(PREFIX);
sb.append("kind=").append(kind);
sb.append(" mode=0x").append(Integer.toHexString(mode));
sb.append(" count=").append(count);
if (indexType != 0) sb.append(" indexType=0x").append(Integer.toHexString(indexType));
sb.append(" first=").append(first).append(" baseVertex=").append(baseVertex);
sb.append(" | prog=").append(st.boundProgram);
if (prog == null) {
sb.append(" progObj=null");
} else {
sb.append(" ffpUboBinding=").append(prog.externalUboBinding);
sb.append(" fragSamplers=").append(prog.fragmentSamplerNames);
sb.append(" vsInputs=[");
boolean firstName = true;
for (int i = 0; i < prog.vertexInputName.length; i++) {
final String n = prog.vertexInputName[i];
if (n == null) continue;
if (!firstName) sb.append(',');
firstName = false;
sb.append(i).append(':').append(n);
}
sb.append(']');
}
sb.append(" | fbo=").append(st.boundFboId);
sb.append(" vp=(").append(st.viewportX).append(',').append(st.viewportY).append(',')
.append(st.viewportW).append(',').append(st.viewportH).append(')');
sb.append(" scissor=").append(st.scissorEnabled);
if (st.scissorEnabled) {
sb.append('(').append(st.scissorX).append(',').append(st.scissorY).append(',')
.append(st.scissorW).append(',').append(st.scissorH).append(')');
}
sb.append(" cull=").append(p.cullEnabled).append("/mode=").append(p.cullFaceMode)
.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.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);
appendTextureUnit(st, 0);
appendTextureUnit(st, 1);
sb.append(" | attribs=[");
int enabled = vao.attribEnabledMask;
boolean firstAttrib = true;
while (enabled != 0) {
final int i = Integer.numberOfTrailingZeros(enabled);
enabled &= enabled - 1;
final int b = vao.attribBinding[i];
if (!firstAttrib) sb.append(' ');
firstAttrib = false;
sb.append(i).append(":size=").append(vao.attribSize[i])
.append(",type=0x").append(Integer.toHexString(vao.attribType[i]))
.append(",norm=").append(vao.attribNormalized[i])
.append(",int=").append(vao.attribIsInteger[i])
.append(",stride=").append(vao.attribStride[i])
.append(",off=").append(vao.attribOffset[i])
.append(",buf=").append(vao.bindingBuffer[b])
.append(",bufOff=").append(vao.bindingOffset[b]);
}
sb.append("] enabledMask=0x").append(Integer.toHexString(vao.attribEnabledMask))
.append(" shaderInputMask=0x").append(Integer.toHexString(p.shaderInputMask))
.append(" ebo=").append(vao.elementBuffer);
appendFfpUniforms(st, prog);
return sb.toString();
}
private void appendTextureUnit(ContextState st, int unit) {
final int id = st.boundTextures[unit];
sb.append(" | tex").append(unit).append("=").append(id);
if (id != 0) {
sb.append(" handle=").append(resourceManager.getTextureHandle(id) != 0);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(id);
if (meta == null) {
sb.append(" meta=null");
} else {
sb.append(" size=").append(meta.width()).append('x').append(meta.height())
.append(" levels=").append(meta.levels())
.append(" sdlFormat=").append(meta.sdlFormat())
.append(" glFormat=0x").append(Integer.toHexString(meta.glFormat()));
}
}
sb.append(" ffpEnabled=").append(GLStateManager.getTextures().getTextureUnitStates(unit).isEnabled());
}
private void appendFfpUniforms(ContextState st, ShaderManager.ProgramObject prog) {
if (prog == null) return;
final int binding = prog.externalUboBinding;
if (binding < 0 || binding >= ContextState.MAX_INDEXED_BUFFERS) return;
final int glId = st.boundUboByIndex[binding];
if (glId == 0) {
sb.append(" | ffpUbo=unbound");
return;
}
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
final ByteBuffer buf = pm != null ? pm.staging : resourceManager.getUboShadow(glId);
if (buf == null) {
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
return;
}
final int base = st.uboRangeOffset[binding];
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
}
private static float floatIgnoringLimit(ByteBuffer buf, int at) {
return MemoryUtil.memGetFloat(MemoryUtil.memAddress0(buf) + at);
}
private void appendUboFloat(ByteBuffer buf, int base, int memberOffset, String label) {
final int at = base + memberOffset;
sb.append(label);
if (at < 0 || at + 4 > buf.capacity()) {
sb.append("oob");
return;
}
sb.append(floatIgnoringLimit(buf, at));
}
private void appendUboMatrix(ByteBuffer buf, int base, int memberOffset, String label) {
final int at = base + memberOffset;
sb.append(label);
if (at < 0 || at + 64 > buf.capacity()) {
sb.append("oob");
return;
}
sb.append('[');
for (int i = 0; i < 16; i++) {
if (i > 0) sb.append(',');
sb.append(floatIgnoringLimit(buf, at + i * 4));
}
sb.append(']');
}
}
@@ -337,6 +337,11 @@ public final class PipelineApplier {
st.ringChunkUsedBlocks = 0;
}
private int fbo0Height(ContextState st) {
final int h = frameManager.getFbo0Height();
return h > 0 ? h : (int) (st.viewportY + st.viewportH);
}
public boolean applyPipelineAndState(ContextState st) {
return applyPipelineAndState(st, frameManager.frame());
}
@@ -435,11 +440,14 @@ public final class PipelineApplier {
}
if (st.viewportDirty) {
float vpY = st.viewportY;
float vpH = st.viewportH;
final float vpY;
final float vpH;
if (renderingToFbo) {
vpY = st.viewportY + st.viewportH;
vpH = -st.viewportH;
} else {
vpY = fbo0Height(st) - st.viewportY - st.viewportH;
vpH = st.viewportH;
}
final long addr = st.cachedViewport.address();
MemoryAccess.putFloat(addr + SDL_GPUViewport.X, st.viewportX);
@@ -455,15 +463,9 @@ public final class PipelineApplier {
if (st.scissorDirty) {
final long addr = st.cachedScissor.address();
final int sx, sy, sw, sh;
final int fbHeight;
if (st.scissorEnabled) {
if (renderingToFbo) {
fbHeight = (fboState != null && fboState.height > 0) ? fboState.height : (int)(st.viewportY + st.viewportH);
} else {
fbHeight = (int)(st.viewportY + st.viewportH);
}
sx = st.scissorX;
sy = fbHeight - st.scissorY - st.scissorH;
sy = renderingToFbo ? st.scissorY : (fbo0Height(st) - st.scissorY - st.scissorH);
sw = st.scissorW;
sh = st.scissorH;
MemoryAccess.putInt(addr + SDL_Rect.X, sx);
@@ -471,11 +473,12 @@ public final class PipelineApplier {
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={} fbHeight={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, fbHeight, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
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, (int) st.viewportY);
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);
}
@@ -674,10 +677,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];
}
@@ -825,11 +827,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,14 @@ 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() {
return lastKey;
}
public void markInputDirty() { inputDirty = true; lastKey = 0L; }
@@ -195,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; }
@@ -292,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));
@@ -386,11 +426,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);
@@ -500,8 +540,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())
@@ -0,0 +1,71 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
public final class CopyRectClip {
public static final long EMPTY = 0L;
private static final long FIELD_MASK = 0xFFFFL;
private CopyRectClip() {}
public static long clipCopyRect(int srcX, int srcY, int dstX, int dstY, int width, int height,
int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
int sx = srcX;
int sy = srcY;
int dx = dstX;
int dy = dstY;
int w = width;
int h = height;
if (sx < 0) {
w += sx;
dx -= sx;
sx = 0;
}
if (sy < 0) {
h += sy;
dy -= sy;
sy = 0;
}
if (dx < 0) {
w += dx;
sx -= dx;
dx = 0;
}
if (dy < 0) {
h += dy;
sy -= dy;
dy = 0;
}
if (srcWidth > 0 && sx + w > srcWidth) w = srcWidth - sx;
if (srcHeight > 0 && sy + h > srcHeight) h = srcHeight - sy;
if (dstWidth > 0 && dx + w > dstWidth) w = dstWidth - dx;
if (dstHeight > 0 && dy + h > dstHeight) h = dstHeight - dy;
if (w <= 0 || h <= 0) return EMPTY;
if ((sx | sy | w | h) > FIELD_MASK) return EMPTY;
return (sx & FIELD_MASK) | ((sy & FIELD_MASK) << 16) | ((w & FIELD_MASK) << 32) | ((h & FIELD_MASK) << 48);
}
public static int srcX(long clip) {
return (int) (clip & FIELD_MASK);
}
public static int srcY(long clip) {
return (int) ((clip >>> 16) & FIELD_MASK);
}
public static int width(long clip) {
return (int) ((clip >>> 32) & FIELD_MASK);
}
public static int height(long clip) {
return (int) ((clip >>> 48) & FIELD_MASK);
}
public static int dstX(long clip, int origSrcX, int origDstX) {
return origDstX + (srcX(clip) - origSrcX);
}
public static int dstY(long clip, int origSrcY, int origDstY) {
return origDstY + (srcY(clip) - origSrcY);
}
}
@@ -242,6 +242,12 @@ public final class FBOClearTracker {
return true;
}
public void resolveDestinationForWrite(ContextState st, long destTex, ResourceManager.TextureMeta destMeta, int level, int dx, int dy, int dz, int w, int h) {
if (dz != 0 || !discardPendingClearIfFullyCovered(st, destTex, dx, dy, level, w, h, destMeta)) {
materializePendingClearForTexture(st, destTex);
}
}
public void materializePendingClearForTexture(ContextState st, long handle) {
if (handle == 0) return;
final boolean depthPending = st.pendingDepthTextures.remove(handle);
@@ -13,6 +13,12 @@ import static org.lwjgl.sdl.SDLGPU.*;
public final class PixelOps {
private PixelOps() {}
public static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0) return 0;
if (level >= Integer.SIZE - 1) return 1;
return Math.max(1, base >> level);
}
public static int defaultMipLevels(int width, int height) {
final int max = Math.max(1, Math.max(width, height));
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
@@ -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();
}
}
}
@@ -2208,7 +2208,24 @@ public final class ResourceManager {
return mapTextureFormat(glFormat, preferredD24, preferredD24S8);
}
public boolean isMappableTextureFormat(int glFormat) {
return lookupTextureFormat(glFormat, preferredD24, preferredD24S8) != SDL_GPU_TEXTUREFORMAT_INVALID;
}
public static int mapTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
final int mapped = lookupTextureFormat(glFormat, preferredD24, preferredD24S8);
if (mapped != SDL_GPU_TEXTUREFORMAT_INVALID) return mapped;
synchronized (UNMAPPED_TEXTURE_FORMAT_SEEN) {
if (UNMAPPED_TEXTURE_FORMAT_SEEN.add(glFormat)) {
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
}
}
return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
}
private static final IntOpenHashSet UNMAPPED_TEXTURE_FORMAT_SEEN = new IntOpenHashSet();
private static int lookupTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
return switch (glFormat) {
case GL11.GL_RGBA, GL11.GL_RGBA8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
case GL11.GL_RGB, GL11.GL_RGB8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; // RGB -> RGBA promotion
@@ -2262,10 +2279,7 @@ public final class ResourceManager {
case GL31.GL_RGBA8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM;
case GL31.GL_RGBA16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM;
case GL31.GL_RGB16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM; // RGB SNORM -> RGBA SNORM promotion
default -> {
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
yield SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
}
default -> SDL_GPU_TEXTUREFORMAT_INVALID;
};
}
@@ -19,6 +19,8 @@ import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
public final class TextureOps {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
@@ -99,9 +101,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;
@@ -113,6 +115,11 @@ public final class TextureOps {
final long destTex = resourceManager.getTextureHandle(destGlId);
if (destTex == 0) return;
if (st.boundReadFboId == 0) {
copyFromDefaultFramebuffer(st, destGlId, destTex, level, xoffset, yoffset, x, y, width, height);
return;
}
final FboState fbo = resourceManager.getFbo(st.boundReadFboId);
if (fbo == null) return;
@@ -121,35 +128,81 @@ public final class TextureOps {
final long srcTex = isDepthDest ? fbo.depthTexture : fbo.colorTextures[fbo.readBufferIndex];
if (srcTex == 0) return;
if (isDepthDest) {
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
return;
}
final int srcGlId = fbo.colorGlIds[fbo.readBufferIndex];
final int srcGlId = isDepthDest ? fbo.depthGlId : fbo.colorGlIds[fbo.readBufferIndex];
final ResourceManager.TextureMeta srcMeta = srcGlId != 0 ? resourceManager.getTextureMeta(srcGlId) : null;
if (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat()) {
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcMeta != null ? srcMeta.width() : fbo.width, srcMeta != null ? srcMeta.height() : fbo.height, levelWidth(destMeta, level), levelHeight(destMeta, level));
if (clip == CopyRectClip.EMPTY) return;
final int sx = CopyRectClip.srcX(clip);
final int sy = CopyRectClip.srcY(clip);
final int dx = CopyRectClip.dstX(clip, x, xoffset);
final int dy = CopyRectClip.dstY(clip, y, yoffset);
final int w = CopyRectClip.width(clip);
final int h = CopyRectClip.height(clip);
fboClearTracker.materializePendingClearForTexture(st, srcTex);
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
if (isDepthDest || (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat())) {
copyTexture(srcTex, sx, sy, destTex, level, dx, dy, w, h);
} else {
blitTexture(srcTex, x, y, width, height, destTex, xoffset, yoffset, width, height, GL11.GL_NEAREST);
blitTexture(srcTex, sx, sy, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST);
}
}
private static int levelWidth(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
}
private static int levelHeight(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
}
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
final ResourceManager.TextureMeta destMeta = resourceManager.getTextureMeta(destGlId);
if (destMeta != null && PixelOps.isDepthFormat(destMeta.sdlFormat())) return;
final long srcTex = frameManager.getFbo0Texture();
if (srcTex == 0) return;
final int srcFullW = frameManager.getFbo0Width();
final int srcFullH = frameManager.getFbo0Height();
if (srcFullW <= 0 || srcFullH <= 0) return;
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcFullW, srcFullH, levelWidth(destMeta, level), levelHeight(destMeta, level));
if (clip == CopyRectClip.EMPTY) return;
final int sx = CopyRectClip.srcX(clip);
final int sy = CopyRectClip.srcY(clip);
final int dx = CopyRectClip.dstX(clip, x, xoffset);
final int dy = CopyRectClip.dstY(clip, y, yoffset);
final int w = CopyRectClip.width(clip);
final int h = CopyRectClip.height(clip);
if (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) {
frameManager.ensureFbo0RenderPass(frameManager.frame(), st);
}
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
blitTexture(srcTex, sx, srcFullH - sy - h, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST, SDL_FLIP_VERTICAL);
}
public static boolean canCopyInsteadOfBlit(ResourceManager.TextureMeta src, ResourceManager.TextureMeta dst, int srcW, int srcH, int dstW, int dstH) {
return src != null && dst != null && src.sdlFormat() == dst.sdlFormat() && srcW > 0 && srcH > 0 && srcW == dstW && srcH == dstH;
}
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstX, int dstY, int w, int h) {
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return;
try (var stack = MemoryStack.stackPush()) {
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).x(srcX).y(srcY);
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).x(dstX).y(dstY);
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY);
SDL_CopyGPUTextureToTexture(cp, src, dst, w, h, 1, false);
}
}
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstX, int dstY, int dstW, int dstH, int glFilter) {
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter) {
blitTexture(srcTex, srcX, srcY, srcW, srcH, dstTex, dstLevel, dstX, dstY, dstW, dstH, glFilter, SDL_FLIP_NONE);
}
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive();
final long cb = frameManager.getCommandBuffer();
@@ -158,9 +211,9 @@ public final class TextureOps {
try (var stack = MemoryStack.stackPush()) {
final var info = SDL_GPUBlitInfo.calloc(stack);
info.source(s -> s.texture(srcTex).x(srcX).y(srcY).w(srcW).h(srcH));
info.destination(d -> d.texture(dstTex).x(dstX).y(dstY).w(dstW).h(dstH));
info.destination(d -> d.texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).w(dstW).h(dstH));
info.filter(glFilter == GL11.GL_LINEAR ? SDL_GPU_FILTER_LINEAR : SDL_GPU_FILTER_NEAREST);
info.flip_mode(0);
info.flip_mode(flipMode);
frameManager.noteBlit();
SDL_BlitGPUTexture(cb, info);
}
@@ -58,6 +58,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArrays;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -599,29 +600,84 @@ public final class ShaderManager {
private static int patchAttribLocations(ByteBuffer spirv, StageReflection vs, Object2IntOpenHashMap<String> bindings, int[] outVecSize, int[] outBaseType, String[] outName, Object2IntOpenHashMap<String> resolvedOut) {
final IntBuffer vsBuf = spirv.asIntBuffer();
int mask = 0;
if (outName != null) Arrays.fill(outName, null);
for (VsInput vi : vs.vsInputs()) {
int finalLoc = vi.originalLocation();
final int desired = bindings.getInt(vi.name());
if (desired != -1 && desired != vi.originalLocation()) {
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
} else {
vsBuf.put(vi.binaryOffset(), desired);
finalLoc = desired;
final List<VsInput> inputs = vs.vsInputs();
final int count = inputs.size();
final int[] finalLoc = new int[count];
int taken = 0;
for (final IntIterator it = bindings.values().iterator(); it.hasNext(); ) {
final int bound = it.nextInt();
if (bound >= 0 && bound < ContextState.MAX_VERTEX_ATTRIBS) taken |= 1 << bound;
}
for (int i = 0; i < count; i++) {
finalLoc[i] = bindings.getInt(inputs.get(i).name());
}
int movers = 0;
for (int i = 0; i < count; i++) {
if (finalLoc[i] != -1) continue;
final int original = inputs.get(i).originalLocation();
if (original >= 0 && original < ContextState.MAX_VERTEX_ATTRIBS && (taken & (1 << original)) == 0) {
finalLoc[i] = original;
taken |= 1 << original;
} else {
movers++;
}
}
for (int n = 0; n < movers; n++) {
int pick = -1;
for (int i = 0; i < count; i++) {
if (finalLoc[i] == -1 && (pick == -1 || inputs.get(i).originalLocation() < inputs.get(pick).originalLocation())) {
pick = i;
}
}
if (resolvedOut != null) resolvedOut.put(vi.name(), finalLoc);
if (finalLoc < 0 || finalLoc >= 16) continue;
mask |= (1 << finalLoc);
outVecSize[finalLoc] = vi.vecSize();
outBaseType[finalLoc] = vi.baseType();
if (outName != null) outName[finalLoc] = vi.name();
final int free = Integer.numberOfTrailingZeros(~taken);
if (free >= ContextState.MAX_VERTEX_ATTRIBS) {
LOG.error("applyAttribLocations: no free vertex attribute slot for '{}'; leaving it at {}", inputs.get(pick).name(), inputs.get(pick).originalLocation());
finalLoc[pick] = inputs.get(pick).originalLocation();
} else {
finalLoc[pick] = free;
taken |= 1 << free;
}
}
int mask = 0;
int seen = 0;
if (outName != null) Arrays.fill(outName, null);
for (int i = 0; i < count; i++) {
final VsInput vi = inputs.get(i);
int loc = finalLoc[i];
if (loc != vi.originalLocation()) {
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
loc = vi.originalLocation();
} else {
vsBuf.put(vi.binaryOffset(), loc);
}
}
finalLoc[i] = loc;
if (resolvedOut != null) resolvedOut.put(vi.name(), loc);
if (loc < 0 || loc >= ContextState.MAX_VERTEX_ATTRIBS) continue;
if ((seen & (1 << loc)) != 0) {
LOG.error("applyAttribLocations: '{}' and '{}' both resolved to location {}", inputNameAtLocation(inputs, finalLoc, i, loc), vi.name(), loc);
}
seen |= 1 << loc;
mask |= 1 << loc;
outVecSize[loc] = vi.vecSize();
outBaseType[loc] = vi.baseType();
if (outName != null) outName[loc] = vi.name();
}
return mask;
}
private static String inputNameAtLocation(List<VsInput> inputs, int[] finalLoc, int limit, int loc) {
for (int i = 0; i < limit; i++) {
if (finalLoc[i] == loc) return inputs.get(i).name();
}
return "<unknown>";
}
public VertexVariant getOrBuildVertexVariant(int program, long key, List<UscaledRetype.Attrib> attribs) {
final ProgramObject prog = programObjects.get(program);
if (prog == null || !prog.linked || prog.vertexSource.isEmpty()) return null;
@@ -1 +0,0 @@
com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService
@@ -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,44 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DispatchIndirectBindingTest {
@AfterEach
void resetContextState() {
final ContextState st = SdlTestRig.contextState();
st.boundIndirectBuffer = 0;
st.boundDispatchIndirectBuffer = 0;
}
@Test
void dispatchIndirectHasItsOwnBindingPoint() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = SdlTestRig.contextState();
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 11);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 22);
assertEquals(11, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
assertEquals(22, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
}
@Test
void bindingDispatchIndirectLeavesTheDrawIndirectSlotAlone() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = SdlTestRig.contextState();
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 33);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 44);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 0);
assertEquals(33, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
assertEquals(0, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
}
}
@@ -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,154 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL40;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ProxyTextureLimitsTest {
@AfterEach
void resetContextState() {
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
st.boundTextures[0] = 0;
st.proxyTexture.reject(0);
}
@Test
void threeDimensionalProxyIsBoundedByTheVolumeLimit() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final int limit = SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE;
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit * 2, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit * 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
}
@Test
void everyProxyDimensionLimitIsAlsoQueryableAsADeviceLimit() {
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL11.GL_MAX_TEXTURE_SIZE));
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
}
@Test
void oneDimensionalArrayLayersUseTheLayerLimitNotTheDimensionLimit() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(512, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_HEIGHT));
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
}
@Test
void arrayLayerCountDoesNotHalvePerLevel() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_RGBA, 128, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(128, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_WIDTH));
assertEquals(64, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_HEIGHT));
}
@Test
void aProbedLevelIsAcceptedOnItsLevelZeroEquivalentSize() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_RGBA, 1024, 512, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(1024, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
assertEquals(512, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_HEIGHT));
assertEquals(8192, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
}
@Test
void aProbedLevelWhoseBaseWouldOverflowTheLimitIsRejected() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_RGBA, SDLGPURenderBackend.MAX_TEXTURE_SIZE, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_TEXTURE_WIDTH));
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_RGBA, 4, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_TEXTURE_WIDTH));
}
@Test
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
}
@Test
void proxyTargetsAnswerTheGlParameterDefaults() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final int target = GL11.GL_PROXY_TEXTURE_2D;
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER));
assertEquals(GL11.GL_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER));
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_S));
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_T));
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL12.GL_TEXTURE_WRAP_R));
assertEquals(0, backend.getTexParameteri(target, GL12.GL_TEXTURE_BASE_LEVEL));
assertEquals(1000, backend.getTexParameteri(target, GL12.GL_TEXTURE_MAX_LEVEL));
assertEquals(-1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MIN_LOD));
assertEquals(1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MAX_LOD));
assertEquals(0.0f, backend.getTexParameterf(target, GL14.GL_TEXTURE_LOD_BIAS));
assertEquals(1.0f, backend.getTexParameterf(target, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT));
}
@Test
void proxyParameterDefaultsIgnoreTheBoundTexturesOwnState() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
st.boundTextures[0] = 8181;
backend.texParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
assertEquals(GL11.GL_NEAREST, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
}
@Test
void proxyTargetsNeverReceiveParameterWrites() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
st.boundTextures[0] = 8282;
backend.texParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
backend.texParameterf(GL11.GL_PROXY_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 4.0f);
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
assertEquals(0.0f, backend.getTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
}
}
@@ -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.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import java.nio.ByteBuffer;
import java.util.stream.Stream;
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 ProxyTextureTargetTest {
private static final int[] PROXY_TARGETS = {
0x8063, 0x8064, 0x8070, 0x851B, 0x8C19, 0x8C1B, 0x84F7, 0x900B, 0x9101, 0x9103
};
private static SDLGPURenderBackend backend;
@BeforeAll
static void setUp() {
backend = new SDLGPURenderBackend();
}
@AfterEach
void resetContextState() {
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
st.boundTextures[0] = 0;
st.boundTextures[1] = 0;
st.proxyTexture.reject(0);
}
@Test
void everyProxyTargetIsRecognized() {
for (int target : PROXY_TARGETS) {
assertTrue(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
}
}
@Test
void realTargetsAreNotProxies() {
final int[] real = {
GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_3D, GL13.GL_TEXTURE_CUBE_MAP,
GL30.GL_TEXTURE_1D_ARRAY, GL30.GL_TEXTURE_2D_ARRAY, GL31.GL_TEXTURE_RECTANGLE,
GL40.GL_TEXTURE_CUBE_MAP_ARRAY, GL32.GL_TEXTURE_2D_MULTISAMPLE, GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
0
};
for (int target : real) {
assertFalse(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
}
}
static Stream<Arguments> levelSizeCases() {
return Stream.of(
Arguments.of("level0", 16384, 0, 16384),
Arguments.of("level1", 16384, 1, 8192),
Arguments.of("level14FloorsAtOne", 16384, 14, 1),
Arguments.of("level15FloorsAtOne", 16384, 15, 1),
Arguments.of("level64FloorsAtOne", 16384, 64, 1),
Arguments.of("baseOneFloorsAtOne", 1, 3, 1),
Arguments.of("oddBaseRoundsDown", 6, 1, 3),
Arguments.of("rejectedProxyBaseZeroLevel0", 0, 0, 0),
Arguments.of("rejectedProxyBaseZeroLevel5", 0, 5, 0),
Arguments.of("rejectedProxyNegativeLevel", 16384, -1, 0));
}
@ParameterizedTest(name = "{0}")
@MethodSource("levelSizeCases")
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
}
@Test
void proxyTexImage2DRecordsDimensionsWithoutTouchingTheBoundTexture() {
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 1;
st.boundTextures[1] = 4242;
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(4242, st.boundTextures[1]);
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
assertEquals(2048, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
assertEquals(GL11.GL_RGBA, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_BORDER));
}
@Test
void oversizedProxyTexImage2DZeroesEveryProxyField() {
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 32768, 32768, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
}
@Test
void proxyArrayTargetIsLimitedByLayerCount() {
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
}
@Test
void proxyStateIsScopedToTheProbedTarget() {
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(256, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_HEIGHT));
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
}
@Test
void unmappableProxyFormatIsRejected() {
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, 0x1BADF00D, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
}
@Test
void proxyBindIsInert() {
final ContextState st = SdlTestRig.contextState();
st.activeTextureUnit = 0;
st.boundTextures[0] = 77;
for (int target : PROXY_TARGETS) {
backend.bindTexture(target, 99);
assertEquals(77, st.boundTextures[0], () -> "0x" + Integer.toHexString(target));
}
}
}
@@ -1,17 +1,34 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.mitchej123.lwjgl.LWJGLService;
import com.gtnewhorizons.angelica.config.SystemProperties;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
class SDLGPULWJGLServiceGatingTest {
@Test
void anUnengagedSdlDeclinesAndNeverArmsTheFallbackTripwire() {
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
assertFalse(SDLGPUGate.isEngaged(), "test JVM must not have an engaged SDL GPU device");
assertEquals(LWJGLService.PRIORITY_UNAVAILABLE, new SDLGPULWJGLService().getPriority());
assertFalse(SDLGPULWJGLService.isOfferedAsAvailable(), "declining must leave fallBackToGL() usable");
assertNull(System.getProperty(key), "an unengaged SDL must not bind the celeritas service");
try {
SDLGPULWJGLService.bind();
assertEquals(SDLGPULWJGLService.class.getName(), System.getProperty(key));
assertThrows(
IllegalStateException.class,
SDLGPULWJGLService::create,
"constructing the service while SDL GPU is unengaged must fail loudly");
} finally {
SDLGPULWJGLService.unbind();
}
assertNull(System.getProperty(key));
assertFalse(SDLGPULWJGLService.isConstructed(), "declining must leave fallBackToGL() usable");
}
}
@@ -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);
}
}
@@ -9,6 +9,7 @@ import java.util.Deque;
import java.util.concurrent.Executor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
@@ -72,6 +73,21 @@ class PresenterTest {
drainer.join();
}
@Test
void requestReusesOneRunnableAcrossCalls() {
final DeferredExecutor executor = new DeferredExecutor();
final Presenter p = presenter(executor);
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
final Runnable first = executor.tasks.peekFirst();
executor.runNext();
p.requestPresent(2L, 10, 10, SDL_FLIP_NONE);
final Runnable second = executor.tasks.peekFirst();
assertSame(first, second, "Presenter must submit the same preallocated Runnable on every request");
}
@Test
void anInlineExecutorReleasesTheSlotImmediately() {
final Presenter p = new Presenter(new FrameManager(new Device()), Runnable::run);
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.AfterAll;
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.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlCopyBlitTest {
private static final int RED = 0xFFFF0000;
private static final int GREEN = 0xFF00FF00;
private static final int BLUE = 0xFF0000FF;
private static final int SCISSOR_ROWS = 16;
private static final byte SENTINEL_BYTE = 0x7B;
private static final int SENTINEL = 0x7B7B7B7B;
private static FrameManager frameManager;
private static ResourceManager resourceManager;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
frameManager.destroyFinalTarget();
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, SIZE, SIZE, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
assertEquals(SIZE, frameManager.getFbo0Height());
}
@AfterAll
static void dropFinalTarget() {
GlsmSdlHeadlessRig.endFrame();
if (frameManager != null) frameManager.destroyFinalTarget();
}
private static void assertRow(int[] pixels, int y, int expected, String label) {
for (int x = 0; x < SIZE; x += 8) {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, expected, label + " row " + y);
}
}
@Test
void scissorOnTheUserFboClipsTheGlBottomRows() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, SIZE, SCISSOR_ROWS);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertRow(pixels, 0, RED, "inside the scissor");
assertRow(pixels, SCISSOR_ROWS - 1, RED, "inside the scissor");
assertRow(pixels, SCISSOR_ROWS, BLUE, "outside the scissor");
assertRow(pixels, SIZE - 1, BLUE, "outside the scissor");
}
private static int createTwoLevelTexture(int level0Argb, int level1Argb) {
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_MIPMAP_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, 0.0f);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1.0f);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
uploadLevel(0, SIZE, level0Argb);
uploadLevel(1, SIZE / 2, level1Argb);
return id;
}
private static void uploadLevel(int level, int size, int argb) {
final ByteBuffer texels = MemoryUtil.memAlloc(size * size * 4);
try {
for (int i = 0; i < size * size; i++) {
texels.put(i * 4, (byte) ((argb >> 16) & 0xFF));
texels.put(i * 4 + 1, (byte) ((argb >> 8) & 0xFF));
texels.put(i * 4 + 2, (byte) (argb & 0xFF));
texels.put(i * 4 + 3, (byte) ((argb >>> 24) & 0xFF));
}
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, size, size, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
} finally {
MemoryUtil.memFree(texels);
}
}
private static int copyGreenIntoLevelOne() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
final int texture = createTwoLevelTexture(RED, BLUE);
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 1, 0, 0, 0, 0, SIZE / 2, SIZE / 2);
return texture;
}
@Test
void copyTexSubImage2DWritesTheRequestedMipLevel() {
final int texture = copyGreenIntoLevelOne();
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.texturedQuad(texture, 0.5f);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "minified sample of level 1");
}
@Test
void copyTexSubImage2DLeavesTheOtherMipLevelsUntouched() {
final int texture = copyGreenIntoLevelOne();
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 4, RED, "magnified sample of the level 0 region the copy would land in");
}
@Test
void readPixelsCrossingTheTopEdgeReturnsTheInRangeRows() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
final int[] pixels = GlsmSdlHeadlessRig.readTarget(0, SIZE / 2, SIZE, SIZE, SENTINEL_BYTE);
assertRow(pixels, 0, GREEN, "first in-range row");
assertRow(pixels, SIZE / 2 - 1, GREEN, "last in-range row");
assertRow(pixels, SIZE / 2, SENTINEL, "first out-of-range row");
assertRow(pixels, SIZE - 1, SENTINEL, "last out-of-range row");
}
private static int createSecondaryFbo() {
final int color = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, color);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depth);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, color, 0);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depth);
return fbo;
}
@Test
void blitFramebufferWithColorAndDepthBitsMovesColor() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
final int destination = createSecondaryFbo();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, destination);
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE,
GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, destination);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "color aspect of a color+depth blit");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 4, SIZE - 5, GREEN, "color aspect of a color+depth blit");
}
@Test
void blitFramebufferIntoFbo0SurvivesTheFramesFirstFbo0Pass() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, 4, 4);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "blit into fbo0 survived the first fbo0 pass");
}
@Test
void blitFramebufferFromFbo0ToTheUserFboKeepsGlOrientation() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 0.0f);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
GlsmSdlHeadlessRig.bindTarget();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "blitted fbo0 bottom");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "blitted fbo0 top");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 - 4, GREEN, "blitted fbo0 below the split");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 + 4, RED, "blitted fbo0 above the split");
}
}
@@ -0,0 +1,141 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.AfterAll;
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.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlFbo0ViewportTest {
private static final int FBO0_W = 64;
private static final int FBO0_H = 96;
private static final int VIEW = 32;
private static final int RED = 0xFFFF0000;
private static final int GREEN = 0xFF00FF00;
private static final int BLUE = 0xFF0000FF;
private static FrameManager frameManager;
private static ResourceManager resourceManager;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
frameManager.destroyFinalTarget();
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, FBO0_W, FBO0_H, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
assertEquals(FBO0_H, frameManager.getFbo0Height());
}
@AfterAll
static void dropFinalTarget() {
GlsmSdlHeadlessRig.endFrame();
if (frameManager != null) frameManager.destroyFinalTarget();
}
private static void bindFbo0WithSmallViewport() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, VIEW, VIEW);
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);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, VIEW, VIEW, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
return id;
}
private static void drawTextureOverTarget(int texture) {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
}
@Test
void partialViewportDrawCoversOnlyTheGlViewportRows() {
bindFbo0WithSmallViewport();
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, VIEW, VIEW), RED, "inside the viewport");
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
}
@Test
void copyTexSubImage2DFromAPartialViewportKeepsGlOrientation() {
bindFbo0WithSmallViewport();
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
final int copy = createCopyTarget();
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 0, VIEW, VIEW);
drawTextureOverTarget(copy);
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "sampled copy bottom");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "sampled copy top");
}
@Test
void copyTexImage2DAllocatesTheDestinationLevel() {
bindFbo0WithSmallViewport();
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
final int copy = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, copy);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
assertNull(resourceManager.getTextureMeta(copy), "destination was allocated before the copy");
GLStateManager.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 0, 0, VIEW, VIEW, 0);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(copy);
assertNotNull(meta, "copyTexImage2D did not allocate the destination");
assertEquals(VIEW, meta.width());
assertEquals(VIEW, meta.height());
GlsmSdlHeadlessRig.endFrame();
final ByteBuffer out = MemoryUtil.memAlloc(VIEW * VIEW * 4);
try {
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
for (int i = 0; i < VIEW * VIEW; i++) {
final int idx = i;
assertEquals((byte) 0xFF, out.get(i * 4), () -> "R at texel " + idx);
assertEquals((byte) 0x00, out.get(i * 4 + 1), () -> "G at texel " + idx);
assertEquals((byte) 0x00, out.get(i * 4 + 2), () -> "B at texel " + idx);
assertEquals((byte) 0xFF, out.get(i * 4 + 3), () -> "A at texel " + idx);
}
} finally {
MemoryUtil.memFree(out);
}
}
}
@@ -0,0 +1,213 @@
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.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlFfpTest {
private static final int TEXEL = 0xFFFF4020;
private static final int LIGHTMAP_SIZE = 16;
private static int baseTexture;
private static int lightmapTexture;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
GlsmSdlHeadlessRig.beginFrame();
baseTexture = GlsmSdlHeadlessRig.createSolidTexture(TEXEL);
lightmapTexture = createLightmapTexture();
GlsmSdlHeadlessRig.endFrame();
}
private static int createLightmapTexture() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final ByteBuffer texels = MemoryUtil.memAlloc(LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);
try {
for (int y = 0; y < LIGHTMAP_SIZE; y++) {
for (int x = 0; x < LIGHTMAP_SIZE; x++) {
final boolean brightest = (x == 0 && y == 0) || (x == LIGHTMAP_SIZE - 1 && y == LIGHTMAP_SIZE - 1);
texels.put((byte) (brightest ? 255 : 0));
texels.put((byte) 255);
texels.put((byte) (brightest ? 255 : 0));
texels.put((byte) 255);
}
}
texels.flip();
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
} finally {
MemoryUtil.memFree(texels);
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
return id;
}
private static void bindBaseTexture() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, baseTexture);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
}
private static void enableLightmapUnit(boolean vanillaTextureMatrix) {
GlsmSdlHeadlessRig.enableLightmapUnit(lightmapTexture, vanillaTextureMatrix);
}
private static void quadUnit0Only() {
GLStateManager.glBegin(GL11.GL_QUADS);
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, false, (short) 0);
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, false, (short) 0);
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, false, (short) 0);
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, false, (short) 0);
GLStateManager.glEnd();
}
private static void quadTwoUnits(boolean vanillaBrightness) {
final short brightness = (short) (vanillaBrightness ? 240 : 0);
GLStateManager.glBegin(GL11.GL_QUADS);
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, true, brightness);
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, true, brightness);
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, true, brightness);
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, true, brightness);
GLStateManager.glEnd();
}
private static void assertTexelEverywhere(String label) {
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
final int corner = pixelAt(pixels, SIZE, 2, 2);
assertEquals(TEXEL, center, () -> label + ": center " + describe(center));
assertEquals(TEXEL, corner, () -> label + ": corner " + describe(corner));
}
@Test
void singleUnitTexturedQuadShowsTheTexel() {
bindBaseTexture();
quadUnit0Only();
assertTexelEverywhere("single unit");
}
@Test
void twoUnitTerrainQuadWithVanillaLightmapMatrixShowsTheTexel() {
bindBaseTexture();
enableLightmapUnit(true);
GlsmSdlHeadlessRig.terrainRenderState();
quadTwoUnits(true);
assertTexelEverywhere("two units, vanilla lightmap matrix");
}
@Test
void twoUnitTerrainQuadWithIdentityLightmapMatrixShowsTheTexel() {
bindBaseTexture();
enableLightmapUnit(false);
GlsmSdlHeadlessRig.terrainRenderState();
quadTwoUnits(false);
assertTexelEverywhere("two units, identity lightmap matrix, zero brightness");
}
@Test
void twoUnitTerrainQuadThroughDisplayListsShowsTheTexel() {
bindBaseTexture();
enableLightmapUnit(true);
GlsmSdlHeadlessRig.terrainRenderState();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(0.0f, 0.0f, 0.0f);
quadTwoUnits(true);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
GLStateManager.glCallList(list);
assertTexelEverywhere("two units, glCallList");
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
final IntBuffer lists = BufferUtils.createIntBuffer(1);
lists.put(0, list);
lists.position(0);
GLStateManager.glCallLists(lists);
assertTexelEverywhere("two units, glCallLists");
}
@Test
void litTexturedQuadIsShaded() {
bindBaseTexture();
enableGuiStandardItemLighting();
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
quadUnit0Only();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
final int red = (center >> 16) & 0xFF;
assertEquals(255, (center >>> 24) & 0xFF, () -> "lit quad alpha: " + describe(center));
assertTrue(red > 100, () -> "lit quad red: " + describe(center));
assertTrue(red < ((TEXEL >> 16) & 0xFF), () -> "lit quad is not attenuated: " + describe(center));
}
private static void enableGuiStandardItemLighting() {
GLStateManager.glPushMatrix();
GLStateManager.glRotatef(-30.0f, 0.0f, 1.0f, 0.0f);
GLStateManager.glRotatef(165.0f, 1.0f, 0.0f, 0.0f);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glEnable(GL11.GL_LIGHT1);
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
final float[] first = normalize(0.2f, 1.0f, -0.7f);
final float[] second = normalize(-0.2f, 1.0f, 0.7f);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(first[0], first[1], first[2], 0.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, floats(second[0], second[1], second[2], 0.0f));
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glShadeModel(GL11.GL_FLAT);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
GLStateManager.glPopMatrix();
}
private static float[] normalize(float x, float y, float z) {
final float len = (float) Math.sqrt(x * x + y * y + z * z);
return new float[]{ x / len, y / len, z / len };
}
private static FloatBuffer floats(float a, float b, float c, float d) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
buf.put(a).put(b).put(c).put(d);
buf.flip();
return buf;
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class GlsmSdlFrameExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
GlsmSdlHeadlessRig.beginFrameAndReset();
}
@Override
public void afterEach(ExtensionContext context) {
GlsmSdlHeadlessRig.endFrame();
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
}
}
@@ -0,0 +1,307 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
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;
import org.lwjgl.opengl.GL30;
import org.lwjgl.sdl.SDLError;
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;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public final class GlsmSdlHeadlessRig {
public static final int SIZE = 64;
private static boolean booted;
private static int fbo;
private static int colorTexture;
private static int depthRenderbuffer;
private GlsmSdlHeadlessRig() {}
public static void boot() {
if (booted) return;
SdlTestRig.initSdlVideo();
assumeTrue(SDLGPUGate.device().createDevice(), () -> "no SDL GPU device: " + SDLError.SDL_GetError());
Reflect.setStatic(SDLGPUGate.class, "engaged", true);
final RenderBackend backend = BackendManager.RENDER_BACKEND;
assumeTrue(backend instanceof SDLGPURenderBackend, () -> "selected backend is " + backend.getName());
backend.onPostWindowCreate(0L);
Reflect.setStaticFinal(GLStateManager.class, "MainThread", Thread.class, Thread.currentThread());
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(SIZE, SIZE)
.directDrawer(TessellatorStreamingDrawer::drawDirect)
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
.build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
assertTrue(GlsmSdlRedirectAgent.transformedCount() > 0, "redirect agent transformed no classes");
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
createTarget();
booted = true;
}
private static void createTarget() {
beginFrame();
colorTexture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, colorTexture);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
depthRenderbuffer = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthRenderbuffer);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTexture, 0);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthRenderbuffer);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
PipelineCache.setSwapchainFormats(new int[]{ colorTargetSdlFormat() });
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);
return meta != null ? meta.sdlFormat() : 0;
}
private static boolean frameOpen;
public static void beginFrame() {
if (frameOpen) return;
BackendManager.RENDER_BACKEND.onFrameBegin();
frameOpen = true;
}
public static void endFrame() {
if (!frameOpen) return;
frameOpen = false;
BackendManager.RENDER_BACKEND.onFrameEnd();
}
public static void bindTarget() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
}
public static void clearTo(float r, float g, float b, float a) {
GLStateManager.glClearColor(r, g, b, a);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
public static void beginFrameAndReset() {
beginFrame();
resetState();
bindTarget();
clearTo(0.0f, 0.0f, 0.0f, 1.0f);
}
public static void resetState() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
GLStateManager.glDisable(GL11.GL_FOG);
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
}
public static void terrainRenderState() {
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glEnable(GL11.GL_FOG);
GLStateManager.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR);
GLStateManager.glFogf(GL11.GL_FOG_START, 1.0e6f);
GLStateManager.glFogf(GL11.GL_FOG_END, 1.0e7f);
}
public static int createSolidTexture(int argb) {
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 = MemoryUtil.memAlloc(2 * 2 * 4);
try {
for (int i = 0; i < 4; i++) {
texels.put((byte) ((argb >> 16) & 0xFF));
texels.put((byte) ((argb >> 8) & 0xFF));
texels.put((byte) (argb & 0xFF));
texels.put((byte) ((argb >>> 24) & 0xFF));
}
texels.flip();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
} finally {
MemoryUtil.memFree(texels);
}
return id;
}
public static void enableLightmapUnit(int lightmapTexture, boolean vanillaTextureMatrix) {
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();
if (vanillaTextureMatrix) {
final float scale = 0.00390625f;
GLStateManager.glScalef(scale, scale, scale);
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
}
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
public static void emitVertex(float x, float y, float u, float v, boolean lightmap, short brightness) {
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glTexCoord2f(u, v);
if (lightmap) {
GLStateManager.glMultiTexCoord2s(GL13.GL_TEXTURE1, brightness, brightness);
}
GLStateManager.glVertex3f(x, y, 0.0f);
}
private static void vertex(float x, float y, float r, float g, float b) {
GLStateManager.glColor4f(r, g, b, 1.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glVertex3f(x, y, 0.0f);
}
private static void texVertex(float x, float y, float u, float v) {
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glTexCoord2f(u, v);
GLStateManager.glVertex3f(x, y, 0.0f);
}
public static void halfQuad(float y0, float y1, float r, float g, float b) {
GLStateManager.glBegin(GL11.GL_QUADS);
vertex(-1.0f, y0, r, g, b);
vertex(1.0f, y0, r, g, b);
vertex(1.0f, y1, r, g, b);
vertex(-1.0f, y1, r, g, b);
GLStateManager.glEnd();
}
public static void solidQuad(float r, float g, float b) {
halfQuad(-1.0f, 1.0f, r, g, b);
}
public static void texturedQuad(int texture, float extent) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glBegin(GL11.GL_QUADS);
texVertex(-extent, -extent, 0.0f, 0.0f);
texVertex(extent, -extent, 1.0f, 0.0f);
texVertex(extent, extent, 1.0f, 1.0f);
texVertex(-extent, extent, 0.0f, 1.0f);
GLStateManager.glEnd();
}
public static int[] readTarget() {
return readTarget(0, 0, SIZE, SIZE, (byte) 0);
}
public static int[] readTarget(int x, int y, int w, int h) {
return readTarget(x, y, w, h, (byte) 0);
}
public static int[] readTarget(int x, int y, int w, int h, byte fill) {
endFrame();
final ByteBuffer pixels = MemoryUtil.memAlloc(w * h * 4);
try {
for (int i = 0; i < w * h * 4; i++) pixels.put(i, fill);
GLStateManager.glReadPixels(x, y, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
final int[] out = new int[w * h];
for (int i = 0; i < out.length; i++) {
final int r = pixels.get(i * 4) & 0xFF;
final int g = pixels.get(i * 4 + 1) & 0xFF;
final int b = pixels.get(i * 4 + 2) & 0xFF;
final int a = pixels.get(i * 4 + 3) & 0xFF;
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
return out;
} finally {
MemoryUtil.memFree(pixels);
}
}
public static int pixelAt(int[] pixels, int rowStride, int x, int y) {
return pixels[y * rowStride + x];
}
public static String describe(int argb) {
return String.format("a=%d r=%d g=%d b=%d", (argb >>> 24) & 0xFF, (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
}
public static void assertPixel(int[] pixels, int stride, int x, int y, int expected, String label) {
final int got = pixelAt(pixels, stride, x, y);
assertEquals(expected, got, () -> label + " at (" + x + "," + y + "): " + describe(got));
}
public static void assertUniform(int[] pixels, int expected, String label) {
for (int i = 0; i < pixels.length; i++) {
final int got = pixels[i];
final int idx = i;
assertEquals(expected, got, () -> label + " at index " + idx + ": " + describe(got));
}
}
}
@@ -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();
}
}
}
Loaded 100 of 253 files, more files were not shown because too many files have changed in this diff. Show more