OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / nubis / nubis_noise_generator.gd

124 lines · gd ref:
@tool
class_name NubisNoiseGenerator

## Generates 3D noise textures for Nubis clouds via compute shader.
## Call generate() once at startup. Textures persist for the effect's lifetime.

var rd: RenderingDevice

var perlin_worley_texture: RID  # 128³ RGBA16F
var detail_texture: RID         # 32³ RGBA16F
var noise_sampler: RID

var _pw_shader: RID
var _pw_pipeline: RID


func setup(rendering_device: RenderingDevice) -> bool:
	rd = rendering_device

	# Load noise generation shader
	var pw_file := load("res://addons/nubis/shaders/noise_perlin_worley.glsl") as RDShaderFile
	if pw_file == null:
		push_error("[NubisNoise] Failed to load noise_perlin_worley.glsl")
		return false
	var pw_spirv := pw_file.get_spirv()
	if pw_spirv == null:
		push_error("[NubisNoise] Failed to get noise SPIR-V")
		return false
	_pw_shader = rd.shader_create_from_spirv(pw_spirv)
	if not _pw_shader.is_valid():
		push_error("[NubisNoise] Failed to create noise shader")
		return false
	_pw_pipeline = rd.compute_pipeline_create(_pw_shader)

	# Create sampler for later use in ray-march shader
	var ss := RDSamplerState.new()
	ss.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
	ss.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
	ss.mip_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
	ss.repeat_u = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
	ss.repeat_v = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
	ss.repeat_w = RenderingDevice.SAMPLER_REPEAT_MODE_REPEAT
	noise_sampler = rd.sampler_create(ss)

	return true


func generate() -> bool:
	if rd == null:
		return false

	var t0 := Time.get_ticks_usec()

	# Generate 128³ Perlin-Worley base texture
	perlin_worley_texture = _generate_3d_noise(128, 4.0, 0.0)
	if not perlin_worley_texture.is_valid():
		push_error("[NubisNoise] Failed to generate Perlin-Worley texture")
		return false

	# Generate 32³ detail texture (same shader, higher frequency, different seed)
	detail_texture = _generate_3d_noise(32, 8.0, 42.0)
	if not detail_texture.is_valid():
		push_error("[NubisNoise] Failed to generate detail texture")
		return false

	var dt := (Time.get_ticks_usec() - t0) / 1000.0
	print("[NubisNoise] Generated 128³ + 32³ noise textures in %.1fms" % dt)
	return true


func _generate_3d_noise(size: int, frequency: float, seed: float) -> RID:
	# Create 3D texture
	var fmt := RDTextureFormat.new()
	fmt.format = RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT
	fmt.width = size
	fmt.height = size
	fmt.depth = size
	fmt.array_layers = 1
	fmt.mipmaps = 1
	fmt.texture_type = RenderingDevice.TEXTURE_TYPE_3D
	fmt.usage_bits = (
		RenderingDevice.TEXTURE_USAGE_STORAGE_BIT |
		RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
	)
	var tex := rd.texture_create(fmt, RDTextureView.new())
	if not tex.is_valid():
		return RID()

	# Bind as storage image
	var tex_uniform := RDUniform.new()
	tex_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
	tex_uniform.binding = 0
	tex_uniform.add_id(tex)

	var uniform_set := UniformSetCacheRD.get_cache(_pw_shader, 0, [tex_uniform])

	# Push constants: size (int), frequency (float), seed (float), pad (float)
	var push_bytes := PackedByteArray()
	push_bytes.append_array(PackedInt32Array([size]).to_byte_array())
	push_bytes.append_array(PackedFloat32Array([frequency, seed, 0.0]).to_byte_array())

	var groups := ceili(float(size) / 8.0)

	# Dispatch within compute list — no submit/sync on shared RenderingDevice.
	# The compute_list_end acts as a barrier before next dispatch.
	var compute_list := rd.compute_list_begin()
	rd.compute_list_bind_compute_pipeline(compute_list, _pw_pipeline)
	rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
	rd.compute_list_set_push_constant(compute_list, push_bytes, push_bytes.size())
	rd.compute_list_dispatch(compute_list, groups, groups, groups)
	rd.compute_list_end()

	return tex


func cleanup() -> void:
	if rd == null:
		return
	if perlin_worley_texture.is_valid(): rd.free_rid(perlin_worley_texture); perlin_worley_texture = RID()
	if detail_texture.is_valid(): rd.free_rid(detail_texture); detail_texture = RID()
	if noise_sampler.is_valid(): rd.free_rid(noise_sampler); noise_sampler = RID()
	if _pw_pipeline.is_valid(): rd.free_rid(_pw_pipeline); _pw_pipeline = RID()
	if _pw_shader.is_valid(): rd.free_rid(_pw_shader); _pw_shader = RID()