125 lines
12 KiB
Markdown
125 lines
12 KiB
Markdown
# Слой оптимизации (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.<key>` (аналог `-Dzakoopt.<key>`).
|
||
|
||
## Дополнительные 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), авто-деградация без явного включения пользователем.
|