OverviewFilesCommitsIssuesDocsReleasesPeers

nvidia-rtx-godot / servers / rendering / renderer_rd / effects / dlss.cpp

1155 lines · cpp ref:
/**************************************************************************/
/*  dlss.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 "dlss.h"

#include "../storage_rd/material_storage.h"
#include "../uniform_set_cache_rd.h"

#ifdef STREAMLINE_ENABLED
#define ENABLE_DLSS 1
#endif

#ifdef ENABLE_DLSS
#include "drivers/streamline/streamline_context.h"
#elif defined(NGX_DLSS_ENABLED)
#define ENABLE_NGX_DLSS 1
#include <dlfcn.h>
#include <string>
#include "core/io/dir_access.h"
#include "core/os/os.h"
#include "drivers/vulkan/godot_vulkan.h"
#include "nvsdk_ngx_vk.h"
#include "nvsdk_ngx_helpers_vk.h"
#include "nvsdk_ngx_helpers_dlssd_vk.h"
#endif

using namespace RendererRD;

#ifdef ENABLE_DLSS

// Texture layout/state constants (avoid including Vulkan/D3D12 headers here).
static constexpr uint64_t DLSS_VK_IMAGE_LAYOUT_SHADER_READ_ONLY = 5;
static constexpr uint64_t DLSS_D3D12_RESOURCE_STATE_NON_PIXEL_SR = 0x40;
static constexpr float DLSS_OPTIMAL_MODE_MAX_DISTANCE = 1000000.0f;
namespace RendererRD {
class DLSSContextInner : public DLSSContext {
public:
	sl::ViewportHandle viewport;
	sl::Constants constants;
	sl::DLSSOptions currentDlssOptions;
	sl::DLSSOptimalSettings currentOptimalSettings;
	sl::DLSSDOptions currentDlssDOptions; // DLSS Ray Reconstruction options

	DLSSContextInner();
	virtual ~DLSSContextInner();

	sl::DLSSMode find_optimal_mode(uint32_t outputWidth, uint32_t outputHeight, uint32_t desiredWidth, uint32_t desiredHeight, sl::DLSSOptimalSettings &out_optimalSettings, bool use_dlss_rr = false) {
		// For DLSS-RR, use the DLSS-D API; for regular DLSS, use the standard DLSS API
		if (use_dlss_rr) {
			if (StreamlineContext::get().slDLSSDGetOptimalSettings == nullptr) {
				return sl::DLSSMode::eOff;
			}
		} else {
			if (StreamlineContext::get().slDLSSGetOptimalSettings == nullptr) {
				return sl::DLSSMode::eOff;
			}
		}

		sl::DLSSMode modes[] = { sl::DLSSMode::eDLAA, sl::DLSSMode::eMaxQuality, sl::DLSSMode::eBalanced, sl::DLSSMode::eMaxPerformance, sl::DLSSMode::eUltraPerformance };
		sl::DLSSOptimalSettings settings[sizeof(modes) / sizeof(modes[0])];
		bool validSettings[sizeof(modes) / sizeof(modes[0])];
		Vector2 distance[sizeof(modes) / sizeof(modes[0])];
		memset(validSettings, 0, sizeof(validSettings));

		for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
			sl::Result result;

			if (use_dlss_rr) {
				sl::DLSSDOptions dlssDOptions = {};
				dlssDOptions.outputWidth = outputWidth;
				dlssDOptions.outputHeight = outputHeight;
				dlssDOptions.mode = modes[i];
				sl::DLSSDOptimalSettings dlssDSettings;
				result = StreamlineContext::get().slDLSSDGetOptimalSettings(dlssDOptions, dlssDSettings);
				if (result == sl::Result::eOk) {
					// Copy to common settings struct
					settings[i].optimalRenderWidth = dlssDSettings.optimalRenderWidth;
					settings[i].optimalRenderHeight = dlssDSettings.optimalRenderHeight;
					settings[i].optimalSharpness = dlssDSettings.optimalSharpness;
					settings[i].renderWidthMin = dlssDSettings.renderWidthMin;
					settings[i].renderHeightMin = dlssDSettings.renderHeightMin;
					settings[i].renderWidthMax = dlssDSettings.renderWidthMax;
					settings[i].renderHeightMax = dlssDSettings.renderHeightMax;
				}
			} else {
				sl::DLSSOptions dlssOptions = {};
				dlssOptions.outputWidth = outputWidth;
				dlssOptions.outputHeight = outputHeight;
				dlssOptions.mode = modes[i];
				result = StreamlineContext::get().slDLSSGetOptimalSettings(dlssOptions, settings[i]);
			}

			if (result != sl::Result::eOk) {
				continue;
			}

			sl::DLSSOptimalSettings &optimalSettings = settings[i];
			if (desiredWidth >= optimalSettings.renderWidthMin &&
					desiredWidth <= optimalSettings.renderWidthMax &&
					desiredHeight >= optimalSettings.renderHeightMin &&
					desiredHeight <= optimalSettings.renderHeightMax) {
				validSettings[i] = true;
				distance[i] = Vector2(fabsf((float)optimalSettings.optimalRenderWidth - (float)desiredWidth), fabsf((float)optimalSettings.optimalRenderHeight - (float)desiredHeight));
			}
		}

		// now select the closest match
		Vector2 closestDistance(DLSS_OPTIMAL_MODE_MAX_DISTANCE, DLSS_OPTIMAL_MODE_MAX_DISTANCE);
		int closestDistanceMatch = -1;
		for (size_t i = 0; i < sizeof(modes) / sizeof(modes[0]); i++) {
			if (validSettings[i]) {
				if (distance[i].length_squared() < closestDistance.length_squared()) {
					closestDistanceMatch = i;
					closestDistance = distance[i];
				}
			}
		}

		if (closestDistanceMatch != -1) {
			out_optimalSettings = settings[closestDistanceMatch];
			return modes[closestDistanceMatch];
		}

		ERR_FAIL_V_MSG(sl::DLSSMode::eOff, "Couldn't find an appropriate DLSS mode.");
	}
}; // end class
}; // end namespace RendererRD

static Vector<unsigned int> g_dlss_freeViewportIndices;
static unsigned int g_dlss_viewportIndex = 1;

DLSSContextInner::~DLSSContextInner() {
	g_dlss_freeViewportIndices.push_back((unsigned int)viewport);
}

DLSSContextInner::DLSSContextInner() {
	if (g_dlss_freeViewportIndices.size() == 0) {
		g_dlss_freeViewportIndices.push_back(g_dlss_viewportIndex++);
	}
	viewport = g_dlss_freeViewportIndices[g_dlss_freeViewportIndices.size() - 1];
	g_dlss_freeViewportIndices.remove_at(g_dlss_freeViewportIndices.size() - 1);
}

DLSSEffect::DLSSEffect() {
	// Initialize motion vector decode shader
	Vector<String> modes;
	modes.push_back("\n");
	shaders.mvec_decode_shader.initialize(modes, "");
	shaders.mvec_decode_version = shaders.mvec_decode_shader.version_create();
	shaders.mvec_decode_pipeline = RD::get_singleton()->compute_pipeline_create(shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0));
}

DLSSEffect::~DLSSEffect() {
	// Deinitialize motion vector decode
	shaders.mvec_decode_shader.version_free(shaders.mvec_decode_version);
}

DLSSContext *DLSSEffect::create_context(Size2i p_internal_size, Size2i p_target_size) {
	DLSSContextInner *context = memnew(RendererRD::DLSSContextInner);

	context->currentDlssOptions.mode = context->find_optimal_mode(p_target_size.width, p_target_size.height, p_internal_size.width, p_internal_size.height, context->currentOptimalSettings);
	context->currentDlssOptions.outputWidth = p_target_size.width;
	context->currentDlssOptions.outputHeight = p_target_size.height;

	context->is_d3d12 = (RD::get_singleton()->get_device_api_name().to_lower() == "d3d12");

	return context;
}

static sl::float4x4 sl_make_identity_matrix() {
	sl::float4x4 ret;
	ret.setRow(0, sl::float4(1.0f, 0.0f, 0.0f, 0.0f));
	ret.setRow(1, sl::float4(0.0f, 1.0f, 0.0f, 0.0f));
	ret.setRow(2, sl::float4(0.0f, 0.0f, 1.0f, 0.0f));
	ret.setRow(3, sl::float4(0.0f, 0.0f, 0.0f, 1.0f));
	return ret;
}

static sl::float4x4 sl_convert_matrix(const Projection &mtx) {
	sl::float4x4 ret;
	ret.setRow(0, sl::float4(mtx.columns[0].x, mtx.columns[1].x, mtx.columns[2].x, mtx.columns[3].x));
	ret.setRow(1, sl::float4(mtx.columns[0].y, mtx.columns[1].y, mtx.columns[2].y, mtx.columns[3].y));
	ret.setRow(2, sl::float4(mtx.columns[0].z, mtx.columns[1].z, mtx.columns[2].z, mtx.columns[3].z));
	ret.setRow(3, sl::float4(mtx.columns[0].w, mtx.columns[1].w, mtx.columns[2].w, mtx.columns[3].w));
	return ret;
}

static sl::float3 sl_convert_vector(const Vector3 &vec) {
	return sl::float3(vec.x, vec.y, vec.z);
}

void DLSSEffect::upscale(const DLSSContext::Parameters &p_params) {
	DLSSContextInner *context = (DLSSContextInner *)p_params.context;

	// Delay enablement
	if (context->delay > 0) {
		--context->delay;
		return;
	}

	// If DLSS is not loaded, escape early
	if (StreamlineContext::get().slDLSSSetOptions == nullptr) {
		return;
	}

	// Begin frame if needed.
	if (StreamlineContext::get().last_token == nullptr) {
		StreamlineContext::get().get_new_frame_token();
	}

	context->last_parameters = p_params;
	context->last_effect = this;

	// Decode mvecs
	{
		RD::get_singleton()->draw_command_begin_label("Decode Invalid Motion Vectors");
		UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
		ERR_FAIL_NULL(uniform_set_cache);
		MaterialStorage *material_storage = MaterialStorage::get_singleton();
		ERR_FAIL_NULL(material_storage);

		// setup our uniforms
		RD::Uniform u_velocity_image(RD::UNIFORM_TYPE_IMAGE, 0, p_params.velocity);
		RD::Uniform u_depth_texture(RD::UNIFORM_TYPE_TEXTURE, 0, p_params.depth);

		RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();

		RID shader = shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0);
		ERR_FAIL_COND(shader.is_null());

		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, shaders.mvec_decode_pipeline);
		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_velocity_image), 0);
		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_depth_texture), 1);

		auto texture_format = RD::get_singleton()->texture_get_format(p_params.velocity);

		float push_constants[20];
		push_constants[0] = texture_format.width;
		push_constants[1] = texture_format.height;
		push_constants[2] = 0.0f;
		push_constants[3] = 0.0f;
		memcpy(push_constants + 4, &p_params.reprojection.columns[0].x, sizeof(float) * 16);
		RD::get_singleton()->compute_list_set_push_constant(compute_list, push_constants, sizeof(push_constants));

		RD::get_singleton()->compute_list_dispatch_threads(compute_list, texture_format.width, texture_format.height, 1);
		RD::get_singleton()->compute_list_add_barrier(compute_list);

		RD::get_singleton()->compute_list_end();
		RD::get_singleton()->draw_command_end_label();
	}

	// Inject DLSS into the render graph
	RD::CallbackResource res[8]; // Increased for DLSS-RR buffers
	int num_resources = 0;
	res[num_resources++].rid = p_params.color;
	res[num_resources++].rid = p_params.output;
	res[num_resources++].rid = p_params.depth;
	res[num_resources++].rid = p_params.velocity;

	// Add DLSS-RR buffers if provided
	if (p_params.dlss_rr) {
		if (p_params.dlss_rr_diffuse_albedo.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_diffuse_albedo;
		}
		if (p_params.dlss_rr_specular_albedo.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_specular_albedo;
		}
		if (p_params.dlss_rr_normal_roughness.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_normal_roughness;
		}
		if (p_params.dlss_rr_specular_hit_dist.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_specular_hit_dist;
		}
	}

	for (int i = 0; i < num_resources; i++) {
		res[i].usage = RD::CALLBACK_RESOURCE_USAGE_TEXTURE_SAMPLE;
	}
	RD::get_singleton()->driver_callback_add((RDD::DriverCallback)DLSSEffect::_upscale_internal_graph_callback, p_params.context, VectorView<RD::CallbackResource>(res, num_resources));
}

void DLSSEffect::_upscale_internal(RDD::CommandBufferID cmdid, const DLSSContext::Parameters &p_params) {
	DLSSContextInner *context = (DLSSContextInner *)p_params.context;

	void *nativeCmdlist = RD::get_singleton()->get_device_driver()->command_buffer_get_native_handle(cmdid);

	// Helper function for tagging resources.
	auto assignResource = [context](sl::Resource *resources, sl::ResourceTag *resourceTags, int &numResources, RID textureRID, sl::BufferType bufferType, sl::ResourceLifecycle lifecycle) {
		if (!textureRID.is_valid() || textureRID.is_null()) {
			return;
		}

		RD::TextureFormat texture_format = RD::get_singleton()->texture_get_format(textureRID);
		uint64_t texture_image = RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE, textureRID);
		uint64_t texture_view = RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_VIEW, textureRID);
		uint64_t texture_device_memory = RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_DEVICE_MEMORY, textureRID);
		uint64_t texture_state = DLSS_VK_IMAGE_LAYOUT_SHADER_READ_ONLY;
		if (context->is_d3d12) {
			texture_state = DLSS_D3D12_RESOURCE_STATE_NON_PIXEL_SR;
		}
		uint64_t texture_vkformat = RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_DATA_FORMAT, textureRID);
		uint64_t texture_usage_flags = RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_USAGE_FLAGS, textureRID);
		auto &destinationResource = resources[numResources];
		if (context->is_d3d12) {
			destinationResource = sl::Resource(sl::ResourceType::eTex2d,
					(void *)texture_view, texture_state);
		} else {
			destinationResource = sl::Resource(sl::ResourceType::eTex2d,
					(void *)texture_image, (void *)texture_device_memory, (void *)texture_view, texture_state);
		}
		destinationResource.width = texture_format.width;
		destinationResource.height = texture_format.height;
		destinationResource.nativeFormat = texture_vkformat;
		destinationResource.arrayLayers = texture_format.array_layers;
		destinationResource.flags = 0;
		destinationResource.mipLevels = texture_format.mipmaps;
		destinationResource.usage = texture_usage_flags;

		resourceTags[numResources] = sl::ResourceTag(resources + numResources, bufferType, lifecycle, nullptr);
		++numResources;
	};

	// Set DLSS or DLSS-RR options depending on mode
	bool use_dlss_rr = p_params.dlss_rr && StreamlineContext::get().slDLSSDSetOptions != nullptr && StreamlineContext::get().streamline_capabilities.dlss_rr_available;

	if (use_dlss_rr) {
		// Set DLSS-RR (Ray Reconstruction) options
		context->currentDlssDOptions.mode = context->currentDlssOptions.mode;
		context->currentDlssDOptions.outputWidth = context->currentDlssOptions.outputWidth;
		context->currentDlssDOptions.outputHeight = context->currentDlssOptions.outputHeight;
		context->currentDlssDOptions.colorBuffersHDR = sl::Boolean::eTrue;
		context->currentDlssDOptions.alphaUpscalingEnabled = p_params.dlss_rr_alpha_upscaling ? sl::Boolean::eTrue : sl::Boolean::eFalse;
		context->currentDlssDOptions.normalRoughnessMode = sl::DLSSDNormalRoughnessMode::ePacked; // Normal XYZ + Roughness W

		// Set world-to-camera and camera-to-world matrices for DLSS-RR
		// worldToCameraView = view matrix (world-to-camera transformation)
		// cameraViewToWorld = inverse view matrix (camera-to-world transformation)
		Transform3D view_matrix = p_params.cam_transform.affine_inverse();
		context->currentDlssDOptions.worldToCameraView = sl_convert_matrix(Projection(view_matrix)); //
		context->currentDlssDOptions.cameraViewToWorld = sl_convert_matrix(Projection(view_matrix).inverse());
		char dlssPreset = p_params.preset;
		if (dlssPreset == '?') {
			dlssPreset = StreamlineContext::get().dlss_rr_default_preset;
		}

		if (dlssPreset == '?') {
			context->currentDlssDOptions.dlaaPreset = sl::DLSSDPreset::eDefault;
			context->currentDlssDOptions.qualityPreset = sl::DLSSDPreset::eDefault;
			context->currentDlssDOptions.balancedPreset = sl::DLSSDPreset::eDefault;
			context->currentDlssDOptions.performancePreset = sl::DLSSDPreset::eDefault;
			context->currentDlssDOptions.ultraPerformancePreset = sl::DLSSDPreset::eDefault;
		} else {
			int presetNo = ((int)dlssPreset - (int)'D');
			sl::DLSSDPreset preset = (sl::DLSSDPreset)((int)sl::DLSSDPreset::ePresetD + presetNo);
			context->currentDlssDOptions.dlaaPreset = preset;
			context->currentDlssDOptions.qualityPreset = preset;
			context->currentDlssDOptions.balancedPreset = preset;
			context->currentDlssDOptions.performancePreset = preset;
			context->currentDlssDOptions.ultraPerformancePreset = preset;
		}

		sl::Result result = StreamlineContext::get().slDLSSDSetOptions(context->viewport, context->currentDlssDOptions);
		if (result != sl::Result::eOk) {
			ERR_FAIL_MSG("Failed to call streamline slDLSSDSetOptions. Result: " + String(StreamlineContext::result_to_string(result)));
		}
	} else if (StreamlineContext::get().slDLSSSetOptions != nullptr && StreamlineContext::get().streamline_capabilities.dlss_available) {
		// Set regular DLSS options
		if (p_params.exposure.is_null() || !p_params.exposure.is_valid()) {
			context->currentDlssOptions.useAutoExposure = sl::Boolean::eTrue;
		} else {
			context->currentDlssOptions.useAutoExposure = sl::Boolean::eFalse;
		}

		context->currentDlssOptions.colorBuffersHDR = sl::Boolean::eTrue;
		char dlssPreset = p_params.preset;
		if (dlssPreset == '?') {
			dlssPreset = StreamlineContext::get().dlss_default_preset;
		}

		if (dlssPreset == '?') {
			context->currentDlssOptions.dlaaPreset = sl::DLSSPreset::eDefault;
			context->currentDlssOptions.qualityPreset = sl::DLSSPreset::eDefault;
			context->currentDlssOptions.balancedPreset = sl::DLSSPreset::eDefault;
			context->currentDlssOptions.performancePreset = sl::DLSSPreset::eDefault;
			context->currentDlssOptions.ultraPerformancePreset = sl::DLSSPreset::eDefault;
		} else {
			int presetNo = ((int)dlssPreset - (int)'F');
			sl::DLSSPreset preset = (sl::DLSSPreset)((int)sl::DLSSPreset::ePresetF + presetNo);
			context->currentDlssOptions.dlaaPreset = preset;
			context->currentDlssOptions.qualityPreset = preset;
			context->currentDlssOptions.balancedPreset = preset;
			context->currentDlssOptions.performancePreset = preset;
			context->currentDlssOptions.ultraPerformancePreset = preset;
		}

		sl::Result result = StreamlineContext::get().slDLSSSetOptions(context->viewport, context->currentDlssOptions);
		if (result != sl::Result::eOk) {
			ERR_FAIL_MSG("Failed to call streamline slDLSSSetOptions. Result: " + String(StreamlineContext::result_to_string(result)));
		}
	}

	// Set SL Options
	if (StreamlineContext::get().slSetConstants != nullptr) {
		sl::float4x4 mtxIdentity = sl_make_identity_matrix();
		context->constants.cameraViewToClip = sl_convert_matrix(p_params.cam_projection); // projection mtx (unjittered)
		context->constants.clipToCameraView = sl_convert_matrix(p_params.cam_projection.inverse()); // projection mtx (unjittered, inverted)
		context->constants.clipToLensClip = mtxIdentity; // keep identity unless some lens distortion is applied
		context->constants.clipToPrevClip = sl_convert_matrix(p_params.reprojection); // reprojection matrix
		context->constants.prevClipToClip = sl_convert_matrix(p_params.reprojection.inverse()); // inverted reprojection matrix

		context->constants.cameraPos = sl_convert_vector(p_params.cam_transform.get_origin());
		context->constants.cameraFwd = sl_convert_vector(-p_params.cam_transform.get_basis().rows[2]);
		context->constants.cameraUp = sl_convert_vector(p_params.cam_transform.get_basis().rows[1]);
		context->constants.cameraRight = sl_convert_vector(p_params.cam_transform.get_basis().rows[0]);

		context->constants.cameraNear = p_params.z_near;
		context->constants.cameraFar = p_params.z_far;
		context->constants.cameraFOV = Math::deg_to_rad(p_params.fovy);
		context->constants.cameraMotionIncluded = sl::Boolean::eTrue;
		context->constants.cameraAspectRatio = static_cast<float>(context->currentDlssOptions.outputWidth) / static_cast<float>(context->currentDlssOptions.outputHeight);
		context->constants.cameraPinholeOffset = sl::float2(0.0f, 0.0f);
		context->constants.depthInverted = p_params.reverse_depth ? sl::Boolean::eTrue : sl::Boolean::eFalse;
		context->constants.motionVectors3D = sl::Boolean::eFalse;
		context->constants.motionVectorsDilated = sl::Boolean::eFalse;
		context->constants.motionVectorsJittered = sl::Boolean::eFalse;
		context->constants.jitterOffset = sl::float2(p_params.jitter.x, p_params.jitter.y);
		context->constants.mvecScale = sl::float2(1.0f, 1.0f);
		context->constants.orthographicProjection = sl::Boolean::eFalse;
		context->constants.reset = p_params.reset_accumulation ? sl::Boolean::eTrue : sl::Boolean::eFalse;
		sl::Result result = StreamlineContext::get().slSetConstants(context->constants, *StreamlineContext::get().last_token, context->viewport);
		if (result != sl::Result::eOk) {
			ERR_FAIL_MSG("Failed to call streamline slSetConstants. Result: " + String(StreamlineContext::result_to_string(result)));
		}
	}

	// Tag resources
	if (StreamlineContext::get().slSetTag != nullptr) {
		sl::Resource resources[10];
		sl::ResourceTag resourceTags[10];
		int numResources = 0;

		assignResource(resources, resourceTags, numResources, p_params.color, sl::kBufferTypeScalingInputColor, sl::ResourceLifecycle::eValidUntilPresent);
		assignResource(resources, resourceTags, numResources, p_params.output, sl::kBufferTypeScalingOutputColor, sl::ResourceLifecycle::eValidUntilPresent);
		assignResource(resources, resourceTags, numResources, p_params.depth, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent);
		assignResource(resources, resourceTags, numResources, p_params.velocity, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eValidUntilPresent);

		// Tag DLSS-RR specific buffers if enabled
		if (use_dlss_rr) {
			// kBufferTypeAlbedo is used for diffuse albedo
			assignResource(resources, resourceTags, numResources, p_params.dlss_rr_diffuse_albedo, sl::kBufferTypeAlbedo, sl::ResourceLifecycle::eValidUntilPresent);
			assignResource(resources, resourceTags, numResources, p_params.dlss_rr_specular_albedo, sl::kBufferTypeSpecularAlbedo, sl::ResourceLifecycle::eValidUntilPresent);
			// kBufferTypeNormalRoughness for packed normal+roughness (XYZ=normal, W=roughness)
			assignResource(resources, resourceTags, numResources, p_params.dlss_rr_normal_roughness, sl::kBufferTypeNormalRoughness, sl::ResourceLifecycle::eValidUntilPresent);
			assignResource(resources, resourceTags, numResources, p_params.dlss_rr_specular_hit_dist, sl::kBufferTypeSpecularHitDistance, sl::ResourceLifecycle::eValidUntilPresent);
		}

		sl::Result result = StreamlineContext::get().slSetTag(context->viewport, resourceTags, numResources, nativeCmdlist);
		if (result != sl::Result::eOk) {
			ERR_FAIL_MSG("Failed to call streamline slSetTag. Result: " + String(StreamlineContext::result_to_string(result)));
		}
	}

	// Toggle DLSS Frame Generation (only enabled in game mode)
	if (StreamlineContext::get().slDLSSGSetOptions != nullptr && StreamlineContext::get().is_game && StreamlineContext::get().streamline_capabilities.dlss_g_available) {
		sl::DLSSGOptions dlssGOptions{};
		bool wantActivateDLSSG = p_params.dlss_g;
		bool canActivateDLSSG = StreamlineContext::get().dlssg_delay == 0;

		// Disable previous DLSS-G context if needed
		if (StreamlineContext::get().dlssg_viewport != sl::ViewportHandle(-1) && ((!wantActivateDLSSG && StreamlineContext::get().dlssg_viewport == context->viewport) || (wantActivateDLSSG && StreamlineContext::get().dlssg_viewport != context->viewport))) {
			WARN_PRINT("Disabling DLSS-G on viewport: " + itos((unsigned int)StreamlineContext::get().dlssg_viewport));
			dlssGOptions.mode = sl::DLSSGMode::eOff;
			sl::Result result = StreamlineContext::get().slDLSSGSetOptions(StreamlineContext::get().dlssg_viewport, dlssGOptions);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slDLSSGSetOptions. Result: " + String(StreamlineContext::result_to_string(result)));
			}

			StreamlineContext::get().dlssg_viewport = sl::ViewportHandle(-1);
		}

		// Enable new DLSS-G context if needed
		if (canActivateDLSSG && wantActivateDLSSG && StreamlineContext::get().dlssg_viewport != context->viewport) {
			WARN_PRINT("Enabling DLSS-G on viewport: " + itos((unsigned int)context->viewport));

			dlssGOptions.mode = sl::DLSSGMode::eOn;
			sl::Result result = StreamlineContext::get().slDLSSGSetOptions(context->viewport, dlssGOptions);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slDLSSGSetOptions. Result: " + String(StreamlineContext::result_to_string(result)));
			}

			StreamlineContext::get().dlssg_viewport = context->viewport;
		}
	}

	// Evaluate DLSS Super Resolution or DLSS Ray Reconstruction
	if (context->currentDlssOptions.mode != sl::DLSSMode::eOff) {
		const sl::BaseStructure *inputs[] = { &context->viewport };
		sl::Result result;

		if (use_dlss_rr && StreamlineContext::get().streamline_capabilities.dlss_rr_available) {
			// Use DLSS Ray Reconstruction
			result = StreamlineContext::get().slEvaluateFeature(sl::kFeatureDLSS_RR, *StreamlineContext::get().last_token, inputs, 1, nativeCmdlist);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slEvaluateFeature for DLSS Ray Reconstruction. Result: " + String(StreamlineContext::result_to_string(result)));
			}
		} else if (StreamlineContext::get().streamline_capabilities.dlss_available) {
			// Use regular DLSS
			result = StreamlineContext::get().slEvaluateFeature(sl::kFeatureDLSS, *StreamlineContext::get().last_token, inputs, 1, nativeCmdlist);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slEvaluateFeature for DLSS Super Resolution. Result: " + String(StreamlineContext::result_to_string(result)));
			}
		}
	}

	// NIS support
	// **********
	if (p_params.sharpness > 0.0f && StreamlineContext::get().slNISSetOptions != nullptr && StreamlineContext::get().streamline_capabilities.nis_available) {
		{ // Set NIS settings
			sl::NISOptions options;
			options.hdrMode = sl::NISHDR::eNone;
			options.mode = sl::NISMode::eSharpen;
			options.sharpness = p_params.sharpness;
			StreamlineContext::get().slNISSetOptions(context->viewport, options);
		}

		{ // Tag NIS buffers
			sl::Resource resources[3];
			sl::ResourceTag resourceTags[3];
			int numResources = 0;

			assignResource(resources, resourceTags, numResources, p_params.output, sl::kBufferTypeScalingInputColor, sl::ResourceLifecycle::eOnlyValidNow);
			assignResource(resources, resourceTags, numResources, p_params.output, sl::kBufferTypeScalingOutputColor, sl::ResourceLifecycle::eValidUntilPresent);

			sl::Result result = StreamlineContext::get().slSetTag(context->viewport, resourceTags, numResources, nativeCmdlist);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slSetTag for NIS. Result: " + String(StreamlineContext::result_to_string(result)));
			}
		}

		{ // Evaluate NIS
			const sl::BaseStructure *inputs[] = { &context->viewport };
			sl::Result result = StreamlineContext::get().slEvaluateFeature(sl::kFeatureNIS, *StreamlineContext::get().last_token, inputs, 1, nativeCmdlist);
			if (result != sl::Result::eOk) {
				ERR_FAIL_MSG("Failed to call streamline slEvaluateFeature for NIS. Result: " + String(StreamlineContext::result_to_string(result)));
			}
		}
	}
}

void RendererRD::DLSSEffect::_upscale_internal_graph_callback(RenderingDeviceDriver *p_driver, RDD::CommandBufferID p_command_buffer, void *p_userdata) {
	DLSSContextInner *self = (DLSSContextInner *)p_userdata;
	self->last_effect->_upscale_internal(p_command_buffer, self->last_parameters);
}

bool DLSSEffect::is_ready(DLSSContext *p_context) {
	DLSSContextInner *context = (DLSSContextInner *)p_context;
	if (context->currentDlssOptions.mode == sl::DLSSMode::eOff) {
		return false; // unsupported mode.
	}
	if (context->delay > 0) {
		return false; // still in delay mode
	}
	return true;
}
#elif defined(ENABLE_NGX_DLSS)

// ============================================================================
// NGX DLSS backend for Linux — bypasses Streamline, talks directly to NGX SDK
// ============================================================================

static bool g_ngx_initialized = false;
static NVSDK_NGX_Parameter *g_ngx_params = nullptr;
static bool g_ngx_dlss_rr_available = false;
static bool g_ngx_dlss_sr_available = false;

namespace RendererRD {
class NGXDLSSContext : public DLSSContext {
public:
	NVSDK_NGX_Handle *dlss_rr_handle = nullptr;
	NVSDK_NGX_Handle *dlss_sr_handle = nullptr;
	bool feature_created = false;
	// DLSS-RR camera matrices. These MUST outlive the evaluate() call: the NGX
	// helper hands them to NVSDK_NGX_Parameter_SetVoidPointer, which stores the
	// POINTER rather than copying, and NGX dereferences it during evaluation --
	// including on later frames and on the SR path, which never sets them. Held
	// as stack locals they dangled the moment evaluate() returned, which is what
	// produced "WorldToScreenMatrix not invertible!" every frame.
	float world_to_view_matrix[16] = {};
	float view_to_clip_matrix[16] = {};
	NVSDK_NGX_PerfQuality_Value perf_quality = NVSDK_NGX_PerfQuality_Value_Balanced;
	uint32_t render_width = 0;
	uint32_t render_height = 0;
	uint32_t output_width = 0;
	uint32_t output_height = 0;

	// Which kind of feature feature_created refers to. RR and SR are distinct NGX
	// features with separate handles, so switching between them at runtime has to
	// tear the old one down -- otherwise the evaluate path picks the branch for
	// the newly requested mode and finds a null handle.
	bool feature_is_rr = false;

	void release_feature() {
		if (dlss_rr_handle) {
			NVSDK_NGX_VULKAN_ReleaseFeature(dlss_rr_handle);
			dlss_rr_handle = nullptr;
		}
		if (dlss_sr_handle) {
			NVSDK_NGX_VULKAN_ReleaseFeature(dlss_sr_handle);
			dlss_sr_handle = nullptr;
		}
		feature_created = false;
	}

	virtual ~NGXDLSSContext() {
		release_feature();
	}
};
} // namespace RendererRD

static void _ngx_log_callback(const char *message, NVSDK_NGX_Logging_Level level, NVSDK_NGX_Feature source) {
	print_line(vformat("[NGX LOG] [%d] %s", (int)level, String(message)));
}

static bool _ngx_ensure_init() {
	if (g_ngx_initialized) {
		return true;
	}

	RenderingDevice *rd = RD::get_singleton();
	if (!rd) {
		return false;
	}

	VkInstance vk_instance = (VkInstance)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TOPMOST_OBJECT, RID(), 0);
	VkPhysicalDevice vk_phys_device = (VkPhysicalDevice)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_PHYSICAL_DEVICE, RID(), 0);
	VkDevice vk_device = (VkDevice)rd->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_LOGICAL_DEVICE, RID(), 0);

	if (!vk_instance || !vk_phys_device || !vk_device) {
		ERR_PRINT("[NGX DLSS] Failed to get Vulkan handles from Godot RenderingDevice");
		return false;
	}

	print_line("[NGX DLSS] Vulkan handles acquired, initializing NGX...");

	// Create the log/data directory
	DirAccess::make_dir_absolute("/tmp/ngx_dlss_logs");

	// Tell NGX where to find DLSS feature .so files (libnvidia-ngx-dlss*.so)
	// Search order:
	//   1) next to executable (shipped game: game.x86_64 + .so in same dir)
	//   2) ../thirdparty/ngx/lib/linux_x86_64 (dev build: binary in bin/, .so at repo root)
	String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
	String ngx_shipped = exe_dir;
	String ngx_dev = exe_dir.path_join("../thirdparty/ngx/lib/linux_x86_64").simplify_path();

	// Convert to wchar_t for NGX API
	// On Linux wchar_t is 4 bytes (UTF-32), so widen char-by-char from UTF-8
	auto to_wstring = [](const String &s) -> std::wstring {
		CharString utf8 = s.utf8();
		std::wstring result;
		result.reserve(utf8.length());
		for (int i = 0; i < utf8.length(); i++) {
			result.push_back((wchar_t)(unsigned char)utf8[i]);
		}
		return result;
	};
	static thread_local std::wstring shipped_ws, dev_ws;
	shipped_ws = to_wstring(ngx_shipped);
	dev_ws = to_wstring(ngx_dev);
	const wchar_t *ngx_search_paths[] = {
		shipped_ws.c_str(),
		dev_ws.c_str(),
	};
	print_line(vformat("[NGX DLSS] Searching for DLSS .so in: %s, %s", ngx_shipped, ngx_dev));

	NVSDK_NGX_FeatureCommonInfo feature_info = {};
	memset(&feature_info, 0, sizeof(feature_info));
	feature_info.PathListInfo.Path = ngx_search_paths;
	feature_info.PathListInfo.Length = 2;
	feature_info.LoggingInfo.MinimumLoggingLevel = NVSDK_NGX_LOGGING_LEVEL_VERBOSE;
	feature_info.LoggingInfo.LoggingCallback = _ngx_log_callback;
	feature_info.LoggingInfo.DisableOtherLoggingSinks = false;

	// Get the REAL Vulkan function pointers from the driver, not volk globals.
	// volk defines vkGetInstanceProcAddr as a global variable (function pointer),
	// but libnvsdk_ngx.a (statically linked) resolves the symbol to the variable
	// address and tries to call it as code — crashing on the pointer bytes.
	// dlsym gives us the actual driver function address.
	void *vulkan_lib = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_NOLOAD);
	if (!vulkan_lib) {
		vulkan_lib = dlopen("libvulkan.so", RTLD_NOW | RTLD_NOLOAD);
	}
	ERR_FAIL_COND_V_MSG(!vulkan_lib, false, "[NGX DLSS] Cannot find loaded libvulkan.so for proc addr");

	PFN_vkGetInstanceProcAddr real_vkGetInstanceProcAddr =
			(PFN_vkGetInstanceProcAddr)dlsym(vulkan_lib, "vkGetInstanceProcAddr");
	PFN_vkGetDeviceProcAddr real_vkGetDeviceProcAddr =
			(PFN_vkGetDeviceProcAddr)dlsym(vulkan_lib, "vkGetDeviceProcAddr");
	dlclose(vulkan_lib);

	ERR_FAIL_COND_V_MSG(!real_vkGetInstanceProcAddr || !real_vkGetDeviceProcAddr, false,
			"[NGX DLSS] dlsym failed for Vulkan proc addrs");

	print_verbose(vformat("[NGX DLSS] Real vkGetInstanceProcAddr: %d, volk global: %d",
			(uint64_t)real_vkGetInstanceProcAddr, (uint64_t)vkGetInstanceProcAddr));

	// Use Init_with_ProjectID as the nvpro-samples reference does
	NVSDK_NGX_Result result = NVSDK_NGX_VULKAN_Init_with_ProjectID(
			"a0f57b54-1daf-4934-90ae-c4035c19df04", // Project ID (GUID format)
			NVSDK_NGX_ENGINE_TYPE_CUSTOM,
			"4.7.0",  // Engine version
			L"/tmp/ngx_dlss_logs",
			vk_instance, vk_phys_device, vk_device,
			real_vkGetInstanceProcAddr, real_vkGetDeviceProcAddr,
			&feature_info,
			NVSDK_NGX_Version_API);

	if (NVSDK_NGX_FAILED(result)) {
		ERR_PRINT(vformat("[NGX DLSS] NVSDK_NGX_VULKAN_Init_Ext2 failed: 0x%x", (uint32_t)result));
		return false;
	}

	// Query required extensions (for diagnostics)
	{
		unsigned int inst_count = 0, dev_count = 0;
		const char **inst_exts = nullptr, **dev_exts = nullptr;
		NVSDK_NGX_Result ext_result = NVSDK_NGX_VULKAN_RequiredExtensions(&inst_count, &inst_exts, &dev_count, &dev_exts);
		if (NVSDK_NGX_SUCCEED(ext_result)) {
			print_line(vformat("[NGX DLSS] Required instance extensions: %d", inst_count));
			for (unsigned int i = 0; i < inst_count; i++) {
				print_line(vformat("[NGX DLSS]   Instance: %s", String(inst_exts[i])));
			}
			print_line(vformat("[NGX DLSS] Required device extensions: %d", dev_count));
			for (unsigned int i = 0; i < dev_count; i++) {
				print_line(vformat("[NGX DLSS]   Device: %s", String(dev_exts[i])));
			}
		} else {
			print_line(vformat("[NGX DLSS] RequiredExtensions query failed: 0x%x", (uint32_t)ext_result));
		}
	}

	// Allocate parameters (needed for feature creation/evaluation)
	result = NVSDK_NGX_VULKAN_AllocateParameters(&g_ngx_params);
	if (NVSDK_NGX_FAILED(result)) {
		ERR_PRINT(vformat("[NGX DLSS] AllocateParameters failed: 0x%x", (uint32_t)result));
		NVSDK_NGX_VULKAN_Shutdown1(nullptr);
		return false;
	}

	// Skip capability parameter checks — they're unreliable on Linux.
	// NGX loaded both dlss and dlssd .so files (confirmed via nvngx.log).
	// We'll try creating features at render time and handle errors there.
	g_ngx_dlss_sr_available = true;
	g_ngx_dlss_rr_available = true;

	g_ngx_initialized = true;
	print_line("[NGX DLSS] Initialized successfully. Will attempt DLSS-RR then fall back to SR.");

	return true;
}

static NVSDK_NGX_Resource_VK _ngx_make_resource(RID p_texture_rid, bool p_read_write) {
	VkImageView vk_view = (VkImageView)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_VIEW, p_texture_rid);
	VkImage vk_image = (VkImage)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE, p_texture_rid);
	VkFormat vk_format = (VkFormat)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_TEXTURE_DATA_FORMAT, p_texture_rid);
	RD::TextureFormat tex_fmt = RD::get_singleton()->texture_get_format(p_texture_rid);

	VkImageSubresourceRange subresource = {};
	subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	subresource.baseMipLevel = 0;
	subresource.levelCount = tex_fmt.mipmaps;
	subresource.baseArrayLayer = 0;
	subresource.layerCount = tex_fmt.array_layers;

	return NVSDK_NGX_Create_ImageView_Resource_VK(
			vk_view, vk_image, subresource, vk_format,
			tex_fmt.width, tex_fmt.height, p_read_write);
}

DLSSEffect::DLSSEffect() {
	Vector<String> modes;
	modes.push_back("\n");
	shaders.mvec_decode_shader.initialize(modes, "");
	shaders.mvec_decode_version = shaders.mvec_decode_shader.version_create();
	shaders.mvec_decode_pipeline = RD::get_singleton()->compute_pipeline_create(shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0));
}

DLSSEffect::~DLSSEffect() {
	shaders.mvec_decode_shader.version_free(shaders.mvec_decode_version);
	if (g_ngx_initialized) {
		if (g_ngx_params) {
			NVSDK_NGX_VULKAN_DestroyParameters(g_ngx_params);
			g_ngx_params = nullptr;
		}
		NVSDK_NGX_VULKAN_Shutdown1(nullptr);
		g_ngx_initialized = false;
	}
}

DLSSContext *DLSSEffect::create_context(Size2i p_internal_size, Size2i p_target_size) {
	if (!_ngx_ensure_init()) {
		ERR_PRINT("[NGX DLSS] Cannot create context — NGX init failed");
		return nullptr;
	}

	RendererRD::NGXDLSSContext *ctx = memnew(RendererRD::NGXDLSSContext);
	ctx->render_width = p_internal_size.width;
	ctx->render_height = p_internal_size.height;
	ctx->output_width = p_target_size.width;
	ctx->output_height = p_target_size.height;

	// Determine quality mode based on render/output ratio
	float ratio = (float)p_internal_size.width / (float)p_target_size.width;
	if (ratio >= 0.95f) {
		ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_DLAA;
	} else if (ratio >= 0.66f) {
		ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_MaxQuality;
	} else if (ratio >= 0.5f) {
		ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_Balanced;
	} else if (ratio >= 0.33f) {
		ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_MaxPerf;
	} else {
		ctx->perf_quality = NVSDK_NGX_PerfQuality_Value_UltraPerformance;
	}

	print_line(vformat("[NGX DLSS] Context created: %dx%d -> %dx%d (quality: %d)",
			p_internal_size.width, p_internal_size.height,
			p_target_size.width, p_target_size.height, (int)ctx->perf_quality));

	return ctx;
}

void DLSSEffect::upscale(const DLSSContext::Parameters &p_params) {
	RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_params.context;
	if (!ctx) {
		return;
	}

	// Delay warmup frames
	if (ctx->delay > 0) {
		--ctx->delay;
		return;
	}

	ctx->last_parameters = p_params;
	ctx->last_effect = this;

	// Decode motion vectors (same as Streamline path)
	{
		RD::get_singleton()->draw_command_begin_label("Decode Invalid Motion Vectors");
		UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
		ERR_FAIL_NULL(uniform_set_cache);
		MaterialStorage *material_storage = MaterialStorage::get_singleton();
		ERR_FAIL_NULL(material_storage);

		RD::Uniform u_velocity_image(RD::UNIFORM_TYPE_IMAGE, 0, p_params.velocity);
		RD::Uniform u_depth_texture(RD::UNIFORM_TYPE_TEXTURE, 0, p_params.depth);

		RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
		RID shader = shaders.mvec_decode_shader.version_get_shader(shaders.mvec_decode_version, 0);
		ERR_FAIL_COND(shader.is_null());

		RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, shaders.mvec_decode_pipeline);
		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_velocity_image), 0);
		RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_depth_texture), 1);

		auto texture_format = RD::get_singleton()->texture_get_format(p_params.velocity);

		float push_constants[20];
		push_constants[0] = texture_format.width;
		push_constants[1] = texture_format.height;
		push_constants[2] = 0.0f;
		push_constants[3] = 0.0f;
		memcpy(push_constants + 4, &p_params.reprojection.columns[0].x, sizeof(float) * 16);
		RD::get_singleton()->compute_list_set_push_constant(compute_list, push_constants, sizeof(push_constants));

		RD::get_singleton()->compute_list_dispatch_threads(compute_list, texture_format.width, texture_format.height, 1);
		RD::get_singleton()->compute_list_add_barrier(compute_list);

		RD::get_singleton()->compute_list_end();
		RD::get_singleton()->draw_command_end_label();
	}

	// Inject NGX DLSS into the render graph via callback
	RD::CallbackResource res[8];
	int num_resources = 0;
	res[num_resources++].rid = p_params.color;
	res[num_resources++].rid = p_params.output;
	res[num_resources++].rid = p_params.depth;
	res[num_resources++].rid = p_params.velocity;

	if (p_params.dlss_rr) {
		if (p_params.dlss_rr_diffuse_albedo.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_diffuse_albedo;
		}
		if (p_params.dlss_rr_specular_albedo.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_specular_albedo;
		}
		if (p_params.dlss_rr_normal_roughness.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_normal_roughness;
		}
		if (p_params.dlss_rr_specular_hit_dist.is_valid()) {
			res[num_resources++].rid = p_params.dlss_rr_specular_hit_dist;
		}
	}

	for (int i = 0; i < num_resources; i++) {
		res[i].usage = RD::CALLBACK_RESOURCE_USAGE_TEXTURE_SAMPLE;
	}
	RD::get_singleton()->driver_callback_add((RDD::DriverCallback)DLSSEffect::_upscale_internal_graph_callback, p_params.context, VectorView<RD::CallbackResource>(res, num_resources));
}

void DLSSEffect::_upscale_internal(RDD::CommandBufferID cmdid, const DLSSContext::Parameters &p_params) {
	RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_params.context;

	VkCommandBuffer vk_cmd = (VkCommandBuffer)RD::get_singleton()->get_device_driver()->command_buffer_get_native_handle(cmdid);
	VkDevice vk_device = (VkDevice)RD::get_singleton()->get_driver_resource(RD::DriverResource::DRIVER_RESOURCE_LOGICAL_DEVICE, RID(), 0);

	bool use_dlss_rr = p_params.dlss_rr && g_ngx_dlss_rr_available;

	// Ray Reconstruction and Super Resolution are separate NGX features with
	// separate handles. Toggling between them at runtime (a settings change) must
	// destroy the existing one, or creation is skipped as already-done and the
	// evaluate branch for the newly requested mode dereferences a null handle.
	if (ctx->feature_created && ctx->feature_is_rr != use_dlss_rr) {
		ctx->release_feature();
	}

	// Create NGX feature on first use
	if (!ctx->feature_created) {
		NVSDK_NGX_Result result;

		// Try DLSS-RR (Ray Reconstruction / denoiser) first if requested
		if (use_dlss_rr && g_ngx_dlss_rr_available) {
			NVSDK_NGX_DLSSD_Create_Params dlssd_create = {};
			dlssd_create.InWidth = ctx->render_width;
			dlssd_create.InHeight = ctx->render_height;
			dlssd_create.InTargetWidth = ctx->output_width;
			dlssd_create.InTargetHeight = ctx->output_height;
			dlssd_create.InPerfQualityValue = ctx->perf_quality;
			// No AutoExposure: Godot's auto_exposure handles it via tonemapping.
			// Double-exposure (DLSS internal + Godot) causes progressive white blowout.
			dlssd_create.InFeatureCreateFlags =
					NVSDK_NGX_DLSS_Feature_Flags_IsHDR |
					NVSDK_NGX_DLSS_Feature_Flags_MVLowRes |
					NVSDK_NGX_DLSS_Feature_Flags_DepthInverted;

			result = NGX_VULKAN_CREATE_DLSSD_EXT1(
					VK_NULL_HANDLE, vk_cmd, 1, 1,
					&ctx->dlss_rr_handle, g_ngx_params, &dlssd_create);

			if (NVSDK_NGX_SUCCEED(result)) {
				ctx->feature_created = true;
				ctx->feature_is_rr = true;
				print_line("[NGX DLSS] DLSS-RR feature created successfully");
			} else {
				WARN_PRINT(vformat("[NGX DLSS] DLSS-RR creation failed (0x%x), falling back to DLSS-SR", (uint32_t)result));
				ctx->dlss_rr_handle = nullptr;
				use_dlss_rr = false;
			}
		}

		// Fall back to DLSS-SR (Super Resolution / upscaler only)
		if (!ctx->feature_created && g_ngx_dlss_sr_available) {
			NVSDK_NGX_DLSS_Create_Params dlss_create = {};
			dlss_create.Feature.InWidth = ctx->render_width;
			dlss_create.Feature.InHeight = ctx->render_height;
			dlss_create.Feature.InTargetWidth = ctx->output_width;
			dlss_create.Feature.InTargetHeight = ctx->output_height;
			dlss_create.Feature.InPerfQualityValue = ctx->perf_quality;
			dlss_create.InFeatureCreateFlags =
					NVSDK_NGX_DLSS_Feature_Flags_IsHDR |
					NVSDK_NGX_DLSS_Feature_Flags_MVLowRes |
					NVSDK_NGX_DLSS_Feature_Flags_DepthInverted;

			result = NGX_VULKAN_CREATE_DLSS_EXT(
					vk_cmd, 1, 1,
					&ctx->dlss_sr_handle, g_ngx_params, &dlss_create);

			if (NVSDK_NGX_FAILED(result)) {
				ERR_PRINT(vformat("[NGX DLSS] DLSS-SR feature creation failed: 0x%x", (uint32_t)result));
				return;
			}
			ctx->feature_created = true;
			ctx->feature_is_rr = false;
			use_dlss_rr = false;
			print_line("[NGX DLSS] DLSS-SR feature created successfully");
		}

		if (!ctx->feature_created) {
			return;
		}
	}

	// Evaluate DLSS
	if (use_dlss_rr && ctx->dlss_rr_handle) {
		// DLSS Ray Reconstruction evaluation
		NVSDK_NGX_Resource_VK color_res = _ngx_make_resource(p_params.color, false);
		NVSDK_NGX_Resource_VK output_res = _ngx_make_resource(p_params.output, true);
		NVSDK_NGX_Resource_VK depth_res = _ngx_make_resource(p_params.depth, false);
		NVSDK_NGX_Resource_VK mvec_res = _ngx_make_resource(p_params.velocity, false);

		NVSDK_NGX_VK_DLSSD_Eval_Params eval = {};
		memset(&eval, 0, sizeof(eval));
		eval.pInColor = &color_res;
		eval.pInOutput = &output_res;
		eval.pInDepth = &depth_res;
		eval.pInMotionVectors = &mvec_res;
		eval.InJitterOffsetX = p_params.jitter.x;
		eval.InJitterOffsetY = p_params.jitter.y;
		eval.InRenderSubrectDimensions.Width = ctx->render_width;
		eval.InRenderSubrectDimensions.Height = ctx->render_height;
		eval.InReset = p_params.reset_accumulation ? 1 : 0;
		// Motion vectors are in UV space (0-1), DLSS expects pixel space — scale by render resolution
		eval.InMVScaleX = (float)ctx->render_width;
		eval.InMVScaleY = (float)ctx->render_height;
		eval.InFrameTimeDeltaInMsec = p_params.delta_time * 1000.0f;

		// G-buffer inputs for DLSS-RR (stack-allocated, valid for duration of evaluate call)
		NVSDK_NGX_Resource_VK diffuse_res, specular_res, normal_res, hit_dist_res;
		if (p_params.dlss_rr_diffuse_albedo.is_valid()) {
			diffuse_res = _ngx_make_resource(p_params.dlss_rr_diffuse_albedo, false);
			eval.pInDiffuseAlbedo = &diffuse_res;
		}
		if (p_params.dlss_rr_specular_albedo.is_valid()) {
			specular_res = _ngx_make_resource(p_params.dlss_rr_specular_albedo, false);
			eval.pInSpecularAlbedo = &specular_res;
		}
		if (p_params.dlss_rr_normal_roughness.is_valid()) {
			normal_res = _ngx_make_resource(p_params.dlss_rr_normal_roughness, false);
			eval.pInNormals = &normal_res;
			eval.pInRoughness = &normal_res; // Packed mode: roughness in W channel of normals
		}
		if (p_params.dlss_rr_specular_hit_dist.is_valid()) {
			hit_dist_res = _ngx_make_resource(p_params.dlss_rr_specular_hit_dist, false);
			eval.pInSpecularHitDistance = &hit_dist_res;
		}

		// View/projection matrices for DLSS-RR. Stored on the context, NOT on the
		// stack -- see the declaration: NGX keeps the pointer and reads it later.
		Transform3D view_matrix = p_params.cam_transform.affine_inverse();
		Projection world_to_view(view_matrix);
		for (int i = 0; i < 4; i++) {
			ctx->world_to_view_matrix[i * 4 + 0] = world_to_view.columns[0][i];
			ctx->world_to_view_matrix[i * 4 + 1] = world_to_view.columns[1][i];
			ctx->world_to_view_matrix[i * 4 + 2] = world_to_view.columns[2][i];
			ctx->world_to_view_matrix[i * 4 + 3] = world_to_view.columns[3][i];
		}
		for (int i = 0; i < 4; i++) {
			ctx->view_to_clip_matrix[i * 4 + 0] = p_params.cam_projection.columns[0][i];
			ctx->view_to_clip_matrix[i * 4 + 1] = p_params.cam_projection.columns[1][i];
			ctx->view_to_clip_matrix[i * 4 + 2] = p_params.cam_projection.columns[2][i];
			ctx->view_to_clip_matrix[i * 4 + 3] = p_params.cam_projection.columns[3][i];
		}
		eval.pInWorldToViewMatrix = ctx->world_to_view_matrix;
		eval.pInViewToClipMatrix = ctx->view_to_clip_matrix;

		NVSDK_NGX_Result result = NGX_VULKAN_EVALUATE_DLSSD_EXT(vk_cmd, ctx->dlss_rr_handle, g_ngx_params, &eval);

		if (NVSDK_NGX_FAILED(result)) {
			ERR_PRINT(vformat("[NGX DLSS] DLSS-RR evaluate failed: 0x%x", (uint32_t)result));
		}
	} else if (ctx->dlss_sr_handle) {
		// DLSS Super Resolution evaluation
		NVSDK_NGX_Resource_VK color_res = _ngx_make_resource(p_params.color, false);
		NVSDK_NGX_Resource_VK output_res = _ngx_make_resource(p_params.output, true);
		NVSDK_NGX_Resource_VK depth_res = _ngx_make_resource(p_params.depth, false);
		NVSDK_NGX_Resource_VK mvec_res = _ngx_make_resource(p_params.velocity, false);

		NVSDK_NGX_VK_DLSS_Eval_Params eval = {};
		memset(&eval, 0, sizeof(eval));
		eval.Feature.pInColor = &color_res;
		eval.Feature.pInOutput = &output_res;
		eval.Feature.InSharpness = p_params.sharpness;
		eval.pInDepth = &depth_res;
		eval.pInMotionVectors = &mvec_res;
		eval.InJitterOffsetX = p_params.jitter.x;
		eval.InJitterOffsetY = p_params.jitter.y;
		eval.InRenderSubrectDimensions.Width = ctx->render_width;
		eval.InRenderSubrectDimensions.Height = ctx->render_height;
		eval.InReset = p_params.reset_accumulation ? 1 : 0;
		// Motion vectors are in UV space (0-1), DLSS expects pixel space — scale by render resolution
		eval.InMVScaleX = (float)ctx->render_width;
		eval.InMVScaleY = (float)ctx->render_height;

		// Defensive: the mode-change path above should always have recreated the
		// feature, but never hand NGX a null handle.
		ERR_FAIL_NULL_MSG(ctx->dlss_sr_handle, "[NGX DLSS] SR evaluate with no SR feature.");
		NVSDK_NGX_Result result = NGX_VULKAN_EVALUATE_DLSS_EXT(vk_cmd, ctx->dlss_sr_handle, g_ngx_params, &eval);
		if (NVSDK_NGX_FAILED(result)) {
			ERR_PRINT(vformat("[NGX DLSS] DLSS-SR evaluate failed: 0x%x", (uint32_t)result));
		}
	}
}

void RendererRD::DLSSEffect::_upscale_internal_graph_callback(RenderingDeviceDriver *p_driver, RDD::CommandBufferID p_command_buffer, void *p_userdata) {
	RendererRD::NGXDLSSContext *self = (RendererRD::NGXDLSSContext *)p_userdata;
	self->last_effect->_upscale_internal(p_command_buffer, self->last_parameters);
}

bool DLSSEffect::is_ready(DLSSContext *p_context) {
	RendererRD::NGXDLSSContext *ctx = (RendererRD::NGXDLSSContext *)p_context;
	if (!ctx || !g_ngx_initialized) {
		return false;
	}
	if (ctx->delay > 0) {
		return false;
	}
	return true;
}

#else
// ============================================================================
// No DLSS backend available — stub implementation
// ============================================================================
DLSSEffect::DLSSEffect() {}
DLSSEffect::~DLSSEffect() {}
DLSSContext *DLSSEffect::create_context(Size2i p_internal_size, Size2i p_target_size) {
	return nullptr;
}
void DLSSEffect::upscale(const DLSSContext::Parameters &p_params) {}
bool DLSSEffect::is_ready(DLSSContext *p_context) {
	return false;
}
void DLSSEffect::_upscale_internal(RDD::CommandBufferID cmdid, const DLSSContext::Parameters &p_params) {}
void DLSSEffect::_upscale_internal_graph_callback(RenderingDeviceDriver *p_driver, RDD::CommandBufferID p_command_buffer, void *p_userdata) {}
#endif