• Godot HelpShaders
  • Help with compute shaders. Using structs in a buffer that contain arrays.

I am currently struggling to correctly encode a PackedByteArray() for this buffer:

#define MAX_POINTS 9

struct Arc {
	vec3 points[MAX_POINTS];
};

layout(set = 0, binding = 5, std430) writeonly buffer Arcs {
	Arc data[];
} arcs;

I know it's complex to use structs as data in buffers. I am making it even more complex by also having an array in the struct but I felt like this may be a better workaround than providing a multi-dimensional array.

Is it possible to decode an array of structs that also contain an array?

Or better yet is it possible to setup a RDUniform so that I can write to a vec3[][] array? (would be my preferred way)

I found this thread which is very similar in what I want todo except for me I have a struct with an array:
https://godotforums.org/d/33761-compute-shader-buffer-update-confusion

  • xyz replied to this.
  • HuskyDreaming Here's the function. Didn't test it but it should work:

    func decode_to_vector3_array(buffer: PackedByteArray) -> PackedVector3Array:
    	var size = buffer.size() / 12 # number of Vector3s in the buffer
    	var header = PackedInt32Array([36, size]).to_byte_array() # first int is type id, second is the element count
    	return bytes_to_var(header + buffer) 

    xyz Yeah it's a fixed array size. The number of points will always be fixed as I do not need that many vertices/points) for the arc. This is what the code is doing for the compute shader (in the main function) and how the array gets assigned.

    void main() {
    	int index = int(gl_GlobalInvocationID.x);
    	if (index >= params.numRoutes) return;
    
    	float radius = params.sphereRadius;
    
    	float time = times.data[index];
    	float bearing = bearings.data[index];
    	float harversine_distance = distances.data[index];
    	vec2 coordinate = coordinates.data[index];
    
    	vec3 points[MAX_POINTS];
    	for (int i = 0; i < int(MAX_POINTS); i++) {
    		float fraction = i / float(MAX_POINTS);
    		float angle = fraction * time * harversine_distance;
    	
    		float interpolated_lat_a = sin(coordinate.x) * cos(angle);
    		float interpolated_lat_b = cos(coordinate.x) * sin(angle) * cos(bearing);
    		float interpolated_lat   = asin(interpolated_lat_a + interpolated_lat_b);
    
    		float interpolated_lon_a = sin(bearing) * sin(angle) * cos(coordinate.x);
    		float interpolated_lon_b = cos(angle) - sin(coordinate.x) * sin(interpolated_lat);
    		float interpolated_lon   = coordinate.y + atan(interpolated_lon_b, interpolated_lon_a);
    
    		float interpolated_x = radius * cos(interpolated_lat) * sin(interpolated_lon);
    		float interpolated_y = radius * sin(interpolated_lat);
    		float interpolated_z = radius * cos(interpolated_lat) * cos(interpolated_lon);
    
    		points[i] = vec3(interpolated_x, interpolated_y, interpolated_z);
    	}
    	
    	arcs.data[index].points = points;
    }

    The problem is, I am really confused on how to decode the data from the buffer:

    	# Retrieve data from the buffer
    	var output_bytes := rd.buffer_get_data(buffer) # The arcs buffer
    	for i in range(output_bytes.size()):
    		push_warning(output_bytes[i])
    • xyz replied to this.

      xyz Thank you I looked into it and managed to get the encoder and decoder to work. Still very new to godot so still trying to find all the built in functions haha:

      Anyways here is the encoding part:

      # Points 
      var points_array := PackedByteArray()
      for i in range(route_count):
      	
      	var points : Array[Vector3] = []
      	var arc = Arc.new(points)
      	var data := PackedByteArray()
      	
      	for point in arc.points:
      		data.append_array(var_to_bytes(point))
      	points_array.append_array(data)
      
      var points_array_size = route_count * globe_data.points
      var points_buffer := rd.storage_buffer_create(4 * 3 * points_array_size, points_array)
      var uniform_points:= RDUniform.new()
      uniform_points.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
      uniform_points.binding = 5
      uniform_points.add_id(points_buffer)

      and here is the decoding part of the code:

      func decode_storage_buffer(buffer: PackedByteArray) -> Array:
      	var arcs : Array[Arc] = []
      	for i in range(route_count):
      		var points : Array[Vector3] = []
      		for j in range(globe_data.points):
      			var index = (i * globe_data.points + j) * 12
      			var x = buffer.decode_float(index)
      			var y = buffer.decode_float(index + 4)
      			var z = buffer.decode_float(index + 8)
      			points.append(Vector3(x, y, z))
      		arcs.append(Arc.new(points))
      	return arcs

      Still need to test if this is working but looks promising as I am getting an array of calculated vectors

      Edit: Could have just left out populating the array when encoding but I just wanted to give an example incase people want to add populated arrays into the buffer.

      • xyz replied to this.

        HuskyDreaming and here is the decoding part of the code:

        Great, although iterating over individual floats and populating the array in GDScript might be a bit slow. Especially if you have a large buffer and need to do it every frame. In that case it may even defeat the purpose of using a compute shader. It'd be better to convert the whole buffer into a PackedVector3Array with just one bytes_to_var() call and then just slice out individual sub-arrays. Note that you'll need to push an additional 8 byte header to the beginning of the original byte buffer in order for bytes_to_var() to interpreted the data correctly.

          xyz Yeah good point. I was expecting it to not really be fast due to iterating over each float again. I will look into the PackedVector3Array. Thanks for all the help!

          • xyz replied to this.

            HuskyDreaming I just noticed you actually need Vector3 arrays, so best to immediately convert to PackedVector3Array (edited my previous post)

            HuskyDreaming Here's the function. Didn't test it but it should work:

            func decode_to_vector3_array(buffer: PackedByteArray) -> PackedVector3Array:
            	var size = buffer.size() / 12 # number of Vector3s in the buffer
            	var header = PackedInt32Array([36, size]).to_byte_array() # first int is type id, second is the element count
            	return bytes_to_var(header + buffer) 

              xyz You are saying I just need to change the decoding part? I can keep the buffer inside the compute shader to be the same as is? and by passing in 8 bytes do you mean update the storage buffer size to be 4 * 3 * points_array_size * 8?

              • xyz replied to this.

                HuskyDreaming This has nothing to do with the shader or the buffer. It's just a fast way to convert packed byte data (shader's output in this case) into GDScript vectors. The function doesn't do anything to the original buffer. It only reads from it. Adding those 8 bytes creates a copy that's passed to bytes_to_var(). So yeah, everything else stays the same.

                  xyz That makes sense. Thank you the above decode function works great. I am doing more testing at the moment.

                  xyz I seem to have run into an issue for some reason the data I am getting back seems to be incorrect.

                  This is the data I expect:

                  [(-0.032415, -0.840178, -0.541341), (-0.034749, -0.813649, -0.580316), (-0.037005, -0.785307, -0.617999), (-0.039179, -0.755216, -0.654305), (-0.041266, -0.723441, -0.689152), (-0.04326, -0.690054, -0.722464), (-0.045159, -0.65513, -0.754166), (-0.046956, -0.618745, -0.784187), (-0.048649, -0.580982, -0.812461)]

                  This is the data I am receiving:
                  [(-0.032414, -0.84018, -0.541338), (0, -0.032414, -0.84018), (-0.541338, 0, -0.032414), (-0.84018, -0.541338, 0), (-0.032414, -0.84018, -0.541338), (0, -0.032414, -0.84018), (-0.541338, 0, -0.032414), (-0.84018, -0.541338, 0), (-0.032414, -0.84018, -0.541338)]

                  Notice how the 0 shifts over every time? o.O

                  Edit: Both Arrays are 9 in size just so you know

                    HuskyDreaming The decoding is correct I can confirm this. However I am wondering if it's something todo with how the shader is written.

                    Well, it looks like the incorrect calculation in the shader. How did you calculate the expected data?

                    2 months later

                    layout(set = 0, binding = 5, std430)

                    The ‘std430’ means data is aligned with 16-bytes, i.e. four float value. That's why the data you actually received shifts a 0 for every vec3, it supplements the alignment automatically.

                    For correct data, you should use vec4 instead of vec3 in both gdscript side and compute shader side.

                    • xyz replied to this.