Class MdiGeometryAtlas

java.lang.Object
com.hbm_m.client.render.MdiGeometryAtlas

public final class MdiGeometryAtlas extends Object
Shared GPU geometry atlas backing the MdiBatchCoordinator.

Holds:

  • One vertex VBO (GL_ARRAY_BUFFER) containing the concatenated per-part vertex bytes (pos vec3 / normal vec3 / uv vec2 / int bone_id, stride 36).
  • One index EBO (GL_ELEMENT_ARRAY_BUFFER) with the concatenated per-part GL_UNSIGNED_INT indices. Per-part draw commands use baseVertex (added to each element index) and a byte offset into the EBO for glDrawElementsInstancedBaseVertexBaseInstance; для DrawElementsIndirectCommand.firstIndex в буфере indirect нужно то же смещение в элементах (байты / 4). Локальные индексы частей — как есть (0..N-1 на часть).
  • One instance VBO with the unsliced 30-float instance layout (loc 4..12 with divisor 1), large enough to hold the sum of all per-renderer MAX_INSTANCES budgets for a single frame.
  • One indirect buffer for the MdiBatchCoordinator to stream commands into.
  • One VAO with all of the above pre-bound.

Geometry registration is lazy and growable: each InstancedStaticPartRenderer that first becomes MDI-eligible calls registerGeometryIfAbsent(com.hbm_m.client.render.InstancedStaticPartRenderer, java.nio.ByteBuffer, java.nio.IntBuffer, int) with a copy of its vertex bytes + index stream. When the existing vertex/index buffers don't have room, we reallocate at double capacity and re-upload all known parts. This is rare (happens only on first-frame growth) and amortised across the session.

Sliced renderers (useSlicedLight=true) are NOT supported here today — they use a different attribute layout and a different shader. Adding a second atlas + a second MDI dispatch later is mechanical, but out of scope for the initial integration.

  • Method Details

    • getOrCreate

      public static MdiGeometryAtlas getOrCreate()
    • resetForResourceLifecycle

      public static void resetForResourceLifecycle()
      Полный сброс атласа при F3+T / disconnect. Только render-thread.

      Без этого geometryByRenderer бессрочно держит старые InstancedStaticPartRenderer как ключи: после clearCaches() на них больше нет ссылок из BER, но Map не даёт их собрать GC и накапливает вторую/третью копию той же геометрии для новых инстансов рендерера — отсюда «cog рисуется как base химзавода», дыры в полигонах и полный отказ MDI до перезахода (слоты/порядок repack расходятся с ожиданиями).

    • isReady

      public boolean isReady()
    • getVaoId

      public int getVaoId()
    • getIndirectBufferId

      public int getIndirectBufferId()
    • getIndirectCommandBufferCapBytes

      public long getIndirectCommandBufferCapBytes()
      Ёмкость GL40.GL_DRAW_INDIRECT_BUFFER (байты); для orphan перед записью команд.
    • getInstanceFloatsPerInstance

      public int getInstanceFloatsPerInstance()
    • getInstanceFadeFloatOffset

      public int getInstanceFadeFloatOffset()
    • getRegisteredGeometryCount

      public int getRegisteredGeometryCount()
      Только для диагностики MDI: число зарегистрированных частей в атласе.
    • enableVertexAttribArraysOnBoundVao

      public void enableVertexAttribArraysOnBoundVao()
      Включает vertex attrib arrays 0..11 на уже привязанном vaoId. После ShaderInstance#apply() / Embeddium chunk-батчей часть массивов может оказаться отключённой; без этого MDI рисует только подмножество атрибутов (типично «видна только base», створки/cogs — нет).
    • acceptsInstanceDataSize

      public boolean acceptsInstanceDataSize(int floatsPerInstance)
    • getCurrentSlot

      public MdiGeometryAtlas.Slot getCurrentSlot(InstancedStaticPartRenderer renderer)
      Текущий MdiGeometryAtlas.Slot для уже зарегистрированного рендерера после любого repackGeometryAndRefreshSlots() (рост VBO/EBO). Под MdiBatchCoordinator.dispatch() нужен именно он: submit копирует смещения в Pending в порядке flushInstancedBatches, а между двумя flush соседних типов машин один registerGeometryIfAbsent может вызвать repack и пересчитать GeoRecord#slot у всех записей — старые значения в Pending тогда указывают в пустоту/чужую геометрию (невидимые двери/сборка, «дырявая» хим установка).
    • evictRendererIfRegistered

      public static void evictRendererIfRegistered(InstancedStaticPartRenderer renderer)
      Удаляет геометрию рендерера из атласа при его InstancedStaticPartRenderer.cleanup() до полного resetForResourceLifecycle(). Иначе в geometryByRenderer остаются «зомби»-ключи (cleanup уже освободил retained-буферы, а GeoRecord продолжает участвовать в repack), что ломает порядок/смещения MDI без срабатывания дрейфа слотов в Pending.
    • registerGeometryIfAbsent

      public MdiGeometryAtlas.Slot registerGeometryIfAbsent(InstancedStaticPartRenderer renderer, ByteBuffer vertexBytes, IntBuffer indices, int indexCount)
      Lazy-register a renderer's geometry. The first call uploads vertex+index bytes into the atlas; subsequent calls return the cached slot.

      Atlas growth: if the new geometry doesn't fit in the current VBO/EBO capacity, both buffers are reallocated at next-power-of-two size and ALL known parts are re-uploaded. This is rare (once per part per session); not on the hot path.

    • ensureInstanceCapacity

      public boolean ensureInstanceCapacity(int instances)
      Ensure the instance VBO can hold instances contiguous instance records.
    • orphanInstanceBuffer

      public void orphanInstanceBuffer(int instances)
      Orphan the instance VBO at the start of a frame (driver-friendly streaming).
    • ensureIndirectCommandByteCapacity

      public void ensureIndirectCommandByteCapacity(int needBytes)
      Расширяет GL_DRAW_INDIRECT_BUFFER при необходимости (редко). Обновление команд — только SubData на стороне координатора.
    • uploadInstanceWindow

      public void uploadInstanceWindow(int floatOffset, FloatBuffer src, int floatCount)
      Upload one window of instance floats at the given instance-float offset.