simplify tools: shared harness setup (pipeline, block proxy, render job, framing, record copy, label cells, contact sheets) for the standalone renderers and the benchmark, block-entity scenes as name/yaw records, one dump pipeline for mob and avatar renderers with a shared submit helper, transform record and jem/entry/subset helpers, posed block entities from direct layer constants, pptest scenes registered once with settle-tick and resolution tables and put() for all blocks, jdk detection and full error log in the dump script, pptest jar rebuilt instead of updated, gradle task printing the tool classpath, comments trimmed
This commit is contained in:
1 parent
b242adb6d2
commit
02e052787c
11 files changed
+1449
-1686
No files matched your search
+36
-79
@@ -1,11 +1,7 @@
|
||||
import eu.mhsl.minecraft.pixelpics.assets.*;
|
||||
import eu.mhsl.minecraft.pixelpics.render.entity.*;
|
||||
import eu.mhsl.minecraft.pixelpics.render.render.*;
|
||||
import eu.mhsl.minecraft.pixelpics.render.sky.SkyContext;
|
||||
import eu.mhsl.minecraft.pixelpics.render.render.RenderJob;
|
||||
import eu.mhsl.minecraft.pixelpics.render.snapshot.WorldSnapshot;
|
||||
import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider;
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.util.Vector;
|
||||
@@ -16,41 +12,24 @@ import java.io.File;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Reproducible render benchmark (no server): farm scenes with hundreds of mobs / dropped items on a block
|
||||
* floor plus a "normal" scene (few mobs, translucent + additive layers). Renders every scene {@code -Druns}
|
||||
* times (after {@code -Dwarmup} runs), prints the median wall time and writes one PNG per scene to
|
||||
* {@code -Dout} (default ~/Dokumente/PixelPics-Entity-Renders/bench) for before/after pixel diffs.
|
||||
* Args: scene names to run (default all): farm_mobs, farm_empty (same view, no entities), farm_mobs_close,
|
||||
* farm_items, village, normal. {@code -Djava.util.concurrent.ForkJoinPool.common.parallelism=N} pins the thread count.
|
||||
* Reproducible render benchmark (no server): farm scenes with hundreds of mobs / dropped items on a grass floor
|
||||
* plus a "normal" scene (few mobs, translucent + additive layers). Renders every scene {@code -Druns} times (after
|
||||
* {@code -Dwarmup} runs), prints median/min wall time and writes one PNG per scene to {@code -Dout} (default
|
||||
* ~/Dokumente/PixelPics-Entity-Renders/bench) for before/after pixel diffs. Args: scene names (default all):
|
||||
* farm_mobs, farm_empty (same view, no entities), farm_mobs_close, farm_items, village, normal.
|
||||
* {@code -Djava.util.concurrent.ForkJoinPool.common.parallelism=N} pins the thread count. Build: see {@link Harness}.
|
||||
*/
|
||||
public class EntityBenchmark {
|
||||
|
||||
static final String ROOT = "/home/elias/Dokumente/mcTestServer/plugins/PixelPics";
|
||||
static final int W = 320, H = 240, SSAA = 3;
|
||||
static final int W = 320, H = 240;
|
||||
|
||||
record Scene(String name, Vector cam, Vector look, List<EntityState> mobs, List<ObjectState> objects) {}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Logger log = Logger.getLogger("bench");
|
||||
ResourcePack pack = ResourcePackLoader.load(new File(ROOT, "resourcepack"), log).orElseThrow();
|
||||
AssetReader reader = new AssetReader(pack);
|
||||
TextureCache textures = new TextureCache(pack);
|
||||
BlockModelRegistry registry = new BlockModelRegistry(reader, textures);
|
||||
BiomeTintProvider tint = new BiomeTintProvider(textures);
|
||||
var geo = new eu.mhsl.minecraft.pixelpics.render.entity.cem.CemModelLoader();
|
||||
geo.load(new java.io.FileInputStream("src/main/resources/cem/cem_template_models.json"), log);
|
||||
geo.load(new java.io.FileInputStream("src/main/resources/cem/vanilla_entity_models.json"), log);
|
||||
var font = eu.mhsl.minecraft.pixelpics.assets.font.FontLoader.load(pack, textures, log);
|
||||
SkinCache skins = new SkinCache();
|
||||
var baker = new eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker(geo, textures, skins);
|
||||
var beBaker = new eu.mhsl.minecraft.pixelpics.render.entity.cem.BlockEntityBaker(geo, textures, skins, font);
|
||||
DefaultScreenRenderer renderer = new DefaultScreenRenderer(registry, tint, textures, baker, beBaker, log);
|
||||
|
||||
Harness h = new Harness("bench");
|
||||
WorldSnapshot world = farmWorld();
|
||||
SkyContext sky = new SkyContext(6000, 0, 6000);
|
||||
int runs = Integer.getInteger("runs", 5), warmup = Integer.getInteger("warmup", 2);
|
||||
File outDir = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-Entity-Renders/bench"));
|
||||
outDir.mkdirs();
|
||||
@@ -58,16 +37,13 @@ public class EntityBenchmark {
|
||||
|
||||
for (Scene sc : scenes()) {
|
||||
if (!only.isEmpty() && !only.contains(sc.name)) continue;
|
||||
Location loc = new Location(null, sc.cam.getX(), sc.cam.getY(), sc.cam.getZ());
|
||||
loc.setDirection(sc.look.clone().subtract(sc.cam));
|
||||
List<Vector> rayMap = EntityTestRender.buildRayMap(loc, W * SSAA, H * SSAA);
|
||||
RenderJob job = new RenderJob(world, rayMap, sc.cam, W, H, sky, sc.mobs, List.of(), List.of(), sc.objects);
|
||||
RenderJob job = Harness.job(world, sc.cam, sc.look, W, H, sc.mobs, List.of(), List.of(), sc.objects);
|
||||
BufferedImage img = null;
|
||||
for (int i = 0; i < warmup; i++) img = renderer.execute(job);
|
||||
for (int i = 0; i < warmup; i++) img = h.renderer.execute(job);
|
||||
long[] times = new long[runs];
|
||||
for (int i = 0; i < runs; i++) {
|
||||
long t0 = System.nanoTime();
|
||||
img = renderer.execute(job);
|
||||
img = h.renderer.execute(job);
|
||||
times[i] = System.nanoTime() - t0;
|
||||
}
|
||||
Arrays.sort(times);
|
||||
@@ -77,7 +53,7 @@ public class EntityBenchmark {
|
||||
}
|
||||
}
|
||||
|
||||
static List<Scene> scenes() {
|
||||
static List<Scene> scenes() throws ReflectiveOperationException {
|
||||
List<Scene> out = new ArrayList<>();
|
||||
// Mob farm: 400 animals in a 20x20 pen (1.4 block spacing), seen from an elevated corner.
|
||||
List<EntityState> farm = new ArrayList<>();
|
||||
@@ -106,9 +82,9 @@ public class EntityBenchmark {
|
||||
ItemSpec spec;
|
||||
if (r.nextInt(4) == 0) {
|
||||
int b = r.nextInt(blocks.length);
|
||||
spec = new ItemSpec(blocks[b].getKey().getKey(), ObjectTestRender.block(blocks[b], blockState[b]), null, false);
|
||||
spec = new ItemSpec(blocks[b].getKey().getKey(), Harness.block(blocks[b], blockState[b]), null, false);
|
||||
} else {
|
||||
spec = new ItemSpec(flat[r.nextInt(flat.length)], null, null, false);
|
||||
spec = Harness.item(flat[r.nextInt(flat.length)]);
|
||||
}
|
||||
items.add(ObjectState.item(a, spec, "ground", true));
|
||||
}
|
||||
@@ -149,11 +125,10 @@ public class EntityBenchmark {
|
||||
few.add(mob("villager", 4, 1, 6, 160, false));
|
||||
few.add(mob("zombie", 6.5, 1, 6, 220, false));
|
||||
few.add(mob("slime", 2, 1, 5.5, 120, false));
|
||||
EntityState creeper = mob("creeper", 7, 1, 3.5, 250, false);
|
||||
few.add(withPowered(creeper));
|
||||
few.add(Harness.copy(mob("creeper", 7, 1, 3.5, 250, false), Map.of("powered", true)));
|
||||
few.add(mob("chicken", 3.5, 1, 1.5, 90, false));
|
||||
List<ObjectState> objs = new ArrayList<>();
|
||||
objs.add(ObjectState.item(Affine.translation(4.5, 1.2, 2).mul(Affine.scale(0.5)), new ItemSpec("apple", null, null, false), "ground", true));
|
||||
objs.add(ObjectState.item(Affine.translation(4.5, 1.2, 2).mul(Affine.scale(0.5)), Harness.item("apple"), "ground", true));
|
||||
objs.add(ObjectState.text(Affine.translation(4.5, 3.2, 4).mul(Affine.rotY(Math.PI)).mul(Affine.scale(0.025)),
|
||||
List.of("PixelPics bench"), 200, 0xFFFFFFFF, 0x40000000, true));
|
||||
out.add(new Scene("normal", new Vector(0, 3.5, -2.5), new Vector(4.5, 1.5, 4), few, objs));
|
||||
@@ -173,20 +148,20 @@ public class EntityBenchmark {
|
||||
static final int[] DYES = {0xFFF9FFFE, 0xFFF9801D, 0xFFC74EBD, 0xFF3AB3DA, 0xFFFED83D, 0xFF80C71F, 0xFFF38BAA,
|
||||
0xFF474F52, 0xFF9D9D97, 0xFF169C9C, 0xFF8932B8, 0xFF3C44AA, 0xFF835432, 0xFF5E7C16, 0xFFB02E26, 0xFF1D1D21};
|
||||
|
||||
static EntityState villager(double x, double z, float yaw, String type, String prof, int level, boolean baby) {
|
||||
return new EntityState("villager", x, 1, z, yaw, baby, 0.6, 1.95,
|
||||
false, null, false, type, 0, 1.0, prof, level,
|
||||
null, false, false, false, null, null,
|
||||
false, 0, 0, 0, false, null, null, null, 0, false, null, null, null, null, null);
|
||||
static EntityState villager(double x, double z, float yaw, String type, String prof, int level, boolean baby)
|
||||
throws ReflectiveOperationException {
|
||||
return Harness.copy(mob("villager", x, 1, z, yaw, baby), Map.of("width", 0.6, "height", 1.95, "variant", type,
|
||||
"profession", prof, "villagerLevel", level));
|
||||
}
|
||||
|
||||
/** {@code s} with wool tint, humanoid armor and collar dye replaced. */
|
||||
static EntityState with(EntityState s, int tint, EntityState.Equipment eq, int collar) {
|
||||
return new EntityState(s.typeKey(), s.x(), s.y(), s.z(), s.bodyYaw(), s.baby(), s.width(), s.height(),
|
||||
s.player(), s.skinUrl(), s.slim(), s.variant(), tint, s.sizeScale(), s.profession(), s.villagerLevel(),
|
||||
s.markings(), s.saddle(), s.chest(), s.arms(), s.bodyEquip(), eq, s.invisible(), s.pitch(),
|
||||
s.bodyDye(), s.tint2(), s.passenger(), s.rightHand(), s.leftHand(), s.headItem(), collar, s.powered(),
|
||||
s.mouthItem(), s.pose(), s.partPoses(), s.shoulderLeft(), s.shoulderRight());
|
||||
static EntityState with(EntityState s, int tint, EntityState.Equipment eq, int collar)
|
||||
throws ReflectiveOperationException {
|
||||
Map<String, Object> o = new HashMap<>();
|
||||
o.put("tint", tint);
|
||||
o.put("equipment", eq);
|
||||
o.put("collar", collar);
|
||||
return Harness.copy(s, o);
|
||||
}
|
||||
|
||||
/** The variant the game always supplies (cow/pig/chicken have no texture without one). */
|
||||
@@ -199,41 +174,23 @@ public class EntityBenchmark {
|
||||
};
|
||||
}
|
||||
|
||||
static EntityState withPowered(EntityState s) {
|
||||
return new EntityState(s.typeKey(), s.x(), s.y(), s.z(), s.bodyYaw(), s.baby(), s.width(), s.height(),
|
||||
s.player(), s.skinUrl(), s.slim(), s.variant(), s.tint(), s.sizeScale(), s.profession(), s.villagerLevel(),
|
||||
s.markings(), s.saddle(), s.chest(), s.arms(), s.bodyEquip(), s.equipment(), s.invisible(), s.pitch(),
|
||||
s.bodyDye(), s.tint2(), s.passenger(), s.rightHand(), s.leftHand(), s.headItem(), s.collar(), true,
|
||||
s.mouthItem(), s.pose(), s.partPoses(), s.shoulderLeft(), s.shoulderRight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Grass floor at y=0 over [-48, 48)². Nothing stacked on it: ambient occlusion asks Material.isOccluding()
|
||||
* for non-air neighbours, which needs a server registry (not available standalone).
|
||||
*/
|
||||
static WorldSnapshot farmWorld() {
|
||||
BlockData air = ObjectTestRender.block(Material.AIR, null);
|
||||
BlockData grass = ObjectTestRender.block(Material.GRASS_BLOCK, "snowy=false");
|
||||
BlockData air = Harness.block(Material.AIR, null);
|
||||
BlockData grass = Harness.block(Material.GRASS_BLOCK, "snowy=false");
|
||||
Map<Long, ChunkSnapshot> chunks = new HashMap<>();
|
||||
for (int cx = -3; cx < 3; cx++) {
|
||||
for (int cz = -3; cz < 3; cz++) {
|
||||
final int bx0 = cx * 16, bz0 = cz * 16;
|
||||
ChunkSnapshot cs = (ChunkSnapshot) Proxy.newProxyInstance(EntityBenchmark.class.getClassLoader(),
|
||||
new Class<?>[]{ChunkSnapshot.class}, (p, m, a) -> {
|
||||
switch (m.getName()) {
|
||||
case "getBlockData": {
|
||||
int y = (int) a[1];
|
||||
return y == 0 ? grass : air;
|
||||
}
|
||||
case "getBiome": return null;
|
||||
case "equals": return p == a[0];
|
||||
case "hashCode": return System.identityHashCode(p);
|
||||
case "toString": return "chunk";
|
||||
}
|
||||
Class<?> rt = m.getReturnType();
|
||||
if (rt == boolean.class) return false;
|
||||
if (rt.isPrimitive()) return 0;
|
||||
return null;
|
||||
new Class<?>[]{ChunkSnapshot.class}, (p, m, a) -> switch (m.getName()) {
|
||||
case "getBlockData" -> (int) a[1] == 0 ? grass : air;
|
||||
case "equals" -> p == a[0];
|
||||
case "hashCode" -> System.identityHashCode(p);
|
||||
case "toString" -> "chunk";
|
||||
default -> Harness.defaultValue(m.getReturnType());
|
||||
});
|
||||
chunks.put(WorldSnapshot.chunkKey(cx, cz), cs);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user