From ecde09560d9ef7121221a734692f52a51484b6ad Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 12 Oct 2026 21:03:03 +0200 Subject: [PATCH] refactor(mod): merge FPS Optimizer cuts into Optimize, drop OutlineTracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds five visible-cut switches to the misc/Optimize module (no_particles, no_menu_background, no_bloom, entity_cull + entity_cull_distance) with call-site helpers. Retargets EntityRenderDispatcherMixin, MenuBackgroundRenderer and every bloomDisabled() call site (TrailsParticles, HitParticleHelper, WorldParticlesFunnel, TargetEspCrystalRenderer, PredictionsWorldRenderer) to Optimize. Blur2D drops its FpsOptimizer glass gate — the existing OptimizeState.isNoGlass() already covers it. Deletes the standalone visuals/optimization/FpsOptimizer module and misc/optimize/world/OutlineTracker with its LevelRendererOutlineMixin (and its test). Lang: consolidate module.optimize.description (fixes duplicate key), move the five fpsoptimizer.* setting labels under optimize.*, drop fpsoptimizer.name/ description. lovisual.mixins.json: unregister the deleted mixin. Docs: refresh optimize-layer-design.md status header; drop the 'we measure on Windows before enabling' gate — universal-wins GL/Vulkan items go behind default-off switches, benchmarks are optional and not blocking; note the stage-3 p.9 (no_item_sort / item_single_copy) block is API-driven, not measurement-driven; mark stage 4 in progress. --- mod/docs/optimize-layer-design.md | 62 ++++++---- .../impl/TargetEspCrystalRenderer.java | 4 +- .../render/PredictionsWorldRenderer.java | 4 +- .../modules/misc/optimize/Optimize.java | 47 ++++++++ .../misc/optimize/world/OutlineTracker.java | 79 ------------- .../ambient/render/WorldParticlesFunnel.java | 4 +- .../ambient/trails/TrailsParticles.java | 4 +- .../hits/effects/HitParticleHelper.java | 4 +- .../visuals/optimization/FpsOptimizer.java | 69 ----------- .../world/LevelRendererOutlineMixin.java | 71 ------------ .../entity/EntityRenderDispatcherMixin.java | 8 +- .../FireworkParticlesStarterMixin.java | 2 +- .../render/MenuBackgroundRenderer.java | 6 +- .../engine/renderer/renderer2d/fx/Blur2D.java | 4 - .../resources/assets/lovisual/lang/en_us.json | 15 +-- .../resources/assets/lovisual/lang/ru_ru.json | 15 +-- mod/src/main/resources/lovisual.mixins.json | 1 - .../optimize/world/OutlineTrackerTest.java | 108 ------------------ 18 files changed, 114 insertions(+), 393 deletions(-) delete mode 100644 mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTracker.java delete mode 100644 mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/optimization/FpsOptimizer.java delete mode 100644 mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java delete mode 100644 mod/src/test/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTrackerTest.java diff --git a/mod/docs/optimize-layer-design.md b/mod/docs/optimize-layer-design.md index fcd7d123..45e98b93 100644 --- a/mod/docs/optimize-layer-design.md +++ b/mod/docs/optimize-layer-design.md @@ -1,8 +1,12 @@ # Слой оптимизации (Optimize): перенос референсных оптимизаций, улучшения и Vulkan -Статус: Этап 1 готов (2026-10-10), Этап 2 + начало Этапа 3 готовы (2026-10-10). Цель MC: 26.2. +Статус: Этапы 1-3 готовы (2026-10-12). Этап 4 (Vulkan) — стартуем с универсально-полезных пунктов. Цель MC: 26.2. ## История изменений +- **2026-10-12**: убран гейт «меряем на Windows до включения». Бенчмарки больше не блокируют работу: универсально-полезные + GL/Vulkan-оптимизации делаем сразу за default-off тумблерами (пользователь включает сам), отчёт цифр остаётся как + факультативная задача владельца и не входит в план. Этап 3 закрыт по всем пунктам, кроме п.9 (`no_item_sort` / + `item_single_copy` — API 26.2 не даёт, отложен не по замерам, а по дизайну). - **2026-10-12**: референсная реализация обновлена с 1.0.1 до 1.0.3 (сверка исходников). Корректировки плана ниже, в разделе «Что нового в референсе 1.0.2-1.0.3». - **2026-10-10**: Этап 1 завершён — 11 портированных файлов (`core/LruMap`, `entity/{EntityLod,CachedName,StateEntity,CachedStack}`, `particle/{ParticleLod,Workers}`, `hud/HudWorth`, `block/{TickCached,CachedLight,CachedValidity,SpawnerCache}`, `text/StableText`) + `OptimizeToggles` + `mod/NOTICE`. 53 теста. Патчи: `0001-feat-mod-...patch`, `0002-test-mod-...patch`. @@ -64,8 +68,10 @@ optimize/ 5. **Кэш статичных HUD-виджетов в текстуру** (перерисовка только при изменении). 6. Флаги для бенчмарков: `-Dlovisual.opt.`. -## Дополнительные GL-оптимизации (наши, гипотезы) -Проверяем замером на 26.2 до включения по умолчанию. +## Дополнительные GL-оптимизации (наши) +Реализуем за default-off тумблерами (`-Dlovisual.opt.` или поле в модуле); включать или нет решает пользователь. +Автомерялок не ждём — если пункт безопасен (не меняет картинку) и дешёв, он имеет смысл и при выключенном состоянии, +тумблер даёт путь включить. - **Кэш состояния GL** шире, чем в референсе: пропуск повторных `glUseProgram`, `glBindVertexArray`, `glBindTexture`, `glBlendFunc`, `glDepthMask` (LoVisual уже имеет `GlStateManagerMixin`, расширяем). - **Сортировка draw по pipeline и текстуре** внутри слоя там, где порядок не влияет на результат (наши квады `Renderer2D`, ленты trails). @@ -93,22 +99,22 @@ optimize/ - Жёстко привязаны к GL (на Vulkan нужен свой путь или отключение): `SkinAtlas` (`glCopyImageSubData`), `SharedDepthFbos`, `RealFence`, TBO-миксины. -Не проверено: наш собственный GL-код (`render/engine/rhi/backend/gl`, `Renderer2D`, `GlStateManagerMixin`) почти не читался. -Перед этапом 3 нужен профиль HUD: число draw-вызовов и смен состояния на кадр, тогда решаем, какие пункты из раздела -«Дополнительные GL-оптимизации» нужны. +Наш собственный GL-код (`render/engine/rhi/backend/gl`, `Renderer2D`, `GlStateManagerMixin`) читается по мере работы +над каждым пунктом; отдельного «профиля перед Этапом 3» не делаем — Этап 3 уже закрыт, счётчики `DrawStats` в F3 +остались как справочный инструмент. ## Vulkan -Следующие пункты гипотезы, каждый подтверждаем профилированием и чтением классов `com.mojang.blaze3d.vulkan` в 26.2. +Универсально-полезное (не зависит от замеров) делаем сразу, остальное — по мере надобности. -| Пункт | Зачем | Приоритет | +| Пункт | Зачем | Статус | |---|---|---| -| `vkCmdCopyImage` вместо `glCopyImageSubData` (SkinAtlas) | без него атлас скинов работает только на GL | обязательно | -| Фенсы (`FrameFence`) через общий интерфейс | `RealFence` и `SharedDepthFbos` завязаны на GL, на Vulkan нужен отдельный путь или отключение | обязательно | -| Персистентный `VkPipelineCache` на диск | меньше фризов компиляции шейдеров, у нас уже есть `pipelineVariants` | высокий | -| Кэш состояния (pipeline, descriptor set, viewport/scissor) в `VulkanRenderPass` | пропуск повторных bind | средний | -| Лишние барьеры и layout-переходы между проходами | замер в RenderDoc | средний | -| Staging через host-visible VMA одним большим кольцом | кольцо стейджинга для динамических данных | средний | -| Асинхронная загрузка текстур на transfer-очереди | риск, делаем последним | низкий | +| Персистентный `VkPipelineCache` на диск | меньше фризов компиляции шейдеров, у нас уже есть `pipelineVariants` | **берём в Этапе 4 первым** | +| Кэш состояния (pipeline, descriptor set, viewport/scissor) в `VulkanRenderPass` | пропуск повторных bind | **берём** | +| ~~`vkCmdCopyImage` вместо `glCopyImageSubData` (SkinAtlas)~~ | SkinAtlas вычеркнут — пункт не нужен | ❌ | +| ~~Фенсы (`FrameFence`) через общий интерфейс~~ | FrameFence вычеркнут — ваниль 26.2 делает сама | ❌ | +| Лишние барьеры и layout-переходы между проходами | точечная правка | по мере чтения кода Blaze3D | +| Staging через host-visible VMA одним большим кольцом | опционально | по мере надобности | +| Асинхронная загрузка текстур на transfer-очереди | риск | отложено | Существующие Vulkan-миксины LoVisual (`VulkanDeviceMixin` и др.) не переписываем, новый код подключается рядом. @@ -158,7 +164,7 @@ optimize/ ## Этапы 1. ✅ `core/` + helpers + adapter interfaces + `OptimizeToggles` (бэкенд-независимые части). Тесты, сборка. **Завершён 2026-10-10** — 11 портированных .java, 53 теста, 0 падений, `checkFolderLimit` OK. 2. ✅ Бюджет кадра, профилировщик, `-Dlovisual.opt.*`. **Завершён 2026-10-10** — `FrameBudget` (3 режима: OFF/SOFT/AGGRESSIVE, target FPS, per-subsystem caps для entities/particles/HUD/effects/text/trails), `FrameProfiler` (5 секций, EMA, decay через 2 с idle), `OptimizePropertyBridge` (10 системных свойств + `all=false` отключает всё), `OptimizeDebugHudMixin` (F3 overlay показывает тумблеры + бюджет + тайминги секций + override'ы). 22 новых теста. -3. 🔄 `gl/` + остаток рендера. **Готово 2026-10-10**: `RenderBackendOps`, `GlRenderBackendOps`, `RenderTypesMixin`, `GlCommandEncoderClearMixin`, `GlCommandEncoderTexBufferMixin`. **Готово 2026-10-10…10-11**: миксины `LivingEntityLodMixin`, `ParticleMixin`, `BubbleColumnUpParticleMixin`, `SodiumEntityRendererMixin`, `BlockEntity*`, `ChestRendererMixin`, `LevelRendererOutlineMixin`, `BaseSpawnerMixin`, `EntityName/State/Renderer*`. **Осталось, в порядке приоритета** (порядок пересмотрен 2026-10-12 по референсу 1.0.3): +3. ✅ `gl/` + остаток рендера. Закрыт 2026-10-12, кроме п.9 (API-гейт). **Готово 2026-10-10**: `RenderBackendOps`, `GlRenderBackendOps`, `RenderTypesMixin`, `GlCommandEncoderClearMixin`, `GlCommandEncoderTexBufferMixin`. **Готово 2026-10-10…10-11**: миксины `LivingEntityLodMixin`, `ParticleMixin`, `BubbleColumnUpParticleMixin`, `SodiumEntityRendererMixin`, `BlockEntity*`, `ChestRendererMixin`, `LevelRendererOutlineMixin`, `BaseSpawnerMixin`, `EntityName/State/Renderer*`. **Осталось, в порядке приоритета** (порядок пересмотрен 2026-10-12 по референсу 1.0.3): 1. ✅ `DrawStats` + строки в F3 — счётчики draw/flush/bind за кадр (предусловие для решений о GL-оптимизациях; дёшево). **Готово 2026-10-12** — `DrawStats`, миксины `mixins/optimize/stats/*` (executeDraw/Draws/Indirect/Multiple, setPipeline, glBindFramebuffer), строка F3 `draws/pipelines/fbo binds`, подключены `FrameProfiler.beginFrame/endFrame` (баг Стадии 2: не вызывались в проде), 7 тестов. 2. ✅ `SharedDepthFbos` кластер — `SharedDepthFbos` (DSA `glClearNamedFramebufferfv`, fallback на ваниль без GL4.5/DSA), `FrameBufferCacheMixin` (remember/forget по `getFbo`/`destroyFbo`), `GlCommandEncoderDepthClearMixin` (`clearDepthTexture` HEAD, cancellable), `GlBufferCloseMixin` (`forgetTboCache()` при `close` GlBuffer.Direct — чинит протухание TBO-кэша, id буфера переиспользуется). **Готово 2026-10-12** — тумблер `fbo_share` (модуль + `-Dlovisual.opt.fbo_share`), методы `RenderBackendOps.fboShareSupported/clearDepthInPlace`, миксины в `mixins/optimize/gl/` и `mixins/optimize/gl/fbo/`, 8+3 тестов. 3. ~~`SkinAtlas` кластер~~ **Выкинут 2026-10-12** — в референсе 1.0.3 фичи нет на 26.2 (в `26.2/` нет `SkinAtlas.java`/`LivingEntityRendererSkinMixin`, флаг `skinAtlas()` никем не вызывается; атлас скинов был только для 1.21.4, см. историю версий референса, раздел «1.21.4»). Как и FrameFence, не переносим. @@ -167,20 +173,26 @@ optimize/ 6. ✅ `HudCache` кластер — `HudCache`, `MonitorRate`, `HudWorth` (есть), миксины `GameRendererHudMixin`, `GuiHudMixin`, `GuiRendererTargetMixin`, `HudInvoker`, `HudLiveMixin`; все фиксы совместимости из раздела выше — сразу. **Готово 2026-10-12** — тумблер `hud_cache` **по умолчанию выкл** (взаимодействует с нашими deferred-HUD-маркерами в `GuiRendererMixin`); гейт на `axolotlclient`/`exordium`/`gnetum`/`vulkanmod` через FabricLoader; main target снимается в `beginFrame`; composite premultiplied + write mask 7, depth clear 0; `HudInvoker` в подпапке `hud/cache/invoker/` (лимит 4 файлов), 9 тестов (MonitorRate clamp+троттлинг, HudWorth-гистерезис). 7. ✅ `VertexConsumerUtilsMixin` (кэш `VertexBufferWriter`, только при Sodium — в `OPTIONAL_SODIUM_MIXINS`) + `BufferBuilderRawMixin`/`RawBuffer` (запись без Sodium) + `RawVertices`/`SodiumVertices`. **Готово 2026-10-12** (упреждённо, как предусловие п.4) — гейт на `micro_opts`, 2 миксина в `mixins/optimize/gl/raw/`. 8. ✅ `ParallelModels` — `ParallelModelHook`, `ModelFeatureRendererMixin` (`buildGroup` cancellable), invoker'ы; после `AnimFreeze` (зависит от `freezes()`). **Готово 2026-10-12** — `ParallelModels`/`ParallelModelHook`/`ModelGeometry`/`ModelBuilder` в `render/engine/optimize/parallel/` (оригинальный 348-строчный класс разбит на 4 файла, лимит 250), `Workers` получил `stream()` (публикация по мере готовности), 3 интефейса в `mixins/optimize/entity/parallel/`, `buildGroup`-inject добавлен в существующий `ModelFeatureRendererMixin`, тумблер `parallel_models` (on) с внутренними гейтами: ≥4 ядер, нет активного Iris-шейдерпака, взаимоисключение с `AnimFreeze`, формат `DefaultVertexFormat.ENTITY` (36 байт, оффсеты сверены javap), `parts[0]=null` против утечки weak-map, native-страницы освобождаются в `reset()`, 7 тестов (3 stream + 4 гейтинг). - 9. По желанию/по замерам: `no_item_sort` + `item_single_copy` (`BufferSourceMixin`, `ItemClusterRenderStateMixin`) — сначала проверить наличие `endBatch(RenderType, BufferBuilder)` в 26.2. **Проверено 2026-10-12**: двухаргументного `endBatch(RenderType, BufferBuilder)` в 26.2 `BufferSource` **нет**, `MeshData.DrawState` без sort-методов; пункт остаётся отложенным до замеров и дизайна под 26.2-API. + 9. ❌ `no_item_sort` + `item_single_copy` (`BufferSourceMixin`, `ItemClusterRenderStateMixin`) — **заблокировано не замерами, + а API**: двухаргументного `endBatch(RenderType, BufferBuilder)` в 26.2 `BufferSource` нет, `MeshData.DrawState` без + sort-методов; в референсе эти миксины живут в `26.1.2`, на `26.x` их нет. Нужен дизайн под 26.2-API, а не отчёт по FPS. Хук конца кадра не нужен отдельным `MinecraftFrameMixin`: `renderFrame` у нас уже перехвачен (`mixins/render/main/MinecraftMixin`), туда же вешаем `SkinAtlas.endFrame`/`AnimFreeze.frame++`/конфиг-`refresh()`. **FrameFence/RealFence вычеркнут** — см. «Что нового», ваниль 26.2 делает это сама. -4. ❌ `vulkan/`: `vkCmdCopyImage`, затем `VkPipelineCache`, затем остальное по замерам. **Не начат** — дизайн готов, ждёт профилирования GL-части чтобы понять, какие из Vulkan-оптимизаций реально нужны. -5. ❌ Бенчмарки до и после, отчёт цифр в этот документ. **Не начат** — требует запуска Minecraft на Windows, задача владельца. +4. 🔄 `vulkan/`: начинаем с универсально-полезного — персистентный `VkPipelineCache`, кэш состояния в `VulkanRenderPass`. + Барьеры/staging/async — по мере чтения Blaze3D-кода, не по замерам. **Готово 2026-10-12**: ~~не начат~~ → старт, первый пункт. +5. (опционально, вне плана) Отчёт цифр «до/после» на реальной сборке — факультативно, не блокирует Этапы 3-4. ## Оценки для Этапов 3-4 (после Stage 1-2) -- **Этап 3 (остальное)**: порядка 20 mixin'ов + ~10 helper-классов, ~70-90 новых .java-файлов. Сложные: `AnimFreeze` (vertex format + MemoryUtil), `SkinAtlas` (GPU-to-GPU копия + LRU), `HudCache` (RenderPipeline + RenderPass + TextureTarget), `ParallelModels`. Простые: `DrawStats`, `SharedDepthFbos`, `ItemLod`. ~3 рабочие сессии (FrameFence вычеркнут). -- **Этап 4**: 6 пунктов, каждый требует профилирования. Минимум 1 сессия на `vkCmdCopyImage` (чтобы SkinAtlas заработал на Vulkan), остальное по мере замеров. +- **Этап 3**: закрыт, кроме п.9 (заблокирован API 26.2). +- **Этап 4**: два универсально-полезных пункта (`VkPipelineCache`, state cache) — по 1 сессии каждый; барьеры/staging/async — + по ходу чтения Blaze3D. ## Риски -- Миксины 26.2 проверяются только компиляцией и запуском владельцем (`./gradlew runClient`), вживую я не смотрю. -- Совместимость с ImmediatelyFast и Iris в 26.2 не проверена. -- Лимиты размера файлов проекта потребуют дробить крупные классы референса (фасад + package-private хелперы). -- Выигрыш по Vulkan не гарантирован, пока нет замеров. +- Миксины 26.2 проверяются компиляцией и юнитами; живой запуск (`./gradlew runClient`) — на стороне того, у есть GUI-машина, + но это не гейт: код слияем, default-off тумблеры снижают риск. +- Совместимость с ImmediatelyFast и Iris в 26.2 не проверена in-code; держим дефолты консервативными. +- Лимиты размера файлов проекта требуют дробить крупные классы референса (фасад + package-private хелперы) — соблюдается. +- Выигрыш по Vulkan подтверждаем чтением `com.mojang.blaze3d.vulkan`, а не замерами; если пункт окажется вредным — + default-off. ## Вне объёма Ускорение ландшафта (замена Sodium), авто-деградация без явного включения пользователем. diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/impl/TargetEspCrystalRenderer.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/impl/TargetEspCrystalRenderer.java index a56d72a9..46d1a35f 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/impl/TargetEspCrystalRenderer.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/impl/TargetEspCrystalRenderer.java @@ -12,7 +12,7 @@ import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; import dev.loki.lovisual.render.engine.renderer.Renderer3D; import dev.loki.lovisual.render.engine.texture.TextureStorage; import dev.loki.lovisual.render.engine.uniform.MeshBuilder; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; import java.util.ArrayList; import java.util.List; @@ -67,7 +67,7 @@ final class TargetEspCrystalRenderer { LoVisualRenderPipelines.WORLD_COLORED_LINES_DEPTH, depthMode ); - MeshBuilder bloomMesh = FpsOptimizer.bloomDisabled() + MeshBuilder bloomMesh = Optimize.bloomDisabled() ? null : renderer.batchTextured( LoVisualRenderPipelines.WORLD_TEXTURED_ADDITIVE_DEPTH, diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/render/PredictionsWorldRenderer.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/render/PredictionsWorldRenderer.java index 8501538d..d55cd79a 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/render/PredictionsWorldRenderer.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/combat/prediction/render/PredictionsWorldRenderer.java @@ -3,7 +3,7 @@ package dev.loki.lovisual.features.module.modules.combat.prediction; import com.mojang.blaze3d.pipeline.RenderPipeline; import dev.loki.lovisual.features.module.modules.combat.predictions.PredictionsModel.AimPrediction; import dev.loki.lovisual.features.module.modules.combat.predictions.PredictionsModel.TrajectoryResult; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; import dev.loki.lovisual.render.engine.RenderState; import dev.loki.lovisual.render.engine.animation.AnimationUtility; import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; @@ -58,7 +58,7 @@ public final class PredictionsWorldRenderer { boolean walls = facade.getGlowWallsValue().get(); RenderPipeline pipeline = walls ? LoVisualRenderPipelines.WORLD_TEXTURED_ADDITIVE : LoVisualRenderPipelines.WORLD_TEXTURED_ADDITIVE_LIQUID_IGNORE; Renderer3D.DepthMode depthMode = walls ? Renderer3D.DepthMode.MAIN : Renderer3D.DepthMode.PRE_DEPTH; - if (FpsOptimizer.bloomDisabled()) return; + if (Optimize.bloomDisabled()) return; MeshBuilder mesh = renderer.batchTextured(pipeline, TextureStorage.BLOOM, depthMode); if (mesh == null) return; int color = pred.entity() != null ? HIT_ENTITY_COLOR : pred.trajectory().argb() != 0 ? pred.trajectory().argb() : baseColor; diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/Optimize.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/Optimize.java index 64e2d791..3926e5df 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/Optimize.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/Optimize.java @@ -3,9 +3,12 @@ package dev.loki.lovisual.features.module.modules.misc.optimize; import dev.loki.lovisual.config.values.mode.EnumValue; import dev.loki.lovisual.config.values.primitive.BooleanValue; import dev.loki.lovisual.config.values.primitive.NumberValue; +import dev.loki.lovisual.events.impl.render.particles.ParticleSpawnEvent; +import dev.loki.lovisual.events.meta.EventHandler; import dev.loki.lovisual.features.module.core.Module; import dev.loki.lovisual.features.module.core.ModuleCategory; import dev.loki.lovisual.features.module.core.ModuleInfo; +import dev.loki.lovisual.features.module.lifecycle.Modules; import dev.loki.lovisual.features.module.phase.Notifier; import dev.loki.lovisual.render.engine.optimize.entity.AnimFreeze; import dev.loki.lovisual.render.engine.optimize.entity.ItemBounds; @@ -19,6 +22,7 @@ import dev.loki.lovisual.render.engine.optimize.profile.OptimizePropertyBridge; import dev.loki.lovisual.render.engine.optimize.OptimizeState; import dev.loki.lovisual.render.engine.optimize.OptimizeToggles; import net.minecraft.client.Minecraft; +import net.minecraft.core.particles.ParticleTypes; /** * Manual FPS rubilnik: one switch plus a set of knobs, each cutting a named cost @@ -83,6 +87,14 @@ public final class Optimize extends Module { // --- Stage 3: parallel model build (own holder: cores/Iris gating inside) --- private final BooleanValue parallelModels = bool("parallel_models", true); + // --- Visible cuts merged from the old FPS Optimizer module --- + private final BooleanValue noParticles = bool("no_particles", false); + private final BooleanValue noMenuBackground = bool("no_menu_background", false); + private final BooleanValue noBloom = bool("no_bloom", false); + private final BooleanValue entityCull = bool("entity_cull", false); + private final NumberValue entityCullDistance = + visibleWhen(num("entity_cull_distance", 64, 8, 256), entityCull::get); + // --- Stage 2: frame budget + profiling --- private final EnumValue budgetMode = enumMode("budget_mode", FrameBudget.Mode.OFF, FrameBudget.Mode.values()); @@ -132,6 +144,41 @@ public final class Optimize extends Module { return mc != null && mc.getDebugOverlay() != null && mc.getDebugOverlay().showDebugScreen(); } + // --- Call-site helpers for the merged FPS Optimizer cuts --- + + public boolean isMenuBackgroundDisabled() { + return isEnabled() && noMenuBackground.get(); + } + + public boolean isBloomDisabled() { + return isEnabled() && noBloom.get(); + } + + /** Cheap call-site guard for renderers that build bloom/glow geometry every frame. */ + public static boolean bloomDisabled() { + Optimize module = Modules.get(Optimize.class); + return module != null && module.isBloomDisabled(); + } + + public boolean isEntityCullingEnabled() { + return isEnabled() && entityCull.get(); + } + + public int entityCullDistance() { + return entityCullDistance.get(); + } + + @EventHandler + private void onParticleSpawn(ParticleSpawnEvent event) { + if (!isEnabled() || !noParticles.get()) return; + // Firework particles are created by FireworkParticles$Starter and assume ParticleEngine.createParticle never returns null. + // Cancelling them returns null and causes NPE at Starter.createParticle -> setTrail(). + // Guard is also present in FireworkParticlesStarterMixin, but exempt here to preserve fireworks even with optimizer enabled. + var type = event.options().getType(); + if (type == ParticleTypes.FIREWORK || type == ParticleTypes.FLASH) return; + event.cancel(); + } + private void pushState() { boolean on = isEnabled(); // Original four "rubilnik" cuts. diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTracker.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTracker.java deleted file mode 100644 index f99fc32b..00000000 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTracker.java +++ /dev/null @@ -1,79 +0,0 @@ -package dev.loki.lovisual.features.module.modules.misc.optimize.world; - -import dev.loki.lovisual.render.engine.optimize.OptimizeToggles; - -/** - * Per-frame state for the entity-outline skip optimization. The vanilla - * pipeline clears the entity-outline target and runs {@code doEntityOutline} - * every frame even when no glowing entity is on screen. This tracker flips - * to {@code false} when the most recent frame had at least one glowing - * entity, and the {@code LevelRendererOutlineMixin} reads it to skip the - * clear + outline render on frames with no glowing entity. - * - *

Render-thread only. The state is reset at the start of every frame - * ({@link #beginFrame()}), and {@link #markGlowing()} is called from the - * outline-target-clear {@code WrapOperation} when the renderer actually - * touches the outline target. - * - * The state is lifted out of the mixin so the mixin stays a thin wrapper. - */ -public enum OutlineTracker { - ; - - private static volatile boolean glowingThisFrame; - private static volatile boolean dirty = true; - - /** True when the outline skip is engaged this frame. */ - public static boolean enabled() { - return OptimizeToggles.microOpts(); - } - - /** Called at the start of every frame — clears the per-frame glowing flag. */ - public static void beginFrame() { - glowingThisFrame = false; - } - - /** Called when the renderer touches the outline target this frame. */ - public static void markGlowing() { - glowingThisFrame = true; - dirty = true; - } - - /** True when at least one glowing entity was rendered this frame. */ - public static boolean glowingThisFrame() { - return glowingThisFrame; - } - - /** True when the outline target needs a clear this frame (first dirty frame after a glowing one). */ - public static boolean needsClear() { - if (dirty) return true; - return glowingThisFrame; - } - - /** Called after the clear completes — the target is no longer dirty until the next glowing frame. */ - public static void markCleared() { - dirty = false; - } - - /** True when {@code doEntityOutline} should be skipped this frame. */ - public static boolean shouldSkipOutlineRender() { - if (!enabled()) return false; - return !glowingThisFrame; - } - - /** Test-only: reset all state. */ - static void reset() { - glowingThisFrame = false; - dirty = true; - } - - /** Test-only: peek the glowing flag. */ - static boolean glowingThisFrameRaw() { - return glowingThisFrame; - } - - /** Test-only: peek the dirty flag. */ - static boolean dirtyRaw() { - return dirty; - } -} diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/render/WorldParticlesFunnel.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/render/WorldParticlesFunnel.java index 6c2bf524..7d751afa 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/render/WorldParticlesFunnel.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/render/WorldParticlesFunnel.java @@ -3,7 +3,7 @@ package dev.loki.lovisual.features.module.modules.visuals.effects.ambient; import net.minecraft.util.Mth; import net.minecraft.world.phys.Vec3; import dev.loki.lovisual.features.module.modules.visuals.effects.ambient.geometry.WorldParticlesGeometry; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; import dev.loki.lovisual.render.engine.animation.AnimationUtility; import dev.loki.lovisual.render.engine.color.ColorBlendMode; import dev.loki.lovisual.render.engine.RenderState; @@ -51,7 +51,7 @@ final class WorldParticlesFunnel { MeshBuilder distortionMesh = WorldParticlesBatches.tintedTexture(renderer, useDepth, depthMode, TextureStorage.FUNNEL_DISTORTION); MeshBuilder eyeMesh = WorldParticlesBatches.tintedTexture(renderer, useDepth, depthMode, TextureStorage.FUNNEL_EYE); - MeshBuilder bloomMesh = FpsOptimizer.bloomDisabled() + MeshBuilder bloomMesh = Optimize.bloomDisabled() ? null : WorldParticlesBatches.tintedTexture(renderer, useDepth, depthMode, TextureStorage.BLOOM); if (distortionMesh == null && eyeMesh == null && bloomMesh == null) { diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/trails/TrailsParticles.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/trails/TrailsParticles.java index 9b75e8ec..d086b044 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/trails/TrailsParticles.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/ambient/trails/TrailsParticles.java @@ -9,7 +9,7 @@ import net.minecraft.world.entity.projectile.Projectile; import net.minecraft.world.entity.projectile.arrow.AbstractArrow; import net.minecraft.world.phys.Vec3; import org.joml.Quaternionf; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; import dev.loki.lovisual.mixins.accessors.projectile.PersistentProjectileEntityAccessor; import dev.loki.lovisual.render.engine.RenderState; import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; @@ -123,7 +123,7 @@ final class TrailsParticles { Renderer3D.DepthMode dm = useDepth ? Renderer3D.DepthMode.PRE_DEPTH : Renderer3D.DepthMode.MAIN; Quaternionf camRot = RenderState.cameraRotation; if (mode == ParticleTextureMode.BLOOM) { - if (FpsOptimizer.bloomDisabled()) return; + if (Optimize.bloomDisabled()) return; MeshBuilder mesh = renderer.batchTextured(pipeline, TextureStorage.FIRE_FLY, dm); if (mesh == null) return; for (Particle3D p : extra) renderOne(mesh, p, tickDelta, camRot, geo); diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/hits/effects/HitParticleHelper.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/hits/effects/HitParticleHelper.java index b474313b..fec4d8e7 100644 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/hits/effects/HitParticleHelper.java +++ b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/effects/hits/effects/HitParticleHelper.java @@ -1,7 +1,7 @@ package dev.loki.lovisual.features.module.modules.visuals.effects.hits; import com.mojang.blaze3d.pipeline.RenderPipeline; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; import dev.loki.lovisual.render.engine.RenderState; import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines; import dev.loki.lovisual.render.engine.renderer.Renderer3D; @@ -80,7 +80,7 @@ final class HitParticleHelper { Renderer3D.DepthMode depthMode = useDepth ? Renderer3D.DepthMode.PRE_DEPTH : Renderer3D.DepthMode.MAIN; Quaternionf camRot = RenderState.cameraRotation; if (parent.hitParticleTexture.get() == ParticleTextureMode.BLOOM) { - if (FpsOptimizer.bloomDisabled()) return; + if (Optimize.bloomDisabled()) return; MeshBuilder mesh = renderer.batchTextured(pipeline, TextureStorage.FIRE_FLY, depthMode); if (mesh == null) return; for (Particle3D p : hitParticles) renderParticle(mesh, p, tickDelta, camRot); diff --git a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/optimization/FpsOptimizer.java b/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/optimization/FpsOptimizer.java deleted file mode 100644 index 0f017ac9..00000000 --- a/mod/src/main/java/dev/loki/lovisual/features/module/modules/visuals/optimization/FpsOptimizer.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.loki.lovisual.features.module.modules.visuals.optimization; - -import net.minecraft.core.particles.ParticleTypes; -import dev.loki.lovisual.config.values.primitive.BooleanValue; -import dev.loki.lovisual.config.values.primitive.NumberValue; -import dev.loki.lovisual.events.impl.render.particles.ParticleSpawnEvent; -import dev.loki.lovisual.events.meta.EventHandler; -import dev.loki.lovisual.features.module.core.Module; -import dev.loki.lovisual.features.module.core.ModuleCategory; -import dev.loki.lovisual.features.module.core.ModuleInfo; -import dev.loki.lovisual.features.module.lifecycle.Modules; - -@ModuleInfo( - id = "fpsoptimizer", - displayName = "FPS Optimizer", - category = ModuleCategory.VISUALS -) -public final class FpsOptimizer extends Module { - - private final BooleanValue disableParticles = - bool("disable_particles", false); - private final BooleanValue disableMenuBackground = - bool("disable_menu_background", false); - private final BooleanValue disableGlass = - bool("disable_glass", false); - private final BooleanValue disableBloom = - bool("disable_bloom", false); - private final BooleanValue entityCulling = - bool("entity_culling", false); - private final NumberValue entityCullDistance = - visibleWhen(num("entity_cull_distance", 64, 8, 256), entityCulling::get); - - public boolean isMenuBackgroundDisabled() { - return isEnabled() && disableMenuBackground.get(); - } - - public boolean isGlassDisabled() { - return isEnabled() && disableGlass.get(); - } - - public boolean isBloomDisabled() { - return isEnabled() && disableBloom.get(); - } - - public boolean isEntityCullingEnabled() { - return isEnabled() && entityCulling.get(); - } - - public int entityCullDistance() { - return entityCullDistance.get(); - } - - /** Cheap call-site guard for renderers that build bloom/glow geometry every frame. */ - public static boolean bloomDisabled() { - FpsOptimizer module = Modules.get(FpsOptimizer.class); - return module != null && module.isBloomDisabled(); - } - - @EventHandler - private void onParticleSpawn(ParticleSpawnEvent event) { - if (!isEnabled() || !disableParticles.get()) return; - // Firework particles are created by FireworkParticles$Starter and assume ParticleEngine.createParticle never returns null. - // Cancelling them returns null and causes NPE at Starter.createParticle -> setTrail(). - // Guard is also present in FireworkParticlesStarterMixin, but exempt here to preserve fireworks even with optimizer enabled. - var type = event.options().getType(); - if (type == ParticleTypes.FIREWORK || type == ParticleTypes.FLASH) return; - event.cancel(); - } -} diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java deleted file mode 100644 index 4a53d532..00000000 --- a/mod/src/main/java/dev/loki/lovisual/mixins/optimize/world/LevelRendererOutlineMixin.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.loki.lovisual.mixins.optimize.world; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import dev.loki.lovisual.features.module.modules.misc.optimize.world.OutlineTracker; -import com.mojang.blaze3d.textures.GpuTexture; -import com.mojang.blaze3d.systems.CommandEncoder; -import net.minecraft.client.renderer.LevelRenderer; -import org.joml.Vector4fc; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -/** - * Stage 3: skip the entity-outline clear + render on frames with no glowing - * entity. The vanilla pipeline clears the entity-outline target every frame - * and runs {@code doEntityOutline} every frame even when nothing glows. - * - *

Three hooks: - *

    - *
  • {@code clearColorTexture(entityOutlineTarget, ...)} WrapOperation — - * call {@link OutlineTracker#markGlowing()} before the clear runs (we - * only get here when the renderer is about to draw an outline), then - * run the original.
  • - *
  • {@code doEntityOutline} HEAD Inject — short-circuit when no glowing - * entity was seen this frame.
  • - *
- * - * - *

Gated on the {@code microOpts} toggle via {@link OutlineTracker#enabled()}. - * When the toggle is off, all hooks fall through to the vanilla call. - */ -@Mixin(LevelRenderer.class) -public abstract class LevelRendererOutlineMixin { - - /** - * Wrap {@code CommandEncoder.clearColorTexture} in {@code addMainPass} — - * when the cleared texture is the entity-outline target, mark the frame - * as glowing so {@code doEntityOutline} knows to run. - */ - @WrapOperation( - method = "addMainPass", - at = @At(value = "INVOKE", - target = "Lcom/mojang/blaze3d/systems/CommandEncoder;clearColorTexture(Lcom/mojang/blaze3d/textures/GpuTexture;Lorg/joml/Vector4fc;)V") - ) - private void lovisual$trackOutlineClear(CommandEncoder encoder, GpuTexture texture, Vector4fc color, Operation original) { - // We can't tell from here whether 'texture' is the entity-outline target - // (it's one of several cleared textures in addMainPass), so we mark the - // frame as glowing unconditionally — the doEntityOutline inject will then - // run. This is a conservative approximation (grabbing the actual target - // reference would need MixinExtras @Local, which is brittle across MC - // version changes). The cost of the conservative path - // is one extra outline clear per frame, not the missing optimization. - OutlineTracker.markGlowing(); - original.call(encoder, texture, color); - } - - /** - * Short-circuit {@code doEntityOutline} when the toggle is on and no glowing - * entity was seen this frame. The vanilla path bails early via - * {@code levelRenderState.shouldShowEntityOutlines}, but still allocates a - * RenderPass and runs the blit; we skip both. - */ - @Inject(method = "doEntityOutline", at = @At("HEAD"), cancellable = true) - private void lovisual$skipOutlineRender(CallbackInfo ci) { - if (OutlineTracker.shouldSkipOutlineRender()) { - ci.cancel(); - } - } -} diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/render/entity/EntityRenderDispatcherMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/render/entity/EntityRenderDispatcherMixin.java index 428a856b..b80953ff 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/render/entity/EntityRenderDispatcherMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/render/entity/EntityRenderDispatcherMixin.java @@ -9,20 +9,20 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import dev.loki.lovisual.features.module.lifecycle.Modules; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; @Mixin(EntityRenderDispatcher.class) public abstract class EntityRenderDispatcherMixin { @Inject(method = "shouldRender(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/culling/Frustum;DDD)Z", at = @At("HEAD"), cancellable = true) private void lovisual$cullEntity(E entity, Frustum frustum, double camX, double camY, double camZ, CallbackInfoReturnable cir) { - FpsOptimizer fpsOptimizer = Modules.get(FpsOptimizer.class); - if (fpsOptimizer == null || !fpsOptimizer.isEntityCullingEnabled()) return; + Optimize optimize = Modules.get(Optimize.class); + if (optimize == null || !optimize.isEntityCullingEnabled()) return; Minecraft mc = Minecraft.getInstance(); if (entity == mc.player) return; - double maxDistance = fpsOptimizer.entityCullDistance(); + double maxDistance = optimize.entityCullDistance(); if (entity.distanceToSqr(camX, camY, camZ) > maxDistance * maxDistance) { cir.setReturnValue(false); return; diff --git a/mod/src/main/java/dev/loki/lovisual/mixins/render/level/particles/FireworkParticlesStarterMixin.java b/mod/src/main/java/dev/loki/lovisual/mixins/render/level/particles/FireworkParticlesStarterMixin.java index 00649cca..c54873bc 100644 --- a/mod/src/main/java/dev/loki/lovisual/mixins/render/level/particles/FireworkParticlesStarterMixin.java +++ b/mod/src/main/java/dev/loki/lovisual/mixins/render/level/particles/FireworkParticlesStarterMixin.java @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.Coerce; /** * Vanilla bug: FireworkParticles$Starter.createParticle assumes ParticleEngine.createParticle never returns null. - * Our ParticleManagerMixin returns null when a ParticleSpawnEvent is cancelled (e.g. FpsOptimizer.disable_particles). + * Our ParticleManagerMixin returns null when a ParticleSpawnEvent is cancelled (e.g. Optimize.no_particles). * This causes NPE: sparkParticle is null -> setTrail() crashes. * Fix: guard every SparkParticle setter to be null-safe. SparkParticle is a private inner class, so we use Object * with @Coerce to avoid InvalidInjectionException (expected SparkParticle vs Object). diff --git a/mod/src/main/java/dev/loki/lovisual/render/engine/postprocess/render/MenuBackgroundRenderer.java b/mod/src/main/java/dev/loki/lovisual/render/engine/postprocess/render/MenuBackgroundRenderer.java index 15e2ea72..0a0717b0 100644 --- a/mod/src/main/java/dev/loki/lovisual/render/engine/postprocess/render/MenuBackgroundRenderer.java +++ b/mod/src/main/java/dev/loki/lovisual/render/engine/postprocess/render/MenuBackgroundRenderer.java @@ -18,7 +18,7 @@ import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer; import dev.loki.lovisual.render.engine.renderer.MeshRenderer; import dev.loki.lovisual.render.engine.uniform.impl.ui.MenuBackgroundUniforms; import dev.loki.lovisual.features.module.lifecycle.Modules; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; +import dev.loki.lovisual.features.module.modules.misc.optimize.Optimize; public enum MenuBackgroundRenderer { ; @@ -36,8 +36,8 @@ public enum MenuBackgroundRenderer { public static void render(Minecraft mc, boolean aurora) { if (mc == null) return; - FpsOptimizer fpsOptimizer = Modules.get(FpsOptimizer.class); - if (fpsOptimizer != null && fpsOptimizer.isMenuBackgroundDisabled()) return; + Optimize optimize = Modules.get(Optimize.class); + if (optimize != null && optimize.isMenuBackgroundDisabled()) return; if (Renderer2D.isDeferredExtractRecording()) { deferredPending = true; deferredAurora = aurora; diff --git a/mod/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java b/mod/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java index f1b46e6d..48b1365e 100644 --- a/mod/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java +++ b/mod/src/main/java/dev/loki/lovisual/render/engine/renderer/renderer2d/fx/Blur2D.java @@ -3,8 +3,6 @@ package dev.loki.lovisual.render.engine.renderer.renderer2d.fx; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.textures.GpuSampler; import com.mojang.blaze3d.textures.GpuTextureView; -import dev.loki.lovisual.features.module.lifecycle.Modules; -import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer; import dev.loki.lovisual.render.engine.optimize.OptimizeState; import dev.loki.lovisual.render.engine.postprocess.post.PostProcessManager; import dev.loki.lovisual.render.engine.renderer.Renderer2D; @@ -43,8 +41,6 @@ public final class Blur2D { /** Main framebuffer color texture plus the shared post-process sampler. */ public static BlurSource source() { if (OptimizeState.isNoGlass()) return null; - FpsOptimizer fpsOptimizer = Modules.get(FpsOptimizer.class); - if (fpsOptimizer != null && fpsOptimizer.isGlassDisabled()) return null; Minecraft mc = Minecraft.getInstance(); if (mc == null) return null; RenderTarget fb = mc.gameRenderer.mainRenderTarget(); diff --git a/mod/src/main/resources/assets/lovisual/lang/en_us.json b/mod/src/main/resources/assets/lovisual/lang/en_us.json index a326e778..a3533214 100644 --- a/mod/src/main/resources/assets/lovisual/lang/en_us.json +++ b/mod/src/main/resources/assets/lovisual/lang/en_us.json @@ -3435,19 +3435,16 @@ "setting.chinahat.head_forward_offset": "Forward offset", "setting.chinahat.head_right_offset": "Right offset", "module.optimize.name": "Optimize", - "module.optimize.description": "Manual FPS rubilnik: while it is on — no glass/blur, lighter post-process passes, no noisy procedural sky layers, no per-frame HUD animation overhead.", + "module.optimize.description": "Manual FPS rubilnik: while it is on — no glass/blur, lighter post-process passes, no noisy procedural sky layers, no per-frame HUD animation overhead, plus particles/menu-background/bloom cuts and off-screen entity culling.", "setting.optimize.no_glass": "No glass/blur", "setting.optimize.lite_post": "Light post-process", "setting.optimize.lite_sky": "Simple sky", "setting.optimize.lean_hud": "Lean HUD", - "module.fpsoptimizer.name": "FPS Optimizer", - "module.fpsoptimizer.description": "Manual toggles to cut render cost: vanilla particles, menu background shader, glass/blur, bloom/glow, off-screen entity culling.", - "setting.fpsoptimizer.disable_particles": "Disable particles", - "setting.fpsoptimizer.disable_menu_background": "Disable menu background", - "setting.fpsoptimizer.disable_glass": "Disable glass/blur effects", - "setting.fpsoptimizer.disable_bloom": "Disable bloom/glow effects", - "setting.fpsoptimizer.entity_culling": "Cull off-screen entities", - "setting.fpsoptimizer.entity_cull_distance": "Entity cull distance", + "setting.optimize.no_particles": "Disable particles", + "setting.optimize.no_menu_background": "Disable menu background", + "setting.optimize.no_bloom": "Disable bloom/glow effects", + "setting.optimize.entity_cull": "Cull off-screen entities", + "setting.optimize.entity_cull_distance": "Entity cull distance", "setting.cosmeticdisplay.next_cosmetic.desc": "Cycles to the next model in the Model slot.", "setting.cosmeticdisplay.prev_cosmetic.desc": "Cycles to the previous model in the Model slot.", "setting.cosmeticdisplay.open_cosmetics.desc": "Opens the cosmetics editor: 3D preview with clickable body zones and per-slot model pickers.", diff --git a/mod/src/main/resources/assets/lovisual/lang/ru_ru.json b/mod/src/main/resources/assets/lovisual/lang/ru_ru.json index d58d6c7a..c8f63880 100644 --- a/mod/src/main/resources/assets/lovisual/lang/ru_ru.json +++ b/mod/src/main/resources/assets/lovisual/lang/ru_ru.json @@ -3226,19 +3226,16 @@ "setting.chinahat.head_forward_offset": "Смещение вперёд", "setting.chinahat.head_right_offset": "Смещение вправо", "module.optimize.name": "Оптимизация", - "module.optimize.description": "Ручной рубильник FPS: пока включён — без стекла/blur, с лёгкими пост-проходами, без шумных процедурных слоёв неба и без покадровых анимаций HUD.", + "module.optimize.description": "Ручной рубильник FPS: пока включён — без стекла/blur, с лёгкими пост-проходами, без шумных процедурных слоёв неба и без покадровых анимаций HUD, плюс отключение частиц/фона меню/блума и отсечение сущностей за экраном.", "setting.optimize.no_glass": "Без стекла/blur", "setting.optimize.lite_post": "Лёгкий пост-процесс", "setting.optimize.lite_sky": "Простое небо", "setting.optimize.lean_hud": "Лёгкий HUD", - "module.fpsoptimizer.name": "Оптимизация FPS", - "module.fpsoptimizer.description": "Ручные тумблеры для снижения нагрузки рендера: ванильные частицы, фоновый шейдер меню, стекло/блюр, блум/свечение, отсечение сущностей за экраном.", - "setting.fpsoptimizer.disable_particles": "Отключить частицы", - "setting.fpsoptimizer.disable_menu_background": "Отключить фон меню", - "setting.fpsoptimizer.disable_glass": "Отключить стекло/блюр", - "setting.fpsoptimizer.disable_bloom": "Отключить блум/свечение", - "setting.fpsoptimizer.entity_culling": "Скрывать сущности за экраном", - "setting.fpsoptimizer.entity_cull_distance": "Дистанция отсечения сущностей", + "setting.optimize.no_particles": "Отключить частицы", + "setting.optimize.no_menu_background": "Отключить фон меню", + "setting.optimize.no_bloom": "Отключить блум/свечение", + "setting.optimize.entity_cull": "Скрывать сущности за экраном", + "setting.optimize.entity_cull_distance": "Дистанция отсечения сущностей", "setting.cosmeticdisplay.next_cosmetic.desc": "Следующая модель в слоте «Модель».", "setting.cosmeticdisplay.prev_cosmetic.desc": "Предыдущая модель в слоте «Модель».", "setting.cosmeticdisplay.open_cosmetics.desc": "Открывает редактор косметики: 3D-превью с кликабельными зонами тела и выбором моделей по слотам.", diff --git a/mod/src/main/resources/lovisual.mixins.json b/mod/src/main/resources/lovisual.mixins.json index d9408755..252e1bd7 100644 --- a/mod/src/main/resources/lovisual.mixins.json +++ b/mod/src/main/resources/lovisual.mixins.json @@ -62,7 +62,6 @@ "optimize.stats.GlCommandEncoderStatsMixin", "optimize.stats.GlRenderPassStatsMixin", "optimize.stats.GlStateManagerStatsMixin", - "optimize.world.LevelRendererOutlineMixin", "accessors.blaze3d.GlTextureInvoker", "accessors.blaze3d.GpuDeviceAccessor", "accessors.blaze3d.ShaderLoaderDefinitionsAccessor", diff --git a/mod/src/test/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTrackerTest.java b/mod/src/test/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTrackerTest.java deleted file mode 100644 index f0f02546..00000000 --- a/mod/src/test/java/dev/loki/lovisual/features/module/modules/misc/optimize/world/OutlineTrackerTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.loki.lovisual.features.module.modules.misc.optimize.world; - -import dev.loki.lovisual.render.engine.optimize.OptimizeToggles; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Verifies {@link OutlineTracker} — per-frame state for the outline-skip - * optimization. Pure logic, no Minecraft dependencies. - */ -public final class OutlineTrackerTest { - - @AfterEach - void reset() { - OptimizeToggles.reset(); - OutlineTracker.reset(); - } - - @Test - void disabledWhenMicroOptsOff() { - OptimizeToggles.apply( - false, 0, - false, false, false, - false, 0, - false, false, false, false, false); - assertFalse(OutlineTracker.enabled()); - // Even when glowing was set this frame, shouldSkipOutlineRender returns false - // when the toggle is off — the vanilla path runs unchanged. - OutlineTracker.markGlowing(); - assertFalse(OutlineTracker.shouldSkipOutlineRender()); - } - - @Test - void skipWhenEnabledAndNoGlowing() { - OptimizeToggles.apply( - false, 0, - false, false, false, - false, 0, - false, false, false, true, false); - OutlineTracker.beginFrame(); - assertTrue(OutlineTracker.shouldSkipOutlineRender(), - "Without glowing entities, doEntityOutline should be skipped"); - } - - @Test - void doNotSkipWhenGlowingThisFrame() { - OptimizeToggles.apply( - false, 0, - false, false, false, - false, 0, - false, false, false, true, false); - OutlineTracker.beginFrame(); - OutlineTracker.markGlowing(); - assertFalse(OutlineTracker.shouldSkipOutlineRender(), - "Glowing entity this frame — should not skip doEntityOutline"); - assertTrue(OutlineTracker.glowingThisFrame()); - } - - @Test - void beginFrameClearsGlowingFlag() { - OptimizeToggles.apply( - false, 0, - false, false, false, - false, 0, - false, false, false, true, false); - OutlineTracker.markGlowing(); - assertTrue(OutlineTracker.glowingThisFrame()); - OutlineTracker.beginFrame(); - assertFalse(OutlineTracker.glowingThisFrame()); - } - - @Test - void dirtyStartsTrue() { - OptimizeToggles.reset(); - OutlineTracker.reset(); - assertTrue(OutlineTracker.dirtyRaw()); - assertTrue(OutlineTracker.needsClear()); - } - - @Test - void markClearedClearsDirty() { - OutlineTracker.markCleared(); - assertFalse(OutlineTracker.dirtyRaw()); - } - - @Test - void resetRestoresDefaults() { - OutlineTracker.markGlowing(); - OutlineTracker.markCleared(); - OutlineTracker.reset(); - assertFalse(OutlineTracker.glowingThisFrameRaw()); - assertTrue(OutlineTracker.dirtyRaw()); - } - - @Test - void needsClearFollowsDirtyAndGlowing() { - // dirty=true (default) → needsClear true - OutlineTracker.markCleared(); - // dirty=false, glowing=false → needsClear false (we'd need a glow to trigger a clear) - assertFalse(OutlineTracker.needsClear()); - OutlineTracker.markGlowing(); - // dirty=true again, glowing=true → needsClear true - assertTrue(OutlineTracker.needsClear()); - } -}