diff --git a/mod/docs/optimize-layer-design.md b/mod/docs/optimize-layer-design.md new file mode 100644 index 00000000..9fcb8ca8 --- /dev/null +++ b/mod/docs/optimize-layer-design.md @@ -0,0 +1,125 @@ +# Слой оптимизации (Optimize): перенос ZakoOpt, улучшения и Vulkan + +Статус: черновик на согласование (2026-10-09). Цель MC: 26.2. + +## Решение +Все оптимизации из ZakoOpt (слои `common`, `26.x`, `26.2`) переносятся в наш модуль `Optimize`, а не живут отдельным +модом. Sodium остаётся необязательным (`suggests`), выбор GL/Vulkan делается по бэкенду Blaze3D, а не по наличию Sodium. + +Лицензия: ZakoOpt под LGPL-3.0, LoVisual под GPLv3, совместимо. Сохраняем указание автора (Zako, канал +https://t.me/StarikZako) в заголовках перенесённых файлов и в `NOTICE`. + +## Две независимые оси +| Ось | Чем определяется | Что меняет | +|---|---|---| +| Бэкенд | `RenderSystem.getDevice() instanceof GlDevice`, иначе Vulkan | какая реализация `gl/` или `vulkan/` включается | +| Sodium | `FabricLoader.isModLoaded("sodium")` | только быстрые пути записи вершин (`VertexBufferWriter`) | + +Без Sodium ландшафт остаётся ванильным. Ускорение ландшафта вне этого плана (возможный отдельный проект: форк Sodium). + +## Структура +Пакет `features/module/modules/misc/optimize/`. Правила проекта: ≤4 `.java` на папку, ≤250 строк на файл, +JUnit на чистую логику, helper-классы вне `mixins.*`, миксины в `mixins/optimize/`. + +``` +optimize/ + Optimize.java модуль и настройки (ключи совместимы с .lvcfg) + core/ бэкенд-независимая логика: LOD-пороги, LRU, кэши, бюджет кадра + entity/ particle/ hud/ block/ text/ перенос из ZakoOpt по категориям + gl/ GL-реализации: shared FBO, GpuFence, lazy clear, glCopyImageSubData + vulkan/ Vulkan-реализации: vkCmdCopyImage, VkPipelineCache, кэш состояния + profile/ профилировщик секций кадра +``` + +Выбор реализации один раз при старте через интерфейс `RenderBackendOps` (копирование текстур, фенсы, кольцо буфера). + +## Что переносим (всё из ZakoOpt) +1. **GL/рендер-пайплайн (то, что реально есть в слоях 26.x/26.2):** Shared Frame Fence (`FrameFence`, `RealFence`), Lazy Clear, + FBO Sharing (`SharedDepthFbos`, `FrameBufferCacheMixin`, `GlBufferCloseMixin`), TBO Cache, RenderType Cache, + VertexConsumer Cache, Entity Sort Skip. Immediate Ring Buffer и Ring Auto-Tune существуют только в слоях 1.21.x, в 26.x их нет + (ванильные буферы там другие), поэтому в порт не входят; ring делаем заново только если замер покажет пользу (см. ниже). +2. **Сущности:** AnimFreeze, Entity LOD, Player LOD, Item LOD, Item Bounds Cache, Skin Atlas, Cached Name, CachedStack. +3. **Частицы:** Particle LOD, Parallel Particles, Parallel Vertices, Particle Light Cache, Particle Physics Skip, + Bubble Column Cache. +4. **Блоки:** Spawner Cull, Spawner Tick Cache, Spawner Replay, Moving Block Cache, Piston Biome Cache, Sign Cache, + Block Entity Cache (CachedLight, CachedValidity). +5. **HUD:** HUD Cache, HUD Worth, GUI Animated Items, GUI Item Atlas, совместимость с ImmediatelyFast Atlas. +6. **Текст:** Bidi Cache, Prepared Text Cache, StableText. +7. **Микро:** MemoryStack Cache, Sign Lookup Cache, Bubble Fluid Cache. + +Поток кадра как у ZakoOpt: `FrameFence.endFrame` -> `SkinAtlas.endFrame` -> `AnimFreeze.frame++` +-> `OptConfig.refresh()` (флаги резолвятся один раз за кадр в примитивные поля). + +## Что делаем лучше, чем в ZakoOpt +1. **Единый бюджет кадра.** Один объект раздаёт лимиты LOD, частицам и нашим эффектам (ProjectileTrails, ElytraTrails, + DeathEffects, Blizzard, GodRays): дистанция, число точек и вершин на кадр. Режим «Выкл / Мягко / Агрессивно», по умолчанию + «Выкл», целевой FPS задаёт пользователь. Это не авто-деградация, прежнее решение (Фаза 9) не нарушается. +2. **Профилировщик секций** (сущности, частицы, HUD, эффекты, текст) в debug-оверлее. +3. **Один конфиг** в `Optimize`, без отдельного экрана ZakoOpt. +4. **Авто-порог параллельных частиц** по замеру (у ZakoOpt порог 4+ ядра взят из одного прогона). +5. **Кэш статичных HUD-виджетов в текстуру** (перерисовка только при изменении). +6. Флаги для бенчмарков: `-Dlovisual.opt.` (аналог `-Dzakoopt.`). + +## Дополнительные GL-оптимизации (наши, гипотезы) +Проверяем замером на 26.2 до включения по умолчанию. +- **Кэш состояния GL** шире, чем у ZakoOpt: пропуск повторных `glUseProgram`, `glBindVertexArray`, `glBindTexture`, `glBlendFunc`, + `glDepthMask` (LoVisual уже имеет `GlStateManagerMixin`, расширяем). +- **Сортировка draw по pipeline и текстуре** внутри слоя там, где порядок не влияет на результат (наши квады `Renderer2D`, ленты trails). +- **Persistent mapped ring** для динамических вершин наших эффектов и HUD (`GL_ARB_buffer_storage`), если профиль покажет стоимость + `glBufferSubData`/map. Ring Auto-Tune из ZakoOpt берём как идею размера. +- **Батчинг HUD:** меньше смен пайплайна между виджетами (общий batch для `Renderer2D.COLOR`/`TEXTURE`). +- **Инвалидация глубины/stencil** (`glInvalidateFramebuffer`) после проходов, чьё содержимое не читается: выгодно на тайловых и мобильных + драйверах, на десктопе эффект мал. +- **Пропуск лишних clear** наших пост-процесс целей (`MenuBackgroundRenderer`, блюр), когда цель целиком перезаписывается. +- **Меньше FBO-переключений** в post-process цепочках (объединение проходов, общий ping-pong). +- **Uniform-блоки:** один обновляемый UBO на кадр вместо множества мелких обновлений (в LoVisual есть `DynamicUniformStorageMixin`). + +## Разбор GL-части ZakoOpt для 26.x/26.2 (что реально делает код) +Файлы в `/storage/project/jvm/ZakoOpt/zakoopt/`: +- `26.x/.../gl/FrameFence.java` + `26.2/.../gl/RealFence.java`: один общий `GpuFence` на кадр вместо фенса на каждый запрос; + если его ждут до конца кадра, реальный фенс создаётся сразу, как в ваниле. Подмена через `GlCommandEncoderMixin.createFence`. +- `26.x/.../mixin/gl/GlCommandEncoderClearMixin.java` (Lazy Clear): после `clearColorAndDepthTextures` не делается лишний + `_glBindFramebuffer(.., 0)`, следующий проход всё равно привязывает свою цель. +- `26.2/.../gl/SharedDepthFbos.java` + `FrameBufferCacheMixin`, `GlBufferCloseMixin`, `GlCommandEncoderDepthClearMixin`: очистка + глубины через кэшированный FBO и `glClearNamedFramebufferfv` (DSA), без attach/detach. Нужен GL 4.5 или `ARB_direct_state_access`. +- `common/.../gl/TexBufferCache.java` + `GlCommandEncoderTexBufferMixin`: не перепривязывает буфер к texel-buffer текстуре, если + связка (формат, буфер) не менялась. +- `26.x/.../mixin/gl/RenderTypesMixin.java`: кэш `entityTranslucent` по `Identifier` вместо `Util.memoize` с `Pair`. +- `common/.../gl/RingAutoTune.java`: логика подбора размера кольца (в 26.x самого ring нет). +- Жёстко привязаны к GL (на Vulkan нужен свой путь или отключение): `SkinAtlas` (`glCopyImageSubData`), `SharedDepthFbos`, + `RealFence`, TBO-миксины. + +Не проверено: наш собственный GL-код (`render/engine/rhi/backend/gl`, `Renderer2D`, `GlStateManagerMixin`) почти не читался. +Перед этапом 3 нужен профиль HUD: число draw-вызовов и смен состояния на кадр, тогда решаем, какие пункты из раздела +«Дополнительные GL-оптимизации» нужны. + +## 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-очереди | риск, делаем последним | низкий | + +Существующие Vulkan-миксины LoVisual (`VulkanDeviceMixin` и др.) не переписываем, новый код подключается рядом. + +## Этапы +1. `core/` + `RenderBackendOps` + порт сущностей, частиц, HUD, текста, блоков (бэкенд-независимые части). Тесты, сборка. +2. Бюджет кадра, профилировщик, `-Dlovisual.opt.*`. +3. `gl/`: shared FBO, FrameFence, lazy clear, TBO cache, SkinAtlas через GL; затем новые GL-идеи из раздела ниже. +4. `vulkan/`: `vkCmdCopyImage`, затем `VkPipelineCache`, затем остальное по замерам. +5. Бенчмарки до и после, отчёт цифр в этот документ. + +## Риски +- Миксины 26.2 проверяются только компиляцией и запуском владельцем (`./gradlew runClient`), вживую я не смотрю. +- Совместимость с ImmediatelyFast и Iris в 26.2 не проверена. +- Лимиты размера файлов проекта потребуют дробить крупные классы ZakoOpt (фасад + package-private хелперы). +- Выигрыш по Vulkan не гарантирован, пока нет замеров. + +## Вне объёма +Ускорение ландшафта (замена Sodium), авто-деградация без явного включения пользователем.