- Introduced `test_scenery_brick_preview.gd` to validate focused placement and CPU draw-cost for large brick walls. - Added `test_scenery_surface_cache.gd` to assess texture budget, visual equivalence, and cache invalidation. - Created `test_scenery_surface_preview.gd` to evaluate adaptive preview selection and placement behavior. - Implemented various helper functions to measure drawing performance and validate expected behaviors during tests. - Added unique identifiers for new test files to ensure proper tracking and management.
150 lines
6.1 KiB
GDScript
150 lines
6.1 KiB
GDScript
extends SceneTree
|
|
## Adaptive preview selection, placement/commit behavior, and per-finish CPU costs.
|
|
|
|
const RENDERER := preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
|
const CONTROLLER := preload("res://scripts/editors/stage/stage_scenery_controller.gd")
|
|
|
|
class TimedWall extends StageWallPanel:
|
|
var draw_times_usec: Array[int] = []
|
|
|
|
func _draw() -> void:
|
|
var started: int = Time.get_ticks_usec()
|
|
super._draw()
|
|
draw_times_usec.append(Time.get_ticks_usec() - started)
|
|
|
|
class TimedGround extends StageBackgroundGround:
|
|
var draw_times_usec: Array[int] = []
|
|
|
|
func _draw() -> void:
|
|
var started: int = Time.get_ticks_usec()
|
|
super._draw()
|
|
draw_times_usec.append(Time.get_ticks_usec() - started)
|
|
|
|
class SlowWall extends StageWallPanel:
|
|
var inject_delay: bool = true
|
|
|
|
func get_surface_draw_origin_world() -> Vector2:
|
|
if inject_delay:
|
|
OS.delay_usec(PREVIEW_DRAW_BUDGET_USEC + 1000)
|
|
return super.get_surface_draw_origin_world()
|
|
|
|
var _failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
root.size = Vector2i(2560, 1440)
|
|
for finish: StringName in RENDERER.WALL_FINISHES + RENDERER.BACKGROUND_FINISHES:
|
|
_expect(not RENDERER.needs_light_preview(finish, Vector2(16.0, 16.0)), "tiny %s keeps its detail" % finish)
|
|
_expect(not RENDERER.needs_light_preview(finish, Vector2.ZERO), "invisible %s has no preview work" % finish)
|
|
_test_placement(finish)
|
|
await _benchmark_finish(finish)
|
|
for finish: StringName in [&"finish_wall_brick", &"finish_wall_wood", &"finish_background_grass", &"finish_background_carpet"]:
|
|
_expect(RENDERER.needs_light_preview(finish, Vector2(2400.0, 1320.0)), "large %s reduces detail automatically" % finish)
|
|
_expect(not RENDERER.needs_light_preview(&"finish_wall_paint", Vector2(100000.0, 100000.0)), "paint stays detailed at any size")
|
|
_expect(not RENDERER.needs_light_preview(&"finish_background_concrete", Vector2(2400.0, 1320.0)), "ordinary concrete slab patterns stay detailed")
|
|
_expect(RENDERER.needs_light_preview(&"finish_background_concrete", Vector2(24000.0, 13200.0)), "very large concrete patterns can also simplify")
|
|
_expect(RENDERER.needs_light_preview(&"finish_wall_wood", Vector2(240.0, 132.0), Vector2(10.0, 10.0)), "work estimate accounts for motif scale")
|
|
await _test_runtime_fallback()
|
|
if _failures.is_empty():
|
|
print("PASS: adaptive scenery surface previews")
|
|
else:
|
|
for failure: String in _failures:
|
|
push_error("FAIL: " + failure)
|
|
quit(0 if _failures.is_empty() else 1)
|
|
|
|
|
|
func _test_placement(finish: StringName) -> void:
|
|
var world := Node2D.new()
|
|
var ghosts := Node2D.new()
|
|
var controller := CONTROLLER.new() as StageSceneryController
|
|
root.add_child(world)
|
|
root.add_child(ghosts)
|
|
root.add_child(controller)
|
|
controller.configure(world, ghosts, null)
|
|
var wall: bool = RENDERER.is_wall_finish(finish)
|
|
var tint: Color = RENDERER.default_tint(finish)
|
|
var start := Vector2(-300.0, -150.0)
|
|
if wall:
|
|
controller.begin_wall(finish, start, tint)
|
|
else:
|
|
controller.begin_background_ground(finish, start, tint)
|
|
var ghost: StageRectSurface = ghosts.get_child(0) as StageRectSurface
|
|
_expect(ghost.interactive_preview, "%s placement opts into adaptive rendering" % finish)
|
|
var press := InputEventMouseButton.new()
|
|
press.button_index = MOUSE_BUTTON_LEFT
|
|
press.pressed = true
|
|
controller.handle_mouse_button(press, start, false)
|
|
controller.handle_mouse_motion(Vector2(2100.0, 1170.0))
|
|
var bounds: Rect2 = ghost.global_transform * ghost.get_local_bounds()
|
|
var release := InputEventMouseButton.new()
|
|
release.button_index = MOUSE_BUTTON_LEFT
|
|
controller.handle_mouse_button(release, Vector2(2100.0, 1170.0), false)
|
|
_expect(world.get_child_count() == 1, "%s commits one surface" % finish)
|
|
var placed: StageRectSurface = world.get_child(0) as StageRectSurface
|
|
_expect(not placed.interactive_preview, "%s commit uses full detail" % finish)
|
|
_expect(placed.finish_id == finish and placed.tint == tint, "%s preserves finish and tint" % finish)
|
|
_expect(bounds.is_equal_approx(placed.global_transform * placed.get_local_bounds()), "%s preserves preview bounds" % finish)
|
|
controller.exit_tool()
|
|
world.free()
|
|
ghosts.free()
|
|
controller.free()
|
|
|
|
|
|
func _benchmark_finish(finish: StringName) -> void:
|
|
var sample: StageRectSurface = TimedWall.new() if RENDERER.is_wall_finish(finish) else TimedGround.new()
|
|
sample.position = Vector2(1280.0, 720.0)
|
|
sample.interactive_preview = true
|
|
sample.configure(Vector2(2400.0, 1320.0), finish, RENDERER.default_tint(finish))
|
|
root.add_child(sample)
|
|
await process_frame
|
|
await process_frame
|
|
var preview_usec: float = await _measure_redraws(sample)
|
|
sample.interactive_preview = false
|
|
await process_frame
|
|
await process_frame
|
|
var full_usec: float = await _measure_redraws(sample)
|
|
print("%s CPU redraw: adaptive=%.2f ms, full=%.2f ms" % [finish, preview_usec / 1000.0, full_usec / 1000.0])
|
|
sample.free()
|
|
|
|
|
|
func _measure_redraws(sample: StageRectSurface) -> float:
|
|
var timings: Array[int] = sample.get("draw_times_usec")
|
|
timings.clear()
|
|
for _iteration: int in 3:
|
|
sample.queue_redraw()
|
|
await process_frame
|
|
await process_frame
|
|
_expect(not timings.is_empty(), "surface draws during benchmark")
|
|
var total: float = 0.0
|
|
for elapsed: int in timings:
|
|
total += float(elapsed)
|
|
return total / maxf(1.0, float(timings.size()))
|
|
|
|
|
|
func _test_runtime_fallback() -> void:
|
|
var sample := SlowWall.new()
|
|
sample.position = Vector2(100.0, 100.0)
|
|
sample.interactive_preview = true
|
|
sample.configure(Vector2(16.0, 16.0), &"finish_wall_wood", Color.WHITE)
|
|
root.add_child(sample)
|
|
await process_frame
|
|
await process_frame
|
|
_expect(sample.get("_preview_over_budget"), "measured slow redraw enables the fallback even for a small panel")
|
|
sample.inject_delay = false
|
|
sample.configure(Vector2(20.0, 20.0), sample.finish_id, sample.tint)
|
|
await process_frame
|
|
await process_frame
|
|
_expect(sample.get("_preview_over_budget"), "fallback stays stable during the current placement session")
|
|
sample.interactive_preview = false
|
|
_expect(not sample.get("_preview_over_budget"), "leaving preview resets the timing fallback")
|
|
sample.free()
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
_failures.append(message)
|