Reduce world overlay render overhead
This commit is contained in:
1 parent
598bb95ff9
commit
5a4c4d914b
10 files changed
+691
-241
No files matched your search
@@ -44,10 +44,14 @@ final class WorldEntityFlameClusters {
|
||||
mspt, Math.max(mspt, maximum), samples);
|
||||
var anchor = new Anchor(centerX(input.bounds()), input.bounds().maxY,
|
||||
centerZ(input.bounds()));
|
||||
var previousAnchor = new Anchor(
|
||||
anchor.x() + input.previousOffsetX(),
|
||||
anchor.y() + input.previousOffsetY(),
|
||||
anchor.z() + input.previousOffsetZ());
|
||||
var visual = new Visual(ObjectPerformanceScale.averageSamples(
|
||||
metric, measurementTicks(input, tickCount)),
|
||||
updatePhase(0x811C9DC5, input.stableId()) & 0x7FFF, 1);
|
||||
return new Marker(anchor, metric, visual);
|
||||
return new Marker(previousAnchor, anchor, metric, visual);
|
||||
}
|
||||
|
||||
private static Marker marker(List<Input> group, long tickCount) {
|
||||
@@ -60,6 +64,9 @@ final class WorldEntityFlameClusters {
|
||||
double x = 0.0D;
|
||||
double z = 0.0D;
|
||||
double y = -Double.MAX_VALUE;
|
||||
double previousX = 0.0D;
|
||||
double previousZ = 0.0D;
|
||||
double previousY = -Double.MAX_VALUE;
|
||||
int phase = 0x811C9DC5;
|
||||
for (Input input : group) {
|
||||
double mspt = positiveFinite(input.metric().mspt());
|
||||
@@ -71,14 +78,23 @@ final class WorldEntityFlameClusters {
|
||||
long inputTicks = measurementTicks(input, tickCount);
|
||||
averageSamples += ObjectPerformanceScale.averageSamples(input.metric(), inputTicks);
|
||||
totalWeight += weight;
|
||||
x += centerX(input.bounds()) * weight;
|
||||
z += centerZ(input.bounds()) * weight;
|
||||
double inputX = centerX(input.bounds());
|
||||
double inputZ = centerZ(input.bounds());
|
||||
x += inputX * weight;
|
||||
z += inputZ * weight;
|
||||
y = Math.max(y, input.bounds().maxY);
|
||||
previousX += (inputX + input.previousOffsetX()) * weight;
|
||||
previousZ += (inputZ + input.previousOffsetZ()) * weight;
|
||||
previousY = Math.max(previousY,
|
||||
input.bounds().maxY + input.previousOffsetY());
|
||||
phase = updatePhase(phase, input.stableId());
|
||||
}
|
||||
PerfReportData.Metric metric = new PerfReportData.Metric(
|
||||
totalMspt, Math.max(totalMspt, totalMax), totalSamples);
|
||||
return new Marker(new Anchor(x / totalWeight, y, z / totalWeight), metric,
|
||||
var previousAnchor = new Anchor(
|
||||
previousX / totalWeight, previousY, previousZ / totalWeight);
|
||||
var anchor = new Anchor(x / totalWeight, y, z / totalWeight);
|
||||
return new Marker(previousAnchor, anchor, metric,
|
||||
new Visual(averageSamples, phase & 0x7FFF, group.size()));
|
||||
}
|
||||
|
||||
@@ -111,9 +127,15 @@ final class WorldEntityFlameClusters {
|
||||
}
|
||||
|
||||
record Input(AABB bounds, PerfReportData.Metric metric,
|
||||
long measurementTicks, int stableId) {
|
||||
long measurementTicks, int stableId,
|
||||
double previousOffsetX, double previousOffsetY, double previousOffsetZ) {
|
||||
Input(AABB bounds, PerfReportData.Metric metric, int stableId) {
|
||||
this(bounds, metric, 0L, stableId);
|
||||
this(bounds, metric, 0L, stableId, 0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
|
||||
Input(AABB bounds, PerfReportData.Metric metric,
|
||||
long measurementTicks, int stableId) {
|
||||
this(bounds, metric, measurementTicks, stableId, 0.0D, 0.0D, 0.0D);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +145,12 @@ final class WorldEntityFlameClusters {
|
||||
record Visual(double flameStrength, int phase, int memberCount) {
|
||||
}
|
||||
|
||||
record Marker(Anchor anchor, PerfReportData.Metric metric, Visual visual) {
|
||||
record Marker(Anchor previousAnchor, Anchor anchor,
|
||||
PerfReportData.Metric metric, Visual visual) {
|
||||
Marker(Anchor anchor, PerfReportData.Metric metric, Visual visual) {
|
||||
this(anchor, anchor, metric, visual);
|
||||
}
|
||||
|
||||
double x() {
|
||||
return anchor.x();
|
||||
}
|
||||
@@ -147,6 +174,18 @@ final class WorldEntityFlameClusters {
|
||||
int memberCount() {
|
||||
return visual.memberCount();
|
||||
}
|
||||
|
||||
double previousX() {
|
||||
return previousAnchor.x();
|
||||
}
|
||||
|
||||
double previousY() {
|
||||
return previousAnchor.y();
|
||||
}
|
||||
|
||||
double previousZ() {
|
||||
return previousAnchor.z();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ClusterBuilder {
|
||||
|
||||
@@ -12,6 +12,8 @@ public final class WorldOverlayMath {
|
||||
public static final double LABEL_TRANSITION_END = 12.0D;
|
||||
public static final double LABEL_FADE_START = 20.0D;
|
||||
public static final double LABEL_FADE_END = 40.0D;
|
||||
static final double SPARK_FADE_START = 20.0D;
|
||||
static final double SPARK_FADE_END = 24.0D;
|
||||
public static final double MAX_FLAME_VIEWPORT_FRACTION = 0.18D;
|
||||
public static final int MAX_DISPLAY_MICROS = 999_999_999;
|
||||
public static final int OVERFLOW_MICROS = MAX_DISPLAY_MICROS + 1;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.moepus.serverwarashi.client.world;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class WorldPerfMarkerBuckets {
|
||||
static final int BUCKET_WIDTH = 8;
|
||||
static final int BUCKET_COUNT = (int) (WorldOverlayMath.OBJECT_FADE_END / BUCKET_WIDTH);
|
||||
private static final int SPARK_BUCKET_COUNT =
|
||||
(int) (WorldOverlayMath.SPARK_FADE_END / BUCKET_WIDTH);
|
||||
private static final int LABEL_BUCKET_COUNT =
|
||||
(int) (WorldOverlayMath.LABEL_FADE_END / BUCKET_WIDTH);
|
||||
private static final double MAX_DISTANCE_SQUARED =
|
||||
WorldOverlayMath.OBJECT_FADE_END * WorldOverlayMath.OBJECT_FADE_END;
|
||||
|
||||
private WorldPerfMarkerBuckets() {
|
||||
}
|
||||
|
||||
static Batch order(List<WorldEntityFlameClusters.Marker> markers, Vec3 camera) {
|
||||
byte[] markerBuckets = new byte[markers.size()];
|
||||
int[] counts = new int[BUCKET_COUNT];
|
||||
int visibleCount = classify(markers, camera, markerBuckets, counts);
|
||||
WorldEntityFlameClusters.Marker[] ordered =
|
||||
new WorldEntityFlameClusters.Marker[visibleCount];
|
||||
int[] writeOffsets = writeOffsets(counts);
|
||||
for (int index = 0; index < markers.size(); index++) {
|
||||
int bucket = markerBuckets[index];
|
||||
if (bucket >= 0) {
|
||||
ordered[writeOffsets[bucket]++] = markers.get(index);
|
||||
}
|
||||
}
|
||||
int sparkBase = visibleCount - nearCount(counts, SPARK_BUCKET_COUNT);
|
||||
int labelBase = visibleCount - nearCount(counts, LABEL_BUCKET_COUNT);
|
||||
return new Batch(ordered, sparkBase, labelBase);
|
||||
}
|
||||
|
||||
static int bucketForDistanceSquared(double distanceSquared) {
|
||||
if (!Double.isFinite(distanceSquared) || distanceSquared < 0.0D
|
||||
|| distanceSquared > MAX_DISTANCE_SQUARED) {
|
||||
return -1;
|
||||
}
|
||||
if (distanceSquared == MAX_DISTANCE_SQUARED) {
|
||||
return BUCKET_COUNT - 1;
|
||||
}
|
||||
int low = 0;
|
||||
int high = BUCKET_COUNT;
|
||||
while (low < high) {
|
||||
int middle = (low + high) >>> 1;
|
||||
double boundary = (middle + 1) * BUCKET_WIDTH;
|
||||
if (distanceSquared < boundary * boundary) {
|
||||
high = middle;
|
||||
} else {
|
||||
low = middle + 1;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
private static int classify(List<WorldEntityFlameClusters.Marker> markers,
|
||||
Vec3 camera,
|
||||
byte[] markerBuckets,
|
||||
int[] counts) {
|
||||
int visibleCount = 0;
|
||||
for (int index = 0; index < markers.size(); index++) {
|
||||
WorldEntityFlameClusters.Marker marker = markers.get(index);
|
||||
int bucket = bucketForDistanceSquared(distanceSquared(marker, camera));
|
||||
markerBuckets[index] = (byte) bucket;
|
||||
if (bucket >= 0) {
|
||||
counts[bucket]++;
|
||||
visibleCount++;
|
||||
}
|
||||
}
|
||||
return visibleCount;
|
||||
}
|
||||
|
||||
private static int[] writeOffsets(int[] counts) {
|
||||
int[] offsets = new int[BUCKET_COUNT];
|
||||
int offset = 0;
|
||||
for (int bucket = BUCKET_COUNT - 1; bucket >= 0; bucket--) {
|
||||
offsets[bucket] = offset;
|
||||
offset += counts[bucket];
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
private static int nearCount(int[] counts, int bucketCount) {
|
||||
int count = 0;
|
||||
for (int bucket = 0; bucket < bucketCount; bucket++) {
|
||||
count += counts[bucket];
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static double distanceSquared(WorldEntityFlameClusters.Marker marker, Vec3 camera) {
|
||||
double dx = marker.x() - camera.x;
|
||||
double dy = marker.y() - camera.y;
|
||||
double dz = marker.z() - camera.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
record Batch(WorldEntityFlameClusters.Marker[] markers,
|
||||
int sparkBase,
|
||||
int labelBase) {
|
||||
int size() {
|
||||
return markers.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.moepus.serverwarashi.client.world;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.BufferUploader;
|
||||
import com.moepus.serverwarashi.common.modules.performance.report.PerfReportLimits;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -20,71 +21,113 @@ import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
|
||||
final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
static final int MAX_MARKER_COUNT = 4_096;
|
||||
static final int TEXELS_PER_MARKER = 3;
|
||||
static final int MAX_MARKER_COUNT = PerfReportLimits.MAX_WORLD_HOTSPOT_ENTRIES;
|
||||
static final int TEXELS_PER_MARKER = 4;
|
||||
static final int FLOATS_PER_MARKER = TEXELS_PER_MARKER * 4;
|
||||
private static final int QUAD_VERTEX_COUNT = 4;
|
||||
private static final int FLOAT_BYTES = Float.BYTES;
|
||||
private static final int MARKER_TEXTURE_UNIT = 2;
|
||||
private static final String MARKER_SAMPLER = "MarkerData";
|
||||
|
||||
private final MarkerBuffer staticMarkers = new MarkerBuffer();
|
||||
private final MarkerBuffer dynamicMarkers = new MarkerBuffer();
|
||||
private int quadVao = -1;
|
||||
private int quadVertexBuffer = -1;
|
||||
private int markerBuffer = -1;
|
||||
private int markerTexture = -1;
|
||||
private FloatBuffer markerStaging;
|
||||
private int markerCapacity;
|
||||
private int markerCount;
|
||||
private int stagingCapacity;
|
||||
private ShaderInstance samplerShader;
|
||||
private int markerSamplerLocation = -1;
|
||||
|
||||
void replace(List<Entry> entries) {
|
||||
void replace(Source source,
|
||||
List<WorldEntityFlameClusters.Marker> markers,
|
||||
Vec3 camera) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
int entryCount = Math.min(entries.size(), MAX_MARKER_COUNT);
|
||||
if (entryCount == 0) {
|
||||
clear();
|
||||
if (markers.size() > MAX_MARKER_COUNT) {
|
||||
throw new IllegalArgumentException("Too many world overlay markers: " + markers.size());
|
||||
}
|
||||
WorldPerfMarkerBuckets.Batch ordered = WorldPerfMarkerBuckets.order(markers, camera);
|
||||
MarkerBuffer target = buffer(source);
|
||||
if (ordered.size() == 0) {
|
||||
target.clear();
|
||||
return;
|
||||
}
|
||||
ensureMarkerCapacity(entryCount);
|
||||
ensureGpuObjects();
|
||||
ensureStagingCapacity(ordered.size());
|
||||
ensureGpuObjects(target);
|
||||
target.originX = camera.x;
|
||||
target.originZ = camera.z;
|
||||
UploadOrigin origin = new UploadOrigin(target.originX, target.originZ);
|
||||
markerStaging.clear();
|
||||
for (int index = 0; index < entryCount; index++) {
|
||||
encodeMarker(entries.get(index), markerStaging);
|
||||
for (WorldEntityFlameClusters.Marker marker : ordered.markers()) {
|
||||
encodeMarker(marker, origin, markerStaging);
|
||||
}
|
||||
markerStaging.flip();
|
||||
upload(target);
|
||||
target.markerCount = ordered.size();
|
||||
target.sparkMarkerBase = ordered.sparkBase();
|
||||
target.labelMarkerBase = ordered.labelBase();
|
||||
}
|
||||
|
||||
private void upload(MarkerBuffer target) {
|
||||
int previousBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
|
||||
try {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, markerBuffer);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, target.markerBuffer);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, markerStaging, GL15.GL_STREAM_DRAW);
|
||||
} finally {
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousBuffer);
|
||||
}
|
||||
markerCount = entryCount;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
markerCount = 0;
|
||||
staticMarkers.clear();
|
||||
dynamicMarkers.clear();
|
||||
}
|
||||
|
||||
void draw(Batch batch, DrawContext context) {
|
||||
if (markerCount == 0) {
|
||||
void clear(Source source) {
|
||||
buffer(source).clear();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return staticMarkers.markerCount == 0 && dynamicMarkers.markerCount == 0;
|
||||
}
|
||||
|
||||
boolean isEmpty(Source source) {
|
||||
return buffer(source).markerCount == 0;
|
||||
}
|
||||
|
||||
double originX(Source source) {
|
||||
return buffer(source).originX;
|
||||
}
|
||||
|
||||
double originZ(Source source) {
|
||||
return buffer(source).originZ;
|
||||
}
|
||||
|
||||
void draw(Source source, Batch batch, DrawContext context) {
|
||||
MarkerBuffer target = buffer(source);
|
||||
int markerBase = markerBase(target, batch);
|
||||
int drawMarkerCount = target.markerCount - markerBase;
|
||||
if (drawMarkerCount == 0) {
|
||||
return;
|
||||
}
|
||||
ShaderInstance shader = context.shader();
|
||||
setDrawUniforms(batch, context);
|
||||
setDrawUniforms(batch, context, markerBase);
|
||||
shader.setSampler("Sampler0", RenderSystem.getShaderTexture(0));
|
||||
shader.setSampler("Sampler1", RenderSystem.getShaderTexture(1));
|
||||
shader.apply();
|
||||
batch.applyBlend();
|
||||
drawInstances(target, shader, batch.instancesFor(drawMarkerCount));
|
||||
}
|
||||
|
||||
private void drawInstances(MarkerBuffer target, ShaderInstance shader, int instanceCount) {
|
||||
int previousVao = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
|
||||
int previousActiveTexture = GlStateManager._getActiveTexture();
|
||||
GlStateManager._activeTexture(GL13.GL_TEXTURE0 + MARKER_TEXTURE_UNIT);
|
||||
int previousMarkerTexture = GL11.glGetInteger(GL31.GL_TEXTURE_BINDING_BUFFER);
|
||||
try {
|
||||
bindMarkerData(shader);
|
||||
bindMarkerData(target, shader);
|
||||
GL30.glBindVertexArray(quadVao);
|
||||
GL31.glDrawArraysInstanced(GL11.GL_TRIANGLE_STRIP, 0, QUAD_VERTEX_COUNT,
|
||||
batch.instancesFor(markerCount));
|
||||
GL31.glDrawArraysInstanced(
|
||||
GL11.GL_TRIANGLE_STRIP, 0, QUAD_VERTEX_COUNT, instanceCount);
|
||||
} finally {
|
||||
GL30.glBindVertexArray(previousVao);
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, previousMarkerTexture);
|
||||
@@ -94,11 +137,20 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private void setDrawUniforms(Batch batch, DrawContext context) {
|
||||
private static int markerBase(MarkerBuffer target, Batch batch) {
|
||||
return switch (batch) {
|
||||
case SPARK -> target.sparkMarkerBase;
|
||||
case LABEL -> target.labelMarkerBase;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static void setDrawUniforms(Batch batch, DrawContext context, int markerBase) {
|
||||
ShaderInstance shader = context.shader();
|
||||
shader.safeGetUniform("RenderPass").set(batch.renderPass());
|
||||
shader.safeGetUniform("ParticleStart").set(batch.particleStart());
|
||||
shader.safeGetUniform("SlotsPerMarker").set(batch.slotsPerMarker());
|
||||
shader.safeGetUniform("MarkerBase").set(markerBase);
|
||||
if (shader.MODEL_VIEW_MATRIX != null) {
|
||||
shader.MODEL_VIEW_MATRIX.set(context.modelView());
|
||||
}
|
||||
@@ -107,7 +159,7 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private void bindMarkerData(ShaderInstance shader) {
|
||||
private void bindMarkerData(MarkerBuffer target, ShaderInstance shader) {
|
||||
if (samplerShader != shader) {
|
||||
int program = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
markerSamplerLocation = GL20.glGetUniformLocation(program, MARKER_SAMPLER);
|
||||
@@ -116,12 +168,12 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
}
|
||||
samplerShader = shader;
|
||||
}
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, markerTexture);
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, target.markerTexture);
|
||||
GL20.glUniform1i(markerSamplerLocation, MARKER_TEXTURE_UNIT);
|
||||
}
|
||||
|
||||
private void ensureMarkerCapacity(int required) {
|
||||
if (required <= markerCapacity) {
|
||||
private void ensureStagingCapacity(int required) {
|
||||
if (required <= stagingCapacity) {
|
||||
return;
|
||||
}
|
||||
int nextCapacity = Math.max(16, Integer.highestOneBit(required - 1) << 1);
|
||||
@@ -134,25 +186,20 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
FloatBuffer nextStaging = MemoryUtil.memAllocFloat(nextCapacity * FLOATS_PER_MARKER);
|
||||
MemoryUtil.memFree(markerStaging);
|
||||
markerStaging = nextStaging;
|
||||
markerCapacity = nextCapacity;
|
||||
stagingCapacity = nextCapacity;
|
||||
}
|
||||
|
||||
private void ensureGpuObjects() {
|
||||
if (quadVao >= 0) {
|
||||
return;
|
||||
}
|
||||
private void ensureGpuObjects(MarkerBuffer target) {
|
||||
int previousVao = GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
|
||||
int previousArrayBuffer = GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING);
|
||||
int previousMarkerTexture = GL11.glGetInteger(GL31.GL_TEXTURE_BINDING_BUFFER);
|
||||
try {
|
||||
createQuad();
|
||||
markerBuffer = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, markerBuffer);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER,
|
||||
(long) FLOATS_PER_MARKER * FLOAT_BYTES, GL15.GL_STREAM_DRAW);
|
||||
markerTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, markerTexture);
|
||||
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL30.GL_RGBA32F, markerBuffer);
|
||||
if (quadVao < 0) {
|
||||
createQuad();
|
||||
}
|
||||
if (target.markerBuffer < 0) {
|
||||
createMarkerBuffer(target);
|
||||
}
|
||||
} finally {
|
||||
GL30.glBindVertexArray(previousVao);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousArrayBuffer);
|
||||
@@ -161,6 +208,16 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private void createMarkerBuffer(MarkerBuffer target) {
|
||||
target.markerBuffer = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, target.markerBuffer);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER,
|
||||
(long) FLOATS_PER_MARKER * FLOAT_BYTES, GL15.GL_STREAM_DRAW);
|
||||
target.markerTexture = GL11.glGenTextures();
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, target.markerTexture);
|
||||
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL30.GL_RGBA32F, target.markerBuffer);
|
||||
}
|
||||
|
||||
private void createQuad() {
|
||||
quadVao = GL30.glGenVertexArrays();
|
||||
quadVertexBuffer = GL15.glGenBuffers();
|
||||
@@ -179,31 +236,35 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
3 * FLOAT_BYTES, 0L);
|
||||
}
|
||||
|
||||
static void encodeMarker(Entry entry, FloatBuffer target) {
|
||||
WorldEntityFlameClusters.Marker marker = entry.marker();
|
||||
static void encodeMarker(WorldEntityFlameClusters.Marker marker,
|
||||
UploadOrigin origin,
|
||||
FloatBuffer target) {
|
||||
int rgb = WorldOverlayMath.colorRgb(marker.flameStrength());
|
||||
int strength = WorldOverlayMath.packFlameStrength(marker.flameStrength());
|
||||
WorldOverlayMath.PackedLabel label = WorldOverlayMath.packLabel(
|
||||
WorldOverlayMath.roundedMicros(marker.metric()));
|
||||
target.put((float) entry.x()).put((float) (entry.y() + 0.04D))
|
||||
.put((float) entry.z()).put((float) strength);
|
||||
target.put((float) (marker.x() - origin.x())).put((float) (marker.y() + 0.04D))
|
||||
.put((float) (marker.z() - origin.z())).put((float) strength);
|
||||
target.put((rgb >> 16 & 0xFF) / 255.0F).put((rgb >> 8 & 0xFF) / 255.0F)
|
||||
.put((rgb & 0xFF) / 255.0F)
|
||||
.put((float) WorldFlameParticleLayout.phaseWord(marker.phase()));
|
||||
target.put((float) label.lowWord()).put((float) label.markedHighWord())
|
||||
.put(WorldOverlayMath.packLabelStrength(marker.flameStrength()) / 255.0F)
|
||||
.put(0.0F);
|
||||
target.put((float) (marker.previousX() - origin.x()))
|
||||
.put((float) (marker.previousY() + 0.04D))
|
||||
.put((float) (marker.previousZ() - origin.z())).put(0.0F);
|
||||
}
|
||||
|
||||
private MarkerBuffer buffer(Source source) {
|
||||
return source == Source.STATIC ? staticMarkers : dynamicMarkers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
if (markerTexture >= 0) {
|
||||
GL11.glDeleteTextures(markerTexture);
|
||||
}
|
||||
if (markerBuffer >= 0) {
|
||||
GL15.glDeleteBuffers(markerBuffer);
|
||||
}
|
||||
deleteMarkerBuffer(staticMarkers);
|
||||
deleteMarkerBuffer(dynamicMarkers);
|
||||
if (quadVertexBuffer >= 0) {
|
||||
GL15.glDeleteBuffers(quadVertexBuffer);
|
||||
}
|
||||
@@ -216,18 +277,30 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
reset();
|
||||
}
|
||||
|
||||
private static void deleteMarkerBuffer(MarkerBuffer target) {
|
||||
if (target.markerTexture >= 0) {
|
||||
GL11.glDeleteTextures(target.markerTexture);
|
||||
}
|
||||
if (target.markerBuffer >= 0) {
|
||||
GL15.glDeleteBuffers(target.markerBuffer);
|
||||
}
|
||||
target.reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
quadVao = -1;
|
||||
quadVertexBuffer = -1;
|
||||
markerBuffer = -1;
|
||||
markerTexture = -1;
|
||||
markerStaging = null;
|
||||
markerCapacity = 0;
|
||||
markerCount = 0;
|
||||
stagingCapacity = 0;
|
||||
samplerShader = null;
|
||||
markerSamplerLocation = -1;
|
||||
}
|
||||
|
||||
enum Source {
|
||||
STATIC,
|
||||
DYNAMIC
|
||||
}
|
||||
|
||||
enum Batch {
|
||||
SMOKE(2, WorldFlameParticleLayout.ORANGE_PARTICLE_START,
|
||||
WorldFlameParticleLayout.ORANGE_PARTICLE_COUNT, BlendMode.PREMULTIPLIED),
|
||||
@@ -294,19 +367,33 @@ final class WorldPerfObjectBuffers implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
record Entry(WorldEntityFlameClusters.Marker marker,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
double distanceSquared) {
|
||||
static Entry from(WorldEntityFlameClusters.Marker marker,
|
||||
Vec3 camera,
|
||||
double distanceSquared) {
|
||||
return new Entry(marker, marker.x() - camera.x, marker.y() - camera.y,
|
||||
marker.z() - camera.z, distanceSquared);
|
||||
}
|
||||
record UploadOrigin(double x, double z) {
|
||||
}
|
||||
|
||||
record DrawContext(ShaderInstance shader, Matrix4f modelView, Matrix4f projection) {
|
||||
}
|
||||
|
||||
private static final class MarkerBuffer {
|
||||
private int markerBuffer = -1;
|
||||
private int markerTexture = -1;
|
||||
private int markerCount;
|
||||
private int sparkMarkerBase;
|
||||
private int labelMarkerBase;
|
||||
private double originX;
|
||||
private double originZ;
|
||||
|
||||
private void clear() {
|
||||
markerCount = 0;
|
||||
sparkMarkerBase = 0;
|
||||
labelMarkerBase = 0;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
markerBuffer = -1;
|
||||
markerTexture = -1;
|
||||
clear();
|
||||
originX = 0.0D;
|
||||
originZ = 0.0D;
|
||||
}
|
||||
}
|
||||
}
|
||||
+209
-114
@@ -13,7 +13,6 @@ import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@@ -23,20 +22,31 @@ import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
final class WorldPerfObjectRenderer {
|
||||
private static final double MAX_DISTANCE_SQUARED =
|
||||
WorldOverlayMath.OBJECT_FADE_END * WorldOverlayMath.OBJECT_FADE_END;
|
||||
private static final double FLAME_FRUSTUM_MARGIN = 1.2D;
|
||||
private static final double FLAME_FRUSTUM_HEIGHT = 4.0D;
|
||||
private static final long STATIC_REFRESH_TICKS = 20L;
|
||||
private static final WorldPerfObjectBuffers.Batch[] DRAW_ORDER = {
|
||||
WorldPerfObjectBuffers.Batch.SMOKE,
|
||||
WorldPerfObjectBuffers.Batch.NORMAL,
|
||||
WorldPerfObjectBuffers.Batch.GLOW,
|
||||
WorldPerfObjectBuffers.Batch.SPARK,
|
||||
WorldPerfObjectBuffers.Batch.LABEL
|
||||
};
|
||||
static final float LABEL_PIXELS = 12.0F;
|
||||
|
||||
private final WorldPerfObjectBuffers buffers = new WorldPerfObjectBuffers();
|
||||
private List<Candidate> candidates = List.of();
|
||||
private final BlockPos.MutableBlockPos lookupPos = new BlockPos.MutableBlockPos();
|
||||
private final Matrix4f staticModelView = new Matrix4f();
|
||||
private final Matrix4f dynamicModelView = new Matrix4f();
|
||||
private List<Candidate> staticCandidates = List.of();
|
||||
private List<Candidate> dynamicCandidates = List.of();
|
||||
private long reportRevision = Long.MIN_VALUE;
|
||||
private long staticRefreshTick = Long.MIN_VALUE;
|
||||
private long dynamicRefreshTick = Long.MIN_VALUE;
|
||||
private String dimension;
|
||||
private ClientConfig.WorldOverlayFilter filter;
|
||||
private int markerLimit;
|
||||
|
||||
void render(RenderLevelStageEvent event,
|
||||
ClientLevel level,
|
||||
@@ -47,132 +57,166 @@ final class WorldPerfObjectRenderer {
|
||||
return;
|
||||
}
|
||||
String currentDimension = level.dimension().location().toString();
|
||||
refreshCandidates(snapshot, currentDimension);
|
||||
List<WorldPerfObjectBuffers.Entry> flames = resolve(event, level, snapshot.tickCount());
|
||||
if (flames.isEmpty()) {
|
||||
buffers.clear();
|
||||
boolean sourceChanged = refreshCandidates(snapshot, currentDimension);
|
||||
long clientTick = level.getGameTime();
|
||||
Vec3 camera = event.getCamera().getPosition();
|
||||
refreshStaticIfNeeded(level, camera, clientTick, sourceChanged);
|
||||
refreshDynamicIfNeeded(event, level, snapshot.tickCount(), camera,
|
||||
clientTick, sourceChanged);
|
||||
if (buffers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
buffers.replace(flames);
|
||||
draw(event, shader);
|
||||
}
|
||||
|
||||
private void refreshCandidates(ClientPerfWorldState.Snapshot snapshot, String currentDimension) {
|
||||
private boolean refreshCandidates(
|
||||
ClientPerfWorldState.Snapshot snapshot, String currentDimension) {
|
||||
ClientConfig.WorldOverlayFilter currentFilter = ClientConfig.worldOverlayFilter();
|
||||
int currentMarkerLimit = ClientConfig.worldOverlayMarkerLimit();
|
||||
if (snapshot.clientRevision() == reportRevision && currentDimension.equals(dimension)
|
||||
&& currentFilter == filter) {
|
||||
return;
|
||||
&& currentFilter == filter && currentMarkerLimit == markerLimit) {
|
||||
return false;
|
||||
}
|
||||
reportRevision = snapshot.clientRevision();
|
||||
dimension = currentDimension;
|
||||
filter = currentFilter;
|
||||
List<Candidate> next = new ArrayList<>();
|
||||
markerLimit = currentMarkerLimit;
|
||||
splitCandidates(snapshot, currentDimension, currentFilter, currentMarkerLimit);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void splitCandidates(ClientPerfWorldState.Snapshot snapshot,
|
||||
String currentDimension,
|
||||
ClientConfig.WorldOverlayFilter currentFilter,
|
||||
int currentMarkerLimit) {
|
||||
if (!currentDimension.equals(snapshot.dimension())) {
|
||||
candidates = List.of();
|
||||
staticCandidates = List.of();
|
||||
dynamicCandidates = List.of();
|
||||
return;
|
||||
}
|
||||
int initialCapacity = Math.min(snapshot.hotspots().size(), currentMarkerLimit);
|
||||
List<Candidate> nextStatic = new ArrayList<>(initialCapacity);
|
||||
List<Candidate> nextDynamic = new ArrayList<>();
|
||||
int selected = 0;
|
||||
for (ClientPerfWorldState.Hotspot hotspot : snapshot.hotspots()) {
|
||||
double flameStrength = ObjectPerformanceScale.averageSamples(
|
||||
hotspot.metric(), hotspot.measurementTicks());
|
||||
if (WorldOverlayMath.shouldRender(flameStrength, currentFilter)) {
|
||||
next.add(new Candidate(hotspot, hotspot.metric(), flameStrength));
|
||||
if (next.size() == WorldPerfObjectBuffers.MAX_MARKER_COUNT) {
|
||||
break;
|
||||
}
|
||||
if (!WorldOverlayMath.shouldRender(flameStrength, currentFilter)) {
|
||||
continue;
|
||||
}
|
||||
Candidate candidate = new Candidate(hotspot, hotspot.metric(), flameStrength);
|
||||
if (hotspot.key().kind() == PerfWorldHotspotIndex.Kind.ENTITY) {
|
||||
nextDynamic.add(candidate);
|
||||
} else {
|
||||
nextStatic.add(candidate);
|
||||
}
|
||||
selected++;
|
||||
if (selected == currentMarkerLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
candidates = List.copyOf(next);
|
||||
staticCandidates = nextStatic;
|
||||
dynamicCandidates = nextDynamic;
|
||||
}
|
||||
|
||||
private List<WorldPerfObjectBuffers.Entry> resolve(RenderLevelStageEvent event,
|
||||
ClientLevel level,
|
||||
long tickCount) {
|
||||
Vec3 camera = event.getCamera().getPosition();
|
||||
private void refreshStaticIfNeeded(ClientLevel level,
|
||||
Vec3 camera,
|
||||
long clientTick,
|
||||
boolean sourceChanged) {
|
||||
if (!sourceChanged && (staticCandidates.isEmpty()
|
||||
|| !staticRefreshDue(staticRefreshTick, clientTick))) {
|
||||
return;
|
||||
}
|
||||
buffers.replace(WorldPerfObjectBuffers.Source.STATIC,
|
||||
resolveBlockEntities(level), camera);
|
||||
staticRefreshTick = clientTick;
|
||||
}
|
||||
|
||||
private void refreshDynamicIfNeeded(RenderLevelStageEvent event,
|
||||
ClientLevel level,
|
||||
long reportTickCount,
|
||||
Vec3 camera,
|
||||
long clientTick,
|
||||
boolean sourceChanged) {
|
||||
if (!sourceChanged && (dynamicCandidates.isEmpty()
|
||||
|| !dynamicRefreshDue(dynamicRefreshTick, clientTick))) {
|
||||
return;
|
||||
}
|
||||
buffers.replace(WorldPerfObjectBuffers.Source.DYNAMIC,
|
||||
resolveEntities(event, level, reportTickCount), camera);
|
||||
dynamicRefreshTick = clientTick;
|
||||
}
|
||||
|
||||
private List<WorldEntityFlameClusters.Marker> resolveBlockEntities(ClientLevel level) {
|
||||
List<WorldEntityFlameClusters.Marker> markers =
|
||||
new ArrayList<>(staticCandidates.size());
|
||||
for (Candidate candidate : staticCandidates) {
|
||||
long packedPosition = candidate.hotspot().key().blockPosition();
|
||||
lookupPos.set(BlockPos.getX(packedPosition), BlockPos.getY(packedPosition),
|
||||
BlockPos.getZ(packedPosition));
|
||||
if (!level.hasChunkAt(lookupPos)) {
|
||||
continue;
|
||||
}
|
||||
BlockEntity blockEntity = level.getBlockEntity(lookupPos);
|
||||
if (blockEntity != null && !blockEntity.isRemoved()
|
||||
&& matchesBlockEntity(candidate.hotspot(), blockEntity)) {
|
||||
markers.add(blockEntityMarker(candidate, packedPosition));
|
||||
}
|
||||
}
|
||||
return markers;
|
||||
}
|
||||
|
||||
private List<WorldEntityFlameClusters.Marker> resolveEntities(
|
||||
RenderLevelStageEvent event, ClientLevel level, long tickCount) {
|
||||
List<WorldEntityFlameClusters.Input> itemEntities = new ArrayList<>();
|
||||
List<WorldEntityFlameClusters.Marker> markers = new ArrayList<>();
|
||||
for (Candidate candidate : candidates) {
|
||||
AABB bounds = candidate.hotspot().key().kind() == PerfWorldHotspotIndex.Kind.ENTITY
|
||||
? entityBounds(event, level, candidate.hotspot())
|
||||
: blockEntityBounds(level, candidate.hotspot());
|
||||
if (bounds == null) {
|
||||
List<WorldEntityFlameClusters.Marker> markers =
|
||||
new ArrayList<>(dynamicCandidates.size());
|
||||
for (Candidate candidate : dynamicCandidates) {
|
||||
Entity entity = level.getEntity(candidate.hotspot().key().entityId());
|
||||
if (!isVisibleEntity(event, candidate.hotspot(), entity)) {
|
||||
continue;
|
||||
}
|
||||
double distanceSquared = bounds.distanceToSqr(camera);
|
||||
if (distanceSquared > MAX_DISTANCE_SQUARED
|
||||
|| !event.getFrustum().isVisible(flameBounds(bounds))) {
|
||||
continue;
|
||||
}
|
||||
if (candidate.hotspot().key().kind() == PerfWorldHotspotIndex.Kind.ENTITY) {
|
||||
var input = new WorldEntityFlameClusters.Input(
|
||||
bounds, candidate.metric(), candidate.hotspot().measurementTicks(),
|
||||
candidate.hotspot().key().entityId());
|
||||
if (shouldClusterEntity(candidate.hotspot().detailKind())) {
|
||||
itemEntities.add(input);
|
||||
} else {
|
||||
markers.add(WorldEntityFlameClusters.single(input, tickCount));
|
||||
}
|
||||
AABB bounds = entity.getBoundingBox();
|
||||
var input = new WorldEntityFlameClusters.Input(
|
||||
bounds, candidate.metric(), candidate.hotspot().measurementTicks(),
|
||||
candidate.hotspot().key().entityId(), entity.xo - entity.getX(),
|
||||
entity.yo - entity.getY(), entity.zo - entity.getZ());
|
||||
if (shouldClusterEntity(candidate.hotspot().detailKind())) {
|
||||
itemEntities.add(input);
|
||||
} else {
|
||||
markers.add(blockEntityMarker(candidate, bounds));
|
||||
markers.add(WorldEntityFlameClusters.single(input, tickCount));
|
||||
}
|
||||
}
|
||||
markers.addAll(WorldEntityFlameClusters.cluster(itemEntities, tickCount));
|
||||
List<WorldPerfObjectBuffers.Entry> result = markers.stream()
|
||||
.map(marker -> WorldPerfObjectBuffers.Entry.from(
|
||||
marker, camera, distanceSquared(marker, camera)))
|
||||
.sorted(Comparator.comparingDouble(
|
||||
WorldPerfObjectBuffers.Entry::distanceSquared).reversed())
|
||||
.toList();
|
||||
return result;
|
||||
return markers;
|
||||
}
|
||||
|
||||
private static AABB flameBounds(AABB bounds) {
|
||||
return new AABB(bounds.minX - FLAME_FRUSTUM_MARGIN, bounds.minY,
|
||||
bounds.minZ - FLAME_FRUSTUM_MARGIN, bounds.maxX + FLAME_FRUSTUM_MARGIN,
|
||||
bounds.maxY + FLAME_FRUSTUM_HEIGHT, bounds.maxZ + FLAME_FRUSTUM_MARGIN);
|
||||
private static boolean isVisibleEntity(RenderLevelStageEvent event,
|
||||
ClientPerfWorldState.Hotspot hotspot,
|
||||
Entity entity) {
|
||||
return entity != null && !entity.isRemoved() && matchesEntity(hotspot, entity)
|
||||
&& (event.getCamera().isDetached() || entity != event.getCamera().getEntity());
|
||||
}
|
||||
|
||||
private static WorldEntityFlameClusters.Marker blockEntityMarker(
|
||||
Candidate candidate, AABB bounds) {
|
||||
int phase = PackedPositionHash.hash(
|
||||
candidate.hotspot().key().blockPosition()) & 0x7FFF;
|
||||
Candidate candidate, long packedPosition) {
|
||||
int phase = PackedPositionHash.hash(packedPosition) & 0x7FFF;
|
||||
var anchor = new WorldEntityFlameClusters.Anchor(
|
||||
(bounds.minX + bounds.maxX) * 0.5D, bounds.maxY,
|
||||
(bounds.minZ + bounds.maxZ) * 0.5D);
|
||||
BlockPos.getX(packedPosition) + 0.5D,
|
||||
BlockPos.getY(packedPosition) + 1.0D,
|
||||
BlockPos.getZ(packedPosition) + 0.5D);
|
||||
var visual = new WorldEntityFlameClusters.Visual(
|
||||
candidate.flameStrength(), phase, 1);
|
||||
return new WorldEntityFlameClusters.Marker(anchor, candidate.metric(), visual);
|
||||
}
|
||||
|
||||
private static double distanceSquared(WorldEntityFlameClusters.Marker marker, Vec3 camera) {
|
||||
double dx = marker.x() - camera.x;
|
||||
double dy = marker.y() - camera.y;
|
||||
double dz = marker.z() - camera.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
static boolean staticRefreshDue(long previousTick, long currentTick) {
|
||||
return previousTick == Long.MIN_VALUE || currentTick < previousTick
|
||||
|| currentTick - previousTick >= STATIC_REFRESH_TICKS;
|
||||
}
|
||||
|
||||
private static AABB entityBounds(RenderLevelStageEvent event,
|
||||
ClientLevel level,
|
||||
ClientPerfWorldState.Hotspot hotspot) {
|
||||
Entity entity = level.getEntity(hotspot.key().entityId());
|
||||
if (entity == null || entity.isRemoved() || !matchesEntity(hotspot, entity)
|
||||
|| (!event.getCamera().isDetached() && entity == event.getCamera().getEntity())) {
|
||||
return null;
|
||||
}
|
||||
float partialTick = event.getPartialTick().getGameTimeDeltaPartialTick(false);
|
||||
double x = Mth.lerp(partialTick, entity.xo, entity.getX());
|
||||
double y = Mth.lerp(partialTick, entity.yo, entity.getY());
|
||||
double z = Mth.lerp(partialTick, entity.zo, entity.getZ());
|
||||
return entity.getBoundingBox().move(x - entity.getX(), y - entity.getY(), z - entity.getZ());
|
||||
}
|
||||
|
||||
private static AABB blockEntityBounds(ClientLevel level, ClientPerfWorldState.Hotspot hotspot) {
|
||||
BlockPos pos = BlockPos.of(hotspot.key().blockPosition());
|
||||
if (!level.hasChunkAt(pos)) {
|
||||
return null;
|
||||
}
|
||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||
return blockEntity != null && !blockEntity.isRemoved() && matchesBlockEntity(hotspot, blockEntity)
|
||||
? new AABB(pos) : null;
|
||||
static boolean dynamicRefreshDue(long previousTick, long currentTick) {
|
||||
return previousTick != currentTick;
|
||||
}
|
||||
|
||||
static boolean shouldClusterEntity(TickDetailKind detailKind) {
|
||||
@@ -204,9 +248,29 @@ final class WorldPerfObjectRenderer {
|
||||
int previousNoiseTexture = RenderSystem.getShaderTexture(1);
|
||||
RenderSystem.setShaderTexture(0, WorldGlyphAtlas.textureId());
|
||||
RenderSystem.setShaderTexture(1, WorldFlameNoiseTexture.textureId());
|
||||
setFrameUniforms(event, shader, minecraft);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.disableDepthTest();
|
||||
RenderSystem.depthMask(false);
|
||||
RenderSystem.enableCull();
|
||||
try {
|
||||
drawBuffers(event, shader);
|
||||
} finally {
|
||||
RenderSystem.depthMask(true);
|
||||
RenderSystem.enableDepthTest();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
RenderSystem.disableBlend();
|
||||
RenderSystem.setShaderTexture(0, previousGlyphTexture);
|
||||
RenderSystem.setShaderTexture(1, previousNoiseTexture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFrameUniforms(
|
||||
RenderLevelStageEvent event, ShaderInstance shader, Minecraft minecraft) {
|
||||
shader.safeGetUniform("ScreenSize").set(
|
||||
(float) minecraft.getWindow().getWidth(), (float) minecraft.getWindow().getHeight());
|
||||
shader.safeGetUniform("LabelPixels").set(LABEL_PIXELS * (float) minecraft.getWindow().getGuiScale());
|
||||
shader.safeGetUniform("LabelPixels").set(
|
||||
LABEL_PIXELS * (float) minecraft.getWindow().getGuiScale());
|
||||
shader.safeGetUniform("FadeDistances").set(
|
||||
(float) WorldOverlayMath.OBJECT_FADE_START,
|
||||
(float) WorldOverlayMath.OBJECT_FADE_END);
|
||||
@@ -216,6 +280,13 @@ final class WorldPerfObjectRenderer {
|
||||
shader.safeGetUniform("LabelFadeDistances").set(
|
||||
(float) WorldOverlayMath.LABEL_FADE_START,
|
||||
(float) WorldOverlayMath.LABEL_FADE_END);
|
||||
shader.safeGetUniform("PartialTick").set(
|
||||
event.getPartialTick().getGameTimeDeltaPartialTick(false));
|
||||
setPackedDataUniforms(shader);
|
||||
setAnimationUniforms(event, shader, minecraft);
|
||||
}
|
||||
|
||||
private static void setPackedDataUniforms(ShaderInstance shader) {
|
||||
shader.safeGetUniform("OverflowValue").set(WorldOverlayMath.OVERFLOW_MICROS);
|
||||
shader.safeGetUniform("LabelWordBase").set(WorldOverlayMath.LABEL_WORD_BASE);
|
||||
shader.safeGetUniform("GlyphCount").set((float) WorldGlyphAtlas.GLYPH_COUNT);
|
||||
@@ -225,35 +296,55 @@ final class WorldPerfObjectRenderer {
|
||||
(float) WorldOverlayMath.MAX_PACKED_FLAME_STRENGTH);
|
||||
shader.safeGetUniform("MaxFlameViewportFraction").set(
|
||||
(float) WorldOverlayMath.MAX_FLAME_VIEWPORT_FRACTION);
|
||||
}
|
||||
|
||||
private static void setAnimationUniforms(
|
||||
RenderLevelStageEvent event, ShaderInstance shader, Minecraft minecraft) {
|
||||
long gameTime = minecraft.level == null ? 0L : minecraft.level.getGameTime();
|
||||
WorldFlameAnimationClock.Snapshot clock = WorldFlameAnimationClock.sample(
|
||||
gameTime, event.getPartialTick().getGameTimeDeltaPartialTick(false));
|
||||
shader.safeGetUniform("AnimationTime").set(clock.noiseSeconds());
|
||||
shader.safeGetUniform("ParticleTime").set(clock.particleSeconds());
|
||||
shader.safeGetUniform("ParticleEpoch").set(clock.particleEpoch());
|
||||
Matrix4f modelView = viewRotationMatrix(event);
|
||||
var drawContext = new WorldPerfObjectBuffers.DrawContext(
|
||||
shader, modelView, event.getProjectionMatrix());
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.disableDepthTest();
|
||||
RenderSystem.depthMask(false);
|
||||
RenderSystem.enableCull();
|
||||
try {
|
||||
buffers.draw(WorldPerfObjectBuffers.Batch.SMOKE, drawContext);
|
||||
buffers.draw(WorldPerfObjectBuffers.Batch.NORMAL, drawContext);
|
||||
buffers.draw(WorldPerfObjectBuffers.Batch.GLOW, drawContext);
|
||||
buffers.draw(WorldPerfObjectBuffers.Batch.SPARK, drawContext);
|
||||
buffers.draw(WorldPerfObjectBuffers.Batch.LABEL, drawContext);
|
||||
} finally {
|
||||
RenderSystem.depthMask(true);
|
||||
RenderSystem.enableDepthTest();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
RenderSystem.disableBlend();
|
||||
RenderSystem.setShaderTexture(0, previousGlyphTexture);
|
||||
RenderSystem.setShaderTexture(1, previousNoiseTexture);
|
||||
}
|
||||
|
||||
private void drawBuffers(RenderLevelStageEvent event, ShaderInstance shader) {
|
||||
WorldPerfObjectBuffers.DrawContext staticContext = drawContext(
|
||||
event, shader, WorldPerfObjectBuffers.Source.STATIC);
|
||||
WorldPerfObjectBuffers.DrawContext dynamicContext = drawContext(
|
||||
event, shader, WorldPerfObjectBuffers.Source.DYNAMIC);
|
||||
for (WorldPerfObjectBuffers.Batch batch : DRAW_ORDER) {
|
||||
if (staticContext != null) {
|
||||
buffers.draw(WorldPerfObjectBuffers.Source.STATIC, batch, staticContext);
|
||||
}
|
||||
if (dynamicContext != null) {
|
||||
buffers.draw(WorldPerfObjectBuffers.Source.DYNAMIC, batch, dynamicContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WorldPerfObjectBuffers.DrawContext drawContext(
|
||||
RenderLevelStageEvent event,
|
||||
ShaderInstance shader,
|
||||
WorldPerfObjectBuffers.Source source) {
|
||||
if (buffers.isEmpty(source)) {
|
||||
return null;
|
||||
}
|
||||
return new WorldPerfObjectBuffers.DrawContext(
|
||||
shader, sourceModelView(event, source), event.getProjectionMatrix());
|
||||
}
|
||||
|
||||
private Matrix4f sourceModelView(
|
||||
RenderLevelStageEvent event, WorldPerfObjectBuffers.Source source) {
|
||||
Vec3 camera = event.getCamera().getPosition();
|
||||
Matrix4f target = source == WorldPerfObjectBuffers.Source.STATIC
|
||||
? staticModelView : dynamicModelView;
|
||||
return target.set(event.getModelViewMatrix()).translate(
|
||||
(float) (buffers.originX(source) - camera.x),
|
||||
(float) -camera.y,
|
||||
(float) (buffers.originZ(source) - camera.z));
|
||||
}
|
||||
|
||||
static Matrix4f cameraRelativeMatrix(
|
||||
RenderLevelStageEvent event, double originX, double originZ) {
|
||||
Vec3 camera = event.getCamera().getPosition();
|
||||
@@ -269,10 +360,14 @@ final class WorldPerfObjectRenderer {
|
||||
|
||||
void close() {
|
||||
buffers.close();
|
||||
candidates = List.of();
|
||||
staticCandidates = List.of();
|
||||
dynamicCandidates = List.of();
|
||||
reportRevision = Long.MIN_VALUE;
|
||||
staticRefreshTick = Long.MIN_VALUE;
|
||||
dynamicRefreshTick = Long.MIN_VALUE;
|
||||
dimension = null;
|
||||
filter = null;
|
||||
markerLimit = 0;
|
||||
}
|
||||
|
||||
private record Candidate(ClientPerfWorldState.Hotspot hotspot,
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
{"name": "ParticlePhaseBase", "type": "int", "count": 1, "values": [512]},
|
||||
{"name": "MaxFlameStrength", "type": "float", "count": 1, "values": [31.9990234375]},
|
||||
{"name": "MaxFlameViewportFraction", "type": "float", "count": 1, "values": [0.18]},
|
||||
{"name": "PartialTick", "type": "float", "count": 1, "values": [0]},
|
||||
{"name": "AnimationTime", "type": "float", "count": 1, "values": [0]},
|
||||
{"name": "ParticleTime", "type": "float", "count": 1, "values": [0]},
|
||||
{"name": "ParticleEpoch", "type": "int", "count": 1, "values": [0]},
|
||||
{"name": "RenderPass", "type": "int", "count": 1, "values": [0]},
|
||||
{"name": "ParticleStart", "type": "int", "count": 1, "values": [0]},
|
||||
{"name": "SlotsPerMarker", "type": "int", "count": 1, "values": [1]}
|
||||
{"name": "SlotsPerMarker", "type": "int", "count": 1, "values": [1]},
|
||||
{"name": "MarkerBase", "type": "int", "count": 1, "values": [0]}
|
||||
]
|
||||
}
|
||||
@@ -14,11 +14,13 @@ uniform float StrengthWordScale;
|
||||
uniform int ParticlePhaseBase;
|
||||
uniform float MaxFlameStrength;
|
||||
uniform float MaxFlameViewportFraction;
|
||||
uniform float PartialTick;
|
||||
uniform float ParticleTime;
|
||||
uniform int ParticleEpoch;
|
||||
uniform int RenderPass;
|
||||
uniform int ParticleStart;
|
||||
uniform int SlotsPerMarker;
|
||||
uniform int MarkerBase;
|
||||
const int RED_PARTICLE_COUNT = 24;
|
||||
const int ORANGE_PARTICLE_COUNT = 20;
|
||||
const int YELLOW_PARTICLE_COUNT = 20;
|
||||
@@ -31,7 +33,7 @@ const int SPARK_PARTICLE_START = GLOW_PARTICLE_START + GLOW_PARTICLE_COUNT;
|
||||
const int SPARK_LAYER = 4;
|
||||
const int LABEL_PASS = 3;
|
||||
const int SPARK_PASS = 4;
|
||||
const int MARKER_TEXELS = 3;
|
||||
const int MARKER_TEXELS = 4;
|
||||
// Flame shape tuning. Higher severity widens the full tapered envelope.
|
||||
const float GREEN_BASE_WIDTH = 1.5;
|
||||
const float GREEN_TIP_WIDTH = 0.8;
|
||||
@@ -39,18 +41,22 @@ const float PURPLE_BASE_WIDTH = 2.4;
|
||||
const float PURPLE_TIP_WIDTH = 1.0;
|
||||
const float WIDTH_TAPER_START = 0.0;
|
||||
const float WIDTH_TAPER_END = 0.88;
|
||||
// Distance LOD tuning. Sparks disappear before the core particle LOD starts.
|
||||
const float PARTICLE_LOD_START = 24.0;
|
||||
const float PARTICLE_LOD_END = 96.0;
|
||||
const float FIRE_LOD_MIN_SCALE = 0.28;
|
||||
const float GLOW_LOD_MIN_SCALE = 0.16;
|
||||
// Distance LOD tuning. Particle density drops after 16 blocks and settles at 32.
|
||||
const float PARTICLE_LOD_START = 16.0;
|
||||
const float PARTICLE_LOD_END = 32.0;
|
||||
const float SPARK_FADE_START = 20.0;
|
||||
const float SPARK_FADE_END = 24.0;
|
||||
const float SMOKE_FADE_START = 24.0;
|
||||
const float SMOKE_FADE_END = 32.0;
|
||||
const float MIN_RED_COUNT = 16.0;
|
||||
const float MIN_ORANGE_COUNT = 14.0;
|
||||
const float MIN_YELLOW_COUNT = 14.0;
|
||||
const float MIN_GLOW_COUNT = 10.0;
|
||||
const float MIN_SPARK_COUNT = 10.0;
|
||||
const float FAR_RED_COUNT = 4.0;
|
||||
const float FAR_ORANGE_COUNT = 3.0;
|
||||
const float FAR_YELLOW_COUNT = 3.0;
|
||||
const float FAR_GLOW_COUNT = 1.0;
|
||||
const vec2 SPARK_COUNT_RAMP = vec2(3.50, 5.0);
|
||||
// Spark motion tuning. Sparks deliberately do not use the flame width envelope.
|
||||
const vec2 SPARK_ASPECT = vec2(1.0, 1.0);
|
||||
@@ -95,6 +101,7 @@ flat out float particleOpacity;
|
||||
flat out float particleLife;
|
||||
struct FlameMarker {
|
||||
vec3 position;
|
||||
vec3 previousPosition;
|
||||
float packedStrength;
|
||||
vec3 color;
|
||||
int phaseWord;
|
||||
@@ -107,7 +114,8 @@ FlameMarker markerAt(int markerIndex) {
|
||||
vec4 positionStrength = texelFetch(MarkerData, base);
|
||||
vec4 colorPhase = texelFetch(MarkerData, base + 1);
|
||||
vec4 label = texelFetch(MarkerData, base + 2);
|
||||
return FlameMarker(positionStrength.xyz, positionStrength.w,
|
||||
vec4 previousPosition = texelFetch(MarkerData, base + 3);
|
||||
return FlameMarker(positionStrength.xyz, previousPosition.xyz, positionStrength.w,
|
||||
colorPhase.rgb, int(colorPhase.w), int(label.x), int(label.y), label.z);
|
||||
}
|
||||
int decimalDigits(int value) {
|
||||
@@ -185,19 +193,27 @@ float minimumParticles(int layer) {
|
||||
if (layer == 3) return MIN_GLOW_COUNT;
|
||||
return MIN_SPARK_COUNT;
|
||||
}
|
||||
float farParticles(int layer) {
|
||||
if (layer == 0) return FAR_RED_COUNT;
|
||||
if (layer == 1) return FAR_ORANGE_COUNT;
|
||||
if (layer == 2) return FAR_YELLOW_COUNT;
|
||||
return FAR_GLOW_COUNT;
|
||||
}
|
||||
int visibleParticles(int layer, float strength, float distanceToCamera) {
|
||||
float countAmount = layer == SPARK_LAYER ? smoothRange(
|
||||
SPARK_COUNT_RAMP.x, SPARK_COUNT_RAMP.y, strength)
|
||||
: severity(strength);
|
||||
float nearCount = mix(minimumParticles(layer), float(layerCapacity(layer)), countAmount);
|
||||
if (layer == SPARK_LAYER) {
|
||||
return distanceToCamera >= SPARK_FADE_END
|
||||
? 0 : int(floor(nearCount + 0.5));
|
||||
float sparkLod = smoothRange(PARTICLE_LOD_START, SPARK_FADE_END,
|
||||
distanceToCamera);
|
||||
float sparkRemaining = 1.0 - sparkLod;
|
||||
return int(floor(nearCount * sparkRemaining * sparkRemaining + 0.5));
|
||||
}
|
||||
float lodAmount = smoothRange(PARTICLE_LOD_START, PARTICLE_LOD_END,
|
||||
distanceToCamera);
|
||||
float minimumScale = layer == 3 ? GLOW_LOD_MIN_SCALE : FIRE_LOD_MIN_SCALE;
|
||||
return max(1, int(floor(nearCount * mix(1.0, minimumScale, lodAmount) + 0.5)));
|
||||
float fastLod = 1.0 - (1.0 - lodAmount) * (1.0 - lodAmount);
|
||||
return int(floor(mix(nearCount, farParticles(layer), fastLod) + 0.5));
|
||||
}
|
||||
bool selectedSlot(int slot, int count, int capacity) {
|
||||
int previous = int(floor(float(slot * count) / float(capacity)));
|
||||
@@ -325,13 +341,17 @@ void renderParticleVertex(vec4 viewPosition, float distanceToCamera, int particl
|
||||
int expectedPass = particleLayer == 3 ? 1 : (spark ? SPARK_PASS : 0);
|
||||
int slot = particleIndex - layerStart(particleLayer);
|
||||
int count = visibleParticles(particleLayer, flameStrength, distanceToCamera);
|
||||
if ((smokePass && (particleLayer != 1 || distanceToCamera >= SMOKE_FADE_END))
|
||||
|| (!smokePass && RenderPass != expectedPass)
|
||||
|| !selectedParticleSlot(particleLayer, slot, count)) {
|
||||
hidePrimitive();
|
||||
return;
|
||||
}
|
||||
float elapsed;
|
||||
int generation;
|
||||
bool alive = particleState(particleLayer, slot, phaseWord,
|
||||
particleLife, elapsed, generation);
|
||||
if ((smokePass && particleLayer != 1)
|
||||
|| (!smokePass && RenderPass != expectedPass)
|
||||
|| !selectedParticleSlot(particleLayer, slot, count) || !alive) {
|
||||
if (!alive) {
|
||||
hidePrimitive();
|
||||
return;
|
||||
}
|
||||
@@ -378,15 +398,20 @@ void renderParticleVertex(vec4 viewPosition, float distanceToCamera, int particl
|
||||
viewPosition.xy += (offset + quadOffset) * viewportScale;
|
||||
particleOpacity = spark
|
||||
? 1.0 - smoothRange(SPARK_FADE_START, SPARK_FADE_END, distanceToCamera)
|
||||
: 1.0;
|
||||
: (smokePass
|
||||
? 1.0 - smoothRange(SMOKE_FADE_START, SMOKE_FADE_END, distanceToCamera)
|
||||
: 1.0);
|
||||
gl_Position = ProjMat * viewPosition;
|
||||
}
|
||||
void main() {
|
||||
bool label = RenderPass == LABEL_PASS;
|
||||
int markerIndex = label ? gl_InstanceID : gl_InstanceID / SlotsPerMarker;
|
||||
int slot = label ? 0 : gl_InstanceID - markerIndex * SlotsPerMarker;
|
||||
int batchMarkerIndex = label ? gl_InstanceID : gl_InstanceID / SlotsPerMarker;
|
||||
int markerIndex = MarkerBase + batchMarkerIndex;
|
||||
int slot = label ? 0 : gl_InstanceID - batchMarkerIndex * SlotsPerMarker;
|
||||
FlameMarker marker = markerAt(markerIndex);
|
||||
vec4 viewPosition = ModelViewMat * vec4(marker.position, 1.0);
|
||||
vec3 markerPosition = mix(marker.previousPosition, marker.position,
|
||||
clamp(PartialTick, 0.0, 1.0));
|
||||
vec4 viewPosition = ModelViewMat * vec4(markerPosition, 1.0);
|
||||
float distanceToCamera = length(viewPosition.xyz);
|
||||
localCoord = label ? vec2(Position.x, 1.0 - Position.y) : Position.xy;
|
||||
vertexColor = vec4(label ? vec3(1.0) : marker.color,
|
||||
|
||||
@@ -92,6 +92,24 @@ class WorldEntityFlameClustersTest {
|
||||
assertEquals(2.0D, marker.flameStrength(), 0.000_001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
void combinesPreviousAndCurrentAnchorsForTickInterpolation() {
|
||||
var first = new WorldEntityFlameClusters.Input(
|
||||
bounds(new Point(0.0D, 0.0D)), metric(1.0D, 1.0D, 1L),
|
||||
1L, 1, -1.0D, 0.0D, 0.5D);
|
||||
var second = new WorldEntityFlameClusters.Input(
|
||||
bounds(new Point(0.5D, 0.0D)), metric(1.0D, 1.0D, 1L),
|
||||
1L, 2, -1.5D, -0.5D, 0.5D);
|
||||
|
||||
WorldEntityFlameClusters.Marker marker =
|
||||
WorldEntityFlameClusters.cluster(List.of(first, second), 1L).get(0);
|
||||
|
||||
assertEquals(0.25D, marker.x(), 0.000_001D);
|
||||
assertEquals(-1.0D, marker.previousX(), 0.000_001D);
|
||||
assertEquals(0.5D, marker.previousZ(), 0.000_001D);
|
||||
assertEquals(65.8D, marker.previousY(), 0.000_001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clustersTheMaximumCoincidentInputBudgetWithoutPairMaterialization() {
|
||||
List<WorldEntityFlameClusters.Input> inputs = new ArrayList<>();
|
||||
|
||||
+72
-32
@@ -1,11 +1,13 @@
|
||||
package com.moepus.serverwarashi.client.world;
|
||||
|
||||
import com.moepus.serverwarashi.common.modules.performance.report.PerfReportData;
|
||||
import com.moepus.serverwarashi.common.modules.performance.report.PerfReportLimits;
|
||||
import com.moepus.serverwarashi.common.modules.performance.sampling.TickDetailKind;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -22,8 +24,10 @@ class WorldPerfObjectRendererTest {
|
||||
|
||||
@Test
|
||||
void drawsGpuParticlesInFiveBatchesWithOrangeReusedForSmoke() {
|
||||
assertEquals(4_096, WorldPerfObjectBuffers.MAX_MARKER_COUNT);
|
||||
assertEquals(5, WorldPerfObjectBuffers.Batch.values().length);
|
||||
assertEquals(PerfReportLimits.MAX_WORLD_HOTSPOT_ENTRIES,
|
||||
WorldPerfObjectBuffers.MAX_MARKER_COUNT);
|
||||
assertEquals(4, WorldPerfObjectBuffers.TEXELS_PER_MARKER);
|
||||
assertEquals(2, WorldPerfObjectBuffers.Source.values().length);
|
||||
assertArrayEquals(new WorldPerfObjectBuffers.Batch[]{
|
||||
WorldPerfObjectBuffers.Batch.SMOKE,
|
||||
WorldPerfObjectBuffers.Batch.NORMAL,
|
||||
@@ -39,11 +43,6 @@ class WorldPerfObjectRendererTest {
|
||||
assertEquals(24, WorldPerfObjectBuffers.Batch.SMOKE.particleStart());
|
||||
assertEquals(64, WorldPerfObjectBuffers.Batch.GLOW.particleStart());
|
||||
assertEquals(80, WorldPerfObjectBuffers.Batch.SPARK.particleStart());
|
||||
assertEquals(0, WorldPerfObjectBuffers.Batch.NORMAL.renderPass());
|
||||
assertEquals(1, WorldPerfObjectBuffers.Batch.GLOW.renderPass());
|
||||
assertEquals(2, WorldPerfObjectBuffers.Batch.SMOKE.renderPass());
|
||||
assertEquals(3, WorldPerfObjectBuffers.Batch.LABEL.renderPass());
|
||||
assertEquals(4, WorldPerfObjectBuffers.Batch.SPARK.renderPass());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,46 +60,87 @@ class WorldPerfObjectRendererTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertsWorldAnchorsToCameraRelativeDoublesBeforeUpload() {
|
||||
var marker = new WorldEntityFlameClusters.Marker(
|
||||
new WorldEntityFlameClusters.Anchor(30_000_000.25D, 70.5D, -29_999_999.75D),
|
||||
new PerfReportData.Metric(1.0D, 1.0D, 1L),
|
||||
new WorldEntityFlameClusters.Visual(1.0D, 0, 1));
|
||||
WorldPerfObjectBuffers.Entry entry = WorldPerfObjectBuffers.Entry.from(
|
||||
marker, new Vec3(30_000_000.0D, 64.0D, -30_000_000.0D), 42.0D);
|
||||
void ordersMarkersByStableEightBlockBuckets() {
|
||||
List<WorldEntityFlameClusters.Marker> markers = List.of(
|
||||
markerAtDistance(1.0D, 1),
|
||||
markerAtDistance(20.0D, 2),
|
||||
markerAtDistance(9.0D, 3),
|
||||
markerAtDistance(90.0D, 4),
|
||||
markerAtDistance(23.0D, 5),
|
||||
markerAtDistance(24.0D, 6),
|
||||
markerAtDistance(40.0D, 7),
|
||||
markerAtDistance(96.0D, 8),
|
||||
markerAtDistance(97.0D, 9));
|
||||
|
||||
assertEquals(0.25D, entry.x());
|
||||
assertEquals(6.5D, entry.y());
|
||||
assertEquals(0.25D, entry.z());
|
||||
assertEquals(42.0D, entry.distanceSquared());
|
||||
WorldPerfMarkerBuckets.Batch batch = WorldPerfMarkerBuckets.order(
|
||||
markers, new Vec3(0.0D, 64.0D, 0.0D));
|
||||
|
||||
assertEquals(8, batch.size());
|
||||
assertEquals(4, batch.sparkBase());
|
||||
assertEquals(3, batch.labelBase());
|
||||
assertArrayEquals(new int[]{4, 8, 7, 6, 2, 5, 3, 1}, phases(batch));
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodesOneCompactMarkerRecordWithoutParticleVertices() {
|
||||
void keepsDistanceBoundariesStrictForLimitedBatches() {
|
||||
assertEquals(0, WorldPerfMarkerBuckets.bucketForDistanceSquared(0.0D));
|
||||
assertEquals(0, WorldPerfMarkerBuckets.bucketForDistanceSquared(63.99D));
|
||||
assertEquals(1, WorldPerfMarkerBuckets.bucketForDistanceSquared(64.0D));
|
||||
assertEquals(3, WorldPerfMarkerBuckets.bucketForDistanceSquared(24.0D * 24.0D));
|
||||
assertEquals(5, WorldPerfMarkerBuckets.bucketForDistanceSquared(40.0D * 40.0D));
|
||||
assertEquals(11, WorldPerfMarkerBuckets.bucketForDistanceSquared(96.0D * 96.0D));
|
||||
assertEquals(-1, WorldPerfMarkerBuckets.bucketForDistanceSquared(96.01D * 96.01D));
|
||||
assertEquals(-1, WorldPerfMarkerBuckets.bucketForDistanceSquared(Double.NaN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodesCurrentAndPreviousAnchorsRelativeToStableOrigin() {
|
||||
var marker = new WorldEntityFlameClusters.Marker(
|
||||
new WorldEntityFlameClusters.Anchor(9.25D, 70.0D, -5.25D),
|
||||
new WorldEntityFlameClusters.Anchor(10.25D, 70.5D, -4.75D),
|
||||
new PerfReportData.Metric(1.0D, 1.0D, 1L),
|
||||
new WorldEntityFlameClusters.Visual(1.0D, 0x7FFF, 1));
|
||||
WorldPerfObjectBuffers.Entry entry = WorldPerfObjectBuffers.Entry.from(
|
||||
marker, new Vec3(10.0D, 64.0D, -5.0D), 1.0D);
|
||||
FloatBuffer encoded = FloatBuffer.allocate(WorldPerfObjectBuffers.FLOATS_PER_MARKER);
|
||||
|
||||
WorldPerfObjectBuffers.encodeMarker(entry, encoded);
|
||||
WorldPerfObjectBuffers.encodeMarker(marker,
|
||||
new WorldPerfObjectBuffers.UploadOrigin(10.0D, -5.0D), encoded);
|
||||
encoded.flip();
|
||||
|
||||
assertEquals(12, encoded.remaining());
|
||||
assertEquals(16, encoded.remaining());
|
||||
assertEquals(0.25F, encoded.get(), 0.0F);
|
||||
assertEquals(6.54F, encoded.get(), 0.0001F);
|
||||
assertEquals(70.54F, encoded.get(), 0.0001F);
|
||||
assertEquals(0.25F, encoded.get(), 0.0F);
|
||||
assertEquals(1_024.0F, encoded.get(), 0.0F);
|
||||
assertEquals(0x40 / 255.0F, encoded.get(), 0.0001F);
|
||||
assertEquals(0xDC / 255.0F, encoded.get(), 0.0001F);
|
||||
assertEquals(0x78 / 255.0F, encoded.get(), 0.0001F);
|
||||
assertEquals(511.0F, encoded.get(), 0.0F);
|
||||
assertEquals(1_000.0F, encoded.get(), 0.0F);
|
||||
assertEquals(-1.0F, encoded.get(), 0.0F);
|
||||
assertEquals(WorldOverlayMath.packLabelStrength(1.0D) / 255.0F,
|
||||
encoded.get(), 0.0001F);
|
||||
encoded.position(12);
|
||||
assertEquals(-0.75F, encoded.get(), 0.0F);
|
||||
assertEquals(70.04F, encoded.get(), 0.0001F);
|
||||
assertEquals(-0.25F, encoded.get(), 0.0F);
|
||||
assertEquals(0.0F, encoded.get(), 0.0F);
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshesStaticAtOneSecondAndDynamicOncePerTick() {
|
||||
assertTrue(WorldPerfObjectRenderer.staticRefreshDue(Long.MIN_VALUE, 100L));
|
||||
assertFalse(WorldPerfObjectRenderer.staticRefreshDue(100L, 119L));
|
||||
assertTrue(WorldPerfObjectRenderer.staticRefreshDue(100L, 120L));
|
||||
assertTrue(WorldPerfObjectRenderer.staticRefreshDue(120L, 5L));
|
||||
assertFalse(WorldPerfObjectRenderer.dynamicRefreshDue(100L, 100L));
|
||||
assertTrue(WorldPerfObjectRenderer.dynamicRefreshDue(100L, 101L));
|
||||
}
|
||||
|
||||
private static int[] phases(WorldPerfMarkerBuckets.Batch batch) {
|
||||
int[] phases = new int[batch.size()];
|
||||
for (int index = 0; index < phases.length; index++) {
|
||||
phases[index] = batch.markers()[index].phase();
|
||||
}
|
||||
return phases;
|
||||
}
|
||||
|
||||
private static WorldEntityFlameClusters.Marker markerAtDistance(
|
||||
double distance, int phase) {
|
||||
return new WorldEntityFlameClusters.Marker(
|
||||
new WorldEntityFlameClusters.Anchor(distance, 64.0D, 0.0D),
|
||||
new PerfReportData.Metric(1.0D, 1.0D, 1L),
|
||||
new WorldEntityFlameClusters.Visual(1.0D, phase, 1));
|
||||
}
|
||||
}
|
||||
+36
-3
@@ -43,11 +43,13 @@ class WorldPerfShaderResourceTest {
|
||||
assertUniform(root, "MaxFlameStrength", WorldOverlayMath.MAX_PACKED_FLAME_STRENGTH);
|
||||
assertUniform(root, "MaxFlameViewportFraction",
|
||||
WorldOverlayMath.MAX_FLAME_VIEWPORT_FRACTION);
|
||||
assertUniform(root, "PartialTick", 0.0D);
|
||||
assertUniform(root, "ParticleTime", 0.0D);
|
||||
assertUniform(root, "ParticleEpoch", 0.0D);
|
||||
assertUniform(root, "RenderPass", 0.0D);
|
||||
assertUniform(root, "ParticleStart", 0.0D);
|
||||
assertUniform(root, "SlotsPerMarker", 1.0D);
|
||||
assertUniform(root, "MarkerBase", 0.0D);
|
||||
assertEquals("Position", root.getAsJsonArray("attributes").get(0).getAsString());
|
||||
assertEquals(1, root.getAsJsonArray("attributes").size());
|
||||
assertTrue(hasSampler(root, "MarkerData"));
|
||||
@@ -58,8 +60,14 @@ class WorldPerfShaderResourceTest {
|
||||
String vertex = resource("world_perf_object.vsh");
|
||||
assertTrue(vertex.contains("uniform samplerBuffer MarkerData;"));
|
||||
assertTrue(vertex.contains("texelFetch(MarkerData, base)"));
|
||||
assertTrue(vertex.contains("texelFetch(MarkerData, base + 3)"));
|
||||
assertEquals(4, shaderIntConstant(vertex, "MARKER_TEXELS"));
|
||||
assertTrue(vertex.contains("gl_InstanceID / SlotsPerMarker"));
|
||||
assertTrue(vertex.contains("MarkerBase + batchMarkerIndex"));
|
||||
assertTrue(vertex.contains("gl_InstanceID - batchMarkerIndex * SlotsPerMarker"));
|
||||
assertTrue(vertex.contains("ParticleStart + slot"));
|
||||
assertTrue(vertex.contains("mix(marker.previousPosition, marker.position"));
|
||||
assertTrue(vertex.contains("clamp(PartialTick, 0.0, 1.0)"));
|
||||
assertEquals(WorldFlameParticleLayout.RED_PARTICLE_COUNT,
|
||||
shaderIntConstant(vertex, "RED_PARTICLE_COUNT"));
|
||||
assertEquals(WorldFlameParticleLayout.ORANGE_PARTICLE_COUNT,
|
||||
@@ -80,14 +88,28 @@ class WorldPerfShaderResourceTest {
|
||||
void objectShaderAppliesLodEnvelopeAndCoherentPlumeSparks() throws Exception {
|
||||
String vertex = resource("world_perf_object.vsh");
|
||||
String fragment = resource("world_perf_object.fsh");
|
||||
assertTrue(vertex.contains("const float PARTICLE_LOD_START = 24.0;"));
|
||||
assertTrue(vertex.contains("const float SPARK_FADE_END = 24.0;"));
|
||||
assertTrue(vertex.contains("distanceToCamera >= SPARK_FADE_END"));
|
||||
assertEquals(16.0D, shaderFloatConstant(vertex, "PARTICLE_LOD_START"));
|
||||
assertEquals(32.0D, shaderFloatConstant(vertex, "PARTICLE_LOD_END"));
|
||||
assertEquals(WorldOverlayMath.SPARK_FADE_START,
|
||||
shaderFloatConstant(vertex, "SPARK_FADE_START"));
|
||||
assertEquals(WorldOverlayMath.SPARK_FADE_END,
|
||||
shaderFloatConstant(vertex, "SPARK_FADE_END"));
|
||||
assertEquals(24.0D, shaderFloatConstant(vertex, "SMOKE_FADE_START"));
|
||||
assertEquals(32.0D, shaderFloatConstant(vertex, "SMOKE_FADE_END"));
|
||||
assertTrue(vertex.contains("const float MIN_RED_COUNT = 16.0;"));
|
||||
assertTrue(vertex.contains("const float MIN_ORANGE_COUNT = 14.0;"));
|
||||
assertTrue(vertex.contains("const float MIN_YELLOW_COUNT = 14.0;"));
|
||||
assertTrue(vertex.contains("const float MIN_GLOW_COUNT = 10.0;"));
|
||||
assertTrue(vertex.contains("const float MIN_SPARK_COUNT = 10.0;"));
|
||||
assertEquals(4.0D, shaderFloatConstant(vertex, "FAR_RED_COUNT"));
|
||||
assertEquals(3.0D, shaderFloatConstant(vertex, "FAR_ORANGE_COUNT"));
|
||||
assertEquals(3.0D, shaderFloatConstant(vertex, "FAR_YELLOW_COUNT"));
|
||||
assertEquals(1.0D, shaderFloatConstant(vertex, "FAR_GLOW_COUNT"));
|
||||
assertTrue(vertex.contains("float fastLod = 1.0 - (1.0 - lodAmount)"));
|
||||
assertTrue(vertex.contains("nearCount * sparkRemaining * sparkRemaining"));
|
||||
assertTrue(vertex.contains("distanceToCamera >= SMOKE_FADE_END"));
|
||||
assertTrue(vertex.indexOf("!selectedParticleSlot")
|
||||
< vertex.indexOf("bool alive = particleState"));
|
||||
assertTrue(vertex.contains("const vec2 SPARK_COUNT_RAMP = vec2(3.50, 5.0);"));
|
||||
assertTrue(vertex.contains("const float GREEN_BASE_WIDTH = 1.5;"));
|
||||
assertTrue(vertex.contains("const float GREEN_TIP_WIDTH = 0.8;"));
|
||||
@@ -178,6 +200,17 @@ class WorldPerfShaderResourceTest {
|
||||
return Integer.parseInt(shader.substring(valueStart, valueEnd));
|
||||
}
|
||||
|
||||
private static double shaderFloatConstant(String shader, String name) {
|
||||
String prefix = "const float " + name + " = ";
|
||||
int valueStart = shader.indexOf(prefix);
|
||||
if (valueStart < 0) {
|
||||
throw new AssertionError("Missing shader constant " + name);
|
||||
}
|
||||
valueStart += prefix.length();
|
||||
int valueEnd = shader.indexOf(';', valueStart);
|
||||
return Double.parseDouble(shader.substring(valueStart, valueEnd));
|
||||
}
|
||||
|
||||
private static void assertCompleteDefaults(JsonObject root) {
|
||||
for (var uniform : root.getAsJsonArray("uniforms")) {
|
||||
JsonObject object = uniform.getAsJsonObject();
|
||||
|
||||
Reference in new issue
Block a user