refactor(mod): merge FPS Optimizer cuts into Optimize, drop OutlineTracker
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.
This commit is contained in:
parent
645896ca4b
commit
ecde09560d
18 changed files with 114 additions and 393 deletions
|
|
@ -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.<key>`.
|
||||
|
||||
## Дополнительные GL-оптимизации (наши, гипотезы)
|
||||
Проверяем замером на 26.2 до включения по умолчанию.
|
||||
## Дополнительные GL-оптимизации (наши)
|
||||
Реализуем за default-off тумблерами (`-Dlovisual.opt.<key>` или поле в модуле); включать или нет решает пользователь.
|
||||
Автомерялок не ждём — если пункт безопасен (не меняет картинку) и дешёв, он имеет смысл и при выключенном состоянии,
|
||||
тумблер даёт путь включить.
|
||||
- **Кэш состояния 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), авто-деградация без явного включения пользователем.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<Integer> entityCullDistance =
|
||||
visibleWhen(num("entity_cull_distance", 64, 8, 256), entityCull::get);
|
||||
|
||||
// --- Stage 2: frame budget + profiling ---
|
||||
private final EnumValue<FrameBudget.Mode> 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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Integer> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*
|
||||
* <p>Three hooks:
|
||||
* <ul>
|
||||
* <li>{@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.</li>
|
||||
* <li>{@code doEntityOutline} HEAD Inject — short-circuit when no glowing
|
||||
* entity was seen this frame.</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* <p>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<Void> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <E extends Entity> void lovisual$cullEntity(E entity, Frustum frustum, double camX, double camY, double camZ, CallbackInfoReturnable<Boolean> 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;
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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-превью с кликабельными зонами тела и выбором моделей по слотам.",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue