Skip to content

Vesuvio 0.3.1: engine-side Vulkan speed-ups, memory check - #1

Draft
Atomsk-debug wants to merge 71 commits into
mainfrom
claude/etna-optimization-cjfxg1
Draft

Atomsk-debug wants to merge 71 commits into
mainfrom
claude/etna-optimization-cjfxg1

Conversation

@Atomsk-debug

@Atomsk-debug Atomsk-debug commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Before: Vesuvio 0.1.x trimmed CPU work (culling, chunk scheduling, ticking). With a shader pack the Mac is GPU-bound, so it gained only about 8% (78 to 84 fps with Etna Ultra at 1080p).

After: 0.3.0 also works on the game's native Vulkan renderer. Every change has its own setting, and F8 off gives exact vanilla.

0.2.0 part

  • Keep Compiled Shaders: a disk VkPipelineCache.
  • Clear Fusion: clears become the next pass's load op; dead clears and unused post depth are dropped.
  • Merge Upload Barriers.
  • Direct Chunk Staging: mapped, fenced staging buffers.
  • Skip Repeated Binds.
  • Reuse Terrain Draw Lists.
  • Face Culling: Smart Splits (Mac).
  • Submit timers (encode/wait) in F3 and the benchmark.
  • Fog Culling, plus sign text, item frame and beacon beam culling.
  • Frame caps while paused or with the window hidden.
  • Memory check: launcher memory advice.

0.3.0 part

  • Keep Framebuffers.
  • Direct Vertex Writes (unified memory only).
  • Compact Terrain Vertices: opt-in, restart; 20-byte chunk vertex.
  • No Metal Argument Buffers: benchmark knob, off.
  • Faster Entity Models: each cube corner transformed once (8 instead of 24); bit-identical to vanilla on 29M test vertices.
  • Cache Sign Text: per-line width and glyph layout kept, checked against font, colour, outline and font generation; §k text never cached.
  • GPU Pass Timers (off; on during a dedicated benchmark step): Vulkan timestamp queries around each render pass, outside the pass, so F3 and the benchmark's "GPU passes" table show which vanilla and Etna passes cost the most GPU time.
  • Lighter hooks: per-vertex, per-entity, per-particle, per-section and per-draw hooks no longer allocate (WrapWithCondition / ModifyExpressionValue instead of Inject / WrapOperation); same behaviour.
  • Old-world references released on world change.
  • Debounced hidden-window cap.
  • Less per-frame garbage.

How: builder reports are in the project files under etna-designs/boost/report-v020-*.md and report-v030-*.md. Runtime audits: 0.2.0 fixed two items (47659e4); 0.3.0 found no medium/high issues; the models/sign audit found none and one cleanup was applied (a4a0bdd); the hook refactor review found no behaviour difference. Test notes: vesuvio-0.3.0/what-to-test.md.

Not run in game yet. No release until Cosimo has tested it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5

With cutout leaves on, vanilla draws every leaf face that touches another leaf. New setting
leafCulling (Rendering page, live): 0 off, 1 smart (default, drops a face when the next three
blocks that way are leaves or opaque), 2 aggressive (two blocks), 3 all. Sections up to three
blocks from a changed block are rebuilt so no hole stays. Works for Etna's shadow map too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
@Atomsk-debug Atomsk-debug self-assigned this Oct 10, 2026
claude added 28 commits October 10, 2026 12:25
…lder)

Options > Video Settings > Vesuvio: Sodium-style pages, options with descriptions, restart markers.
F8 (rebindable in Controls) switches all optimizations on/off. Counters on F3 and optionally in the
log every 5 s. A benchmark times Vesuvio off/on and each live switch off in turn, writes the table to
latest.log and shows a summary.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Live number settings off the General page now get a step of their own: set to
their lowest value (leafCulling 0 = vanilla), or to their default when already
at the lowest. Their values are saved at the start and put back with the toggles.
Also: "Etna Vesuvio" -> "Vesuvio" in en_us strings and the javadoc, tooltip
text updated in en_us/it_it, comments name the real mixins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…nchmark

LeafCulling kept counters but never registered them with VesuvioStats, so the
F3 line, stats log and benchmark counter table showed nothing for it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Four start-up options on the Memory page, each leaving vanilla bytecode when off: lighter
thread checks in chunk section containers, shared identical block shapes (walls, fences,
panes), shared per-state face tables, shared namespace strings. Skipped automatically when
FerriteCore or ModernFix is installed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
A step that changes how meshes are built (the master switch or leafCulling)
rebuilds every loaded section; 3 s is often not enough at high render distance,
so the first measured frames included the rebuild. Settling now also waits for
the compile queue to empty, up to 30 s.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Opaque section meshes are sorted into 7 facing groups at compile time; vanilla's main view
draws only the groups that can face the camera (one or two draws per section). Etna's shadow
pass and other callers keep whole meshes unless they opt in through FaceCulling's API.
Settings faceCulling (on) and faceCullingMinGap (64), both live.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…jfxg1

# Conflicts:
#	src/main/resources/assets/vesuvio/lang/en_us.json
… builder)

A background thread ray-tests candidates through the block grid; entities and block entities
whose fresh result is hidden are not extracted. Block entities also get a per-entity frustum
test. Particles in sections the occlusion graph can't reach are skipped. Settings entityCulling,
blockEntityCulling, particleCulling (all on, live).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…from the frame builder)

animatedTextures: block-atlas sprites not seen for 8 texture ticks are not redrawn (terrain
sections, baked quads and sprite UV reads mark them). idleFps/idleFpsLimit: capped frame rate
when unfocused, one frame a second when minimized. On macOS, MoltenVK concurrent shader
compilation at instance creation (fastStartupCompile, on) and opt-in async submits (off).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…d hopper lists, path types, POI searches, suffocation (from the ticks builder)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…wer far re-sorts

- chunkScheduler: LevelExtractor's section-update loop queues only as many far rebuilds per frame as
  keep the workers busy, nearest first; nearby and player-changed sections still go at once; the rest
  stay dirty for later frames (no more thousands of region copies and queue entries in one frame).
- skipEmptySections: all-air sections get the EMPTY mesh directly (no region copy, no task).
- trimSectionCopies: SectionCopy keeps only its own section's block entities.
- chunkUploadBudget: UberGpuBuffer uploads only what fits in N ms per frame, the rest waits (grows,
  then a full send after 10 frames); MeshOrder keeps an older build from replacing a newer one.
- translucentSortThrottle: far, axis-aligned sections re-sort when the camera moved >= distance/32.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…installed

Fabric API's Indigo renderer replaces vanilla's block renderer during chunk building, so hooks
inside ModelBlockRenderer never ran. Leaf culling now hooks Block.shouldRenderFace (called by
both renderers) with the block's position recorded in SectionCompiler; animated sprites are
taken from each block's model before either renderer draws it. Fix a stale path in CREDITS.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…ns skipped, upload time budget, fewer far re-sorts (from the chunks builder)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The frame state (camera entity, frustum, view area, occlusion graph) is cleared when
LevelExtractor.extract returns, but the per-frame flags stayed set, so a call to the hooked
methods outside extract (another mod extracting entities, block entities or particles on its own)
would have dereferenced the cleared state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…settings box, log and benchmark

Registers with VesuvioStats: terrain quads skipped % and face draws added (faces), entities /
block entities / particles culled (entities), block shapes shared and memory saved in KB
(memory), animated sprites paused (frame). Each reads a plain static, no allocation. F3 now
shows the counters three per line so the longer list stays on screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The staging area only starts over after a frame that sent every staged buffer. While the budget
holds entries back it keeps filling across frames, and once full every compile worker spins on
the dispatcher's lock until the forced frame. Ignore the budget for a frame that finds it more
than half full.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
endCompile returned early when the mode was off, so a mode switched off during a compile left the
worker thread holding the region (and a stale leaf position for later compiles).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…ating

Only water and lava have vanilla fluid models. With Fabric API a modded fluid is drawn with its
render handler's sprites, which the section's sprite list never named, so an animated modded
fluid froze a few ticks after its section was built. Such sections are now "unknown".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Reuse Terrain Draw Lists now builds fresh while Fog Culling cuts sections in
the main view: the cut depends on the exact camera position and fog distance,
which the draw-list cache key does not hold (standing still while Blindness
wore off would otherwise keep the fogged-out sections missing).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…cannot cut anything; NEEDS gap

- FogCulling.beginMainView: when the fog cut-off lies past every section of the view area (render distance plus a
  margin horizontally, the level's height vertically), the main view does not count as cutting. Rain in the
  overworld (fog end 768 at full rain) used to make mainViewCutting() true with nothing ever cut, so
  SectionDrawCache rebuilt the lists every frame. Culling results are unchanged.
- VesuvioMixinPlugin: FrameCompiledSectionMeshMixin needs FrameSpriteContentsMixin. AnimatedTextures.markMesh casts a
  mesh's sprites to SpriteContentsExt, and SectionDrawCache (LevelRendererDrawCacheMixin) calls it without going
  through FrameLevelRendererMixin's own NEEDS entry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
@Atomsk-debug Atomsk-debug changed the title Vesuvio 0.1.0: first optimizations Vesuvio 0.2.0: engine-side Vulkan speed-ups, memory check Oct 11, 2026
claude added 10 commits October 11, 2026 02:18
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…ppedVertexPools)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…uffersOff)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…e hidden-window cap

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…list reuse

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…rainVertices (off, restart)

Half-float positions rounded towards the middle of their 1/16 cell (exact on the
1/16 grid), 16-bit unorm texture coordinates rounded towards the middle of their
quad, colour and light unchanged. The format is set in RenderPipelines' terrain
snippet at class init, so every chunk pipeline and every copy (Etna's shadow and
shadowed terrain pipelines) agrees. BufferBuilder fast and generic paths, the
translucent sort and FaceSorter read/write the new format. Float texture
coordinates (24 bytes) after a block atlas over 8192 was seen. With the setting
off at start-up none of the mixins is applied.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
@Atomsk-debug Atomsk-debug changed the title Vesuvio 0.2.0: engine-side Vulkan speed-ups, memory check Vesuvio 0.3.0: engine-side Vulkan speed-ups, memory check Oct 11, 2026
claude added 14 commits October 11, 2026 03:22
fastEntityModels: each model cube's distinct corners are transformed once
(8 instead of 24 transforms per box) through the same Matrix4f call with the
same inputs; normals, vertex order and the 11-argument addVertex unchanged, so
the bytes are vanilla's bit for bit (checked offline against the game's own
Cube.compile). Layout kept on the cube, checked by identity before each use.

signTextCache: each sign line is wrapped once per SignText render-messages
array; its width and prepared glyph layout (plain and 8x outline) are kept and
replayed with the current pose. Keyed on font, position, colour, shadow,
include-empty, background, outline colour and a font generation bumped on
FontSet reset/close and world change; obfuscated text and non-resource fonts
are never cached; LRU bound of 4096 lines.

Counters: model cubes fast, sign text cache hits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
…page)

New live setting gpuPassTimers (off by default). On the Vulkan backend each frame in
flight gets a timestamp query pool (512 queries, grows to 8192), reset from the CPU
(hostQueryReset) right after the backend's own wait for that frame slot and read at
the same moment without waiting. vkCmdWriteTimestamp2 (ALL_COMMANDS) right before
vkCmdBeginRenderPass2 and right after vkCmdEndRenderPass2, never inside a pass, plus
the start and end of every command buffer of the frame (main, object init and
transient staging, the last two counted as uploads). Passes are grouped by their
label (first 64, then "other"), so Etna's passes show by name.

F3 shows the GPU frame time and the five slowest passes (1 s average). The benchmark
adds a "GPU pass timers" step (user settings plus the timers, off in every other
step; its "vs on" is the cost of measuring) and logs a "GPU passes" table: top 15
passes, other passes, uploads, between passes, ms per frame and share.

Unsupported queues (timestampValidBits 0) are detected once; the setting then does
nothing and says so. Pools go to the encoder's deferred destroy queue when the
setting goes off or the device closes. Off: no query written, no pool.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The BufferBuilder addVertex/setUv hooks no longer allocate a CallbackInfo
per vertex; with compact formats off the BLOCK/ENTITY fast paths carry no
Vesuvio hook at all. Written bytes are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Occlusion and fog tests on the frustum result of entities and particles,
and the section fog test, become MEVs: no Operation Object[] or boxing and
no LocalRefImpl per entity/particle/section. Fog hooks move to dedicated
mixins with explicit priorities so the evaluation order stays occlusion
first, then fog (section fog before sprite marking), independent of config
registration order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Leaf recording becomes a ModifyReceiver on the seed lookup, animated sprite
collection MEVs on the model lookup and the fluid test, and face grouping a
ModifyReturnValue: no Operation argument arrays, boxed seeds, local
wrappers or CallbackInfoReturnable per block or compile. Leaf checks test
the block type before reading the volatile mode.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The face-culling cut is a WrapWithCondition (FaceCulling adds the cut draws
itself), skipSamePipeline a WrapWithCondition and skipSameDescriptors an MEV
on the dirty test instead of a cancellable HEAD inject. The draw-list cache
fills vanilla's list without a throwaway array copy; freed allocations are
seen through an MEV.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The fast model path hooks the cube's face-list read (MEV returning no faces
once it wrote the cube) and reuses vanilla's scratch vector; the render
thread uses a plain scratch array instead of a ThreadLocal lookup. The
suffocation hook is cut down so the JIT can inline it and drop its CIR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Resort throttle, transfer barrier hold, kept framebuffer destroy, deferred
clears and item frame/sign culling become WrapWithCondition; the staging
source check and the chunk scheduler become MEVs, the scheduler working in
vanilla's own region cache instead of a second one per frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
The saved cache (a few MB) was copied onto LWJGL's 64 KB MemoryStack in
PipelineDiskCache.open, throwing OutOfMemoryError (Out of stack space) from
the VulkanDevice constructor on every launch after the first. It now goes in
memAlloc'd heap memory freed right after vkCreatePipelineCache; any Throwable
while loading falls back to no cache; caches over 64 MB are ignored. The
instance extension list for the MoltenVK settings also moves off the stack.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFKwqp3d86DzZCXDdBVGE5
@Atomsk-debug Atomsk-debug changed the title Vesuvio 0.3.0: engine-side Vulkan speed-ups, memory check Vesuvio 0.3.1: engine-side Vulkan speed-ups, memory check Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants