первые наработки по рендеру
This commit is contained in:
@@ -1,11 +1,29 @@
|
||||
extends SceneTree
|
||||
|
||||
## Usage:
|
||||
## godot --headless --path <project> --script res://src/tools/bake_adt_terrain_cache.gd -- \
|
||||
## --map Azeroth --jobs 4 --force
|
||||
##
|
||||
## `--jobs N` starts N separate Godot worker processes. Process isolation keeps
|
||||
## ResourceSaver/Image/loader state independent while using multiple CPU cores.
|
||||
|
||||
const ADT_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/adt_builder.gd")
|
||||
const BAKED_TILE_SCRIPT := preload("res://src/resources/baked_adt_tile.gd")
|
||||
const PROGRESS_DIR := "res://data/cache/bake_progress"
|
||||
const PROGRESS_REPORT_INTERVAL_MS := 2000
|
||||
|
||||
var _builder
|
||||
var _loader
|
||||
var _image_cache: Dictionary = {}
|
||||
var _progress_path := ""
|
||||
var _progress_worker_index := -1
|
||||
var _progress_worker_count := 1
|
||||
var _progress_total := 0
|
||||
var _progress_processed := 0
|
||||
var _progress_baked := 0
|
||||
var _progress_skipped := 0
|
||||
var _progress_failed := 0
|
||||
var _progress_last_tile := ""
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
@@ -13,13 +31,13 @@ func _initialize() -> void:
|
||||
var map_name := _get_arg_value(args, "--map", "Azeroth")
|
||||
var extracted_dir := _normalize_res_path(_get_arg_value(args, "--extracted", "res://data/extracted"))
|
||||
var output_dir := _normalize_res_path(_get_arg_value(args, "--output", "res://data/cache/baked_terrain_v2"))
|
||||
var legacy_texture_size = _get_optional_int_arg(args, "--texture-size")
|
||||
var legacy_texture_size: Variant = _get_optional_int_arg(args, "--texture-size")
|
||||
var full_texture_size := maxi(
|
||||
256,
|
||||
_get_arg_value(
|
||||
args,
|
||||
"--full-texture-size",
|
||||
str(legacy_texture_size if legacy_texture_size != null else 2048)
|
||||
str(legacy_texture_size if legacy_texture_size != null else 4196)
|
||||
).to_int()
|
||||
)
|
||||
var coarse_texture_size := maxi(
|
||||
@@ -32,8 +50,15 @@ func _initialize() -> void:
|
||||
)
|
||||
var tile_x: Variant = _get_optional_int_arg(args, "--tile-x")
|
||||
var tile_y: Variant = _get_optional_int_arg(args, "--tile-y")
|
||||
var jobs := maxi(1, _get_arg_value(args, "--jobs", "1").to_int())
|
||||
var worker_index: Variant = _get_optional_int_arg(args, "--worker-index")
|
||||
var worker_count := maxi(1, _get_arg_value(args, "--worker-count", "1").to_int())
|
||||
var force := args.has("--force")
|
||||
|
||||
if jobs > 1 and worker_index == null:
|
||||
_run_parallel_controller(args, jobs)
|
||||
return
|
||||
|
||||
_builder = ADT_BUILDER_SCRIPT.new()
|
||||
if not ClassDB.class_exists("ADTLoader"):
|
||||
push_error("ADTLoader not found. Rebuild GDExtension first.")
|
||||
@@ -59,6 +84,9 @@ func _initialize() -> void:
|
||||
var skipped := 0
|
||||
var failed := 0
|
||||
var started_ms := Time.get_ticks_msec()
|
||||
var tile_index := 0
|
||||
var worker_total := _count_worker_tiles(dir.get_files(), map_name, tile_x, tile_y, worker_index, worker_count)
|
||||
_progress_setup(worker_index, worker_count, worker_total)
|
||||
|
||||
for file_name in dir.get_files():
|
||||
if not file_name.ends_with(".adt"):
|
||||
@@ -77,6 +105,11 @@ func _initialize() -> void:
|
||||
continue
|
||||
if tile_y != null and ty != tile_y:
|
||||
continue
|
||||
if worker_index != null:
|
||||
if tile_index % worker_count != int(worker_index):
|
||||
tile_index += 1
|
||||
continue
|
||||
tile_index += 1
|
||||
var source_res_path := map_dir.path_join(file_name)
|
||||
var source_abs_path := ProjectSettings.globalize_path(source_res_path)
|
||||
var output_res_path := output_dir.path_join(map_name).path_join("%s_%d_%d.res" % [map_name, tx, ty])
|
||||
@@ -84,12 +117,14 @@ func _initialize() -> void:
|
||||
|
||||
if not force and FileAccess.file_exists(output_abs_path):
|
||||
skipped += 1
|
||||
_progress_update(tx, ty, baked, skipped, failed)
|
||||
continue
|
||||
|
||||
var data: Dictionary = _loader.call("load_adt", source_abs_path)
|
||||
if data.is_empty() or not data.has("chunks"):
|
||||
push_warning("Bake failed to parse ADT: %s" % source_res_path)
|
||||
failed += 1
|
||||
_progress_update(tx, ty, baked, skipped, failed)
|
||||
continue
|
||||
|
||||
var full_payload: Dictionary = _builder.build_baked_tile_render_payload(
|
||||
@@ -108,6 +143,7 @@ func _initialize() -> void:
|
||||
if full_payload.is_empty():
|
||||
push_warning("Bake produced empty full mesh: %s" % source_res_path)
|
||||
failed += 1
|
||||
_progress_update(tx, ty, baked, skipped, failed)
|
||||
continue
|
||||
|
||||
var baked_tile: Resource = BAKED_TILE_SCRIPT.new()
|
||||
@@ -129,18 +165,223 @@ func _initialize() -> void:
|
||||
if save_err != OK:
|
||||
push_warning("Failed to save baked tile: %s (err=%d)" % [output_res_path, save_err])
|
||||
failed += 1
|
||||
_progress_update(tx, ty, baked, skipped, failed)
|
||||
continue
|
||||
|
||||
baked += 1
|
||||
_progress_update(tx, ty, baked, skipped, failed)
|
||||
if baked % 25 == 0:
|
||||
print("Baked %d tiles..." % baked)
|
||||
|
||||
var elapsed_ms := Time.get_ticks_msec() - started_ms
|
||||
print("Bake finished. baked=%d skipped=%d failed=%d elapsed=%.2fs" % [
|
||||
baked, skipped, failed, float(elapsed_ms) / 1000.0])
|
||||
_progress_finish(baked, skipped, failed)
|
||||
quit(0 if failed == 0 else 2)
|
||||
|
||||
|
||||
func _run_parallel_controller(args: PackedStringArray, jobs: int) -> void:
|
||||
var started_ms := Time.get_ticks_msec()
|
||||
var last_report_ms := 0
|
||||
var threads: Array[Thread] = []
|
||||
_prepare_progress_dir()
|
||||
|
||||
print("Starting parallel ADT bake: jobs=%d" % jobs)
|
||||
for worker_index in jobs:
|
||||
var thread := Thread.new()
|
||||
var err := thread.start(_run_worker_process.bind(args, worker_index, jobs))
|
||||
if err != OK:
|
||||
push_error("Failed to start bake worker thread %d (err=%d)" % [worker_index, err])
|
||||
quit(1)
|
||||
return
|
||||
threads.append(thread)
|
||||
|
||||
var finished := false
|
||||
while not finished:
|
||||
OS.delay_msec(250)
|
||||
var now := Time.get_ticks_msec()
|
||||
if now - last_report_ms >= PROGRESS_REPORT_INTERVAL_MS:
|
||||
_print_parallel_progress(jobs, started_ms)
|
||||
last_report_ms = now
|
||||
finished = true
|
||||
for thread in threads:
|
||||
if thread.is_alive():
|
||||
finished = false
|
||||
break
|
||||
|
||||
var failed := 0
|
||||
for thread in threads:
|
||||
var exit_code: int = int(thread.wait_to_finish())
|
||||
if exit_code != 0:
|
||||
failed += 1
|
||||
|
||||
var elapsed_ms := Time.get_ticks_msec() - started_ms
|
||||
_print_parallel_progress(jobs, started_ms)
|
||||
print("Parallel bake finished. jobs=%d failed_workers=%d elapsed=%.2fs" % [
|
||||
jobs, failed, float(elapsed_ms) / 1000.0])
|
||||
quit(0 if failed == 0 else 2)
|
||||
|
||||
|
||||
func _run_worker_process(args: PackedStringArray, worker_index: int, worker_count: int) -> int:
|
||||
var worker_args := PackedStringArray([
|
||||
"--headless",
|
||||
"--path",
|
||||
ProjectSettings.globalize_path("res://"),
|
||||
"--script",
|
||||
"res://src/tools/bake_adt_terrain_cache.gd",
|
||||
"--",
|
||||
])
|
||||
|
||||
var skip_next := false
|
||||
for i in args.size():
|
||||
if skip_next:
|
||||
skip_next = false
|
||||
continue
|
||||
var arg := args[i]
|
||||
if arg == "--jobs":
|
||||
skip_next = i + 1 < args.size()
|
||||
continue
|
||||
if arg == "--worker-index" or arg == "--worker-count":
|
||||
skip_next = i + 1 < args.size()
|
||||
continue
|
||||
worker_args.append(arg)
|
||||
|
||||
worker_args.append("--jobs")
|
||||
worker_args.append("1")
|
||||
worker_args.append("--worker-index")
|
||||
worker_args.append(str(worker_index))
|
||||
worker_args.append("--worker-count")
|
||||
worker_args.append(str(worker_count))
|
||||
|
||||
print("Worker %d/%d started" % [worker_index + 1, worker_count])
|
||||
var exit_code := OS.execute(OS.get_executable_path(), worker_args)
|
||||
print("Worker %d/%d finished with exit=%d" % [worker_index + 1, worker_count, exit_code])
|
||||
return exit_code
|
||||
|
||||
|
||||
func _prepare_progress_dir() -> void:
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(PROGRESS_DIR))
|
||||
var dir := DirAccess.open(PROGRESS_DIR)
|
||||
if dir == null:
|
||||
return
|
||||
for file_name in dir.get_files():
|
||||
if file_name.begins_with("adt_worker_") and file_name.ends_with(".json"):
|
||||
dir.remove(file_name)
|
||||
|
||||
|
||||
func _progress_setup(worker_index: Variant, worker_count: int, total: int) -> void:
|
||||
_progress_worker_index = int(worker_index) if worker_index != null else 0
|
||||
_progress_worker_count = worker_count
|
||||
_progress_total = total
|
||||
_progress_processed = 0
|
||||
_progress_baked = 0
|
||||
_progress_skipped = 0
|
||||
_progress_failed = 0
|
||||
_progress_last_tile = ""
|
||||
_progress_path = PROGRESS_DIR.path_join("adt_worker_%d.json" % _progress_worker_index)
|
||||
_progress_write(false)
|
||||
|
||||
|
||||
func _progress_update(tx: int, ty: int, baked: int, skipped: int, failed: int) -> void:
|
||||
_progress_processed += 1
|
||||
_progress_baked = baked
|
||||
_progress_skipped = skipped
|
||||
_progress_failed = failed
|
||||
_progress_last_tile = "%d_%d" % [tx, ty]
|
||||
_progress_write(false)
|
||||
|
||||
|
||||
func _progress_finish(baked: int, skipped: int, failed: int) -> void:
|
||||
_progress_baked = baked
|
||||
_progress_skipped = skipped
|
||||
_progress_failed = failed
|
||||
_progress_processed = _progress_total
|
||||
_progress_write(true)
|
||||
|
||||
|
||||
func _progress_write(done: bool) -> void:
|
||||
if _progress_path.is_empty():
|
||||
return
|
||||
var file := FileAccess.open(_progress_path, FileAccess.WRITE)
|
||||
if file == null:
|
||||
return
|
||||
file.store_string(JSON.stringify({
|
||||
"worker": _progress_worker_index,
|
||||
"workers": _progress_worker_count,
|
||||
"total": _progress_total,
|
||||
"processed": _progress_processed,
|
||||
"baked": _progress_baked,
|
||||
"skipped": _progress_skipped,
|
||||
"failed": _progress_failed,
|
||||
"last_tile": _progress_last_tile,
|
||||
"done": done,
|
||||
}))
|
||||
|
||||
|
||||
func _print_parallel_progress(jobs: int, started_ms: int) -> void:
|
||||
var now := Time.get_ticks_msec()
|
||||
var total := 0
|
||||
var processed := 0
|
||||
var baked := 0
|
||||
var skipped := 0
|
||||
var failed := 0
|
||||
var done := 0
|
||||
for worker_index in jobs:
|
||||
var path := PROGRESS_DIR.path_join("adt_worker_%d.json" % worker_index)
|
||||
if not FileAccess.file_exists(path):
|
||||
continue
|
||||
var file := FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
continue
|
||||
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
||||
if not (parsed is Dictionary):
|
||||
continue
|
||||
var row: Dictionary = parsed
|
||||
total += int(row.get("total", 0))
|
||||
processed += int(row.get("processed", 0))
|
||||
baked += int(row.get("baked", 0))
|
||||
skipped += int(row.get("skipped", 0))
|
||||
failed += int(row.get("failed", 0))
|
||||
if bool(row.get("done", false)):
|
||||
done += 1
|
||||
|
||||
var elapsed := maxf(0.001, float(now - started_ms) / 1000.0)
|
||||
var rate := float(processed) / elapsed
|
||||
print("Progress: %d/%d tiles, baked=%d skipped=%d failed=%d, workers done=%d/%d, %.2f tiles/s" % [
|
||||
processed, total, baked, skipped, failed, done, jobs, rate])
|
||||
|
||||
|
||||
func _count_worker_tiles(
|
||||
files: PackedStringArray,
|
||||
map_name: String,
|
||||
tile_x: Variant,
|
||||
tile_y: Variant,
|
||||
worker_index: Variant,
|
||||
worker_count: int) -> int:
|
||||
var count := 0
|
||||
var tile_index := 0
|
||||
for file_name in files:
|
||||
if not file_name.ends_with(".adt"):
|
||||
continue
|
||||
var parts := file_name.trim_suffix(".adt").split("_")
|
||||
if parts.size() != 3 or parts[0] != map_name:
|
||||
continue
|
||||
if not parts[1].is_valid_int() or not parts[2].is_valid_int():
|
||||
continue
|
||||
var tx := parts[1].to_int()
|
||||
var ty := parts[2].to_int()
|
||||
if tile_x != null and tx != tile_x:
|
||||
continue
|
||||
if tile_y != null and ty != tile_y:
|
||||
continue
|
||||
if worker_index != null and tile_index % worker_count != int(worker_index):
|
||||
tile_index += 1
|
||||
continue
|
||||
tile_index += 1
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _get_arg_value(args: PackedStringArray, name: String, default_value: String) -> String:
|
||||
var index := args.find(name)
|
||||
if index >= 0 and index + 1 < args.size():
|
||||
|
||||
@@ -81,6 +81,7 @@ func _initialize() -> void:
|
||||
|
||||
var elapsed := float(Time.get_ticks_msec() - total_start) / 1000.0
|
||||
print("\nAll done in %.1fs." % elapsed)
|
||||
WMO_BUILDER_SCRIPT.clear_caches()
|
||||
quit(0)
|
||||
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ func _initialize() -> void:
|
||||
print("[%d/%d] baked=%d skipped=%d failed=%d" % [i, total, baked, skipped, failed])
|
||||
|
||||
print("Done. baked=%d skipped=%d failed=%d" % [baked, skipped, failed])
|
||||
WMO_BUILDER_SCRIPT.clear_caches()
|
||||
quit(0)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user