шейдер воды
This commit is contained in:
@@ -299,6 +299,7 @@ addons/mpq_extractor/loaders/wow_liquid_material.gd
|
|||||||
|
|
||||||
- ADT `MH2O` через `ADTBuilder.build_tile_water_scene`;
|
- ADT `MH2O` через `ADTBuilder.build_tile_water_scene`;
|
||||||
- WMO `MLIQ` через `WMOBuilder._build_group_liquid`.
|
- WMO `MLIQ` через `WMOBuilder._build_group_liquid`.
|
||||||
|
- Для cached terrain tiles (`streaming_tile`/`baked_tile`) вода не хранится в terrain cache: `StreamingWorldLoader` запускает отдельную background ADT load task только для `MH2O`, затем на main thread прикрепляет `Water` node к уже созданному tile root.
|
||||||
|
|
||||||
Что делает shader:
|
Что делает shader:
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ const QUALITY_HIGH := "High"
|
|||||||
@export var wmo_build_instances_per_tick: int = 1
|
@export var wmo_build_instances_per_tick: int = 1
|
||||||
## WMO render-cache mesh groups attached per frame.
|
## WMO render-cache mesh groups attached per frame.
|
||||||
@export var wmo_render_group_ops_per_tick: int = 24
|
@export var wmo_render_group_ops_per_tick: int = 24
|
||||||
|
## Raw ADT water parse tasks running in parallel for cached terrain tiles.
|
||||||
|
@export var max_concurrent_water_tasks: int = 1
|
||||||
|
## Cached-tile water roots attached per frame.
|
||||||
|
@export var water_finalize_ops_per_tick: int = 1
|
||||||
## Maximum cached WMO .tscn size instantiated while streaming. Larger WMOs need a lightweight render cache.
|
## Maximum cached WMO .tscn size instantiated while streaming. Larger WMOs need a lightweight render cache.
|
||||||
@export var wmo_max_runtime_scene_mb: float = 8.0
|
@export var wmo_max_runtime_scene_mb: float = 8.0
|
||||||
## Minimum camera movement before recomputing streaming targets.
|
## Minimum camera movement before recomputing streaming targets.
|
||||||
@@ -170,6 +174,11 @@ var _terrain_splat_cache_tasks: Dictionary = {}
|
|||||||
var _terrain_splat_tasks: Dictionary = {}
|
var _terrain_splat_tasks: Dictionary = {}
|
||||||
var _terrain_splat_result_mutex := Mutex.new()
|
var _terrain_splat_result_mutex := Mutex.new()
|
||||||
var _terrain_splat_result_queue: Array = []
|
var _terrain_splat_result_queue: Array = []
|
||||||
|
var _water_load_queue: Array = []
|
||||||
|
var _water_load_queued: Dictionary = {}
|
||||||
|
var _water_load_tasks: Dictionary = {}
|
||||||
|
var _water_result_mutex := Mutex.new()
|
||||||
|
var _water_result_queue: Array = []
|
||||||
var _tile_result_mutex := Mutex.new()
|
var _tile_result_mutex := Mutex.new()
|
||||||
var _tile_result_queue: Array = []
|
var _tile_result_queue: Array = []
|
||||||
var _shared_tex_cache: Dictionary = {}
|
var _shared_tex_cache: Dictionary = {}
|
||||||
@@ -285,6 +294,10 @@ func _process(delta: float) -> void:
|
|||||||
_drain_terrain_splat_results()
|
_drain_terrain_splat_results()
|
||||||
_profile_section(timings, "terrainsplat", section_start, profile_enabled)
|
_profile_section(timings, "terrainsplat", section_start, profile_enabled)
|
||||||
section_start = Time.get_ticks_usec() if profile_enabled else 0
|
section_start = Time.get_ticks_usec() if profile_enabled else 0
|
||||||
|
_process_water_load_queue()
|
||||||
|
_drain_water_load_results()
|
||||||
|
_profile_section(timings, "water", section_start, profile_enabled)
|
||||||
|
section_start = Time.get_ticks_usec() if profile_enabled else 0
|
||||||
_process_queues()
|
_process_queues()
|
||||||
_profile_section(timings, "terrainq", section_start, profile_enabled)
|
_profile_section(timings, "terrainq", section_start, profile_enabled)
|
||||||
section_start = Time.get_ticks_usec() if profile_enabled else 0
|
section_start = Time.get_ticks_usec() if profile_enabled else 0
|
||||||
@@ -871,7 +884,7 @@ func _log_hitch_profile(start_usec: int, timings: Array[String], did_refresh: bo
|
|||||||
var total_ms := float(Time.get_ticks_usec() - start_usec) / 1000.0
|
var total_ms := float(Time.get_ticks_usec() - start_usec) / 1000.0
|
||||||
if total_ms < maxf(1.0, hitch_profiler_threshold_ms):
|
if total_ms < maxf(1.0, hitch_profiler_threshold_ms):
|
||||||
return
|
return
|
||||||
print("HITCH %.2fms refresh=%s queues[tile=%d loading=%d terrainup=%d terrainctl=%d terrainsplat=%d detail=%d m2task=%d m2mesh=%d m2build=%d wmobuild=%d wmogroups=%d lod+=%d lod-=%d chunk+=%d chunk-=%d] %s" % [
|
print("HITCH %.2fms refresh=%s queues[tile=%d loading=%d terrainup=%d terrainctl=%d terrainsplat=%d water=%d detail=%d m2task=%d m2mesh=%d m2build=%d wmobuild=%d wmogroups=%d lod+=%d lod-=%d chunk+=%d chunk-=%d] %s" % [
|
||||||
total_ms,
|
total_ms,
|
||||||
str(did_refresh),
|
str(did_refresh),
|
||||||
_tile_load_queue.size(),
|
_tile_load_queue.size(),
|
||||||
@@ -879,6 +892,7 @@ func _log_hitch_profile(start_usec: int, timings: Array[String], did_refresh: bo
|
|||||||
_terrain_upgrade_tasks.size(),
|
_terrain_upgrade_tasks.size(),
|
||||||
_terrain_control_splat_cache_tasks.size(),
|
_terrain_control_splat_cache_tasks.size(),
|
||||||
_terrain_splat_queue_size(),
|
_terrain_splat_queue_size(),
|
||||||
|
_water_load_queue.size() + _water_load_tasks.size(),
|
||||||
_detail_asset_queue.size(),
|
_detail_asset_queue.size(),
|
||||||
_m2_group_tasks.size(),
|
_m2_group_tasks.size(),
|
||||||
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
_m2_mesh_finalize_queue.size() + _m2_mesh_load_requests.size(),
|
||||||
@@ -969,7 +983,7 @@ func _tick_runtime_stats(delta: float) -> void:
|
|||||||
m2_multimeshes += int(m2_counts["multimeshes"])
|
m2_multimeshes += int(m2_counts["multimeshes"])
|
||||||
m2_instances += int(m2_counts["instances"])
|
m2_instances += int(m2_counts["instances"])
|
||||||
|
|
||||||
print("PERF fps=%.1f tiles=%d wanted=%d retained=%d loading=%d terrainup=%d terrainctl=%d terrainsplat=%d src[baked=%d ctl=%d splat=%d] cache=%d lod_rid=%d lod_node=%d lod_hidden=%d lod_prepared=%d chunk_rid=%d chunk_node=%d queues[tile=%d detail=%d m2_task=%d m2_mesh=%d m2_build=%d wmo_build=%d wmo_groups=%d lod+=%d lod-=%d chunk+=%d chunk-=%d] m2_tiles=%d m2_mm=%d m2_inst=%d wmo=%d" % [
|
print("PERF fps=%.1f tiles=%d wanted=%d retained=%d loading=%d terrainup=%d terrainctl=%d terrainsplat=%d water=%d src[baked=%d ctl=%d splat=%d] cache=%d lod_rid=%d lod_node=%d lod_hidden=%d lod_prepared=%d chunk_rid=%d chunk_node=%d queues[tile=%d detail=%d m2_task=%d m2_mesh=%d m2_build=%d wmo_build=%d wmo_groups=%d lod+=%d lod-=%d chunk+=%d chunk-=%d] m2_tiles=%d m2_mm=%d m2_inst=%d wmo=%d" % [
|
||||||
Engine.get_frames_per_second(),
|
Engine.get_frames_per_second(),
|
||||||
_tile_states.size(),
|
_tile_states.size(),
|
||||||
wanted_tiles,
|
wanted_tiles,
|
||||||
@@ -978,6 +992,7 @@ func _tick_runtime_stats(delta: float) -> void:
|
|||||||
_terrain_upgrade_tasks.size(),
|
_terrain_upgrade_tasks.size(),
|
||||||
_terrain_control_splat_cache_tasks.size(),
|
_terrain_control_splat_cache_tasks.size(),
|
||||||
_terrain_splat_queue_size(),
|
_terrain_splat_queue_size(),
|
||||||
|
_water_load_queue.size() + _water_load_tasks.size(),
|
||||||
terrain_baked_full,
|
terrain_baked_full,
|
||||||
terrain_control_splat,
|
terrain_control_splat,
|
||||||
terrain_splat,
|
terrain_splat,
|
||||||
@@ -1889,6 +1904,100 @@ func _drain_terrain_splat_results() -> void:
|
|||||||
_rebuild_chunk_queues()
|
_rebuild_chunk_queues()
|
||||||
|
|
||||||
|
|
||||||
|
func _request_tile_water_load(state: Dictionary) -> void:
|
||||||
|
if not enable_water:
|
||||||
|
return
|
||||||
|
if bool(state.get("water_loaded", false)):
|
||||||
|
return
|
||||||
|
var key := String(state.get("key", ""))
|
||||||
|
var path := String(state.get("path", ""))
|
||||||
|
if key.is_empty() or path.is_empty():
|
||||||
|
return
|
||||||
|
if _water_load_tasks.has(key) or _water_load_queued.has(key):
|
||||||
|
return
|
||||||
|
_water_load_queue.append({
|
||||||
|
"key": key,
|
||||||
|
"path": path,
|
||||||
|
})
|
||||||
|
_water_load_queued[key] = true
|
||||||
|
|
||||||
|
|
||||||
|
func _process_water_load_queue() -> void:
|
||||||
|
if not enable_water:
|
||||||
|
return
|
||||||
|
var limit := maxi(1, max_concurrent_water_tasks)
|
||||||
|
while _water_load_tasks.size() < limit and not _water_load_queue.is_empty():
|
||||||
|
var request: Dictionary = _water_load_queue.pop_front()
|
||||||
|
var key := String(request.get("key", ""))
|
||||||
|
_water_load_queued.erase(key)
|
||||||
|
if key.is_empty() or _water_load_tasks.has(key):
|
||||||
|
continue
|
||||||
|
if not _tile_states.has(key):
|
||||||
|
continue
|
||||||
|
var path := String(request.get("path", ""))
|
||||||
|
if path.is_empty():
|
||||||
|
continue
|
||||||
|
var task_id: int = WorkerThreadPool.add_task(_load_tile_water_task.bind(key, path))
|
||||||
|
_water_load_tasks[key] = task_id
|
||||||
|
|
||||||
|
|
||||||
|
func _load_tile_water_task(key: String, adt_path: String) -> void:
|
||||||
|
var data: Dictionary = {}
|
||||||
|
var loader = ClassDB.instantiate("ADTLoader")
|
||||||
|
if loader:
|
||||||
|
data = loader.call("load_adt", adt_path)
|
||||||
|
|
||||||
|
_water_result_mutex.lock()
|
||||||
|
_water_result_queue.append({
|
||||||
|
"key": key,
|
||||||
|
"path": adt_path,
|
||||||
|
"data": data,
|
||||||
|
})
|
||||||
|
_water_result_mutex.unlock()
|
||||||
|
|
||||||
|
|
||||||
|
func _drain_water_load_results() -> void:
|
||||||
|
var results: Array = []
|
||||||
|
var budget := maxi(0, water_finalize_ops_per_tick)
|
||||||
|
if budget <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
_water_result_mutex.lock()
|
||||||
|
while budget > 0 and not _water_result_queue.is_empty():
|
||||||
|
results.append(_water_result_queue.pop_front())
|
||||||
|
budget -= 1
|
||||||
|
_water_result_mutex.unlock()
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
var key := String(result.get("key", ""))
|
||||||
|
if key.is_empty():
|
||||||
|
continue
|
||||||
|
if not _water_load_tasks.has(key):
|
||||||
|
continue
|
||||||
|
WorkerThreadPool.wait_for_task_completion(int(_water_load_tasks[key]))
|
||||||
|
_water_load_tasks.erase(key)
|
||||||
|
if _shutting_down or not _tile_states.has(key):
|
||||||
|
continue
|
||||||
|
|
||||||
|
var state: Dictionary = _tile_states[key]
|
||||||
|
if String(state.get("path", "")) != String(result.get("path", "")):
|
||||||
|
continue
|
||||||
|
var tile_root: Node = state.get("root", null)
|
||||||
|
if tile_root == null or not is_instance_valid(tile_root):
|
||||||
|
continue
|
||||||
|
|
||||||
|
var data: Dictionary = result.get("data", {})
|
||||||
|
if not data.is_empty():
|
||||||
|
var origin: Vector3 = state.get("origin", Vector3.ZERO)
|
||||||
|
var water_root: Node3D = _builder.build_tile_water_scene(data, origin)
|
||||||
|
if water_root:
|
||||||
|
tile_root.add_child(water_root)
|
||||||
|
_set_editor_owner_recursive(water_root)
|
||||||
|
|
||||||
|
state["water_loaded"] = true
|
||||||
|
_tile_states[key] = state
|
||||||
|
|
||||||
|
|
||||||
func _wait_for_tile_tasks() -> void:
|
func _wait_for_tile_tasks() -> void:
|
||||||
for pending in _tile_loading_tasks.values():
|
for pending in _tile_loading_tasks.values():
|
||||||
if pending.get("mode", "raw") == "raw":
|
if pending.get("mode", "raw") == "raw":
|
||||||
@@ -1980,6 +2089,15 @@ func _wait_for_tile_tasks() -> void:
|
|||||||
_terrain_splat_result_queue.clear()
|
_terrain_splat_result_queue.clear()
|
||||||
_terrain_splat_result_mutex.unlock()
|
_terrain_splat_result_mutex.unlock()
|
||||||
|
|
||||||
|
for task_id in _water_load_tasks.values():
|
||||||
|
WorkerThreadPool.wait_for_task_completion(int(task_id))
|
||||||
|
_water_load_tasks.clear()
|
||||||
|
_water_load_queue.clear()
|
||||||
|
_water_load_queued.clear()
|
||||||
|
_water_result_mutex.lock()
|
||||||
|
_water_result_queue.clear()
|
||||||
|
_water_result_mutex.unlock()
|
||||||
|
|
||||||
|
|
||||||
func _should_accept_loaded_tile(request: Dictionary) -> bool:
|
func _should_accept_loaded_tile(request: Dictionary) -> bool:
|
||||||
if _tile_states.has(request["key"]):
|
if _tile_states.has(request["key"]):
|
||||||
@@ -2050,6 +2168,7 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
|
|||||||
"wanted": true,
|
"wanted": true,
|
||||||
"retained": true,
|
"retained": true,
|
||||||
"root": tile_root,
|
"root": tile_root,
|
||||||
|
"water_loaded": true,
|
||||||
"m2_names": data.get("m2_names", PackedStringArray()),
|
"m2_names": data.get("m2_names", PackedStringArray()),
|
||||||
"m2_placements": data.get("m2_placements", []),
|
"m2_placements": data.get("m2_placements", []),
|
||||||
"m2_built": false,
|
"m2_built": false,
|
||||||
@@ -2125,6 +2244,7 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
|||||||
"wanted": true,
|
"wanted": true,
|
||||||
"retained": true,
|
"retained": true,
|
||||||
"root": tile_root,
|
"root": tile_root,
|
||||||
|
"water_loaded": false,
|
||||||
"m2_names": baked_m2_names,
|
"m2_names": baked_m2_names,
|
||||||
"m2_placements": baked_m2_placements,
|
"m2_placements": baked_m2_placements,
|
||||||
"m2_built": false,
|
"m2_built": false,
|
||||||
@@ -2136,6 +2256,7 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
|||||||
state["desired_tile_lod"] = _compute_desired_tile_lod(state, _last_focus_pos)
|
state["desired_tile_lod"] = _compute_desired_tile_lod(state, _last_focus_pos)
|
||||||
state["desired_tile_lod_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
state["desired_tile_lod_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
||||||
_tile_states[key] = state
|
_tile_states[key] = state
|
||||||
|
_request_tile_water_load(state)
|
||||||
state = _sync_detail_assets(state, _last_focus_pos)
|
state = _sync_detail_assets(state, _last_focus_pos)
|
||||||
_tile_states[key] = state
|
_tile_states[key] = state
|
||||||
_rebuild_chunk_queues()
|
_rebuild_chunk_queues()
|
||||||
@@ -2198,6 +2319,7 @@ func _finalize_loaded_streaming_tile(request: Dictionary, stream_tile: Resource)
|
|||||||
"wanted": true,
|
"wanted": true,
|
||||||
"retained": true,
|
"retained": true,
|
||||||
"root": tile_root,
|
"root": tile_root,
|
||||||
|
"water_loaded": false,
|
||||||
"m2_names": stream_m2_names,
|
"m2_names": stream_m2_names,
|
||||||
"m2_placements": stream_m2_placements,
|
"m2_placements": stream_m2_placements,
|
||||||
"m2_built": false,
|
"m2_built": false,
|
||||||
@@ -2210,6 +2332,7 @@ func _finalize_loaded_streaming_tile(request: Dictionary, stream_tile: Resource)
|
|||||||
state["desired_tile_lod"] = _compute_desired_tile_lod(state, _last_focus_pos)
|
state["desired_tile_lod"] = _compute_desired_tile_lod(state, _last_focus_pos)
|
||||||
state["desired_tile_lod_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
state["desired_tile_lod_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
||||||
_tile_states[key] = state
|
_tile_states[key] = state
|
||||||
|
_request_tile_water_load(state)
|
||||||
state = _sync_terrain_quality(state, _last_focus_pos)
|
state = _sync_terrain_quality(state, _last_focus_pos)
|
||||||
state = _sync_detail_assets(state, _last_focus_pos)
|
state = _sync_detail_assets(state, _last_focus_pos)
|
||||||
_tile_states[key] = state
|
_tile_states[key] = state
|
||||||
@@ -2492,6 +2615,11 @@ func _release_tile(key: String) -> void:
|
|||||||
_terrain_control_splat_cache_tasks.erase(key)
|
_terrain_control_splat_cache_tasks.erase(key)
|
||||||
_terrain_splat_cache_tasks.erase(key)
|
_terrain_splat_cache_tasks.erase(key)
|
||||||
_terrain_splat_tasks.erase(key)
|
_terrain_splat_tasks.erase(key)
|
||||||
|
_water_load_queued.erase(key)
|
||||||
|
for i in range(_water_load_queue.size() - 1, -1, -1):
|
||||||
|
var pending: Dictionary = _water_load_queue[i]
|
||||||
|
if String(pending.get("key", "")) == key:
|
||||||
|
_water_load_queue.remove_at(i)
|
||||||
var tile_lod_rid: RID = state.get("tile_lod_rid", RID())
|
var tile_lod_rid: RID = state.get("tile_lod_rid", RID())
|
||||||
if tile_lod_rid.is_valid():
|
if tile_lod_rid.is_valid():
|
||||||
_free_render_instance(tile_lod_rid)
|
_free_render_instance(tile_lod_rid)
|
||||||
@@ -2522,6 +2650,12 @@ func _clear_streamed_world() -> void:
|
|||||||
_terrain_splat_result_mutex.lock()
|
_terrain_splat_result_mutex.lock()
|
||||||
_terrain_splat_result_queue.clear()
|
_terrain_splat_result_queue.clear()
|
||||||
_terrain_splat_result_mutex.unlock()
|
_terrain_splat_result_mutex.unlock()
|
||||||
|
_water_load_queue.clear()
|
||||||
|
_water_load_queued.clear()
|
||||||
|
_water_load_tasks.clear()
|
||||||
|
_water_result_mutex.lock()
|
||||||
|
_water_result_queue.clear()
|
||||||
|
_water_result_mutex.unlock()
|
||||||
_detail_asset_queue.clear()
|
_detail_asset_queue.clear()
|
||||||
_detail_asset_queued.clear()
|
_detail_asset_queued.clear()
|
||||||
for key in _m2_build_jobs.keys():
|
for key in _m2_build_jobs.keys():
|
||||||
|
|||||||
Reference in New Issue
Block a user