Files
owniverse/tests/android_benchmark.gd
T

111 lines
5.0 KiB
GDScript
Raw Normal View History

extends Control
const E = preload("res://world/owniverse/math/exact.gd")
const RESULT_PATH := "user://android_benchmark_result.txt"
const FRAME_COUNT := 120
var status_label: Label
func _ready() -> void:
status_label = Label.new()
status_label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
status_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
status_label.add_theme_font_size_override("font_size", 32)
add_child(status_label)
call_deferred("_run")
func _set_status(message: String) -> void:
status_label.text = message
var file := FileAccess.open(RESULT_PATH, FileAccess.WRITE)
if file != null:
file.store_line(message)
file.flush()
func _restore(value):
if value is float and value == floorf(value) and absf(value) <= 9007199254740991:
return int(value)
if value is Dictionary:
for key in value:
value[key] = _restore(value[key])
elif value is Array:
for index in value.size():
value[index] = _restore(value[index])
return value
func _load_payload_threaded() -> Dictionary:
var file := FileAccess.open("res://tests/artifacts/all_objects_speed6.adventure.json", FileAccess.READ)
if file == null:
return {"error": "Cannot open benchmark save: %s" % FileAccess.get_open_error()}
var parser := JSON.new()
var error := parser.parse(file.get_as_text())
if error != OK:
return {"error": "JSON parse error at line %d: %s" % [parser.get_error_line(), parser.get_error_message()]}
var data = E.decode(_restore(parser.data))
if not data is Dictionary or not data.has("state"):
return {"error": "Benchmark save has no state"}
return {"payload": data}
func _run() -> void:
_set_status("Reading and decoding benchmark save on a worker thread…")
var worker := Thread.new()
var start_error := worker.start(Callable(self, "_load_payload_threaded"))
if start_error != OK:
_set_status("ERROR: could not start loader thread (%s)" % start_error)
return
while worker.is_alive():
await get_tree().process_frame
var loaded: Dictionary = worker.wait_to_finish()
if loaded.has("error"):
_set_status("ERROR: " + str(loaded.error))
return
_set_status("Save decoded. Initializing UI scene…")
var payload: Dictionary = loaded.payload
var scene: Node = load("res://world/world.tscn").instantiate()
var stage = scene.get_node("Stage")
stage.owniverse.free()
stage.set_script(load("res://tests/fixtures/benchmark_ui_stage.gd"))
get_tree().root.add_child(scene)
await get_tree().process_frame
stage.owniverse.state = payload.state.duplicate(true)
stage.owniverse.restore_frame_checkpoint(payload.get("simulation", {}))
stage.owniverse.presentation.refresh(stage.owniverse.state, stage.owniverse.catalog)
stage.set_owniverse_item_value()
stage.stage_page_scroller.scroll_horizontal = 4 * stage.PAGE_WIDTH
var initial_requested = stage.owniverse.frame_clock.get("datetime", stage.owniverse.state.time.datetime)
var initial_published = stage.owniverse.state.time.datetime
stage.owniverse.profile_enabled = true
stage.measuring = true
_set_status("Running speed 6: 0/%d frames" % FRAME_COUNT)
for frame in FRAME_COUNT:
await get_tree().process_frame
if frame % 5 == 4:
_set_status("Running speed 6: %d/%d frames; commits=%d" % [frame + 1, FRAME_COUNT, stage.owniverse.frame_commits])
stage.measuring = false
var samples: Array = stage.frame_samples
if samples.is_empty():
_set_status("ERROR: no rendered frame samples; frames=%d" % stage.frame_samples.size())
return
var total: float = 0.0
for sample in samples:
total += sample
samples.sort()
var requested_advance = E.subtract(stage.owniverse.frame_clock.datetime, initial_requested)
var published_advance = E.subtract(stage.owniverse.state.time.datetime, initial_published)
var process_samples: Array = stage.process_samples
process_samples.sort()
var process_total: float = 0.0
for sample in process_samples:
process_total += sample
var process_summary := "no _process samples"
if not process_samples.is_empty():
process_summary = "mean=%.2fms p95=%.2fms max=%.2fms samples=%d" % [process_total / process_samples.size(), process_samples[int(ceil(process_samples.size() * 0.95)) - 1], process_samples.back(), process_samples.size()]
var budget: Variant = stage.owniverse.frame_budget
var max_slice_summary: String = "none"
if budget != null:
max_slice_summary = "%.2fms (%s)" % [budget.max_slice_usec / 1000.0, budget.max_slice_phase]
var result := "DONE\nFPS=%.2f mean=%.2fms p95=%.2fms max=%.2fms frames=%d commits=%d\nStage _process CPU: %s\nMax uninterrupted simulation slice: %s\nRequested=%s published=%s throughput=%s\nFinal adaptive step=%s\nProfile=%s" % [1000.0 / (total / samples.size()), total / samples.size(), samples[int(ceil(samples.size() * 0.95)) - 1], samples.back(), samples.size(), stage.owniverse.frame_commits, process_summary, max_slice_summary, str(requested_advance), str(published_advance), str(E.divide(published_advance, requested_advance)), str(stage.owniverse.frame_step_seconds), str(stage.owniverse.profile)]
_set_status(result)
get_tree().quit()