Симуляция распределена во времени

This commit is contained in:
2026-08-08 17:15:14 +04:00
parent cfc9bccb89
commit 0faa8066af
2 changed files with 206 additions and 8 deletions
@@ -1,12 +1,33 @@
package su.divan2000.veila.client.render.fog; package su.divan2000.veila.client.render.fog;
import java.util.ArrayDeque;
import java.util.Locale;
public final class FogSimulationManager { public final class FogSimulationManager {
private static boolean initialized = false; private static boolean initialized = false;
private static long lastSimulationTime = 0; private static long lastSimulationTime = 0;
// Симулируем каждые 100 мс (10 раз в секунду) // Симулируем каждые 100 мс (10 раз в секунду)
private static final long SIMULATION_INTERVAL_NS = 100_000_000L; private static final long SIMULATION_INTERVAL_NS = 300_000_000L;
private static final long TARGET_STEP_DURATION_NS = 300_000_000L;
private static final long FRAME_TIME_WINDOW_NS = 100_000_000L;
private static final long STAT_WINDOW_NS = 5_000_000_000L;
private static final int MAX_LAYERS_PER_FRAME = 32;
private static final int MIN_LAYERS_PER_FRAME = 2;
private static final double LAYER_BUDGET_FACTOR = 0.75;
private static final ArrayDeque<Long> recentFrameDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> recentFrameTimestamps = new ArrayDeque<>();
private static final ArrayDeque<Long> baselineFrameDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> baselineFrameTimestamps = new ArrayDeque<>();
private static final ArrayDeque<Long> completedStepDurations = new ArrayDeque<>();
private static final ArrayDeque<Long> completedStepTimestamps = new ArrayDeque<>();
private static long lastFrameTimestamp = 0;
private static long lastStatsPrintTime = 0;
private static boolean simulationActive = false;
private static int currentLayer = 0;
private static long simulationStartedAtNs = 0;
private static long averageLayerCostNs = 1_000_000L;
private static float globalEmissionMultiplier = 0.6f; private static float globalEmissionMultiplier = 0.6f;
private static float globalAbsorptionMultiplier = 1.0f; private static float globalAbsorptionMultiplier = 1.0f;
@@ -36,15 +57,47 @@ public final class FogSimulationManager {
if (!initialized) return; if (!initialized) return;
long currentTime = System.nanoTime(); long currentTime = System.nanoTime();
if (lastFrameTimestamp != 0) {
long frameDuration = currentTime - lastFrameTimestamp;
recordFrameDuration(frameDuration, currentTime);
if (!simulationActive) {
recordBaselineFrameDuration(frameDuration, currentTime);
}
}
lastFrameTimestamp = currentTime;
if (currentTime - lastSimulationTime >= SIMULATION_INTERVAL_NS) { if (!simulationActive && currentTime - lastSimulationTime >= SIMULATION_INTERVAL_NS) {
beginSimulation();
lastSimulationTime = currentTime; lastSimulationTime = currentTime;
simulate(); }
if (simulationActive) {
runSimulationStep();
} }
} }
private static void simulate() { private static void beginSimulation() {
FogSimulationShader.simulate( simulationActive = true;
currentLayer = 0;
simulationStartedAtNs = System.nanoTime();
averageLayerCostNs = 1_000_000L;
}
private static void runSimulationStep() {
int remainingLayers = FogWorldVolume.SIZE_Z - currentLayer;
if (remainingLayers <= 0) {
FogDensityTextures.swap();
simulationActive = false;
return;
}
int layersThisFrame = computeLayersPerFrame(remainingLayers);
int endLayer = Math.min(FogWorldVolume.SIZE_Z, currentLayer + layersThisFrame);
long workStart = System.nanoTime();
FogSimulationShader.simulateRange(
currentLayer,
endLayer,
globalEmissionMultiplier, globalEmissionMultiplier,
globalAbsorptionMultiplier, globalAbsorptionMultiplier,
globalDecay, globalDecay,
@@ -53,6 +106,125 @@ public final class FogSimulationManager {
downDiffusion, downDiffusion,
horizontalDiffusion horizontalDiffusion
); );
long workDuration = System.nanoTime() - workStart;
if (layersThisFrame > 0) {
long observedCost = Math.max(500_000L, workDuration / layersThisFrame);
averageLayerCostNs = Math.max(500_000L, (long) (averageLayerCostNs * 0.8 + observedCost * 0.2));
}
currentLayer = endLayer;
if (currentLayer >= FogWorldVolume.SIZE_Z) {
long stepDuration = System.nanoTime() - simulationStartedAtNs;
recordCompletedStep(stepDuration);
FogDensityTextures.swap();
simulationActive = false;
}
}
private static int computeLayersPerFrame(int remainingLayers) {
long baselineFrameTime = getBaselineFrameTimeNs();
if (baselineFrameTime <= 0) {
baselineFrameTime = 16_000_000L;
}
long elapsedSimulationTime = System.nanoTime() - simulationStartedAtNs;
long remainingBudgetNs = TARGET_STEP_DURATION_NS - elapsedSimulationTime;
if (remainingBudgetNs <= 0) {
return Math.min(remainingLayers, MAX_LAYERS_PER_FRAME);
}
long frameBudgetNs = Math.max(1_000_000L, (long) (baselineFrameTime * LAYER_BUDGET_FACTOR));
int layersByBudget = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.round((double) frameBudgetNs / averageLayerCostNs));
int framesLeft = Math.max(1, (int) Math.ceil((double) remainingBudgetNs / baselineFrameTime));
int layersByProgress = Math.max(MIN_LAYERS_PER_FRAME, (int) Math.ceil((double) remainingLayers / framesLeft));
int layers = Math.min(MAX_LAYERS_PER_FRAME, Math.min(layersByBudget, layersByProgress));
return Math.min(remainingLayers, Math.max(MIN_LAYERS_PER_FRAME, layers));
}
private static void recordFrameDuration(long duration, long timestamp) {
recentFrameDurations.addLast(duration);
recentFrameTimestamps.addLast(timestamp);
while (!recentFrameTimestamps.isEmpty() && timestamp - recentFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
recentFrameTimestamps.removeFirst();
recentFrameDurations.removeFirst();
}
}
private static void recordBaselineFrameDuration(long duration, long timestamp) {
baselineFrameDurations.addLast(duration);
baselineFrameTimestamps.addLast(timestamp);
while (!baselineFrameTimestamps.isEmpty() && timestamp - baselineFrameTimestamps.peekFirst() > FRAME_TIME_WINDOW_NS) {
baselineFrameTimestamps.removeFirst();
baselineFrameDurations.removeFirst();
}
}
private static long getAverageFrameTimeNs() {
if (recentFrameDurations.isEmpty()) {
return 16_000_000L;
}
long sum = 0;
for (Long duration : recentFrameDurations) {
sum += duration;
}
return sum / recentFrameDurations.size();
}
private static long getBaselineFrameTimeNs() {
if (baselineFrameDurations.isEmpty()) {
return 16_000_000L;
}
long sum = 0;
for (Long duration : baselineFrameDurations) {
sum += duration;
}
return sum / baselineFrameDurations.size();
}
private static void recordCompletedStep(long duration) {
long timestamp = System.nanoTime();
completedStepDurations.addLast(duration);
completedStepTimestamps.addLast(timestamp);
while (!completedStepTimestamps.isEmpty() && timestamp - completedStepTimestamps.peekFirst() > STAT_WINDOW_NS) {
completedStepTimestamps.removeFirst();
completedStepDurations.removeFirst();
}
if (lastStatsPrintTime == 0 || timestamp - lastStatsPrintTime >= STAT_WINDOW_NS) {
printStepStats();
lastStatsPrintTime = timestamp;
}
}
private static void printStepStats() {
if (completedStepDurations.isEmpty()) {
return;
}
long sum = 0;
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
for (Long duration : completedStepDurations) {
sum += duration;
min = Math.min(min, duration);
max = Math.max(max, duration);
}
long average = sum / completedStepDurations.size();
System.out.printf(Locale.ROOT,
"[FogSim] step stats last 5s: avg=%.2f ms min=%.2f ms max=%.2f ms%n",
average / 1_000_000.0,
min / 1_000_000.0,
max / 1_000_000.0);
} }
public static int getCurrentDensityTexture() { public static int getCurrentDensityTexture() {
@@ -79,6 +79,31 @@ public final class FogSimulationShader {
float upDiffusion, float upDiffusion,
float downDiffusion, float downDiffusion,
float horizontalDiffusion float horizontalDiffusion
) {
simulateRange(
0,
FogWorldVolume.SIZE_Z,
globalEmissionMultiplier,
globalAbsorptionMultiplier,
globalDecay,
diffusionRate,
upDiffusion,
downDiffusion,
horizontalDiffusion
);
FogDensityTextures.swap();
}
public static void simulateRange(
int startZ,
int endZ,
float globalEmissionMultiplier,
float globalAbsorptionMultiplier,
float globalDecay,
float diffusionRate,
float upDiffusion,
float downDiffusion,
float horizontalDiffusion
) { ) {
if (program == null) return; if (program == null) return;
@@ -172,7 +197,10 @@ public final class FogSimulationShader {
// РЕНДЕРИНГ // РЕНДЕРИНГ
// ======================================== // ========================================
for (int z = 0; z < FogWorldVolume.SIZE_Z; z++) { int clampedStartZ = Math.max(0, Math.min(FogWorldVolume.SIZE_Z, startZ));
int clampedEndZ = Math.max(clampedStartZ, Math.min(FogWorldVolume.SIZE_Z, endZ));
for (int z = clampedStartZ; z < clampedEndZ; z++) {
GL30.glFramebufferTexture3D( GL30.glFramebufferTexture3D(
GL30.GL_FRAMEBUFFER, GL30.GL_FRAMEBUFFER,
GL30.GL_COLOR_ATTACHMENT0, GL30.GL_COLOR_ATTACHMENT0,
@@ -237,7 +265,5 @@ public final class FogSimulationShader {
// Восстанавливаем depth mask // Восстанавливаем depth mask
GL11.glDepthMask(previousDepthMask); GL11.glDepthMask(previousDepthMask);
// Меняем текстуры местами
FogDensityTextures.swap();
} }
} }