LoVisual/mod/docs/optimize-layer-design.md
loki5512344 d5f17eb0ac
refactor(mod): dispatch RenderBackendOps.current() by active backend (Stage 4 kickoff)
Stage 4 Vulkan groundwork. Before this, RenderBackendOps.current() unconditionally
returned GlRenderBackendOps.INSTANCE with a 'Stage 3-prep' comment. Under Vulkan
the GL impl is still harmless (its methods all short-circuit on OptimizeToggles
and the GL mixins never fire), but the interface lied about the active backend.

- New misc/optimize/vulkan/VulkanRenderBackendOps: singleton, no-op stub, all
  *Supported() return false, cache/map methods do nothing. This is the skeleton
  the Stage 4 items (persistent VkPipelineCache, VulkanRenderPass state cache)
  will grow into.
- RenderBackendOps.current() inspects RenderSystem.getDevice().getClass().getName()
  and returns VulkanRenderBackendOps for com.mojang.blaze3d.vulkan.* devices,
  GlRenderBackendOps otherwise. Class-name check (not instanceof) so this method
  itself does not force linking of GlDevice/VulkanDevice.
- Wrapped getDevice() in try/catch: unit tests call current() before RenderSystem
  has a device and previously took the GL path; now they still do via the fallback.

Doc: Stage 4 kickoff entry in the changelog.
2026-10-12 22:10:10 +02:00

32 KiB
Raw Blame History

Слой оптимизации (Optimize): перенос референсных оптимизаций, улучшения и Vulkan

Статус: Этапы 1-3 готовы (2026-10-12). Этап 4 (Vulkan) — стартуем с универсально-полезных пунктов. Цель MC: 26.2.

История изменений

  • 2026-10-12: Этап 4 старт — RenderBackendOps.current() теперь определяет активный бэкенд через RenderSystem.getDevice().getClass().getName() (class-name, не instanceof, чтобы не форсировать линковку GlDevice/VulkanDevice); добавлен misc/optimize/vulkan/VulkanRenderBackendOps — no-op-заглушка со всеми *Supported() == false. Под GL всё работает как раньше; под Vulkan mixins из mixins/optimize/gl/* не загружаются, и теперь хотя бы интерфейс честно возвращает правильный бэкенд. Живые Vulkan-пути (VkPipelineCache, state cache) — следующий шаг.
  • 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.
  • 2026-10-10: Этап 2 завершён — FrameBudget (mode + target FPS + per-subsystem caps), FrameProfiler (5 секций EMA-таймингов), OptimizePropertyBridge (-Dlovisual.opt.* overrides), OptimizeDebugHudMixin (F3 overlay). 22 новых теста. Optimize.java расширен тумблерами budget_mode / target_fps / show_profiler.
  • 2026-10-10: Этап 3 (частично) — RenderBackendOps интерфейс + GlRenderBackendOps реализация (lazy clear, TBO cache, RenderTypes cache). Три mixin'а зарегистрированы в lovisual.mixins.json: RenderTypesMixin, GlCommandEncoderClearMixin, GlCommandEncoderTexBufferMixin. SkinAtlas/AnimFreeze/HudCache/FrameFence оставлены на поздний Этап 3.

Решение

Все оптимизации из референса (слои common, 26.x, 26.2) переносятся в наш модуль Optimize, а не живут отдельным модом. Sodium остаётся необязательным (suggests), выбор GL/Vulkan делается по бэкенду Blaze3D, а не по наличию Sodium.

Две независимые оси

Ось Чем определяется Что меняет
Бэкенд 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/    перенос из референса по категориям
  gl/                GL-реализации: shared FBO, GpuFence, lazy clear, glCopyImageSubData
  vulkan/            Vulkan-реализации: vkCmdCopyImage, VkPipelineCache, кэш состояния
  profile/           профилировщик секций кадра

Выбор реализации один раз при старте через интерфейс RenderBackendOps (копирование текстур, фенсы, кольцо буфера).

Что переносим (всё из референса)

  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.

Поток кадра как в референсе: FrameFence.endFrame -> SkinAtlas.endFrame -> AnimFreeze.frame++ -> OptConfig.refresh() (флаги резолвятся один раз за кадр в примитивные поля).

Что делаем лучше, чем в референсе

  1. Единый бюджет кадра. Один объект раздаёт лимиты LOD, частицам и нашим эффектам (ProjectileTrails, ElytraTrails, DeathEffects, Blizzard, GodRays): дистанция, число точек и вершин на кадр. Режим «Выкл / Мягко / Агрессивно», по умолчанию «Выкл», целевой FPS задаёт пользователь. Это не авто-деградация, прежнее решение (Фаза 9) не нарушается.
  2. Профилировщик секций (сущности, частицы, HUD, эффекты, текст) в debug-оверлее.
  3. Один конфиг в Optimize, без отдельного экрана референса.
  4. Авто-порог параллельных частиц по замеру (в референсе порог 4+ ядра взят из одного прогона). → Упрощено 2026-10-12: сделано как в референсе — CORES >= 4 + -Dlovisual.opt.parallel_particles=auto|true|false. Модуль Optimize.parallel_particles (default true = AUTO) и Workers.shouldParallelParticles() — plumbing готов, mixin-точка на vanilla extraction приложится позже (26.2 QuadParticleGroup/SpriteBillboardParticle).
  5. Кэш статичных HUD-виджетов в текстуру (перерисовка только при изменении).
  6. Флаги для бенчмарков: -Dlovisual.opt.<key>.

Дополнительные GL-оптимизации (наши)

Реализуем за default-off тумблерами (-Dlovisual.opt.<key> или поле в модуле); включать или нет решает пользователь. Автомерялок не ждём — если пункт безопасен (не меняет картинку) и дешёв, он имеет смысл и при выключенном состоянии, тумблер даёт путь включить.

  • ✅ Кэш состояния GL шире, чем в референсе: пропуск повторных glUseProgram, glBindVertexArray, glBindTexture, glBlendFunc, glDepthMask (LoVisual уже имеет GlStateManagerMixin, расширяем). Готово 2026-10-12, частичнo: glUseProgram и glBindVertexArray — по байткоду com.mojang.blaze3d.opengl.GlStateManager в 26.2 это единственный два «сырых» бинда без ванильного кэша; остальные (glBindTexture/glBlendFunc/glDepthMask) уже идут через ванильные TEXTURES/BLEND/DEPTH state-массивы, дублировать их кэш — только хуже. render/engine/optimize/gl/state/GlStateCache
    • миксин mixins/optimize/gl/state/GlStateManagerCacheMixin, гейт micro_opts, 6 тестов, invalidация на glDeleteProgram и на Optimize.onDisable.
  • Сортировка draw по pipeline и текстуре внутри слоя там, где порядок не влияет на результат (наши квады Renderer2D, ленты trails).
  • Persistent mapped ring для динамических вершин наших эффектов и HUD (GL_ARB_buffer_storage), если профиль покажет стоимость glBufferSubData/map. Ring Auto-Tune из референса берём как идею размера.
  • Батчинг HUD: меньше смен пайплайна между виджетами (общий batch для Renderer2D.COLOR/TEXTURE).
  • Инвалидация глубины/stencil (glInvalidateFramebuffer) после проходов, чьё содержимое не читается: выгодно на тайловых и мобильных драйверах, на десктопе эффект мал.
  • Пропуск лишних clear наших пост-процесс целей (MenuBackgroundRenderer, блюр), когда цель целиком перезаписывается.
  • Меньше FBO-переключений в post-process цепочках (объединение проходов, общий ping-pong).
  • Uniform-блоки: один обновляемый UBO на кадр вместо множества мелких обновлений (в LoVisual есть DynamicUniformStorageMixin).

Разбор GL-части референса для 26.x/26.2 (что реально делает код)

Файлы референса:

  • 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» не делаем — Этап 3 уже закрыт, счётчики DrawStats в F3 остались как справочный инструмент.

Vulkan

Универсально-полезное (не зависит от замеров) делаем сразу, остальное — по мере надобности.

Пункт Зачем Статус
Персистентный 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 и др.) не переписываем, новый код подключается рядом.

Что нового в референсе 1.0.2-1.0.3 (сверено 2026-10-12)

Референс обновился с 1.0.1 до 1.0.3 (+166 файлов). Что важно для нашего плана:

  • FrameFence/RealFence для 26.2 не нужен. Комментарий в 26.1.2/gl/GlCommandEncoderMixin: «26.1 ещё делает GL-sync на каждый fence; с 26.2 ваниль делает один fence на submit и ждёт его по индексу submit'а». Хук createFence переехал из 26.x в модуль 26.1.2, в 26.2 его нет; в changelog прямо сказано, что опция «Один fence на кадр» оставлена только для 26.1.2, с 26.2 это делает игра. Плюс история краша Xaero's World Map («Cannot wait on a fence for the current submit») при общем fence — риск, который ваниль уже закрыла. => Пункт «FrameFence/RealFence» из Этапа 3 вычеркиваем; остаётся только хук конца кадра (MinecraftFrameMixin: SkinAtlas.endFrame, AnimFreeze.frame++, refresh() конфига).
  • Счётчики draw-вызовов (DrawStats, common/gl/DrawStats.java, 75 строк) — ровно наше заявленное предусловие «перед Этапом 3 нужен профиль HUD: число draw-вызовов и смен состояния на кадр». Считает draws/flush'и слоёв/FBO-bind'ы, средние за секунду, топ-5 слоёв, рисует только пока открыт F3. Дёшево, даёт данные для раздела «Дополнительные GL-оптимизации». => Включить в Этап 3 первым пунктом.
  • Parallel Models (крупное новое, Impact HIGH). 26.2/ModelFeatureRendererMixin перехватывает buildGroup и собирает группу submit'ов на нескольких ядрах (ParallelModelHook + ParallelModels + два invoker'а). Заявлено +20-50% FPS в толпе бронированных мобов, отдельная опция. Отключается при <4 ядрах и при активном шейдерпаке Iris (!(IRIS && IrisApi…isShaderPackInUse()) — Iris собирает расширенные атрибуты из вершин, отданных по одной). Зависит от AnimFreeze.freezes() (часть, которую нельзя реплеить) — ставить после AnimFreeze.
  • ItemLod получил точный таргет в 26.2: entity/ItemFeatureRendererMixin — @WrapOperation на Submit.quads() в prepareMainSubmit/prepareOutlineSubmit/prepareFoilSubmit, плоские квады дальше ItemLod.distanceSq().
  • Выброшенные предметы (новое): no_item_sort (пропуск сортировки item_entity_translucent_cull, по умолчанию вкл, до +40% FPS на кучах), item_single_copy (стак одним предметом), ItemBounds (кэш AABB модели предмета, IdentityHashMap с полной очисткой >4096 — render state пересоздаётся каждый кадр, его собственный кэш умирает вместе с ним). BufferSourceMixin+RenderTypeAccessor живут в модуле 26.1.2, в 26.x их нет — перед переносом проверить, есть ли в 26.2 MultiBufferSource.BufferSource.endBatch(RenderType, BufferBuilder) и RenderType.sortOnUpload().
  • Фиксы HudCache, которые надо взять сразу (иначе воспроизведём баги): главный target снимается в beginFrame (main = gameRenderer.mainRenderTarget()) и сравнение идёт с ним, а не с «текущим» — иначе редиректим чужой target (Xaero подменяет свой, пока рисует); двойной троттлинг GuiItemRefresh+HudCache приводил к пропавшим зачарованным предметам хотбара (фикс: пропускать свой троттлинг, когда HudCache.active()); composite — premultiplied + write mask 7 (RGB), depth clear 0 (reversed depth в 26.x); кэш выключается при чужих HUD-кэшах (axolotlclient, exordium, gnetum) и под VulkanMod; интервал = рефреш-рейт монитора (MonitorRate, GLFW-опрос 1 раз в секунду).
  • Правило утечек: кэши с нативной памятью/GL-объектами чистить и на перезагрузку ресурсов (F3+T), и на смену мира. Фикс 1.0.3: BubbleColumnUpParticleMixin держал ClientLevel в статике через сильную ссылку — теперь WeakReference
    • очистка карты при смене уровня; MovingBlockCache/ParallelModels чинили утечки native/model.
  • Порог параллельных частиц: план п.4 («авто-порог по замеру») упрощаем до того, что сделано в референсе — CORES >= 4 + -D…=auto|true|false; 6+ ядер оказалось догадкой (замер на 4 ядрах дал +12%), вернули к 4. Workers получил stream() — публикация задач по мере готовности, рабочие берут их сразу, пока готовится остальное.
  • MixinPlugin — условная регистрация миксинов (Sodium/Vulkan/Lunar, shouldApplyMixin + getMixins), а тумблеры в UI прячутся, если миксин не построен/не применяется (available() → MixinPlugin::applies). У нас уже есть mixins/LoVisualMixinPlugin — расширить для Vulkan (Этап 4) и для «миксин есть не всегда».
  • Не берём: Lunar multi-draw (лаунчер Lunar, не наш случай); blockEntityEarlyCull перенесён только в 1.21.9+, в 26.x его нет — при случае проверить, применим ли к 26.2; 26.3 поддержка и 26.3/renames.txt пригодятся при апгрейде версии (пакет com.mojang.blaze3d → com.mojang.renderpearl).

Этапы

  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-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, не переносим.
    4. ✅ AnimFreeze кластер — AnimFreeze (capture раз в тик, реплей в вершины), EntityAnimHolderMixin, AnimHolder, метод freezes(state, pose) (часть, построенная каждый кадр в другом месте, не реплеим — это и чинит рябь брони), RawVertices/SodiumVertices. Готово 2026-10-12 — AnimFreeze.configure(enabled, distance) (свой холдер, не OptimizeToggles), тумблеры entity_anim_freeze (по умолчанию выкл) + entity_anim_distance (16), frame++ в MinecraftMixin.renderFrame HEAD, вызов из ModelFeatureRendererMixin.prepareModel (@WrapOperation на Model.renderToBuffer), инфраструктура сырых вершин из п.7 (RawBuffer/BufferBuilderRawMixin/RawVertices/SodiumVertices/VertexConsumerUtilsMixin), 3 теста. TypeBuilders не выделялся — свёрнут в приватные хелперы AnimFreeze.
    5. ✅ ItemLod — ItemLod + ItemFeatureRendererMixin (@WrapOperation на Submit.quads() в prepareMain/Outline/FoilSubmit) + ItemBounds. Готово 2026-10-12 — ItemLod/ItemBounds в render/engine/optimize/entity/ (в entity/ теперь ровно 4 файла — лимит исчерпан), LOD-хук добавлен в существующий ItemFeatureRendererMixin, ItemStackRenderStateMixin+ItemLayerAccessor в mixins/render/item/bounds/, тумблеры item_lod/item_lod_distance (8)/item_bounds, 9 тестов.
    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) — заблокировано не замерами, а 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/: начинаем с универсально-полезного — персистентный VkPipelineCache, кэш состояния в VulkanRenderPass. Барьеры/staging/async — по мере чтения Blaze3D-кода, не по замерам. Готово 2026-10-12: не начат → старт, первый пункт.
  5. (опционально, вне плана) Отчёт цифр «до/после» на реальной сборке — факультативно, не блокирует Этапы 3-4.

Оценки для Этапов 3-4 (после Stage 1-2)

  • Этап 3: закрыт, кроме п.9 (заблокирован API 26.2).
  • Этап 4: два универсально-полезных пункта (VkPipelineCache, state cache) — по 1 сессии каждый; барьеры/staging/async — по ходу чтения Blaze3D.

Риски

  • Миксины 26.2 проверяются компиляцией и юнитами; живой запуск (./gradlew runClient) — на стороне того, у есть GUI-машина, но это не гейт: код слияем, default-off тумблеры снижают риск.
  • Совместимость с ImmediatelyFast и Iris в 26.2 не проверена in-code; держим дефолты консервативными.
  • Лимиты размера файлов проекта требуют дробить крупные классы референса (фасад + package-private хелперы) — соблюдается.
  • Выигрыш по Vulkan подтверждаем чтением com.mojang.blaze3d.vulkan, а не замерами; если пункт окажется вредным — default-off.

Вне объёма

Ускорение ландшафта (замена Sodium), авто-деградация без явного включения пользователем.