- Motion vectors: single unjittered reprojection matrix (80 bytes) instead of
two jittered matrices (160 bytes), eliminating numerical instability from
jitter amplification through large world coordinates
- Sharpen pass: fix Y-flip for correct UV sampling, double-buffer descriptor
sets to avoid race with in-flight command buffers
- MSAA: auto-disable when FSR2 enabled, grey out AA setting in UI
- Accumulation: variance-based neighborhood clamping in YCoCg space,
correct history layout transitions
- Frame index: wrap at 256 for stable Halton sequence
Full FSR 2.2 pipeline with depth-based motion vector reprojection,
temporal accumulation with YCoCg neighborhood clamping, and RCAS
contrast-adaptive sharpening.
Architecture (designed for FSR 3.x frame generation readiness):
- Camera: Halton(2,3) sub-pixel jitter with unjittered projection
stored separately for motion vector computation
- Motion vectors: compute shader reconstructs world position from
depth + inverse VP, reprojects with previous frame's VP
- Temporal accumulation: compute shader blends 5-10% current frame
with 90-95% clamped history, adaptive blend for disocclusion
- History: ping-pong R16G16B16A16 buffers at display resolution
- Sharpening: RCAS fragment pass with contrast-adaptive weights
Integration:
- FSR2 replaces both FSR1 and MSAA when enabled
- Scene renders to internal resolution framebuffer (no MSAA)
- Compute passes run between scene and swapchain render passes
- Camera cut detection resets history on teleport
- Quality presets shared with FSR1 (0.50-0.77 scale factors)
- UI: "Upscaling" combo with Off/FSR 1.0/FSR 2.2 options
- Overlap M2 and character animation updates via std::async (~2-5ms saved)
- Thread-local collision scratch buffers for concurrent floor queries
- Parallel terrain/WMO/M2 floor queries in camera controller
- Seed new M2 instance bones from existing siblings to eliminate pop-in flash
- Fix shadow flicker: snap center along stable light axes instead of in view space
- Increase shadow distance default to 300 units (slider max 500)
Move CPU-heavy BLP texture decoding from main thread to background worker
threads for all hot paths: terrain M2 models, WMO doodad M2s, WMO textures,
creature models, and gameobject WMOs. Each renderer (M2, WMO, Character) now
accepts a pre-decoded BLP cache that loadTexture() checks before falling back
to synchronous decode.
Defer WMO normal/height map generation (3 per-pixel passes: luminance, box
blur, Sobel) during terrain streaming finalization — this was the dominant
remaining bottleneck after BLP pre-decoding.
Terrain streaming stalls: 1576ms → 124ms worst case.
- Replace per-frame VMA alloc/free of material UBOs with a ring buffer in
CharacterRenderer (~500 allocations/frame eliminated)
- Batch all ready terrain tiles into a single GPU upload during load screen
(processAllReadyTiles instead of one-at-a-time with individual fence waits)
- Lift per-frame creature/GO spawn budgets during load screen warmup phase
- Add background world preloader: saves last world position to disk, pre-warms
AssetManager file cache with ADT files starting at app init (login screen)
so terrain workers get instant cache hits when Enter World is clicked
- Distance-filter expensive collision guard to 8-unit melee range
- Merge 3 CharacterRenderer update loops into single pass
- Time-budget instrumentation for slow update stages (>3ms threshold)
- Count-based async creature model upload budget (max 3/frame in-game)
- 1-per-frame game object spawn + per-doodad time budget for transport loading
- Use deque for creature spawn queue to avoid O(n) front-erase
- Add movementSuppressTimer to camera controller that forces all movement
keys to read as false, preventing held W key from carrying through
loading screens (fixes always-running-forward after instance portals)
- Increase shadow frustum default from 60 to 72 units (+20%)
- Make shadow distance configurable via setShadowDistance() (40-200 range)
- Add shadow distance slider in Video settings tab (persisted to config)
Remove frame throttling that skipped shadow updates in dense scenes,
causing visible flicker on player and NPCs. Reduce shadow half-extent
from 180 to 60 for 3x higher resolution on nearby shadows.
Fix VK_ERROR_DEVICE_LOST crash by allocating per-frame scene history
images (color + depth) instead of a single shared image that raced
between frames in flight. Water refraction can now be toggled via
Settings > Video > Water Refraction.
Without refraction: richer blue base colors, animated caustic shimmer,
and normal-based color shifts give the water visible life. With
refraction: clean screen-space refraction with Beer-Lambert absorption.
Disabling clears scene history to black for immediate fallback.
- Split M2 instances into fast-path index lists (animated, particle-only,
particle-all, smoke) to avoid iterating all 46K instances per frame
- Cache model flags (hasAnimation, disableAnimation, isSmoke, etc.) on
M2Instance struct to eliminate per-frame hash lookups
- Replace full rebuildSpatialIndex on position/transform updates with
incremental grid cell remove+add, preventing 8.5ms/frame rebuild cost
- Advance animTime for all instances (texture UV animation) but only
compute bones and particles for the ~3K that need it
M2_UPDATE: 10.7ms → 2.0ms, FPS: 35 → 55-59
The single sceneColorImage races between frames with MAX_FRAMES_IN_FLIGHT=2:
frame N-1's water shader reads it while frame N's captureSceneHistory writes
it via vkCmdCopyImage. Pipeline barriers only sync within a single command
buffer, not across submissions on the same queue.
This caused VK_ERROR_DEVICE_LOST after ~700 frames on any map with water.
Disable the capture entirely for now — water renders without refraction.
TODO: allocate per-frame scene history images to eliminate the race.
Three fixes:
1. Water captureSceneHistory gated on hasSurfaces() — the image layout
transitions (PRESENT_SRC→TRANSFER_SRC→PRESENT_SRC) were running every
frame even on WMO-only maps with no water, causing VK_ERROR_DEVICE_LOST.
2. Tile cache invalidation: softReset() now clears tileCache_ since cache
keys are (x,y) without map name — prevents stale cross-map cache hits.
3. Copy terrain/mesh into TerrainTile instead of std::move — the moved-from
PendingTile was cached with empty data, so subsequent map loads returned
tiles with 0 valid chunks from cache.
Also adds diagnostic skip env vars (WOWEE_SKIP_TERRAIN, WOWEE_SKIP_SKY,
WOWEE_SKIP_PREPASSES) and a 0-chunk warning in loadTerrain.
After VK_ERROR_DEVICE_LOST, beginFrame returns VK_NULL_HANDLE but
renderWorld() and renderHUD() were still called, passing the null
handle to vkCmdBindPipeline which triggered a validation abort.
Root cause: LOGIN_VERIFY_WORLD path did not set areaTriggerCheckTimer_ or
areaTriggerSuppressFirst_, so the Stockades exit portal (AT 503) fired
immediately on login, teleporting the player back to Stormwind and crashing
the GPU during the unexpected map transition.
Fixes:
- Set 5s area trigger cooldown + suppress-first in handleLoginVerifyWorld
(same as SMSG_NEW_WORLD handler already did for teleports)
- Add deviceLost_ flag to VkContext so beginFrame returns immediately once
VK_ERROR_DEVICE_LOST is detected, preventing infinite retry loops
- Track device lost from both fence wait and queue submit paths
- Extract initializeRenderers() from loadTestTerrain() so WMO-only maps
(dungeons/raids) initialize renderers directly without a dummy ADT path
- Defer setState(IN_GAME) until after processing any pending deferred world
entry, preventing brief IN_GAME flicker on the wrong map
- Remove verbose area trigger debug logging (every-second position spam)
Reset descriptor pools in CharacterRenderer/M2Renderer/WMORenderer on map
change to prevent VK_ERROR_DEVICE_LOST from pool exhaustion. Defer re-entrant
SMSG_NEW_WORLD during active world load to avoid recursive cleanup crashes.
Gate swim bubbles on swimming state, skip redundant shadow pipeline re-init,
add WOWEE_SKIP_* env vars for render isolation debugging.
- Fix shutdown hang: skip vmaDestroyAllocator (walked thousands of allocations),
replace unsafe pthread_timedjoin_np with plain join + early-exit checks in workers
- Bank window: full icon rendering, click-and-hold pickup (0.10s), drag-drop for
all bank slots including bank bag equip slots, same-slot drop detection
- Loading screen: process one tile per frame for live progress updates
- Camera reset: trust server position in online mode to avoid spawning under WMOs
- Fix PLAYER_BYTES/PLAYER_BYTES_2 field indices, preserve purchasedBankBagSlots
across inventory rebuilds, fix bank slot purchase result codes
Remove freeze-while-moving and idle smoothing logic from shadow
center computation. Texel snapping already prevents shimmer, so
the shadow projection can track the player directly each frame.
Remove isInterior restriction from WMO shadow sampling so city
buildings (flagged as interior groups) correctly receive shadows.
Apply shadow to interior-lit surfaces. Enable shadows by default
and persist the setting across sessions.
- Spawn dark point-sprite insects buzzing around cattails/reeds/kelp/seaweed
- Fix firefly M2 particles: exempt from alpha dampening and forced gravity
- Make water shoreline/crest foam more irregular with UV warping and bluer tint
- Play WaterFootstep splash sounds when wading in shallow water
- Spawn foot splash particles at water surface on each water footstep
- Reduce inland water wave amplitude from 0.18 to 0.08 for calmer lakes
- Use authoritative playerRace/playerGender at spawn for voice profiles
instead of unreliable model name parsing
- Support nonbinary gender with useFemaleModel body type fallback
- Move voice setup into spawnPlayerCharacter() for all spawn paths
- Remove legacy single-player default Human Male clip preloading
- Make loading screen text black and move progress bar to top
Strip 26 chrono::now() timing calls per frame from renderer update loop,
periodic LOG_INFO/LOG_DEBUG from terrain/character/quest/heartbeat paths,
and dead m2ProfileCounter variable.
- Fix MLIQ vertex stride: each vertex is 8 bytes (4 flow + 4 height), not 4
- Use MLIQ tile flags to mask out tiles with no liquid (bridges, covered areas)
- Disable wave displacement on WMO water to prevent edge slosh artifacts
- Convert screen-space depth to vertical depth for shoreline foam and water
transparency, preventing false shoreline effects on occluding geometry
- Add underwater blue fog overlay and scene fog shift (terrain water only)
- Add getNearestWaterHeightAt to avoid false underwater detection from
elevated WMO water surfaces
- Tint refracted scene toward water color to mask occlusion edge artifacts
- Lower WMO water by 1 unit to match terrain water level
- Skybox now uses DBC sky colors (skyTop/skyMiddle/skyBand1/skyBand2) instead
of hardcoded C++ color curves, with 3-band gradient and Rayleigh/Mie scattering
- Clouds receive sun direction for lit edges, self-shadowing, and silver lining
- Fixed sun quad box artifact with proper edge fade in celestial shader
- Lens flare attenuated by fog, cloud density, and weather intensity
- Replaced garish green/purple lens flare ghosts with warm natural palette
- Added zone-based weather system for single-player mode with per-zone rain/snow
configuration, probability-based activation, and smooth intensity transitions
- Server SMSG_WEATHER remains authoritative when connected to a server
Water scene history textures and 1x pass resources were not recreated on
window resize/fullscreen, causing stale undersized textures that produced
directional transparency artifacts.
Player interaction ripples: vertex shader adds radial damped-sine displacement
centered on player position, fragment shader adds matching normal perturbation
for specular highlights. Player XY packed into shadowParams.zw, ripple strength
into fogParams.w. Separate 1x render pass for water when MSAA is active to
avoid MSAA-induced darkening — water renders after main pass resolves, using
the resolved swapchain image and depth resolve target. Water 1x framebuffers
rebuilt on swapchain recreate (window resize).
- Add .setMultisample() to minimap display pipeline and recreatePipelines() for MSAA changes
- Defer all swapchain recreation in window.cpp to beginFrame() via markSwapchainDirty()
to prevent mid-frame render pass destruction crashes on resolution/fullscreen change
- Move spawnPlayerCharacter() call to after loadTestTerrain() where character renderer exists
MSAA change was called mid-frame from settings UI, destroying the render pass
and framebuffers while the command buffer was still recording. Now deferred
via pendingMsaaSamples_ flag, applied in beginFrame() before any GPU state.
Also add +180° to M2 game object orientation to fix facing direction.
Render pass begin used 2 clear values but MSAA render pass has 3
attachments (MSAA color, depth, resolve). Vulkan requires clear
value count >= attachment count, causing a driver crash at 8x.
Also fix renderYawM2 reference removed in previous commit.
- Add error handling: revert to 1x if recreateSwapchain fails
- Clamp requested MSAA to device maximum before applying
- Retry MSAA color image allocation without TRANSIENT on failure
- Remove redundant vkDeviceWaitIdle from WMO/M2/Character recreatePipelines
(caller already waits once, was causing ~13 stalls instead of 1)
- renderWorld() now calls wmoRenderer, characterRenderer, m2Renderer (+smoke,
particles) in the correct opaque→transparent order; water moved after all
opaques; per-subsystem timing active in live path
- Deleted the 310-line #if 0 GL stub block and removed #include <GL/glew.h>
(last GL reference in renderer.cpp)
- Full-screen overlay pipeline (postprocess.vert + overlay.frag, alpha blend,
no depth test/write) for underwater tint; lazily initialized, renders a blue
tint when camera is meaningfully below the water surface; canal vs open-water
tint colours preserved from original design
- overlay.frag.glsl / overlay.frag.spv added
- drive shadow light direction from live LightingManager directionalDir
- normalize shadow light to downward-facing convention with grazing-angle guard
- make celestial/sky sun placement robust to directionalDir convention mismatches
- keep visible sun above horizon while preserving shadow alignment
- ensure player transform sync is not gated by third-person so player shadow stays consistent
- hold shadow projection center during movement and snap once on stop to remove delayed catch-up
- smooth foliage caster transitions using blended phase-shifted UV samples
- tune foliage motion frequencies/amplitudes for less popping
- increase shadow map resolution to 2048 and retune terrain PCF texel scale
- increase global shadow strength from 0.62 to 0.68 for stronger, clearer shadows