Симуляция распределена во времени
This commit is contained in:
@@ -1,12 +1,33 @@
|
||||
package su.divan2000.veila.client.render.fog;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class FogSimulationManager {
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static long lastSimulationTime = 0;
|
||||
|
||||
// Симулируем каждые 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 globalAbsorptionMultiplier = 1.0f;
|
||||
@@ -36,15 +57,47 @@ public final class FogSimulationManager {
|
||||
if (!initialized) return;
|
||||
|
||||
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;
|
||||
simulate();
|
||||
}
|
||||
|
||||
if (simulationActive) {
|
||||
runSimulationStep();
|
||||
}
|
||||
}
|
||||
|
||||
private static void simulate() {
|
||||
FogSimulationShader.simulate(
|
||||
private static void beginSimulation() {
|
||||
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,
|
||||
globalAbsorptionMultiplier,
|
||||
globalDecay,
|
||||
@@ -53,6 +106,125 @@ public final class FogSimulationManager {
|
||||
downDiffusion,
|
||||
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() {
|
||||
|
||||
@@ -79,6 +79,31 @@ public final class FogSimulationShader {
|
||||
float upDiffusion,
|
||||
float downDiffusion,
|
||||
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;
|
||||
|
||||
@@ -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.GL_FRAMEBUFFER,
|
||||
GL30.GL_COLOR_ATTACHMENT0,
|
||||
@@ -237,7 +265,5 @@ public final class FogSimulationShader {
|
||||
// Восстанавливаем depth mask
|
||||
GL11.glDepthMask(previousDepthMask);
|
||||
|
||||
// Меняем текстуры местами
|
||||
FogDensityTextures.swap();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user