Compute-based pixel counting and basic ui

This commit is contained in:
Martin Felis
2024-01-27 15:58:16 +01:00
parent ad98216b2f
commit 993c69ea0a
21 changed files with 763 additions and 38 deletions
+56 -17
View File
@@ -1,13 +1,19 @@
[gd_scene load_steps=5 format=3 uid="uid://rd48gl8k5x34"]
[gd_scene load_steps=8 format=3 uid="uid://rd48gl8k5x34"]
[ext_resource type="Script" path="res://Game.gd" id="1_7jqda"]
[ext_resource type="PackedScene" uid="uid://b1nm5h3yccr16" path="res://scenes/World.tscn" id="2_voimb"]
[ext_resource type="FontFile" uid="uid://bf6q51e8x2s1h" path="res://assets/fonts/Bangers-Regular/Bangers-Regular.ttf" id="3_52dp0"]
[ext_resource type="Script" path="res://scenes/GameUI.gd" id="4_2tdl1"]
[sub_resource type="World3D" id="World3D_5g233"]
[sub_resource type="ViewportTexture" id="ViewportTexture_2lmj8"]
viewport_path = NodePath("Viewport")
[sub_resource type="Theme" id="Theme_qtmht"]
default_font = ExtResource("3_52dp0")
default_font_size = 30
[node name="Game" type="Node"]
script = ExtResource("1_7jqda")
@@ -39,28 +45,61 @@ size_flags_vertical = 4
texture = SubResource("ViewportTexture_2lmj8")
stretch_mode = 6
[node name="HSplitContainer" type="HSplitContainer" parent="."]
[node name="TopBar" type="Panel" parent="."]
visible = false
custom_minimum_size = Vector2(0, 100)
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
size_flags_horizontal = 4
size_flags_vertical = 4
[node name="Panel2" type="Panel" parent="."]
custom_minimum_size = Vector2(0, 80)
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
[node name="GameUI" type="Control" parent="."]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 4
size_flags_vertical = 4
theme = SubResource("Theme_qtmht")
script = ExtResource("4_2tdl1")
world_path = NodePath("../Viewport/World")
[node name="WorldView" type="TextureRect" parent="HSplitContainer"]
texture_filter = 1
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
texture = SubResource("ViewportTexture_2lmj8")
stretch_mode = 6
[node name="Player1Widgets" type="VBoxContainer" parent="GameUI"]
layout_mode = 1
offset_right = 49.0
offset_bottom = 77.0
alignment = 1
[node name="WorldView2" type="TextureRect" parent="HSplitContainer"]
texture_filter = 1
[node name="NameLabel" type="Label" parent="GameUI/Player1Widgets"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
texture = SubResource("ViewportTexture_2lmj8")
stretch_mode = 6
text = "Red"
[node name="ScoreLabel" type="Label" parent="GameUI/Player1Widgets"]
layout_mode = 2
text = "0
"
[node name="Player2Widgets" type="VBoxContainer" parent="GameUI"]
layout_direction = 3
layout_mode = 0
offset_left = 7.0
offset_right = 56.0
offset_bottom = 77.0
size_flags_horizontal = 8
alignment = 1
[node name="NameLabel" type="Label" parent="GameUI/Player2Widgets"]
layout_mode = 2
text = "Blue"
[node name="ScoreLabel" type="Label" parent="GameUI/Player2Widgets"]
layout_mode = 2
text = "0
"
+22
View File
@@ -0,0 +1,22 @@
extends Control
@export var world_path : NodePath = NodePath()
@onready var player1_score_label : Label = $'Player1Widgets/ScoreLabel'
@onready var player2_score_label : Label = $'Player2Widgets/ScoreLabel'
@onready var player2_widgets : Control = $'Player2Widgets'
var world : World = null
# Called when the node enters the scene tree for the first time.
func _ready():
world = get_node(world_path)
if world.players.size() == 1:
player2_widgets.visible = false
func _process(_delta):
player1_score_label.text = str(world.players[0].score)
if player2_widgets.visible:
player2_score_label.text = str(world.players[1].score)
+156
View File
@@ -0,0 +1,156 @@
# based on:
# - https://www.youtube.com/watch?v=5CKvGYqagyI
# - https://pastebin.com/wXCqk1nQ
class_name PixelCountComputeShader extends Node
@export var paint_viewport_path: NodePath = NodePath()
var red_score := 0
var blue_score := 0
var pink_score := 0
var thread: Thread
var semaphore: Semaphore
var mutex: Mutex
var exit := false
var paint_texture: ViewportTexture
var paint_viewport: Viewport
var rd: RenderingDevice
var shader
var pipeline
var pixel_count_result_buffer
var pixel_count_uniform
var v_tex
var samp
var tex_uniform
var uniform_set
func _ready():
paint_viewport = get_node(paint_viewport_path)
paint_texture = paint_viewport.get_texture()
mutex = Mutex.new()
semaphore = Semaphore.new()
thread = Thread.new()
thread.start(_thread_calculate_score)
# We will be using our own RenderingDevice to handle the compute commands
rd = RenderingServer.create_local_rendering_device()
# Create shader and pipeline
var shader_file = load("res://materials/shader/PixelCountCompute.glsl")
var shader_spirv = shader_file.get_spirv()
shader = rd.shader_create_from_spirv(shader_spirv)
pipeline = rd.compute_pipeline_create(shader)
var pb = PackedInt32Array([0,0,0])
var pbb = pb.to_byte_array()
pixel_count_result_buffer = rd.storage_buffer_create(pbb.size(), pbb)
pixel_count_uniform = RDUniform.new()
pixel_count_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
pixel_count_uniform.binding = 0
pixel_count_uniform.add_id(pixel_count_result_buffer)
var img = paint_texture.get_image()
var ta = img.get_data()
var fmt = RDTextureFormat.new()
fmt.width = paint_texture.get_width()
fmt.height = paint_texture.get_height()
fmt.usage_bits = RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT | RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
fmt.format = RenderingDevice.DATA_FORMAT_R8G8B8A8_SRGB
v_tex = rd.texture_create(fmt, RDTextureView.new(), [ta])
var samp_state = RDSamplerState.new()
samp_state.unnormalized_uvw = true
samp = rd.sampler_create(samp_state)
tex_uniform = RDUniform.new()
tex_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
tex_uniform.binding = 1
tex_uniform.add_id(samp)
tex_uniform.add_id(v_tex)
uniform_set = rd.uniform_set_create([pixel_count_uniform, tex_uniform], shader, 0)
func recalculate_score():
await RenderingServer.frame_post_draw
semaphore.post()
func get_blue_score() -> int:
var result := blue_score
return result
func get_red_score() -> int:
var result := red_score
return result
func _thread_calculate_score():
while true:
semaphore.wait()
mutex.lock()
var should_exit := exit
mutex.unlock()
if should_exit:
break
mutex.lock()
var image := paint_texture.get_image()
#var image := paint_viewport.get_texture()
mutex.unlock()
var size = image.get_size()
var ta = image.get_data()
rd.texture_update(v_tex, 0, ta)
# Start compute list to start recording our compute commands
var compute_list = rd.compute_list_begin()
# Bind the pipeline, this tells the GPU what shader to use
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
# Binds the uniform set with the data we want to give our shader
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
# Dispatch 1x1x1 (XxYxZ) work groups
rd.compute_list_dispatch(compute_list, 1024/4, 1024/4, 1)
#rd.compute_list_add_barrier(compute_list)
# Tell the GPU we are done with this compute task
rd.compute_list_end()
# Force the GPU to start our commands
rd.submit()
# Force the CPU to wait for the GPU to finish with the recorded commands
rd.sync()
mutex.lock()
# Now we can grab our data from the storage buffer
var byte_data = rd.buffer_get_data(pixel_count_result_buffer)
var output := byte_data.to_int32_array()
red_score = floor(output[0]/100)
blue_score = floor(output[1]/100)
pink_score = floor(output[2]/100)
var pb = PackedInt32Array([0,0,0])
var pbb = pb.to_byte_array()
rd.buffer_update(pixel_count_result_buffer, 0, pbb.size(), pbb)
mutex.unlock()
func _exit_tree():
mutex.lock()
exit = true
mutex.unlock()
semaphore.post()
thread.wait_to_finish()
+7
View File
@@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://bqgleq3w0o5wj"]
[ext_resource type="Script" path="res://scenes/PixelCountComputeShader.gd" id="1_g7ep2"]
[node name="PixelCountComputeShader" type="Node"]
script = ExtResource("1_g7ep2")
paint_viewport_path = NodePath("../DrawViewport")
+50 -8
View File
@@ -1,27 +1,61 @@
extends Node3D
class_name World extends Node3D
@export var players: Array
@onready var score_update_timer: Timer = $ScoreUpdateTimer
@onready var pixel_count_compute_shader: PixelCountComputeShader = $PixelCountComputeShader
@onready var camera: Camera3D = $Camera3D
@onready var level: Node3D = $Level
@onready var world_coloring_viewport: SubViewport = $WorldColoringViewport
@onready var player1: CharacterBody3D = $Player1
@onready var player2: CharacterBody3D = $Player2
var camera_offset: Vector3;
var camera_position: Vector3;
var world_coloring_material: ShaderMaterial
var world_plane: Plane = Plane(Vector3.UP, Vector3.ZERO)
# Called when the node enters the scene tree for the first time.
func _ready():
camera_offset = camera.global_position - player1.global_position
world_coloring_material = load("res://materials/WorldColoringMaterialPass.tres")
world_coloring_material.set_shader_parameter("world_color_texture", world_coloring_viewport.get_texture())
var player1: CharacterBody3D = $Player1
var player2: CharacterBody3D = $Player2
players.append(player1)
players.append(player2)
camera_position = camera.global_position
score_update_timer.connect("timeout", update_score)
apply_world_coloring_recursive(level)
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(_delta):
camera.global_position = player1.global_position + camera_offset
var player1 : Player = players[0]
var player_center = Vector3.ZERO
var player_coords_rect : Rect2 = Rect2(
player1.global_position.x,
player1.global_position.z,
0,
0,
)
for player: CharacterBody3D in players:
player_center += player.global_position
player_coords_rect = player_coords_rect.expand(Vector2(player.global_position.x, player.global_position.z))
player_center = player_center / players.size()
player_center.y = 0
var view_width = player_coords_rect.size.x + 10
var view_height = player_coords_rect.size.y + 15
var camera_dist = max (view_width, view_height) * 0.5 / sin(deg_to_rad(camera.fov) * 0.5)
var camera_direction : Vector3 = -camera.basis.z
camera_position = player_center - camera_direction * camera_dist
camera.global_position = camera_position
pass
func _physics_process(_delta):
@@ -41,3 +75,11 @@ func assign_world_coloring_pass(mesh_instance: MeshInstance3D) -> void:
return
material.next_pass = world_coloring_material
func update_score():
pixel_count_compute_shader.recalculate_score()
players[0].score = pixel_count_compute_shader.red_score
if players.size() >= 1:
players[1].score = pixel_count_compute_shader.blue_score
+30 -5
View File
@@ -1,14 +1,22 @@
[gd_scene load_steps=15 format=3 uid="uid://b1nm5h3yccr16"]
[gd_scene load_steps=18 format=3 uid="uid://b1nm5h3yccr16"]
[ext_resource type="Script" path="res://scenes/World.gd" id="1_gtcjp"]
[ext_resource type="PackedScene" uid="uid://bfyjtfdko3l7o" path="res://entities/Player.tscn" id="2_a343a"]
[ext_resource type="Texture2D" uid="uid://bnsrnuuq28p4d" path="res://assets/textures/player_draw_mask.png" id="4_dipd5"]
[ext_resource type="Material" uid="uid://bg3dawo5kkosg" path="res://materials/GrassNoiseMaterial.tres" id="4_pseda"]
[ext_resource type="Texture2D" uid="uid://vcebfpqo2ko7" path="res://assets/textures/player_bomb_mask.png" id="5_17c1g"]
[ext_resource type="PackedScene" path="res://scenes/PixelCountComputeShader.tscn" id="6_75vqy"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_5gauv"]
sky_energy_multiplier = 5.0
[sub_resource type="Sky" id="Sky_qfldx"]
sky_material = SubResource("ProceduralSkyMaterial_5gauv")
[sub_resource type="Environment" id="Environment_f3jci"]
background_mode = 2
sky = SubResource("Sky_qfldx")
ambient_light_source = 3
ambient_light_color = Color(0.521569, 0.521569, 0.521569, 1)
ambient_light_energy = 2.64
[sub_resource type="World3D" id="World3D_s7kgt"]
@@ -37,13 +45,22 @@ size = Vector3(4, 1, 0.4)
[node name="World" type="Node3D"]
script = ExtResource("1_gtcjp")
[node name="ScoreUpdateTimer" type="Timer" parent="."]
wait_time = 0.33
autostart = true
[node name="PixelCountComputeShader" parent="." instance=ExtResource("6_75vqy")]
paint_viewport_path = NodePath("../WorldColoringViewport")
[node name="Player1" parent="." instance=ExtResource("2_a343a")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.121184, 0)
color = Color(1, 0, 0, 1)
coloring_sprite_path = NodePath("../WorldColoringViewport/Player1Sprite")
coloring_bomb_sprite_path = NodePath("../WorldColoringViewport/Player1BombSprite")
[node name="Player2" parent="." instance=ExtResource("2_a343a")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.63193, 0, 0)
color = Color(0.878431, 0, 0.980392, 1)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.121184, 0)
color = Color(0, 0, 1, 1)
move_right_action = "move_right_p2"
move_left_action = "move_left_p2"
move_down_action = "move_down_p2"
@@ -72,22 +89,30 @@ own_world_3d = true
world_3d = SubResource("World3D_s7kgt")
transparent_bg = true
handle_input_locally = false
size = Vector2i(1000, 1000)
size = Vector2i(1024, 1024)
render_target_clear_mode = 1
render_target_update_mode = 4
[node name="Player1Sprite" type="Sprite2D" parent="WorldColoringViewport"]
modulate = Color(0.117647, 0.564706, 1, 1)
position = Vector2(500, 500)
texture = ExtResource("4_dipd5")
[node name="Player1BombSprite" type="Sprite2D" parent="WorldColoringViewport"]
visible = false
modulate = Color(0.117647, 0.564706, 1, 1)
position = Vector2(500, 500)
texture = ExtResource("5_17c1g")
[node name="Player2Sprite" type="Sprite2D" parent="WorldColoringViewport"]
modulate = Color(0.878431, 0, 0.980392, 1)
position = Vector2(530, 500)
texture = ExtResource("4_dipd5")
[node name="Player2BombSprite" type="Sprite2D" parent="WorldColoringViewport"]
visible = false
modulate = Color(0.878431, 0, 0.980392, 1)
position = Vector2(530, 500)
texture = ExtResource("5_17c1g")
[node name="Level" type="Node3D" parent="."]