OverviewFilesCommitsIssuesDocsReleasesPeers

nvidia-rtx-godot / servers / rendering / renderer_rd / forward_clustered / scene_shader_raytracing.cpp

1767 lines · cpp ref:
/**************************************************************************/
/*  scene_shader_raytracing.cpp                                           */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#include "scene_shader_raytracing.h"

#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
#include "core/math/math_defs.h"
#include "core/os/os.h"
#include "servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h"
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"

using namespace RendererSceneRenderImplementation;

static void _dump_failed_shader(const String &p_source, const String &p_label, const String &p_error) {
	// The compiler error is the only thing that identifies WHY the hit group failed;
	// without it a failed hit group is indistinguishable from one that was never
	// registered, and the level silently renders nothing.
	if (!p_error.is_empty()) {
		ERR_PRINT("RT shader compile failed (" + p_label + "): " + p_error);
	}
	String tmp_dir = OS::get_singleton()->get_temp_path();
	String path = tmp_dir.path_join("rt_shader_" + p_label + ".glsl");
	Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE);
	if (f.is_valid()) {
		f->store_string(p_source);
		WARN_PRINT("Dumped failed shader to: " + path);
	}
}

namespace {

struct RaygenShaderOption {
	uint32_t rt_flag_mask;
	const char *define_snippet;
};

// Variant index is a uint32 bitmask: bit i selects RAYGEN_SHADER_OPTIONS[i].
// Add entries here to grow the raygen permutation set (2^N variants).
static constexpr RaygenShaderOption RAYGEN_SHADER_OPTIONS[] = {
	{ SceneShaderRaytracing::RT_FLAG_DLSS_RR_ENABLED, "#define DLSS_RR_ENABLED\n" },
	{ SceneShaderRaytracing::RT_FLAG_SER_ENABLED, "#define USE_SER\n" },
};

static constexpr uint32_t RAYGEN_SHADER_OPTION_COUNT = sizeof(RAYGEN_SHADER_OPTIONS) / sizeof(RAYGEN_SHADER_OPTIONS[0]);

static uint32_t _raygen_variant_from_rt_flags(uint32_t p_rt_flags) {
	uint32_t variant = 0;
	for (uint32_t i = 0; i < RAYGEN_SHADER_OPTION_COUNT; i++) {
		if (p_rt_flags & RAYGEN_SHADER_OPTIONS[i].rt_flag_mask) {
			variant |= (1u << i);
		}
	}
	return variant;
}

// Returns the GLSL preamble for the variant identified by p_variant_mask.
// Each set bit i activates RAYGEN_SHADER_OPTIONS[i].define_snippet.
static String _raygen_variant_preamble(uint32_t p_variant_mask) {
	String preamble = "\n";
	for (uint32_t opt = 0; opt < RAYGEN_SHADER_OPTION_COUNT; opt++) {
		if (p_variant_mask & (1u << opt)) {
			preamble += RAYGEN_SHADER_OPTIONS[opt].define_snippet;
		}
	}
	return preamble;
}

} // namespace

void SceneShaderRaytracing::ShaderData::set_code(const String &p_code) {
	code = p_code;

	if (raygen_version.is_null()) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		raygen_version = SceneShaderRaytracing::singleton->raygen_shader.version_create();
	}

	blend_mode = BLEND_MODE_MIX;
	depth_draw = DEPTH_DRAW_OPAQUE;
	depth_test = DEPTH_TEST_ENABLED;
	cull_mode = CULL_BACK;
	alpha_antialiasing_mode = ALPHA_ANTIALIASING_OFF;

	uses_point_size = false;
	uses_alpha = false;
	uses_alpha_clip = false;
	uses_alpha_antialiasing = false;
	uses_blend_alpha = false;
	uses_depth_prepass_alpha = false;
	uses_discard = false;
	uses_roughness = false;
	uses_normal = false;
	uses_tangent = false;
	uses_normal_map = false;
	uses_vertex = false;
	uses_position = false;
	uses_sss = false;
	uses_transmittance = false;
	uses_time = false;
	uses_screen_texture = false;
	uses_screen_texture_mipmaps = false;
	uses_depth_texture = false;
	uses_normal_texture = false;
	uses_vertex_time = false;
	uses_fragment_time = false;
	writes_modelview_or_projection = false;
	uses_world_coordinates = false;
	uses_particle_trails = false;
	wireframe = false;
	unshaded = false;

	ubo_size = 0;
	uniforms.clear();
	ubo_offsets.clear();
	texture_uniforms.clear();
	_clear_vertex_input_mask_cache();
	pipeline_hash_map.clear_pipelines();
}

bool SceneShaderRaytracing::ShaderData::is_animated() const {
	return (uses_fragment_time && uses_discard) || (uses_vertex_time && uses_vertex);
}

bool SceneShaderRaytracing::ShaderData::casts_shadows() const {
	bool has_read_screen_alpha = uses_screen_texture || uses_depth_texture || uses_normal_texture;
	bool has_base_alpha = (uses_alpha && (!uses_alpha_clip || uses_alpha_antialiasing)) || has_read_screen_alpha;
	bool has_alpha = has_base_alpha || uses_blend_alpha;

	return !has_alpha || (uses_depth_prepass_alpha && !(depth_draw == DEPTH_DRAW_DISABLED || depth_test == DEPTH_TEST_DISABLED));
}

RenderingServerTypes::ShaderNativeSourceCode SceneShaderRaytracing::ShaderData::get_native_source_code() const {
	// For raytracing: return source code from raygen shader, not rasterization shader
	if (raygen_version.is_valid()) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		return SceneShaderRaytracing::singleton->raygen_shader.version_get_native_source_code(raygen_version);
	} else {
		return RenderingServerTypes::ShaderNativeSourceCode();
	}
}

SceneShaderRaytracing::ShaderVersion SceneShaderRaytracing::ShaderData::_get_shader_version(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const {
	// Simplified: we only have one shader version now (index 0)
	return ShaderVersion(0);
}

void SceneShaderRaytracing::ShaderData::_create_pipeline(PipelineKey p_pipeline_key) {
#if PRINT_PIPELINE_COMPILATION_KEYS
	print_line(
			"HASH:", p_pipeline_key.hash(),
			"VERSION:", version,
			"VERTEX:", p_pipeline_key.vertex_format_id,
			"FRAMEBUFFER:", p_pipeline_key.framebuffer_format_id,
			"CULL:", p_pipeline_key.cull_mode,
			"PRIMITIVE:", p_pipeline_key.primitive_type,
			"VERSION:", p_pipeline_key.version,
			"PASS FLAGS:", p_pipeline_key.color_pass_flags,
			"SPEC PACKED #0:", p_pipeline_key.shader_specialization.packed_0,
			"WIREFRAME:", p_pipeline_key.wireframe);
#endif

	// Simplified: just create a simple opaque color blend state
	RD::PipelineColorBlendState blend_state = RD::PipelineColorBlendState::create_disabled(1);

	RD::PipelineDepthStencilState depth_stencil_state;
	if (depth_test != DEPTH_TEST_DISABLED) {
		depth_stencil_state.enable_depth_test = true;
		depth_stencil_state.depth_compare_operator = RD::COMPARE_OP_GREATER_OR_EQUAL;
		depth_stencil_state.enable_depth_write = depth_draw != DEPTH_DRAW_DISABLED ? true : false;
	}

	RD::RenderPrimitive primitive_rd_table[RSE::PRIMITIVE_MAX] = {
		RD::RENDER_PRIMITIVE_POINTS,
		RD::RENDER_PRIMITIVE_LINES,
		RD::RENDER_PRIMITIVE_LINESTRIPS,
		RD::RENDER_PRIMITIVE_TRIANGLES,
		RD::RENDER_PRIMITIVE_TRIANGLE_STRIPS,
	};

	RD::RenderPrimitive primitive_rd = uses_point_size ? RD::RENDER_PRIMITIVE_POINTS : primitive_rd_table[p_pipeline_key.primitive_type];

	RD::PipelineRasterizationState raster_state;
	raster_state.cull_mode = p_pipeline_key.cull_mode;
	raster_state.wireframe = wireframe || p_pipeline_key.wireframe;

	RD::PipelineMultisampleState multisample_state;
	multisample_state.sample_count = RD::get_singleton()->framebuffer_format_get_texture_samples(p_pipeline_key.framebuffer_format_id, 0);

	// Simplified: no specialization constants
	Vector<RD::PipelineSpecializationConstant> specialization_constants;

	RID shader_rid = get_shader_variant(p_pipeline_key.version, p_pipeline_key.color_pass_flags, p_pipeline_key.ubershader);
	ERR_FAIL_COND(shader_rid.is_null());

	RID pipeline = RD::get_singleton()->render_pipeline_create(shader_rid, p_pipeline_key.framebuffer_format_id, p_pipeline_key.vertex_format_id, primitive_rd, raster_state, multisample_state, depth_stencil_state, blend_state, 0, 0, specialization_constants);
	ERR_FAIL_COND(pipeline.is_null());

	pipeline_hash_map.add_compiled_pipeline(p_pipeline_key.hash(), pipeline);
}

RD::PolygonCullMode SceneShaderRaytracing::ShaderData::get_cull_mode_from_cull_variant(CullVariant p_cull_variant) {
	const RD::PolygonCullMode cull_mode_rd_table[CULL_VARIANT_MAX][3] = {
		{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_FRONT, RD::POLYGON_CULL_BACK },
		{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_BACK, RD::POLYGON_CULL_FRONT },
		{ RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED, RD::POLYGON_CULL_DISABLED }
	};

	return cull_mode_rd_table[p_cull_variant][cull_mode];
}

RID SceneShaderRaytracing::ShaderData::_get_shader_variant(ShaderVersion p_shader_version) const {
	return RID();
}

void SceneShaderRaytracing::ShaderData::_clear_vertex_input_mask_cache() {
	for (uint32_t i = 0; i < VERTEX_INPUT_MASKS_SIZE; i++) {
		vertex_input_masks[i].store(0);
	}
}

RID SceneShaderRaytracing::ShaderData::get_shader_variant(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) const {
	return _get_shader_variant(_get_shader_version(p_pipeline_version, p_color_pass_flags, p_ubershader));
}

RID SceneShaderRaytracing::ShaderData::get_raygen_shader_variant() const {
	if (raygen_version.is_valid()) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		ERR_FAIL_NULL_V(SceneShaderRaytracing::singleton, RID());
		return SceneShaderRaytracing::singleton->raygen_shader.version_get_shader(raygen_version, 0);
	} else {
		return RID();
	}
}

uint64_t SceneShaderRaytracing::ShaderData::get_vertex_input_mask(PipelineVersion p_pipeline_version, uint32_t p_color_pass_flags, bool p_ubershader) {
	// Vertex input masks require knowledge of the shader. Since querying the shader can be expensive due to high contention and the necessary mutex, we cache the result instead.
	ShaderVersion shader_version = _get_shader_version(p_pipeline_version, p_color_pass_flags, p_ubershader);
	uint64_t input_mask = vertex_input_masks[shader_version].load(std::memory_order_relaxed);
	if (input_mask == 0) {
		RID shader_rid = _get_shader_variant(shader_version);
		ERR_FAIL_COND_V(shader_rid.is_null(), 0);

		input_mask = RD::get_singleton()->shader_get_vertex_input_attribute_mask(shader_rid);
		vertex_input_masks[shader_version].store(input_mask, std::memory_order_relaxed);
	}

	return input_mask;
}

bool SceneShaderRaytracing::ShaderData::is_valid() const {
	// Validity follows raygen_version.
	if (raygen_version.is_valid()) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		ERR_FAIL_NULL_V(SceneShaderRaytracing::singleton, false);
		return SceneShaderRaytracing::singleton->raygen_shader.version_is_valid(raygen_version);
	} else {
		return false;
	}
}

SceneShaderRaytracing::ShaderData::ShaderData() :
		shader_list_element(this) {
	pipeline_hash_map.set_creation_object_and_function(this, &ShaderData::_create_pipeline);
	pipeline_hash_map.set_compilations(SceneShaderRaytracing::singleton->pipeline_compilations, &SceneShaderRaytracing::singleton_mutex);
}

SceneShaderRaytracing::ShaderData::~ShaderData() {
	pipeline_hash_map.clear_pipelines();

	// Tear down raygen only (no raster shader RID).
	if (raygen_version.is_valid()) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		ERR_FAIL_NULL(SceneShaderRaytracing::singleton);
		SceneShaderRaytracing::singleton->raygen_shader.version_free(raygen_version);
	}
}

Pair<ShaderRD *, RID> SceneShaderRaytracing::ShaderData::get_native_shader_and_version() const {
	MutexLock lock(SceneShaderRaytracing::singleton_mutex);
	if (SceneShaderRaytracing::singleton == nullptr) {
		return Pair<ShaderRD *, RID>(nullptr, RID());
	}
	return Pair<ShaderRD *, RID>(&SceneShaderRaytracing::singleton->raygen_shader, raygen_version); // Raygen pair for tooling.
}

RendererRD::MaterialStorage::ShaderData *SceneShaderRaytracing::_create_shader_func() {
	MutexLock lock(SceneShaderRaytracing::singleton_mutex);
	ShaderData *shader_data = memnew(ShaderData);
	singleton->shader_list.add(&shader_data->shader_list_element);
	return shader_data;
}

void SceneShaderRaytracing::MaterialData::set_render_priority(int p_priority) {
	priority = p_priority - RSE::MATERIAL_RENDER_PRIORITY_MIN; //8 bits
}

void SceneShaderRaytracing::MaterialData::set_next_pass(RID p_pass) {
	next_pass = p_pass;
}

bool SceneShaderRaytracing::MaterialData::update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) {
	// RT path does not drive MaterialStorage uniform sets here.
	return true;
}

SceneShaderRaytracing::MaterialData::~MaterialData() {
	free_parameters_uniform_set(uniform_set);
}

RendererRD::MaterialStorage::MaterialData *SceneShaderRaytracing::_create_material_func(ShaderData *p_shader) {
	MaterialData *material_data = memnew(MaterialData);
	material_data->shader_data = p_shader;
	return material_data;
}

SceneShaderRaytracing *SceneShaderRaytracing::singleton = nullptr;
Mutex SceneShaderRaytracing::singleton_mutex;

SceneShaderRaytracing::SceneShaderRaytracing() {
	// There should be only one of these, contained within our RenderForwardClustered singleton.
	singleton = this;
}

SceneShaderRaytracing::~SceneShaderRaytracing() {
	_join_lane_for_shutdown();
	invalidate_pipeline_bundles();

	if (raygen_shader_version.is_valid()) {
		raygen_shader.version_free(raygen_shader_version);
	}

	singleton = nullptr;
}

void SceneShaderRaytracing::invalidate_pipeline_bundles() {
	// Bundles own HG + base_shader RIDs; free_rid is deferred-safe for in-flight frames.
	for (KeyValue<uint32_t, PipelineBundle> &kv : pipeline_bundles) {
		PipelineBundle &b = kv.value;
		if (b.hit_sbt.is_valid()) {
			RD::get_singleton()->free_rid(b.hit_sbt);
		}
		if (b.pipeline.is_valid()) {
			RD::get_singleton()->free_rid(b.pipeline);
		}
		for (const RID &rid : b.per_hg_shaders) {
			if (rid.is_valid()) {
				RD::get_singleton()->free_rid(rid);
			}
		}
		if (b.base_shader.is_valid()) {
			RD::get_singleton()->free_rid(b.base_shader);
		}
	}
	pipeline_bundles.clear();
	variant_compile_contexts.clear();
}

// Async bundle rebuild task (worker: SPIR-V + pipeline; main thread: SBT + swap). Single-lane globally.

struct SceneShaderRaytracing::PipelineBuildTask {
	uint32_t rt_flags = 0;

	RID base_shader;
	Vector<RD::PipelineSpecializationConstant> spec_constants;
	Vector<RD::ShaderStageSPIRVData> base_non_hit_stages;
	RD::ShaderStageSPIRVData base_any_hit_stage;
	bool base_any_hit_valid = false;

	struct SlotInput {
		RID existing_per_hg_shader;
		bool needs_compile = false;
		bool uses_alpha_clip = false;
		bool is_procedural = false;
		String ch_src;
		String ah_src;
		String is_src;
	};
	LocalVector<SlotInput> slots;

	LocalVector<RID> new_per_hg_shaders;
	LocalVector<bool> new_ready_mask;
	RID new_pipeline;
	bool failed = false;
	String error;

	SafeFlag abort_requested;
	SafeFlag done;
	WorkerThreadPool::TaskID worker_id = WorkerThreadPool::INVALID_TASK_ID;
};

const char *SceneShaderRaytracing::_bindless_table_name(ShaderLanguage::DataType p_type) {
	// Names must match the declarations in scene_raytracing_raygen.glsl.
	switch (p_type) {
		case ShaderLanguage::TYPE_SAMPLER2DARRAY:
		case ShaderLanguage::TYPE_ISAMPLER2DARRAY:
		case ShaderLanguage::TYPE_USAMPLER2DARRAY:
			return "bindless_textures_2d_array";
		case ShaderLanguage::TYPE_SAMPLER3D:
		case ShaderLanguage::TYPE_ISAMPLER3D:
		case ShaderLanguage::TYPE_USAMPLER3D:
			return "bindless_textures_3d";
		case ShaderLanguage::TYPE_SAMPLERCUBE:
			return "bindless_textures_cube";
		case ShaderLanguage::TYPE_SAMPLERCUBEARRAY:
			return "bindless_textures_cube_array";
		default:
			return "bindless_textures";
	}
}

void SceneShaderRaytracing::_strip_texture_globals(String &r_globals, const String &p_tex_name) {
	const String decl_marker = " m_" + p_tex_name + ";";
	int pos = r_globals.find(decl_marker);
	if (pos < 0) {
		return;
	}
	int line_start = r_globals.rfind("\n", pos);
	line_start = (line_start < 0) ? 0 : line_start + 1;
	int line_end = r_globals.find("\n", pos);
	if (line_end < 0) {
		line_end = r_globals.length();
	} else {
		line_end += 1;
	}
	r_globals = r_globals.substr(0, line_start) + r_globals.substr(line_end);
}

void SceneShaderRaytracing::_finalize_uniforms_with_textures(
		CustomShaderEntry &r_entry,
		const ShaderCompiler::GeneratedCode &p_gen_code,
		const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms,
		bool p_strip_intersection_globals) {
	// ShaderCompiler rounds uniform_total_size to 16 bytes for UBO requirements,
	// but our buffer_reference struct uses std140 member layout without UBO-level padding.
	// Recompute the raw (unrounded) end of the last uniform member.
	uint32_t raw_uniform_end = 0;
	for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &kv : p_uniforms) {
		const ShaderLanguage::ShaderNode::Uniform &uu = kv.value;
		if (ShaderLanguage::is_sampler_type(uu.type) || uu.order < 0 || uu.order >= (int)p_gen_code.uniform_offsets.size()) {
			continue;
		}
		uint32_t end = p_gen_code.uniform_offsets[uu.order] + ShaderLanguage::get_datatype_size(uu.type);
		if (end > raw_uniform_end) {
			raw_uniform_end = end;
		}
	}

	for (int ti = 0; ti < p_gen_code.texture_uniforms.size(); ti++) {
		const ShaderCompiler::GeneratedCode::Texture &tex = p_gen_code.texture_uniforms[ti];
		if (tex.name.is_empty()) {
			continue;
		}

		_strip_texture_globals(r_entry.fragment_globals, tex.name);
		if (p_strip_intersection_globals) {
			_strip_texture_globals(r_entry.intersection_globals, tex.name);
		}

		// Append a bindless-index uint member to the uniform buffer.
		uint32_t offset = raw_uniform_end;
		if (offset % 4 != 0) {
			offset += 4 - (offset % 4);
		}

		TextureUniformInfo tui;
		tui.name = tex.name;
		tui.type = tex.type;
		tui.hint = tex.hint;
		tui.use_color = tex.use_color;
		tui.is_global = tex.global;
		tui.buffer_offset = offset;
		r_entry.texture_uniforms.push_back(tui);

		r_entry.uniform_members += "uint m_" + tex.name + ";\n";
		raw_uniform_end = offset + 4;
	}

	r_entry.uniform_total_size = raw_uniform_end;
	if (r_entry.uniform_total_size % 16 != 0) {
		r_entry.uniform_total_size += 16 - (r_entry.uniform_total_size % 16);
	}
}

// Slot table: append-only by 128-bit source hash (stable across reloads).

uint32_t SceneShaderRaytracing::register_custom_shader(uint32_t /*p_shader_id*/, RID p_material) {
	return _register_slot(/*p_shader_id*/ 0, p_material, /*p_is_procedural=*/false);
}

uint32_t SceneShaderRaytracing::register_procedural_shader(uint32_t /*p_shader_id*/, RID p_material) {
	return _register_slot(/*p_shader_id*/ 0, p_material, /*p_is_procedural=*/true);
}

uint32_t SceneShaderRaytracing::_register_slot(uint32_t /*p_shader_id*/, RID p_material, bool p_is_procedural) {
	RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton();
	SourceHash128 hash;
	hash.a = material_storage->material_get_shader_code_rt_hash(p_material);
	hash.b = material_storage->material_get_shader_code_rt_hash_b(p_material);
	if (hash.is_zero()) {
		return 0;
	}

	HashMap<SourceHash128, uint32_t, SourceHash128Hasher>::Iterator it = source_hash_to_slot.find(hash);
	if (it != source_hash_to_slot.end()) {
		return it->value;
	}

	// New slot: preprocess here; heavy compile is on worker.
	CustomShaderEntry entry;
	entry.is_procedural = p_is_procedural;
	if (!_preprocess_shader(p_material, p_is_procedural, entry)) {
		// Failed slot still occupies an index so hash lookups stay stable.
		HitGroupSlot failed_slot;
		failed_slot.source_hash = hash;
		failed_slot.state = HGState::Failed;
		uint32_t failed_index = (uint32_t)hit_group_slots.size();
		hit_group_slots.push_back(failed_slot);
		source_hash_to_slot.insert(hash, failed_index);
		for (KeyValue<uint32_t, PipelineBundle> &kv : pipeline_bundles) {
			_bundle_resize_for_slots(kv.value);
			kv.value.per_hg_states[failed_index] = HGState::Failed;
		}
		return 0;
	}
	// Empty custom HG source uses slot 0 (default).
	if (!p_is_procedural && entry.fragment_code.is_empty() && entry.vertex_code.is_empty()) {
		return 0;
	}
	if (p_is_procedural && entry.intersection_code.is_empty()) {
		return 0;
	}

	HitGroupSlot slot;
	slot.source_hash = hash;
	slot.state = HGState::Ready;
	slot.entry = entry;
	uint32_t slot_index = (uint32_t)hit_group_slots.size();
	hit_group_slots.push_back(slot);
	source_hash_to_slot.insert(hash, slot_index);

	for (KeyValue<uint32_t, PipelineBundle> &kv : pipeline_bundles) {
		PipelineBundle &bundle = kv.value;
		_bundle_resize_for_slots(bundle);
		bundle.dirty = true;
	}

	return slot_index;
}

bool SceneShaderRaytracing::_preprocess_shader(RID p_material, bool p_is_procedural, CustomShaderEntry &r_entry) {
	String code = RendererRD::MaterialStorage::get_singleton()->material_get_shader_code_rt(p_material);
	if (code.is_empty()) {
		return false;
	}

	ShaderCompiler::IdentifierActions actions;
	actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX;
	actions.entry_point_stages["fragment"] = ShaderCompiler::STAGE_FRAGMENT;
	if (p_is_procedural) {
		actions.entry_point_stages["light"] = ShaderCompiler::STAGE_FRAGMENT;
		actions.entry_point_stages["intersection"] = ShaderCompiler::STAGE_INTERSECTION;
	}

	bool detected_alpha_clip = false;
	actions.usage_flag_pointers["ALPHA_SCISSOR_THRESHOLD"] = &detected_alpha_clip;
	actions.usage_flag_pointers["ALPHA_HASH_SCALE"] = &detected_alpha_clip;

	HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniform_sink;
	actions.uniforms = &uniform_sink;

	ShaderCompiler::GeneratedCode gen_code;
	Error err = compiler.compile(RSE::SHADER_SPATIAL, code, &actions, String(), gen_code);
	if (err != OK) {
		WARN_PRINT(vformat("RT: Failed to preprocess %s shader; falling back to default material.",
				p_is_procedural ? String("procedural") : String("custom")));
		return false;
	}

	r_entry.is_procedural = p_is_procedural;
	r_entry.uses_alpha_clip = detected_alpha_clip;
	r_entry.vertex_code = gen_code.code.has("vertex") ? gen_code.code["vertex"] : String();
	r_entry.fragment_code = gen_code.code.has("fragment") ? gen_code.code["fragment"] : String();
	r_entry.fragment_globals = gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT];
	if (p_is_procedural) {
		r_entry.intersection_code = gen_code.code.has("intersection") ? gen_code.code["intersection"] : String();
		r_entry.intersection_globals = gen_code.stage_globals[ShaderCompiler::STAGE_INTERSECTION];
	}
	r_entry.uniform_members = gen_code.uniforms;
	r_entry.uniform_total_size = gen_code.uniform_total_size;
	r_entry.uniform_offsets = gen_code.uniform_offsets;
	r_entry.uniforms = uniform_sink;
	_finalize_uniforms_with_textures(r_entry, gen_code, uniform_sink, /*strip_intersection_globals=*/p_is_procedural);
	return true;
}

void SceneShaderRaytracing::finalize_custom_shaders() {
	async_compilation_enabled = GLOBAL_GET_CACHED(bool, "rendering/pathtracer/async_shader_compilation");

	_kick_rebuild_if_idle(); // Async dispatch only; sync drains below.

	if (!async_compilation_enabled) {
		_drain_lane_inline_main_thread();
	}
}

const SceneShaderRaytracing::CustomShaderEntry *SceneShaderRaytracing::get_custom_shader_entry(uint32_t p_slot_index) const {
	if (p_slot_index == 0 || p_slot_index >= hit_group_slots.size()) {
		return nullptr;
	}
	const HitGroupSlot &slot = hit_group_slots[p_slot_index];
	if (slot.state == HGState::Failed) {
		return nullptr;
	}
	return &slot.entry;
}

bool SceneShaderRaytracing::is_hg_ready_in_bundle(uint32_t p_slot_index, uint32_t p_rt_flags) const {
	if (p_slot_index == 0) {
		return true;
	}
	if (p_slot_index >= hit_group_slots.size()) {
		return false;
	}
	HashMap<uint32_t, PipelineBundle>::ConstIterator it = pipeline_bundles.find(p_rt_flags);
	if (it == pipeline_bundles.end()) {
		return false;
	}
	const PipelineBundle &b = it->value;
	if (p_slot_index >= b.live_hg_count) {
		return false;
	}
	if (p_slot_index >= b.live_ready_mask.size()) {
		return false;
	}
	return b.live_ready_mask[p_slot_index];
}

uint32_t SceneShaderRaytracing::compute_rt_flags(const float *p_env_params, bool p_fog_enabled) {
	uint32_t flags = RT_FLAG_NONE;
	uint32_t sample_count = 1;
	uint32_t max_bounces = 3;

	if (p_env_params) {
		if (p_env_params[RT_PARAM_VIS_MODE] != 0.0f) {
			flags |= RT_FLAG_DEBUG_VIS_ENABLED;
		}
		sample_count = MAX(1u, (uint32_t)p_env_params[RT_PARAM_SAMPLE_COUNT]);
		max_bounces = MAX(1u, MIN(8u, (uint32_t)p_env_params[RT_PARAM_MAX_BOUNCES]));
		if ((uint32_t)p_env_params[RT_PARAM_DENOISER] == RSE::PT_DENOISER_DLSS_RAY_RECONSTRUCTION) {
			flags |= RT_FLAG_DLSS_RR_ENABLED;
		}
	}

	if (p_fog_enabled) {
		flags |= RT_FLAG_FOG_ENABLED;
	}

	if (GLOBAL_GET("rendering/pathtracer/use_shader_execution_reordering")) {
		flags |= RT_FLAG_SER_ENABLED;
	}

	return rt_flags_pack(flags, sample_count, max_bounces);
}

SceneShaderRaytracing *SceneShaderRaytracing::get_singleton() {
	if (singleton == nullptr) {
		MutexLock lock(SceneShaderRaytracing::singleton_mutex);
		if (singleton == nullptr) {
			memnew(SceneShaderRaytracing);
		}
	}
	return singleton;
}

// Per-rt_flags base SPIR-V + HG templates (immutable cache for worker tasks).

bool SceneShaderRaytracing::_ensure_variant_compile_context(uint32_t p_rt_flags) {
	if (variant_compile_contexts.has(p_rt_flags)) {
		return true;
	}
	ERR_FAIL_COND_V(!raygen_shader_version.is_valid(), false);

	int variant = (int)_raygen_variant_from_rt_flags(p_rt_flags);

	Vector<String> sources = raygen_shader.version_build_variant_stage_sources(raygen_shader_version, variant);
	ERR_FAIL_COND_V(sources.is_empty(), false);

	auto inject_define = [](Vector<String> &p_sources, const String &p_define) {
		for (int i = 0; i < p_sources.size(); i++) {
			if (p_sources[i].is_empty()) {
				continue;
			}
			int ver_pos = p_sources[i].find("#version");
			if (ver_pos >= 0) {
				int nl = p_sources[i].find("\n", ver_pos);
				if (nl >= 0) {
					p_sources.write[i] = p_sources[i].insert(nl + 1, p_define);
				}
			}
		}
	};

	// Fixed intersection hit-attribute layout for all variants (keeps base_shader layout stable).
	inject_define(sources, "#define ENABLE_INTERSECTION_SHADERS\n");

	if (p_rt_flags & RT_FLAG_DEBUG_VIS_ENABLED) {
		inject_define(sources, "#define RT_DEBUG_ENABLED\n");
	}

	// Procedural templates come from intersection stage; strip from base HG0 path.
	String intersection_source_template;
	if (sources.size() > RD::SHADER_STAGE_INTERSECTION) {
		intersection_source_template = sources[RD::SHADER_STAGE_INTERSECTION];
		sources.write[RD::SHADER_STAGE_INTERSECTION] = String();
	}

	String base_ch_src = sources[RD::SHADER_STAGE_CLOSEST_HIT];
	String base_ah_src = sources[RD::SHADER_STAGE_ANY_HIT];
	ERR_FAIL_COND_V(base_ch_src.is_empty(), false);
	ERR_FAIL_COND_V(base_ah_src.is_empty(), false);

	static const String custom_hg_define = "#define RT_CUSTOM_HIT_GROUP\n";
	int base_ch_ver = base_ch_src.find("#version");
	int base_ah_ver = base_ah_src.find("#version");
	ERR_FAIL_COND_V(base_ch_ver < 0 || base_ah_ver < 0, false);

	VariantCompileContext ctx;
	ctx.ch_template = base_ch_src.insert(base_ch_src.find("\n", base_ch_ver) + 1, custom_hg_define);
	ctx.ah_template = base_ah_src.insert(base_ah_src.find("\n", base_ah_ver) + 1, custom_hg_define);
	if (!intersection_source_template.is_empty()) {
		int base_is_ver = intersection_source_template.find("#version");
		if (base_is_ver >= 0) {
			ctx.is_template = intersection_source_template.insert(intersection_source_template.find("\n", base_is_ver) + 1, custom_hg_define);
		}
	}

	// Full base stages; CH/AH SPIR-V is HG0 default; RG+miss duplicated into each HG bytecode.
	Vector<RD::ShaderStageSPIRVData> base_stages;
	{
		MutexLock lock(spirv_compile_mutex);
		base_stages = ShaderRD::compile_stages(sources, {});
	}
	ERR_FAIL_COND_V(base_stages.is_empty(), false);

	for (const RD::ShaderStageSPIRVData &sd : base_stages) {
		if (sd.shader_stage == RD::SHADER_STAGE_RAYGEN || sd.shader_stage == RD::SHADER_STAGE_MISS) {
			ctx.base_non_hit_stages.push_back(sd);
		} else if (sd.shader_stage == RD::SHADER_STAGE_ANY_HIT) {
			ctx.base_any_hit_stage = sd;
			ctx.base_any_hit_valid = true;
		}
	}

	variant_compile_contexts.insert(p_rt_flags, ctx);

	// Creates layout-defining base_shader RID for uniform sets (immutable per variant).
	HashMap<uint32_t, PipelineBundle>::Iterator bit = pipeline_bundles.find(p_rt_flags);
	if (bit != pipeline_bundles.end() && bit->value.base_shader.is_valid()) {
		return true;
	}

	Vector<uint8_t> base_binary;
	{
		MutexLock lock(spirv_compile_mutex);
		base_binary = RD::get_singleton()->shader_compile_binary_from_spirv(base_stages, "RT_base");
	}
	ERR_FAIL_COND_V(base_binary.is_empty(), false);

	RID base_shader_rd = RD::get_singleton()->shader_create_from_bytecode(base_binary);
	ERR_FAIL_COND_V(!base_shader_rd.is_valid(), false);

	PipelineBundle &bundle = pipeline_bundles[p_rt_flags];
	bundle.base_shader = base_shader_rd;
	_bundle_resize_for_slots(bundle);
	if (!bundle.per_hg_states.is_empty()) {
		bundle.per_hg_states[0] = HGState::Ready;
	}

	return true;
}

void SceneShaderRaytracing::_bundle_resize_for_slots(PipelineBundle &r_bundle) {
	uint32_t n = (uint32_t)hit_group_slots.size();
	uint32_t old = (uint32_t)r_bundle.per_hg_states.size();
	if (old == n) {
		return;
	}
	r_bundle.per_hg_states.resize(n);
	r_bundle.per_hg_shaders.resize(n);
	for (uint32_t i = old; i < n; i++) {
		r_bundle.per_hg_states[i] = HGState::Empty;
		r_bundle.per_hg_shaders[i] = RID();
	}
	if (n > 0 && r_bundle.base_shader.is_valid()) {
		r_bundle.per_hg_states[0] = HGState::Ready;
	}
}

const SceneShaderRaytracing::PipelineBundle &SceneShaderRaytracing::ensure_pipeline_bundle(uint32_t p_rt_flags) {
	static PipelineBundle EMPTY_BUNDLE;

	HashMap<uint32_t, PipelineBundle>::Iterator it = pipeline_bundles.find(p_rt_flags);
	if (it != pipeline_bundles.end() && it->value.initial_pipeline_built) {
		return it->value;
	}

	if (!_ensure_variant_compile_context(p_rt_flags)) {
		WARN_PRINT(vformat("RT: Failed to build base compile context for variant 0x%x.", p_rt_flags));
		return EMPTY_BUNDLE;
	}

	PipelineBundle &bundle = pipeline_bundles[p_rt_flags];
	_bundle_resize_for_slots(bundle);

	// Sync bootstrap: HG0-only pipeline + SBT on render thread; dirty if extra slots exist.
	if (!_build_initial_bundle(p_rt_flags, bundle)) {
		return EMPTY_BUNDLE;
	}
	if (hit_group_slots.size() > 1) {
		bundle.dirty = true;
	}
	bundle.initial_pipeline_built = true;
	return bundle;
}

// Minimal HG0 + empty sentinel pipeline/SBT for first use of a variant.

bool SceneShaderRaytracing::_build_initial_bundle(uint32_t p_rt_flags, PipelineBundle &r_bundle) {
	if (!r_bundle.base_shader.is_valid()) {
		return false;
	}

	Vector<RD::PipelineSpecializationConstant> spec_constants;
	{
		RD::PipelineSpecializationConstant sc;
		sc.constant_id = 0;
		sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
		sc.int_value = (int)p_rt_flags;
		spec_constants.push_back(sc);
	}

	RD::PipelineShader base_ps = { r_bundle.base_shader, spec_constants };
	RD::HitGroup hg0_default;
	hg0_default.closest_hit_shader = base_ps;
	hg0_default.any_hit_shader = base_ps;
	const RD::HitGroup empty_hg;

	// HG[0]=default, HG[1]=empty until rebuild maps custom slots.
	LocalVector<RD::HitGroup> hit_groups;
	hit_groups.push_back(hg0_default);
	hit_groups.push_back(empty_hg);

	RID new_pipeline = RD::get_singleton()->raytracing_pipeline_create(
			{ &base_ps, 1 }, { &base_ps, 1 },
			{ hit_groups.ptr(), (uint64_t)hit_groups.size() },
			RT_MAX_RECURSION_DEPTH);
	if (new_pipeline.is_null()) {
		WARN_PRINT(vformat("RT: initial raytracing_pipeline_create failed for variant 0x%x.", p_rt_flags));
		return false;
	}
	RD::get_singleton()->set_resource_name(new_pipeline, String("RT Pipeline initial [flags=") + itos(p_rt_flags) + "]");

	static constexpr uint32_t HIT_SBT_CAPACITY = 4096;
	uint32_t sbt_size = HIT_SBT_CAPACITY;

	RID new_sbt = RD::get_singleton()->hit_sbt_create(new_pipeline, sbt_size);
	if (new_sbt.is_null()) {
		RD::get_singleton()->free_rid(new_pipeline);
		return false;
	}
	RD::get_singleton()->set_resource_name(new_sbt, String("RT Hit SBT initial [flags=") + itos(p_rt_flags) + "]");

	RD::HitShaderBindingTableRange sbt_range = RD::get_singleton()->hit_sbt_range_alloc(new_sbt, sbt_size);
	if (!sbt_range) {
		RD::get_singleton()->free_rid(new_sbt);
		RD::get_singleton()->free_rid(new_pipeline);
		return false;
	}

	// Instance slot i maps to HG min(i,1) until full rebuild (custom TLAS-skipped meanwhile).
	LocalVector<uint32_t> indices;
	indices.resize(sbt_size);
	for (uint32_t i = 0; i < sbt_size; i++) {
		indices[i] = (i == 0) ? 0 : 1;
	}
	RD::get_singleton()->hit_sbt_range_update(new_sbt, sbt_range, 0, indices);

	r_bundle.pipeline = new_pipeline;
	r_bundle.hit_sbt = new_sbt;
	r_bundle.live_hg_count = 1;
	r_bundle.live_ready_mask.clear();
	r_bundle.live_ready_mask.push_back(true);
	return true;
}

SceneShaderRaytracing::PipelineBuildTask *SceneShaderRaytracing::_make_pipeline_build_task(uint32_t p_rt_flags, PipelineBundle &p_bundle) {
	if (!_ensure_variant_compile_context(p_rt_flags)) {
		return nullptr;
	}
	HashMap<uint32_t, VariantCompileContext>::ConstIterator cit = variant_compile_contexts.find(p_rt_flags);
	if (cit == variant_compile_contexts.end()) {
		return nullptr;
	}
	const VariantCompileContext &ctx = cit->value;

	PipelineBuildTask *task = memnew(PipelineBuildTask);
	task->rt_flags = p_rt_flags;
	task->base_shader = p_bundle.base_shader;
	task->base_non_hit_stages = ctx.base_non_hit_stages;
	task->base_any_hit_stage = ctx.base_any_hit_stage;
	task->base_any_hit_valid = ctx.base_any_hit_valid;
	{
		RD::PipelineSpecializationConstant sc;
		sc.constant_id = 0;
		sc.type = RD::PIPELINE_SPECIALIZATION_CONSTANT_TYPE_INT;
		sc.int_value = (int)p_rt_flags;
		task->spec_constants.push_back(sc);
	}

	uint32_t slot_count = (uint32_t)hit_group_slots.size();
	task->slots.resize(slot_count);

	task->slots[0].existing_per_hg_shader = RID();
	task->slots[0].needs_compile = false;

	for (uint32_t i = 1; i < slot_count; i++) {
		const HitGroupSlot &slot = hit_group_slots[i];
		PipelineBuildTask::SlotInput &si = task->slots[i];

		si.uses_alpha_clip = slot.entry.uses_alpha_clip;
		si.is_procedural = slot.entry.is_procedural;

		if (slot.state != HGState::Ready) {
			si.needs_compile = false;
			si.existing_per_hg_shader = RID();
			continue;
		}

		if (i < p_bundle.per_hg_shaders.size() && p_bundle.per_hg_shaders[i].is_valid() &&
				i < p_bundle.per_hg_states.size() && p_bundle.per_hg_states[i] == HGState::Ready) {
			si.existing_per_hg_shader = p_bundle.per_hg_shaders[i];
			si.needs_compile = false;
			continue;
		}
		if (i < p_bundle.per_hg_states.size() && p_bundle.per_hg_states[i] == HGState::Failed) {
			si.needs_compile = false;
			continue;
		}

		const CustomShaderEntry &entry = slot.entry;
		si.needs_compile = true;

		String tex_defines;
		for (int ti = 0; ti < entry.texture_uniforms.size(); ti++) {
			const TextureUniformInfo &tui = entry.texture_uniforms[ti];
			// Must name the table whose element type matches the uniform's sampler
			// type. Pointing everything at bindless_textures (texture2D) makes a
			// sampler2DArray uniform generate sampler2DArray(texture2D, ...), which
			// glslang rejects -- taking the whole hit group down with it.
			tex_defines += "#define m_" + tui.name + " " + _bindless_table_name(tui.type) +
					"[nonuniformEXT(material.m_" + tui.name + ")]\n";
		}
		String uniform_members = entry.uniform_members.is_empty() ? String("float _rt_pad;") : entry.uniform_members;

		String vertex_function;
		String vertex_call;
		if (!entry.vertex_code.is_empty()) {
			vertex_function = "void rt_run_vertex_shader() {\n" + entry.vertex_code + "\n}\n";
			vertex_call = "rt_run_vertex_shader();";
		}

		// Procedural with no fragment stage: default material shading.
		String fragment_code = entry.fragment_code;
		if (fragment_code.is_empty() && entry.is_procedural) {
			fragment_code =
					"vec2 mat_uv = uv_interp * rt_mat.uv1_scale + rt_mat.uv1_offset;\n"
					"vec4 albedo_tex = sample_material_texture(rt_mat.albedo_texture_idx, mat_uv, rt_mat.flags);\n"
					"albedo = albedo_tex.rgb * rt_mat.albedo_color.rgb;\n"
					"alpha = albedo_tex.a * rt_mat.albedo_color.a;\n"
					"vec3 orm = sample_material_texture(rt_mat.orm_texture_idx, mat_uv, rt_mat.flags).rgb;\n"
					"roughness = orm.g * rt_mat.roughness;\n"
					"metallic = orm.b * rt_mat.metallic;\n"
					"if ((rt_mat.flags & 1u) != 0u) {\n"
					"    normal_map = sample_material_texture(rt_mat.normal_texture_idx, mat_uv, rt_mat.flags).rgb;\n"
					"    normal_map_depth = rt_mat.normal_map_depth;\n"
					"}\n"
					"if ((rt_mat.flags & 2u) != 0u) {\n"
					"    emission = sample_material_texture(rt_mat.emission_texture_idx, mat_uv, rt_mat.flags).rgb\n"
					"             * rt_mat.emission_color * rt_mat.emission_strength;\n"
					"}\n";
		}

		{
			String s = ctx.ch_template;
			s = s.replace("/* RT_CUSTOM_FRAGMENT_GLOBALS */", entry.fragment_globals);
			s = s.replace("/* RT_CUSTOM_FRAGMENT_CODE */", fragment_code);
			s = s.replace("/* RT_CUSTOM_UNIFORM_MEMBERS */", uniform_members);
			s = s.replace("/* RT_CUSTOM_TEXTURE_DEFINES */", tex_defines);
			s = s.replace("/* RT_CUSTOM_VERTEX_FUNCTION */", vertex_function);
			s = s.replace("/* RT_CUSTOM_VERTEX_CALL */", vertex_call);
			si.ch_src = s;
		}
		if (entry.uses_alpha_clip) {
			String s = ctx.ah_template;
			s = s.replace("/* RT_CUSTOM_FRAGMENT_GLOBALS */", entry.fragment_globals);
			s = s.replace("/* RT_CUSTOM_FRAGMENT_CODE */", fragment_code);
			s = s.replace("/* RT_CUSTOM_UNIFORM_MEMBERS */", uniform_members);
			s = s.replace("/* RT_CUSTOM_TEXTURE_DEFINES */", tex_defines);
			s = s.replace("/* RT_CUSTOM_VERTEX_FUNCTION */", vertex_function);
			s = s.replace("/* RT_CUSTOM_VERTEX_CALL */", vertex_call);
			si.ah_src = s;
		}
		if (entry.is_procedural && !ctx.is_template.is_empty()) {
			String s = ctx.is_template;
			s = s.replace("/* RT_CUSTOM_INTERSECTION_GLOBALS */", entry.intersection_globals);
			s = s.replace("/* RT_CUSTOM_INTERSECTION_CODE */", entry.intersection_code);
			s = s.replace("/* RT_CUSTOM_UNIFORM_MEMBERS */", uniform_members);
			s = s.replace("/* RT_CUSTOM_TEXTURE_DEFINES */", tex_defines);
			si.is_src = s;
		}
	}

	return task;
}

void SceneShaderRaytracing::_build_pipeline_worker_static(void *p_userdata) {
	PipelineBuildTask *t = static_cast<PipelineBuildTask *>(p_userdata);
	if (singleton) {
		singleton->_build_pipeline_worker(t);
	} else {
		t->done.set();
	}
}

void SceneShaderRaytracing::_build_pipeline_worker(PipelineBuildTask *p_task) {
	auto check_abort = [p_task]() {
		return p_task->abort_requested.is_set();
	};
	auto finish = [p_task]() {
		p_task->done.set();
	};

	uint32_t n = (uint32_t)p_task->slots.size();
	p_task->new_per_hg_shaders.resize(n);
	p_task->new_ready_mask.resize(n);

	LocalVector<RID> created_here;
	auto cleanup_created = [&]() {
		for (const RID &r : created_here) {
			if (r.is_valid()) {
				RD::get_singleton()->free_rid(r);
			}
		}
		created_here.clear();
		for (uint32_t i = 0; i < n; i++) {
			if (p_task->slots[i].existing_per_hg_shader.is_null()) {
				p_task->new_per_hg_shaders[i] = RID();
			}
		}
	};

	if (n > 0) {
		p_task->new_per_hg_shaders[0] = RID();
		p_task->new_ready_mask[0] = true;
	}

	String error;
	for (uint32_t i = 1; i < n; i++) {
		if (check_abort()) {
			cleanup_created();
			p_task->failed = true;
			finish();
			return;
		}

		const PipelineBuildTask::SlotInput &si = p_task->slots[i];

		if (si.existing_per_hg_shader.is_valid()) {
			p_task->new_per_hg_shaders[i] = si.existing_per_hg_shader;
			p_task->new_ready_mask[i] = true;
			continue;
		}
		if (!si.needs_compile) {
			p_task->new_per_hg_shaders[i] = RID();
			p_task->new_ready_mask[i] = false;
			continue;
		}

		Vector<RD::ShaderStageSPIRVData> custom_stages = p_task->base_non_hit_stages;

		Vector<uint8_t> ch_spirv;
		{
			MutexLock lock(spirv_compile_mutex);
			error = String();
			ch_spirv = RD::get_singleton()->shader_compile_spirv_from_source(
					RD::SHADER_STAGE_CLOSEST_HIT, si.ch_src, RD::SHADER_LANGUAGE_GLSL, &error);
		}
		if (ch_spirv.is_empty()) {
			_dump_failed_shader(si.ch_src, vformat("hg%d_v%x_closest_hit", i, p_task->rt_flags), error);
			p_task->new_per_hg_shaders[i] = RID();
			p_task->new_ready_mask[i] = false;
			continue;
		}
		{
			RD::ShaderStageSPIRVData sd;
			sd.shader_stage = RD::SHADER_STAGE_CLOSEST_HIT;
			sd.spirv = ch_spirv;
			custom_stages.push_back(sd);
		}

		if (si.uses_alpha_clip) {
			Vector<uint8_t> ah_spirv;
			{
				MutexLock lock(spirv_compile_mutex);
				error = String();
				ah_spirv = RD::get_singleton()->shader_compile_spirv_from_source(
						RD::SHADER_STAGE_ANY_HIT, si.ah_src, RD::SHADER_LANGUAGE_GLSL, &error);
			}
			if (ah_spirv.is_empty()) {
				_dump_failed_shader(si.ah_src, vformat("hg%d_v%x_any_hit", i, p_task->rt_flags), error);
				p_task->new_per_hg_shaders[i] = RID();
				p_task->new_ready_mask[i] = false;
				continue;
			}
			RD::ShaderStageSPIRVData sd;
			sd.shader_stage = RD::SHADER_STAGE_ANY_HIT;
			sd.spirv = ah_spirv;
			custom_stages.push_back(sd);
		} else if (p_task->base_any_hit_valid) {
			custom_stages.push_back(p_task->base_any_hit_stage);
		}

		if (si.is_procedural && !si.is_src.is_empty()) {
			Vector<uint8_t> is_spirv;
			{
				MutexLock lock(spirv_compile_mutex);
				error = String();
				is_spirv = RD::get_singleton()->shader_compile_spirv_from_source(
						RD::SHADER_STAGE_INTERSECTION, si.is_src, RD::SHADER_LANGUAGE_GLSL, &error);
			}
			if (is_spirv.is_empty()) {
				_dump_failed_shader(si.is_src, vformat("hg%d_v%x_intersection", i, p_task->rt_flags), error);
				p_task->new_per_hg_shaders[i] = RID();
				p_task->new_ready_mask[i] = false;
				continue;
			}
			RD::ShaderStageSPIRVData sd;
			sd.shader_stage = RD::SHADER_STAGE_INTERSECTION;
			sd.spirv = is_spirv;
			custom_stages.push_back(sd);
		}

		Vector<uint8_t> binary;
		{
			MutexLock lock(spirv_compile_mutex);
			binary = RD::get_singleton()->shader_compile_binary_from_spirv(
					custom_stages, vformat("RT_hg%d_v%x", i, p_task->rt_flags));
		}
		if (binary.is_empty()) {
			p_task->new_per_hg_shaders[i] = RID();
			p_task->new_ready_mask[i] = false;
			continue;
		}

		RID rid = RD::get_singleton()->shader_create_from_bytecode(binary);
		if (!rid.is_valid()) {
			p_task->new_per_hg_shaders[i] = RID();
			p_task->new_ready_mask[i] = false;
			continue;
		}
		p_task->new_per_hg_shaders[i] = rid;
		p_task->new_ready_mask[i] = true;
		created_here.push_back(rid);
	}

	if (check_abort()) {
		cleanup_created();
		p_task->failed = true;
		finish();
		return;
	}

	// raytracing_pipeline_create may run off render thread (RD uses internal sync).
	RD::PipelineShader base_ps = { p_task->base_shader, p_task->spec_constants };
	RD::HitGroup hg0_default;
	hg0_default.closest_hit_shader = base_ps;
	hg0_default.any_hit_shader = base_ps;

	// No-op HG: failed / pending slots + trailing SBT sentinel (never default material).
	const RD::HitGroup empty_hg;

	// Layout: [0] default [1..n-1] custom or empty [n] sentinel.
	LocalVector<RD::HitGroup> hit_groups;
	hit_groups.resize(n + 1);
	hit_groups[0] = hg0_default;
	for (uint32_t i = 1; i < n; i++) {
		if (!p_task->new_ready_mask[i] || p_task->new_per_hg_shaders[i].is_null()) {
			hit_groups[i] = empty_hg;
			continue;
		}
		RD::PipelineShader custom_ps = { p_task->new_per_hg_shaders[i], p_task->spec_constants };
		RD::HitGroup hg;
		hg.closest_hit_shader = custom_ps;
		hg.any_hit_shader = p_task->slots[i].uses_alpha_clip ? custom_ps : base_ps;
		if (p_task->slots[i].is_procedural) {
			hg.intersection_shader = custom_ps;
		}
		hit_groups[i] = hg;
	}
	hit_groups[n] = empty_hg;

	RID new_pipeline = RD::get_singleton()->raytracing_pipeline_create(
			{ &base_ps, 1 }, { &base_ps, 1 },
			{ hit_groups.ptr(), (uint64_t)hit_groups.size() },
			RT_MAX_RECURSION_DEPTH);
	if (new_pipeline.is_null()) {
		cleanup_created();
		p_task->failed = true;
		p_task->error = "raytracing_pipeline_create returned null";
		finish();
		return;
	}
	p_task->new_pipeline = new_pipeline;
	finish();
}

void SceneShaderRaytracing::_finalize_pipeline_build(PipelineBuildTask *p_task) {
	HashMap<uint32_t, PipelineBundle>::Iterator bit = pipeline_bundles.find(p_task->rt_flags);
	if (bit == pipeline_bundles.end()) {
		for (uint32_t i = 0; i < p_task->slots.size(); i++) {
			if (p_task->slots[i].existing_per_hg_shader.is_null() &&
					i < p_task->new_per_hg_shaders.size() &&
					p_task->new_per_hg_shaders[i].is_valid()) {
				RD::get_singleton()->free_rid(p_task->new_per_hg_shaders[i]);
			}
		}
		if (p_task->new_pipeline.is_valid()) {
			RD::get_singleton()->free_rid(p_task->new_pipeline);
		}
		return;
	}
	PipelineBundle &bundle = bit->value;
	_bundle_resize_for_slots(bundle);

	if (p_task->failed) {
		WARN_PRINT(vformat("RT: pipeline rebuild failed for variant 0x%x: %s",
				p_task->rt_flags, p_task->error));
		return;
	}

	// Install worker outputs into bundle arrays.
	uint32_t n = (uint32_t)p_task->slots.size();
	for (uint32_t i = 1; i < n; i++) {
		const PipelineBuildTask::SlotInput &si = p_task->slots[i];
		if (i >= bundle.per_hg_shaders.size()) {
			break;
		}
		if (si.existing_per_hg_shader.is_valid()) {
			continue;
		}
		if (si.needs_compile && i < p_task->new_per_hg_shaders.size() && p_task->new_per_hg_shaders[i].is_valid()) {
			bundle.per_hg_shaders[i] = p_task->new_per_hg_shaders[i];
			bundle.per_hg_states[i] = HGState::Ready;
		} else if (si.needs_compile) {
			bundle.per_hg_states[i] = HGState::Failed;
		}
	}

	static constexpr uint32_t HIT_SBT_CAPACITY = 4096;
	uint32_t sbt_size = MAX(n, HIT_SBT_CAPACITY);

	RID new_sbt = RD::get_singleton()->hit_sbt_create(p_task->new_pipeline, sbt_size);
	if (new_sbt.is_null()) {
		WARN_PRINT(vformat("RT: hit_sbt_create failed for variant 0x%x.", p_task->rt_flags));
		RD::get_singleton()->free_rid(p_task->new_pipeline);
		return;
	}
	RD::get_singleton()->set_resource_name(new_sbt, String("RT Hit SBT [flags=") + itos(p_task->rt_flags) + "]");

	RD::HitShaderBindingTableRange sbt_range = RD::get_singleton()->hit_sbt_range_alloc(new_sbt, sbt_size);
	if (!sbt_range) {
		WARN_PRINT(vformat("RT: hit_sbt_range_alloc failed for variant 0x%x.", p_task->rt_flags));
		RD::get_singleton()->free_rid(new_sbt);
		RD::get_singleton()->free_rid(p_task->new_pipeline);
		return;
	}

	// Slots i >= n map to sentinel HG n.
	LocalVector<uint32_t> indices;
	indices.resize(sbt_size);
	for (uint32_t i = 0; i < sbt_size; i++) {
		indices[i] = (i < n) ? i : n;
	}
	RD::get_singleton()->hit_sbt_range_update(new_sbt, sbt_range, 0, indices);

	// SBT before pipeline, never the other way round: hit_sbt_create registers the
	// SBT as a dependency of its pipeline (rendering_device.cpp:768), so freeing the
	// pipeline first frees the SBT with it and the next free is a double free.
	// Freeing the SBT first unregisters it from the pipeline's dependency set, which
	// is what invalidate_pipeline_bundles() already does.
	if (bundle.hit_sbt.is_valid()) {
		RD::get_singleton()->free_rid(bundle.hit_sbt);
	}
	if (bundle.pipeline.is_valid()) {
		RD::get_singleton()->free_rid(bundle.pipeline);
	}
	RD::get_singleton()->set_resource_name(p_task->new_pipeline, String("RT Pipeline [flags=") + itos(p_task->rt_flags) + "]");
	bundle.pipeline = p_task->new_pipeline;
	bundle.hit_sbt = new_sbt;
	bundle.live_hg_count = n;
	bundle.live_ready_mask = p_task->new_ready_mask;
}

// Single-lane rebuild dispatcher.

void SceneShaderRaytracing::_enqueue_build(PipelineBuildTask *p_task) {
	MutexLock lock(compile_lane.mutex);
	compile_lane.queue.push_back(p_task);
	_dispatch_next_locked();
}

void SceneShaderRaytracing::_dispatch_next_locked() {
	if (!async_compilation_enabled) {
		return;
	}
	if (compile_lane.current != nullptr || compile_lane.queue.is_empty()) {
		return;
	}
	PipelineBuildTask *t = compile_lane.queue[0];
	compile_lane.queue.remove_at(0);
	compile_lane.current = t;
	t->worker_id = WorkerThreadPool::get_singleton()->add_native_task(
			&SceneShaderRaytracing::_build_pipeline_worker_static, t, /*high_priority=*/false,
			"RT Pipeline Build");
}

void SceneShaderRaytracing::_kick_rebuild_if_idle() {
	bool lane_idle;
	{
		MutexLock lock(compile_lane.mutex);
		lane_idle = (compile_lane.current == nullptr) && compile_lane.queue.is_empty();
	}
	if (!lane_idle) {
		return;
	}
	for (KeyValue<uint32_t, PipelineBundle> &kv : pipeline_bundles) {
		if (!kv.value.dirty || !kv.value.initial_pipeline_built) {
			continue;
		}
		PipelineBuildTask *task = _make_pipeline_build_task(kv.key, kv.value);
		if (task) {
			kv.value.dirty = false;
			_enqueue_build(task);
		}
		break;
	}
}

void SceneShaderRaytracing::drain_completed_compiles() {
	while (true) {
		PipelineBuildTask *finished = nullptr;
		{
			MutexLock lock(compile_lane.mutex);
			if (compile_lane.current && compile_lane.current->done.is_set()) {
				finished = compile_lane.current;
				compile_lane.current = nullptr;
			}
		}
		if (!finished) {
			break;
		}
		if (finished->worker_id != WorkerThreadPool::INVALID_TASK_ID) {
			WorkerThreadPool::get_singleton()->wait_for_task_completion(finished->worker_id);
		}
		_finalize_pipeline_build(finished);
		memdelete(finished);

		{
			MutexLock lock(compile_lane.mutex);
			_dispatch_next_locked();
		}
	}
}

void SceneShaderRaytracing::_drain_lane_inline_main_thread() {
	while (true) {
		PipelineBuildTask *t = nullptr;
		{
			MutexLock lock(compile_lane.mutex);
			if (compile_lane.current) {
				break;
			}
			if (compile_lane.queue.is_empty()) {
				break;
			}
			t = compile_lane.queue[0];
			compile_lane.queue.remove_at(0);
		}
		_build_pipeline_worker(t);
		_finalize_pipeline_build(t);
		memdelete(t);
	}
}

void SceneShaderRaytracing::_join_lane_for_shutdown() {
	PipelineBuildTask *current = nullptr;
	LocalVector<PipelineBuildTask *> queued;
	{
		MutexLock lock(compile_lane.mutex);
		current = compile_lane.current;
		compile_lane.current = nullptr;
		queued = compile_lane.queue;
		compile_lane.queue.clear();
	}

	if (current) {
		current->abort_requested.set();
		if (current->worker_id != WorkerThreadPool::INVALID_TASK_ID && WorkerThreadPool::get_singleton()) {
			WorkerThreadPool::get_singleton()->wait_for_task_completion(current->worker_id);
		}
		if (current->new_pipeline.is_valid()) {
			RD::get_singleton()->free_rid(current->new_pipeline);
		}
		memdelete(current);
	}
	for (PipelineBuildTask *t : queued) {
		memdelete(t);
	}
}

void SceneShaderRaytracing::init(const String p_defines) {
	async_compilation_enabled = (bool)GLOBAL_GET("rendering/pathtracer/async_shader_compilation");

	// Raygen: one mode per bitmask of RAYGEN_SHADER_OPTIONS.
	const uint32_t variant_count = 1u << RAYGEN_SHADER_OPTION_COUNT;
	Vector<String> modes;
	modes.resize((int)variant_count);
	String *modes_ptr = modes.ptrw();
	for (uint32_t mask = 0; mask < variant_count; mask++) {
		modes_ptr[mask] = _raygen_variant_preamble(mask);
	}
	raygen_shader.initialize(modes, p_defines);

	// Slot 0: default HG (source_hash stays zero).
	{
		HitGroupSlot default_slot;
		default_slot.state = HGState::Ready;
		hit_group_slots.push_back(default_slot);
	}

	// Now create a version to access the embedded raytracing shader
	raygen_shader_version = raygen_shader.version_create();
	if (raygen_shader_version.is_valid()) {
		const PipelineBundle &b = ensure_pipeline_bundle(RT_FLAG_NONE);
		if (!b.pipeline.is_valid()) {
			WARN_PRINT("Failed to create default raytracing pipeline bundle");
		}
	} else {
		WARN_PRINT("Failed to create raytracing shader version");
	}

	{
		// Shader compiler (not used for RT, but needed for compatibility).
		ShaderCompiler::DefaultIdentifierActions actions;

		actions.renames["MODEL_MATRIX"] = "read_model_matrix";
		actions.renames["MODEL_NORMAL_MATRIX"] = "model_normal_matrix";
		actions.renames["VIEW_MATRIX"] = "read_view_matrix";
		actions.renames["INV_VIEW_MATRIX"] = "inv_view_matrix";
		actions.renames["PROJECTION_MATRIX"] = "projection_matrix";
		actions.renames["INV_PROJECTION_MATRIX"] = "inv_projection_matrix";
		actions.renames["MODELVIEW_MATRIX"] = "(read_view_matrix * read_model_matrix)";
		actions.renames["MODELVIEW_NORMAL_MATRIX"] = "mat3(read_view_matrix * read_model_matrix)";
		actions.renames["MAIN_CAM_INV_VIEW_MATRIX"] = "inv_view_matrix";

		actions.renames["VERTEX"] = "vertex";
		actions.renames["NORMAL"] = "normal";
		actions.renames["TANGENT"] = "tangent";
		actions.renames["BINORMAL"] = "binormal";
		actions.renames["POSITION"] = "position";
		actions.renames["UV"] = "uv_interp";
		actions.renames["UV2"] = "uv2_interp";
		actions.renames["COLOR"] = "color_interp";
		actions.renames["POINT_SIZE"] = "rt_point_size";
		actions.renames["INSTANCE_ID"] = "rt_instance_id";
		actions.renames["VERTEX_ID"] = "rt_vertex_id";

		actions.renames["ALPHA_SCISSOR_THRESHOLD"] = "alpha_scissor_threshold";
		actions.renames["ALPHA_HASH_SCALE"] = "alpha_hash_scale";
		actions.renames["ALPHA_ANTIALIASING_EDGE"] = "alpha_antialiasing_edge";
		actions.renames["ALPHA_TEXTURE_COORDINATE"] = "alpha_texture_coordinate";

		// Builtins.

		actions.renames["TIME"] = "global_time";
		actions.renames["PREV_TIME"] = "global_prev_time";
		actions.renames["EXPOSURE"] = "(1.0 / scene_data_block.data.emissive_exposure_normalization)";
		actions.renames["PI"] = String::num(Math::PI);
		actions.renames["TAU"] = String::num(Math::TAU);
		actions.renames["E"] = String::num(Math::E);
		actions.renames["OUTPUT_IS_SRGB"] = "SHADER_IS_SRGB";
		actions.renames["CLIP_SPACE_FAR"] = "SHADER_SPACE_FAR";
		actions.renames["VIEWPORT_SIZE"] = "read_viewport_size";

		actions.renames["FRAGCOORD"] = "rt_frag_coord";
		actions.renames["FRONT_FACING"] = "rt_front_facing";
		actions.renames["NORMAL_MAP"] = "normal_map";
		actions.renames["NORMAL_MAP_DEPTH"] = "normal_map_depth";
		actions.renames["ALBEDO"] = "albedo";
		actions.renames["ALPHA"] = "alpha";
		actions.renames["PREMUL_ALPHA_FACTOR"] = "premul_alpha";
		actions.renames["METALLIC"] = "metallic";
		actions.renames["SPECULAR"] = "specular";
		actions.renames["ROUGHNESS"] = "roughness";
		actions.renames["RIM"] = "rim";
		actions.renames["RIM_TINT"] = "rim_tint";
		actions.renames["CLEARCOAT"] = "clearcoat";
		actions.renames["CLEARCOAT_ROUGHNESS"] = "clearcoat_roughness";
		actions.renames["ANISOTROPY"] = "anisotropy";
		actions.renames["ANISOTROPY_FLOW"] = "anisotropy_flow";
		actions.renames["SSS_STRENGTH"] = "sss_strength";
		actions.renames["SSS_TRANSMITTANCE_COLOR"] = "transmittance_color";
		actions.renames["SSS_TRANSMITTANCE_DEPTH"] = "transmittance_depth";
		actions.renames["SSS_TRANSMITTANCE_BOOST"] = "transmittance_boost";
		actions.renames["BACKLIGHT"] = "backlight";
		actions.renames["AO"] = "ao";
		actions.renames["AO_LIGHT_AFFECT"] = "ao_light_affect";
		actions.renames["EMISSION"] = "emission";
		actions.renames["POINT_COORD"] = "rt_point_coord";
		actions.renames["INSTANCE_CUSTOM"] = "instance_custom";
		actions.renames["SCREEN_UV"] = "rt_screen_uv";
		actions.renames["DEPTH"] = "rt_depth";
		actions.renames["FOG"] = "fog";
		actions.renames["RADIANCE"] = "custom_radiance";
		actions.renames["IRRADIANCE"] = "custom_irradiance";
		actions.renames["BONE_INDICES"] = "bone_attrib";
		actions.renames["BONE_WEIGHTS"] = "weight_attrib";
		actions.renames["CUSTOM0"] = "custom0_attrib";
		actions.renames["CUSTOM1"] = "custom1_attrib";
		actions.renames["CUSTOM2"] = "custom2_attrib";
		actions.renames["CUSTOM3"] = "custom3_attrib";
		actions.renames["LIGHT_VERTEX"] = "light_vertex";

		// RT-only. A path tracer evaluates the material once per bounce per
		// sample, so letting a shader cheapen itself away from the camera-visible
		// hit scales with pt_bounces. Absent under rasterization, so guard uses
		// behind the RT define.
		actions.renames["BOUNCE_INDEX"] = "rt_bounce_index";
		actions.renames["PRIMARY_HIT"] = "rt_primary_hit";
		// Ray-differential stand-in for dFdx/dFdy(WORLD_POSITION), which cannot
		// exist in a hit shader. Feed these to textureGrad instead of taking
		// derivatives, or the sampler falls back to LOD 0 and aliases.
		actions.renames["RAY_FOOTPRINT_X"] = "rt_footprint_x";
		actions.renames["RAY_FOOTPRINT_Y"] = "rt_footprint_y";

		actions.renames["NODE_POSITION_WORLD"] = "read_model_matrix[3].xyz";
		actions.renames["CAMERA_POSITION_WORLD"] = "inv_view_matrix[3].xyz";
		actions.renames["CAMERA_DIRECTION_WORLD"] = "inv_view_matrix[2].xyz";
		actions.renames["CAMERA_VISIBLE_LAYERS"] = "scene_data_block.data.camera_visible_layers";
		actions.renames["NODE_POSITION_VIEW"] = "(read_view_matrix * read_model_matrix)[3].xyz";

		actions.renames["VIEW_INDEX"] = "ViewIndex";
		actions.renames["VIEW_MONO_LEFT"] = "0";
		actions.renames["VIEW_RIGHT"] = "1";
		actions.renames["EYE_OFFSET"] = "eye_offset";

		// For light.
		actions.renames["VIEW"] = "view";
		actions.renames["SPECULAR_AMOUNT"] = "specular_amount";
		actions.renames["LIGHT_COLOR"] = "light_color";
		actions.renames["LIGHT_IS_DIRECTIONAL"] = "is_directional";
		actions.renames["LIGHT"] = "light";
		actions.renames["ATTENUATION"] = "attenuation";
		actions.renames["DIFFUSE_LIGHT"] = "diffuse_light";
		actions.renames["SPECULAR_LIGHT"] = "specular_light";

		actions.usage_defines["NORMAL"] = "#define NORMAL_USED\n";
		actions.usage_defines["TANGENT"] = "#define TANGENT_USED\n";
		actions.usage_defines["BINORMAL"] = "@TANGENT";
		actions.usage_defines["RIM"] = "#define LIGHT_RIM_USED\n";
		actions.usage_defines["RIM_TINT"] = "@RIM";
		actions.usage_defines["CLEARCOAT"] = "#define LIGHT_CLEARCOAT_USED\n";
		actions.usage_defines["CLEARCOAT_ROUGHNESS"] = "@CLEARCOAT";
		actions.usage_defines["ANISOTROPY"] = "#define LIGHT_ANISOTROPY_USED\n";
		actions.usage_defines["ANISOTROPY_FLOW"] = "@ANISOTROPY";
		actions.usage_defines["AO"] = "#define AO_USED\n";
		actions.usage_defines["AO_LIGHT_AFFECT"] = "#define AO_USED\n";
		actions.usage_defines["UV"] = "#define UV_USED\n";
		actions.usage_defines["UV2"] = "#define UV2_USED\n";
		actions.usage_defines["BONE_INDICES"] = "#define BONES_USED\n";
		actions.usage_defines["BONE_WEIGHTS"] = "#define WEIGHTS_USED\n";
		actions.usage_defines["CUSTOM0"] = "#define CUSTOM0_USED\n";
		actions.usage_defines["CUSTOM1"] = "#define CUSTOM1_USED\n";
		actions.usage_defines["CUSTOM2"] = "#define CUSTOM2_USED\n";
		actions.usage_defines["CUSTOM3"] = "#define CUSTOM3_USED\n";
		actions.usage_defines["NORMAL_MAP"] = "#define NORMAL_MAP_USED\n";
		actions.usage_defines["NORMAL_MAP_DEPTH"] = "@NORMAL_MAP";
		actions.usage_defines["COLOR"] = "#define COLOR_USED\n";
		actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";
		actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n";
		actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX_USED\n";
		actions.usage_defines["PREMUL_ALPHA_FACTOR"] = "#define PREMUL_ALPHA_USED\n";

		actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n";
		actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n";
		actions.usage_defines["ALPHA_ANTIALIASING_EDGE"] = "#define ALPHA_ANTIALIASING_EDGE_USED\n";
		actions.usage_defines["ALPHA_TEXTURE_COORDINATE"] = "@ALPHA_ANTIALIASING_EDGE";

		actions.usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n";
		actions.usage_defines["SSS_TRANSMITTANCE_DEPTH"] = "#define ENABLE_TRANSMITTANCE\n";
		actions.usage_defines["BACKLIGHT"] = "#define LIGHT_BACKLIGHT_USED\n";
		actions.usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n";

		actions.usage_defines["FOG"] = "#define CUSTOM_FOG_USED\n";
		actions.usage_defines["RADIANCE"] = "#define CUSTOM_RADIANCE_USED\n";
		actions.usage_defines["IRRADIANCE"] = "#define CUSTOM_IRRADIANCE_USED\n";

		actions.usage_defines["MODEL_MATRIX"] = "#define MODEL_MATRIX_USED\n";

		actions.render_mode_defines["skip_vertex_transform"] = "#define SKIP_TRANSFORM_USED\n";
		actions.render_mode_defines["world_vertex_coords"] = "#define VERTEX_WORLD_COORDS_USED\n";
		actions.render_mode_defines["ensure_correct_normals"] = "#define ENSURE_CORRECT_NORMALS\n";
		actions.render_mode_defines["cull_front"] = "#define DO_SIDE_CHECK\n";
		actions.render_mode_defines["cull_disabled"] = "#define DO_SIDE_CHECK\n";
		actions.render_mode_defines["particle_trails"] = "#define USE_PARTICLE_TRAILS\n";
		actions.render_mode_defines["depth_prepass_alpha"] = "#define USE_OPAQUE_PREPASS\n";

		bool force_lambert = GLOBAL_GET("rendering/shading/overrides/force_lambert_over_burley");

		if (!force_lambert) {
			actions.render_mode_defines["diffuse_burley"] = "#define DIFFUSE_BURLEY\n";
		}

		actions.render_mode_defines["diffuse_lambert_wrap"] = "#define DIFFUSE_LAMBERT_WRAP\n";
		actions.render_mode_defines["diffuse_toon"] = "#define DIFFUSE_TOON\n";

		actions.render_mode_defines["sss_mode_skin"] = "#define SSS_MODE_SKIN\n";

		actions.render_mode_defines["specular_schlick_ggx"] = "#define SPECULAR_SCHLICK_GGX\n";

		actions.render_mode_defines["specular_toon"] = "#define SPECULAR_TOON\n";
		actions.render_mode_defines["specular_disabled"] = "#define SPECULAR_DISABLED\n";
		actions.render_mode_defines["shadows_disabled"] = "#define SHADOWS_DISABLED\n";
		actions.render_mode_defines["ambient_light_disabled"] = "#define AMBIENT_LIGHT_DISABLED\n";
		actions.render_mode_defines["shadow_to_opacity"] = "#define USE_SHADOW_TO_OPACITY\n";
		actions.render_mode_defines["unshaded"] = "#define MODE_UNSHADED\n";

		bool force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
		if (!force_vertex_shading) {
			// If forcing vertex shading, this will be defined already.
			actions.render_mode_defines["vertex_lighting"] = "#define USE_VERTEX_LIGHTING\n";
		}

		actions.render_mode_defines["debug_shadow_splits"] = "#define DEBUG_DRAW_PSSM_SPLITS\n";
		actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";

		actions.base_texture_binding_index = 1;
		actions.texture_layout_set = RenderForwardClustered::MATERIAL_UNIFORM_SET;
		actions.base_uniform_string = "material.";
		actions.base_varying_index = 14;
		actions.suppress_varying_io = true;

		actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP;
		actions.default_repeat = ShaderLanguage::REPEAT_ENABLE;
		actions.global_buffer_array_variable = "global_shader_uniforms.data";
		actions.instance_uniform_index_variable = "instances.data[instance_index_interp].instance_uniforms_ofs";

		actions.check_multiview_samplers = RendererCompositorRD::get_singleton()->is_xr_enabled(); // Make sure we check sampling multiview textures.

		// Intersection stage built-in renames (must match locals in the GLSL template).
		actions.renames["ORIGIN"] = "m_ORIGIN";
		actions.renames["DIRECTION"] = "m_DIRECTION";
		actions.renames["WORLD_ORIGIN"] = "m_WORLD_ORIGIN";
		actions.renames["WORLD_DIRECTION"] = "m_WORLD_DIRECTION";
		actions.renames["T_MIN"] = "m_T_MIN";
		actions.renames["T_MAX"] = "m_T_MAX";
		actions.renames["HIT_UV"] = "m_HIT_UV";
		actions.renames["HIT_NORMAL"] = "m_HIT_NORMAL";
		actions.renames["HIT_TANGENT"] = "m_HIT_TANGENT";
		actions.renames["PREV_POSITION"] = "m_PREV_POSITION";
		actions.renames["AABB_MIN"] = "m_AABB_MIN";
		actions.renames["AABB_MAX"] = "m_AABB_MAX";

		// Stage function rename: bypass _mkid prefix to match the GLSL macro.
		actions.renames["report_intersection"] = "report_intersection";

		compiler.initialize(actions);
	}

	// Raster shader/material/sampler defaults unused on RT path.
	default_shader = RID();
	default_material = RID();
	default_shader_rd = RID();
	default_shader_sdfgi_rd = RID();
	default_material_shader_ptr = nullptr;
	default_material_uniform_set = RID();

	overdraw_material_shader = RID();
	overdraw_material = RID();
	overdraw_material_shader_ptr = nullptr;
	overdraw_material_uniform_set = RID();

	debug_shadow_splits_material_shader = RID();
	debug_shadow_splits_material = RID();
	debug_shadow_splits_material_shader_ptr = nullptr;
	debug_shadow_splits_material_uniform_set = RID();

	default_vec4_xform_buffer = RID();
	default_vec4_xform_uniform_set = RID();
	shadow_sampler = RID();
}

void SceneShaderRaytracing::set_default_specialization(const ShaderSpecialization &p_specialization) {
	default_specialization = p_specialization;

	for (SelfList<ShaderData> *E = shader_list.first(); E; E = E->next()) {
		E->self()->pipeline_hash_map.clear_pipelines();
	}
}

void SceneShaderRaytracing::enable_advanced_shader_group(bool p_needs_multiview) {
}

bool SceneShaderRaytracing::is_multiview_shader_group_enabled() const {
	return false;
}

bool SceneShaderRaytracing::is_advanced_shader_group_enabled(bool p_multiview) const {
	return false;
}

uint32_t SceneShaderRaytracing::get_pipeline_compilations(RSE::PipelineSource p_source) {
	MutexLock lock(SceneShaderRaytracing::singleton_mutex);
	return pipeline_compilations[p_source];
}