post_process_grayscale.gd 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. @tool
  2. extends CompositorEffect
  3. class_name PostProcessGrayScale
  4. var rd: RenderingDevice
  5. var shader: RID
  6. var pipeline: RID
  7. func _init() -> void:
  8. effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
  9. rd = RenderingServer.get_rendering_device()
  10. RenderingServer.call_on_render_thread(_initialize_compute)
  11. # System notifications, we want to react on the notification that
  12. # alerts us we are about to be destroyed.
  13. func _notification(what: int) -> void:
  14. if what == NOTIFICATION_PREDELETE:
  15. if shader.is_valid():
  16. # Freeing our shader will also free any dependents such as the pipeline!
  17. rd.free_rid(shader)
  18. #region Code in this region runs on the rendering thread.
  19. # Compile our shader at initialization.
  20. func _initialize_compute() -> void:
  21. rd = RenderingServer.get_rendering_device()
  22. if not rd:
  23. return
  24. # Compile our shader.
  25. var shader_file := load("res://post_process_grayscale.glsl")
  26. var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
  27. shader = rd.shader_create_from_spirv(shader_spirv)
  28. if shader.is_valid():
  29. pipeline = rd.compute_pipeline_create(shader)
  30. # Called by the rendering thread every frame.
  31. func _render_callback(p_effect_callback_type: EffectCallbackType, p_render_data: RenderData) -> void:
  32. if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_TRANSPARENT and pipeline.is_valid():
  33. # Get our render scene buffers object, this gives us access to our render buffers.
  34. # Note that implementation differs per renderer hence the need for the cast.
  35. var render_scene_buffers := p_render_data.get_render_scene_buffers()
  36. if render_scene_buffers:
  37. # Get our render size, this is the 3D render resolution!
  38. var size: Vector2i = render_scene_buffers.get_internal_size()
  39. if size.x == 0 and size.y == 0:
  40. return
  41. # We can use a compute shader here.
  42. @warning_ignore("integer_division")
  43. var x_groups := (size.x - 1) / 8 + 1
  44. @warning_ignore("integer_division")
  45. var y_groups := (size.y - 1) / 8 + 1
  46. var z_groups := 1
  47. # Create push constant.
  48. # Must be aligned to 16 bytes and be in the same order as defined in the shader.
  49. var push_constant := PackedFloat32Array([
  50. size.x,
  51. size.y,
  52. 0.0,
  53. 0.0,
  54. ])
  55. # Loop through views just in case we're doing stereo rendering. No extra cost if this is mono.
  56. var view_count: int = render_scene_buffers.get_view_count()
  57. for view in view_count:
  58. # Get the RID for our color image, we will be reading from and writing to it.
  59. var input_image: RID = render_scene_buffers.get_color_layer(view)
  60. # Create a uniform set, this will be cached, the cache will be cleared if our viewports configuration is changed.
  61. var uniform := RDUniform.new()
  62. uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
  63. uniform.binding = 0
  64. uniform.add_id(input_image)
  65. var uniform_set := UniformSetCacheRD.get_cache(shader, 0, [uniform])
  66. # Run our compute shader.
  67. var compute_list := rd.compute_list_begin()
  68. rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
  69. rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
  70. rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4)
  71. rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups)
  72. rd.compute_list_end()
  73. #endregion