шейдер воды
This commit is contained in:
@@ -57,6 +57,10 @@ const QUALITY_HIGH := "High"
|
||||
@export var wmo_build_instances_per_tick: int = 1
|
||||
## WMO render-cache mesh groups attached per frame.
|
||||
@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.
|
||||
@export var wmo_max_runtime_scene_mb: float = 8.0
|
||||
## 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_result_mutex := Mutex.new()
|
||||
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_queue: Array = []
|
||||
var _shared_tex_cache: Dictionary = {}
|
||||
@@ -285,6 +294,10 @@ func _process(delta: float) -> void:
|
||||
_drain_terrain_splat_results()
|
||||
_profile_section(timings, "terrainsplat", section_start, profile_enabled)
|
||||
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()
|
||||
_profile_section(timings, "terrainq", section_start, profile_enabled)
|
||||
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
|
||||
if total_ms < maxf(1.0, hitch_profiler_threshold_ms):
|
||||
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,
|
||||
str(did_refresh),
|
||||
_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_control_splat_cache_tasks.size(),
|
||||
_terrain_splat_queue_size(),
|
||||
_water_load_queue.size() + _water_load_tasks.size(),
|
||||
_detail_asset_queue.size(),
|
||||
_m2_group_tasks.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_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(),
|
||||
_tile_states.size(),
|
||||
wanted_tiles,
|
||||
@@ -978,6 +992,7 @@ func _tick_runtime_stats(delta: float) -> void:
|
||||
_terrain_upgrade_tasks.size(),
|
||||
_terrain_control_splat_cache_tasks.size(),
|
||||
_terrain_splat_queue_size(),
|
||||
_water_load_queue.size() + _water_load_tasks.size(),
|
||||
terrain_baked_full,
|
||||
terrain_control_splat,
|
||||
terrain_splat,
|
||||
@@ -1889,6 +1904,100 @@ func _drain_terrain_splat_results() -> void:
|
||||
_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:
|
||||
for pending in _tile_loading_tasks.values():
|
||||
if pending.get("mode", "raw") == "raw":
|
||||
@@ -1980,6 +2089,15 @@ func _wait_for_tile_tasks() -> void:
|
||||
_terrain_splat_result_queue.clear()
|
||||
_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:
|
||||
if _tile_states.has(request["key"]):
|
||||
@@ -2050,6 +2168,7 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
|
||||
"wanted": true,
|
||||
"retained": true,
|
||||
"root": tile_root,
|
||||
"water_loaded": true,
|
||||
"m2_names": data.get("m2_names", PackedStringArray()),
|
||||
"m2_placements": data.get("m2_placements", []),
|
||||
"m2_built": false,
|
||||
@@ -2125,6 +2244,7 @@ func _finalize_loaded_baked_tile(request: Dictionary, baked_tile: Resource) -> v
|
||||
"wanted": true,
|
||||
"retained": true,
|
||||
"root": tile_root,
|
||||
"water_loaded": false,
|
||||
"m2_names": baked_m2_names,
|
||||
"m2_placements": baked_m2_placements,
|
||||
"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_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
||||
_tile_states[key] = state
|
||||
_request_tile_water_load(state)
|
||||
state = _sync_detail_assets(state, _last_focus_pos)
|
||||
_tile_states[key] = state
|
||||
_rebuild_chunk_queues()
|
||||
@@ -2198,6 +2319,7 @@ func _finalize_loaded_streaming_tile(request: Dictionary, stream_tile: Resource)
|
||||
"wanted": true,
|
||||
"retained": true,
|
||||
"root": tile_root,
|
||||
"water_loaded": false,
|
||||
"m2_names": stream_m2_names,
|
||||
"m2_placements": stream_m2_placements,
|
||||
"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_visible"] = _compute_desired_tile_lod_visible(state, _last_focus_pos)
|
||||
_tile_states[key] = state
|
||||
_request_tile_water_load(state)
|
||||
state = _sync_terrain_quality(state, _last_focus_pos)
|
||||
state = _sync_detail_assets(state, _last_focus_pos)
|
||||
_tile_states[key] = state
|
||||
@@ -2492,6 +2615,11 @@ func _release_tile(key: String) -> void:
|
||||
_terrain_control_splat_cache_tasks.erase(key)
|
||||
_terrain_splat_cache_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())
|
||||
if tile_lod_rid.is_valid():
|
||||
_free_render_instance(tile_lod_rid)
|
||||
@@ -2522,6 +2650,12 @@ func _clear_streamed_world() -> void:
|
||||
_terrain_splat_result_mutex.lock()
|
||||
_terrain_splat_result_queue.clear()
|
||||
_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_queued.clear()
|
||||
for key in _m2_build_jobs.keys():
|
||||
|
||||
Reference in New Issue
Block a user