generated from krampus/template-godot4
Clear total calculation is now multithreaded.
Note: This relies on the rendering thread model changed to "Threaded", which is an experimental feature. This may cause big problems in the future!! See: https://docs.godotengine.org/en/stable/tutorials/performance/thread_safe_apis.html#rendering
This commit is contained in:
parent
26ba514eb4
commit
c676393e6d
@ -110,3 +110,7 @@ interact={
|
|||||||
3d_render/layer_2="HUD"
|
3d_render/layer_2="HUD"
|
||||||
3d_physics/layer_1="Physics"
|
3d_physics/layer_1="Physics"
|
||||||
3d_physics/layer_2="Interactive"
|
3d_physics/layer_2="Interactive"
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
driver/threads/thread_model=2
|
||||||
|
@ -27,13 +27,6 @@ func enable() -> void:
|
|||||||
interactive.enabled = true
|
interactive.enabled = true
|
||||||
|
|
||||||
|
|
||||||
func _on_gunk_body_painted() -> void:
|
|
||||||
if not enabled:
|
|
||||||
var clear_total := gunk_body.get_clear_total()
|
|
||||||
if clear_total >= CLEAR_THRESHOLD:
|
|
||||||
enable()
|
|
||||||
|
|
||||||
|
|
||||||
func _activate() -> void:
|
func _activate() -> void:
|
||||||
animation_player.play("activate")
|
animation_player.play("activate")
|
||||||
activated.emit()
|
activated.emit()
|
||||||
@ -43,3 +36,8 @@ func _activate() -> void:
|
|||||||
|
|
||||||
func _animation_finished(_anim_name: StringName) -> void:
|
func _animation_finished(_anim_name: StringName) -> void:
|
||||||
interactive.enabled = true
|
interactive.enabled = true
|
||||||
|
|
||||||
|
|
||||||
|
func _on_gunk_body_clear_total_updated(clear_total: float) -> void:
|
||||||
|
if not enabled and clear_total >= CLEAR_THRESHOLD:
|
||||||
|
enable()
|
||||||
|
@ -241,5 +241,5 @@ script = ExtResource("10_qw6jt")
|
|||||||
shape = SubResource("BoxShape3D_6maql")
|
shape = SubResource("BoxShape3D_6maql")
|
||||||
|
|
||||||
[connection signal="animation_finished" from="AnimationPlayer" to="." method="_animation_finished"]
|
[connection signal="animation_finished" from="AnimationPlayer" to="." method="_animation_finished"]
|
||||||
[connection signal="painted" from="GunkBody" to="." method="_on_gunk_body_painted"]
|
[connection signal="clear_total_updated" from="GunkBody" to="." method="_on_gunk_body_clear_total_updated"]
|
||||||
[connection signal="activated" from="Interactive" to="." method="_activate"]
|
[connection signal="activated" from="Interactive" to="." method="_activate"]
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
class_name GunkBody extends StaticBody3D
|
class_name GunkBody extends StaticBody3D
|
||||||
## StaticBody3D with an associated "gunkable" mesh.
|
## StaticBody3D with an associated "gunkable" mesh.
|
||||||
|
|
||||||
signal painted
|
## Emitted from the main thread after the clear total is asynchronously updated.
|
||||||
|
signal clear_total_updated(clear_total: float)
|
||||||
|
|
||||||
const CONTINUITY_LIMIT := 16
|
const CONTINUITY_LIMIT := 16
|
||||||
const BUFFER_LIMIT := 3
|
const BUFFER_LIMIT := 3
|
||||||
const FACE_EPSILON := 0.01
|
const FACE_EPSILON := 0.01
|
||||||
const MASK_COLOR := Color.RED
|
const MASK_COLOR := Color.RED
|
||||||
|
const CLEAR_TOTAL_EPSILON := 1
|
||||||
|
|
||||||
@export var mask_dim := 256
|
@export var mask_dim := 256
|
||||||
@export var mesh_instance: MeshInstance3D
|
@export var mesh_instance: MeshInstance3D
|
||||||
@ -20,7 +22,14 @@ var _multiline_buffer := PackedVector2Array()
|
|||||||
var _multiline_width := 1.0
|
var _multiline_width := 1.0
|
||||||
|
|
||||||
var _clear_total := 0.0
|
var _clear_total := 0.0
|
||||||
var _clear_total_dirty := true
|
var _prev_clear_total := 0.0
|
||||||
|
|
||||||
|
# _clear_total is async computed in separate thread
|
||||||
|
var _mask_tx: Texture2D
|
||||||
|
var _thread: Thread
|
||||||
|
var _mutex: Mutex = Mutex.new()
|
||||||
|
var _semaphore: Semaphore = Semaphore.new()
|
||||||
|
var _exit_thread := false
|
||||||
|
|
||||||
@onready var mesh: ArrayMesh = mesh_instance.mesh
|
@onready var mesh: ArrayMesh = mesh_instance.mesh
|
||||||
@onready var gunk_mat: ShaderMaterial = mesh_instance.mesh.surface_get_material(0).next_pass
|
@onready var gunk_mat: ShaderMaterial = mesh_instance.mesh.surface_get_material(0).next_pass
|
||||||
@ -36,6 +45,56 @@ func _ready() -> void:
|
|||||||
gunk_mat.set_shader_parameter("gunk_mask", mask_viewport.get_texture())
|
gunk_mat.set_shader_parameter("gunk_mask", mask_viewport.get_texture())
|
||||||
meshtool.create_from_surface(mesh, 0)
|
meshtool.create_from_surface(mesh, 0)
|
||||||
|
|
||||||
|
_thread = Thread.new()
|
||||||
|
_thread.start(_async_compute_clear_total)
|
||||||
|
|
||||||
|
|
||||||
|
func _trigger_recompute_deferred() -> void:
|
||||||
|
_mutex.lock()
|
||||||
|
_mask_tx = mask_viewport.get_texture()
|
||||||
|
#_mask_img = mask_viewport.get_texture().get_image()
|
||||||
|
_mutex.unlock()
|
||||||
|
_semaphore.post()
|
||||||
|
|
||||||
|
|
||||||
|
func _async_compute_clear_total() -> void:
|
||||||
|
while true:
|
||||||
|
_semaphore.wait()
|
||||||
|
|
||||||
|
# check exit flag
|
||||||
|
_mutex.lock()
|
||||||
|
var exiting := _exit_thread
|
||||||
|
_mutex.unlock()
|
||||||
|
|
||||||
|
if exiting:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Get mask from GPU
|
||||||
|
# TODO: does this need mutex protection?
|
||||||
|
_mutex.lock()
|
||||||
|
var mask_img := _mask_tx.get_image()
|
||||||
|
_mutex.unlock()
|
||||||
|
mask_img.convert(Image.FORMAT_R8)
|
||||||
|
var px_data := mask_img.get_data()
|
||||||
|
var px_sum := 0
|
||||||
|
for px in px_data:
|
||||||
|
px_sum += px
|
||||||
|
var new_total := px_sum / 255.0
|
||||||
|
|
||||||
|
# Write total
|
||||||
|
_mutex.lock()
|
||||||
|
_clear_total = new_total
|
||||||
|
_mutex.unlock()
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
# Flag processing flag to cleanly exit
|
||||||
|
_mutex.lock()
|
||||||
|
_exit_thread = true
|
||||||
|
_mutex.unlock()
|
||||||
|
_semaphore.post()
|
||||||
|
_thread.wait_to_finish()
|
||||||
|
|
||||||
|
|
||||||
func clear_all() -> void:
|
func clear_all() -> void:
|
||||||
mask_control.queue_draw(
|
mask_control.queue_draw(
|
||||||
@ -51,16 +110,12 @@ func clear_all() -> void:
|
|||||||
## This will use a cached result unless the mask has been painted since the last calculation.
|
## This will use a cached result unless the mask has been painted since the last calculation.
|
||||||
## Be aware that cache misses are potentially quite expensive.
|
## Be aware that cache misses are potentially quite expensive.
|
||||||
func get_clear_total() -> float:
|
func get_clear_total() -> float:
|
||||||
if _clear_total_dirty:
|
# Protect with mutex
|
||||||
var mask_img := mask_viewport.get_texture().get_image()
|
_mutex.lock()
|
||||||
mask_img.convert(Image.FORMAT_R8)
|
var total := _clear_total
|
||||||
var px_data := mask_img.get_data()
|
_mutex.unlock()
|
||||||
var px_sum := 0
|
|
||||||
for px in px_data:
|
return total
|
||||||
px_sum += px
|
|
||||||
_clear_total = px_sum / 255.0
|
|
||||||
_clear_total_dirty = false
|
|
||||||
return _clear_total
|
|
||||||
|
|
||||||
|
|
||||||
## Transform cartesian coordinates to barycentric wrt the given triangle.
|
## Transform cartesian coordinates to barycentric wrt the given triangle.
|
||||||
@ -182,6 +237,14 @@ func add_to_multiline(
|
|||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
# Check if processing thread has updated the clear total
|
||||||
|
_mutex.lock()
|
||||||
|
var new_total := _clear_total
|
||||||
|
_mutex.unlock()
|
||||||
|
if abs(new_total - _prev_clear_total) > CLEAR_TOTAL_EPSILON:
|
||||||
|
clear_total_updated.emit(new_total)
|
||||||
|
_prev_clear_total = new_total
|
||||||
|
|
||||||
# If paint_continuous wasn't called last frame, stop the current polyline.
|
# If paint_continuous wasn't called last frame, stop the current polyline.
|
||||||
if not _continued_paint_this_frame:
|
if not _continued_paint_this_frame:
|
||||||
_polyline_buffer = []
|
_polyline_buffer = []
|
||||||
@ -199,5 +262,5 @@ func _process(_delta: float) -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_mask_painted() -> void:
|
func _on_mask_painted() -> void:
|
||||||
_clear_total_dirty = true
|
# XXX any problem with posting each frame?
|
||||||
painted.emit()
|
call_deferred("_trigger_recompute_deferred")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user