From 0faa8066af894e29701e691ff22ecedd1e368099 Mon Sep 17 00:00:00 2001 From: DIvan2000 Date: Sat, 8 Aug 2026 17:15:14 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B8=D0=BC=D1=83=D0=BB=D1=8F=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=80=D0=B0=D1=81=D0=BF=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=BE=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../render/fog/FogSimulationManager.java | 182 +++++++++++++++++- .../render/fog/FogSimulationShader.java | 32 ++- 2 files changed, 206 insertions(+), 8 deletions(-) diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java index 5a4e0ac..c89ab1e 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationManager.java @@ -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 recentFrameDurations = new ArrayDeque<>(); + private static final ArrayDeque recentFrameTimestamps = new ArrayDeque<>(); + private static final ArrayDeque baselineFrameDurations = new ArrayDeque<>(); + private static final ArrayDeque baselineFrameTimestamps = new ArrayDeque<>(); + private static final ArrayDeque completedStepDurations = new ArrayDeque<>(); + private static final ArrayDeque 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() { diff --git a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java index db52487..0d05e21 100644 --- a/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java +++ b/src/client/java/su/divan2000/veila/client/render/fog/FogSimulationShader.java @@ -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(); } } \ No newline at end of file