Skip to content

Commit 282f3d1

Browse files
authored
Merge pull request #98 from phaseLineStudios/feature/performance-improvements
Feature/performance improvements
2 parents eb0c665 + 801380a commit 282f3d1

17 files changed

+1212
-111
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
shader_type spatial;
2+
3+
render_mode cull_disabled;
4+
5+
uniform sampler2D map_tex : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
6+
uniform float brightness : hint_range(0.0, 1.0) = 0.88;
7+
uniform float contrast : hint_range(0.0, 2.0) = 1.06;
8+
uniform float sharpen_strength : hint_range(0.0, 3.0) = 0.65;
9+
uniform float unshaded : hint_range(0.0, 1.0) = 0.0;
10+
11+
void fragment() {
12+
vec2 texel = 1.0 / vec2(textureSize(map_tex, 0));
13+
14+
vec3 c = texture(map_tex, UV).rgb;
15+
vec3 b = (
16+
texture(map_tex, UV + vec2(texel.x, 0.0)).rgb
17+
+ texture(map_tex, UV - vec2(texel.x, 0.0)).rgb
18+
+ texture(map_tex, UV + vec2(0.0, texel.y)).rgb
19+
+ texture(map_tex, UV - vec2(0.0, texel.y)).rgb
20+
) * 0.25;
21+
22+
vec3 col = c + (c - b) * sharpen_strength;
23+
col = (col - vec3(0.5)) * contrast + vec3(0.5);
24+
col = clamp(col * brightness, 0.0, 1.0);
25+
26+
ALBEDO = col * (1.0 - unshaded);
27+
EMISSION = col * unshaded;
28+
29+
ROUGHNESS = 1.0;
30+
METALLIC = 0.0;
31+
SPECULAR = 0.0;
32+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://7bkjmqdec1go

src/scenes/system/unit_counter.tscn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ unique_name_in_owner = true
5454
disable_3d = true
5555
transparent_bg = true
5656
size = Vector2i(2048, 2048)
57-
render_target_update_mode = 4
57+
render_target_update_mode = 0
5858

5959
[node name="Background" type="PanelContainer" parent="FaceRenderer"]
6060
unique_name_in_owner = true
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
[gd_scene load_steps=2 format=3]
2+
3+
[ext_resource type="Script" path="res://scripts/ui/ViewportReadOverlay.gd" id="1_overlay"]
4+
5+
[node name="ViewportReadOverlay" type="CanvasLayer"]
6+
layer = 50
7+
script = ExtResource("1_overlay")
8+
9+
[node name="Root" type="Control" parent="."]
10+
anchors_preset = 15
11+
anchor_right = 1.0
12+
anchor_bottom = 1.0
13+
mouse_filter = 0
14+
15+
[node name="Background" type="ColorRect" parent="Root"]
16+
anchors_preset = 15
17+
anchor_right = 1.0
18+
anchor_bottom = 1.0
19+
mouse_filter = 0
20+
color = Color(0, 0, 0, 0.65)
21+
22+
[node name="Margin" type="MarginContainer" parent="Root"]
23+
anchors_preset = 15
24+
anchor_right = 1.0
25+
anchor_bottom = 1.0
26+
mouse_filter = 0
27+
theme_override_constants/margin_left = 80
28+
theme_override_constants/margin_top = 60
29+
theme_override_constants/margin_right = 80
30+
theme_override_constants/margin_bottom = 60
31+
32+
[node name="Panel" type="PanelContainer" parent="Root/Margin"]
33+
layout_mode = 2
34+
mouse_filter = 0
35+
36+
[node name="VBox" type="VBoxContainer" parent="Root/Margin/Panel"]
37+
layout_mode = 2
38+
mouse_filter = 0
39+
40+
[node name="TopBar" type="HBoxContainer" parent="Root/Margin/Panel/VBox"]
41+
layout_mode = 2
42+
mouse_filter = 0
43+
44+
[node name="TitleLabel" type="Label" parent="Root/Margin/Panel/VBox/TopBar"]
45+
unique_name_in_owner = true
46+
layout_mode = 2
47+
size_flags_horizontal = 3
48+
text = "View"
49+
50+
[node name="CloseButton" type="Button" parent="Root/Margin/Panel/VBox/TopBar"]
51+
unique_name_in_owner = true
52+
layout_mode = 2
53+
text = "Close"
54+
55+
[node name="ViewportTexture" type="TextureRect" parent="Root/Margin/Panel/VBox"]
56+
unique_name_in_owner = true
57+
layout_mode = 2
58+
size_flags_horizontal = 3
59+
size_flags_vertical = 3
60+
mouse_filter = 0

src/scripts/core/DocumentController.gd

Lines changed: 82 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ const MAX_TRANSCRIPT_ENTRIES := 50
2626
## Refresh delay in seconds (wait for user to stop navigating)
2727
const REFRESH_DELAY := 0.15
2828

29+
@export_group("Performance")
30+
## If true, bake a CPU ImageTexture with mipmaps from the viewport (expensive).
31+
@export var bake_viewport_mipmaps: bool = false
32+
## Delay before rebuilding transcript pages after new entries (seconds).
33+
@export var transcript_update_delay_sec: float = 0.25
34+
2935
## Sound to play when page is changed.
3036
@export var page_change_sounds: Array[AudioStream] = [
3137
preload("res://audio/sfx/sfx_paper_flip_01.wav"),
@@ -69,7 +75,7 @@ var _transcript_entries: Array[Dictionary] = []
6975

7076
## Transcript update mutex to prevent concurrent updates
7177
var _transcript_updating := false
72-
var _transcript_pending_entries: Array[Dictionary] = []
78+
var _transcript_update_needs_rerun := false
7379

7480
## Current scenario reference
7581
var _scenario: ScenarioData
@@ -85,6 +91,7 @@ var _briefing_material: StandardMaterial3D
8591
## Debounce timers for texture refresh
8692
var _intel_refresh_timer: Timer
8793
var _transcript_refresh_timer: Timer
94+
var _transcript_update_timer: Timer
8895
var _briefing_refresh_timer: Timer
8996

9097

@@ -126,24 +133,30 @@ func _setup_viewports() -> void:
126133
_intel_viewport = SubViewport.new()
127134
_intel_viewport.size = render_size
128135
_intel_viewport.transparent_bg = false
129-
_intel_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
136+
_intel_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED
130137
_intel_viewport.gui_disable_input = false
138+
_intel_viewport.msaa_2d = Viewport.MSAA_2X
139+
_intel_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
131140
add_child(_intel_viewport)
132141

133142
# Transcript viewport
134143
_transcript_viewport = SubViewport.new()
135144
_transcript_viewport.size = render_size
136145
_transcript_viewport.transparent_bg = false
137-
_transcript_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
146+
_transcript_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED
138147
_transcript_viewport.gui_disable_input = false
148+
_transcript_viewport.msaa_2d = Viewport.MSAA_2X
149+
_transcript_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
139150
add_child(_transcript_viewport)
140151

141152
# Briefing viewport
142153
_briefing_viewport = SubViewport.new()
143154
_briefing_viewport.size = render_size
144155
_briefing_viewport.transparent_bg = false
145-
_briefing_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
156+
_briefing_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED
146157
_briefing_viewport.gui_disable_input = false
158+
_briefing_viewport.msaa_2d = Viewport.MSAA_2X
159+
_briefing_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED
147160
add_child(_briefing_viewport)
148161

149162

@@ -163,6 +176,13 @@ func _setup_refresh_timers() -> void:
163176
_transcript_refresh_timer.timeout.connect(_do_transcript_refresh)
164177
add_child(_transcript_refresh_timer)
165178

179+
# Transcript update timer (coalesce new entries)
180+
_transcript_update_timer = Timer.new()
181+
_transcript_update_timer.wait_time = maxf(transcript_update_delay_sec, 0.01)
182+
_transcript_update_timer.one_shot = true
183+
_transcript_update_timer.timeout.connect(_do_transcript_update)
184+
add_child(_transcript_update_timer)
185+
166186
# Briefing timer
167187
_briefing_refresh_timer = Timer.new()
168188
_briefing_refresh_timer.wait_time = REFRESH_DELAY
@@ -375,24 +395,25 @@ func _update_transcript_content(follow_new_messages: bool) -> void:
375395
_transcript_face.update_page_indicator()
376396
_display_page(_transcript_face, _transcript_content, _transcript_pages, target_page)
377397

378-
# Refresh texture immediately for transcript updates (no debounce needed)
379-
await _do_transcript_refresh()
398+
if _transcript_refresh_timer:
399+
_transcript_refresh_timer.start()
380400

381401

382-
## Add a radio transmission to the transcript
383-
## [param speaker] Who is speaking (e.g., "PLAYER", "ALPHA", "HQ")
384-
## [param message] The message text
385-
func add_transcript_entry(speaker: String, message: String) -> void:
386-
var timestamp := _get_mission_timestamp()
387-
var entry := {"timestamp": timestamp, "speaker": speaker, "message": message}
388-
389-
_transcript_entries.append(entry)
402+
func _queue_transcript_update() -> void:
403+
if _transcript_update_timer == null:
404+
return
405+
if _transcript_updating:
406+
_transcript_update_needs_rerun = true
407+
return
408+
_transcript_update_timer.wait_time = maxf(transcript_update_delay_sec, 0.01)
409+
if not _transcript_update_timer.is_stopped():
410+
return
411+
_transcript_update_timer.start()
390412

391-
if _transcript_entries.size() > MAX_TRANSCRIPT_ENTRIES:
392-
_transcript_entries.pop_front()
393413

414+
func _do_transcript_update() -> void:
394415
if _transcript_updating:
395-
_transcript_pending_entries.append(entry)
416+
_transcript_update_needs_rerun = true
396417
return
397418

398419
_transcript_updating = true
@@ -405,22 +426,24 @@ func add_transcript_entry(speaker: String, message: String) -> void:
405426

406427
_transcript_updating = false
407428

408-
if _transcript_pending_entries.size() > 0:
409-
_transcript_pending_entries.clear()
410-
await _refresh_transcript_display()
429+
if _transcript_update_needs_rerun:
430+
_transcript_update_needs_rerun = false
431+
_queue_transcript_update()
411432

412433

413-
## Refresh transcript display without adding new entries
414-
func _refresh_transcript_display() -> void:
415-
_transcript_updating = true
434+
## Add a radio transmission to the transcript
435+
## [param speaker] Who is speaking (e.g., "PLAYER", "ALPHA", "HQ")
436+
## [param message] The message text
437+
func add_transcript_entry(speaker: String, message: String) -> void:
438+
var timestamp: String = _get_mission_timestamp()
439+
var entry: Dictionary = {"timestamp": timestamp, "speaker": speaker, "message": message}
416440

417-
var was_on_last_page := false
418-
if _transcript_face and _transcript_pages.size() > 0:
419-
was_on_last_page = _transcript_face.current_page >= _transcript_pages.size() - 1
441+
_transcript_entries.append(entry)
420442

421-
await _update_transcript_content(was_on_last_page)
443+
if _transcript_entries.size() > MAX_TRANSCRIPT_ENTRIES:
444+
_transcript_entries.pop_front()
422445

423-
_transcript_updating = false
446+
_queue_transcript_update()
424447

425448

426449
## Get current mission timestamp as formatted string
@@ -434,6 +457,14 @@ func _get_mission_timestamp() -> String:
434457

435458
## Apply rendered textures to clipboard materials
436459
func _apply_textures() -> void:
460+
# Render each SubViewport once before capturing to textures.
461+
if _intel_viewport:
462+
_intel_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
463+
if _transcript_viewport:
464+
_transcript_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
465+
if _briefing_viewport:
466+
_briefing_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
467+
437468
await get_tree().process_frame
438469
await get_tree().process_frame # Extra frame to ensure render complete
439470

@@ -445,6 +476,7 @@ func _apply_textures() -> void:
445476
## Refresh the transcript document texture after content updates
446477
func _refresh_transcript_texture() -> void:
447478
if _transcript_material and _transcript_viewport:
479+
_transcript_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
448480
await get_tree().process_frame # Wait for render
449481
_refresh_texture(_transcript_material, _transcript_viewport)
450482

@@ -492,6 +524,22 @@ func _refresh_texture(material: StandardMaterial3D, viewport: SubViewport) -> vo
492524
LogService.warning("Cannot refresh: material or viewport is null", "DocumentController.gd")
493525
return
494526

527+
# Reduce glare so text stays readable under strong lights.
528+
material.albedo_color = Color.WHITE
529+
material.metallic = 0.0
530+
material.roughness = 1.0
531+
material.specular = 0.0
532+
533+
material.texture_filter = (
534+
BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
535+
if bake_viewport_mipmaps
536+
else BaseMaterial3D.TEXTURE_FILTER_LINEAR
537+
)
538+
539+
if not bake_viewport_mipmaps:
540+
material.albedo_texture = viewport.get_texture()
541+
return
542+
495543
var img := viewport.get_texture().get_image()
496544
if img == null:
497545
LogService.warning("Failed to get image from viewport", "DocumentController.gd")
@@ -713,18 +761,24 @@ func _on_briefing_page_changed(page_index: int) -> void:
713761

714762
## Debounced refresh functions - called after timer expires
715763
func _do_intel_refresh() -> void:
764+
if _intel_viewport:
765+
_intel_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
716766
await get_tree().process_frame
717767
await get_tree().process_frame
718768
_refresh_texture(_intel_material, _intel_viewport)
719769

720770

721771
func _do_transcript_refresh() -> void:
772+
if _transcript_viewport:
773+
_transcript_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
722774
await get_tree().process_frame
723775
await get_tree().process_frame
724776
_refresh_texture(_transcript_material, _transcript_viewport)
725777

726778

727779
func _do_briefing_refresh() -> void:
780+
if _briefing_viewport:
781+
_briefing_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
728782
await get_tree().process_frame
729783
await get_tree().process_frame
730784
_refresh_texture(_briefing_material, _briefing_viewport)

0 commit comments

Comments
 (0)