Unexpected shader/image update latency when moving a sprite2D?

Godot Version

4.5

Question

Hello. I have a synchronisation problem within a process(). I am genuinely at loss as to how it could happen. See the video, look at how the sprite2D moves forth, but the sahder/shadows lags behind for a frame. But it should not be possible. I Suspect foul play with how the image of a Sprite2D is updated maybe it takes effect after a frame?:

I have a lighting system rendered onto a Sprite2D. Each pixel of that sprite2D corresponds to a tile of 8x8 pixels of the real world. Each frame the Sprite2D _process() does the following:

  1. updates its global_position to an coordinate multiple of Vect(8,8)

  2. Launches the shader for calculations, passing relevant data, including its current updated position

  3. When done, the shader sync() the output buffer

  4. The output buffer now contains the processed image data of the current frame. It is used to update the image for the Sprite2D

I genuinely do not understand why it’s happening. I had the same paradigm before, and it was working. I’ve set the sprite to move every second, and the shader to output the value of the position. then i’m printing both the input the position and the output of the shader for the frame before it moves, the frame that it moves at, and the frame right after, and they are as expected/consistant so i’m really at loss as to what might happen here. Any help would be welcome, i’m losing my mind here, i’m also updating the image texture of the sprite2D in the same process() after everything. the position i feed is a float, it doesn’t change if i make it into an int properly before passing it, the error remains.

You have very complex code and have chosen to give us screen shots of snippets of it. You also appear to be circumventing Godot’s 2D lighting system in preference to a homegrown one. Those few of us on here who might be able to answer your question are going to need more info.

  1. Show us you code and format it correctly. The template for posting a question literally showed you how to do that.
  2. I recommend you also tell us what you are trying to accomplish with this code, and why the built-in system didn’t provide it to you.
  3. Explain what you mean when you say, “I had the same paradigm before, and it was working.” In another version of Godot? Which one? In Unity, Unreal, or another Engine? What changed that it stopped working?

```gd
# How to format code
```

  1. i’ll paste the relevant code below
  2. I wanted a lighting system that works per quarter of tiles (my tiles are 16x16) that also included the ability to have partial shadows from shadowcasters. The system is composed of “light nodes” extended from the 2D node class and of a single lighting module extended from a Sprite2D. The lightnodes sends their data (position, intensity, light color, …) when needed to the module. The module is just Sprite2D overlayed on the screen and blended with the image under. each frame the module proceeds as i described in my original message.
  3. sorry, that was unclear. I mean that i have a previous iteration of coding that system in the same version of godot, it worked similarly but it wasn’t paralellized well so sluggish. I’ll send it in another message

```

class_name CustomLight
extends Node2D

@onready var player : Player = self.get_tree().current_scene.get_node(“player”) #to check distance to rendering windo@onready
@onready var Light_module : LightModule = self.get_tree().current_scene.get_node(“LightModule”)

var screen_resolution : Vector2i =  GlobalValues.screen_resolution
var screen_tile_width: int = GlobalValues.screen_tile_width
var screen_tile_height: int = GlobalValues.screen_tile_height
var work_group_width : int = GlobalValues.work_group_width
var work_group_height : int = GlobalValues.work_group_height

#—variables@onready
@onready var player_coordinates : Vector2i = floor(player.global_position/8) #tile player_coordinates
var light_module_index : int

#—light parame@exporters—
@export var light_direction : Ve@export_rangetor2
@export_range(0, 3.64, 0.01) var light_angle : float = 3.64 #angle of the con@export of light
@export var light_tint : Color = Color(1.0, 1.0, 1.0, 1.0) #color picker, third v@export_rangelue is ignored
@export_range(0, 50, 0.1) var light_intens@export_rangety : float = 1
@export_range(0, 1, 0.01) var light_constant_falloff : float  = 1@export_range#falloff of the light
@export_range(0, 0.04, 0.0001) var light_linear_falloff : float  @export_range 0.005 #falloff of the light
@export_range(0, 0.001, 0.000001) var light_quadratic_falloff : fl@onreadyat  = 0.00005 #falloff of the light
@onready var light_color : Vector4 = Vector4(light_tint.r, light_tint.g, light_tint.b, light_intensity) #actual vector passed to the shaders

func _ready() → void:
Light_module.add_light_node(self)

#func _physics_process(delta: float) → void:
#player_coordinates = floor(player.global_position/8)

#func _process(delta: float) → void:
#if (self.global_position - player.global_position).x > screen_resolution.x/2 || (self.global_position - player.global_position).y > screen_resolution.y/2:
#light_angle
#light_direction = 2*(1+sin(Engine.get_frames_drawn()/70.))
#light_color.w = light_intensity
#if = 2*(1+sin(Engine.get_frames_drawn()/70.))
#light_direction = light_direction.rotated(0.01)
#light_direction = light_direction.normalized()
#if Engine.get_frames_drawn() %100 ==0:
#light_color = Vector4(randf(), randf(), randf(), light_intensity)
#l@onreadyght_module.colors[self] = light_color

func _exit_tree() → void:
Light_module.remove_light_node(light_module_index)

```

and

```



´@onready´

class_name LightModule
extends Sprite2D

@onready var player @onready Player = self.get_tree().current_scene.get_node(“player”) #access to the player (to check distance to rendering window)
@onready var player_coordinates : Vector2i  #tile player_coordinates
@onready var occluder_grabber : Area2D = self.get_node(“occluder_grabber”)

#—screen parameters—
var image_data : PackedByteArray
var screen_resolution : Vector2i =  GlobalValues.screen_resolution
var screen_tile_width: int = GlobalValues.screen_tile_width
var screen_tile_height: int = GlobalValues.screen_tile_height
var work_group_width : int = GlobalValues.work_group_width
var work_group_height : int = GlobalValues.work_group_height
var half_screen : Vector2 = -Vector2(screen_tile_width, screen_tile_height)*4
var image_pass : Image @export Image.create_empty(screen_tile_width,screen_tile@export_rangeheight,false,Image.FORMAT_RGBAF)
var image_texture: Ima@onreadyeTexture = ImageTexture.create_from_image(image_pass)

#—shader  parameters—
@export var ambient_light_tint : Color = Color(1,1,1,1)
@export_range(0, 50, 0.1) var ambient_light_intensity : float = 0.0
@onready var ambient_light_color : Vector4 = Vector4(ambient_light_tint.r, ambient_light_tint.g, ambient_light_tint.b, ambient_light_intensity) #actual vector passed to the shaders

var light_array : Array[CustomLight] #useless i think

var direction_array : PackedFloat32Array #stores Vector2
var angle_array : PackedFloat32Array
var color_array : PackedFloat32Array #stores Vector4
var falloff_factor_array : PackedFloat32Array
var position_array : PackedFloat32Array
var occluder_opacity_array : PackedFloat32Array
var occluder_size_array : PackedInt32Array
var triangularized_occluder_array : PackedFloat32Array

var occluder_nb : int
var occluders_total_size : int

#—shaders, pipelines, uniform sets—
var rd : RenderingDevice = GlobalValues.rd

var cast_shadow_shader_file := load(“res://shaders/CustomLight_V3_cast_shadow_shader.glsl”) #load GLSL shader
var cast_shadow_shader : RID = rd.shader_create_from_spirv(cast_shadow_shader_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var cast_shadow_pipeline : RID = rd.compute_pipeline_create(cast_shadow_shader) # Create a compute pipeline
var cast_shadow_uniform_set : RID

var shader_file := load(“res://shaders/CustomLight_V3_compute_shader.glsl”) #load GLSL shader
var shader : RID = rd.shader_create_from_spirv(shader_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var pipeline : RID = rd.compute_pipeline_create(shader) # Create a compute pipeline
var compute_list : int
var uniform_set : RID

var output_file := load(“res://shaders/CustomLight_V4_output_shader.glsl”) #load GLSL shader
var output_shader : RID = rd.shader_create_from_spirv(output_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var output_pipeline : RID = rd.compute_pipeline_create(output_shader) # Create a compute pipeline
var output_uniform_set : RID

#—bytes, buffers & uniforms—
var input_bytes : PackedByteArray #stores a collection of input data
var input_buffer : RID
var input_uniform : RDUniform
var direction_bytes : PackedByteArray #stores the light_directions
var direction_buffer : RID
var direction_uniform : RDUniform
var angle_bytes : PackedByteArray #stores the light_angles
var angle_buffer : RID
var angle_uniform : RDUniform
var color_bytes : PackedByteArray #stores the light_colors
var color_buffer : RID
var color_uniform : RDUniform
var falloff_factor_bytes : PackedByteArray #stores the light_falloff factors
var falloff_factor_buffer : RID
var falloff_factor_uniform : RDUniform
var position_bytes : PackedByteArray #stores the light position
var position_buffer : RID
var position_uniform : RDUniform
var triangularized_occluder_bytes : PackedByteArray #stores the triangularized occluders
var triangularized_occluder_buffer : RID
var triangularized_occluder_uniform : RDUniform
var cast_shadow_bytes : PackedByteArray #stores the cast shadows
var cast_shadow_buffer : RID
var cast_shadow_uniform : RDUniform
var occluder_bytes : PackedByteArray #stores the occluder polygon data as spawned by the chunks
var occluder_buffer : RID
var occluder_uniform : RDUniform
var occluder_size_bytes : PackedByteArray #stores the sizes of successive occluders
var occluder_size_buffer : RID
var occluder_size_uniform : RDUniform
var occluder_opacity_bytes : PackedByteArray #stores the opacity of the occluders
var occluder_opacity_buffer : RID
var occluder_opacity_uniform : RDUniform
var output_bytes : PackedByteArray #stores the output
var output_buffer : RID
var output_uniform : RDUniform
var ambient_bytes : PackedByteArray
var ambient_buffer : RID
var ambient_uniform : RDUniform

func _ready()->void:
self.scale = Vector2(8,8)
self.texture = image_texture
self.visible = false
self.get_child(0).position = Vector2(screen_tile_width/2, screen_tile_height/2)
output_bytes.resize(4screen_tile_widthscreen_tile_height*4)
output_buffer = rd.storage_buffer_create(output_bytes.size(),output_bytes)
global_position = half_screen
new_global_position = half_screen

print(global_position)
print(new_global_position)

func _physics_process(delta: float) → void:
pass

var new_global_position : Vector2
var int_pos : Vector2i
##input: /
##function: processes all the light nodes with shaders. Cast shadows are first calculated then the intensity per pixel
func _process(delta: float) → void:
if (Engine.get_frames_drawn()%120 == 0):
print(“\nnew change incoming:”)
print("coordinates: ", global_position)
if (Engine.get_frames_drawn()%120 == 1):
global_position = new_global_position
new_global_position += Vector2(8,8)
print("coordinates: ", global_position)
if (Engine.get_frames_drawn()%120 == 2):
print("coordinates: ", global_position)
int_pos = global_position.snapped(Vector2i(1,1))
print(“int_post:”, int_pos )
#global_position = half_screen + snapped(player.global_position,Vector2(8,8))

#var time_ms0 = Time.get_ticks_usec()
#var time_ms = Time.get_ticks_usec()

if(player.global_position.y>0):
	ambient_light_color.w = ambient_light_intensity*max(0,1-player.global_position.y/600.0) 

for i in range(light_array.size()):
	position_array[2*i] = light_array[i].global_position.x
	position_array[2*i+1] = light_array[i].global_position.y

input_bytes = PackedInt32Array([int_pos.x, int_pos.y, screen_tile_width, screen_tile_height]).to_byte_array()
direction_bytes = direction_array.to_byte_array()
angle_bytes  = angle_array.to_byte_array()
color_bytes = color_array.to_byte_array()
falloff_factor_bytes = falloff_factor_array.to_byte_array()
position_bytes = position_array.to_byte_array()
triangularized_occluder_bytes = PackedInt32Array([triangularized_occluder_array.size()/2, occluders_total_size*2*4]).to_byte_array() #t_o_vertex_nb, cast_shadow_vertex_nb
triangularized_occluder_bytes.append_array(triangularized_occluder_array.to_byte_array())
occluder_bytes = get_occluders().to_byte_array()
cast_shadow_bytes.resize(light_array.size() * occluders_total_size*4*2*4*4 ) #4 bytes per float32, 2 triangles per side, 4 vertex per triangle (counting opacity), 4 float32 per vertex (vec3 take vec4 size)
#print("OccluderGet + some stuff: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()
occluder_size_bytes = PackedInt32Array([light_array.size(), occluders_total_size*2*4, occluder_nb, 0]).to_byte_array() #light nb, cast_shadow_vertex_nb, occluder_nb, padding
occluder_size_bytes.append_array(occluder_size_array.to_byte_array())
occluder_opacity_bytes = occluder_opacity_array.to_byte_array()
ambient_bytes = PackedFloat32Array([ambient_light_color.x, ambient_light_color.y, ambient_light_color.z, ambient_light_color.w]).to_byte_array()

input_buffer = rd.storage_buffer_create(input_bytes.size(), input_bytes)
direction_buffer = rd.storage_buffer_create(direction_bytes.size(), direction_bytes)
angle_buffer = rd.storage_buffer_create(angle_bytes.size(), angle_bytes)
color_buffer = rd.storage_buffer_create(color_bytes.size(), color_bytes)
falloff_factor_buffer = rd.storage_buffer_create(falloff_factor_bytes.size(), falloff_factor_bytes)
position_buffer = rd.storage_buffer_create(position_bytes.size(), position_bytes)
triangularized_occluder_buffer = rd.storage_buffer_create(triangularized_occluder_bytes.size(), triangularized_occluder_bytes)
cast_shadow_buffer = rd.storage_buffer_create(cast_shadow_bytes.size(), cast_shadow_bytes)
occluder_buffer = rd.storage_buffer_create(occluder_bytes.size(), occluder_bytes)
occluder_size_buffer = rd.storage_buffer_create(occluder_size_bytes.size(), occluder_size_bytes)
occluder_opacity_buffer = rd.storage_buffer_create(occluder_opacity_bytes.size(), occluder_opacity_bytes)
ambient_buffer = rd.storage_buffer_create(ambient_bytes.size(),ambient_bytes) 

input_uniform = RDUniform.new() 
input_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
input_uniform.binding = 0
input_uniform.add_id(input_buffer)

direction_uniform = RDUniform.new() 
direction_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
direction_uniform.binding = 1
direction_uniform.add_id(direction_buffer)

angle_uniform = RDUniform.new() 
angle_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
angle_uniform.binding = 2
angle_uniform.add_id(angle_buffer)

color_uniform = RDUniform.new() 
color_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
color_uniform.binding = 3
color_uniform.add_id(color_buffer)

falloff_factor_uniform = RDUniform.new() 
falloff_factor_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
falloff_factor_uniform.binding = 4
falloff_factor_uniform.add_id(falloff_factor_buffer)

position_uniform = RDUniform.new()
position_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
position_uniform.binding = 5
position_uniform.add_id(position_buffer)

triangularized_occluder_uniform = RDUniform.new()
triangularized_occluder_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
triangularized_occluder_uniform.binding = 6
triangularized_occluder_uniform.add_id(triangularized_occluder_buffer)

cast_shadow_uniform = RDUniform.new()
cast_shadow_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
cast_shadow_uniform.binding = 7
cast_shadow_uniform.add_id(cast_shadow_buffer)

output_uniform = RDUniform.new()
output_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
output_uniform.binding = 8
output_uniform.add_id(output_buffer)

occluder_uniform = RDUniform.new()
occluder_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_uniform.binding = 9
occluder_uniform.add_id(occluder_buffer)

occluder_size_uniform = RDUniform.new()
occluder_size_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_size_uniform.binding = 10
occluder_size_uniform.add_id(occluder_size_buffer)

occluder_opacity_uniform = RDUniform.new()
occluder_opacity_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_opacity_uniform.binding = 11
occluder_opacity_uniform.add_id(occluder_opacity_buffer)

ambient_uniform = RDUniform.new()
ambient_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
ambient_uniform.binding = 12
ambient_uniform.add_id(ambient_buffer)

uniform_set = rd.uniform_set_create([input_uniform, direction_uniform, angle_uniform, color_uniform, falloff_factor_uniform, 
position_uniform, triangularized_occluder_uniform, cast_shadow_uniform, output_uniform], shader, 0)
cast_shadow_uniform_set = rd.uniform_set_create([position_uniform, cast_shadow_uniform, occluder_uniform, occluder_size_uniform, 
occluder_opacity_uniform], cast_shadow_shader, 1)
output_uniform_set = rd.uniform_set_create([ambient_uniform, output_uniform], output_shader, 2)

#print("Uniforms + some buffers set up: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()

var res : int = occluders_total_size/64 +1
compute_list = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, cast_shadow_pipeline) 
rd.compute_list_bind_uniform_set(compute_list, cast_shadow_uniform_set, 1)
rd.compute_list_dispatch(compute_list, res, light_array.size(), 1)
rd.compute_list_add_barrier(compute_list)
rd.compute_list_bind_compute_pipeline(compute_list, pipeline) 
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
rd.compute_list_dispatch(compute_list, work_group_width*work_group_height/4, light_array.size(), 1) #TODO back to light light_array.size()
rd.compute_list_add_barrier(compute_list)
rd.compute_list_bind_compute_pipeline(compute_list, output_pipeline) 
rd.compute_list_bind_uniform_set(compute_list, output_uniform_set, 2)
rd.compute_list_dispatch(compute_list, work_group_width*work_group_height, 1, 1)
rd.compute_list_end()
rd.submit()
rd.sync()

#print("Shader done: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()

output_bytes = rd.buffer_get_data(output_buffer)
image_pass.set_data(screen_tile_width, screen_tile_height, false,Image.FORMAT_RGBAF, output_bytes)
image_texture.update(image_pass)

if (Engine.get_frames_drawn()%120 == 0 || Engine.get_frames_drawn()%120 == 1|| Engine.get_frames_drawn()%120 == 2):
	print("passed coordinates: (", output_bytes.to_float32_array()[0], "," ,output_bytes.to_float32_array()[1], ")")
	print("--synced--")
	
#print("grabbing the data: ", float(Time.get_ticks_usec() -time_ms)/1000)
#print("Total time taken in ms: ", float(Time.get_ticks_usec() -time_ms0)/1000, "\n")

#note: for the rasterizer see: 
 and for the cast shadows: https://www.gamedev.net/tutorials/_/technical/graphics-programming-and-theory/dynamic-2d-soft-shadows-r3065/

##input: /
##function: outputs polygons of occluders within range of the light source in the form [vertex_1.x, vertex_1.y, vertex_2.x, … ]. Also update related variables
func get_occluders()-> PackedFloat32Array:
var acc : int = 0
var occluders : PackedFloat32Array = 

var overlapping_nodes := occluder_grabber.get_overlapping_bodies()
occluder_opacity_array.clear()
occluder_size_array.clear()
triangularized_occluder_array.clear()
for node in overlapping_nodes:
if node.get_script() != null && node.get_script().get_global_name() == “SemiTranspBody2D”:
occluder_opacity_array.append(node.transparency)
map_add_position(occluders, node.get_child(0)) #should that work? on some i do get_node(“hitbox”) but somehow here it’s not needed?
acc += node.get_child(0).polygon.size()
occluder_size_array.append(acc)
triangularize_polygon(node.global_position, node.get_child(0).polygon, node.transparency)
elif node.get_class() == “StaticBody2D”:
occluder_opacity_array.append(0)
map_add_position(occluders, node.get_child(0))
acc += node.get_child(0).polygon.size()
occluder_size_array.append(acc)
elif node.get_class() == “CharacterBody2D” && !(node == get_parent()): #TODO repair that, somehow it bugs out - smth to do with going too far in the array of triangles i think?
occluder_opacity_array.append(0)
map_add_position(occluders, node.get_node(“hitbox”))
acc += node.get_node(“hitbox”).polygon.size()
occluder_size_array.append(acc)
occluders_total_size = occluders.size()/2 # counts the total size of occluders to resize the array properly. equal to #input:
occluder_nb = overlapping_nodes.size()
return occluders
#input: /

##input: a node with a polygon data in PackedFloat32Array form and an array
##function: appends the polygons slid by the position of the node to the array
func map_add_position(input_array : PackedFloat32Array, node : Node2D) → void:
var return_array : PackedFloat32Array
return_array.resize(2node.polygon.size())
for i in range(0, node.polygon.size()):
return_array[2i] = node.polygon[i].x + node.global_position.x
return_array[2*i+1] = node.polygon[i].y + node.global_position.y
input_array.append_array(return_array)
#function: /

##input: a node with a polygon data
##function: outputs a triangularized polygon in an array in which triangles take the form [vertex1.x, vertex1.y, … vertex3.y, opacity, 0]
func triangularize_polygon(obj_position : Vector2, poly : PackedVector2Array, opacity : float) → void:
var triangulated_index = Geometry2D.triangulate_polygon(poly)
for i in range(triangulated_index.size()/3):
triangularized_occluder_array.append_array(PackedFloat32Array(
[obj_position.x + poly[triangulated_index[3i]].x, obj_position.y + poly[triangulated_index[3i]].y,
obj_position.x + poly[triangulated_index[3i+2]].x, obj_position.y + poly[triangulated_index[3i+2]].y,
obj_position.x + poly[triangulated_index[3i+1]].x, obj_position.y + poly[triangulated_index[3i+1]].y,
opacity, 0]
))
return
#note: /

##input: a light node to add to the list
##function: adds it then rebuild the light property arrays
func add_light_node(node : CustomLight) → void:
node.light_module_index = light_array.size()
light_array.append(node)
rebuild_light_arrays(light_array.size()-1)
#function: /

##input: the index of a light to remove from the list
##function: removes it then rebuild the light property arrays
func remove_light_node(index : int) → void:
light_array.pop_at(index)
for i in range(index, light_array.size()):
light_array[i].light_module_index = i
rebuild_light_arrays(index)
#input: /

##input: an index from which to start rebuilding the property arrays
##function: properly resize and fills the arrays
func rebuild_light_arrays(start_index : int = 0) → void:
position_array.resize(2light_array.size())
direction_array.resize(2light_array.size())
angle_array.resize(light_array.size())
color_array.resize(4light_array.size())
falloff_factor_array.resize(3light_array.size())
for i in range(start_index, light_array.size()):
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
angle_array[i] = light_array[i].light_angle
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3*i+2] = light_array[i].light_quadratic_falloff
#note: /

##function: the index of a light node and an integer representing a value to update
##function: updates the value of that light
func update_light_value(i : int, value : int = -1) → void :
match value:
-1:
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
angle_array[i] = light_array[i].light_angle
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3i+2] = light_array[i].light_quadratic_falloff
0:
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
1:
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
2:
angle_array[i] = light_array[i].light_angle
3:
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
4:
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3i+2] = light_array[i].light_quadratic_falloff
#input: /

##input: /
##function: free rids before exiting the tree
func _exit_tree() → void:
rd.free_rid(cast_shadow_shader)
rd.free_rid(cast_shadow_pipeline)
rd.free_rid(cast_shadow_uniform_set)
rd.free_rid(shader)
rd.free_rid(pipeline)
rd.free_rid(uniform_set)
rd.free_rid(output_shader)
rd.free_rid(output_pipeline)
rd.free_rid(output_uniform_set)
rd.free_rid(input_buffer)
rd.free_rid(direction_buffer)
rd.free_rid(angle_buffer)
rd.free_rid(color_buffer)
rd.free_rid(falloff_factor_buffer)
rd.free_rid(position_buffer)
rd.free_rid(triangularized_occluder_buffer)
rd.free_rid(cast_shadow_buffer)
rd.free_rid(occluder_buffer)
rd.free_rid(occluder_size_buffer)
rd.free_rid(occluder_opacity_buffer)
rd.free_rid(output_buffer)
rd.free_rid(ambient_buffer)
#note: /

```

i will post a link to an online repository a little bit later, i’m doing something else right now and trying to do it in a rush is a bad idea

1 Like

I ended up solving my issue. The problem came from that i did not properly reset the output buffer containing the end image at the end of frames, causing the lighting of last frame to be taken into account.

Making a new minimal reproduction project did show me another bug that i fixed, so it wasn’t wasted efforts thankfully.

1 Like