Add dependencies

This commit is contained in:
Garrett Gunnell 2025-09-01 14:25:15 -07:00
parent 62d012f9c0
commit 8674f46fe0
8 changed files with 467 additions and 1 deletions

@ -1 +0,0 @@
Subproject commit 9a5aa6925481aa9db1428f7c7b93d80565807152

View file

@ -0,0 +1,24 @@
#kernel ExposureExample
layout(rgba16f, set = 0, binding = 0) uniform image2D _RenderTarget;
layout(binding = 1) uniform UniformBufferObject {
vec4 _Exposure;
};
layout(push_constant, std430) uniform Params {
vec2 raster_size;
};
[numthreads(8, 8, 1)]
void ExposureExample() {
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = ivec2(raster_size);
if (uv.x >= size.x || uv.y >= size.y) return;
vec3 color = imageLoad(_RenderTarget, uv).rgb * _Exposure.gba * _Exposure.r;
imageStore(_RenderTarget, uv, vec4(color, 1.0));
}

View file

@ -0,0 +1,64 @@
@tool
extends CompositorEffect
class_name ExposureCompositorEffect
@export_group("Shader Settings")
@export var exposure = Vector4(2, 1, 1, 1)
var rd : RenderingDevice
var exposure_compute : ACompute
func _init():
effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
rd = RenderingServer.get_rendering_device()
# To make use of an existing ACompute shader we use its filename to access it, in this case, the example compute shader file is 'exposure_example.acompute'
exposure_compute = ACompute.new('exposure_example')
func _notification(what):
if what == NOTIFICATION_PREDELETE:
# ACompute will handle the freeing of any resources attached to it
exposure_compute.free()
func _render_callback(p_effect_callback_type, p_render_data):
if not enabled: return
if p_effect_callback_type != EFFECT_CALLBACK_TYPE_POST_TRANSPARENT: return
if not rd:
push_error("No rendering device")
return
var render_scene_buffers : RenderSceneBuffersRD = p_render_data.get_render_scene_buffers()
if not render_scene_buffers:
push_error("No buffer to render to")
return
var size = render_scene_buffers.get_internal_size()
if size.x == 0 and size.y == 0:
push_error("Rendering to 0x0 buffer")
return
var x_groups = (size.x - 1) / 8 + 1
var y_groups = (size.y - 1) / 8 + 1
var z_groups = 1
# Vulkan has a feature known as push constants which are like uniform sets but for very small amounts of data
var push_constant : PackedFloat32Array = PackedFloat32Array([size.x, size.y, 0.0, 0.0])
for view in range(render_scene_buffers.get_view_count()):
var input_image = render_scene_buffers.get_color_layer(view)
# Pack the exposure vector into a byte array
var uniform_array = PackedFloat32Array([exposure.x, exposure.y, exposure.z, exposure.w]).to_byte_array()
# ACompute handles uniform caching under the hood, as long as the exposure value doesn't change or the render target doesn't change, these functions will only do work once
exposure_compute.set_texture(0, input_image)
exposure_compute.set_uniform_buffer(1, uniform_array)
exposure_compute.set_push_constant(push_constant.to_byte_array())
# Dispatch the compute kernel
exposure_compute.dispatch(0, x_groups, y_groups, z_groups)

View file

@ -0,0 +1 @@
uid://de55kuko5hepf

View file

@ -0,0 +1,237 @@
@tool
extends Node
var shader_file_regex = RegEx.new()
var shader_files : Array = Array()
var compute_shader_file_paths : Array = Array()
var rd : RenderingDevice
var shader_compilations = {}
var shader_code_cache = {}
var compute_shader_kernel_compilations = {}
func find_files(dir_name) -> void:
var dir = DirAccess.open(dir_name)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if dir.current_is_dir():
find_files(dir_name + '/' + file_name)
else:
# if file_name.get_extension() == 'glsl'and shader_file_regex.search(file_name):
# shader_files.push_back(dir_name + '/' + file_name)
if file_name.get_extension() == 'acompute':
compute_shader_file_paths.push_back(dir_name + '/' + file_name)
file_name = dir.get_next()
func get_shader_name(file_path: String) -> String:
return file_path.get_file().split(".")[0]
func compile_shader(shader_file_path) -> void:
var shader_name = shader_file_path.split("/")[-1].split(".glsl")[0]
if shader_compilations.has(shader_name):
if shader_compilations[shader_name].is_valid():
print("Freeing: " + shader_name)
rd.free_rid(shader_compilations[shader_name])
var shader_code = FileAccess.open(shader_file_path, FileAccess.READ).get_as_text()
shader_code_cache[shader_name] = shader_code
var shader_compilation = RID()
var shader_source : RDShaderSource = RDShaderSource.new()
shader_source.language = RenderingDevice.SHADER_LANGUAGE_GLSL
shader_source.source_compute = shader_code
var shader_spirv : RDShaderSPIRV = rd.shader_compile_spirv_from_source(shader_source)
if shader_spirv.compile_error_compute != "":
push_error(shader_spirv.compile_error_compute)
push_error("In: " + shader_code)
return
print("Compiling: " + shader_name)
shader_compilation = rd.shader_create_from_spirv(shader_spirv)
if not shader_compilation.is_valid():
return
shader_compilations[shader_name] = shader_compilation
func compile_compute_shader(compute_shader_file_path) -> void:
var compute_shader_name = get_shader_name(compute_shader_file_path)
print("Compiling Compute Shader: " + compute_shader_name)
var file = FileAccess.open(compute_shader_file_path, FileAccess.READ)
var raw_shader_code_string = file.get_as_text()
shader_code_cache[compute_shader_file_path] = raw_shader_code_string
var raw_shader_code = raw_shader_code_string.split("\n")
var kernel_names = Array()
# Strip out kernel names
while file.get_position() < file.get_length():
var line = file.get_line()
if line.begins_with("#kernel "):
var kernel_name = line.split("#kernel")[1].strip_edges()
# print("Kernel Found: " + kernel_name)
kernel_names.push_back(kernel_name)
raw_shader_code.remove_at(0)
else:
break
# If no kernels defined at top of file, fail to compile
if kernel_names.size() == 0:
push_error("Failed to compile: " + compute_shader_file_path)
push_error("Reason: No kernels found")
return
# If no code after kernel definitions or if nothing in file at all, fail to compile
if file.get_position() >= file.get_length():
push_error("Failed to compile: " + compute_shader_file_path)
push_error("Reason: No shader code found")
return
# Verify kernels exist
raw_shader_code_string = "\n".join(raw_shader_code)
for kernel_name in kernel_names:
if not raw_shader_code_string.contains(kernel_name):
push_error("Failed to compile: " + compute_shader_file_path)
push_error("Reason: " + kernel_name + " kernel not found!")
var kernel_to_thread_group_count = {}
# Find kernels and extract thread groups
for i in raw_shader_code.size():
var line = raw_shader_code[i]
for kernel_name in kernel_names:
if line.contains(kernel_name) and line.contains('void'):
# print("Found kernel " + kernel_name + " at line " + str(i + kernel_names.size() + 1))
# find thread group count by searching previous line of code from kernel function
var newLine = raw_shader_code[i - 1].strip_edges()
if newLine.contains('numthreads'):
var thread_groups = newLine.split('(')[-1].split(')')[0].split(',')
if thread_groups.size() != 3:
push_error("Failed to compile: " + compute_shader_file_path)
push_error("Reason: kernel thread group syntax error")
kernel_to_thread_group_count[kernel_name] = Array()
for n in thread_groups.size():
kernel_to_thread_group_count[kernel_name].push_back((thread_groups[n].strip_edges()))
raw_shader_code.set(i - 1, "")
# print(kernel_to_thread_group_count[kernel_name])
else:
push_error("Failed to compile: " + compute_shader_file_path)
push_error("Reason: kernel thread group count not found")
return
# Compile kernels
compute_shader_kernel_compilations[compute_shader_name] = Array()
for kernel_name in kernel_names:
var shader_code = PackedStringArray(raw_shader_code)
# Insert GLSL thread group layout for the kernel
var thread_group = kernel_to_thread_group_count[kernel_name]
shader_code.insert(0, "layout(local_size_x = " + thread_group[0] + ", local_size_y = " + thread_group[1] + ", local_size_z = " + thread_group[2] + ") in;")
# Insert GLSL version at top of file
shader_code.insert(0, "#version 450")
# Replace kernel name with main
var shader_code_string = "\n".join(shader_code).replace(kernel_name, "main")
# Compile shader
var shader_compilation = RID()
var shader_source : RDShaderSource = RDShaderSource.new()
shader_source.language = RenderingDevice.SHADER_LANGUAGE_GLSL
shader_source.source_compute = shader_code_string
var shader_spirv : RDShaderSPIRV = rd.shader_compile_spirv_from_source(shader_source)
if shader_spirv.compile_error_compute != "":
push_error(shader_spirv.compile_error_compute)
push_error("In: " + shader_code_string)
return
print("- Compiling Kernel: " + kernel_name)
shader_compilation = rd.shader_create_from_spirv(shader_spirv)
if not shader_compilation.is_valid():
return
compute_shader_kernel_compilations[compute_shader_name].push_back(shader_compilation)
# print(shader_code_string)
# print("\n".join(raw_shader_code))
func _init() -> void:
rd = RenderingServer.get_rendering_device()
find_files("res://")
for shader_file in shader_files:
compile_shader(shader_file)
for file_path in compute_shader_file_paths:
compile_compute_shader(file_path)
func _process(delta: float) -> void:
# Compare current shader code with cached shader code and recompile if changed
for file_path in compute_shader_file_paths:
if shader_code_cache[file_path] != FileAccess.open(file_path, FileAccess.READ).get_as_text():
var shader_name = get_shader_name(file_path)
# Free existing kernels
for kernel in compute_shader_kernel_compilations[shader_name]:
rd.free_rid(kernel)
compute_shader_kernel_compilations[shader_name].clear()
compile_compute_shader(file_path)
func _notification(what):
if what == NOTIFICATION_PREDELETE or what == NOTIFICATION_WM_CLOSE_REQUEST:
var shader_names = shader_compilations.keys()
for shader_name in shader_names:
var shader = shader_compilations[shader_name]
if shader.is_valid():
print("Freeing: " + shader_name)
rd.free_rid(shader)
for compute_shader in compute_shader_kernel_compilations.keys():
for kernel in compute_shader_kernel_compilations[compute_shader]:
rd.free_rid(kernel)
func get_shader_compilation(shader_name: String) -> RID:
return shader_compilations[shader_name]
func get_compute_kernel_compilation(shader_name, kernel_index):
return compute_shader_kernel_compilations[shader_name][kernel_index]
func get_compute_kernel_compilations(shader_name):
return compute_shader_kernel_compilations[shader_name]

View file

@ -0,0 +1 @@
uid://dad3g6godfma

View file

@ -0,0 +1,139 @@
@tool
extends Object
class_name ACompute
var kernels = Array()
var rd : RenderingDevice
var shader_name : String
var shader_id : RID
var push_constant : PackedByteArray
var uniform_set_gpu_id : RID
var uniform_set_cache : Array
var current_bound_uniform_set_cpu_copy : Array
var refresh_uniforms = true
# Contains the contents of the uniform array itself
var uniform_buffer_cache = {}
# Contains the RIDs for the gpu versions of the uniform array
var uniform_buffer_id_cache = {}
func get_kernel(index: int) -> RID:
return kernels[index]
func set_push_constant(_push_constant: PackedByteArray) -> void:
push_constant = PackedByteArray(_push_constant)
func set_texture(binding: int, texture: RID) -> void:
var u : RDUniform = RDUniform.new()
u.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
u.binding = binding
u.add_id(texture)
cache_uniform(u)
func create_uniform_buffer(binding: int, uniform_array: PackedByteArray) -> void:
var uniform_buffer_id = rd.uniform_buffer_create(uniform_array.size(), uniform_array)
var u : RDUniform = RDUniform.new()
u.uniform_type = RenderingDevice.UNIFORM_TYPE_UNIFORM_BUFFER
u.binding = binding
u.add_id(uniform_buffer_id)
uniform_buffer_id_cache[binding] = uniform_buffer_id
uniform_buffer_cache[binding] = PackedByteArray(uniform_array)
cache_uniform(u)
func set_uniform_buffer(binding: int, uniform_array: PackedByteArray) -> void:
# Instantiate GPU memory if first time setting uniform buffer
if not uniform_buffer_cache.has(binding):
create_uniform_buffer(binding, uniform_array)
return
# Compare array contents with cache and skip update if nothing changed
if uniform_array == uniform_buffer_cache.get(binding):
return
# Update uniform buffer
rd.buffer_update(uniform_buffer_id_cache.get(binding), 0, uniform_array.size(), uniform_array)
# Cache array contents
uniform_buffer_cache[binding] = PackedByteArray(uniform_array)
func cache_uniform(u: RDUniform) -> void:
if uniform_set_cache.size() - 1 < u.binding:
refresh_uniforms = true
uniform_set_cache.resize(u.binding + 1)
# If uniform has had its info changed then set flag to refresh gpu side uniform data
if uniform_set_cache[u.binding]:
var old_uniform_ids = uniform_set_cache[u.binding].get_ids()
var new_uniform_ids = u.get_ids()
if old_uniform_ids.size() != new_uniform_ids.size():
refresh_uniforms = true
else:
for i in old_uniform_ids.size():
if old_uniform_ids[i].get_id() != new_uniform_ids[i].get_id():
refresh_uniforms = true
break
uniform_set_cache[u.binding] = u
func _init(_shader_name: String) -> void:
rd = RenderingServer.get_rendering_device()
uniform_set_cache = Array()
shader_name = _shader_name
shader_id = AcerolaShaderCompiler.get_compute_kernel_compilation(shader_name, 0)
for kernel in AcerolaShaderCompiler.get_compute_kernel_compilations(shader_name):
kernels.push_back(rd.compute_pipeline_create(kernel))
func dispatch(kernel_index: int, x_groups: int, y_groups: int, z_groups: int) -> void:
var global_shader_id = AcerolaShaderCompiler.get_compute_kernel_compilation(shader_name, 0)
# Recreate kernel pipelines if shader was recompiled
if shader_id != global_shader_id:
shader_id = global_shader_id
kernels.clear()
for kernel in AcerolaShaderCompiler.get_compute_kernel_compilations(shader_name):
kernels.push_back(rd.compute_pipeline_create(kernel))
uniform_set_gpu_id = rd.uniform_set_create(uniform_set_cache, global_shader_id, 0)
# Reallocate GPU memory if uniforms need updating
if refresh_uniforms:
if uniform_set_gpu_id.is_valid(): rd.free_rid(uniform_set_gpu_id)
uniform_set_gpu_id = rd.uniform_set_create(uniform_set_cache, global_shader_id, 0)
refresh_uniforms = false
var compute_list := rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, kernels[kernel_index])
rd.compute_list_bind_uniform_set(compute_list, uniform_set_gpu_id, 0)
rd.compute_list_set_push_constant(compute_list, push_constant, push_constant.size())
rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups)
rd.compute_list_end()
func free() -> void:
for kernel in kernels:
rd.free_rid(kernel)
for binding in uniform_buffer_id_cache.keys():
rd.free_rid(uniform_buffer_id_cache[binding])
if uniform_set_gpu_id.is_valid(): rd.free_rid(uniform_set_gpu_id)

View file

@ -0,0 +1 @@
uid://dmhdvw6cfhyac