Симуляция распределена во времени
This commit is contained in:
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user