Passing an image to a compute shader?

Godot Version

v4.5.1.stable.steam [f62fdbde1]

Question

How do I connect a Texture2D to a compute shader in Godot? I want to do some computations on some stuff, and adjust the image according to the results, but doing it every frame.

I keep getting various errors depending on what I try to set up and pass a texture or image to the compute shader and I don’t understand why or how to get it through.

Relevant code snippets:

	var testimage = Image.create_empty(1920,1080,false,Image.FORMAT_RGBA8)
	testimage.fill(Color.BLUE)
	TestTexture = ImageTexture.create_from_image(testimage)

#Uniform 1 passes along just fine at the pipeline.
	var uniform := RDUniform.new()
	uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
	uniform.binding = 0 
	uniform.add_id(buffer1)
#This gives me errors no matter what UNIFORM_TYPE I try.
	var uniform2 := RDUniform.new()
	#uniform2.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
	uniform2.uniform_type = RenderingDevice.UNIFORM_TYPE_TEXTURE
	uniform2.binding = 1 
	uniform2.add_id(TestTexture.get_rid())
	var uniform_set := rd.uniform_set_create([uniform,uniform2], shader, 0) # the last parameter (the 0)

I keep getting claims that I’m not passing a “valid texture” or “valid image” to the shader. I’ve read setting up a Sampler2D might help, but I thought directly referencing the image would be better.

Are you using the main rendering device?

I think so?
Here’s a fuller version of the code, minus the glsl shader:

extends Node2D
var rd := RenderingServer.create_local_rendering_device()
var buffer1 : RID
var buffer2 : RID
var shader_file = load(“res://ShaderTest/BasicTest/GodotTestShader2.glsl”) #PUT PATH HERE
var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
var constants = [1080,1924] #Array of constants to pass along to compute file
var level_image = Image.new()

func _ready()->void:
	var shader := rd.shader_create_from_spirv(shader_spirv)
	var input := PackedInt32Array([1,0])
	input.resize(1080*1920)
	input.fill(0)
	var input_bytes := input.to_byte_array()
	buffer1 = rd.storage_buffer_create(input_bytes.size(), input_bytes)

	var uniform := RDUniform.new()
	uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
	uniform.binding = 0 # this needs to match the "binding" in our shader file
	uniform.add_id(buffer1)


	var testimage = Image.create_empty(1920,1080,false,Image.FORMAT_RGB8)
	testimage.fill(Color.BLUE)
	var texture_test = ImageTexture.create_from_image(testimage)
	buffer2 = texture_test.get_rid()
	var uniform2 := RDUniform.new()

	uniform2.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
	uniform2.binding = 1 # this needs to match the "binding" in our shader file
	uniform2.add_id(buffer2)
	var uniform_set := rd.uniform_set_create([uniform,uniform2], shader, 0) # the last parameter (the 0)
# Create a compute pipeline
	var pipeline := rd.compute_pipeline_create(shader,_create_specialization_constants(constants))
	var compute_list := rd.compute_list_begin()
	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,2160,1,1)
	rd.compute_list_end()

func _create_specialization_constants(constant_array : Array)->Array[RDPipelineSpecializationConstant]:
	var new_array : Array[RDPipelineSpecializationConstant] = 

	for i in constant_array.size():
		var constant := RDPipelineSpecializationConstant.new()
		constant.constant_id = i
		constant.value = constant_array[i]
		new_array.push_back(constant)
	return new_array

func free() → void:
	rd.free_rid(buffer1)
	rd.free_rid(buffer2)
	rd.free()

The shader itself is:

#[compute]
#version 450

layout(constant_id=0) const int map_size_x = 1920; //takes our constant_id from godot and assigns it to CONSTANT_0, with 10 as a default if nothing is assigned.
layout(constant_id=1) const int map_size_y = 1080;
layout(local_size_x = 960, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0, std430) restrict buffer MyDataBuffer {
    	uint data[];
}
my_data_buffer;
layout(set = 0, binding = 1) uniform texture1D my_texture;

I’ve tried layout(set=0,binding=1) uniform uimage2D as well as other formats and no matter what combinations I do in the shader and the GDScript, I seem to keep missing something major.

Edited code formatting

You’re creating a new logical device. Afaik, it’s not possible to share buffers between different devices. Your best bet is to get the texture data from one device and re-create the texture on another.

1 Like

Ok, so is there an easy way to do that, or a particular tutorial you can point me towards? I’ve had trouble finding good documentation on passing between GDScript and a compute shader.

Or do you think it’d be simpler and easier to simply pass the image data to the compute shader (one of very few things I’ve managed to figure out at this point), play with the raw data, then pass it back to GDScript and reconvert the data to an image?

You already create the image data on the cpu. Don’t make a texture object using ImageTexture.create_from_image() because that sends the image data to the main rendering device and the RID gotten from there will be useless on the rendering device you’re running the shader on. Instead use texture_create() on the rendering device that’s doing the compute. You’ll have to populate the RDTextureFormat object but it’s pretty straightforward.

1 Like

Thank you! Working on this and running into some hurdles, but I think I can figure this out now that I know what direction to go in now that I have one.

If you have problems with what exactly to put into RDTextureFormat, create the texture normally, obtain its rendering device RID via RenderingServer::texture_get_rd_texture(), obtain the main rendering device via RenderingServer::get_rendering_device() and then call RenderingDevice::texture_get_format() on the main rendering device. This will return properly set up RDTextureFormat. You can look at the values used there.

1 Like