perf: harden runtime for weaker hardware

This commit is contained in:
Rijad Zuzo
2026-08-12 10:02:45 +02:00
parent cd29da95e0
commit 34428d836a
47 changed files with 2164 additions and 243 deletions
+4 -1
View File
@@ -173,6 +173,7 @@ material = SubResource("Material_site_canvas")
size = Vector3(2.9, 0.08, 1.4)
[sub_resource type="ProceduralSkyMaterial" id="SkyMaterial_jajce"]
resource_local_to_scene = true
sky_top_color = Color(0.28, 0.52, 0.72, 1)
sky_horizon_color = Color(0.88, 0.74, 0.52, 1)
ground_bottom_color = Color(0.18, 0.24, 0.14, 1)
@@ -180,9 +181,11 @@ ground_horizon_color = Color(0.68, 0.6, 0.44, 1)
sun_angle_max = 18.0
[sub_resource type="Sky" id="Sky_jajce"]
resource_local_to_scene = true
sky_material = SubResource("SkyMaterial_jajce")
[sub_resource type="Environment" id="Environment_greybox"]
resource_local_to_scene = true
background_mode = 2
sky = SubResource("Sky_jajce")
background_energy_multiplier = 0.9
@@ -204,7 +207,7 @@ fog_height = -2.0
fog_height_density = 0.08
fog_aerial_perspective = 0.35
fog_sky_affect = 0.28
volumetric_fog_enabled = true
volumetric_fog_enabled = false
volumetric_fog_density = 0.0035
volumetric_fog_albedo = Color(0.82, 0.9, 0.82, 1)
volumetric_fog_emission = Color(0.06, 0.09, 0.07, 1)
+13 -2
View File
@@ -34,9 +34,12 @@ const NIGHT_LIGHT_ENERGY := 0.3
const SUNSET_LIGHT_COLOR := Color(1, 0.55, 0.3, 1)
const SUNRISE_LIGHT_COLOR := Color(1, 0.6, 0.45, 1)
const SUNRISE_SUNSET_ENERGY := 0.8
const PRESENTATION_UPDATE_INTERVAL_SECONDS := 0.1
var elapsed := 0.0
var _simulation_node: Node
var _presentation_elapsed := 0.0
var _last_applied_cycle := -1.0
func _ready() -> void:
@@ -47,17 +50,25 @@ func _ready() -> void:
_apply_environment(initial_cycle)
func _process(_delta: float) -> void:
func _process(delta: float) -> void:
_presentation_elapsed += delta
if _presentation_elapsed < PRESENTATION_UPDATE_INTERVAL_SECONDS:
return
var update_delta := _presentation_elapsed
_presentation_elapsed = 0.0
if _simulation_node == null or not is_instance_valid(_simulation_node):
_find_simulation_node()
var cycle: float
if _simulation_node != null and "clock" in _simulation_node and _simulation_node.clock != null:
cycle = _simulation_node.clock.time_of_day()
else:
elapsed += _delta
elapsed += update_delta
if elapsed >= cycle_duration_seconds:
elapsed = fmod(elapsed, cycle_duration_seconds)
cycle = elapsed / cycle_duration_seconds
if is_equal_approx(cycle, _last_applied_cycle):
return
_last_applied_cycle = cycle
_apply_light_rotation(cycle)
_apply_environment(cycle)
+336
View File
@@ -1,12 +1,348 @@
class_name JajceWorld
extends Node3D
enum PresentationQuality {
HIGH,
BALANCED,
LOW,
}
const BALANCED_RENDER_SCALE := 0.85
const LOW_RENDER_SCALE := 0.7
const BALANCED_SHADOW_DISTANCE := 120.0
const LOW_SHADOW_DISTANCE := 80.0
const BALANCED_GRASS_SPACING := 1.0
const LOW_GRASS_SPACING := 2.0
const BALANCED_GRASS_FIXED_FPS := 18
const LOW_GRASS_FIXED_FPS := 12
const BALANCED_GRASS_INTERACTORS := 4
const BALANCED_GRASS_UPDATE_INTERVAL := 0.12
const HIGH_VOLUMETRIC_FOG_ENABLED := true
static var _viewport_scale_states: Dictionary = {}
@export_enum("High", "Balanced", "Low") var presentation_quality: int = PresentationQuality.BALANCED
@onready var terrain: Terrain3D = $TerrainRoot/Terrain3D
@onready var world_environment: WorldEnvironment = $WorldEnvironment
@onready var directional_light: DirectionalLight3D = $DirectionalLight3D
@onready var grass_field: Node3D = $TerrainRoot/Terrain3D/CozyGrassField
@onready var grass_controller: Node = $TerrainRoot/GrassInteractionController
var _active_presentation_quality := -1
var _authored_presentation_captured := false
var _authored_render_scale := 1.0
var _authored_glow_enabled := false
var _authored_volumetric_fog_enabled := HIGH_VOLUMETRIC_FOG_ENABLED
var _authored_adjustment_enabled := false
var _authored_shadow_distance := 0.0
var _authored_shadow_mode := DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
var _authored_grass_visible := true
var _authored_grass_physics_enabled := true
var _authored_grass_spacing := 1.0
var _authored_grass_fixed_fps := 30
var _authored_grass_interactors := 1
var _authored_grass_update_interval := 0.1
var _authored_grass_controller_enabled := true
var _last_applied_render_scale := -1.0
var _viewport_instance_id := 0
func _ready() -> void:
_isolate_mutable_presentation_resources()
_capture_authored_presentation()
apply_presentation_quality(_resolve_requested_quality())
call_deferred("_initialize_world_presentation")
func _exit_tree() -> void:
_release_viewport_scale_owner()
func apply_presentation_quality(quality: int) -> bool:
if quality not in PresentationQuality.values():
push_warning("JajceWorld: unknown presentation quality %s" % quality)
return false
if not _authored_presentation_captured:
_capture_authored_presentation()
if not _authored_presentation_captured:
return false
if quality == _active_presentation_quality:
return true
match quality:
PresentationQuality.HIGH:
_apply_environment_quality(
_authored_render_scale,
_authored_glow_enabled,
_authored_volumetric_fog_enabled,
_authored_adjustment_enabled,
_authored_shadow_distance,
_authored_shadow_mode
)
_apply_grass_quality(
_authored_grass_visible,
_authored_grass_physics_enabled,
_authored_grass_spacing,
_authored_grass_fixed_fps,
_authored_grass_interactors,
_authored_grass_update_interval,
_authored_grass_controller_enabled
)
PresentationQuality.BALANCED:
_apply_environment_quality(
minf(_authored_render_scale, BALANCED_RENDER_SCALE),
_authored_glow_enabled,
false,
_authored_adjustment_enabled,
minf(_authored_shadow_distance, BALANCED_SHADOW_DISTANCE),
DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS
)
_apply_grass_quality(
_authored_grass_visible,
_authored_grass_physics_enabled,
maxf(_authored_grass_spacing, BALANCED_GRASS_SPACING),
mini(_authored_grass_fixed_fps, BALANCED_GRASS_FIXED_FPS),
mini(_authored_grass_interactors, BALANCED_GRASS_INTERACTORS),
maxf(_authored_grass_update_interval, BALANCED_GRASS_UPDATE_INTERVAL),
_authored_grass_controller_enabled
)
PresentationQuality.LOW:
_apply_environment_quality(
minf(_authored_render_scale, LOW_RENDER_SCALE),
false,
false,
false,
minf(_authored_shadow_distance, LOW_SHADOW_DISTANCE),
DirectionalLight3D.SHADOW_ORTHOGONAL
)
_apply_grass_quality(
false,
false,
maxf(_authored_grass_spacing, LOW_GRASS_SPACING),
mini(_authored_grass_fixed_fps, LOW_GRASS_FIXED_FPS),
0,
_authored_grass_update_interval,
false
)
_active_presentation_quality = quality
return true
func get_active_presentation_quality() -> int:
return _active_presentation_quality
func get_active_presentation_quality_name() -> StringName:
match _active_presentation_quality:
PresentationQuality.HIGH:
return &"high"
PresentationQuality.BALANCED:
return &"balanced"
PresentationQuality.LOW:
return &"low"
return &"unknown"
func _capture_authored_presentation() -> void:
if _authored_presentation_captured:
return
var environment := _environment()
if environment == null or grass_field == null or grass_controller == null:
return
_register_viewport_scale_owner()
_authored_glow_enabled = environment.glow_enabled
# Keep the serialized scene compatibility-safe; High deliberately opts into
# this Forward+-only authored effect after the quality profile is resolved.
_authored_volumetric_fog_enabled = HIGH_VOLUMETRIC_FOG_ENABLED
_authored_adjustment_enabled = environment.adjustment_enabled
_authored_shadow_distance = directional_light.directional_shadow_max_distance
_authored_shadow_mode = directional_light.directional_shadow_mode
_authored_grass_visible = grass_field.visible
_authored_grass_physics_enabled = grass_field.is_physics_processing()
_authored_grass_spacing = float(grass_field.get("instance_spacing"))
_authored_grass_fixed_fps = int(grass_field.get("process_fixed_fps"))
_authored_grass_interactors = int(grass_controller.get("max_interactors"))
_authored_grass_update_interval = float(grass_controller.get("update_interval"))
_authored_grass_controller_enabled = grass_controller.is_processing()
_authored_presentation_captured = true
func _isolate_mutable_presentation_resources() -> void:
var source_environment := _environment()
if source_environment != null:
var isolated_environment := source_environment.duplicate(true) as Environment
if source_environment.sky != null:
var isolated_sky := source_environment.sky.duplicate(true) as Sky
if source_environment.sky.sky_material != null:
isolated_sky.sky_material = source_environment.sky.sky_material.duplicate(true)
isolated_environment.sky = isolated_sky
world_environment.environment = isolated_environment
var source_grass_material := grass_field.get("mesh_material_override") as ShaderMaterial
if source_grass_material != null:
var isolated_grass_material := source_grass_material.duplicate(true) as ShaderMaterial
grass_field.set("mesh_material_override", isolated_grass_material)
grass_controller.set("grass_material", isolated_grass_material)
var source_process_material := grass_field.get("process_material") as ShaderMaterial
if source_process_material != null:
var isolated_process_material := source_process_material.duplicate(true) as ShaderMaterial
grass_field.set("process_material", isolated_process_material)
var particles: Array = grass_field.get("particle_nodes")
for value in particles:
var particle := value as GPUParticles3D
if particle != null:
particle.process_material = isolated_process_material
func _resolve_requested_quality() -> int:
for argument in OS.get_cmdline_user_args():
if argument.begins_with("--quality="):
return _quality_from_name(argument.trim_prefix("--quality="))
if argument.begins_with("--presentation-quality="):
return _quality_from_name(argument.trim_prefix("--presentation-quality="))
return presentation_quality
func _quality_from_name(value: String) -> int:
match value.strip_edges().to_lower():
"high":
return PresentationQuality.HIGH
"low":
return PresentationQuality.LOW
"balanced":
return PresentationQuality.BALANCED
push_warning("JajceWorld: unknown presentation quality '%s'; using scene default" % value)
return presentation_quality
func _apply_environment_quality(
render_scale: float,
glow_enabled: bool,
volumetric_fog_enabled: bool,
adjustment_enabled: bool,
shadow_distance: float,
shadow_mode: int
) -> void:
_claim_viewport_scale_owner()
get_viewport().scaling_3d_scale = render_scale
_last_applied_render_scale = render_scale
var environment := _environment()
if environment != null:
environment.glow_enabled = glow_enabled
environment.volumetric_fog_enabled = (volumetric_fog_enabled and _supports_volumetric_fog())
environment.adjustment_enabled = adjustment_enabled
directional_light.directional_shadow_max_distance = shadow_distance
directional_light.directional_shadow_mode = shadow_mode
func _apply_grass_quality(
is_visible: bool,
physics_enabled: bool,
instance_spacing: float,
fixed_fps: int,
max_interactors: int,
update_interval: float,
controller_enabled: bool
) -> void:
grass_field.set("instance_spacing", instance_spacing)
grass_field.set("process_fixed_fps", fixed_fps)
grass_field.visible = is_visible
grass_field.set_physics_process(physics_enabled)
grass_controller.set("max_interactors", maxi(max_interactors, 1))
grass_controller.set("update_interval", update_interval)
grass_controller.set_process(controller_enabled)
grass_controller.set("_elapsed", update_interval)
var particles: Array = grass_field.get("particle_nodes")
for value in particles:
var particle := value as GPUParticles3D
if particle == null:
continue
particle.emitting = is_visible and physics_enabled
if particle.emitting:
particle.restart(true)
var grass_material := grass_field.get("mesh_material_override") as ShaderMaterial
if grass_material != null and not controller_enabled:
grass_material.set_shader_parameter("interactor_count", 0)
if physics_enabled:
grass_field.set("last_pos", Vector3.INF)
func _environment() -> Environment:
if world_environment == null:
return null
return world_environment.environment
func _supports_volumetric_fog() -> bool:
return RenderingServer.get_current_rendering_method() == "forward_plus"
func _register_viewport_scale_owner() -> void:
var viewport := get_viewport()
_viewport_instance_id = viewport.get_instance_id()
var state: Dictionary = _viewport_scale_states.get(_viewport_instance_id, {})
if state.is_empty():
state = {"base_scale": viewport.scaling_3d_scale, "owners": []}
_authored_render_scale = float(state["base_scale"])
_viewport_scale_states[_viewport_instance_id] = state
_claim_viewport_scale_owner()
func _claim_viewport_scale_owner() -> void:
if _viewport_instance_id == 0:
return
var state: Dictionary = _viewport_scale_states.get(_viewport_instance_id, {})
if state.is_empty():
return
var owners: Array = state["owners"]
for index in range(owners.size() - 1, -1, -1):
var owner: Object = (owners[index] as WeakRef).get_ref()
if owner == null or owner == self:
owners.remove_at(index)
owners.append(weakref(self))
state["owners"] = owners
_viewport_scale_states[_viewport_instance_id] = state
func _release_viewport_scale_owner() -> void:
if _viewport_instance_id == 0 or not _viewport_scale_states.has(_viewport_instance_id):
return
var viewport := get_viewport()
var state: Dictionary = _viewport_scale_states[_viewport_instance_id]
var owners: Array = state["owners"]
var was_active_owner := false
for index in range(owners.size() - 1, -1, -1):
var owner: Object = (owners[index] as WeakRef).get_ref()
if owner == self:
was_active_owner = index == owners.size() - 1
owners.remove_at(index)
elif owner == null:
owners.remove_at(index)
if not owners.is_empty():
state["owners"] = owners
_viewport_scale_states[_viewport_instance_id] = state
if (
was_active_owner
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
):
var previous_owner := (owners[-1] as WeakRef).get_ref() as JajceWorld
if previous_owner != null and previous_owner._last_applied_render_scale >= 0.0:
viewport.scaling_3d_scale = previous_owner._last_applied_render_scale
else:
_viewport_scale_states.erase(_viewport_instance_id)
if (
was_active_owner
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
):
viewport.scaling_3d_scale = float(state["base_scale"])
_viewport_instance_id = 0
_last_applied_render_scale = -1.0
func _initialize_world_presentation() -> void:
var active_camera := get_viewport().get_camera_3d()
if active_camera != null:
@@ -3,6 +3,7 @@
[ext_resource type="Shader" path="res://world/jajce/materials/cozy_grass.gdshader" id="1_grass"]
[resource]
resource_local_to_scene = true
render_priority = 0
shader = ExtResource("1_grass")
shader_parameter/wind_direction = Vector2(1, 0.7)
@@ -12,6 +12,7 @@ seamless = true
noise = SubResource("FastNoise_grass")
[resource]
resource_local_to_scene = true
shader = ExtResource("1_process_shader")
shader_parameter/main_noise = SubResource("NoiseTexture_grass")
shader_parameter/main_noise_scale = 0.012