157 lines
4.1 KiB
GDScript
157 lines
4.1 KiB
GDScript
|
# 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()
|