godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 92 forks source link

Add support for Shader Storage Buffer Objects (SSBO) #7516

Open 2fd5 opened 1 year ago

2fd5 commented 1 year ago

Describe the project you are working on

I'm working on a 2D game with lots of procedural generation and data-driven visual effects. Being able to efficiently pass arrays and struct data to shaders would allow more flexibility.

Describe the problem or limitation you are having in your project

Currently in Godot, the options for passing dynamic data to shaders are limited. Textures/samplers can work but require encoding data to textures. Uniforms only allow single values, not arrays or structs. This makes certain shader techniques difficult.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Implement shader storage buffer objects (SSBOs) to allow passing arrays, structs, and other custom data layouts to shaders.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The proposed interface is:

# GDScript

var ssbo = SSBO.new()
ssbo.layout = {
  step = 8, 
  stride = 8,
  attributes = [
    {name="velocities", type=TYPE_VEC3, count=1024},
    {name="matrices", type=TYPE_MAT4, count=16}
  ] 
}

ssbo.data = # array of data matching layout

material.set_shader_param("ssbo", ssbo)
//Shader

layout(std140) buffer Velocities {
  vec3 velocities[];
}; 

layout(std140) buffer Matrices {
  mat4 matrices[];
};

void fragment() {
  vec3 vel = velocities[index];
  mat4 mat = matrices[otherIndex]; 
}

Here is an example of generating arrays for two attributes in an SSBO and assigning them to ssbo.data:

# Generate velocity data
var vel_data = []
for i in range(1024):
  vel_data.append(Vector3(randf(), randf(), randf()))

# Generate matrix data  
var mat_data = []
for i in range(16):
  mat_data.append(Matrix4()) # initialize to identity 

# Assign to SSBO  
ssbo.data = {
  "velocities": vel_data,
  "matrices": mat_data
}

The key points:

The shader can then index into each array by name.

Allow to access data directly in SSBO like

# Access the velocities array 
ssbo.data["velocities"][0] = Vector3(10, 0, 0)

# Access the matrices array
ssbo.data["matrices"][5] = Matrix4.IDENTITY 

# Get a matrix
var m = ssbo.data["matrices"][2]

If this enhancement will not be used often, can it be worked around with a few lines of script?

This could be worked around by encoding data in textures, but that is less flexible and optimal. Uniforms are limited to single values. SSBO provides the right level of abstraction.

Is there a reason why this should be core and not an add-on in the asset library?

Efficient data passing is a core graphics need. Textures work for some cases but are not optimal for all data. SSBO fills this gap and brings Godot up to par with other modern engines.

Calinou commented 1 year ago

Note that SSBOs don't have an equivalent in OpenGL ES 3.0, so any shaders using those will not work when using the Compatibility rendering method.

Uniforms only allow single values, not arrays or structs.

Uniform arrays are supported since 4.0, though only with a predetermined size: https://github.com/godotengine/godot-proposals/issues/931

2fd5 commented 1 year ago

Hello, I appreciate you pointing out the existing uniform array functionality added in Godot 4.0. I was unaware of that addition previously, so thank you for bringing it to my attention.

As you noted, shader storage buffers do not have an OpenGL ES 3.0 equivalent. I did not realize this initially. As such, any shaders utilizing SSBOs would only be compatible with the Forward+ rendering path in Godot.

With that technical context in mind, here is an updated summary of the proposal:

Add support for shader storage buffers (SSBO) in Godot's Vulkan renderer, to enable passing of dynamically-sized arrays, structs, and custom data layouts to shaders. This would facilitate more advanced data-driven effects compared to predefined uniform arrays. The limitation is shaders using SSBOs would only work with Forward+ rendering.

Please provide any additional feedback on improving this proposal while preserving the core intent! I'm open to refining it based on the helpful information you've provided regarding Godot's rendering architecture and capabilities.

clayjohn commented 1 year ago

The current state of things right now is that Godot does support SSBO's, but only with the RD renderers and only when using RD rendering commands (i.e. they don't work in GDShaders).

That being said, I am wary of adding significant complexity to GDShaders that will be inaccessible to many users. Especially since it is unclear what benefit SSBO's would have in this context.

As a reminder, SSBOs function like Uniform Buffers except they are (potentially) slower on some hardware, and have a much larger size limit. You can also write to them from compute shaders just like images, in fact, most GPUs use the same instructions for reading/writing SSBOs as they do for reading/writing images (the performance is the same).

In light of the above, it seems like the benefit of SSBOs is that they are a little easier to write to from CPU-side than images (but more cumbersome than uniform arrays), and they don't have the size limit of uniform arrays.

To me, that niche benefit doesn't really seem worth adding the complexity. Especially since uniform buffers and images are supported on all hardware and all of our backends.

PureAsbestos commented 1 year ago

Is this related to #6989?

BovineOx commented 1 year ago

+1 ideally would be good - I am looking to port my compute shader and frag shaders from Unity and I am reliant on structured buffers, though I did have a hacky version working using textures, I suppose I could use that or I could use lots of structured buffers.

bndbsh commented 9 months ago

As a reminder, SSBOs function like Uniform Buffers except they are (potentially) slower on some hardware, and have a much larger size limit. You can also write to them from compute shaders just like images, in fact, most GPUs use the same instructions for reading/writing SSBOs as they do for reading/writing images (the performance is the same).

In light of the above, it seems like the benefit of SSBOs is that they are a little easier to write to from CPU-side than images (but more cumbersome than uniform arrays), and they don't have the size limit of uniform arrays.

The main benefit for me is that they are possible to write to from other shaders, making it possible to offload more work to the GPU and share state across stages and passes. In addition to not having the same size limit, they are dynamically sized which allows for more generic shaders.

To me, that niche benefit doesn't really seem worth adding the complexity. Especially since uniform buffers and images are supported on all hardware and all of our backends.

I use shader buffers all the time and would not consider them niche, but perhaps I'm an outlier. I agree that their overall benefits are less clear when one is not in control of the full rendering pipeline as is the case with Godot, but I still think a simple interface (e.g. as flat buffers of primitives like float rather than structs as in GLSL) to shader buffers can bring a lot to the table.

Khasehemwy commented 5 months ago

Yes, we do need similar functions. The current uniform size limit is quite strict. For example, I want to draw dynamic movements of many instances, I'm going to pass two large buffers (vec3, represent positions for every instance) to the shader, and pass a ratio (float) to interpolate the position. It will be much simpler if the shader supports Storage Buffer. Now I still don't know how to implement this conveniently (not consider particles).

ODtian commented 5 months ago

Seems storage buffer (If i recognize it correctly, it is ssbo) is already defined for compute shader and can be add to uniform set just like other uniforms. Maybe the only issue here is define those we need in gdshader. Looks good!

lemonJumps commented 5 months ago

I'm not gonna lie, but it would be nice to be able to use them in gdshader. the structured data is one thing, but for me it's the ability to use these buffers as persistent storage for filtering between frames or share data across other shaders. I'd love to use them for things like thermal or infrared night vision, or custom on screen effects. And in combination with rendering server hooks you could tell the post processing shaders where easch object/object type is in the image. Just imagine the possibilities. The other thing is, using purely rendering device, in my limited experience with it, essentially removes everything that rendering server does for you, including the preview in editor. Which is fine if you're writing graphics from ground up, but it really sucks if all you want is to add few effects like hologram borders.

ODtian commented 5 months ago

I'm not gonna lie, but it would be nice to be able to use them in gdshader. the structured data is one thing, but for me it's the ability to use these buffers as persistent storage for filtering between frames or share data across other shaders. I'd love to use them for things like thermal or infrared night vision, or custom on screen effects. And in combination with rendering server hooks you could tell the post processing shaders where easch object/object type is in the image. Just imagine the possibilities. The other thing is, using purely rendering device, in my limited experience with it, essentially removes everything that rendering server does for you, including the preview in editor. Which is fine if you're writing graphics from ground up, but it really sucks if all you want is to add few effects like hologram borders.

@lemonJumps As what I know, maybe you can try the new CompositorEffect in 4.3. Basically you can insert some render device code into pipeline every frame and get exist buffer like depth/scene color or calculate yours using compute shader, you can also bind a rd buffer to a Texture2DRD to use these buffers (without writing back to CPU) in gd shader.

I haven't tried these already, but probably it will solve your problem.