12 KiB
Слой оптимизации (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)
- 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 делаем заново только если замер покажет пользу (см. ниже). - Сущности: AnimFreeze, Entity LOD, Player LOD, Item LOD, Item Bounds Cache, Skin Atlas, Cached Name, CachedStack.
- Частицы: Particle LOD, Parallel Particles, Parallel Vertices, Particle Light Cache, Particle Physics Skip, Bubble Column Cache.
- Блоки: Spawner Cull, Spawner Tick Cache, Spawner Replay, Moving Block Cache, Piston Biome Cache, Sign Cache, Block Entity Cache (CachedLight, CachedValidity).
- HUD: HUD Cache, HUD Worth, GUI Animated Items, GUI Item Atlas, совместимость с ImmediatelyFast Atlas.
- Текст: Bidi Cache, Prepared Text Cache, StableText.
- Микро: MemoryStack Cache, Sign Lookup Cache, Bubble Fluid Cache.
Поток кадра как у ZakoOpt: FrameFence.endFrame -> SkinAtlas.endFrame -> AnimFreeze.frame++
-> OptConfig.refresh() (флаги резолвятся один раз за кадр в примитивные поля).
Что делаем лучше, чем в ZakoOpt
- Единый бюджет кадра. Один объект раздаёт лимиты LOD, частицам и нашим эффектам (ProjectileTrails, ElytraTrails, DeathEffects, Blizzard, GodRays): дистанция, число точек и вершин на кадр. Режим «Выкл / Мягко / Агрессивно», по умолчанию «Выкл», целевой FPS задаёт пользователь. Это не авто-деградация, прежнее решение (Фаза 9) не нарушается.
- Профилировщик секций (сущности, частицы, HUD, эффекты, текст) в debug-оверлее.
- Один конфиг в
Optimize, без отдельного экрана ZakoOpt. - Авто-порог параллельных частиц по замеру (у ZakoOpt порог 4+ ядра взят из одного прогона).
- Кэш статичных HUD-виджетов в текстуру (перерисовка только при изменении).
- Флаги для бенчмарков:
-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 и др.) не переписываем, новый код подключается рядом.
Этапы
core/+RenderBackendOps+ порт сущностей, частиц, HUD, текста, блоков (бэкенд-независимые части). Тесты, сборка.- Бюджет кадра, профилировщик,
-Dlovisual.opt.*. gl/: shared FBO, FrameFence, lazy clear, TBO cache, SkinAtlas через GL; затем новые GL-идеи из раздела ниже.vulkan/:vkCmdCopyImage, затемVkPipelineCache, затем остальное по замерам.- Бенчмарки до и после, отчёт цифр в этот документ.
Риски
- Миксины 26.2 проверяются только компиляцией и запуском владельцем (
./gradlew runClient), вживую я не смотрю. - Совместимость с ImmediatelyFast и Iris в 26.2 не проверена.
- Лимиты размера файлов проекта потребуют дробить крупные классы ZakoOpt (фасад + package-private хелперы).
- Выигрыш по Vulkan не гарантирован, пока нет замеров.
Вне объёма
Ускорение ландшафта (замена Sodium), авто-деградация без явного включения пользователем.