extends Node
## RTX Feature Test Harness
## Cycles through all path tracing features to verify they work on Linux/RTX 2070
##
## Controls:
## 1-9: Set visualization mode (0=full PT, 1=mirror, 2=geo normals, etc.)
## 0: Full path tracing (default)
## B: Cycle max bounces (1-4)
## S: Cycle samples per pixel (1, 2, 4)
## R: Toggle raytracing on/off
## P: Print current RT params
## N: Cycle DLSS mode (see below)
## [/]: Adjust sharpness (DLSS-SR only)
## F5: Run automated feature test (cycles all modes)
## F12: Take screenshot to /tmp/rtx_screenshots/
# DLSS modes — ordered from fastest to highest quality
enum DLSSMode { OFF, SR_PERF, SR_QUALITY, RR_PERF, RR_QUALITY, RR_DLAA }
const DLSS_MODE_NAMES = {
DLSSMode.OFF: "OFF",
DLSSMode.SR_PERF: "SR Performance (50%)",
DLSSMode.SR_QUALITY: "SR Quality (67%)",
DLSSMode.RR_PERF: "RR Performance (50%)",
DLSSMode.RR_QUALITY: "RR Quality (67%)",
DLSSMode.RR_DLAA: "RR DLAA (native res)",
}
@export var start_pt_enabled: bool = true
var env: Environment
var screenshot_count: int = 0
var dlss_mode: int = DLSSMode.OFF
var current_vis_mode: int = 0
var current_bounces: int = 1
var current_samples: int = 1
var current_sharpness: float = 0.5
var auto_test_running: bool = false
var auto_test_step: int = 0
var auto_test_timer: float = 0.0
var use_legacy_api: bool = false # true = old raytracing_* API, false = new pathtracing_* API
const VIS_MODE_NAMES = {
0: "Full Path Tracing",
1: "Mirror Reflection",
2: "Geometry Normals",
3: "Final Normals (w/ normal map)",
4: "Tangent-Space Normal Map",
5: "Tangent",
6: "Bitangent",
7: "UV Visualization",
8: "Albedo Only",
9: "ORM Texture",
10: "DLSS-RR Diffuse Albedo",
11: "DLSS-RR Specular Albedo",
12: "DLSS-RR Normal+Roughness",
13: "DLSS-RR Specular Hit Dist",
14: "Metalness",
15: "Roughness",
16: "View-Space Normals",
17: "Diffuse+Specular Split",
18: "Fresnel F0",
19: "Front/Back Face",
}
func _ready() -> void:
# Wait for environment to be available
await get_tree().process_frame
await get_tree().process_frame
var we := _find_world_environment()
if we and we.environment:
env = we.environment
# Detect API version: new builds have pathtracing_enabled, old have raytracing_enabled
use_legacy_api = true
for prop in env.get_property_list():
if prop["name"] == "pathtracing_enabled":
use_legacy_api = false
break
if use_legacy_api:
print("[RTX Test] Using LEGACY API (raytracing_*)")
_init_rt_params()
print("[RTX Test] Environment found. Pathtracing: %s" % _get_rt_enabled())
print("[RTX Test] Controls: 0-9=vis, B=bounces, S=samples, R=toggle RT, N=cycle DLSS, P=status, F12=screenshot")
# Start with DLSS off to avoid NGX errors on broken drivers
_set_dlss_mode(DLSSMode.OFF)
# Auto-start benchmark if --benchmark flag passed
if "--benchmark" in OS.get_cmdline_user_args():
print("[RTX Benchmark] Auto-start via --benchmark flag")
# Wait a few frames for everything to settle
await get_tree().create_timer(1.0).timeout
_start_auto_test()
else:
push_error("[RTX Test] No WorldEnvironment found!")
func _init_rt_params() -> void:
# Enable RT (removed from tscn for dual-API compatibility)
_set_rt_enabled(start_pt_enabled)
if use_legacy_api:
var rt_params := PackedFloat64Array()
rt_params.resize(16)
rt_params[0] = 0 # debug mode
rt_params[1] = 1 # spp
rt_params[2] = 1 # bounces
rt_params[3] = 0 # denoiser (was DLSS_RR_ENABLED)
rt_params[4] = 0 # light count (old index, now at 14)
env.set("raytracing_params", rt_params)
else:
env.pathtracing_debug_mode = 0
env.pathtracing_samples_per_pixel = 1
env.pathtracing_max_bounces = 1
env.pathtracing_denoiser = 0
_print_status()
# --- Dual API helpers ---
func _get_rt_enabled() -> bool:
if use_legacy_api:
return env.get("raytracing_enabled")
return env.pathtracing_enabled
func _set_rt_enabled(val: bool) -> void:
if use_legacy_api:
env.set("raytracing_enabled", val)
else:
env.pathtracing_enabled = val
func _set_debug_mode(mode: int) -> void:
if use_legacy_api:
var p = env.get("raytracing_params")
p[0] = mode
env.set("raytracing_params", p)
else:
env.pathtracing_debug_mode = mode
func _set_spp(spp: int) -> void:
if use_legacy_api:
var p = env.get("raytracing_params")
p[1] = spp
env.set("raytracing_params", p)
else:
env.pathtracing_samples_per_pixel = spp
func _set_bounces(bounces: int) -> void:
if use_legacy_api:
var p = env.get("raytracing_params")
p[2] = bounces
env.set("raytracing_params", p)
else:
env.pathtracing_max_bounces = bounces
func _set_denoiser(val: int) -> void:
if use_legacy_api:
var p = env.get("raytracing_params")
p[3] = val
env.set("raytracing_params", p)
else:
env.pathtracing_denoiser = val
func _input(event: InputEvent) -> void:
if not env:
return
if event is InputEventKey and event.pressed and not event.echo:
match event.keycode:
# Number keys 0-9 for vis modes
KEY_0: _set_vis_mode(0)
KEY_1: _set_vis_mode(1)
KEY_2: _set_vis_mode(2)
KEY_3: _set_vis_mode(3)
KEY_4: _set_vis_mode(4)
KEY_5: _set_vis_mode(5)
KEY_6: _set_vis_mode(6)
KEY_7: _set_vis_mode(7)
KEY_8: _set_vis_mode(8)
KEY_9: _set_vis_mode(9)
KEY_B: _cycle_bounces()
KEY_S: _cycle_samples()
KEY_R: _toggle_raytracing()
KEY_N: _cycle_dlss()
KEY_P: _print_status()
KEY_BRACKETLEFT: _adjust_sharpness(-0.1)
KEY_BRACKETRIGHT: _adjust_sharpness(0.1)
KEY_F12: _take_screenshot()
func _set_vis_mode(mode: int) -> void:
current_vis_mode = mode
_set_debug_mode(mode)
var name = VIS_MODE_NAMES.get(mode, "Unknown")
print("[RTX Test] Vis Mode: %d (%s)" % [mode, name])
func _cycle_bounces() -> void:
current_bounces = (current_bounces % 4) + 1
_set_bounces(current_bounces)
print("[RTX Test] Max Bounces: %d" % current_bounces)
func _cycle_samples() -> void:
match current_samples:
1: current_samples = 2
2: current_samples = 4
_: current_samples = 1
_set_spp(current_samples)
print("[RTX Test] Samples Per Pixel: %d" % current_samples)
func _toggle_raytracing() -> void:
_set_rt_enabled(not _get_rt_enabled())
print("[RTX Test] Pathtracing: %s" % ("ON" if _get_rt_enabled() else "OFF"))
func _adjust_sharpness(delta: float) -> void:
current_sharpness = clampf(current_sharpness + delta, 0.0, 1.0)
get_viewport().fsr_sharpness = current_sharpness * 2.0 # Viewport range is 0-2
print("[RTX Test] Sharpness: %.1f (fsr=%.1f)" % [current_sharpness, current_sharpness * 2.0])
func _cycle_dlss() -> void:
_set_dlss_mode((dlss_mode + 1) % DLSSMode.size())
func _set_dlss_mode(mode: int) -> void:
dlss_mode = mode
var viewport := get_viewport()
match mode:
DLSSMode.OFF:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_BILINEAR
viewport.scaling_3d_scale = 1.0
_set_denoiser(0)
DLSSMode.SR_PERF:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_DLSS
viewport.scaling_3d_scale = 0.5
_set_denoiser(0)
DLSSMode.SR_QUALITY:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_DLSS
viewport.scaling_3d_scale = 0.67
_set_denoiser(0)
DLSSMode.RR_PERF:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_DLSS
viewport.scaling_3d_scale = 0.5
_set_denoiser(1)
DLSSMode.RR_QUALITY:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_DLSS
viewport.scaling_3d_scale = 0.67
_set_denoiser(1)
DLSSMode.RR_DLAA:
viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_DLSS
viewport.scaling_3d_scale = 1.0
_set_denoiser(1)
print("[RTX Test] DLSS: %s" % DLSS_MODE_NAMES[mode])
func _print_status() -> void:
print("=== RTX Status ===")
print(" Pathtracing Enabled: %s" % _get_rt_enabled())
print(" Vis Mode: %d (%s)" % [current_vis_mode, VIS_MODE_NAMES.get(current_vis_mode, "?")])
print(" Samples/Pixel: %d" % current_samples)
print(" Max Bounces: %d" % current_bounces)
print(" DLSS: %s" % DLSS_MODE_NAMES[dlss_mode])
print(" FPS: %.1f" % Engine.get_frames_per_second())
# Check RT capability
var rd = RenderingServer.get_rendering_device()
if rd:
var has_rt = rd.has_feature(RenderingDevice.SUPPORTS_RAYTRACING_PIPELINE)
var has_accel = rd.has_feature(RenderingDevice.SUPPORTS_RAY_QUERY)
print(" VK RT Pipeline: %s" % has_rt)
print(" VK Ray Query: %s" % has_accel)
print("==================")
func _start_auto_test() -> void:
if auto_test_running:
print("[RTX Test] Auto test already running")
return
print("[RTX Benchmark] Starting exhaustive benchmark...")
print("[RTX Benchmark] Disabling VSync for uncapped measurements")
print("[RTX Benchmark] Each config runs for 4s (1s warmup + 3s measure)")
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
auto_test_running = true
auto_test_step = 0
auto_test_timer = 0.0
bench_results.clear()
bench_frame_times.clear()
bench_warmup = true
_bench_apply_config(0)
# Benchmark state
var bench_results: Array = []
var bench_frame_times: Array[float] = []
var bench_warmup: bool = true
const BENCH_WARMUP_TIME := 1.0
const BENCH_MEASURE_TIME := 3.0
# Benchmark configurations: [description, rt_on, bounces, spp, dlss_mode]
var bench_configs: Array = [
# Rasterizer baseline
["Rasterized (Forward+)", false, 0, 0, DLSSMode.OFF],
# RT basics — bounces sweep
["RT 1spp 1bounce NO DLSS", true, 1, 1, DLSSMode.OFF],
["RT 1spp 2bounce NO DLSS", true, 2, 1, DLSSMode.OFF],
["RT 1spp 3bounce NO DLSS", true, 3, 1, DLSSMode.OFF],
["RT 1spp 4bounce NO DLSS", true, 4, 1, DLSSMode.OFF],
# RT basics — spp sweep
["RT 2spp 2bounce NO DLSS", true, 2, 2, DLSSMode.OFF],
["RT 4spp 2bounce NO DLSS", true, 2, 4, DLSSMode.OFF],
# DLSS-SR
["RT 1spp 1b SR Perf 50%", true, 1, 1, DLSSMode.SR_PERF],
["RT 1spp 1b SR Quality 67%", true, 1, 1, DLSSMode.SR_QUALITY],
["RT 1spp 2b SR Perf 50%", true, 2, 1, DLSSMode.SR_PERF],
# DLSS-RR
["RT 1spp 1b RR Perf 50%", true, 1, 1, DLSSMode.RR_PERF],
["RT 1spp 1b RR Quality 67%", true, 1, 1, DLSSMode.RR_QUALITY],
["RT 1spp 1b RR DLAA native", true, 1, 1, DLSSMode.RR_DLAA],
# SKIP: RT 1spp 2b RR — vkCreateRayTracingPipelinesKHR fails with VK_ERROR_INITIALIZATION_FAILED
# on RTX 2070. Pipeline can't be created with 2-bounce + RR shader permutation (driver bug).
# ["RT 1spp 2b RR Perf 50%", true, 2, 1, DLSSMode.RR_PERF],
# ["RT 1spp 2b RR Quality 67%", true, 2, 1, DLSSMode.RR_QUALITY],
["RT 2spp 2b RR Perf 50%", true, 2, 2, DLSSMode.RR_PERF],
]
func _process(delta: float) -> void:
if not auto_test_running:
return
auto_test_timer += delta
if bench_warmup:
# Warmup phase — let DLSS/RT stabilize
if auto_test_timer >= BENCH_WARMUP_TIME:
auto_test_timer = 0.0
bench_warmup = false
bench_frame_times.clear()
return
# Measurement phase — collect frame times
bench_frame_times.append(delta * 1000.0) # ms
if auto_test_timer >= BENCH_MEASURE_TIME:
# Record results for this config
_bench_record_result()
# Move to next config
auto_test_step += 1
if auto_test_step >= bench_configs.size():
_bench_finish()
return
# Apply next config
_bench_apply_config(auto_test_step)
auto_test_timer = 0.0
bench_warmup = true
bench_frame_times.clear()
func _bench_apply_config(idx: int) -> void:
var cfg = bench_configs[idx]
var desc: String = cfg[0]
var rt_on: bool = cfg[1]
var bounces: int = cfg[2]
var spp: int = cfg[3]
var dlss: int = cfg[4]
print("[RTX Benchmark %d/%d] Setting up: %s" % [idx + 1, bench_configs.size(), desc])
if rt_on and not _get_rt_enabled():
_toggle_raytracing()
elif not rt_on and _get_rt_enabled():
_toggle_raytracing()
if rt_on:
_set_vis_mode(0)
current_samples = spp
current_bounces = bounces
_set_spp(spp)
_set_bounces(bounces)
_set_dlss_mode(dlss)
func _bench_record_result() -> void:
if bench_frame_times.is_empty():
return
var cfg = bench_configs[auto_test_step]
var desc: String = cfg[0]
# Sort for percentiles
var sorted: Array = bench_frame_times.duplicate()
sorted.sort()
var count := sorted.size()
var sum := 0.0
for t in sorted:
sum += t
var avg_ms := sum / count
var avg_fps := 1000.0 / avg_ms
var min_ms: float = sorted[0]
var max_ms: float = sorted[count - 1]
var p1_ms: float = sorted[int(count * 0.01)]
var p50_ms: float = sorted[int(count * 0.50)]
var p99_ms: float = sorted[int(count * 0.99)]
var result := {
"config": desc,
"avg_fps": avg_fps,
"avg_ms": avg_ms,
"min_ms": min_ms,
"max_ms": max_ms,
"p1_ms": p1_ms,
"p50_ms": p50_ms,
"p99_ms": p99_ms,
"frames": count,
}
bench_results.append(result)
var stall_tag := ""
if avg_fps < 10.0:
stall_tag = " *** STALL (%.1f FPS < 10) ***" % avg_fps
elif p99_ms > 100.0:
stall_tag = " ** HIGH P99 (%.0f ms) **" % p99_ms
print("[RTX Benchmark] %s: %.1f FPS (avg %.1f ms, p1 %.1f, p50 %.1f, p99 %.1f, %d frames)%s" % [
desc, avg_fps, avg_ms, p1_ms, p50_ms, p99_ms, count, stall_tag])
func _bench_finish() -> void:
auto_test_running = false
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
print("[RTX Benchmark] VSync re-enabled")
# Print summary table
print("")
print("=" .repeat(100))
print("[RTX Benchmark] RESULTS — %s" % Time.get_datetime_string_from_system())
print("=" .repeat(100))
print("%-40s %8s %8s %8s %8s %8s %6s" % ["Configuration", "Avg FPS", "Avg ms", "P1% ms", "P50 ms", "P99 ms", "Frames"])
print("-" .repeat(100))
for r in bench_results:
print("%-40s %8.1f %8.1f %8.1f %8.1f %8.1f %6d" % [
r.config, r.avg_fps, r.avg_ms, r.p1_ms, r.p50_ms, r.p99_ms, r.frames])
print("=" .repeat(100))
# Save to /tmp (ephemeral) and to permanent benchmark storage.
var timestamp := Time.get_datetime_string_from_system().replace(":", "-")
var label := ""
for arg in OS.get_cmdline_user_args():
if arg.begins_with("--label="):
label = arg.substr(8)
var basename := "rtx_benchmark_%s%s" % [timestamp, ("_" + label) if label else ""]
var tmp_path := "/tmp/%s.txt" % basename
var perm_dir := "/storage/projects/code/godot/nvidia-rtx-godot/benchmarks"
DirAccess.make_dir_recursive_absolute(perm_dir)
for path in [tmp_path, "%s/%s.txt" % [perm_dir, basename]]:
var f := FileAccess.open(path, FileAccess.WRITE)
if f:
f.store_line("RTX Benchmark Results — %s" % Time.get_datetime_string_from_system())
f.store_line("Scene: global_illumination demo")
f.store_line("")
f.store_line("%-40s %8s %8s %8s %8s %8s %6s" % ["Configuration", "Avg FPS", "Avg ms", "P1% ms", "P50 ms", "P99 ms", "Frames"])
f.store_line("-" .repeat(100))
for r in bench_results:
f.store_line("%-40s %8.1f %8.1f %8.1f %8.1f %8.1f %6d" % [
r.config, r.avg_fps, r.avg_ms, r.p1_ms, r.p50_ms, r.p99_ms, r.frames])
f.close()
print("[RTX Benchmark] Saved to: %s" % path)
# Also save as CSV (both locations).
for path in [tmp_path.replace(".txt", ".csv"), "%s/%s.csv" % [perm_dir, basename]]:
var csv := FileAccess.open(path, FileAccess.WRITE)
if csv:
csv.store_line("config,avg_fps,avg_ms,min_ms,max_ms,p1_ms,p50_ms,p99_ms,frames")
for r in bench_results:
csv.store_line("%s,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d" % [
r.config, r.avg_fps, r.avg_ms, r.min_ms, r.max_ms, r.p1_ms, r.p50_ms, r.p99_ms, r.frames])
csv.close()
print("[RTX Benchmark] CSV saved to: %s" % path)
# Restore defaults
_set_dlss_mode(DLSSMode.RR_PERF)
# Auto-quit after benchmark completes
print("[RTX Benchmark] Done. Exiting.")
get_tree().quit()
func _find_world_environment() -> WorldEnvironment:
for node in get_tree().root.get_children():
var we := _search_for_world_env(node)
if we:
return we
return null
func _search_for_world_env(node: Node) -> WorldEnvironment:
if node is WorldEnvironment:
return node
for child in node.get_children():
var result := _search_for_world_env(child)
if result:
return result
return null
func _take_screenshot() -> void:
var dir := "/tmp/rtx_screenshots"
DirAccess.make_dir_absolute(dir)
var image := get_viewport().get_texture().get_image()
var timestamp := Time.get_datetime_string_from_system().replace(":", "-")
var mode_name : String = str(DLSS_MODE_NAMES[dlss_mode]).to_lower().replace(" ", "-").replace("(", "").replace(")", "").replace("%", "pct")
var vis_name : String = str(VIS_MODE_NAMES.get(current_vis_mode, "mode%d" % current_vis_mode)).to_lower().replace(" ", "-")
var filename := "%s/rtx_%s_%s_%dspp_%db_%s_%03d.png" % [
dir, timestamp, mode_name, current_samples, current_bounces, vis_name, screenshot_count]
image.save_png(filename)
screenshot_count += 1
print("[RTX Test] Screenshot saved: %s" % filename)